From d2b9dee4b971435c2ebf7f6bd6c11fe0ae065408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Sun, 31 May 2026 15:41:02 +0000 Subject: [PATCH] feat: wrap refs/walk/gc/var with {type,value} envelope (Phase 3) Fixes #71 --- .../src/__snapshots__/e2e.test.ts.snap | 36 ++++--- packages/cli-json-cas/src/e2e.test.ts | 2 +- packages/cli-json-cas/src/index.ts | 101 +++++------------- 3 files changed, 51 insertions(+), 88 deletions(-) diff --git a/packages/cli-json-cas/src/__snapshots__/e2e.test.ts.snap b/packages/cli-json-cas/src/__snapshots__/e2e.test.ts.snap index 7ef613a..44bdbec 100644 --- a/packages/cli-json-cas/src/__snapshots__/e2e.test.ts.snap +++ b/packages/cli-json-cas/src/__snapshots__/e2e.test.ts.snap @@ -20,15 +20,27 @@ exports[`Phase 1: CAS Core 1.9 verify returns ok for valid node 1`] = ` } `; -exports[`Phase 1: CAS Core 1.10 refs lists direct references (snapshot) 1`] = `""`; +exports[`Phase 1: CAS Core 1.10 refs lists direct references (snapshot) 1`] = ` +"{ + "type": "4N5REDA48XYJP", + "value": [] +}" +`; -exports[`Phase 1: CAS Core 1.11 walk shows traversal tree (snapshot) 1`] = `"ERARPP19YJT05"`; +exports[`Phase 1: CAS Core 1.11 walk shows traversal tree (snapshot) 1`] = ` +"{ + "type": "7124NEATTWYYY", + "value": [ + "ERARPP19YJT05" + ] +}" +`; exports[`Phase 2: Schema Validation 2.3 put against non-existent schema hash fails 1`] = `"Schema not found: AAAAAAAAAAAAA"`; exports[`Phase 3: Variable System 3.1 var set creates variable 1`] = ` { - "type": "E1D32N3GT69Q8", + "type": "AYHQD2YA9G667", "value": { "labels": [], "name": "myapp/config", @@ -41,7 +53,7 @@ exports[`Phase 3: Variable System 3.1 var set creates variable 1`] = ` exports[`Phase 3: Variable System 3.2 var get returns variable 1`] = ` { - "type": "E1D32N3GT69Q8", + "type": "BVW2SAJ8606EZ", "value": { "labels": [], "name": "myapp/config", @@ -54,7 +66,7 @@ exports[`Phase 3: Variable System 3.2 var get returns variable 1`] = ` exports[`Phase 3: Variable System 3.3 var list shows all variables 1`] = ` { - "type": "E1D32N3GT69Q8", + "type": "3BY1S4RKNMR0P", "value": [ { "labels": [], @@ -69,7 +81,7 @@ exports[`Phase 3: Variable System 3.3 var list shows all variables 1`] = ` exports[`Phase 3: Variable System 3.4 var list prefix filters by prefix 1`] = ` { - "type": "E1D32N3GT69Q8", + "type": "3BY1S4RKNMR0P", "value": [ { "labels": [], @@ -84,7 +96,7 @@ exports[`Phase 3: Variable System 3.4 var list prefix filters by prefix 1`] = ` exports[`Phase 3: Variable System 3.5 var set upsert updates existing variable 1`] = ` { - "type": "E1D32N3GT69Q8", + "type": "AYHQD2YA9G667", "value": { "labels": [], "name": "myapp/config", @@ -97,7 +109,7 @@ exports[`Phase 3: Variable System 3.5 var set upsert updates existing variable 1 exports[`Phase 3: Variable System 3.6 var tag adds kv tag and label 1`] = ` { - "type": "E1D32N3GT69Q8", + "type": "BKMJ3DJHTS6VB", "value": { "labels": [ "important", @@ -114,7 +126,7 @@ exports[`Phase 3: Variable System 3.6 var tag adds kv tag and label 1`] = ` exports[`Phase 3: Variable System 3.7 var list --tag env:prod filters by kv tag 1`] = ` { - "type": "E1D32N3GT69Q8", + "type": "3BY1S4RKNMR0P", "value": [ { "labels": [ @@ -133,7 +145,7 @@ exports[`Phase 3: Variable System 3.7 var list --tag env:prod filters by kv tag exports[`Phase 3: Variable System 3.8 var list --tag important filters by label 1`] = ` { - "type": "E1D32N3GT69Q8", + "type": "3BY1S4RKNMR0P", "value": [ { "labels": [ @@ -152,7 +164,7 @@ exports[`Phase 3: Variable System 3.8 var list --tag important filters by label exports[`Phase 3: Variable System 3.9 var tag remove deletes label 1`] = ` { - "type": "E1D32N3GT69Q8", + "type": "BKMJ3DJHTS6VB", "value": { "labels": [], "name": "myapp/config", @@ -167,7 +179,7 @@ exports[`Phase 3: Variable System 3.9 var tag remove deletes label 1`] = ` exports[`Phase 3: Variable System 3.10 var delete removes variable 1`] = ` { - "type": "E1D32N3GT69Q8", + "type": "ASWN8JEGAG7AP", "value": [ { "labels": [], diff --git a/packages/cli-json-cas/src/e2e.test.ts b/packages/cli-json-cas/src/e2e.test.ts index 1858e6b..beaeaef 100644 --- a/packages/cli-json-cas/src/e2e.test.ts +++ b/packages/cli-json-cas/src/e2e.test.ts @@ -420,7 +420,7 @@ describe("Phase 6: GC", () => { const { exitCode, stdout } = await runCli(["gc"]); expect(exitCode).toBe(0); // Assert structural shape only — exact counts depend on phase history - const result = JSON.parse(stdout) as Record; + const result = envValue(stdout) as Record; expect(typeof result.total).toBe("number"); expect(typeof result.reachable).toBe("number"); expect(typeof result.collected).toBe("number"); diff --git a/packages/cli-json-cas/src/index.ts b/packages/cli-json-cas/src/index.ts index 09de50d..9e1a72b 100644 --- a/packages/cli-json-cas/src/index.ts +++ b/packages/cli-json-cas/src/index.ts @@ -3,7 +3,7 @@ import { existsSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { join, resolve } from "node:path"; -import type { Hash, JSONSchema, Store, VariableStore } from "@uncaged/json-cas"; +import type { Hash, Store, VariableStore } from "@uncaged/json-cas"; import { bootstrap, CasNodeNotFoundError, @@ -13,7 +13,6 @@ import { getSchema, InvalidTagFormatError, InvalidVariableNameError, - putSchema, refs, renderAsync, renderDirect, @@ -145,55 +144,6 @@ async function resolveTypeHash(typeHashOrAlias: string): Promise { return typeHashOrAlias; } -/** - * Get the Variable schema's CAS hash - * This is the type hash used in JSON envelopes - */ -async function getVariableSchemaHash(): Promise { - const store = await openStore(); - - // Define the Variable JSON Schema (updated for new model with composite key) - const variableSchema: JSONSchema = { - title: "Variable", - type: "object", - properties: { - name: { type: "string" }, - schema: { type: "string" }, - value: { type: "string" }, - created: { type: "number" }, - updated: { type: "number" }, - tags: { type: "object" }, - labels: { type: "array", items: { type: "string" } }, - }, - required: [ - "name", - "schema", - "value", - "created", - "updated", - "tags", - "labels", - ], - }; - - // Compute hash or retrieve from store - const hash = await putSchema(store, variableSchema); - return hash; -} - -/** - * Wrap Variable output in JSON envelope - */ -async function wrapVariableEnvelope( - variable: unknown, -): Promise<{ type: Hash; value: unknown }> { - const typeHash = await getVariableSchemaHash(); - return { - type: typeHash, - value: variable, - }; -} - /** * Parse tag/label arguments * Returns: { tags: Record, labels: string[], deleteNames: string[] } @@ -296,9 +246,7 @@ async function cmdRefs(args: string[]): Promise { const node = store.get(hash); if (node === null) die(`Node not found: ${hash}`); const refHashes = refs(store, node); - for (const r of refHashes) { - console.log(r); - } + out(await wrapEnvelope(store, "@output/refs", refHashes)); } async function cmdWalk(args: string[]): Promise { @@ -314,15 +262,16 @@ async function cmdWalk(args: string[]): Promise { }); const printed = new Set(); + const lines: string[] = []; function printNode(h: Hash, prefix: string, isLast: boolean): void { const connector = prefix === "" ? "" : isLast ? "└── " : "├── "; if (printed.has(h)) { - console.log(`${prefix}${connector}${h} (seen)`); + lines.push(`${prefix}${connector}${h} (seen)`); return; } printed.add(h); - console.log(`${prefix}${connector}${h}`); + lines.push(`${prefix}${connector}${h}`); const kids = childMap.get(h) ?? []; const childPrefix = @@ -333,10 +282,13 @@ async function cmdWalk(args: string[]): Promise { } printNode(hash, "", true); + out(await wrapEnvelope(store, "@output/walk", lines.join("\n"))); } else { + const hashes: Hash[] = []; walk(store, hash, (h) => { - console.log(h); + hashes.push(h); }); + out(await wrapEnvelope(store, "@output/walk", hashes)); } } @@ -471,7 +423,8 @@ async function cmdVarSet(args: string[]): Promise { die("Usage: json-cas var set [--tag ...]"); } - const varStore = await openVarStore(); + const store = await openStore(); + const varStore = createVariableStore(resolve(varDbPath), store); try { // Parse tags/labels from --tag flags @@ -498,8 +451,7 @@ async function cmdVarSet(args: string[]): Promise { : undefined; const variable = varStore.set(name, value, options); - const envelope = await wrapVariableEnvelope(variable); - out(envelope); + out(await wrapEnvelope(store, "@output/var-set", variable)); } catch (e) { if ( e instanceof InvalidVariableNameError || @@ -522,15 +474,15 @@ async function cmdVarGet(args: string[]): Promise { die("Usage: json-cas var get --schema "); } - const varStore = await openVarStore(); + const store = await openStore(); + const varStore = createVariableStore(resolve(varDbPath), store); try { const variable = varStore.get(name, schema); if (variable === null) { die(`Error: Variable not found: name=${name}, schema=${schema}`); } - const envelope = await wrapVariableEnvelope(variable); - out(envelope); + out(await wrapEnvelope(store, "@output/var-get", variable)); } finally { varStore.close(); } @@ -544,19 +496,18 @@ async function cmdVarDelete(args: string[]): Promise { die("Usage: json-cas var delete [--schema ]"); } - const varStore = await openVarStore(); + const store = await openStore(); + const varStore = createVariableStore(resolve(varDbPath), store); try { if (schema !== undefined) { // Precise deletion: remove specific (name, schema) variant const variable = varStore.remove(name, schema); - const envelope = await wrapVariableEnvelope(variable); - out(envelope); + out(await wrapEnvelope(store, "@output/var-delete", variable)); } else { // Batch deletion: remove all variants for this name const variables = varStore.remove(name); - const envelope = await wrapVariableEnvelope(variables); - out(envelope); + out(await wrapEnvelope(store, "@output/var-delete", variables)); } } catch (e) { if (e instanceof VariableNotFoundError) { @@ -581,7 +532,8 @@ async function cmdVarTag(args: string[]): Promise { die("Usage: json-cas var tag --schema "); } - const varStore = await openVarStore(); + const store = await openStore(); + const varStore = createVariableStore(resolve(varDbPath), store); try { const { tags, labels, deleteNames } = parseTagsLabels(tagArgs); @@ -592,8 +544,7 @@ async function cmdVarTag(args: string[]): Promise { delete: deleteNames.length > 0 ? deleteNames : undefined, }); - const envelope = await wrapVariableEnvelope(variable); - out(envelope); + out(await wrapEnvelope(store, "@output/var-tag", variable)); } catch (e) { if ( e instanceof VariableNotFoundError || @@ -613,7 +564,8 @@ async function cmdVarList(args: string[]): Promise { const schema = flags.schema as string | undefined; const tagFlags = flags.tag; - const varStore = await openVarStore(); + const store = await openStore(); + const varStore = createVariableStore(resolve(varDbPath), store); try { // Parse tags/labels from --tag flags @@ -635,8 +587,7 @@ async function cmdVarList(args: string[]): Promise { tags: Object.keys(tags).length > 0 ? tags : undefined, labels: labels.length > 0 ? labels : undefined, }); - const envelope = await wrapVariableEnvelope(variables); - out(envelope); + out(await wrapEnvelope(store, "@output/var-list", variables)); } catch (e) { if (e instanceof InvalidVariableNameError) { die(`Error: ${e.message}`); @@ -817,7 +768,7 @@ async function cmdGc(_args: string[]): Promise { try { const stats = gc(store, varStore); - out(stats); + out(await wrapEnvelope(store, "@output/gc", stats)); } finally { varStore.close(); }