@prometheus-ags/entity-graph-sdl 3.0.0-alpha.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/dist/index.d.cts +92 -0
- package/dist/index.d.ts +92 -0
- package/dist/index.js +92 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +88 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +35 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @prometheus-ags/entity-graph-sdl
|
|
3
|
+
*
|
|
4
|
+
* The Schema Definition Language (SDL) for the entity graph: a stable JSON/TOML
|
|
5
|
+
* schema format + a parser that produces a validated intermediate
|
|
6
|
+
* representation (IR). Every code generator (Rust CLI, Flutter, framework
|
|
7
|
+
* bindings) consumes the SAME IR so generated targets never diverge.
|
|
8
|
+
*
|
|
9
|
+
* Format (schema.json):
|
|
10
|
+
* {
|
|
11
|
+
* "version": "1.0",
|
|
12
|
+
* "entities": {
|
|
13
|
+
* "user": {
|
|
14
|
+
* "fields": { "id": {"type":"string","primary":true}, "name": {"type":"string","required":true} },
|
|
15
|
+
* "relations": { "orders": {"type":"hasMany","target":"order","foreignKey":"userId"} }
|
|
16
|
+
* }
|
|
17
|
+
* },
|
|
18
|
+
* "config": { "localFirst": {"engine":"pglite","sync":"electric"}, "ai": {"mcp":true,"a2a":true} }
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
type SdlScalarType = "string" | "number" | "integer" | "decimal" | "boolean" | "datetime" | "date" | "json" | "enum" | "uuid";
|
|
22
|
+
interface SdlFieldDef {
|
|
23
|
+
type: SdlScalarType;
|
|
24
|
+
primary?: boolean;
|
|
25
|
+
required?: boolean;
|
|
26
|
+
unique?: boolean;
|
|
27
|
+
default?: unknown;
|
|
28
|
+
/** For enum fields. */
|
|
29
|
+
values?: string[];
|
|
30
|
+
/** Auto-managed by the store/server (e.g. createdAt). */
|
|
31
|
+
auto?: boolean;
|
|
32
|
+
}
|
|
33
|
+
type SdlRelationKind = "belongsTo" | "hasMany" | "manyToMany";
|
|
34
|
+
interface SdlRelationDef {
|
|
35
|
+
type: SdlRelationKind;
|
|
36
|
+
target: string;
|
|
37
|
+
foreignKey?: string;
|
|
38
|
+
/** For manyToMany. */
|
|
39
|
+
through?: string;
|
|
40
|
+
}
|
|
41
|
+
interface SdlEntityDef {
|
|
42
|
+
fields: Record<string, SdlFieldDef>;
|
|
43
|
+
relations?: Record<string, SdlRelationDef>;
|
|
44
|
+
/** Optional table name override (defaults to the entity key). */
|
|
45
|
+
table?: string;
|
|
46
|
+
}
|
|
47
|
+
interface SdlConfig {
|
|
48
|
+
localFirst?: {
|
|
49
|
+
engine?: "pglite" | "sqlite" | "none";
|
|
50
|
+
sync?: "electric" | "yjs" | "none";
|
|
51
|
+
};
|
|
52
|
+
ai?: {
|
|
53
|
+
mcp?: boolean;
|
|
54
|
+
a2a?: boolean;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
interface SdlDocument {
|
|
58
|
+
version: string;
|
|
59
|
+
entities: Record<string, SdlEntityDef>;
|
|
60
|
+
config?: SdlConfig;
|
|
61
|
+
}
|
|
62
|
+
interface IrField extends SdlFieldDef {
|
|
63
|
+
name: string;
|
|
64
|
+
}
|
|
65
|
+
interface IrRelation extends SdlRelationDef {
|
|
66
|
+
name: string;
|
|
67
|
+
}
|
|
68
|
+
interface IrEntity {
|
|
69
|
+
name: string;
|
|
70
|
+
table: string;
|
|
71
|
+
primaryKey: string;
|
|
72
|
+
fields: IrField[];
|
|
73
|
+
relations: IrRelation[];
|
|
74
|
+
}
|
|
75
|
+
interface EntityGraphIR {
|
|
76
|
+
version: string;
|
|
77
|
+
entities: IrEntity[];
|
|
78
|
+
config: SdlConfig;
|
|
79
|
+
}
|
|
80
|
+
declare class SdlValidationError extends Error {
|
|
81
|
+
readonly path: string;
|
|
82
|
+
constructor(message: string, path: string);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Parse + validate an SDL document into the generator IR. Throws
|
|
86
|
+
* {@link SdlValidationError} with a precise path on the first problem.
|
|
87
|
+
*/
|
|
88
|
+
declare function parseSdl(doc: unknown): EntityGraphIR;
|
|
89
|
+
/** Parse SDL from a JSON string. */
|
|
90
|
+
declare function parseSdlJson(json: string): EntityGraphIR;
|
|
91
|
+
|
|
92
|
+
export { type EntityGraphIR, type IrEntity, type IrField, type IrRelation, type SdlConfig, type SdlDocument, type SdlEntityDef, type SdlFieldDef, type SdlRelationDef, type SdlRelationKind, type SdlScalarType, SdlValidationError, parseSdl, parseSdlJson };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @prometheus-ags/entity-graph-sdl
|
|
3
|
+
*
|
|
4
|
+
* The Schema Definition Language (SDL) for the entity graph: a stable JSON/TOML
|
|
5
|
+
* schema format + a parser that produces a validated intermediate
|
|
6
|
+
* representation (IR). Every code generator (Rust CLI, Flutter, framework
|
|
7
|
+
* bindings) consumes the SAME IR so generated targets never diverge.
|
|
8
|
+
*
|
|
9
|
+
* Format (schema.json):
|
|
10
|
+
* {
|
|
11
|
+
* "version": "1.0",
|
|
12
|
+
* "entities": {
|
|
13
|
+
* "user": {
|
|
14
|
+
* "fields": { "id": {"type":"string","primary":true}, "name": {"type":"string","required":true} },
|
|
15
|
+
* "relations": { "orders": {"type":"hasMany","target":"order","foreignKey":"userId"} }
|
|
16
|
+
* }
|
|
17
|
+
* },
|
|
18
|
+
* "config": { "localFirst": {"engine":"pglite","sync":"electric"}, "ai": {"mcp":true,"a2a":true} }
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
type SdlScalarType = "string" | "number" | "integer" | "decimal" | "boolean" | "datetime" | "date" | "json" | "enum" | "uuid";
|
|
22
|
+
interface SdlFieldDef {
|
|
23
|
+
type: SdlScalarType;
|
|
24
|
+
primary?: boolean;
|
|
25
|
+
required?: boolean;
|
|
26
|
+
unique?: boolean;
|
|
27
|
+
default?: unknown;
|
|
28
|
+
/** For enum fields. */
|
|
29
|
+
values?: string[];
|
|
30
|
+
/** Auto-managed by the store/server (e.g. createdAt). */
|
|
31
|
+
auto?: boolean;
|
|
32
|
+
}
|
|
33
|
+
type SdlRelationKind = "belongsTo" | "hasMany" | "manyToMany";
|
|
34
|
+
interface SdlRelationDef {
|
|
35
|
+
type: SdlRelationKind;
|
|
36
|
+
target: string;
|
|
37
|
+
foreignKey?: string;
|
|
38
|
+
/** For manyToMany. */
|
|
39
|
+
through?: string;
|
|
40
|
+
}
|
|
41
|
+
interface SdlEntityDef {
|
|
42
|
+
fields: Record<string, SdlFieldDef>;
|
|
43
|
+
relations?: Record<string, SdlRelationDef>;
|
|
44
|
+
/** Optional table name override (defaults to the entity key). */
|
|
45
|
+
table?: string;
|
|
46
|
+
}
|
|
47
|
+
interface SdlConfig {
|
|
48
|
+
localFirst?: {
|
|
49
|
+
engine?: "pglite" | "sqlite" | "none";
|
|
50
|
+
sync?: "electric" | "yjs" | "none";
|
|
51
|
+
};
|
|
52
|
+
ai?: {
|
|
53
|
+
mcp?: boolean;
|
|
54
|
+
a2a?: boolean;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
interface SdlDocument {
|
|
58
|
+
version: string;
|
|
59
|
+
entities: Record<string, SdlEntityDef>;
|
|
60
|
+
config?: SdlConfig;
|
|
61
|
+
}
|
|
62
|
+
interface IrField extends SdlFieldDef {
|
|
63
|
+
name: string;
|
|
64
|
+
}
|
|
65
|
+
interface IrRelation extends SdlRelationDef {
|
|
66
|
+
name: string;
|
|
67
|
+
}
|
|
68
|
+
interface IrEntity {
|
|
69
|
+
name: string;
|
|
70
|
+
table: string;
|
|
71
|
+
primaryKey: string;
|
|
72
|
+
fields: IrField[];
|
|
73
|
+
relations: IrRelation[];
|
|
74
|
+
}
|
|
75
|
+
interface EntityGraphIR {
|
|
76
|
+
version: string;
|
|
77
|
+
entities: IrEntity[];
|
|
78
|
+
config: SdlConfig;
|
|
79
|
+
}
|
|
80
|
+
declare class SdlValidationError extends Error {
|
|
81
|
+
readonly path: string;
|
|
82
|
+
constructor(message: string, path: string);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Parse + validate an SDL document into the generator IR. Throws
|
|
86
|
+
* {@link SdlValidationError} with a precise path on the first problem.
|
|
87
|
+
*/
|
|
88
|
+
declare function parseSdl(doc: unknown): EntityGraphIR;
|
|
89
|
+
/** Parse SDL from a JSON string. */
|
|
90
|
+
declare function parseSdlJson(json: string): EntityGraphIR;
|
|
91
|
+
|
|
92
|
+
export { type EntityGraphIR, type IrEntity, type IrField, type IrRelation, type SdlConfig, type SdlDocument, type SdlEntityDef, type SdlFieldDef, type SdlRelationDef, type SdlRelationKind, type SdlScalarType, SdlValidationError, parseSdl, parseSdlJson };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
var SdlValidationError = class extends Error {
|
|
5
|
+
path;
|
|
6
|
+
constructor(message, path) {
|
|
7
|
+
super(`${message} (at ${path})`);
|
|
8
|
+
this.name = "SdlValidationError";
|
|
9
|
+
this.path = path;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var SCALAR_TYPES = /* @__PURE__ */ new Set([
|
|
13
|
+
"string",
|
|
14
|
+
"number",
|
|
15
|
+
"integer",
|
|
16
|
+
"decimal",
|
|
17
|
+
"boolean",
|
|
18
|
+
"datetime",
|
|
19
|
+
"date",
|
|
20
|
+
"json",
|
|
21
|
+
"enum",
|
|
22
|
+
"uuid"
|
|
23
|
+
]);
|
|
24
|
+
function parseSdl(doc) {
|
|
25
|
+
if (!isObject(doc)) throw new SdlValidationError("SDL root must be an object", "$");
|
|
26
|
+
const version = typeof doc.version === "string" ? doc.version : "1.0";
|
|
27
|
+
if (!isObject(doc.entities)) throw new SdlValidationError("`entities` must be an object", "$.entities");
|
|
28
|
+
const entities = [];
|
|
29
|
+
for (const [name, raw] of Object.entries(doc.entities)) {
|
|
30
|
+
const path = `$.entities.${name}`;
|
|
31
|
+
if (!isObject(raw) || !isObject(raw.fields)) {
|
|
32
|
+
throw new SdlValidationError("entity must have a `fields` object", path);
|
|
33
|
+
}
|
|
34
|
+
const def = raw;
|
|
35
|
+
const fields = [];
|
|
36
|
+
let primaryKey = null;
|
|
37
|
+
for (const [fname, fdefRaw] of Object.entries(def.fields)) {
|
|
38
|
+
const fpath = `${path}.fields.${fname}`;
|
|
39
|
+
if (!isObject(fdefRaw)) throw new SdlValidationError("field must be an object", fpath);
|
|
40
|
+
const fdef = fdefRaw;
|
|
41
|
+
if (!SCALAR_TYPES.has(fdef.type)) throw new SdlValidationError(`unknown field type "${fdef.type}"`, fpath);
|
|
42
|
+
if (fdef.type === "enum" && (!Array.isArray(fdef.values) || fdef.values.length === 0)) {
|
|
43
|
+
throw new SdlValidationError("enum field requires non-empty `values`", fpath);
|
|
44
|
+
}
|
|
45
|
+
if (fdef.primary) {
|
|
46
|
+
if (primaryKey) throw new SdlValidationError("multiple primary keys not supported", fpath);
|
|
47
|
+
primaryKey = fname;
|
|
48
|
+
}
|
|
49
|
+
fields.push({ ...fdef, name: fname });
|
|
50
|
+
}
|
|
51
|
+
if (!primaryKey) throw new SdlValidationError("entity needs exactly one `primary: true` field", path);
|
|
52
|
+
const relations = [];
|
|
53
|
+
for (const [rname, rdefRaw] of Object.entries(def.relations ?? {})) {
|
|
54
|
+
const rpath = `${path}.relations.${rname}`;
|
|
55
|
+
if (!isObject(rdefRaw)) throw new SdlValidationError("relation must be an object", rpath);
|
|
56
|
+
const rdef = rdefRaw;
|
|
57
|
+
if (!["belongsTo", "hasMany", "manyToMany"].includes(rdef.type)) {
|
|
58
|
+
throw new SdlValidationError(`unknown relation type "${rdef.type}"`, rpath);
|
|
59
|
+
}
|
|
60
|
+
if (typeof rdef.target !== "string") throw new SdlValidationError("relation needs a `target`", rpath);
|
|
61
|
+
relations.push({ ...rdef, name: rname });
|
|
62
|
+
}
|
|
63
|
+
entities.push({ name, table: def.table ?? name, primaryKey, fields, relations });
|
|
64
|
+
}
|
|
65
|
+
const names = new Set(entities.map((e) => e.name));
|
|
66
|
+
for (const e of entities) {
|
|
67
|
+
for (const r of e.relations) {
|
|
68
|
+
if (!names.has(r.target)) {
|
|
69
|
+
throw new SdlValidationError(`relation target "${r.target}" is not a defined entity`, `$.entities.${e.name}.relations.${r.name}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { version, entities, config: doc.config ?? {} };
|
|
74
|
+
}
|
|
75
|
+
function parseSdlJson(json) {
|
|
76
|
+
let doc;
|
|
77
|
+
try {
|
|
78
|
+
doc = JSON.parse(json);
|
|
79
|
+
} catch (cause) {
|
|
80
|
+
throw new SdlValidationError("invalid JSON", "$");
|
|
81
|
+
}
|
|
82
|
+
return parseSdl(doc);
|
|
83
|
+
}
|
|
84
|
+
function isObject(v) {
|
|
85
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
exports.SdlValidationError = SdlValidationError;
|
|
89
|
+
exports.parseSdl = parseSdl;
|
|
90
|
+
exports.parseSdlJson = parseSdlJson;
|
|
91
|
+
//# sourceMappingURL=index.js.map
|
|
92
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @prometheus-ags/entity-graph-sdl\n *\n * The Schema Definition Language (SDL) for the entity graph: a stable JSON/TOML\n * schema format + a parser that produces a validated intermediate\n * representation (IR). Every code generator (Rust CLI, Flutter, framework\n * bindings) consumes the SAME IR so generated targets never diverge.\n *\n * Format (schema.json):\n * {\n * \"version\": \"1.0\",\n * \"entities\": {\n * \"user\": {\n * \"fields\": { \"id\": {\"type\":\"string\",\"primary\":true}, \"name\": {\"type\":\"string\",\"required\":true} },\n * \"relations\": { \"orders\": {\"type\":\"hasMany\",\"target\":\"order\",\"foreignKey\":\"userId\"} }\n * }\n * },\n * \"config\": { \"localFirst\": {\"engine\":\"pglite\",\"sync\":\"electric\"}, \"ai\": {\"mcp\":true,\"a2a\":true} }\n * }\n */\n\n// ── Source format types ───────────────────────────────────────────────────\n\nexport type SdlScalarType =\n | \"string\" | \"number\" | \"integer\" | \"decimal\" | \"boolean\"\n | \"datetime\" | \"date\" | \"json\" | \"enum\" | \"uuid\";\n\nexport interface SdlFieldDef {\n type: SdlScalarType;\n primary?: boolean;\n required?: boolean;\n unique?: boolean;\n default?: unknown;\n /** For enum fields. */\n values?: string[];\n /** Auto-managed by the store/server (e.g. createdAt). */\n auto?: boolean;\n}\n\nexport type SdlRelationKind = \"belongsTo\" | \"hasMany\" | \"manyToMany\";\n\nexport interface SdlRelationDef {\n type: SdlRelationKind;\n target: string;\n foreignKey?: string;\n /** For manyToMany. */\n through?: string;\n}\n\nexport interface SdlEntityDef {\n fields: Record<string, SdlFieldDef>;\n relations?: Record<string, SdlRelationDef>;\n /** Optional table name override (defaults to the entity key). */\n table?: string;\n}\n\nexport interface SdlConfig {\n localFirst?: { engine?: \"pglite\" | \"sqlite\" | \"none\"; sync?: \"electric\" | \"yjs\" | \"none\" };\n ai?: { mcp?: boolean; a2a?: boolean };\n}\n\nexport interface SdlDocument {\n version: string;\n entities: Record<string, SdlEntityDef>;\n config?: SdlConfig;\n}\n\n// ── Validated IR (what generators consume) ────────────────────────────────\n\nexport interface IrField extends SdlFieldDef {\n name: string;\n}\nexport interface IrRelation extends SdlRelationDef {\n name: string;\n}\nexport interface IrEntity {\n name: string;\n table: string;\n primaryKey: string;\n fields: IrField[];\n relations: IrRelation[];\n}\nexport interface EntityGraphIR {\n version: string;\n entities: IrEntity[];\n config: SdlConfig;\n}\n\nexport class SdlValidationError extends Error {\n readonly path: string;\n constructor(message: string, path: string) {\n super(`${message} (at ${path})`);\n this.name = \"SdlValidationError\";\n this.path = path;\n }\n}\n\nconst SCALAR_TYPES = new Set<SdlScalarType>([\n \"string\", \"number\", \"integer\", \"decimal\", \"boolean\", \"datetime\", \"date\", \"json\", \"enum\", \"uuid\",\n]);\n\n/**\n * Parse + validate an SDL document into the generator IR. Throws\n * {@link SdlValidationError} with a precise path on the first problem.\n */\nexport function parseSdl(doc: unknown): EntityGraphIR {\n if (!isObject(doc)) throw new SdlValidationError(\"SDL root must be an object\", \"$\");\n const version = typeof doc.version === \"string\" ? doc.version : \"1.0\";\n if (!isObject(doc.entities)) throw new SdlValidationError(\"`entities` must be an object\", \"$.entities\");\n\n const entities: IrEntity[] = [];\n for (const [name, raw] of Object.entries(doc.entities)) {\n const path = `$.entities.${name}`;\n if (!isObject(raw) || !isObject(raw.fields)) {\n throw new SdlValidationError(\"entity must have a `fields` object\", path);\n }\n const def = raw as unknown as SdlEntityDef;\n\n const fields: IrField[] = [];\n let primaryKey: string | null = null;\n for (const [fname, fdefRaw] of Object.entries(def.fields)) {\n const fpath = `${path}.fields.${fname}`;\n if (!isObject(fdefRaw)) throw new SdlValidationError(\"field must be an object\", fpath);\n const fdef = fdefRaw as SdlFieldDef;\n if (!SCALAR_TYPES.has(fdef.type)) throw new SdlValidationError(`unknown field type \"${fdef.type}\"`, fpath);\n if (fdef.type === \"enum\" && (!Array.isArray(fdef.values) || fdef.values.length === 0)) {\n throw new SdlValidationError(\"enum field requires non-empty `values`\", fpath);\n }\n if (fdef.primary) {\n if (primaryKey) throw new SdlValidationError(\"multiple primary keys not supported\", fpath);\n primaryKey = fname;\n }\n fields.push({ ...fdef, name: fname });\n }\n if (!primaryKey) throw new SdlValidationError(\"entity needs exactly one `primary: true` field\", path);\n\n const relations: IrRelation[] = [];\n for (const [rname, rdefRaw] of Object.entries(def.relations ?? {})) {\n const rpath = `${path}.relations.${rname}`;\n if (!isObject(rdefRaw)) throw new SdlValidationError(\"relation must be an object\", rpath);\n const rdef = rdefRaw as SdlRelationDef;\n if (![\"belongsTo\", \"hasMany\", \"manyToMany\"].includes(rdef.type)) {\n throw new SdlValidationError(`unknown relation type \"${rdef.type}\"`, rpath);\n }\n if (typeof rdef.target !== \"string\") throw new SdlValidationError(\"relation needs a `target`\", rpath);\n relations.push({ ...rdef, name: rname });\n }\n\n entities.push({ name, table: def.table ?? name, primaryKey, fields, relations });\n }\n\n // Cross-reference relation targets.\n const names = new Set(entities.map((e) => e.name));\n for (const e of entities) {\n for (const r of e.relations) {\n if (!names.has(r.target)) {\n throw new SdlValidationError(`relation target \"${r.target}\" is not a defined entity`, `$.entities.${e.name}.relations.${r.name}`);\n }\n }\n }\n\n return { version, entities, config: (doc.config as SdlConfig) ?? {} };\n}\n\n/** Parse SDL from a JSON string. */\nexport function parseSdlJson(json: string): EntityGraphIR {\n let doc: unknown;\n try { doc = JSON.parse(json); } catch (cause) {\n throw new SdlValidationError(\"invalid JSON\", \"$\");\n }\n return parseSdl(doc);\n}\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var SdlValidationError = class extends Error {
|
|
3
|
+
path;
|
|
4
|
+
constructor(message, path) {
|
|
5
|
+
super(`${message} (at ${path})`);
|
|
6
|
+
this.name = "SdlValidationError";
|
|
7
|
+
this.path = path;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var SCALAR_TYPES = /* @__PURE__ */ new Set([
|
|
11
|
+
"string",
|
|
12
|
+
"number",
|
|
13
|
+
"integer",
|
|
14
|
+
"decimal",
|
|
15
|
+
"boolean",
|
|
16
|
+
"datetime",
|
|
17
|
+
"date",
|
|
18
|
+
"json",
|
|
19
|
+
"enum",
|
|
20
|
+
"uuid"
|
|
21
|
+
]);
|
|
22
|
+
function parseSdl(doc) {
|
|
23
|
+
if (!isObject(doc)) throw new SdlValidationError("SDL root must be an object", "$");
|
|
24
|
+
const version = typeof doc.version === "string" ? doc.version : "1.0";
|
|
25
|
+
if (!isObject(doc.entities)) throw new SdlValidationError("`entities` must be an object", "$.entities");
|
|
26
|
+
const entities = [];
|
|
27
|
+
for (const [name, raw] of Object.entries(doc.entities)) {
|
|
28
|
+
const path = `$.entities.${name}`;
|
|
29
|
+
if (!isObject(raw) || !isObject(raw.fields)) {
|
|
30
|
+
throw new SdlValidationError("entity must have a `fields` object", path);
|
|
31
|
+
}
|
|
32
|
+
const def = raw;
|
|
33
|
+
const fields = [];
|
|
34
|
+
let primaryKey = null;
|
|
35
|
+
for (const [fname, fdefRaw] of Object.entries(def.fields)) {
|
|
36
|
+
const fpath = `${path}.fields.${fname}`;
|
|
37
|
+
if (!isObject(fdefRaw)) throw new SdlValidationError("field must be an object", fpath);
|
|
38
|
+
const fdef = fdefRaw;
|
|
39
|
+
if (!SCALAR_TYPES.has(fdef.type)) throw new SdlValidationError(`unknown field type "${fdef.type}"`, fpath);
|
|
40
|
+
if (fdef.type === "enum" && (!Array.isArray(fdef.values) || fdef.values.length === 0)) {
|
|
41
|
+
throw new SdlValidationError("enum field requires non-empty `values`", fpath);
|
|
42
|
+
}
|
|
43
|
+
if (fdef.primary) {
|
|
44
|
+
if (primaryKey) throw new SdlValidationError("multiple primary keys not supported", fpath);
|
|
45
|
+
primaryKey = fname;
|
|
46
|
+
}
|
|
47
|
+
fields.push({ ...fdef, name: fname });
|
|
48
|
+
}
|
|
49
|
+
if (!primaryKey) throw new SdlValidationError("entity needs exactly one `primary: true` field", path);
|
|
50
|
+
const relations = [];
|
|
51
|
+
for (const [rname, rdefRaw] of Object.entries(def.relations ?? {})) {
|
|
52
|
+
const rpath = `${path}.relations.${rname}`;
|
|
53
|
+
if (!isObject(rdefRaw)) throw new SdlValidationError("relation must be an object", rpath);
|
|
54
|
+
const rdef = rdefRaw;
|
|
55
|
+
if (!["belongsTo", "hasMany", "manyToMany"].includes(rdef.type)) {
|
|
56
|
+
throw new SdlValidationError(`unknown relation type "${rdef.type}"`, rpath);
|
|
57
|
+
}
|
|
58
|
+
if (typeof rdef.target !== "string") throw new SdlValidationError("relation needs a `target`", rpath);
|
|
59
|
+
relations.push({ ...rdef, name: rname });
|
|
60
|
+
}
|
|
61
|
+
entities.push({ name, table: def.table ?? name, primaryKey, fields, relations });
|
|
62
|
+
}
|
|
63
|
+
const names = new Set(entities.map((e) => e.name));
|
|
64
|
+
for (const e of entities) {
|
|
65
|
+
for (const r of e.relations) {
|
|
66
|
+
if (!names.has(r.target)) {
|
|
67
|
+
throw new SdlValidationError(`relation target "${r.target}" is not a defined entity`, `$.entities.${e.name}.relations.${r.name}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { version, entities, config: doc.config ?? {} };
|
|
72
|
+
}
|
|
73
|
+
function parseSdlJson(json) {
|
|
74
|
+
let doc;
|
|
75
|
+
try {
|
|
76
|
+
doc = JSON.parse(json);
|
|
77
|
+
} catch (cause) {
|
|
78
|
+
throw new SdlValidationError("invalid JSON", "$");
|
|
79
|
+
}
|
|
80
|
+
return parseSdl(doc);
|
|
81
|
+
}
|
|
82
|
+
function isObject(v) {
|
|
83
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { SdlValidationError, parseSdl, parseSdlJson };
|
|
87
|
+
//# sourceMappingURL=index.mjs.map
|
|
88
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @prometheus-ags/entity-graph-sdl\n *\n * The Schema Definition Language (SDL) for the entity graph: a stable JSON/TOML\n * schema format + a parser that produces a validated intermediate\n * representation (IR). Every code generator (Rust CLI, Flutter, framework\n * bindings) consumes the SAME IR so generated targets never diverge.\n *\n * Format (schema.json):\n * {\n * \"version\": \"1.0\",\n * \"entities\": {\n * \"user\": {\n * \"fields\": { \"id\": {\"type\":\"string\",\"primary\":true}, \"name\": {\"type\":\"string\",\"required\":true} },\n * \"relations\": { \"orders\": {\"type\":\"hasMany\",\"target\":\"order\",\"foreignKey\":\"userId\"} }\n * }\n * },\n * \"config\": { \"localFirst\": {\"engine\":\"pglite\",\"sync\":\"electric\"}, \"ai\": {\"mcp\":true,\"a2a\":true} }\n * }\n */\n\n// ── Source format types ───────────────────────────────────────────────────\n\nexport type SdlScalarType =\n | \"string\" | \"number\" | \"integer\" | \"decimal\" | \"boolean\"\n | \"datetime\" | \"date\" | \"json\" | \"enum\" | \"uuid\";\n\nexport interface SdlFieldDef {\n type: SdlScalarType;\n primary?: boolean;\n required?: boolean;\n unique?: boolean;\n default?: unknown;\n /** For enum fields. */\n values?: string[];\n /** Auto-managed by the store/server (e.g. createdAt). */\n auto?: boolean;\n}\n\nexport type SdlRelationKind = \"belongsTo\" | \"hasMany\" | \"manyToMany\";\n\nexport interface SdlRelationDef {\n type: SdlRelationKind;\n target: string;\n foreignKey?: string;\n /** For manyToMany. */\n through?: string;\n}\n\nexport interface SdlEntityDef {\n fields: Record<string, SdlFieldDef>;\n relations?: Record<string, SdlRelationDef>;\n /** Optional table name override (defaults to the entity key). */\n table?: string;\n}\n\nexport interface SdlConfig {\n localFirst?: { engine?: \"pglite\" | \"sqlite\" | \"none\"; sync?: \"electric\" | \"yjs\" | \"none\" };\n ai?: { mcp?: boolean; a2a?: boolean };\n}\n\nexport interface SdlDocument {\n version: string;\n entities: Record<string, SdlEntityDef>;\n config?: SdlConfig;\n}\n\n// ── Validated IR (what generators consume) ────────────────────────────────\n\nexport interface IrField extends SdlFieldDef {\n name: string;\n}\nexport interface IrRelation extends SdlRelationDef {\n name: string;\n}\nexport interface IrEntity {\n name: string;\n table: string;\n primaryKey: string;\n fields: IrField[];\n relations: IrRelation[];\n}\nexport interface EntityGraphIR {\n version: string;\n entities: IrEntity[];\n config: SdlConfig;\n}\n\nexport class SdlValidationError extends Error {\n readonly path: string;\n constructor(message: string, path: string) {\n super(`${message} (at ${path})`);\n this.name = \"SdlValidationError\";\n this.path = path;\n }\n}\n\nconst SCALAR_TYPES = new Set<SdlScalarType>([\n \"string\", \"number\", \"integer\", \"decimal\", \"boolean\", \"datetime\", \"date\", \"json\", \"enum\", \"uuid\",\n]);\n\n/**\n * Parse + validate an SDL document into the generator IR. Throws\n * {@link SdlValidationError} with a precise path on the first problem.\n */\nexport function parseSdl(doc: unknown): EntityGraphIR {\n if (!isObject(doc)) throw new SdlValidationError(\"SDL root must be an object\", \"$\");\n const version = typeof doc.version === \"string\" ? doc.version : \"1.0\";\n if (!isObject(doc.entities)) throw new SdlValidationError(\"`entities` must be an object\", \"$.entities\");\n\n const entities: IrEntity[] = [];\n for (const [name, raw] of Object.entries(doc.entities)) {\n const path = `$.entities.${name}`;\n if (!isObject(raw) || !isObject(raw.fields)) {\n throw new SdlValidationError(\"entity must have a `fields` object\", path);\n }\n const def = raw as unknown as SdlEntityDef;\n\n const fields: IrField[] = [];\n let primaryKey: string | null = null;\n for (const [fname, fdefRaw] of Object.entries(def.fields)) {\n const fpath = `${path}.fields.${fname}`;\n if (!isObject(fdefRaw)) throw new SdlValidationError(\"field must be an object\", fpath);\n const fdef = fdefRaw as SdlFieldDef;\n if (!SCALAR_TYPES.has(fdef.type)) throw new SdlValidationError(`unknown field type \"${fdef.type}\"`, fpath);\n if (fdef.type === \"enum\" && (!Array.isArray(fdef.values) || fdef.values.length === 0)) {\n throw new SdlValidationError(\"enum field requires non-empty `values`\", fpath);\n }\n if (fdef.primary) {\n if (primaryKey) throw new SdlValidationError(\"multiple primary keys not supported\", fpath);\n primaryKey = fname;\n }\n fields.push({ ...fdef, name: fname });\n }\n if (!primaryKey) throw new SdlValidationError(\"entity needs exactly one `primary: true` field\", path);\n\n const relations: IrRelation[] = [];\n for (const [rname, rdefRaw] of Object.entries(def.relations ?? {})) {\n const rpath = `${path}.relations.${rname}`;\n if (!isObject(rdefRaw)) throw new SdlValidationError(\"relation must be an object\", rpath);\n const rdef = rdefRaw as SdlRelationDef;\n if (![\"belongsTo\", \"hasMany\", \"manyToMany\"].includes(rdef.type)) {\n throw new SdlValidationError(`unknown relation type \"${rdef.type}\"`, rpath);\n }\n if (typeof rdef.target !== \"string\") throw new SdlValidationError(\"relation needs a `target`\", rpath);\n relations.push({ ...rdef, name: rname });\n }\n\n entities.push({ name, table: def.table ?? name, primaryKey, fields, relations });\n }\n\n // Cross-reference relation targets.\n const names = new Set(entities.map((e) => e.name));\n for (const e of entities) {\n for (const r of e.relations) {\n if (!names.has(r.target)) {\n throw new SdlValidationError(`relation target \"${r.target}\" is not a defined entity`, `$.entities.${e.name}.relations.${r.name}`);\n }\n }\n }\n\n return { version, entities, config: (doc.config as SdlConfig) ?? {} };\n}\n\n/** Parse SDL from a JSON string. */\nexport function parseSdlJson(json: string): EntityGraphIR {\n let doc: unknown;\n try { doc = JSON.parse(json); } catch (cause) {\n throw new SdlValidationError(\"invalid JSON\", \"$\");\n }\n return parseSdl(doc);\n}\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@prometheus-ags/entity-graph-sdl",
|
|
3
|
+
"version": "3.0.0-alpha.0",
|
|
4
|
+
"description": "Schema Definition Language (SDL) for the entity graph — the shared contract every code generator (Rust CLI, Flutter, framework bindings) consumes.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.mjs",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
|
+
"require": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"entity-graph",
|
|
26
|
+
"schema",
|
|
27
|
+
"sdl",
|
|
28
|
+
"codegen"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsup",
|
|
32
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
33
|
+
"test": "vitest run"
|
|
34
|
+
}
|
|
35
|
+
}
|