Compare commits

...

3 Commits

Author SHA1 Message Date
Scott Wei 1b53cf5ff8 feat: remove Store.list() from interface
Removes the list() method from the Store type and all implementations.
Callers now use listByType() or has() instead.

The CLI 'list' subcommand is removed. 'schema list' now uses
listByType(metaHash) to enumerate schemas.

Closes #11
2026-05-19 01:24:12 +08:00
xiaoju 17ed619900 chore: release @uncaged/* 0.3.0 2026-05-18 15:00:40 +00:00
xiaomo bad62a82a9 Merge pull request 'feat: disallow self-referencing nodes except via bootstrap()' (#13) from feat/12-no-null-type into main 2026-05-18 14:59:17 +00:00
14 changed files with 61 additions and 49 deletions
+8
View File
@@ -1,5 +1,13 @@
# @uncaged/cli-json-cas
## 0.3.0
### Patch Changes
- Updated dependencies []:
- @uncaged/json-cas@0.3.0
- @uncaged/json-cas-fs@0.3.0
## 0.2.0
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@uncaged/cli-json-cas",
"version": "0.2.0",
"version": "0.3.0",
"type": "module",
"bin": {
"json-cas": "./src/index.ts"
@@ -9,7 +9,7 @@
"test": "bun test"
},
"dependencies": {
"@uncaged/json-cas": "^0.2.0",
"@uncaged/json-cas-fs": "^0.2.0"
"@uncaged/json-cas": "^0.3.0",
"@uncaged/json-cas-fs": "^0.3.0"
}
}
+3 -13
View File
@@ -116,10 +116,10 @@ async function cmdSchemaGet(args: string[]): Promise<void> {
async function cmdSchemaList(): Promise<void> {
const store = openStore();
const metaHash = await bootstrap(store);
for (const hash of store.list()) {
for (const hash of store.listByType(metaHash)) {
if (hash === metaHash) continue;
const node = store.get(hash);
if (node !== null && node.type === metaHash) {
if (node !== null) {
const schema = node.payload as JSONSchema;
const name =
(schema.title as string | undefined) ??
@@ -176,12 +176,7 @@ async function cmdVerify(args: string[]): Promise<void> {
console.log(ok ? "ok" : "corrupted");
}
async function cmdList(): Promise<void> {
const store = openStore();
for (const hash of store.list()) {
console.log(hash);
}
}
async function cmdRefs(args: string[]): Promise<void> {
const hash = args[0];
@@ -271,7 +266,6 @@ Commands:
get <hash> Print node as JSON
has <hash> Print true/false
verify <hash> Verify integrity, print ok/corrupted
list List all hashes
refs <hash> List direct cas_ref edges
walk <hash> [--format tree] Recursive traversal
hash <type-hash> <file.json> Compute hash without storing (dry run)
@@ -337,10 +331,6 @@ switch (cmd) {
await cmdVerify(rest);
break;
case "list":
await cmdList();
break;
case "refs":
await cmdRefs(rest);
break;
+11
View File
@@ -1,5 +1,16 @@
# @uncaged/json-cas-fs
## 0.3.0
### Minor Changes
- Disallow self-referencing nodes in put(). typeHash is now required (no null). Self-ref only via bootstrap().
### Patch Changes
- Updated dependencies []:
- @uncaged/json-cas@0.3.0
## 0.2.0
### Minor Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@uncaged/json-cas-fs",
"version": "0.2.0",
"version": "0.3.0",
"type": "module",
"main": "./src/index.ts",
"exports": {
@@ -10,7 +10,7 @@
"test": "bun test"
},
"dependencies": {
"@uncaged/json-cas": "^0.2.0",
"@uncaged/json-cas": "^0.3.0",
"cborg": "^4.2.3"
}
}
+10 -10
View File
@@ -30,15 +30,15 @@ describe("createFsStore – init and bootstrap", () => {
test("store opens against an existing empty dir", () => {
const store = createFsStore(dir);
expect(store.list()).toEqual([]);
expect(store.listByType("0000000000000")).toEqual([]);
});
test("store creates the directory on first put", async () => {
const nested = join(dir, "sub", "store");
const store = createFsStore(nested);
const typeHash = await computeSelfHash({ name: "t" });
await store.put(typeHash, { x: 1 });
expect(store.list()).toHaveLength(1);
const hash = await store.put(typeHash, { x: 1 });
expect(store.has(hash)).toBe(true);
});
test("bootstrap returns a valid 13-char self-referencing hash", async () => {
@@ -58,7 +58,7 @@ describe("createFsStore – init and bootstrap", () => {
const h2 = await bootstrap(store);
expect(h1).toBe(h2);
expect(store.list()).toHaveLength(1);
expect(store.listByType(h1)).toHaveLength(1);
});
});
@@ -84,7 +84,7 @@ describe("createFsStore – persistence round-trip", () => {
const store2 = createFsStore(dir);
expect(store2.has(h1)).toBe(true);
expect(store2.has(h2)).toBe(true);
expect(store2.list()).toHaveLength(2);
expect(store2.listByType(typeHash)).toHaveLength(2);
});
test("round-trip preserves type, payload, and timestamp", async () => {
@@ -125,7 +125,7 @@ describe("createFsStore – persistence round-trip", () => {
const ts2 = store2.get(hash)?.timestamp;
expect(ts1).toBe(ts2);
expect(store2.list()).toHaveLength(1);
expect(store2.listByType(typeHash)).toHaveLength(1);
});
});
@@ -151,7 +151,7 @@ describe("createFsStore – has and list", () => {
expect(store.has(hash)).toBe(true);
});
test("list returns all stored hashes", async () => {
test("listByType returns all stored hashes for a type", async () => {
const store = createFsStore(dir);
const typeHash = await computeSelfHash({ name: "t" });
@@ -159,16 +159,16 @@ describe("createFsStore – has and list", () => {
const h2 = await store.put(typeHash, { a: 2 });
const h3 = await store.put(typeHash, { a: 3 });
const all = store.list();
const all = store.listByType(typeHash);
expect(all).toHaveLength(3);
expect(all).toContain(h1);
expect(all).toContain(h2);
expect(all).toContain(h3);
});
test("list returns empty array on fresh store", () => {
test("listByType returns empty array on fresh store", () => {
const store = createFsStore(dir);
expect(store.list()).toEqual([]);
expect(store.listByType("0000000000000")).toEqual([]);
});
test("get returns null for unknown hash", () => {
-4
View File
@@ -167,10 +167,6 @@ export function createFsStore(dir: string): Store {
return data.has(hash);
},
list(): Hash[] {
return [...data.keys()];
},
listByType(typeHash: Hash): Hash[] {
return typeIndex.get(typeHash) ?? [];
},
+7
View File
@@ -1,5 +1,12 @@
# @uncaged/json-cas-workflow
## 0.3.0
### Patch Changes
- Updated dependencies []:
- @uncaged/json-cas@0.3.0
## 0.2.0
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@uncaged/json-cas-workflow",
"version": "0.2.0",
"version": "0.3.0",
"type": "module",
"main": "./src/index.ts",
"exports": {
@@ -10,6 +10,6 @@
"test": "bun test"
},
"dependencies": {
"@uncaged/json-cas": "^0.2.0"
"@uncaged/json-cas": "^0.3.0"
}
}
+6
View File
@@ -1,5 +1,11 @@
# @uncaged/json-cas
## 0.3.0
### Minor Changes
- Disallow self-referencing nodes in put(). typeHash is now required (no null). Self-ref only via bootstrap().
## 0.2.0
### Minor Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@uncaged/json-cas",
"version": "0.2.0",
"version": "0.3.0",
"type": "module",
"main": "./src/index.ts",
"exports": {
+8 -9
View File
@@ -97,7 +97,7 @@ describe("createMemoryStore – put and get", () => {
const h1 = await store.put(typeHash, { n: 42 });
const h2 = await store.put(typeHash, { n: 42 });
expect(h1).toBe(h2);
expect(store.list()).toHaveLength(1);
expect(store.listByType(typeHash)).toHaveLength(1);
});
test("put does not create self-referencing nodes", async () => {
@@ -127,9 +127,9 @@ describe("createMemoryStore – put and get", () => {
});
// ──────────────────────────────────────────────────────────────────────────────
// Step 4: store.has() and store.list()
// Step 4: store.has()
// ──────────────────────────────────────────────────────────────────────────────
describe("createMemoryStore – has and list", () => {
describe("createMemoryStore – has", () => {
test("has returns false before put, true after", async () => {
const store = createMemoryStore();
const typeHash = await computeSelfHash({ name: "t" });
@@ -140,7 +140,7 @@ describe("createMemoryStore – has and list", () => {
expect(store.has(hash)).toBe(true);
});
test("list returns all stored hashes", async () => {
test("listByType returns all stored hashes for a type", async () => {
const store = createMemoryStore();
const typeHash = await computeSelfHash({ name: "t" });
@@ -148,16 +148,16 @@ describe("createMemoryStore – has and list", () => {
const h2 = await store.put(typeHash, { a: 2 });
const h3 = await store.put(typeHash, { a: 3 });
const all = store.list();
const all = store.listByType(typeHash);
expect(all).toHaveLength(3);
expect(all).toContain(h1);
expect(all).toContain(h2);
expect(all).toContain(h3);
});
test("list returns empty array on fresh store", () => {
test("listByType returns empty array on fresh store", () => {
const store = createMemoryStore();
expect(store.list()).toEqual([]);
expect(store.listByType("0000000000000")).toEqual([]);
});
});
@@ -249,7 +249,6 @@ describe("bootstrap", () => {
put: async () => "0000000000000",
get: () => null,
has: () => false,
list: () => [],
listByType: () => [],
};
await expect(bootstrap(store)).rejects.toThrow(
@@ -295,6 +294,6 @@ describe("bootstrap", () => {
const h2 = await bootstrap(store);
expect(h1).toBe(h2);
expect(store.list()).toHaveLength(1);
expect(store.listByType(h1)).toHaveLength(1);
});
});
-4
View File
@@ -44,10 +44,6 @@ export function createMemoryStore(): Store {
return data.has(hash);
},
list(): Hash[] {
return [...data.keys()];
},
listByType(typeHash: Hash): Hash[] {
const set = byType.get(typeHash);
return set ? [...set] : [];
-1
View File
@@ -23,6 +23,5 @@ export type Store = {
put(typeHash: Hash, payload: unknown): Promise<Hash>;
get(hash: Hash): CasNode | null;
has(hash: Hash): boolean;
list(): Hash[];
listByType(typeHash: Hash): Hash[];
};