import Base import ./LogicalKey.bend as LogicalKey import ./ContentObject.bend as ContentObject import ./JsonAdapter.bend as JsonAdapter import ./Value.bend as Value import ./Store.bend as Store import 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