@@ -25,11 +25,20 @@ export {
|
|||||||
export { createMemoryStore } from "./store.js";
|
export { createMemoryStore } from "./store.js";
|
||||||
export type {
|
export type {
|
||||||
CasNode,
|
CasNode,
|
||||||
|
CasStore,
|
||||||
Hash,
|
Hash,
|
||||||
|
HistoryEntry,
|
||||||
ListEntry,
|
ListEntry,
|
||||||
ListOptions,
|
ListOptions,
|
||||||
ListSort,
|
ListSort,
|
||||||
|
OcasStore,
|
||||||
Store,
|
Store,
|
||||||
|
Tag,
|
||||||
|
TagOp,
|
||||||
|
TagStore,
|
||||||
|
VarListOptions,
|
||||||
|
VarSetOptions,
|
||||||
|
VarStore,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
export type { Variable } from "./variable.js";
|
export type { Variable } from "./variable.js";
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
import { describe, expect, test } from "bun:test";
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import type {
|
||||||
|
CasNode,
|
||||||
|
CasStore,
|
||||||
|
Hash,
|
||||||
|
HistoryEntry,
|
||||||
|
ListEntry,
|
||||||
|
ListOptions,
|
||||||
|
OcasStore,
|
||||||
|
Tag,
|
||||||
|
TagOp,
|
||||||
|
TagStore,
|
||||||
|
Variable,
|
||||||
|
VarListOptions,
|
||||||
|
VarSetOptions,
|
||||||
|
VarStore,
|
||||||
|
} from "./index.js";
|
||||||
|
|
||||||
|
describe("CasStore type", () => {
|
||||||
|
test("has all required methods with correct signatures", () => {
|
||||||
|
const stub: CasStore = {
|
||||||
|
get: (_h: Hash): CasNode | null => null,
|
||||||
|
put: (_t: Hash, _p: unknown): Hash => "0000000000000",
|
||||||
|
has: (_h: Hash): boolean => false,
|
||||||
|
delete: (_h: Hash): boolean => false,
|
||||||
|
listByType: (_t: Hash, _o?: ListOptions): ListEntry[] => [],
|
||||||
|
listMeta: (_o?: ListOptions): ListEntry[] => [],
|
||||||
|
listSchemas: (_o?: ListOptions): ListEntry[] => [],
|
||||||
|
};
|
||||||
|
expect(typeof stub.get).toBe("function");
|
||||||
|
expect(typeof stub.put).toBe("function");
|
||||||
|
expect(typeof stub.has).toBe("function");
|
||||||
|
expect(typeof stub.delete).toBe("function");
|
||||||
|
expect(typeof stub.listByType).toBe("function");
|
||||||
|
expect(typeof stub.listMeta).toBe("function");
|
||||||
|
expect(typeof stub.listSchemas).toBe("function");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("VarStore type", () => {
|
||||||
|
test("VarStore shape", () => {
|
||||||
|
const sample: Variable = {
|
||||||
|
name: "@x/y",
|
||||||
|
schema: "0",
|
||||||
|
value: "0",
|
||||||
|
created: 0,
|
||||||
|
updated: 0,
|
||||||
|
tags: {},
|
||||||
|
labels: [],
|
||||||
|
};
|
||||||
|
const stub: VarStore = {
|
||||||
|
set: (_n: string, _h: Hash, _o?: VarSetOptions): Variable => sample,
|
||||||
|
get: (_n: string, _s?: Hash): Variable | null => null,
|
||||||
|
remove: (_n: string, _s?: Hash): Variable[] => [],
|
||||||
|
update: (_n: string, _h: Hash, _o?: VarSetOptions): Variable => sample,
|
||||||
|
list: (_o?: VarListOptions): Variable[] => [],
|
||||||
|
history: (_n: string, _s?: Hash): HistoryEntry[] => [],
|
||||||
|
close: (): void => {},
|
||||||
|
};
|
||||||
|
expect(typeof stub.set).toBe("function");
|
||||||
|
expect(typeof stub.get).toBe("function");
|
||||||
|
expect(typeof stub.remove).toBe("function");
|
||||||
|
expect(typeof stub.update).toBe("function");
|
||||||
|
expect(typeof stub.list).toBe("function");
|
||||||
|
expect(typeof stub.history).toBe("function");
|
||||||
|
expect(typeof stub.close).toBe("function");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("VarSetOptions shape", () => {
|
||||||
|
const opts: VarSetOptions = { tags: { k: "v" }, labels: ["l"] };
|
||||||
|
expect(opts.tags?.k).toBe("v");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("VarListOptions extends ListOptions", () => {
|
||||||
|
const opts: VarListOptions = {
|
||||||
|
namePrefix: "@x/",
|
||||||
|
exactName: "@x/y",
|
||||||
|
schema: "0",
|
||||||
|
tags: { k: "v" },
|
||||||
|
labels: ["l"],
|
||||||
|
sort: "created",
|
||||||
|
desc: true,
|
||||||
|
limit: 10,
|
||||||
|
offset: 0,
|
||||||
|
};
|
||||||
|
expect(opts.namePrefix).toBe("@x/");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("HistoryEntry shape", () => {
|
||||||
|
const e: HistoryEntry = { value: "0", position: 0, setAt: 0 };
|
||||||
|
expect(e.position).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("TagStore type", () => {
|
||||||
|
test("TagStore shape", () => {
|
||||||
|
const stub: TagStore = {
|
||||||
|
tag: (_t: Hash, _ops: TagOp[]): Tag[] => [],
|
||||||
|
untag: (_t: Hash, _k: string[]): void => {},
|
||||||
|
tags: (_t: Hash): Tag[] => [],
|
||||||
|
listByTag: (_t: string, _o?: ListOptions): Hash[] => [],
|
||||||
|
};
|
||||||
|
expect(typeof stub.tag).toBe("function");
|
||||||
|
expect(typeof stub.untag).toBe("function");
|
||||||
|
expect(typeof stub.tags).toBe("function");
|
||||||
|
expect(typeof stub.listByTag).toBe("function");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Tag and TagOp shapes", () => {
|
||||||
|
const t: Tag = { key: "k", value: "v", target: "0", created: 0 };
|
||||||
|
const tNull: Tag = { key: "label", value: null, target: "0", created: 0 };
|
||||||
|
const op1: TagOp = { op: "set", key: "k", value: "v" };
|
||||||
|
const op2: TagOp = { op: "delete", key: "k" };
|
||||||
|
expect(t.value).toBe("v");
|
||||||
|
expect(tNull.value).toBeNull();
|
||||||
|
expect(op1.op).toBe("set");
|
||||||
|
expect(op2.op).toBe("delete");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Aggregate OcasStore type", () => {
|
||||||
|
test("has cas, var, tag fields", () => {
|
||||||
|
type _AssertCas = OcasStore["cas"] extends CasStore ? true : false;
|
||||||
|
type _AssertVar = OcasStore["var"] extends VarStore ? true : false;
|
||||||
|
type _AssertTag = OcasStore["tag"] extends TagStore ? true : false;
|
||||||
|
const a: _AssertCas = true;
|
||||||
|
const b: _AssertVar = true;
|
||||||
|
const c: _AssertTag = true;
|
||||||
|
expect(a && b && c).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("source convention", () => {
|
||||||
|
test("types.ts uses 'type' not 'interface' for new types", () => {
|
||||||
|
const src = readFileSync(join(import.meta.dir, "types.ts"), "utf8");
|
||||||
|
for (const name of ["CasStore", "VarStore", "TagStore"]) {
|
||||||
|
expect(src).toMatch(new RegExp(`export\\s+type\\s+${name}\\b`));
|
||||||
|
expect(src).not.toMatch(new RegExp(`interface\\s+${name}\\b`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Exports surface", () => {
|
||||||
|
test("@ocas/core exports the new type names (compile-time only)", () => {
|
||||||
|
type _C = CasStore extends object ? true : never;
|
||||||
|
type _V = VarStore extends object ? true : never;
|
||||||
|
type _T = TagStore extends object ? true : never;
|
||||||
|
type _O = OcasStore extends object ? true : never;
|
||||||
|
const _checks: [_C, _V, _T, _O] = [true, true, true, true];
|
||||||
|
expect(_checks.length).toBe(4);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { Variable } from "./variable.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 13-character uppercase Crockford Base32 string produced by XXH64.
|
* 13-character uppercase Crockford Base32 string produced by XXH64.
|
||||||
*/
|
*/
|
||||||
@@ -58,3 +60,98 @@ export type Store = {
|
|||||||
listSchemas(options?: ListOptions): ListEntry[];
|
listSchemas(options?: ListOptions): ListEntry[];
|
||||||
delete(hash: Hash): void;
|
delete(hash: Hash): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronous content-addressable store interface (new unified design).
|
||||||
|
* Unlike legacy `Store`, `put` returns the hash synchronously.
|
||||||
|
*/
|
||||||
|
export type CasStore = {
|
||||||
|
get(hash: Hash): CasNode | null;
|
||||||
|
put(typeHash: Hash, payload: unknown): Hash;
|
||||||
|
has(hash: Hash): boolean;
|
||||||
|
delete(hash: Hash): boolean;
|
||||||
|
listByType(typeHash: Hash, options?: ListOptions): ListEntry[];
|
||||||
|
listMeta(options?: ListOptions): ListEntry[];
|
||||||
|
listSchemas(options?: ListOptions): ListEntry[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for setting/updating a variable.
|
||||||
|
*/
|
||||||
|
export type VarSetOptions = {
|
||||||
|
tags?: Record<string, string>;
|
||||||
|
labels?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for listing variables.
|
||||||
|
*/
|
||||||
|
export type VarListOptions = ListOptions & {
|
||||||
|
namePrefix?: string;
|
||||||
|
exactName?: string;
|
||||||
|
schema?: Hash;
|
||||||
|
tags?: Record<string, string>;
|
||||||
|
labels?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One entry in a variable's history (most-recent-first per `position`).
|
||||||
|
*/
|
||||||
|
export type HistoryEntry = {
|
||||||
|
value: Hash;
|
||||||
|
position: number;
|
||||||
|
setAt: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable store interface — mutable bindings (name, schema) → hash.
|
||||||
|
*/
|
||||||
|
export type VarStore = {
|
||||||
|
set(name: string, hash: Hash, options?: VarSetOptions): Variable;
|
||||||
|
get(name: string, schema?: Hash): Variable | null;
|
||||||
|
remove(name: string, schema?: Hash): Variable[];
|
||||||
|
update(name: string, hash: Hash, options?: VarSetOptions): Variable;
|
||||||
|
list(options?: VarListOptions): Variable[];
|
||||||
|
history(name: string, schema?: Hash): HistoryEntry[];
|
||||||
|
close(): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tag attached to a CAS target. `value === null` indicates a label
|
||||||
|
* (bare identifier); otherwise a key-value tag.
|
||||||
|
*/
|
||||||
|
export type Tag = {
|
||||||
|
key: string;
|
||||||
|
value: string | null;
|
||||||
|
target: Hash;
|
||||||
|
created: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tag mutation operation: set (with value) or delete (key only).
|
||||||
|
*/
|
||||||
|
export type TagOp = {
|
||||||
|
op: "set" | "delete";
|
||||||
|
key: string;
|
||||||
|
value?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag store interface — manages key-value tags and labels on CAS targets.
|
||||||
|
*/
|
||||||
|
export type TagStore = {
|
||||||
|
tag(target: Hash, operations: TagOp[]): Tag[];
|
||||||
|
untag(target: Hash, keys: string[]): void;
|
||||||
|
tags(target: Hash): Tag[];
|
||||||
|
listByTag(tag: string, options?: ListOptions): Hash[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregate OCAS store: bundles CAS, variable, and tag stores.
|
||||||
|
* Named `OcasStore` to avoid colliding with the legacy `Store` export.
|
||||||
|
*/
|
||||||
|
export type OcasStore = {
|
||||||
|
cas: CasStore;
|
||||||
|
var: VarStore;
|
||||||
|
tag: TagStore;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user