src/Staging.bend

ber-core-store@0.1.2.0 · 109 lines · 0 definitions

1import Base
2import ber-core-store@0.1.2.0/src/LogicalKey.bend as LogicalKey
3import ber-core-store@0.1.2.0/src/ContentObject.bend as ContentObject
4import ber-core-store@0.1.2.0/src/JsonAdapter.bend as JsonAdapter
5import ber-core-store@0.1.2.0/src/Value.bend as Value
6import ber-core-store@0.1.2.0/src/Store.bend as Store
7import bend-kit-json@0.3.0.0/json.bend as Json
8
9# Uncommitted working set. Staged values live at stage/{session}/{ns}/{id};
10# the per-session key manifest lives at stage-index/{session} as a canonical
11# JSON string list. Point operations only — no prefix scan anywhere.
12# Kit is named for the Val TYPE only; operations go through JsonAdapter.
13# Effects are Store Op actions composed in do-blocks.
14
15# Addresses the per-session staged-key manifest.
16def stage_index_key(+session_identifier: String) -> String:
17  "stage-index/" ++ session_identifier
18
19# Encodes the manifest as a canonical JSON string list.
20def serialize_key_list(staged_keys: List<&2, String>) -> String:
21  JsonAdapter.encode_canonical(JsonAdapter.make_arr(ContentObject.encode_each_reference(staged_keys)))
22
23# Treats a failed decode as an empty manifest.
24def index_from_strings(strings_maybe: Maybe<&2, List<&2, String>>) -> List<&2, String>:
25  match strings_maybe:
26    case None{}:
27      Nil{}
28    case Some{key_texts}:
29      key_texts
30
31# Unwraps the manifest array, then decodes.
32def index_from_document(items_maybe: Maybe<&2, List<&2, Json.Val>>) -> List<&2, String>:
33  match items_maybe:
34    case None{}:
35      Nil{}
36    case Some{json_items}:
37      index_from_strings(ContentObject.extract_each_string(json_items))
38
39# Parses stored manifest text; malformed reads as empty.
40def index_from_text(parse_result: Maybe<&2, Json.Val>) -> List<&2, String>:
41  match parse_result:
42    case None{}:
43      Nil{}
44    case Some{document}:
45      index_from_document(JsonAdapter.as_arr(document))
46
47# Loads the manifest; missing storage reads as empty.
48def parse_stage_index(stored_maybe: Maybe<&2, String>) -> List<&2, String>:
49  match stored_maybe:
50    case None{}:
51      Nil{}
52    case Some{stored_text}:
53      index_from_text(JsonAdapter.parse_text(stored_text))
54
55# True short-circuits the membership scan.
56def decide_contains(head_matches: Bool, recursive_result: Bool) -> Bool:
57  match head_matches:
58    case True{}:
59      True{}
60    case False{}:
61      recursive_result
62
63# Scans the manifest for the target key.
64def contains_staged_key(+target_key: String, staged_keys: List<&2, String>) -> Bool:
65  match staged_keys:
66    case Nil{}:
67      False{}
68    case Con{head_key, remaining_keys}:
69      decide_contains(String.eq(head_key, target_key), contains_staged_key(target_key, remaining_keys))
70
71# Prepends unless already present.
72def prepend_unless_present(already_present: Bool, target_key: String, staged_keys: List<&2, String>) -> List<&2, String>:
73  match already_present:
74    case True{}:
75      staged_keys
76    case False{}:
77      Con{target_key, staged_keys}
78
79# Adds the key unless the manifest has it.
80def add_key_unless_present(+target_key: String, +staged_keys: List<&2, String>) -> List<&2, String>:
81  prepend_unless_present(contains_staged_key(target_key, staged_keys), target_key, staged_keys)
82
83# Loads the session manifest.
84def load_stage_index(+session_identifier: String) -> Store.Op<&2, List<&2, String>>:
85  do Store.Op<&2, List<&2, String>>:
86    stored_maybe : Maybe<&2, String> <- Store.oget(stage_index_key(session_identifier))
87    return parse_stage_index(stored_maybe)
88
89# Stores one staged value and records its key.
90def record_stage_entry(+session_identifier: String, +namespace_name: String, +record_identifier: String, value_hash: String) -> Store.Op<&2, LogicalKey.LogicalKey>:
91  do Store.Op<&2, LogicalKey.LogicalKey>:
92    +full_key_text : String = LogicalKey.encode_logical_key(LogicalKey.Make{namespace_name, record_identifier})
93    current_index : List<&2, String> <- load_stage_index(session_identifier)
94    stored_unit : Unit <- Store.oput(LogicalKey.build_stage_storage_key(session_identifier, namespace_name, record_identifier), value_hash)
95    saved_unit : Unit <- Store.oput(stage_index_key(session_identifier), serialize_key_list(add_key_unless_present(full_key_text, current_index)))
96    return LogicalKey.Make{namespace_name, record_identifier}
97
98# Stages a value for later commit.
99def put_record(+session_identifier: String, +namespace_name: String, +record_identifier: String, value: Value.Value) -> Store.Op<&2, LogicalKey.LogicalKey>:
100  do Store.Op<&2, LogicalKey.LogicalKey>:
101    +value_hash : String <- ContentObject.store_content_object(value, Nil{})
102    staged_key : LogicalKey.LogicalKey <- record_stage_entry(session_identifier, namespace_name, record_identifier, value_hash)
103    return staged_key
104
105# Stages a tombstone for later commit.
106def delete_record(+session_identifier: String, +namespace_name: String, +record_identifier: String) -> Store.Op<&2, LogicalKey.LogicalKey>:
107  do Store.Op<&2, LogicalKey.LogicalKey>:
108    staged_key : LogicalKey.LogicalKey <- record_stage_entry(session_identifier, namespace_name, record_identifier, "TOMBSTONE")
109    return staged_key