# OCAS **Object Content Addressable Store** — self-describing CAS with JSON Schema typed nodes. Every node has a typed payload: its `type` field is the hash of a JSON Schema that describes the payload shape. Hashes are 13-character Crockford Base32 strings derived from XXH64 over deterministic CBOR encoding. Payloads can reference other nodes via `format: "cas_ref"` fields, forming a traversable DAG. ## Install ```bash pnpm add -g @ocas/cli ``` The store is auto-created and bootstrapped on first use — no `init` command needed. > Requires Node.js >= 22.5.0 (uses built-in node:sqlite) ## Quick Start ```bash # Register a schema (schemas are just nodes typed by the meta-schema) echo '{ "type": "object", "properties": { "title": { "type": "string" }, "done": { "type": "boolean" } }, "required": ["title", "done"], "additionalProperties": false }' | ocas put @ocas/schema -p # → { "type": "...", "value": "1ABC2DEF34567" } # Give it a friendly name ocas var set @todo/schema 1ABC2DEF34567 # Store a todo item echo '{ "title": "Buy milk", "done": false }' | ocas put @todo/schema -p # → { "type": "...", "value": "9XYZ8WVU76543" } # Retrieve it ocas get 9XYZ8WVU76543 # Verify integrity + schema validation ocas verify 9XYZ8WVU76543 ``` ## Envelope Format Every command outputs a `{ type, value }` JSON envelope. `type` is the hash of the result schema, `value` is the payload. This makes output self-describing and composable: ```bash # Pipe any command's output into render ocas put @ocas/schema schema.json | ocas render -p # Or use the shorthand -r flag ocas get 9XYZ8WVU76543 -r # Extract values with jq ocas list --type @ocas/schema | jq -r '.value[].hash' ``` `render` is the only command that emits raw text instead of an envelope. ## Commands ### Store & Retrieve ```bash ocas put # store a node, returns its hash ocas put -p # read payload from stdin ocas get # retrieve a node ocas has # check if a node exists ocas hash # compute hash without storing ocas verify # integrity check + schema validation ``` ### Graph Traversal ```bash ocas refs # list direct cas_ref edges ocas walk # recursive DAG traversal ocas walk --format tree # tree-view output ``` ### Listing & Querying ```bash ocas list --type # list nodes by type ocas list-schema # list all schemas ocas list-meta # list meta-schema hashes ``` All list commands support sorting and pagination: ```bash ocas list --type todo/schema --sort updated --desc --limit 20 ocas list --type todo/schema --offset 20 --limit 20 # page 2 ocas list-schema --sort created --limit 50 ``` ### Variables Variables are mutable pointers to immutable data — like git branches pointing to commits. Names must follow `@scope/name` format (e.g. `@myapp/config`). The `@ocas/*` scope is reserved. ```bash ocas var set @myapp/config # bind a name to a hash ocas var get @myapp/config # look up current binding ocas var delete @myapp/config # remove binding ocas var list [prefix] # list variables (prefix filter) ocas var list @myapp/ --tag env:prod # filter by scope prefix and tag ocas var history @myapp/config # show last 10 values (LRU) ``` **Tags & labels** — attach metadata to variables: ```bash ocas var set @myapp/config --tag env:prod --tag pinned ocas var tag @myapp/config --schema status:active # add tag ocas var tag @myapp/config --schema :status # remove tag ``` Any command that takes a hash also accepts a variable name: ```bash ocas get @myapp/config # resolves to the bound hash ocas put @ocas/schema s.json # @ocas/schema is a builtin variable ``` ### Templates & Rendering Bind a [LiquidJS](https://liquidjs.com/) template to a schema, then render nodes of that type: ```bash # Set a template ocas template set --inline "Todo: {{ payload.title }} [{{ payload.done }}]" # Render a node (uses the template for its type, falls back to YAML) ocas render # Render from a pipe (any envelope) ocas gc | ocas render -p # Inline render shorthand ocas get -r ``` Render options for recursive reference expansion: ```bash ocas render --resolution 3 # max recursion depth ocas render --decay 0.5 # depth decay factor ocas render --epsilon 0.01 # cutoff threshold ``` ### Garbage Collection ```bash ocas gc # collect unreachable nodes ocas gc | ocas render -p # human-readable stats ``` Nodes reachable from any variable binding are kept; everything else is swept. ## Global Flags | Flag | Description | |------|-------------| | `--home ` | Store directory (default: `$OCAS_HOME` or `~/.ocas`) | | `--var-db ` | Variable database path | | `--json` | Compact single-line JSON output | | `--pipe`, `-p` | Read from stdin | | `--render`, `-r` | Render output inline | | `--sort created\|updated` | Sort key for list commands (default: `created`) | | `--limit ` | Max results (default: 100) | | `--offset ` | Skip first N results (default: 0) | | `--desc` | Sort descending | ## Architecture ``` ┌───────────┐ │ @ocas/cli │ CLI interface └─────┬─────┘ │ ┌───────────┐ │ @ocas/fs │ Filesystem store (CBOR + SQLite) └─────┬─────┘ │ ┌────────────┐ │ @ocas/core │ Hashing, schemas, validation, bootstrap └────────────┘ ``` ## Using as a Library ```bash pnpm add @ocas/core # in-memory store pnpm add @ocas/core @ocas/fs # + filesystem persistence ``` ```typescript import { bootstrap, createMemoryStore, putSchema } from "@ocas/core"; const store = createMemoryStore(); await bootstrap(store); const typeHash = await putSchema(store, { type: "object", properties: { message: { type: "string" } }, required: ["message"], additionalProperties: false, }); const hash = await store.put(typeHash, { message: "hello" }); const node = store.get(hash); ``` For filesystem persistence, use `@ocas/fs`: ```typescript import { openStore } from "@ocas/fs"; const store = await openStore("/path/to/store"); ``` See individual package READMEs for full API docs: [`@ocas/core`](packages/core/README.md) · [`@ocas/fs`](packages/fs/README.md) · [`@ocas/cli`](packages/cli/README.md) ## Development ```bash git clone && cd ocas pnpm install pnpm run build # tsc --build pnpm test # run all tests pnpm run check # biome lint pnpm run format # biome format ``` ## License [MIT](LICENSE)