Merge pull request 'feat: bootstrap writes to varStore' (#21) from feat/17-bootstrap-varstore into main
This commit was merged in pull request #21.
This commit is contained in:
+66
-28
@@ -156,31 +156,37 @@ async function readStdinJson(): Promise<unknown> {
|
||||
/**
|
||||
* Open the filesystem-backed CAS store.
|
||||
* Automatically creates directory and bootstraps if needed.
|
||||
* If a varStore is provided, builtin schema aliases are written to it during bootstrap.
|
||||
*/
|
||||
async function openStore(): Promise<Store> {
|
||||
async function openStore(varStore?: VariableStore): Promise<Store> {
|
||||
const fullPath = resolve(storePath);
|
||||
return await openFsStore(fullPath);
|
||||
return await openFsStore(fullPath, varStore);
|
||||
}
|
||||
|
||||
async function openVarStore(): Promise<VariableStore> {
|
||||
const store = await openStore();
|
||||
return createVariableStore(resolve(varDbPath), store);
|
||||
const varStore = createVariableStore(resolve(varDbPath), store);
|
||||
// Populate varStore with builtin schema aliases (idempotent).
|
||||
await bootstrap(store, varStore);
|
||||
return varStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a type-hash, handling @ aliases
|
||||
* If the input starts with @, resolve it via bootstrap
|
||||
* Otherwise, return the hash as-is
|
||||
* Resolve a type-hash, handling @ aliases via varStore lookup.
|
||||
*/
|
||||
async function resolveTypeHash(typeHashOrAlias: string): Promise<Hash> {
|
||||
if (typeHashOrAlias.startsWith("@")) {
|
||||
const store = await openStore();
|
||||
const builtinSchemas = await bootstrap(store);
|
||||
const resolvedHash = builtinSchemas[typeHashOrAlias];
|
||||
if (!resolvedHash) {
|
||||
die(`Schema not found: ${typeHashOrAlias}`);
|
||||
const varStore = await openVarStore();
|
||||
try {
|
||||
const variants = varStore.list({ exactName: typeHashOrAlias });
|
||||
const first = variants[0];
|
||||
if (!first) {
|
||||
die(`Schema not found: ${typeHashOrAlias}`);
|
||||
}
|
||||
return first.value;
|
||||
} finally {
|
||||
varStore.close();
|
||||
}
|
||||
return resolvedHash;
|
||||
}
|
||||
return typeHashOrAlias;
|
||||
}
|
||||
@@ -235,8 +241,7 @@ async function cmdPut(args: string[]): Promise<void> {
|
||||
|
||||
// Schema nodes: use putSchema() which validates via isValidSchema() (recursive)
|
||||
// instead of ajv against meta-schema (which can't express recursive constraints)
|
||||
const builtinSchemas = await bootstrap(store);
|
||||
const metaHash = builtinSchemas["@ocas/schema"];
|
||||
const metaHash = await resolveTypeHash("@ocas/schema");
|
||||
if (typeHash === metaHash) {
|
||||
try {
|
||||
const hash = await putSchema(store, payload as Record<string, unknown>);
|
||||
@@ -283,7 +288,10 @@ async function cmdHas(args: string[]): Promise<void> {
|
||||
const hash = args[0];
|
||||
if (!hash) die("Usage: ocas has <hash>");
|
||||
const store = await openStore();
|
||||
await out(await wrapEnvelope(store, "@ocas/output/has", store.has(hash)), store);
|
||||
await out(
|
||||
await wrapEnvelope(store, "@ocas/output/has", store.has(hash)),
|
||||
store,
|
||||
);
|
||||
}
|
||||
|
||||
async function cmdVerify(args: string[]): Promise<void> {
|
||||
@@ -345,7 +353,10 @@ async function cmdWalk(args: string[]): Promise<void> {
|
||||
}
|
||||
|
||||
printNode(hash, "", true);
|
||||
await out(await wrapEnvelope(store, "@ocas/output/walk", lines.join("\n")), store);
|
||||
await out(
|
||||
await wrapEnvelope(store, "@ocas/output/walk", lines.join("\n")),
|
||||
store,
|
||||
);
|
||||
} else {
|
||||
const hashes: Hash[] = [];
|
||||
walk(store, hash, (h) => {
|
||||
@@ -523,7 +534,10 @@ async function cmdVarSet(args: string[]): Promise<void> {
|
||||
: undefined;
|
||||
|
||||
const variable = varStore.set(name, value, options);
|
||||
await out(await wrapEnvelope(store, "@ocas/output/var-set", variable), store);
|
||||
await out(
|
||||
await wrapEnvelope(store, "@ocas/output/var-set", variable),
|
||||
store,
|
||||
);
|
||||
} catch (e) {
|
||||
if (
|
||||
e instanceof InvalidVariableNameError ||
|
||||
@@ -554,7 +568,10 @@ async function cmdVarGet(args: string[]): Promise<void> {
|
||||
if (variable === null) {
|
||||
die(`Error: Variable not found: name=${name}, schema=${schema}`);
|
||||
}
|
||||
await out(await wrapEnvelope(store, "@ocas/output/var-get", variable), store);
|
||||
await out(
|
||||
await wrapEnvelope(store, "@ocas/output/var-get", variable),
|
||||
store,
|
||||
);
|
||||
} finally {
|
||||
varStore.close();
|
||||
}
|
||||
@@ -579,11 +596,17 @@ async function cmdVarDelete(args: string[]): Promise<void> {
|
||||
if (schema !== undefined) {
|
||||
// Precise deletion: remove specific (name, schema) variant
|
||||
const variable = varStore.remove(name, schema);
|
||||
await out(await wrapEnvelope(store, "@ocas/output/var-delete", variable), store);
|
||||
await out(
|
||||
await wrapEnvelope(store, "@ocas/output/var-delete", variable),
|
||||
store,
|
||||
);
|
||||
} else {
|
||||
// Batch deletion: remove all variants for this name
|
||||
const variables = varStore.remove(name);
|
||||
await out(await wrapEnvelope(store, "@ocas/output/var-delete", variables), store);
|
||||
await out(
|
||||
await wrapEnvelope(store, "@ocas/output/var-delete", variables),
|
||||
store,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof VariableNotFoundError) {
|
||||
@@ -620,7 +643,10 @@ async function cmdVarTag(args: string[]): Promise<void> {
|
||||
delete: deleteNames.length > 0 ? deleteNames : undefined,
|
||||
});
|
||||
|
||||
await out(await wrapEnvelope(store, "@ocas/output/var-tag", variable), store);
|
||||
await out(
|
||||
await wrapEnvelope(store, "@ocas/output/var-tag", variable),
|
||||
store,
|
||||
);
|
||||
} catch (e) {
|
||||
if (
|
||||
e instanceof VariableNotFoundError ||
|
||||
@@ -663,7 +689,10 @@ async function cmdVarList(args: string[]): Promise<void> {
|
||||
tags: Object.keys(tags).length > 0 ? tags : undefined,
|
||||
labels: labels.length > 0 ? labels : undefined,
|
||||
});
|
||||
await out(await wrapEnvelope(store, "@ocas/output/var-list", variables), store);
|
||||
await out(
|
||||
await wrapEnvelope(store, "@ocas/output/var-list", variables),
|
||||
store,
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof InvalidVariableNameError) {
|
||||
die(`Error: ${e.message}`);
|
||||
@@ -733,7 +762,8 @@ async function cmdTemplateSet(args: string[]): Promise<void> {
|
||||
schemaHash,
|
||||
contentHash,
|
||||
}),
|
||||
store);
|
||||
store,
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof CasNodeNotFoundError) {
|
||||
die(`Error: ${e.message}`);
|
||||
@@ -775,7 +805,8 @@ async function cmdTemplateGet(args: string[]): Promise<void> {
|
||||
"@ocas/output/template-get",
|
||||
node.payload as string,
|
||||
),
|
||||
store);
|
||||
store,
|
||||
);
|
||||
} finally {
|
||||
varStore.close();
|
||||
}
|
||||
@@ -797,7 +828,10 @@ async function cmdTemplateList(_args: string[]): Promise<void> {
|
||||
contentHash: v.value,
|
||||
}));
|
||||
|
||||
await out(await wrapEnvelope(store, "@ocas/output/template-list", templates), store);
|
||||
await out(
|
||||
await wrapEnvelope(store, "@ocas/output/template-list", templates),
|
||||
store,
|
||||
);
|
||||
} finally {
|
||||
varStore.close();
|
||||
}
|
||||
@@ -822,7 +856,8 @@ async function cmdTemplateDelete(args: string[]): Promise<void> {
|
||||
await wrapEnvelope(store, "@ocas/output/template-delete", {
|
||||
deleted: true,
|
||||
}),
|
||||
store);
|
||||
store,
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof VariableNotFoundError) {
|
||||
die(`Error: Template not found for schema: ${schemaHash}`);
|
||||
@@ -864,7 +899,10 @@ async function cmdListMeta(_args: string[]): Promise<void> {
|
||||
async function cmdListSchema(_args: string[]): Promise<void> {
|
||||
const store = await openStore();
|
||||
const hashes = store.listSchemas();
|
||||
await out(await wrapEnvelope(store, "@ocas/output/list-schema", hashes), store);
|
||||
await out(
|
||||
await wrapEnvelope(store, "@ocas/output/list-schema", hashes),
|
||||
store,
|
||||
);
|
||||
}
|
||||
|
||||
function printUsage(): void {
|
||||
@@ -1018,4 +1056,4 @@ switch (cmd) {
|
||||
|
||||
default:
|
||||
die(`Unknown command: ${cmd}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,209 @@ exports[`Phase 3: Variable System 3.3 var list shows all variables 1`] = `
|
||||
{
|
||||
"type": "AF0XACGXHPMC1",
|
||||
"value": [
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/schema",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "CTS5P6RD8HMCS",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/string",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "7VQ43ZSJTEWA7",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/number",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "BEAZQGKVXMZT8",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/integer",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "B26JM4PBHPAFK",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/boolean",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "1AVHCXEJVDCPP",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/bool",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "1AVHCXEJVDCPP",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/object",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "944RT37WX1PQ5",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/array",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "D45CW047XS17Y",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/null",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "8E33KAS0HMAZ7",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/put",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "4ZHWK21APCFZ5",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/get",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "FB4K0SXG68ZFS",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/has",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "FHXQQZMVHW924",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/hash",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "1B24CBF95Q5G6",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/verify",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "52HEFB52BD0GF",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/refs",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "2TKP4RGBJ4V43",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/walk",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "4HG6MD3XG5H5C",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/list",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "CTCEXSNPWMAQQ",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/list-meta",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "0V41JBWK72HS3",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/list-schema",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "AW24Q8BKXQYTE",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/var-set",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "0Q5EMYK4SYSS9",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/var-get",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "7C75FQT98KKQD",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/var-delete",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "C3MYPR5RGQFZT",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/var-tag",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "9103EYRMM949A",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/var-list",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "AF0XACGXHPMC1",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/template-set",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "BJDHPAE4Q8TXM",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/template-get",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "0B0HBHZGYHR84",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/template-list",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "8917JQTD1R5JF",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/template-delete",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "BY7BGZJND3N7R",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "@ocas/output/gc",
|
||||
"schema": "CTS5P6RD8HMCS",
|
||||
"tags": {},
|
||||
"value": "7KHZTY010988K",
|
||||
},
|
||||
{
|
||||
"labels": [],
|
||||
"name": "myapp/config",
|
||||
|
||||
@@ -32,23 +32,26 @@ const OUTPUT_ALIASES = [
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe("bootstrap - Built-in Schemas", () => {
|
||||
test("should return map of 26 built-in schema aliases to hashes", async () => {
|
||||
test("should return map of 29 built-in schema aliases to hashes", async () => {
|
||||
const store = createMemoryStore();
|
||||
const builtinSchemas = await bootstrap(store);
|
||||
|
||||
// Should return object with 6 primitive + 20 output aliases = 26
|
||||
// Should return object with 9 primitive + 20 output aliases = 29
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/schema");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/string");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/number");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/integer");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/boolean");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/bool");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/object");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/array");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/bool");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/null");
|
||||
|
||||
for (const alias of OUTPUT_ALIASES) {
|
||||
expect(builtinSchemas).toHaveProperty(alias);
|
||||
}
|
||||
|
||||
expect(Object.keys(builtinSchemas)).toHaveLength(26);
|
||||
expect(Object.keys(builtinSchemas)).toHaveLength(29);
|
||||
|
||||
// All values should be valid hashes
|
||||
for (const [_alias, hash] of Object.entries(builtinSchemas)) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
isBootstrapCapableStore,
|
||||
} from "./bootstrap-capable.js";
|
||||
import type { Hash, Store } from "./types.js";
|
||||
import type { VariableStore } from "./variable-store.js";
|
||||
|
||||
const JSON_SCHEMA_TYPES = [
|
||||
"string",
|
||||
@@ -282,11 +283,18 @@ const OUTPUT_SCHEMAS: ReadonlyArray<
|
||||
|
||||
/**
|
||||
* Write the meta-schema seed node into the store and register built-in schemas.
|
||||
* The returned object contains aliases for the meta-schema, 5 primitive schemas,
|
||||
* and 18 @ocas/output/* schemas (24 total).
|
||||
* The returned object contains aliases for the meta-schema, primitive schemas,
|
||||
* and @ocas/output/* schemas.
|
||||
* Idempotent: calling bootstrap multiple times returns the same hashes.
|
||||
*
|
||||
* If a varStore is provided, all aliases are also written to it via
|
||||
* varStore.set(name, hash). This bypasses @ocas/ namespace protection
|
||||
* (protection is enforced only at the CLI layer).
|
||||
*/
|
||||
export async function bootstrap(store: Store): Promise<Record<string, Hash>> {
|
||||
export async function bootstrap(
|
||||
store: Store,
|
||||
varStore?: VariableStore,
|
||||
): Promise<Record<string, Hash>> {
|
||||
if (!isBootstrapCapableStore(store)) {
|
||||
throw new Error("Store does not support bootstrap");
|
||||
}
|
||||
@@ -297,23 +305,37 @@ export async function bootstrap(store: Store): Promise<Record<string, Hash>> {
|
||||
// 2. Register built-in primitive schemas directly (without putSchema to avoid recursion)
|
||||
const stringHash = await store.put(metaHash, { type: "string" });
|
||||
const numberHash = await store.put(metaHash, { type: "number" });
|
||||
const integerHash = await store.put(metaHash, { type: "integer" });
|
||||
const boolHash = await store.put(metaHash, { type: "boolean" });
|
||||
const objectHash = await store.put(metaHash, { type: "object" });
|
||||
const arrayHash = await store.put(metaHash, { type: "array" });
|
||||
const boolHash = await store.put(metaHash, { type: "boolean" });
|
||||
const nullHash = await store.put(metaHash, { type: "null" });
|
||||
|
||||
// 3. Register @ocas/output/* schemas
|
||||
const aliases: Record<string, Hash> = {
|
||||
"@ocas/schema": metaHash,
|
||||
"@ocas/string": stringHash,
|
||||
"@ocas/number": numberHash,
|
||||
"@ocas/integer": integerHash,
|
||||
"@ocas/boolean": boolHash,
|
||||
"@ocas/bool": boolHash,
|
||||
"@ocas/object": objectHash,
|
||||
"@ocas/array": arrayHash,
|
||||
"@ocas/bool": boolHash,
|
||||
"@ocas/null": nullHash,
|
||||
};
|
||||
|
||||
for (const [alias, schema] of OUTPUT_SCHEMAS) {
|
||||
aliases[alias] = await store.put(metaHash, schema);
|
||||
}
|
||||
|
||||
// 4. Write all aliases to varStore (when provided).
|
||||
// Idempotent: VariableStore.set is an upsert. Bypasses @ocas/ namespace
|
||||
// protection — protection is only enforced on the CLI `var set` command.
|
||||
if (varStore !== undefined) {
|
||||
for (const [name, hash] of Object.entries(aliases)) {
|
||||
varStore.set(name, hash);
|
||||
}
|
||||
}
|
||||
|
||||
return aliases;
|
||||
}
|
||||
|
||||
@@ -271,9 +271,12 @@ describe("bootstrap", () => {
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/schema");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/string");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/number");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/integer");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/boolean");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/bool");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/object");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/array");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/bool");
|
||||
expect(builtinSchemas).toHaveProperty("@ocas/null");
|
||||
|
||||
// All values should be valid hashes
|
||||
for (const hash of Object.values(builtinSchemas)) {
|
||||
@@ -281,7 +284,7 @@ describe("bootstrap", () => {
|
||||
expect(hash).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
|
||||
}
|
||||
|
||||
expect(Object.keys(builtinSchemas)).toHaveLength(26);
|
||||
expect(Object.keys(builtinSchemas)).toHaveLength(29);
|
||||
});
|
||||
|
||||
test("meta-schema node is stored and retrievable", async () => {
|
||||
@@ -318,7 +321,7 @@ describe("bootstrap", () => {
|
||||
const h2 = await bootstrap(store);
|
||||
|
||||
expect(h1).toEqual(h2);
|
||||
// All 26 built-in schemas should be typed by the meta-schema
|
||||
expect(store.listByType(h1["@ocas/schema"] ?? "")).toHaveLength(26);
|
||||
// All built-in schemas typed by the meta-schema (1 self + 7 unique primitives + 20 outputs)
|
||||
expect(store.listByType(h1["@ocas/schema"] ?? "")).toHaveLength(28);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,7 +67,7 @@ describe("createFsStore – init and bootstrap", () => {
|
||||
const h2 = await bootstrap(store);
|
||||
|
||||
expect(h1).toEqual(h2);
|
||||
expect(store.listByType(h1["@ocas/schema"] ?? "")).toHaveLength(26);
|
||||
expect(store.listByType(h1["@ocas/schema"] ?? "")).toHaveLength(28);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -10,7 +10,12 @@ import {
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import type { BootstrapCapableStore, CasNode, Hash } from "@ocas/core";
|
||||
import type {
|
||||
BootstrapCapableStore,
|
||||
CasNode,
|
||||
Hash,
|
||||
VariableStore,
|
||||
} from "@ocas/core";
|
||||
|
||||
import {
|
||||
BOOTSTRAP_STORE,
|
||||
@@ -310,10 +315,15 @@ export function createFsStore(dir: string): BootstrapCapableStore {
|
||||
* 4. Runs bootstrap (which is idempotent)
|
||||
*
|
||||
* @param dir - The directory path for the store
|
||||
* @param varStore - Optional variable store; when provided, builtin schema
|
||||
* aliases are written to it during bootstrap
|
||||
* @returns A Promise resolving to the BootstrapCapableStore
|
||||
* @throws Error if the path exists but is not a directory
|
||||
*/
|
||||
export async function openStore(dir: string): Promise<BootstrapCapableStore> {
|
||||
export async function openStore(
|
||||
dir: string,
|
||||
varStore?: VariableStore,
|
||||
): Promise<BootstrapCapableStore> {
|
||||
// Create directory if it doesn't exist
|
||||
try {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
@@ -323,7 +333,7 @@ export async function openStore(dir: string): Promise<BootstrapCapableStore> {
|
||||
if (nodeError.code === "EACCES") {
|
||||
throw new Error(`Permission denied: cannot access store at ${dir}`);
|
||||
}
|
||||
if (nodeError.code === "ENOTDIR") {
|
||||
if (nodeError.code === "ENOTDIR" || nodeError.code === "EEXIST") {
|
||||
throw new Error(`Path exists but is not a directory: ${dir}`);
|
||||
}
|
||||
}
|
||||
@@ -350,7 +360,7 @@ export async function openStore(dir: string): Promise<BootstrapCapableStore> {
|
||||
const store = createFsStore(dir);
|
||||
|
||||
// Bootstrap (idempotent)
|
||||
await bootstrap(store);
|
||||
await bootstrap(store, varStore);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user