src/Staging.bend source
src/Staging.bend on the hub · documented module
import Baseimport ./LogicalKey.bend as LogicalKeyimport ./ContentObject.bend as ContentObjectimport ./JsonAdapter.bend as JsonAdapterimport ./Value.bend as Valueimport ./Store.bend as Storeimport bend-kit-json@0.3.0.0/json.bend as Json# Uncommitted working set. Staged values live at stage/{session}/{ns}/{id};# the per-session key manifest lives at stage-index/{session} as a canonical# JSON string list. Point operations only — no prefix scan anywhere.# Kit is named for the Val TYPE only; operations go through JsonAdapter.# Effects are Store Op actions composed in do-blocks.# Addresses the per-session staged-key manifest.def stage_index_key(+session_identifier: String) -> String: "stage-index/" ++ session_identifier# Encodes the manifest as a canonical JSON string list.def serialize_key_list(staged_keys: List<&2, String>) -> String: JsonAdapter.encode_canonical(JsonAdapter.make_arr(ContentObject.encode_each_reference(staged_keys)))# Treats a failed decode as an empty manifest.def index_from_strings(strings_maybe: Maybe<&2, List<&2, String>>) -> List<&2, String>: match strings_maybe: case None{}: Nil{} case Some{key_texts}: key_texts# Unwraps the manifest array, then decodes.def index_from_document(items_maybe: Maybe<&2, List<&2, Json.Val>>) -> List<&2, String>: match items_maybe: case None{}: Nil{} case Some{json_items}: index_from_strings(ContentObject.extract_each_string(json_items))# Parses stored manifest text; malformed reads as empty.def index_from_text(parse_result: Maybe<&2, Json.Val>) -> List<&2, String>: match parse_result: case None{}: Nil{} case Some{document}: index_from_document(JsonAdapter.as_arr(document))# Loads the manifest; missing storage reads as empty.def parse_stage_index(stored_maybe: Maybe<&2, String>) -> List<&2, String>: match stored_maybe: case None{}: Nil{} case Some{stored_text}: index_from_text(JsonAdapter.parse_text(stored_text))# True short-circuits the membership scan.def decide_contains(head_matches: Bool, recursive_result: Bool) -> Bool: match head_matches: case True{}: True{} case False{}: recursive_result# Scans the manifest for the target key.def contains_staged_key(+target_key: String, staged_keys: List<&2, String>) -> Bool: match staged_keys: case Nil{}: False{} case Con{head_key, remaining_keys}: decide_contains(String.eq(head_key, target_key), contains_staged_key(target_key, remaining_keys))# Prepends unless already present.def prepend_unless_present(already_present: Bool, target_key: String, staged_keys: List<&2, String>) -> List<&2, String>: match already_present: case True{}: staged_keys case False{}: Con{target_key, staged_keys}# Adds the key unless the manifest has it.def add_key_unless_present(+target_key: String, +staged_keys: List<&2, String>) -> List<&2, String>: prepend_unless_present(contains_staged_key(target_key, staged_keys), target_key, staged_keys)# Loads the session manifest.def load_stage_index(+session_identifier: String) -> Store.Op<&2, List<&2, String>>: do Store.Op<&2, List<&2, String>>: stored_maybe : Maybe<&2, String> <- Store.oget(stage_index_key(session_identifier)) return parse_stage_index(stored_maybe)# Stores one staged value and records its key.def record_stage_entry(+session_identifier: String, +namespace_name: String, +record_identifier: String, value_hash: String) -> Store.Op<&2, LogicalKey.LogicalKey>: do Store.Op<&2, LogicalKey.LogicalKey>: +full_key_text : String = LogicalKey.encode_logical_key(LogicalKey.Make{namespace_name, record_identifier}) current_index : List<&2, String> <- load_stage_index(session_identifier) stored_unit : Unit <- Store.oput(LogicalKey.build_stage_storage_key(session_identifier, namespace_name, record_identifier), value_hash) saved_unit : Unit <- Store.oput(stage_index_key(session_identifier), serialize_key_list(add_key_unless_present(full_key_text, current_index))) return LogicalKey.Make{namespace_name, record_identifier}# Stages a value for later commit.def put_record(+session_identifier: String, +namespace_name: String, +record_identifier: String, value: Value.Value) -> Store.Op<&2, LogicalKey.LogicalKey>: do Store.Op<&2, LogicalKey.LogicalKey>: +value_hash : String <- ContentObject.store_content_object(value, Nil{}) staged_key : LogicalKey.LogicalKey <- record_stage_entry(session_identifier, namespace_name, record_identifier, value_hash) return staged_key# Stages a tombstone for later commit.def delete_record(+session_identifier: String, +namespace_name: String, +record_identifier: String) -> Store.Op<&2, LogicalKey.LogicalKey>: do Store.Op<&2, LogicalKey.LogicalKey>: staged_key : LogicalKey.LogicalKey <- record_stage_entry(session_identifier, namespace_name, record_identifier, "TOMBSTONE") return staged_key