feat: wrap put/get/has/hash/verify/list with {type,value} envelope (Phase 2)
Fixes #70
This commit is contained in:
@@ -2,15 +2,23 @@
|
||||
|
||||
exports[`Phase 1: CAS Core 1.6 get returns node JSON (snapshot) 1`] = `
|
||||
{
|
||||
"payload": {
|
||||
"age": 30,
|
||||
"name": "Alice",
|
||||
"type": "ASE7K6A0HG8W9",
|
||||
"value": {
|
||||
"payload": {
|
||||
"age": 30,
|
||||
"name": "Alice",
|
||||
},
|
||||
"type": "7XX5H51CVD9H0",
|
||||
},
|
||||
"type": "7XX5H51CVD9H0",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Phase 1: CAS Core 1.9 verify returns ok for valid node 1`] = `"ok"`;
|
||||
exports[`Phase 1: CAS Core 1.9 verify returns ok for valid node 1`] = `
|
||||
{
|
||||
"type": "8E2M8H30BHXS8",
|
||||
"value": "ok",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Phase 1: CAS Core 1.10 refs lists direct references (snapshot) 1`] = `""`;
|
||||
|
||||
@@ -216,16 +224,16 @@ exports[`Phase 7: Edge Cases 7.5 no subcommand shows help text 1`] = `
|
||||
"Usage: json-cas [--store <path>] [--json] <command> [args]
|
||||
|
||||
Commands:
|
||||
put <type-hash> <file.json> Store node, print hash
|
||||
get <hash> Print node as JSON
|
||||
has <hash> Print true/false
|
||||
verify <hash> Verify integrity + schema, print ok/corrupted/invalid
|
||||
put <type-hash> <file.json> Store node, print { type, value } envelope (value=hash)
|
||||
get <hash> Print node as { type, value } envelope
|
||||
has <hash> Print { type, value } envelope (value=boolean)
|
||||
verify <hash> Verify integrity + schema → { type, value } (value=ok/corrupted/invalid)
|
||||
refs <hash> List direct cas_ref edges
|
||||
walk <hash> [--format tree] Recursive traversal
|
||||
hash <type-hash> <file.json> Compute hash without storing (dry run)
|
||||
hash <type-hash> <file.json> Compute hash without storing → { type, value } envelope
|
||||
render <hash> [options] Render node as YAML with resolution decay
|
||||
render --pipe/-p [options] Render { type, value } from stdin
|
||||
list --type <hash-or-alias> List all hashes for a given type
|
||||
list --type <hash-or-alias> List hashes for a type → { type, value } envelope (value=string[])
|
||||
var set <name> <hash> [--tag <tag>...] Create/update a variable
|
||||
var get <name> --schema <hash> Get a variable by name + schema
|
||||
var delete <name> [--schema <hash>] Delete variable(s)
|
||||
|
||||
@@ -15,6 +15,11 @@ import { createFsStore, openStore as openFsStore } from "@uncaged/json-cas-fs";
|
||||
const pkgPath = resolve(import.meta.dir, "../package.json");
|
||||
const entrypoint = resolve(import.meta.dir, "index.ts");
|
||||
|
||||
/** Extract the `value` field from a { type, value } envelope JSON string. */
|
||||
function envValue(json: string): unknown {
|
||||
return (JSON.parse(json.trim()) as { value: unknown }).value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a schema directly via the library (CLI schema put was removed).
|
||||
* Returns the type hash.
|
||||
@@ -168,8 +173,8 @@ describe("@ Alias Resolution - put", () => {
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
// Should output a valid hash (13 chars)
|
||||
expect(stdout).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
// Should output an envelope whose value is a valid hash (13 chars)
|
||||
expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
});
|
||||
|
||||
test("ucas put @number <file> should resolve alias", async () => {
|
||||
@@ -185,7 +190,7 @@ describe("@ Alias Resolution - put", () => {
|
||||
);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
});
|
||||
|
||||
test("ucas put @object <file> should resolve alias", async () => {
|
||||
@@ -201,7 +206,7 @@ describe("@ Alias Resolution - put", () => {
|
||||
);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
});
|
||||
|
||||
test("ucas put @invalid <file> should fail", async () => {
|
||||
@@ -236,7 +241,7 @@ describe("@ Alias Resolution - hash", () => {
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
expect(stdout).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -313,10 +318,10 @@ describe("Issue #50: Schema Validation in put", () => {
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
expect(stdout.trim()).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
|
||||
// Verify node was stored
|
||||
const hash = stdout.trim();
|
||||
const hash = envValue(stdout) as string;
|
||||
const { exitCode: hasExitCode } = await runCli(["has", hash], tmpStore);
|
||||
expect(hasExitCode).toBe(0);
|
||||
} finally {
|
||||
@@ -355,7 +360,7 @@ describe("Issue #50: Schema Validation in put", () => {
|
||||
);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout.trim()).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
} finally {
|
||||
rmSync(tmpStore, { recursive: true, force: true });
|
||||
}
|
||||
@@ -403,7 +408,7 @@ describe("Issue #50: Schema Validation in put", () => {
|
||||
);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout.trim()).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
} finally {
|
||||
rmSync(tmpStore, { recursive: true, force: true });
|
||||
}
|
||||
@@ -423,7 +428,7 @@ describe("Issue #50: Schema Validation in put", () => {
|
||||
);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout.trim()).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
} finally {
|
||||
rmSync(tmpStore, { recursive: true, force: true });
|
||||
}
|
||||
@@ -469,7 +474,7 @@ describe("Issue #50: Schema Validation in put", () => {
|
||||
["has", "0000000000000"],
|
||||
tmpStore,
|
||||
);
|
||||
expect(hasOutput.trim()).toBe("false");
|
||||
expect(envValue(hasOutput)).toBe(false);
|
||||
} finally {
|
||||
rmSync(tmpStore, { recursive: true, force: true });
|
||||
}
|
||||
@@ -692,7 +697,7 @@ describe("Issue #50: Schema Validation in put", () => {
|
||||
);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout.trim()).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
} finally {
|
||||
rmSync(tmpStore, { recursive: true, force: true });
|
||||
}
|
||||
@@ -850,33 +855,30 @@ describe("Suite 6: CLI Integration with Templates", () => {
|
||||
// Create node
|
||||
const nodeFile = join(tmpStore, "node.json");
|
||||
writeFileSync(nodeFile, JSON.stringify({ name: "Alice" }));
|
||||
const { stdout: nodeHash } = await runCli(
|
||||
const { stdout: nodeOut } = await runCli(
|
||||
["put", schemaHash.trim(), nodeFile],
|
||||
tmpStore,
|
||||
);
|
||||
const nodeHash = envValue(nodeOut) as string;
|
||||
|
||||
// Create template file (JSON-encoded string)
|
||||
const templateFile = join(tmpStore, "template.json");
|
||||
writeFileSync(templateFile, JSON.stringify("Hello {{ payload.name }}!"));
|
||||
const { stdout: tmplHash } = await runCli(
|
||||
const { stdout: tmplOut } = await runCli(
|
||||
["put", "@string", templateFile],
|
||||
tmpStore,
|
||||
);
|
||||
const tmplHash = envValue(tmplOut) as string;
|
||||
|
||||
// Register template
|
||||
await runCli(
|
||||
[
|
||||
"var",
|
||||
"set",
|
||||
`@ucas/template/text/${schemaHash.trim()}`,
|
||||
tmplHash.trim(),
|
||||
],
|
||||
["var", "set", `@ucas/template/text/${schemaHash.trim()}`, tmplHash],
|
||||
tmpStore,
|
||||
);
|
||||
|
||||
// Render with template
|
||||
const { stdout: output, exitCode } = await runCli(
|
||||
["render", nodeHash.trim()],
|
||||
["render", nodeHash],
|
||||
tmpStore,
|
||||
);
|
||||
|
||||
@@ -911,21 +913,23 @@ describe("Suite 6: CLI Integration with Templates", () => {
|
||||
// Create child node
|
||||
const childFile = join(tmpStore, "child.json");
|
||||
writeFileSync(childFile, JSON.stringify({ value: "child", child: null }));
|
||||
const { stdout: childHash } = await runCli(
|
||||
const { stdout: childOut } = await runCli(
|
||||
["put", schemaHash.trim(), childFile],
|
||||
tmpStore,
|
||||
);
|
||||
const childHash = envValue(childOut) as string;
|
||||
|
||||
// Create parent node
|
||||
const parentFile = join(tmpStore, "parent.json");
|
||||
writeFileSync(
|
||||
parentFile,
|
||||
JSON.stringify({ value: "parent", child: childHash.trim() }),
|
||||
JSON.stringify({ value: "parent", child: childHash }),
|
||||
);
|
||||
const { stdout: parentHash } = await runCli(
|
||||
const { stdout: parentOut } = await runCli(
|
||||
["put", schemaHash.trim(), parentFile],
|
||||
tmpStore,
|
||||
);
|
||||
const parentHash = envValue(parentOut) as string;
|
||||
|
||||
// Create template showing resolution (JSON-encoded string)
|
||||
const templateFile = join(tmpStore, "template.json");
|
||||
@@ -933,25 +937,21 @@ describe("Suite 6: CLI Integration with Templates", () => {
|
||||
templateFile,
|
||||
JSON.stringify("{{ payload.value }}(res={{ resolution }})"),
|
||||
);
|
||||
const { stdout: tmplHash } = await runCli(
|
||||
const { stdout: tmplOut } = await runCli(
|
||||
["put", "@string", templateFile],
|
||||
tmpStore,
|
||||
);
|
||||
const tmplHash = envValue(tmplOut) as string;
|
||||
|
||||
// Register template
|
||||
await runCli(
|
||||
[
|
||||
"var",
|
||||
"set",
|
||||
`@ucas/template/text/${schemaHash.trim()}`,
|
||||
tmplHash.trim(),
|
||||
],
|
||||
["var", "set", `@ucas/template/text/${schemaHash.trim()}`, tmplHash],
|
||||
tmpStore,
|
||||
);
|
||||
|
||||
// Render with custom decay
|
||||
const { stdout: output, exitCode } = await runCli(
|
||||
["render", parentHash.trim(), "--decay", "0.7"],
|
||||
["render", parentHash, "--decay", "0.7"],
|
||||
tmpStore,
|
||||
);
|
||||
|
||||
@@ -979,10 +979,11 @@ describe("Suite 6: CLI Integration with Templates", () => {
|
||||
|
||||
const nodeFile = join(tmpStore, "node.json");
|
||||
writeFileSync(nodeFile, JSON.stringify({ name: "Bob" }));
|
||||
const { stdout: nodeHash } = await runCli(
|
||||
const { stdout: nodeOut } = await runCli(
|
||||
["put", schemaHash.trim(), nodeFile],
|
||||
tmpStore,
|
||||
);
|
||||
const nodeHash = envValue(nodeOut) as string;
|
||||
|
||||
// Create template (JSON-encoded string)
|
||||
const templateFile = join(tmpStore, "template.json");
|
||||
@@ -990,25 +991,21 @@ describe("Suite 6: CLI Integration with Templates", () => {
|
||||
templateFile,
|
||||
JSON.stringify("Greetings {{ payload.name }}!"),
|
||||
);
|
||||
const { stdout: tmplHash } = await runCli(
|
||||
const { stdout: tmplOut } = await runCli(
|
||||
["put", "@string", templateFile],
|
||||
tmpStore,
|
||||
);
|
||||
const tmplHash = envValue(tmplOut) as string;
|
||||
|
||||
await runCli(
|
||||
[
|
||||
"var",
|
||||
"set",
|
||||
`@ucas/template/text/${schemaHash.trim()}`,
|
||||
tmplHash.trim(),
|
||||
],
|
||||
["var", "set", `@ucas/template/text/${schemaHash.trim()}`, tmplHash],
|
||||
tmpStore,
|
||||
);
|
||||
|
||||
const { stdout: output, exitCode } = await runCli(
|
||||
[
|
||||
"render",
|
||||
nodeHash.trim(),
|
||||
nodeHash,
|
||||
"--resolution",
|
||||
"0.8",
|
||||
"--decay",
|
||||
@@ -1043,14 +1040,15 @@ describe("Suite 6: CLI Integration with Templates", () => {
|
||||
|
||||
const nodeFile = join(tmpStore, "node.json");
|
||||
writeFileSync(nodeFile, JSON.stringify({ name: "Charlie" }));
|
||||
const { stdout: nodeHash } = await runCli(
|
||||
const { stdout: nodeOut } = await runCli(
|
||||
["put", schemaHash.trim(), nodeFile],
|
||||
tmpStore,
|
||||
);
|
||||
const nodeHash = envValue(nodeOut) as string;
|
||||
|
||||
// No template registered - should fall back to YAML
|
||||
const { stdout: output, exitCode } = await runCli(
|
||||
["render", nodeHash.trim()],
|
||||
["render", nodeHash],
|
||||
tmpStore,
|
||||
);
|
||||
|
||||
@@ -1079,13 +1077,14 @@ describe("Suite 6: CLI Integration with Templates", () => {
|
||||
|
||||
const nodeFile = join(tmpStore, "node.json");
|
||||
writeFileSync(nodeFile, JSON.stringify({ name: "Test" }));
|
||||
const { stdout: nodeHash } = await runCli(
|
||||
const { stdout: nodeOut } = await runCli(
|
||||
["put", schemaHash.trim(), nodeFile],
|
||||
tmpStore,
|
||||
);
|
||||
const nodeHash = envValue(nodeOut) as string;
|
||||
|
||||
const { exitCode, stderr } = await runCli(
|
||||
["render", nodeHash.trim(), "--decay", "1.5"],
|
||||
["render", nodeHash, "--decay", "1.5"],
|
||||
tmpStore,
|
||||
);
|
||||
|
||||
@@ -1126,14 +1125,15 @@ describe("Suite 6: CLI Integration with Templates", () => {
|
||||
// Create and store a simple string node
|
||||
const nodeFile = join(tmpStore, "test.json");
|
||||
writeFileSync(nodeFile, JSON.stringify("hello world"));
|
||||
const { stdout: nodeHash } = await runCli(
|
||||
const { stdout: nodeOut } = await runCli(
|
||||
["put", stringType, nodeFile],
|
||||
tmpStore,
|
||||
);
|
||||
const nodeHash = envValue(nodeOut) as string;
|
||||
|
||||
// Render the valid hash
|
||||
const { exitCode, stdout, stderr } = await runCli(
|
||||
["render", nodeHash.trim()],
|
||||
["render", nodeHash],
|
||||
tmpStore,
|
||||
);
|
||||
|
||||
|
||||
@@ -54,6 +54,11 @@ function stripVolatile(json: string): unknown {
|
||||
return strip(JSON.parse(json));
|
||||
}
|
||||
|
||||
/** Extract the `value` field from a { type, value } envelope JSON string. */
|
||||
function envValue(json: string): unknown {
|
||||
return (JSON.parse(json) as { value: unknown }).value;
|
||||
}
|
||||
|
||||
// ---- Phase 1: CAS Core ----
|
||||
|
||||
describe("Phase 1: CAS Core", () => {
|
||||
@@ -88,8 +93,8 @@ describe("Phase 1: CAS Core", () => {
|
||||
writeFileSync(nodeFile, JSON.stringify({ name: "Alice", age: 30 }));
|
||||
const { stdout, exitCode } = await runCli(["put", typeHash, nodeFile]);
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
nodeHash = stdout;
|
||||
expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
nodeHash = envValue(stdout) as string;
|
||||
});
|
||||
|
||||
test("1.6 get returns node JSON (snapshot)", async () => {
|
||||
@@ -101,19 +106,19 @@ describe("Phase 1: CAS Core", () => {
|
||||
test("1.7 has returns true for existing node", async () => {
|
||||
const { stdout, exitCode } = await runCli(["has", nodeHash]);
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toBe("true");
|
||||
expect(envValue(stdout)).toBe(true);
|
||||
});
|
||||
|
||||
test("1.8 has returns false for non-existing hash", async () => {
|
||||
const { stdout, exitCode } = await runCli(["has", "AAAAAAAAAAAAA"]);
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toBe("false");
|
||||
expect(envValue(stdout)).toBe(false);
|
||||
});
|
||||
|
||||
test("1.9 verify returns ok for valid node", async () => {
|
||||
const { stdout, exitCode } = await runCli(["verify", nodeHash]);
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toMatchSnapshot();
|
||||
expect(stripVolatile(stdout)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("1.10 refs lists direct references (snapshot)", async () => {
|
||||
@@ -132,13 +137,13 @@ describe("Phase 1: CAS Core", () => {
|
||||
const nodeFile = join(tmpStore, "test-node.json");
|
||||
const { stdout, exitCode } = await runCli(["hash", typeHash, nodeFile]);
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toBe(nodeHash);
|
||||
expect(envValue(stdout)).toBe(nodeHash);
|
||||
});
|
||||
|
||||
test("1.13 list --type returns nodes of that type", async () => {
|
||||
const { stdout, exitCode } = await runCli(["list", "--type", typeHash]);
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain(nodeHash);
|
||||
expect(envValue(stdout)).toContain(nodeHash);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -163,7 +168,7 @@ describe("Phase 2: Schema Validation", () => {
|
||||
test("2.2 verify on valid node returns ok (hash + schema)", async () => {
|
||||
const { stdout, exitCode } = await runCli(["verify", nodeHash]);
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toBe("ok");
|
||||
expect(envValue(stdout)).toBe("ok");
|
||||
});
|
||||
|
||||
test("2.3 put against non-existent schema hash fails", async () => {
|
||||
@@ -222,12 +227,13 @@ describe("Phase 3: Variable System", () => {
|
||||
test("3.5 var set upsert updates existing variable", async () => {
|
||||
const node2File = join(tmpStore, "node2.json");
|
||||
writeFileSync(node2File, JSON.stringify({ name: "Bob", age: 25 }));
|
||||
const { stdout: node2Hash } = await runCli(["put", typeHash, node2File]);
|
||||
const { stdout: node2Out } = await runCli(["put", typeHash, node2File]);
|
||||
const node2Hash = envValue(node2Out) as string;
|
||||
const { exitCode, stdout } = await runCli([
|
||||
"var",
|
||||
"set",
|
||||
"myapp/config",
|
||||
node2Hash.trim(),
|
||||
node2Hash,
|
||||
]);
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stripVolatile(stdout)).toMatchSnapshot();
|
||||
@@ -405,7 +411,7 @@ describe("Phase 6: GC", () => {
|
||||
const gcNodeFile = join(tmpStore, "gc-node.json");
|
||||
writeFileSync(gcNodeFile, JSON.stringify({ name: "GcAlice", age: 30 }));
|
||||
const { stdout } = await runCli(["put", typeHash, gcNodeFile]);
|
||||
gcNodeHash = stdout.trim();
|
||||
gcNodeHash = envValue(stdout) as string;
|
||||
// Set a var referencing this node so it survives GC during Phase 6
|
||||
await runCli(["var", "set", "gc-test/ref", gcNodeHash]);
|
||||
});
|
||||
@@ -428,25 +434,21 @@ describe("Phase 6: GC", () => {
|
||||
const { exitCode } = await runCli(["gc"]);
|
||||
expect(exitCode).toBe(0);
|
||||
const { stdout } = await runCli(["has", gcNodeHash]);
|
||||
expect(stdout).toBe("true");
|
||||
expect(envValue(stdout)).toBe(true);
|
||||
});
|
||||
|
||||
test("6.3 gc reclaims orphan node", async () => {
|
||||
const orphanFile = join(tmpStore, "orphan.json");
|
||||
writeFileSync(orphanFile, JSON.stringify({ name: "Orphan", age: 99 }));
|
||||
const { stdout: orphanHashRaw } = await runCli([
|
||||
"put",
|
||||
typeHash,
|
||||
orphanFile,
|
||||
]);
|
||||
const orphanHash = orphanHashRaw.trim();
|
||||
const { stdout: orphanOut } = await runCli(["put", typeHash, orphanFile]);
|
||||
const orphanHash = envValue(orphanOut) as string;
|
||||
|
||||
const { stdout: beforeGc } = await runCli(["has", orphanHash]);
|
||||
expect(beforeGc).toBe("true");
|
||||
expect(envValue(beforeGc)).toBe(true);
|
||||
|
||||
await runCli(["gc"]);
|
||||
const { stdout: afterGc } = await runCli(["has", orphanHash]);
|
||||
expect(afterGc).toBe("false");
|
||||
expect(envValue(afterGc)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
validate,
|
||||
verify,
|
||||
walk,
|
||||
wrapEnvelope,
|
||||
} from "@uncaged/json-cas";
|
||||
import { openStore as openFsStore } from "@uncaged/json-cas-fs";
|
||||
|
||||
@@ -253,7 +254,7 @@ async function cmdPut(args: string[]): Promise<void> {
|
||||
}
|
||||
|
||||
const hash = await store.put(typeHash, payload);
|
||||
console.log(hash);
|
||||
out(await wrapEnvelope(store, "@output/put", hash));
|
||||
}
|
||||
|
||||
async function cmdGet(args: string[]): Promise<void> {
|
||||
@@ -262,14 +263,14 @@ async function cmdGet(args: string[]): Promise<void> {
|
||||
const store = await openStore();
|
||||
const node = store.get(hash);
|
||||
if (node === null) die(`Node not found: ${hash}`);
|
||||
out(node);
|
||||
out(await wrapEnvelope(store, "@output/get", node));
|
||||
}
|
||||
|
||||
async function cmdHas(args: string[]): Promise<void> {
|
||||
const hash = args[0];
|
||||
if (!hash) die("Usage: json-cas has <hash>");
|
||||
const store = await openStore();
|
||||
console.log(String(store.has(hash)));
|
||||
out(await wrapEnvelope(store, "@output/has", store.has(hash)));
|
||||
}
|
||||
|
||||
async function cmdVerify(args: string[]): Promise<void> {
|
||||
@@ -279,12 +280,13 @@ async function cmdVerify(args: string[]): Promise<void> {
|
||||
const node = store.get(hash);
|
||||
if (node === null) die(`Node not found: ${hash}`);
|
||||
const ok = await verify(hash, node);
|
||||
let status: string;
|
||||
if (!ok) {
|
||||
console.log("corrupted");
|
||||
status = "corrupted";
|
||||
} else {
|
||||
const valid = validate(store, node);
|
||||
console.log(valid ? "ok" : "invalid");
|
||||
status = validate(store, node) ? "ok" : "invalid";
|
||||
}
|
||||
out(await wrapEnvelope(store, "@output/verify", status));
|
||||
}
|
||||
|
||||
async function cmdRefs(args: string[]): Promise<void> {
|
||||
@@ -346,7 +348,8 @@ async function cmdHash(args: string[]): Promise<void> {
|
||||
const typeHash = await resolveTypeHash(typeHashOrAlias);
|
||||
const payload = readJsonFile(file);
|
||||
const hash = await computeHash(typeHash, payload);
|
||||
console.log(hash);
|
||||
const store = await openStore();
|
||||
out(await wrapEnvelope(store, "@output/hash", hash));
|
||||
}
|
||||
|
||||
async function cmdRender(args: string[]): Promise<void> {
|
||||
@@ -826,9 +829,8 @@ async function cmdList(_args: string[]): Promise<void> {
|
||||
die("Usage: json-cas list --type <hash-or-alias>");
|
||||
const typeHash = await resolveTypeHash(typeFlag);
|
||||
const store = await openStore();
|
||||
for (const hash of store.listByType(typeHash)) {
|
||||
console.log(hash);
|
||||
}
|
||||
const hashes = Array.from(store.listByType(typeHash));
|
||||
out(await wrapEnvelope(store, "@output/list", hashes));
|
||||
}
|
||||
|
||||
function printUsage(): void {
|
||||
@@ -836,16 +838,16 @@ function printUsage(): void {
|
||||
Usage: json-cas [--store <path>] [--json] <command> [args]
|
||||
|
||||
Commands:
|
||||
put <type-hash> <file.json> Store node, print hash
|
||||
get <hash> Print node as JSON
|
||||
has <hash> Print true/false
|
||||
verify <hash> Verify integrity + schema, print ok/corrupted/invalid
|
||||
put <type-hash> <file.json> Store node, print { type, value } envelope (value=hash)
|
||||
get <hash> Print node as { type, value } envelope
|
||||
has <hash> Print { type, value } envelope (value=boolean)
|
||||
verify <hash> Verify integrity + schema → { type, value } (value=ok/corrupted/invalid)
|
||||
refs <hash> List direct cas_ref edges
|
||||
walk <hash> [--format tree] Recursive traversal
|
||||
hash <type-hash> <file.json> Compute hash without storing (dry run)
|
||||
hash <type-hash> <file.json> Compute hash without storing → { type, value } envelope
|
||||
render <hash> [options] Render node as YAML with resolution decay
|
||||
render --pipe/-p [options] Render { type, value } from stdin
|
||||
list --type <hash-or-alias> List all hashes for a given type
|
||||
list --type <hash-or-alias> List hashes for a type → { type, value } envelope (value=string[])
|
||||
var set <name> <hash> [--tag <tag>...] Create/update a variable
|
||||
var get <name> --schema <hash> Get a variable by name + schema
|
||||
var delete <name> [--schema <hash>] Delete variable(s)
|
||||
|
||||
Reference in New Issue
Block a user