src/ContentObject.bend source
src/ContentObject.bend on the hub · documented module
import Baseimport ./ContentHash.bend as ContentHashimport ./LogicalKey.bend as LogicalKeyimport ./JsonAdapter.bend as JsonAdapterimport ./Value.bend as Valueimport ./Store.bend as Storeimport bend-kit-json@0.3.0.0/json.bend as Json# Content-addressed objects persisted as canonical JSON: equal content gives# byte-identical text, so the SHA-256 of the text is a stable address.# Payloads are multi-type Values (text, document, blob); references stay# string lists. Kit and Value are named for TYPES only; all operations go# through JsonAdapter and Value. Effects are Store Op actions in do-blocks.# Addressable payload with content hash and references.type ContentObject is Data: Make{content_hash: String, payload: Value.Value, references: List<&2, String>}# Encodes each reference string as a JSON string.def encode_each_reference(references: List<&2, String>) -> List<&2, Json.Val>: match references: case Nil{}: Nil{} case Con{head_reference, remaining_references}: Con{JsonAdapter.make_str(head_reference), encode_each_reference(remaining_references)}# Builds the payload/references field list.def encode_reference_list(payload: Value.Value, references: List<&2, String>) -> List<&2, Sigma<&2, &2, String, _ => Json.Val>>: Con{JsonAdapter.make_kv("payload", Value.value_to_json(payload)), Con{JsonAdapter.make_kv("references", JsonAdapter.make_arr(encode_each_reference(references))), Nil{}}}# Canonical text of a content object; the hashed bytes.def canonical_content(payload: Value.Value, references: List<&2, String>) -> String: JsonAdapter.encode_canonical(JsonAdapter.make_obj(encode_reference_list(payload, references)))# Persists the text under its hash and returns the hash.def finish_store(+content_hash: String, +canonical_text: String) -> Store.Op<&2, String>: do Store.Op<&2, String>: stored_unit : Unit <- Store.oput(LogicalKey.build_object_storage_key(content_hash), canonical_text) return content_hash# Hashes the text, then stores it.def wrap_store(+canonical_text: String) -> Store.Op<&2, String>: finish_store(ContentHash.compute_sha256_hex(canonical_text), canonical_text)# SHA-256 of the canonical text; the address consumers store and load by.def store_content_object(payload: Value.Value, references: List<&2, String>) -> Store.Op<&2, String>: wrap_store(canonical_content(payload, references))# Conses decoded text onto the tail, failing closed on None.def prepend_tail_text(head_text: String, parsed_tail: Maybe<&2, List<&2, String>>) -> Maybe<&2, List<&2, String>>: match parsed_tail: case None{}: None{} case Some{tail_texts}: Some{Con{head_text, tail_texts}}# Unwraps the head string, then prepends.def prepend_string_value(head_maybe: Maybe<&2, String>, parsed_tail: Maybe<&2, List<&2, String>>) -> Maybe<&2, List<&2, String>>: match head_maybe: case None{}: None{} case Some{head_text}: prepend_tail_text(head_text, parsed_tail)# Decodes one JSON array item as a string.def prepend_string(head_item: Json.Val, parsed_tail: Maybe<&2, List<&2, String>>) -> Maybe<&2, List<&2, String>>: prepend_string_value(JsonAdapter.as_str(head_item), parsed_tail)# Decodes a JSON string array; None on any non-string.def extract_each_string(json_items: List<&2, Json.Val>) -> Maybe<&2, List<&2, String>>: match json_items: case Nil{}: Some{Nil{}} case Con{head_item, remaining_items}: prepend_string(head_item, extract_each_string(remaining_items))# Reassembles the object once references decode.def wrap_reference_texts(content_hash: String, payload_value: Value.Value, texts_maybe: Maybe<&2, List<&2, String>>) -> Maybe<&2, ContentObject>: match texts_maybe: case None{}: None{} case Some{reference_texts}: Some{Make{content_hash, payload_value, reference_texts}}# Unwraps the references array, then reassembles.def finish_object(content_hash: String, payload_value: Value.Value, references_maybe: Maybe<&2, List<&2, Json.Val>>) -> Maybe<&2, ContentObject>: match references_maybe: case None{}: None{} case Some{reference_items}: wrap_reference_texts(content_hash, payload_value, extract_each_string(reference_items))# Unwraps the references field, then finishes.def extract_with_references(content_hash: String, payload_value: Value.Value, references_maybe: Maybe<&2, Json.Val>) -> Maybe<&2, ContentObject>: match references_maybe: case None{}: None{} case Some{references_json}: finish_object(content_hash, payload_value, JsonAdapter.as_arr(references_json))# Unwraps the payload value, then continues.def extract_with_payload_value(content_hash: String, payload_value_maybe: Maybe<&2, Value.Value>, references_maybe: Maybe<&2, Json.Val>) -> Maybe<&2, ContentObject>: match payload_value_maybe: case None{}: None{} case Some{payload_value}: extract_with_references(content_hash, payload_value, references_maybe)# Unwraps the payload field, then continues.def extract_with_payload(content_hash: String, +document: Json.Val, payload_maybe: Maybe<&2, Json.Val>) -> Maybe<&2, ContentObject>: match payload_maybe: case None{}: None{} case Some{payload_json}: extract_with_payload_value(content_hash, Value.value_from_json(payload_json), JsonAdapter.get_field(document, "references"))# Builds the object from parsed JSON; None on malformed input.def object_from_parse_result(content_hash: String, parse_result: Maybe<&2, Json.Val>) -> Maybe<&2, ContentObject>: match parse_result: case None{}: None{} case Some{+document}: extract_with_payload(content_hash, document, JsonAdapter.get_field(document, "payload"))# Parses stored canonical text into an object.def parse_canonical_object(content_hash: String, stored_text: String) -> Maybe<&2, ContentObject>: object_from_parse_result(content_hash, JsonAdapter.parse_text(stored_text))# Parses loaded text; missing storage reads as None.def parse_loaded_object(content_hash: String, stored_maybe: Maybe<&2, String>) -> Maybe<&2, ContentObject>: match stored_maybe: case None{}: None{} case Some{stored_text}: parse_canonical_object(content_hash, stored_text)# None on missing or malformed stored text; never returns a corrupt object.def load_content_object(+content_hash: String) -> Store.Op<&2, Maybe<&2, ContentObject>>: do Store.Op<&2, Maybe<&2, ContentObject>>: stored_maybe : Maybe<&2, String> <- Store.oget(LogicalKey.build_object_storage_key(content_hash)) return parse_loaded_object(content_hash, stored_maybe)# Projects the payload out of an object.def object_payload_of(content_object: ContentObject) -> Value.Value: match content_object: case Make{content_hash, payload, references}: payload