Integrates LiquidJS as the template engine for CAS node rendering with
custom {% render %} tag supporting recursive rendering with resolution
decay. Templates are discovered via variables under
@ucas/template/text/<type-hash>. When ucas render <hash> is invoked,
the system queries for a registered template; if found, uses LiquidJS;
otherwise falls back to Phase 3's default YAML renderer.
Key features:
- Custom {% render %} tag with recursive CAS node rendering
- Decay priority chain: template decay > CLI --decay > default 0.5
- Context variables: resolution, epsilon, hash, payload, type, timestamp
- Graceful fallback: No template → YAML rendering (Phase 3)
- Zero breaking changes: All Phase 3 tests still pass
- Template discovery via @ucas/template/text/<type-hash> variables
Implementation:
- New file: packages/json-cas/src/liquid-render.ts — LiquidJS integration
- Modified: packages/json-cas/src/render.ts — Template lookup + YAML fallback
- New file: packages/json-cas/src/liquid-render.test.ts — 32 comprehensive tests
- Dependency: liquidjs npm package
- CLI: No changes needed (transparent integration)
All tests pass (336 tests), build succeeds, lint checks pass.
Closes #40
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
json-cas
Self-describing content-addressable storage with JSON Schema typed nodes.
Overview
json-cas is a monorepo for storing and validating JSON data in a content-addressable store (CAS). Each node has a typed payload: its type field is the hash of a JSON Schema node that describes the payload shape. Hashes are 13-character Crockford Base32 strings derived from XXH64 over deterministic CBOR encoding.
A bootstrap meta-schema is stored as a self-referencing seed node (type === hash). All other schemas are registered as nodes typed by that meta-schema. Payloads can reference other nodes via format: "cas_ref" fields; the library provides traversal, reference extraction, and integrity verification.
Use the in-memory store for tests and embedded apps, the filesystem store for persistence, and the CLI for local store management.
Architecture
┌─────────────────┐
│ cli-json-cas │
└────────┬────────┘
│
▼
┌─────────────────┐
│ json-cas-fs │
└────────┬────────┘
│
▼
┌─────────────────┐
│ json-cas │ (core)
└─────────────────┘
| Layer | Package | Role |
|---|---|---|
| Core | @uncaged/json-cas |
Hashing, schemas, stores, verify, bootstrap |
| Storage | @uncaged/json-cas-fs |
Filesystem-backed Store |
| CLI | @uncaged/cli-json-cas |
json-cas command-line tool |
Packages
| Package | Description | Type |
|---|---|---|
@uncaged/json-cas |
Core CAS engine — hashing, schema, store, verify, bootstrap | lib |
@uncaged/json-cas-fs |
Filesystem-backed CAS store | lib |
@uncaged/cli-json-cas |
CLI tool (json-cas binary) |
cli |
Quick Start
git clone <repo-url>
cd json-cas
bun install --no-cache
bun run build
import {
bootstrap,
createMemoryStore,
putSchema,
validate,
} from "@uncaged/json-cas";
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);
console.log(validate(store, node!)); // true
For a persistent store:
import { createFsStore } from "@uncaged/json-cas-fs";
import { bootstrap } from "@uncaged/json-cas";
const store = createFsStore("/path/to/store");
await bootstrap(store);
Or use the CLI (see CLI Reference and packages/cli-json-cas/README.md).
CLI Reference
Binary: json-cas (from @uncaged/cli-json-cas). Default store: ~/.uncaged/json-cas.
Usage: json-cas [--store <path>] [--json] <command> [args]
Commands:
init Create store dir and write bootstrap seed
bootstrap Write meta-schema seed, print hash
schema put <file.json> Register schema, print type hash
schema get <type-hash> Print schema JSON
schema list List all schemas (name + hash)
schema validate <hash> Validate node against its schema
put <type-hash> <file.json> Store node, print hash
get <hash> Print node as JSON
has <hash> Print true/false
verify <hash> Verify integrity, print ok/corrupted
refs <hash> List direct cas_ref edges
walk <hash> [--format tree] Recursive traversal
hash <type-hash> <file.json> Compute hash without storing (dry run)
cat <hash> [--payload] Output node (--payload for payload only)
Flags:
--store <path> Store directory (default: ~/.uncaged/json-cas)
--json Compact JSON output
Development
bun install --no-cache # install workspace dependencies
bun run build # tsc --build (libs)
bun run check # biome check
bun run format # biome format --write
bun test # run all package tests
Publishing
Releases use Changesets. From the repo root:
bun run release # changeset version → build → publish to npm (@uncaged/*)
Individual packages block prepublishOnly and expect releases via the workspace release script.