@genetik/content 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eugene Michasiw
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # @genetik/content
2
+
3
+ Types, validation, and normalization for the Genetik content model: flat node map, entry id, validation against a schema, and inline → flat + id generation.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @genetik/content
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ - **Types**: `GenetikContent`, `ContentNode`, `SlotValue`, `GenetikContentInput`, `InlineNode` — canonical and input shapes.
14
+ - **Validation**: `validateContent(schema, content)` — validates content against a schema (block types, config, slots, link integrity).
15
+ - **Normalization**: `normalizeContent(schema, input, options?)` — flattens inline nodes (when schema allows) and assigns ids.
16
+
17
+ See the [docs](/docs/packages/content) for full API and examples.
package/dist/index.cjs ADDED
@@ -0,0 +1,268 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ let _genetik_schema = require("@genetik/schema");
3
+ let nanoid = require("nanoid");
4
+
5
+ //#region src/normalize.ts
6
+ /**
7
+ * Normalizes content that may contain inline nodes in slots (when schema allows)
8
+ * to canonical flat form. Inline nodes are flattened, assigned ids, and slot
9
+ * values become id references. Id-only slots are left unchanged.
10
+ */
11
+ function normalizeContent(schema, input, options) {
12
+ const generateId = options?.generateId ?? nanoid.nanoid;
13
+ const resultNodes = {};
14
+ for (const [nodeId, inputNode] of Object.entries(input.nodes)) {
15
+ const { node, extraNodes } = normalizeNode(schema, inputNode, nodeId, generateId);
16
+ resultNodes[nodeId] = node;
17
+ Object.assign(resultNodes, extraNodes);
18
+ }
19
+ return {
20
+ entryId: input.entryId,
21
+ nodes: resultNodes
22
+ };
23
+ }
24
+ function normalizeNode(schema, input, nodeId, generateId) {
25
+ const block = input.block;
26
+ const blockType = (0, _genetik_schema.getBlockType)(schema, block);
27
+ const config = input.config ?? {};
28
+ if (!blockType) {
29
+ const node = {
30
+ id: nodeId,
31
+ block,
32
+ config
33
+ };
34
+ for (const k of Object.keys(input)) {
35
+ if (k === "id" || k === "block" || k === "config") continue;
36
+ const v = input[k];
37
+ if (typeof v === "string" || Array.isArray(v) && v.every((x) => typeof x === "string")) node[k] = v;
38
+ }
39
+ return {
40
+ node,
41
+ extraNodes: {}
42
+ };
43
+ }
44
+ const node = {
45
+ id: nodeId,
46
+ block,
47
+ config
48
+ };
49
+ let extraNodes = {};
50
+ for (const slotDef of blockType.slots) {
51
+ const value = input[slotDef.name];
52
+ if (value === void 0 || value === null) continue;
53
+ if (slotDef.referenceMode === "id") {
54
+ node[slotDef.name] = value;
55
+ continue;
56
+ }
57
+ if (slotDef.multiple) {
58
+ const arr = Array.isArray(value) ? value : [value];
59
+ const ids = [];
60
+ for (const item of arr) if (typeof item === "string") ids.push(item);
61
+ else if (isInlineNode(item)) {
62
+ const childId = generateId();
63
+ const { node: childNode, extraNodes: childExtras } = normalizeNode(schema, item, childId, generateId);
64
+ ids.push(childId);
65
+ extraNodes = {
66
+ ...extraNodes,
67
+ ...childExtras,
68
+ [childId]: childNode
69
+ };
70
+ }
71
+ node[slotDef.name] = ids;
72
+ } else if (typeof value === "string") node[slotDef.name] = value;
73
+ else if (isInlineNode(value)) {
74
+ const childId = generateId();
75
+ const { node: childNode, extraNodes: childExtras } = normalizeNode(schema, value, childId, generateId);
76
+ node[slotDef.name] = childId;
77
+ extraNodes = {
78
+ ...extraNodes,
79
+ ...childExtras,
80
+ [childId]: childNode
81
+ };
82
+ }
83
+ }
84
+ return {
85
+ node,
86
+ extraNodes
87
+ };
88
+ }
89
+ function isInlineNode(value) {
90
+ return value !== null && typeof value === "object" && !Array.isArray(value) && "block" in value;
91
+ }
92
+
93
+ //#endregion
94
+ //#region src/parse.ts
95
+ /**
96
+ * Parses a JSON string into content and checks the minimal shape (entryId and nodes).
97
+ * Does not validate against a schema or normalize; use validateContent and
98
+ * normalizeContent for that.
99
+ *
100
+ * @param raw - JSON string (e.g. from user input or API)
101
+ * @returns ParseContentResult: either { ok: true, content } or { ok: false, error }
102
+ */
103
+ function parseContentJson(raw) {
104
+ let parsed;
105
+ try {
106
+ parsed = JSON.parse(raw);
107
+ } catch (e) {
108
+ return {
109
+ ok: false,
110
+ error: e instanceof Error ? e.message : "Invalid JSON"
111
+ };
112
+ }
113
+ if (parsed === null || typeof parsed !== "object") return {
114
+ ok: false,
115
+ error: "Content must be an object"
116
+ };
117
+ const obj = parsed;
118
+ if (typeof obj.entryId !== "string") return {
119
+ ok: false,
120
+ error: "Content must have entryId (string)"
121
+ };
122
+ if (obj.nodes === null || typeof obj.nodes !== "object") return {
123
+ ok: false,
124
+ error: "Content must have nodes (object)"
125
+ };
126
+ return {
127
+ ok: true,
128
+ content: obj
129
+ };
130
+ }
131
+
132
+ //#endregion
133
+ //#region src/validate.ts
134
+ /**
135
+ * Validates content against a schema. Checks:
136
+ * - entryId is present and exists in nodes
137
+ * - Every node has id, block, config; block type exists in schema
138
+ * - Each node's config is valid against the block's JSON Schema
139
+ * - Slot values are id or id[] per slot definition; no extra slot keys
140
+ * - All referenced ids exist in the node map (link integrity)
141
+ */
142
+ function validateContent(schema, content) {
143
+ const errors = [];
144
+ if (content === null || typeof content !== "object" || Array.isArray(content)) return {
145
+ valid: false,
146
+ errors: [{
147
+ path: "",
148
+ message: "Content must be an object"
149
+ }]
150
+ };
151
+ const c = content;
152
+ const entryId = c.entryId;
153
+ const nodes = c.nodes;
154
+ if (typeof entryId !== "string" || entryId === "") errors.push({
155
+ path: "entryId",
156
+ message: "entryId must be a non-empty string"
157
+ });
158
+ if (nodes === null || typeof nodes !== "object" || Array.isArray(nodes)) errors.push({
159
+ path: "nodes",
160
+ message: "nodes must be an object (map of id to node)"
161
+ });
162
+ const nodeMap = nodes;
163
+ const allIds = new Set(nodeMap ? Object.keys(nodeMap) : []);
164
+ if (nodeMap) for (const [key, val] of Object.entries(nodeMap)) {
165
+ const nodeError = validateNode(schema, key, val, "nodes");
166
+ if (nodeError) errors.push(nodeError);
167
+ }
168
+ if (typeof entryId === "string" && entryId !== "" && nodeMap && !(entryId in nodeMap)) errors.push({
169
+ path: "entryId",
170
+ message: `entryId "${entryId}" is not in nodes`
171
+ });
172
+ if (nodeMap) for (const [nodeKey, val] of Object.entries(nodeMap)) {
173
+ const refErrors = collectSlotReferenceErrors(nodeMap, val, `nodes.${nodeKey}`, allIds);
174
+ errors.push(...refErrors);
175
+ }
176
+ return {
177
+ valid: errors.length === 0,
178
+ errors
179
+ };
180
+ }
181
+ function validateNode(schema, mapKey, value, path) {
182
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return {
183
+ path: `${path}.${mapKey}`,
184
+ message: "Node must be an object"
185
+ };
186
+ const node = value;
187
+ const id = node.id;
188
+ const block = node.block;
189
+ const config = node.config;
190
+ if (typeof id !== "string" || id === "") return {
191
+ path: `${path}.${mapKey}.id`,
192
+ message: "Node id must be a non-empty string"
193
+ };
194
+ if (id !== mapKey) return {
195
+ path: `${path}.${mapKey}.id`,
196
+ message: `Node id "${id}" must match map key "${mapKey}"`
197
+ };
198
+ if (typeof block !== "string" || block === "") return {
199
+ path: `${path}.${mapKey}.block`,
200
+ message: "Node block must be a non-empty string"
201
+ };
202
+ const blockType = block ? (0, _genetik_schema.getBlockType)(schema, block) : void 0;
203
+ if (!blockType) return {
204
+ path: `${path}.${mapKey}.block`,
205
+ message: `Unknown block type: ${block}`
206
+ };
207
+ if (config !== void 0 && (config === null || typeof config !== "object" || Array.isArray(config))) return {
208
+ path: `${path}.${mapKey}.config`,
209
+ message: "Node config must be an object"
210
+ };
211
+ const configResult = (0, _genetik_schema.validateConfigAgainstDefinition)(blockType, config ?? {});
212
+ if (!configResult.valid && configResult.errors?.length) {
213
+ const first = configResult.errors[0];
214
+ return {
215
+ path: `${path}.${mapKey}.config`,
216
+ message: first.message ?? "Invalid config"
217
+ };
218
+ }
219
+ const slotNames = new Set(blockType.slots.map((s) => s.name));
220
+ for (const key of Object.keys(node)) {
221
+ if (key === "id" || key === "block" || key === "config") continue;
222
+ if (!slotNames.has(key)) return {
223
+ path: `${path}.${mapKey}`,
224
+ message: `Unknown slot "${key}" for block type "${block}"`
225
+ };
226
+ const slotDef = blockType.slots.find((s) => s.name === key);
227
+ if (!slotDef) continue;
228
+ const slotValue = node[key];
229
+ if (slotValue === void 0 || slotValue === null) continue;
230
+ if (slotDef.multiple) {
231
+ if (!Array.isArray(slotValue)) return {
232
+ path: `${path}.${mapKey}.${key}`,
233
+ message: `Slot "${key}" must be an array of ids (multiple: true)`
234
+ };
235
+ if (slotValue.some((v) => typeof v !== "string")) return {
236
+ path: `${path}.${mapKey}.${key}`,
237
+ message: `Slot "${key}" must contain only string ids`
238
+ };
239
+ } else if (typeof slotValue !== "string") return {
240
+ path: `${path}.${mapKey}.${key}`,
241
+ message: `Slot "${key}" must be a single id string (multiple: false)`
242
+ };
243
+ }
244
+ return null;
245
+ }
246
+ function collectSlotReferenceErrors(nodeMap, value, path, allIds) {
247
+ const errors = [];
248
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return errors;
249
+ const node = value;
250
+ for (const [key, slotValue] of Object.entries(node)) {
251
+ if (key === "id" || key === "block" || key === "config") continue;
252
+ const ids = Array.isArray(slotValue) ? slotValue : slotValue === void 0 || slotValue === null ? [] : [slotValue];
253
+ for (const id of ids) {
254
+ if (typeof id !== "string") continue;
255
+ if (!allIds.has(id)) errors.push({
256
+ path: `${path}.${key}`,
257
+ message: `Referenced id "${id}" is not in nodes (dangling reference)`
258
+ });
259
+ }
260
+ }
261
+ return errors;
262
+ }
263
+
264
+ //#endregion
265
+ exports.normalizeContent = normalizeContent;
266
+ exports.parseContentJson = parseContentJson;
267
+ exports.validateContent = validateContent;
268
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/normalize.ts","../src/parse.ts","../src/validate.ts"],"sourcesContent":["import type { GenetikSchema } from \"@genetik/schema\";\nimport { getBlockType } from \"@genetik/schema\";\nimport { nanoid } from \"nanoid\";\nimport type { ContentNode, GenetikContent, GenetikContentInput } from \"./types.js\";\n\nexport interface NormalizeOptions {\n /** Id generator for new nodes from inline. Defaults to nanoid. */\n generateId?: () => string;\n}\n\n/**\n * Normalizes content that may contain inline nodes in slots (when schema allows)\n * to canonical flat form. Inline nodes are flattened, assigned ids, and slot\n * values become id references. Id-only slots are left unchanged.\n */\nexport function normalizeContent(\n schema: GenetikSchema,\n input: GenetikContentInput,\n options?: NormalizeOptions\n): GenetikContent {\n const generateId = options?.generateId ?? nanoid;\n const resultNodes: Record<string, ContentNode> = {};\n\n for (const [nodeId, inputNode] of Object.entries(input.nodes)) {\n const { node, extraNodes } = normalizeNode(schema, inputNode as Record<string, unknown>, nodeId, generateId);\n resultNodes[nodeId] = node;\n Object.assign(resultNodes, extraNodes);\n }\n\n return {\n entryId: input.entryId,\n nodes: resultNodes,\n };\n}\n\nfunction normalizeNode(\n schema: GenetikSchema,\n input: Record<string, unknown>,\n nodeId: string,\n generateId: () => string\n): { node: ContentNode; extraNodes: Record<string, ContentNode> } {\n const block = input.block as string;\n const blockType = getBlockType(schema, block);\n const config = (input.config as Record<string, unknown>) ?? {};\n\n if (!blockType) {\n const node: ContentNode = { id: nodeId, block, config };\n for (const k of Object.keys(input)) {\n if (k === \"id\" || k === \"block\" || k === \"config\") continue;\n const v = input[k];\n if (typeof v === \"string\" || (Array.isArray(v) && v.every((x) => typeof x === \"string\"))) node[k] = v;\n }\n return { node, extraNodes: {} };\n }\n\n const node: ContentNode = { id: nodeId, block, config };\n let extraNodes: Record<string, ContentNode> = {};\n\n for (const slotDef of blockType.slots) {\n const value = input[slotDef.name];\n if (value === undefined || value === null) continue;\n\n if (slotDef.referenceMode === \"id\") {\n node[slotDef.name] = value;\n continue;\n }\n\n if (slotDef.multiple) {\n const arr = Array.isArray(value) ? value : [value];\n const ids: string[] = [];\n for (const item of arr) {\n if (typeof item === \"string\") {\n ids.push(item);\n } else if (isInlineNode(item)) {\n const childId = generateId();\n const { node: childNode, extraNodes: childExtras } = normalizeNode(\n schema,\n item as Record<string, unknown>,\n childId,\n generateId\n );\n ids.push(childId);\n extraNodes = { ...extraNodes, ...childExtras, [childId]: childNode };\n }\n }\n node[slotDef.name] = ids;\n } else {\n if (typeof value === \"string\") {\n node[slotDef.name] = value;\n } else if (isInlineNode(value)) {\n const childId = generateId();\n const { node: childNode, extraNodes: childExtras } = normalizeNode(\n schema,\n value as Record<string, unknown>,\n childId,\n generateId\n );\n node[slotDef.name] = childId;\n extraNodes = { ...extraNodes, ...childExtras, [childId]: childNode };\n }\n }\n }\n\n return { node, extraNodes };\n}\n\nfunction isInlineNode(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value) && \"block\" in value;\n}\n","import type { GenetikContent } from \"./types.js\";\n\n/**\n * Result of parsing a content JSON string. Use this when parsing user or API input\n * so you can show a clear error instead of throwing.\n */\nexport type ParseContentResult =\n | { ok: true; content: GenetikContent }\n | { ok: false; error: string };\n\n/**\n * Parses a JSON string into content and checks the minimal shape (entryId and nodes).\n * Does not validate against a schema or normalize; use validateContent and\n * normalizeContent for that.\n *\n * @param raw - JSON string (e.g. from user input or API)\n * @returns ParseContentResult: either { ok: true, content } or { ok: false, error }\n */\nexport function parseContentJson(raw: string): ParseContentResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (e) {\n return { ok: false, error: e instanceof Error ? e.message : \"Invalid JSON\" };\n }\n\n if (parsed === null || typeof parsed !== \"object\") {\n return { ok: false, error: \"Content must be an object\" };\n }\n\n const obj = parsed as Record<string, unknown>;\n if (typeof obj.entryId !== \"string\") {\n return { ok: false, error: \"Content must have entryId (string)\" };\n }\n if (obj.nodes === null || typeof obj.nodes !== \"object\") {\n return { ok: false, error: \"Content must have nodes (object)\" };\n }\n\n return { ok: true, content: obj as unknown as GenetikContent };\n}\n","import type { GenetikSchema } from \"@genetik/schema\";\nimport { getBlockType } from \"@genetik/schema\";\nimport { validateConfigAgainstDefinition } from \"@genetik/schema\";\nimport type { ContentNode } from \"./types.js\";\n\nexport interface ContentValidationError {\n /** Path to the problem (e.g. \"nodes.a\", \"nodes.a.config\"). */\n path: string;\n /** Human-readable message. */\n message: string;\n}\n\nexport interface ContentValidationResult {\n valid: boolean;\n errors: ContentValidationError[];\n}\n\n/**\n * Validates content against a schema. Checks:\n * - entryId is present and exists in nodes\n * - Every node has id, block, config; block type exists in schema\n * - Each node's config is valid against the block's JSON Schema\n * - Slot values are id or id[] per slot definition; no extra slot keys\n * - All referenced ids exist in the node map (link integrity)\n */\nexport function validateContent(\n schema: GenetikSchema,\n content: unknown\n): ContentValidationResult {\n const errors: ContentValidationError[] = [];\n\n if (content === null || typeof content !== \"object\" || Array.isArray(content)) {\n return {\n valid: false,\n errors: [{ path: \"\", message: \"Content must be an object\" }],\n };\n }\n\n const c = content as Record<string, unknown>;\n const entryId = c.entryId;\n const nodes = c.nodes;\n\n if (typeof entryId !== \"string\" || entryId === \"\") {\n errors.push({ path: \"entryId\", message: \"entryId must be a non-empty string\" });\n }\n\n if (nodes === null || typeof nodes !== \"object\" || Array.isArray(nodes)) {\n errors.push({ path: \"nodes\", message: \"nodes must be an object (map of id to node)\" });\n }\n\n const nodeMap = nodes as Record<string, unknown> | undefined;\n const allIds = new Set<string>(nodeMap ? Object.keys(nodeMap) : []);\n\n if (nodeMap) {\n for (const [key, val] of Object.entries(nodeMap)) {\n const nodeError = validateNode(schema, key, val, \"nodes\");\n if (nodeError) errors.push(nodeError);\n }\n }\n\n if (typeof entryId === \"string\" && entryId !== \"\" && nodeMap && !(entryId in nodeMap)) {\n errors.push({ path: \"entryId\", message: `entryId \"${entryId}\" is not in nodes` });\n }\n\n if (nodeMap) {\n for (const [nodeKey, val] of Object.entries(nodeMap)) {\n const refErrors = collectSlotReferenceErrors(nodeMap, val, `nodes.${nodeKey}`, allIds);\n errors.push(...refErrors);\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n}\n\nfunction validateNode(\n schema: GenetikSchema,\n mapKey: string,\n value: unknown,\n path: string\n): ContentValidationError | null {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n return { path: `${path}.${mapKey}`, message: \"Node must be an object\" };\n }\n\n const node = value as Record<string, unknown>;\n const id = node.id;\n const block = node.block;\n const config = node.config;\n\n if (typeof id !== \"string\" || id === \"\") {\n return { path: `${path}.${mapKey}.id`, message: \"Node id must be a non-empty string\" };\n }\n if (id !== mapKey) {\n return { path: `${path}.${mapKey}.id`, message: `Node id \"${id}\" must match map key \"${mapKey}\"` };\n }\n if (typeof block !== \"string\" || block === \"\") {\n return { path: `${path}.${mapKey}.block`, message: \"Node block must be a non-empty string\" };\n }\n\n const blockType = block ? getBlockType(schema, block as string) : undefined;\n if (!blockType) {\n return {\n path: `${path}.${mapKey}.block`,\n message: `Unknown block type: ${block}`,\n };\n }\n\n if (config !== undefined && (config === null || typeof config !== \"object\" || Array.isArray(config))) {\n return { path: `${path}.${mapKey}.config`, message: \"Node config must be an object\" };\n }\n\n const configResult = validateConfigAgainstDefinition(blockType, config ?? {});\n if (!configResult.valid && configResult.errors?.length) {\n const first = configResult.errors[0]!;\n return {\n path: `${path}.${mapKey}.config`,\n message: first.message ?? \"Invalid config\",\n };\n }\n\n const slotNames = new Set(blockType.slots.map((s) => s.name));\n for (const key of Object.keys(node)) {\n if (key === \"id\" || key === \"block\" || key === \"config\") continue;\n if (!slotNames.has(key)) {\n return {\n path: `${path}.${mapKey}`,\n message: `Unknown slot \"${key}\" for block type \"${block}\"`,\n };\n }\n const slotDef = blockType.slots.find((s) => s.name === key);\n if (!slotDef) continue;\n const slotValue = node[key];\n if (slotValue === undefined || slotValue === null) continue;\n if (slotDef.multiple) {\n if (!Array.isArray(slotValue)) {\n return {\n path: `${path}.${mapKey}.${key}`,\n message: `Slot \"${key}\" must be an array of ids (multiple: true)`,\n };\n }\n if (slotValue.some((v) => typeof v !== \"string\")) {\n return {\n path: `${path}.${mapKey}.${key}`,\n message: `Slot \"${key}\" must contain only string ids`,\n };\n }\n } else {\n if (typeof slotValue !== \"string\") {\n return {\n path: `${path}.${mapKey}.${key}`,\n message: `Slot \"${key}\" must be a single id string (multiple: false)`,\n };\n }\n }\n }\n\n return null;\n}\n\nfunction collectSlotReferenceErrors(\n nodeMap: Record<string, unknown>,\n value: unknown,\n path: string,\n allIds: Set<string>\n): ContentValidationError[] {\n const errors: ContentValidationError[] = [];\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) return errors;\n\n const node = value as ContentNode;\n for (const [key, slotValue] of Object.entries(node)) {\n if (key === \"id\" || key === \"block\" || key === \"config\") continue;\n const ids = Array.isArray(slotValue) ? slotValue : slotValue === undefined || slotValue === null ? [] : [slotValue];\n for (const id of ids) {\n if (typeof id !== \"string\") continue;\n if (!allIds.has(id)) {\n errors.push({\n path: `${path}.${key}`,\n message: `Referenced id \"${id}\" is not in nodes (dangling reference)`,\n });\n }\n }\n }\n return errors;\n}\n"],"mappings":"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"}
@@ -0,0 +1,128 @@
1
+ import { GenetikSchema } from "@genetik/schema";
2
+
3
+ //#region src/types.d.ts
4
+ /**
5
+ * Canonical content model: flat node map and entry point.
6
+ * Slot values are id references (string or array of ids).
7
+ */
8
+ /**
9
+ * A single node in the content tree. In canonical form, slot values
10
+ * are string (id) or string[] (ordered ids). Keys besides id, block,
11
+ * and config are slot names defined by the block type.
12
+ */
13
+ interface ContentNode {
14
+ /** Node id; must match the key in the nodes map. */
15
+ id: string;
16
+ /** Block type name (must exist in schema). */
17
+ block: string;
18
+ /** Block-specific configuration (validated against block's JSON Schema). */
19
+ config: Record<string, unknown>;
20
+ /** Slot name → id or array of ids. Other keys are not allowed in canonical form. */
21
+ [slotName: string]: unknown;
22
+ }
23
+ /**
24
+ * Slot value in canonical form: either a single node id or an ordered list of ids.
25
+ */
26
+ type SlotValue = string | string[];
27
+ /**
28
+ * Content definition in canonical form: one entry id and a flat map of nodes.
29
+ * No nesting; slots reference children by id.
30
+ */
31
+ interface GenetikContent {
32
+ /** Root node id (must exist in nodes). */
33
+ entryId: string;
34
+ /** Flat map of node id → node. */
35
+ nodes: Record<string, ContentNode>;
36
+ }
37
+ /**
38
+ * Input form for a slot value when the schema allows inline (referenceMode "inline" or "both").
39
+ * Can be an id, array of ids, a single inline node object, or array of id or inline node.
40
+ */
41
+ type InlineSlotValue = string | string[] | InlineNode | (string | InlineNode)[];
42
+ /**
43
+ * Inline node: can appear in a slot when schema allows. Has block, config, optional id,
44
+ * and slot keys whose values may be id(s) or nested inline. Normalization flattens these
45
+ * and assigns ids.
46
+ */
47
+ interface InlineNode {
48
+ /** Optional; if missing, normalization will generate one. */
49
+ id?: string;
50
+ block: string;
51
+ config: Record<string, unknown>;
52
+ [slotName: string]: unknown;
53
+ }
54
+ /**
55
+ * Content in input form: same shape as canonical but slot values may be inline (when schema allows).
56
+ * Used as input to normalizeContent().
57
+ */
58
+ interface GenetikContentInput {
59
+ entryId: string;
60
+ nodes: Record<string, ContentNodeInput>;
61
+ }
62
+ /**
63
+ * Node in input form: id must match map key; slot values may be id, id[], or inline (per schema).
64
+ */
65
+ interface ContentNodeInput {
66
+ id: string;
67
+ block: string;
68
+ config: Record<string, unknown>;
69
+ [slotName: string]: unknown;
70
+ }
71
+ //#endregion
72
+ //#region src/normalize.d.ts
73
+ interface NormalizeOptions {
74
+ /** Id generator for new nodes from inline. Defaults to nanoid. */
75
+ generateId?: () => string;
76
+ }
77
+ /**
78
+ * Normalizes content that may contain inline nodes in slots (when schema allows)
79
+ * to canonical flat form. Inline nodes are flattened, assigned ids, and slot
80
+ * values become id references. Id-only slots are left unchanged.
81
+ */
82
+ declare function normalizeContent(schema: GenetikSchema, input: GenetikContentInput, options?: NormalizeOptions): GenetikContent;
83
+ //#endregion
84
+ //#region src/parse.d.ts
85
+ /**
86
+ * Result of parsing a content JSON string. Use this when parsing user or API input
87
+ * so you can show a clear error instead of throwing.
88
+ */
89
+ type ParseContentResult = {
90
+ ok: true;
91
+ content: GenetikContent;
92
+ } | {
93
+ ok: false;
94
+ error: string;
95
+ };
96
+ /**
97
+ * Parses a JSON string into content and checks the minimal shape (entryId and nodes).
98
+ * Does not validate against a schema or normalize; use validateContent and
99
+ * normalizeContent for that.
100
+ *
101
+ * @param raw - JSON string (e.g. from user input or API)
102
+ * @returns ParseContentResult: either { ok: true, content } or { ok: false, error }
103
+ */
104
+ declare function parseContentJson(raw: string): ParseContentResult;
105
+ //#endregion
106
+ //#region src/validate.d.ts
107
+ interface ContentValidationError {
108
+ /** Path to the problem (e.g. "nodes.a", "nodes.a.config"). */
109
+ path: string;
110
+ /** Human-readable message. */
111
+ message: string;
112
+ }
113
+ interface ContentValidationResult {
114
+ valid: boolean;
115
+ errors: ContentValidationError[];
116
+ }
117
+ /**
118
+ * Validates content against a schema. Checks:
119
+ * - entryId is present and exists in nodes
120
+ * - Every node has id, block, config; block type exists in schema
121
+ * - Each node's config is valid against the block's JSON Schema
122
+ * - Slot values are id or id[] per slot definition; no extra slot keys
123
+ * - All referenced ids exist in the node map (link integrity)
124
+ */
125
+ declare function validateContent(schema: GenetikSchema, content: unknown): ContentValidationResult;
126
+ //#endregion
127
+ export { type ContentNode, type ContentNodeInput, type ContentValidationError, type ContentValidationResult, type GenetikContent, type GenetikContentInput, type InlineNode, type InlineSlotValue, type NormalizeOptions, type ParseContentResult, type SlotValue, normalizeContent, parseContentJson, validateContent };
128
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/normalize.ts","../src/parse.ts","../src/validate.ts"],"mappings":";;;;;;AAUA;;;;;;UAAiB,WAAA;EAMP;EAJR,EAAA;EAMiB;EAJjB,KAAA;EAUU;EARV,MAAA,EAAQ,MAAA;;GAEP,QAAA;AAAA;AAYH;;;AAAA,KANY,SAAA;;;;;UAMK,cAAA;EAIkB;EAFjC,OAAA;EASyB;EAPzB,KAAA,EAAO,MAAA,SAAe,WAAA;AAAA;;AAcxB;;;KAPY,eAAA,uBAAsC,UAAA,aAAuB,UAAA;;;;;;UAOxD,UAAA;EAKE;EAHjB,EAAA;EACA,KAAA;EACA,MAAA,EAAQ,MAAA;EAAA,CACP,QAAA;AAAA;;;;;UAOc,mBAAA;EACf,OAAA;EACA,KAAA,EAAO,MAAA,SAAe,gBAAA;AAAA;;;;UAMP,gBAAA;EACf,EAAA;EACA,KAAA;EACA,MAAA,EAAQ,MAAA;EAAA,CACP,QAAA;AAAA;;;UCnEc,gBAAA;EDKA;ECHf,UAAA;AAAA;;;;;;iBAQc,gBAAA,CACd,MAAA,EAAQ,aAAA,EACR,KAAA,EAAO,mBAAA,EACP,OAAA,GAAU,gBAAA,GACT,cAAA;;;;;ADTH;;KEJY,kBAAA;EACN,EAAA;EAAU,OAAA,EAAS,cAAA;AAAA;EACnB,EAAA;EAAW,KAAA;AAAA;;;AFgBjB;;;;;AAMA;iBEZgB,gBAAA,CAAiB,GAAA,WAAc,kBAAA;;;UCb9B,sBAAA;;EAEf,IAAA;EHG0B;EGD1B,OAAA;AAAA;AAAA,UAGe,uBAAA;EACf,KAAA;EACA,MAAA,EAAQ,sBAAA;AAAA;;;;AHUV;;;;;iBGCgB,eAAA,CACd,MAAA,EAAQ,aAAA,EACR,OAAA,YACC,uBAAA"}
@@ -0,0 +1,128 @@
1
+ import { GenetikSchema } from "@genetik/schema";
2
+
3
+ //#region src/types.d.ts
4
+ /**
5
+ * Canonical content model: flat node map and entry point.
6
+ * Slot values are id references (string or array of ids).
7
+ */
8
+ /**
9
+ * A single node in the content tree. In canonical form, slot values
10
+ * are string (id) or string[] (ordered ids). Keys besides id, block,
11
+ * and config are slot names defined by the block type.
12
+ */
13
+ interface ContentNode {
14
+ /** Node id; must match the key in the nodes map. */
15
+ id: string;
16
+ /** Block type name (must exist in schema). */
17
+ block: string;
18
+ /** Block-specific configuration (validated against block's JSON Schema). */
19
+ config: Record<string, unknown>;
20
+ /** Slot name → id or array of ids. Other keys are not allowed in canonical form. */
21
+ [slotName: string]: unknown;
22
+ }
23
+ /**
24
+ * Slot value in canonical form: either a single node id or an ordered list of ids.
25
+ */
26
+ type SlotValue = string | string[];
27
+ /**
28
+ * Content definition in canonical form: one entry id and a flat map of nodes.
29
+ * No nesting; slots reference children by id.
30
+ */
31
+ interface GenetikContent {
32
+ /** Root node id (must exist in nodes). */
33
+ entryId: string;
34
+ /** Flat map of node id → node. */
35
+ nodes: Record<string, ContentNode>;
36
+ }
37
+ /**
38
+ * Input form for a slot value when the schema allows inline (referenceMode "inline" or "both").
39
+ * Can be an id, array of ids, a single inline node object, or array of id or inline node.
40
+ */
41
+ type InlineSlotValue = string | string[] | InlineNode | (string | InlineNode)[];
42
+ /**
43
+ * Inline node: can appear in a slot when schema allows. Has block, config, optional id,
44
+ * and slot keys whose values may be id(s) or nested inline. Normalization flattens these
45
+ * and assigns ids.
46
+ */
47
+ interface InlineNode {
48
+ /** Optional; if missing, normalization will generate one. */
49
+ id?: string;
50
+ block: string;
51
+ config: Record<string, unknown>;
52
+ [slotName: string]: unknown;
53
+ }
54
+ /**
55
+ * Content in input form: same shape as canonical but slot values may be inline (when schema allows).
56
+ * Used as input to normalizeContent().
57
+ */
58
+ interface GenetikContentInput {
59
+ entryId: string;
60
+ nodes: Record<string, ContentNodeInput>;
61
+ }
62
+ /**
63
+ * Node in input form: id must match map key; slot values may be id, id[], or inline (per schema).
64
+ */
65
+ interface ContentNodeInput {
66
+ id: string;
67
+ block: string;
68
+ config: Record<string, unknown>;
69
+ [slotName: string]: unknown;
70
+ }
71
+ //#endregion
72
+ //#region src/normalize.d.ts
73
+ interface NormalizeOptions {
74
+ /** Id generator for new nodes from inline. Defaults to nanoid. */
75
+ generateId?: () => string;
76
+ }
77
+ /**
78
+ * Normalizes content that may contain inline nodes in slots (when schema allows)
79
+ * to canonical flat form. Inline nodes are flattened, assigned ids, and slot
80
+ * values become id references. Id-only slots are left unchanged.
81
+ */
82
+ declare function normalizeContent(schema: GenetikSchema, input: GenetikContentInput, options?: NormalizeOptions): GenetikContent;
83
+ //#endregion
84
+ //#region src/parse.d.ts
85
+ /**
86
+ * Result of parsing a content JSON string. Use this when parsing user or API input
87
+ * so you can show a clear error instead of throwing.
88
+ */
89
+ type ParseContentResult = {
90
+ ok: true;
91
+ content: GenetikContent;
92
+ } | {
93
+ ok: false;
94
+ error: string;
95
+ };
96
+ /**
97
+ * Parses a JSON string into content and checks the minimal shape (entryId and nodes).
98
+ * Does not validate against a schema or normalize; use validateContent and
99
+ * normalizeContent for that.
100
+ *
101
+ * @param raw - JSON string (e.g. from user input or API)
102
+ * @returns ParseContentResult: either { ok: true, content } or { ok: false, error }
103
+ */
104
+ declare function parseContentJson(raw: string): ParseContentResult;
105
+ //#endregion
106
+ //#region src/validate.d.ts
107
+ interface ContentValidationError {
108
+ /** Path to the problem (e.g. "nodes.a", "nodes.a.config"). */
109
+ path: string;
110
+ /** Human-readable message. */
111
+ message: string;
112
+ }
113
+ interface ContentValidationResult {
114
+ valid: boolean;
115
+ errors: ContentValidationError[];
116
+ }
117
+ /**
118
+ * Validates content against a schema. Checks:
119
+ * - entryId is present and exists in nodes
120
+ * - Every node has id, block, config; block type exists in schema
121
+ * - Each node's config is valid against the block's JSON Schema
122
+ * - Slot values are id or id[] per slot definition; no extra slot keys
123
+ * - All referenced ids exist in the node map (link integrity)
124
+ */
125
+ declare function validateContent(schema: GenetikSchema, content: unknown): ContentValidationResult;
126
+ //#endregion
127
+ export { type ContentNode, type ContentNodeInput, type ContentValidationError, type ContentValidationResult, type GenetikContent, type GenetikContentInput, type InlineNode, type InlineSlotValue, type NormalizeOptions, type ParseContentResult, type SlotValue, normalizeContent, parseContentJson, validateContent };
128
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/normalize.ts","../src/parse.ts","../src/validate.ts"],"mappings":";;;;;;AAUA;;;;;;UAAiB,WAAA;EAMP;EAJR,EAAA;EAMiB;EAJjB,KAAA;EAUU;EARV,MAAA,EAAQ,MAAA;;GAEP,QAAA;AAAA;AAYH;;;AAAA,KANY,SAAA;;;;;UAMK,cAAA;EAIkB;EAFjC,OAAA;EASyB;EAPzB,KAAA,EAAO,MAAA,SAAe,WAAA;AAAA;;AAcxB;;;KAPY,eAAA,uBAAsC,UAAA,aAAuB,UAAA;;;;;;UAOxD,UAAA;EAKE;EAHjB,EAAA;EACA,KAAA;EACA,MAAA,EAAQ,MAAA;EAAA,CACP,QAAA;AAAA;;;;;UAOc,mBAAA;EACf,OAAA;EACA,KAAA,EAAO,MAAA,SAAe,gBAAA;AAAA;;;;UAMP,gBAAA;EACf,EAAA;EACA,KAAA;EACA,MAAA,EAAQ,MAAA;EAAA,CACP,QAAA;AAAA;;;UCnEc,gBAAA;EDKA;ECHf,UAAA;AAAA;;;;;;iBAQc,gBAAA,CACd,MAAA,EAAQ,aAAA,EACR,KAAA,EAAO,mBAAA,EACP,OAAA,GAAU,gBAAA,GACT,cAAA;;;;;ADTH;;KEJY,kBAAA;EACN,EAAA;EAAU,OAAA,EAAS,cAAA;AAAA;EACnB,EAAA;EAAW,KAAA;AAAA;;;AFgBjB;;;;;AAMA;iBEZgB,gBAAA,CAAiB,GAAA,WAAc,kBAAA;;;UCb9B,sBAAA;;EAEf,IAAA;EHG0B;EGD1B,OAAA;AAAA;AAAA,UAGe,uBAAA;EACf,KAAA;EACA,MAAA,EAAQ,sBAAA;AAAA;;;;AHUV;;;;;iBGCgB,eAAA,CACd,MAAA,EAAQ,aAAA,EACR,OAAA,YACC,uBAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,265 @@
1
+ import { getBlockType, validateConfigAgainstDefinition } from "@genetik/schema";
2
+ import { nanoid } from "nanoid";
3
+
4
+ //#region src/normalize.ts
5
+ /**
6
+ * Normalizes content that may contain inline nodes in slots (when schema allows)
7
+ * to canonical flat form. Inline nodes are flattened, assigned ids, and slot
8
+ * values become id references. Id-only slots are left unchanged.
9
+ */
10
+ function normalizeContent(schema, input, options) {
11
+ const generateId = options?.generateId ?? nanoid;
12
+ const resultNodes = {};
13
+ for (const [nodeId, inputNode] of Object.entries(input.nodes)) {
14
+ const { node, extraNodes } = normalizeNode(schema, inputNode, nodeId, generateId);
15
+ resultNodes[nodeId] = node;
16
+ Object.assign(resultNodes, extraNodes);
17
+ }
18
+ return {
19
+ entryId: input.entryId,
20
+ nodes: resultNodes
21
+ };
22
+ }
23
+ function normalizeNode(schema, input, nodeId, generateId) {
24
+ const block = input.block;
25
+ const blockType = getBlockType(schema, block);
26
+ const config = input.config ?? {};
27
+ if (!blockType) {
28
+ const node = {
29
+ id: nodeId,
30
+ block,
31
+ config
32
+ };
33
+ for (const k of Object.keys(input)) {
34
+ if (k === "id" || k === "block" || k === "config") continue;
35
+ const v = input[k];
36
+ if (typeof v === "string" || Array.isArray(v) && v.every((x) => typeof x === "string")) node[k] = v;
37
+ }
38
+ return {
39
+ node,
40
+ extraNodes: {}
41
+ };
42
+ }
43
+ const node = {
44
+ id: nodeId,
45
+ block,
46
+ config
47
+ };
48
+ let extraNodes = {};
49
+ for (const slotDef of blockType.slots) {
50
+ const value = input[slotDef.name];
51
+ if (value === void 0 || value === null) continue;
52
+ if (slotDef.referenceMode === "id") {
53
+ node[slotDef.name] = value;
54
+ continue;
55
+ }
56
+ if (slotDef.multiple) {
57
+ const arr = Array.isArray(value) ? value : [value];
58
+ const ids = [];
59
+ for (const item of arr) if (typeof item === "string") ids.push(item);
60
+ else if (isInlineNode(item)) {
61
+ const childId = generateId();
62
+ const { node: childNode, extraNodes: childExtras } = normalizeNode(schema, item, childId, generateId);
63
+ ids.push(childId);
64
+ extraNodes = {
65
+ ...extraNodes,
66
+ ...childExtras,
67
+ [childId]: childNode
68
+ };
69
+ }
70
+ node[slotDef.name] = ids;
71
+ } else if (typeof value === "string") node[slotDef.name] = value;
72
+ else if (isInlineNode(value)) {
73
+ const childId = generateId();
74
+ const { node: childNode, extraNodes: childExtras } = normalizeNode(schema, value, childId, generateId);
75
+ node[slotDef.name] = childId;
76
+ extraNodes = {
77
+ ...extraNodes,
78
+ ...childExtras,
79
+ [childId]: childNode
80
+ };
81
+ }
82
+ }
83
+ return {
84
+ node,
85
+ extraNodes
86
+ };
87
+ }
88
+ function isInlineNode(value) {
89
+ return value !== null && typeof value === "object" && !Array.isArray(value) && "block" in value;
90
+ }
91
+
92
+ //#endregion
93
+ //#region src/parse.ts
94
+ /**
95
+ * Parses a JSON string into content and checks the minimal shape (entryId and nodes).
96
+ * Does not validate against a schema or normalize; use validateContent and
97
+ * normalizeContent for that.
98
+ *
99
+ * @param raw - JSON string (e.g. from user input or API)
100
+ * @returns ParseContentResult: either { ok: true, content } or { ok: false, error }
101
+ */
102
+ function parseContentJson(raw) {
103
+ let parsed;
104
+ try {
105
+ parsed = JSON.parse(raw);
106
+ } catch (e) {
107
+ return {
108
+ ok: false,
109
+ error: e instanceof Error ? e.message : "Invalid JSON"
110
+ };
111
+ }
112
+ if (parsed === null || typeof parsed !== "object") return {
113
+ ok: false,
114
+ error: "Content must be an object"
115
+ };
116
+ const obj = parsed;
117
+ if (typeof obj.entryId !== "string") return {
118
+ ok: false,
119
+ error: "Content must have entryId (string)"
120
+ };
121
+ if (obj.nodes === null || typeof obj.nodes !== "object") return {
122
+ ok: false,
123
+ error: "Content must have nodes (object)"
124
+ };
125
+ return {
126
+ ok: true,
127
+ content: obj
128
+ };
129
+ }
130
+
131
+ //#endregion
132
+ //#region src/validate.ts
133
+ /**
134
+ * Validates content against a schema. Checks:
135
+ * - entryId is present and exists in nodes
136
+ * - Every node has id, block, config; block type exists in schema
137
+ * - Each node's config is valid against the block's JSON Schema
138
+ * - Slot values are id or id[] per slot definition; no extra slot keys
139
+ * - All referenced ids exist in the node map (link integrity)
140
+ */
141
+ function validateContent(schema, content) {
142
+ const errors = [];
143
+ if (content === null || typeof content !== "object" || Array.isArray(content)) return {
144
+ valid: false,
145
+ errors: [{
146
+ path: "",
147
+ message: "Content must be an object"
148
+ }]
149
+ };
150
+ const c = content;
151
+ const entryId = c.entryId;
152
+ const nodes = c.nodes;
153
+ if (typeof entryId !== "string" || entryId === "") errors.push({
154
+ path: "entryId",
155
+ message: "entryId must be a non-empty string"
156
+ });
157
+ if (nodes === null || typeof nodes !== "object" || Array.isArray(nodes)) errors.push({
158
+ path: "nodes",
159
+ message: "nodes must be an object (map of id to node)"
160
+ });
161
+ const nodeMap = nodes;
162
+ const allIds = new Set(nodeMap ? Object.keys(nodeMap) : []);
163
+ if (nodeMap) for (const [key, val] of Object.entries(nodeMap)) {
164
+ const nodeError = validateNode(schema, key, val, "nodes");
165
+ if (nodeError) errors.push(nodeError);
166
+ }
167
+ if (typeof entryId === "string" && entryId !== "" && nodeMap && !(entryId in nodeMap)) errors.push({
168
+ path: "entryId",
169
+ message: `entryId "${entryId}" is not in nodes`
170
+ });
171
+ if (nodeMap) for (const [nodeKey, val] of Object.entries(nodeMap)) {
172
+ const refErrors = collectSlotReferenceErrors(nodeMap, val, `nodes.${nodeKey}`, allIds);
173
+ errors.push(...refErrors);
174
+ }
175
+ return {
176
+ valid: errors.length === 0,
177
+ errors
178
+ };
179
+ }
180
+ function validateNode(schema, mapKey, value, path) {
181
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return {
182
+ path: `${path}.${mapKey}`,
183
+ message: "Node must be an object"
184
+ };
185
+ const node = value;
186
+ const id = node.id;
187
+ const block = node.block;
188
+ const config = node.config;
189
+ if (typeof id !== "string" || id === "") return {
190
+ path: `${path}.${mapKey}.id`,
191
+ message: "Node id must be a non-empty string"
192
+ };
193
+ if (id !== mapKey) return {
194
+ path: `${path}.${mapKey}.id`,
195
+ message: `Node id "${id}" must match map key "${mapKey}"`
196
+ };
197
+ if (typeof block !== "string" || block === "") return {
198
+ path: `${path}.${mapKey}.block`,
199
+ message: "Node block must be a non-empty string"
200
+ };
201
+ const blockType = block ? getBlockType(schema, block) : void 0;
202
+ if (!blockType) return {
203
+ path: `${path}.${mapKey}.block`,
204
+ message: `Unknown block type: ${block}`
205
+ };
206
+ if (config !== void 0 && (config === null || typeof config !== "object" || Array.isArray(config))) return {
207
+ path: `${path}.${mapKey}.config`,
208
+ message: "Node config must be an object"
209
+ };
210
+ const configResult = validateConfigAgainstDefinition(blockType, config ?? {});
211
+ if (!configResult.valid && configResult.errors?.length) {
212
+ const first = configResult.errors[0];
213
+ return {
214
+ path: `${path}.${mapKey}.config`,
215
+ message: first.message ?? "Invalid config"
216
+ };
217
+ }
218
+ const slotNames = new Set(blockType.slots.map((s) => s.name));
219
+ for (const key of Object.keys(node)) {
220
+ if (key === "id" || key === "block" || key === "config") continue;
221
+ if (!slotNames.has(key)) return {
222
+ path: `${path}.${mapKey}`,
223
+ message: `Unknown slot "${key}" for block type "${block}"`
224
+ };
225
+ const slotDef = blockType.slots.find((s) => s.name === key);
226
+ if (!slotDef) continue;
227
+ const slotValue = node[key];
228
+ if (slotValue === void 0 || slotValue === null) continue;
229
+ if (slotDef.multiple) {
230
+ if (!Array.isArray(slotValue)) return {
231
+ path: `${path}.${mapKey}.${key}`,
232
+ message: `Slot "${key}" must be an array of ids (multiple: true)`
233
+ };
234
+ if (slotValue.some((v) => typeof v !== "string")) return {
235
+ path: `${path}.${mapKey}.${key}`,
236
+ message: `Slot "${key}" must contain only string ids`
237
+ };
238
+ } else if (typeof slotValue !== "string") return {
239
+ path: `${path}.${mapKey}.${key}`,
240
+ message: `Slot "${key}" must be a single id string (multiple: false)`
241
+ };
242
+ }
243
+ return null;
244
+ }
245
+ function collectSlotReferenceErrors(nodeMap, value, path, allIds) {
246
+ const errors = [];
247
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return errors;
248
+ const node = value;
249
+ for (const [key, slotValue] of Object.entries(node)) {
250
+ if (key === "id" || key === "block" || key === "config") continue;
251
+ const ids = Array.isArray(slotValue) ? slotValue : slotValue === void 0 || slotValue === null ? [] : [slotValue];
252
+ for (const id of ids) {
253
+ if (typeof id !== "string") continue;
254
+ if (!allIds.has(id)) errors.push({
255
+ path: `${path}.${key}`,
256
+ message: `Referenced id "${id}" is not in nodes (dangling reference)`
257
+ });
258
+ }
259
+ }
260
+ return errors;
261
+ }
262
+
263
+ //#endregion
264
+ export { normalizeContent, parseContentJson, validateContent };
265
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/normalize.ts","../src/parse.ts","../src/validate.ts"],"sourcesContent":["import type { GenetikSchema } from \"@genetik/schema\";\nimport { getBlockType } from \"@genetik/schema\";\nimport { nanoid } from \"nanoid\";\nimport type { ContentNode, GenetikContent, GenetikContentInput } from \"./types.js\";\n\nexport interface NormalizeOptions {\n /** Id generator for new nodes from inline. Defaults to nanoid. */\n generateId?: () => string;\n}\n\n/**\n * Normalizes content that may contain inline nodes in slots (when schema allows)\n * to canonical flat form. Inline nodes are flattened, assigned ids, and slot\n * values become id references. Id-only slots are left unchanged.\n */\nexport function normalizeContent(\n schema: GenetikSchema,\n input: GenetikContentInput,\n options?: NormalizeOptions\n): GenetikContent {\n const generateId = options?.generateId ?? nanoid;\n const resultNodes: Record<string, ContentNode> = {};\n\n for (const [nodeId, inputNode] of Object.entries(input.nodes)) {\n const { node, extraNodes } = normalizeNode(schema, inputNode as Record<string, unknown>, nodeId, generateId);\n resultNodes[nodeId] = node;\n Object.assign(resultNodes, extraNodes);\n }\n\n return {\n entryId: input.entryId,\n nodes: resultNodes,\n };\n}\n\nfunction normalizeNode(\n schema: GenetikSchema,\n input: Record<string, unknown>,\n nodeId: string,\n generateId: () => string\n): { node: ContentNode; extraNodes: Record<string, ContentNode> } {\n const block = input.block as string;\n const blockType = getBlockType(schema, block);\n const config = (input.config as Record<string, unknown>) ?? {};\n\n if (!blockType) {\n const node: ContentNode = { id: nodeId, block, config };\n for (const k of Object.keys(input)) {\n if (k === \"id\" || k === \"block\" || k === \"config\") continue;\n const v = input[k];\n if (typeof v === \"string\" || (Array.isArray(v) && v.every((x) => typeof x === \"string\"))) node[k] = v;\n }\n return { node, extraNodes: {} };\n }\n\n const node: ContentNode = { id: nodeId, block, config };\n let extraNodes: Record<string, ContentNode> = {};\n\n for (const slotDef of blockType.slots) {\n const value = input[slotDef.name];\n if (value === undefined || value === null) continue;\n\n if (slotDef.referenceMode === \"id\") {\n node[slotDef.name] = value;\n continue;\n }\n\n if (slotDef.multiple) {\n const arr = Array.isArray(value) ? value : [value];\n const ids: string[] = [];\n for (const item of arr) {\n if (typeof item === \"string\") {\n ids.push(item);\n } else if (isInlineNode(item)) {\n const childId = generateId();\n const { node: childNode, extraNodes: childExtras } = normalizeNode(\n schema,\n item as Record<string, unknown>,\n childId,\n generateId\n );\n ids.push(childId);\n extraNodes = { ...extraNodes, ...childExtras, [childId]: childNode };\n }\n }\n node[slotDef.name] = ids;\n } else {\n if (typeof value === \"string\") {\n node[slotDef.name] = value;\n } else if (isInlineNode(value)) {\n const childId = generateId();\n const { node: childNode, extraNodes: childExtras } = normalizeNode(\n schema,\n value as Record<string, unknown>,\n childId,\n generateId\n );\n node[slotDef.name] = childId;\n extraNodes = { ...extraNodes, ...childExtras, [childId]: childNode };\n }\n }\n }\n\n return { node, extraNodes };\n}\n\nfunction isInlineNode(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value) && \"block\" in value;\n}\n","import type { GenetikContent } from \"./types.js\";\n\n/**\n * Result of parsing a content JSON string. Use this when parsing user or API input\n * so you can show a clear error instead of throwing.\n */\nexport type ParseContentResult =\n | { ok: true; content: GenetikContent }\n | { ok: false; error: string };\n\n/**\n * Parses a JSON string into content and checks the minimal shape (entryId and nodes).\n * Does not validate against a schema or normalize; use validateContent and\n * normalizeContent for that.\n *\n * @param raw - JSON string (e.g. from user input or API)\n * @returns ParseContentResult: either { ok: true, content } or { ok: false, error }\n */\nexport function parseContentJson(raw: string): ParseContentResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (e) {\n return { ok: false, error: e instanceof Error ? e.message : \"Invalid JSON\" };\n }\n\n if (parsed === null || typeof parsed !== \"object\") {\n return { ok: false, error: \"Content must be an object\" };\n }\n\n const obj = parsed as Record<string, unknown>;\n if (typeof obj.entryId !== \"string\") {\n return { ok: false, error: \"Content must have entryId (string)\" };\n }\n if (obj.nodes === null || typeof obj.nodes !== \"object\") {\n return { ok: false, error: \"Content must have nodes (object)\" };\n }\n\n return { ok: true, content: obj as unknown as GenetikContent };\n}\n","import type { GenetikSchema } from \"@genetik/schema\";\nimport { getBlockType } from \"@genetik/schema\";\nimport { validateConfigAgainstDefinition } from \"@genetik/schema\";\nimport type { ContentNode } from \"./types.js\";\n\nexport interface ContentValidationError {\n /** Path to the problem (e.g. \"nodes.a\", \"nodes.a.config\"). */\n path: string;\n /** Human-readable message. */\n message: string;\n}\n\nexport interface ContentValidationResult {\n valid: boolean;\n errors: ContentValidationError[];\n}\n\n/**\n * Validates content against a schema. Checks:\n * - entryId is present and exists in nodes\n * - Every node has id, block, config; block type exists in schema\n * - Each node's config is valid against the block's JSON Schema\n * - Slot values are id or id[] per slot definition; no extra slot keys\n * - All referenced ids exist in the node map (link integrity)\n */\nexport function validateContent(\n schema: GenetikSchema,\n content: unknown\n): ContentValidationResult {\n const errors: ContentValidationError[] = [];\n\n if (content === null || typeof content !== \"object\" || Array.isArray(content)) {\n return {\n valid: false,\n errors: [{ path: \"\", message: \"Content must be an object\" }],\n };\n }\n\n const c = content as Record<string, unknown>;\n const entryId = c.entryId;\n const nodes = c.nodes;\n\n if (typeof entryId !== \"string\" || entryId === \"\") {\n errors.push({ path: \"entryId\", message: \"entryId must be a non-empty string\" });\n }\n\n if (nodes === null || typeof nodes !== \"object\" || Array.isArray(nodes)) {\n errors.push({ path: \"nodes\", message: \"nodes must be an object (map of id to node)\" });\n }\n\n const nodeMap = nodes as Record<string, unknown> | undefined;\n const allIds = new Set<string>(nodeMap ? Object.keys(nodeMap) : []);\n\n if (nodeMap) {\n for (const [key, val] of Object.entries(nodeMap)) {\n const nodeError = validateNode(schema, key, val, \"nodes\");\n if (nodeError) errors.push(nodeError);\n }\n }\n\n if (typeof entryId === \"string\" && entryId !== \"\" && nodeMap && !(entryId in nodeMap)) {\n errors.push({ path: \"entryId\", message: `entryId \"${entryId}\" is not in nodes` });\n }\n\n if (nodeMap) {\n for (const [nodeKey, val] of Object.entries(nodeMap)) {\n const refErrors = collectSlotReferenceErrors(nodeMap, val, `nodes.${nodeKey}`, allIds);\n errors.push(...refErrors);\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n}\n\nfunction validateNode(\n schema: GenetikSchema,\n mapKey: string,\n value: unknown,\n path: string\n): ContentValidationError | null {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n return { path: `${path}.${mapKey}`, message: \"Node must be an object\" };\n }\n\n const node = value as Record<string, unknown>;\n const id = node.id;\n const block = node.block;\n const config = node.config;\n\n if (typeof id !== \"string\" || id === \"\") {\n return { path: `${path}.${mapKey}.id`, message: \"Node id must be a non-empty string\" };\n }\n if (id !== mapKey) {\n return { path: `${path}.${mapKey}.id`, message: `Node id \"${id}\" must match map key \"${mapKey}\"` };\n }\n if (typeof block !== \"string\" || block === \"\") {\n return { path: `${path}.${mapKey}.block`, message: \"Node block must be a non-empty string\" };\n }\n\n const blockType = block ? getBlockType(schema, block as string) : undefined;\n if (!blockType) {\n return {\n path: `${path}.${mapKey}.block`,\n message: `Unknown block type: ${block}`,\n };\n }\n\n if (config !== undefined && (config === null || typeof config !== \"object\" || Array.isArray(config))) {\n return { path: `${path}.${mapKey}.config`, message: \"Node config must be an object\" };\n }\n\n const configResult = validateConfigAgainstDefinition(blockType, config ?? {});\n if (!configResult.valid && configResult.errors?.length) {\n const first = configResult.errors[0]!;\n return {\n path: `${path}.${mapKey}.config`,\n message: first.message ?? \"Invalid config\",\n };\n }\n\n const slotNames = new Set(blockType.slots.map((s) => s.name));\n for (const key of Object.keys(node)) {\n if (key === \"id\" || key === \"block\" || key === \"config\") continue;\n if (!slotNames.has(key)) {\n return {\n path: `${path}.${mapKey}`,\n message: `Unknown slot \"${key}\" for block type \"${block}\"`,\n };\n }\n const slotDef = blockType.slots.find((s) => s.name === key);\n if (!slotDef) continue;\n const slotValue = node[key];\n if (slotValue === undefined || slotValue === null) continue;\n if (slotDef.multiple) {\n if (!Array.isArray(slotValue)) {\n return {\n path: `${path}.${mapKey}.${key}`,\n message: `Slot \"${key}\" must be an array of ids (multiple: true)`,\n };\n }\n if (slotValue.some((v) => typeof v !== \"string\")) {\n return {\n path: `${path}.${mapKey}.${key}`,\n message: `Slot \"${key}\" must contain only string ids`,\n };\n }\n } else {\n if (typeof slotValue !== \"string\") {\n return {\n path: `${path}.${mapKey}.${key}`,\n message: `Slot \"${key}\" must be a single id string (multiple: false)`,\n };\n }\n }\n }\n\n return null;\n}\n\nfunction collectSlotReferenceErrors(\n nodeMap: Record<string, unknown>,\n value: unknown,\n path: string,\n allIds: Set<string>\n): ContentValidationError[] {\n const errors: ContentValidationError[] = [];\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) return errors;\n\n const node = value as ContentNode;\n for (const [key, slotValue] of Object.entries(node)) {\n if (key === \"id\" || key === \"block\" || key === \"config\") continue;\n const ids = Array.isArray(slotValue) ? slotValue : slotValue === undefined || slotValue === null ? [] : [slotValue];\n for (const id of ids) {\n if (typeof id !== \"string\") continue;\n if (!allIds.has(id)) {\n errors.push({\n path: `${path}.${key}`,\n message: `Referenced id \"${id}\" is not in nodes (dangling reference)`,\n });\n }\n }\n }\n return errors;\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@genetik/content",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.mjs",
10
+ "types": "./dist/index.d.mts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.cjs",
15
+ "types": "./dist/index.d.mts"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "dependencies": {
22
+ "nanoid": "^5.0.9",
23
+ "@genetik/schema": "0.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "eslint": "^9.39.1",
27
+ "tsdown": "0.20.3",
28
+ "typescript": "5.9.2",
29
+ "vitest": "^2.1.8",
30
+ "@genetik/typescript-config": "0.0.0",
31
+ "@genetik/eslint-config": "0.0.0"
32
+ },
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "license": "MIT",
37
+ "scripts": {
38
+ "build": "tsdown",
39
+ "dev": "tsdown --watch",
40
+ "lint": "eslint . --max-warnings 0",
41
+ "check-types": "tsc --noEmit",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest"
44
+ }
45
+ }