@metaobjectsdev/sdk 0.9.0-rc.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/agent-context/README.md +14 -0
- package/agent-context/servers/csharp.meta.json +5 -0
- package/agent-context/servers/java.meta.json +5 -0
- package/agent-context/servers/kotlin.meta.json +5 -0
- package/agent-context/servers/python.meta.json +5 -0
- package/agent-context/servers/typescript.meta.json +5 -0
- package/agent-context/skills/metaobjects-authoring/SKILL.md +308 -0
- package/agent-context/skills/metaobjects-codegen/SKILL.md +99 -0
- package/agent-context/skills/metaobjects-codegen/references/csharp.md +87 -0
- package/agent-context/skills/metaobjects-codegen/references/java.md +94 -0
- package/agent-context/skills/metaobjects-codegen/references/kotlin.md +110 -0
- package/agent-context/skills/metaobjects-codegen/references/typescript.md +135 -0
- package/agent-context/skills/metaobjects-prompts/SKILL.md +148 -0
- package/agent-context/skills/metaobjects-prompts/references/csharp.md +110 -0
- package/agent-context/skills/metaobjects-prompts/references/java.md +108 -0
- package/agent-context/skills/metaobjects-prompts/references/kotlin.md +130 -0
- package/agent-context/skills/metaobjects-prompts/references/python.md +116 -0
- package/agent-context/skills/metaobjects-prompts/references/typescript.md +150 -0
- package/agent-context/skills/metaobjects-runtime-ui/SKILL.md +130 -0
- package/agent-context/skills/metaobjects-runtime-ui/references/java.md +96 -0
- package/agent-context/skills/metaobjects-runtime-ui/references/kotlin.md +99 -0
- package/agent-context/skills/metaobjects-runtime-ui/references/react.md +86 -0
- package/agent-context/skills/metaobjects-runtime-ui/references/tanstack.md +119 -0
- package/agent-context/skills/metaobjects-runtime-ui/references/typescript.md +92 -0
- package/agent-context/skills/metaobjects-verify/SKILL.md +107 -0
- package/agent-context/skills/metaobjects-verify/references/migration.md +72 -0
- package/agent-context/templates/always-on.md.mustache +27 -0
- package/dist/agent-context/assemble.d.ts +7 -0
- package/dist/agent-context/assemble.d.ts.map +1 -0
- package/dist/agent-context/assemble.js +61 -0
- package/dist/agent-context/assemble.js.map +1 -0
- package/dist/agent-context/content-root.d.ts +8 -0
- package/dist/agent-context/content-root.d.ts.map +1 -0
- package/dist/agent-context/content-root.js +35 -0
- package/dist/agent-context/content-root.js.map +1 -0
- package/dist/agent-context/index.d.ts +6 -0
- package/dist/agent-context/index.d.ts.map +1 -0
- package/dist/agent-context/index.js +6 -0
- package/dist/agent-context/index.js.map +1 -0
- package/dist/agent-context/resolve.d.ts +13 -0
- package/dist/agent-context/resolve.d.ts.map +1 -0
- package/dist/agent-context/resolve.js +30 -0
- package/dist/agent-context/resolve.js.map +1 -0
- package/dist/agent-context/scaffold.d.ts +60 -0
- package/dist/agent-context/scaffold.d.ts.map +1 -0
- package/dist/agent-context/scaffold.js +61 -0
- package/dist/agent-context/scaffold.js.map +1 -0
- package/dist/agent-context/types.d.ts +21 -0
- package/dist/agent-context/types.d.ts.map +1 -0
- package/dist/agent-context/types.js +12 -0
- package/dist/agent-context/types.js.map +1 -0
- package/dist/agent-docs/body.d.ts +5 -1
- package/dist/agent-docs/body.d.ts.map +1 -1
- package/dist/agent-docs/body.js +7 -3
- package/dist/agent-docs/body.js.map +1 -1
- package/dist/forge-types.js +2 -2
- package/dist/forge-types.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +3 -1
- package/dist/memory.js.map +1 -1
- package/dist/storage/errors.d.ts +3 -3
- package/dist/storage/errors.d.ts.map +1 -1
- package/dist/storage/errors.js +6 -6
- package/dist/storage/errors.js.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +1 -1
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/lifecycle.js +4 -4
- package/dist/storage/lifecycle.js.map +1 -1
- package/dist/storage/read.js +4 -4
- package/dist/storage/read.js.map +1 -1
- package/dist/storage/write.js +2 -2
- package/dist/storage/write.js.map +1 -1
- package/package.json +40 -22
- package/scripts/bundle-agent-context.mjs +19 -0
- package/src/agent-context/assemble.ts +68 -0
- package/src/agent-context/content-root.ts +35 -0
- package/src/agent-context/index.ts +5 -0
- package/src/agent-context/resolve.ts +33 -0
- package/src/agent-context/scaffold.ts +103 -0
- package/src/agent-context/types.ts +31 -0
- package/src/agent-docs/body.ts +7 -3
- package/src/forge-types.ts +2 -2
- package/src/index.ts +6 -3
- package/src/memory.ts +3 -1
- package/src/storage/errors.ts +6 -6
- package/src/storage/index.ts +3 -3
- package/src/storage/lifecycle.ts +4 -4
- package/src/storage/read.ts +4 -4
- package/src/storage/write.ts +2 -2
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import type { AssembledFile, Stack } from "./types.js";
|
|
3
|
+
|
|
4
|
+
/** Consumer-relative path of the sidecar manifest that tracks scaffolded files. */
|
|
5
|
+
export const AGENT_CONTEXT_MANIFEST_PATH = ".metaobjects/.agent-context.json";
|
|
6
|
+
|
|
7
|
+
export interface Manifest {
|
|
8
|
+
version: 1;
|
|
9
|
+
/**
|
|
10
|
+
* The MetaObjects version that last scaffolded this agent context. Used to nudge
|
|
11
|
+
* a re-scaffold when the installed version moves ahead (the skills/docs ship with
|
|
12
|
+
* the package, so an upgrade can leave the copied-in context stale). Optional for
|
|
13
|
+
* back-compat with manifests written before version tracking existed.
|
|
14
|
+
*/
|
|
15
|
+
generatedBy?: string;
|
|
16
|
+
servers: string[];
|
|
17
|
+
clients: string[];
|
|
18
|
+
/** consumer-relative path → sha256 of the contents as last scaffolded. */
|
|
19
|
+
files: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function hashContents(s: string): string {
|
|
23
|
+
return createHash("sha256").update(s, "utf8").digest("hex");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ScaffoldDecision {
|
|
27
|
+
/** files to (over)write at their own path: new, or unmodified-since-last-scaffold. */
|
|
28
|
+
writes: { path: string; contents: string }[];
|
|
29
|
+
/** hand-edited files: write the fresh contents to `<path>.new`, leave the original. */
|
|
30
|
+
conflicts: { path: string; newPath: string; contents: string }[];
|
|
31
|
+
/** the manifest to persist after writing. */
|
|
32
|
+
manifest: Manifest;
|
|
33
|
+
/** paths the prior manifest tracked that are no longer assembled (e.g. stack shrank) — reported, never auto-deleted. */
|
|
34
|
+
removed: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Decide what to write for a (re-)scaffold. Pure: filesystem access is via the
|
|
39
|
+
* `readCurrent` callback (returns the on-disk contents, or undefined if absent).
|
|
40
|
+
* A file is safe to overwrite iff it is absent, or its on-disk hash still equals
|
|
41
|
+
* the hash the prior manifest recorded (i.e. the user hasn't hand-edited it).
|
|
42
|
+
*/
|
|
43
|
+
export function planScaffold(opts: {
|
|
44
|
+
stack: Stack;
|
|
45
|
+
assembled: AssembledFile[];
|
|
46
|
+
prior: Manifest | undefined;
|
|
47
|
+
readCurrent: (path: string) => string | undefined;
|
|
48
|
+
/** The MetaObjects version doing the scaffold — stamped into the manifest. */
|
|
49
|
+
generatedBy: string;
|
|
50
|
+
}): ScaffoldDecision {
|
|
51
|
+
const { stack, assembled, prior, readCurrent, generatedBy } = opts;
|
|
52
|
+
const writes: ScaffoldDecision["writes"] = [];
|
|
53
|
+
const conflicts: ScaffoldDecision["conflicts"] = [];
|
|
54
|
+
const files: Record<string, string> = {};
|
|
55
|
+
|
|
56
|
+
for (const f of assembled) {
|
|
57
|
+
files[f.path] = hashContents(f.contents);
|
|
58
|
+
const current = readCurrent(f.path);
|
|
59
|
+
if (current === undefined) {
|
|
60
|
+
writes.push({ path: f.path, contents: f.contents });
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const priorHash = prior?.files[f.path];
|
|
64
|
+
if (priorHash !== undefined && hashContents(current) === priorHash) {
|
|
65
|
+
writes.push({ path: f.path, contents: f.contents }); // unmodified → refresh to latest
|
|
66
|
+
} else {
|
|
67
|
+
conflicts.push({ path: f.path, newPath: `${f.path}.new`, contents: f.contents });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const assembledPaths = new Set(assembled.map((f) => f.path));
|
|
72
|
+
const removed = prior ? Object.keys(prior.files).filter((p) => !assembledPaths.has(p)) : [];
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
writes,
|
|
76
|
+
conflicts,
|
|
77
|
+
manifest: { version: 1, generatedBy, servers: stack.servers, clients: stack.clients, files },
|
|
78
|
+
removed,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A one-line nudge if the scaffolded agent context predates the installed MetaObjects
|
|
84
|
+
* (so `gen`/`verify` can remind the user to refresh the skills after an upgrade), or
|
|
85
|
+
* `null` when there is nothing to say — no agent context scaffolded, or it is in sync.
|
|
86
|
+
* Advisory only: never throws, never blocks, never writes.
|
|
87
|
+
*/
|
|
88
|
+
export function agentContextStaleness(opts: {
|
|
89
|
+
manifest: Manifest | undefined;
|
|
90
|
+
currentVersion: string;
|
|
91
|
+
}): string | null {
|
|
92
|
+
const { manifest, currentVersion } = opts;
|
|
93
|
+
if (manifest === undefined) return null; // no agent context here → nothing to nudge
|
|
94
|
+
// Exact-equality on purpose: ANY drift nudges (a re-scaffold is cheap + idempotent).
|
|
95
|
+
// Don't "fix" this into a semver compare — a prerelease/build-metadata difference is
|
|
96
|
+
// still a reason to refresh, and the nudge is advisory, never a gate.
|
|
97
|
+
if (manifest.generatedBy === currentVersion) return null; // in sync
|
|
98
|
+
const from = manifest.generatedBy ?? "an older MetaObjects";
|
|
99
|
+
return (
|
|
100
|
+
`MetaObjects agent context was generated by ${from}; you're on ${currentVersion}. ` +
|
|
101
|
+
`Re-run 'meta init --docs-only --refresh-docs' to refresh the .claude/skills docs.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const SERVER_LANGS = ["typescript", "java", "kotlin", "csharp", "python"] as const;
|
|
2
|
+
export type ServerLang = (typeof SERVER_LANGS)[number];
|
|
3
|
+
|
|
4
|
+
export const CLIENT_FRAMEWORKS = ["react", "tanstack", "angular"] as const;
|
|
5
|
+
export type ClientFramework = (typeof CLIENT_FRAMEWORKS)[number];
|
|
6
|
+
|
|
7
|
+
/** Always-present token: schema migrations are TS-owned for every port (ADR-0015). */
|
|
8
|
+
export const MIGRATION_TOKEN = "migration";
|
|
9
|
+
|
|
10
|
+
export const SKILL_NAMES = [
|
|
11
|
+
"metaobjects-authoring",
|
|
12
|
+
"metaobjects-codegen",
|
|
13
|
+
"metaobjects-runtime-ui",
|
|
14
|
+
"metaobjects-prompts",
|
|
15
|
+
"metaobjects-verify",
|
|
16
|
+
] as const;
|
|
17
|
+
export type SkillName = (typeof SKILL_NAMES)[number];
|
|
18
|
+
|
|
19
|
+
/** The resolved tech-stack of a consumer project. */
|
|
20
|
+
export interface Stack {
|
|
21
|
+
servers: ServerLang[]; // deduped, in SERVER_LANGS order
|
|
22
|
+
clients: ClientFramework[]; // deduped, in CLIENT_FRAMEWORKS order
|
|
23
|
+
/** servers ∪ clients ∪ {"migration"} — the install-selection set for reference fragments. */
|
|
24
|
+
tokens: ReadonlySet<string>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A file the assembler emits, path relative to the consumer project root. */
|
|
28
|
+
export interface AssembledFile {
|
|
29
|
+
path: string; // e.g. ".metaobjects/AGENTS.md", ".claude/skills/metaobjects-codegen/references/java.md"
|
|
30
|
+
contents: string;
|
|
31
|
+
}
|
package/src/agent-docs/body.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
// Agent reference docs body. Scaffolded into .metaobjects/AGENTS.md and CLAUDE.md by `meta init`.
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated The single-blob agent doc is replaced by the assembled agent-context
|
|
4
|
+
* (see `@metaobjectsdev/sdk/agent-context`). Kept only for back-compat; not scaffolded by `meta init`.
|
|
5
|
+
*/
|
|
2
6
|
export const AGENT_DOCS_BODY = `# Meta Forge — agent reference
|
|
3
7
|
|
|
4
8
|
This file is scaffolded by \`meta init\` and lives alongside your \`metaobjects/\` records. It teaches AI coding assistants (Claude Code, Codex, etc.) how to read and modify MetaObjects metadata correctly. Refresh after CLI updates with \`meta init --refresh-docs\`.
|
|
@@ -148,7 +152,7 @@ The v0.2 keys (\`super\`, \`overlay\`, \`override\`, \`isInterface\`, \`implemen
|
|
|
148
152
|
### Package paths and inheritance
|
|
149
153
|
|
|
150
154
|
- Package segments separated by \`::\` — \`acme::common::id\`
|
|
151
|
-
- Relative references in \`extends:\` — \`..::common::id\` means "go up to parent package, descend into \`common::id\`"
|
|
155
|
+
- Relative references in \`extends:\` — \`..::common::id\` means "go up to parent package, descend into \`common::id\`". Relative forms (\`..::\` parent-relative, leading \`::\` root-absolute) are a **YAML-authoring affordance only**; canonical JSON must be fully-qualified (a relative ref in JSON is rejected with \`ERR_RELATIVE_REF_IN_CANONICAL\`).
|
|
152
156
|
- Cross-file resolution works as long as all files are passed to Loader (or live in the same \`metaobjects/\` directory)
|
|
153
157
|
|
|
154
158
|
### Two special intercepted attrs (parser-routed)
|
|
@@ -318,7 +322,7 @@ When a list needs computed columns (counts, sums, joined fields), create a **pro
|
|
|
318
322
|
{ "origin": { "subType": "aggregate",
|
|
319
323
|
"@agg": "count", "@of": "Week.id", "@via": "Program.weeks" }}
|
|
320
324
|
]}},
|
|
321
|
-
{ "identity": { "subType": "primary", "@fields": "id" } }
|
|
325
|
+
{ "identity": { "subType": "primary", "name": "id", "@fields": "id" } }
|
|
322
326
|
]
|
|
323
327
|
}
|
|
324
328
|
}
|
|
@@ -517,7 +521,7 @@ metaobjects.config.ts generator wiring (committed)
|
|
|
517
521
|
"@forgeSource": "human",
|
|
518
522
|
"@forgePrimaryLocation": "src/db/users.schema.ts",
|
|
519
523
|
"children": [
|
|
520
|
-
{"field": {"name": "id", "extends": "
|
|
524
|
+
{"field": {"name": "id", "extends": "common::id"}},
|
|
521
525
|
{"field": {"name": "email", "subType": "string",
|
|
522
526
|
"@column": "email_address",
|
|
523
527
|
"children": [{"validator": {"subType": "required"}}]
|
package/src/forge-types.ts
CHANGED
|
@@ -114,7 +114,7 @@ import {
|
|
|
114
114
|
} from "@metaobjectsdev/metadata";
|
|
115
115
|
|
|
116
116
|
/** Minimal concrete MetaData subclass used for all forge descriptive nodes. */
|
|
117
|
-
class
|
|
117
|
+
class ForgeNode extends MetaData {}
|
|
118
118
|
|
|
119
119
|
function wildcardOf(childType: string): ChildRule {
|
|
120
120
|
return {
|
|
@@ -133,7 +133,7 @@ function def(
|
|
|
133
133
|
return {
|
|
134
134
|
typeId: new TypeId(type, subType),
|
|
135
135
|
description,
|
|
136
|
-
factory: (typeId, name) => new
|
|
136
|
+
factory: (typeId, name) => new ForgeNode(typeId, name),
|
|
137
137
|
childRules,
|
|
138
138
|
attributes: [],
|
|
139
139
|
};
|
package/src/index.ts
CHANGED
|
@@ -16,9 +16,9 @@ export {
|
|
|
16
16
|
listRecords,
|
|
17
17
|
promoteRecord,
|
|
18
18
|
supersede,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
ForgeRecordNotFoundError,
|
|
20
|
+
ForgeAlreadyPromotedError,
|
|
21
|
+
ForgeRecordParseError,
|
|
22
22
|
} from "./storage/index.js";
|
|
23
23
|
export type { ListOptions } from "./storage/index.js";
|
|
24
24
|
|
|
@@ -103,3 +103,6 @@ export {
|
|
|
103
103
|
PACKAGE_MANIFEST_FILE,
|
|
104
104
|
} from "./package.js";
|
|
105
105
|
export type { PackageManifest } from "./package.js";
|
|
106
|
+
|
|
107
|
+
// Agent context — stack resolver, file assembler, and vocabulary types
|
|
108
|
+
export * from "./agent-context/index.js";
|
package/src/memory.ts
CHANGED
|
@@ -97,7 +97,9 @@ export async function loadMemory(
|
|
|
97
97
|
// against the merged tree afterwards) — dep packages first, current last.
|
|
98
98
|
const paths = await collectMetadataPaths(repoRoot);
|
|
99
99
|
|
|
100
|
-
const loader = new MetaDataLoader({
|
|
100
|
+
const loader = new MetaDataLoader({
|
|
101
|
+
registry,
|
|
102
|
+
});
|
|
101
103
|
const result = await loader.load(paths.map((p) => new FileSource(p)));
|
|
102
104
|
|
|
103
105
|
if (result.errors.length > 0) {
|
package/src/storage/errors.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
export class
|
|
1
|
+
export class ForgeRecordNotFoundError extends Error {
|
|
2
2
|
constructor(public readonly path: string) {
|
|
3
3
|
super(`Record not found: ${path}`);
|
|
4
|
-
this.name = "
|
|
4
|
+
this.name = "ForgeRecordNotFoundError";
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export class
|
|
8
|
+
export class ForgeAlreadyPromotedError extends Error {
|
|
9
9
|
constructor(public readonly path: string) {
|
|
10
10
|
super(`A canonical record already exists at: ${path}`);
|
|
11
|
-
this.name = "
|
|
11
|
+
this.name = "ForgeAlreadyPromotedError";
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export class
|
|
15
|
+
export class ForgeRecordParseError extends Error {
|
|
16
16
|
constructor(
|
|
17
17
|
public readonly path: string,
|
|
18
18
|
public override readonly cause: unknown,
|
|
19
19
|
) {
|
|
20
20
|
super(`Failed to parse record at ${path}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
21
|
-
this.name = "
|
|
21
|
+
this.name = "ForgeRecordParseError";
|
|
22
22
|
}
|
|
23
23
|
}
|
package/src/storage/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { listRecords } from "./list.js";
|
|
|
4
4
|
export type { ListOptions } from "./list.js";
|
|
5
5
|
export { promoteRecord, supersede } from "./lifecycle.js";
|
|
6
6
|
export {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
ForgeRecordNotFoundError,
|
|
8
|
+
ForgeAlreadyPromotedError,
|
|
9
|
+
ForgeRecordParseError,
|
|
10
10
|
} from "./errors.js";
|
package/src/storage/lifecycle.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { AnyRecord } from "../records/any.js";
|
|
|
2
2
|
import type { RecordType } from "../records/core.js";
|
|
3
3
|
import { readRecord, recordExists } from "./read.js";
|
|
4
4
|
import { writeRecord, removeRecord } from "./write.js";
|
|
5
|
-
import {
|
|
5
|
+
import { ForgeAlreadyPromotedError, ForgeRecordNotFoundError } from "./errors.js";
|
|
6
6
|
import { recordPath } from "../paths.js";
|
|
7
7
|
|
|
8
8
|
export async function promoteRecord(
|
|
@@ -11,10 +11,10 @@ export async function promoteRecord(
|
|
|
11
11
|
id: string,
|
|
12
12
|
): Promise<void> {
|
|
13
13
|
if (!(await recordExists(metaRoot, type, id, { pending: true }))) {
|
|
14
|
-
throw new
|
|
14
|
+
throw new ForgeRecordNotFoundError(recordPath(metaRoot, type, id, { pending: true }));
|
|
15
15
|
}
|
|
16
16
|
if (await recordExists(metaRoot, type, id)) {
|
|
17
|
-
throw new
|
|
17
|
+
throw new ForgeAlreadyPromotedError(recordPath(metaRoot, type, id));
|
|
18
18
|
}
|
|
19
19
|
const record = await readRecord(metaRoot, type, id, { pending: true });
|
|
20
20
|
await writeRecord(metaRoot, record);
|
|
@@ -27,7 +27,7 @@ export async function supersede(
|
|
|
27
27
|
newRecord: AnyRecord,
|
|
28
28
|
): Promise<void> {
|
|
29
29
|
if (!(await recordExists(metaRoot, newRecord.type, oldId))) {
|
|
30
|
-
throw new
|
|
30
|
+
throw new ForgeRecordNotFoundError(recordPath(metaRoot, newRecord.type, oldId));
|
|
31
31
|
}
|
|
32
32
|
// Write the new record first, so a failure leaves the old record intact.
|
|
33
33
|
await writeRecord(metaRoot, newRecord);
|
package/src/storage/read.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { AnyRecord } from "../records/any.js";
|
|
|
3
3
|
import { AnyRecord as AnyRecordSchema } from "../records/any.js";
|
|
4
4
|
import type { RecordType } from "../records/core.js";
|
|
5
5
|
import { recordPath } from "../paths.js";
|
|
6
|
-
import {
|
|
6
|
+
import { ForgeRecordNotFoundError, ForgeRecordParseError } from "./errors.js";
|
|
7
7
|
|
|
8
8
|
export async function readRecord(
|
|
9
9
|
metaRoot: string,
|
|
@@ -16,17 +16,17 @@ export async function readRecord(
|
|
|
16
16
|
try {
|
|
17
17
|
raw = await readFile(path, "utf8");
|
|
18
18
|
} catch (err) {
|
|
19
|
-
if (isNoEntError(err)) throw new
|
|
19
|
+
if (isNoEntError(err)) throw new ForgeRecordNotFoundError(path);
|
|
20
20
|
throw err;
|
|
21
21
|
}
|
|
22
22
|
let parsedJson: unknown;
|
|
23
23
|
try {
|
|
24
24
|
parsedJson = JSON.parse(raw);
|
|
25
25
|
} catch (err) {
|
|
26
|
-
throw new
|
|
26
|
+
throw new ForgeRecordParseError(path, err);
|
|
27
27
|
}
|
|
28
28
|
const result = AnyRecordSchema.safeParse(parsedJson);
|
|
29
|
-
if (!result.success) throw new
|
|
29
|
+
if (!result.success) throw new ForgeRecordParseError(path, result.error);
|
|
30
30
|
return result.data;
|
|
31
31
|
}
|
|
32
32
|
|
package/src/storage/write.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { dirname } from "node:path";
|
|
|
3
3
|
import type { AnyRecord } from "../records/any.js";
|
|
4
4
|
import { AnyRecord as AnyRecordSchema } from "../records/any.js";
|
|
5
5
|
import { recordPath } from "../paths.js";
|
|
6
|
-
import {
|
|
6
|
+
import { ForgeRecordNotFoundError } from "./errors.js";
|
|
7
7
|
import { recordExists } from "./read.js";
|
|
8
8
|
|
|
9
9
|
export async function writeRecord(
|
|
@@ -26,7 +26,7 @@ export async function removeRecord(
|
|
|
26
26
|
opts: { pending?: boolean } = {},
|
|
27
27
|
): Promise<void> {
|
|
28
28
|
if (!(await recordExists(metaRoot, type, id, opts))) {
|
|
29
|
-
throw new
|
|
29
|
+
throw new ForgeRecordNotFoundError(recordPath(metaRoot, type, id, opts));
|
|
30
30
|
}
|
|
31
31
|
await unlink(recordPath(metaRoot, type, id, opts));
|
|
32
32
|
}
|