@glyphjs/ir 0.1.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) 2025 Glyph JS Contributors
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,41 @@
1
+ # @glyphjs/ir
2
+
3
+ IR (Intermediate Representation) utilities for Glyph JS: validation, diffing, patching, and migration.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @glyphjs/ir
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { validateIR, diffIR, applyPatch, createEmptyIR } from '@glyphjs/ir';
15
+
16
+ // Create a blank document
17
+ const doc = createEmptyIR();
18
+
19
+ // Validate an IR document
20
+ const diagnostics = validateIR(doc);
21
+
22
+ // Diff two IR documents
23
+ const patch = diffIR(oldDoc, newDoc);
24
+
25
+ // Apply a patch
26
+ const updated = applyPatch(oldDoc, patch);
27
+ ```
28
+
29
+ ## API
30
+
31
+ - `validateIR(ir)` -- validate an IR document and return diagnostics
32
+ - `diffIR(a, b)` -- compute a patch that transforms `a` into `b`
33
+ - `applyPatch(ir, patch)` -- apply a patch to an IR document
34
+ - `composePatch(a, b)` -- compose two patches into one
35
+ - `registerMigration(migration)` / `migrateIR(ir)` -- version migration support
36
+ - `generateBlockId()` / `generateDocumentId()` -- deterministic ID generation
37
+ - `createEmptyIR()` -- factory for a blank IR document
38
+
39
+ ## Docs
40
+
41
+ https://github.com/VledicFranco/glyphjs
package/dist/index.cjs ADDED
@@ -0,0 +1,453 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+
5
+ // src/ids.ts
6
+ function sha256Hex(input) {
7
+ return crypto.createHash("sha256").update(input).digest("hex");
8
+ }
9
+ function generateBlockId(documentId, blockType, content) {
10
+ const contentFingerprint = sha256Hex(content);
11
+ const hash = sha256Hex(documentId + blockType + contentFingerprint);
12
+ return `b-${hash.slice(0, 12)}`;
13
+ }
14
+ function generateDocumentId(options) {
15
+ if (options.glyphId) {
16
+ return options.glyphId;
17
+ }
18
+ if (options.filePath) {
19
+ return options.filePath.replace(/\\/g, "/");
20
+ }
21
+ if (options.content) {
22
+ const hash = sha256Hex(options.content);
23
+ return `doc-${hash.slice(0, 16)}`;
24
+ }
25
+ return `doc-${sha256Hex("")}`.slice(0, 20);
26
+ }
27
+ function resolveBlockIdCollisions(ids) {
28
+ const seen = /* @__PURE__ */ new Map();
29
+ const result = [];
30
+ for (const id of ids) {
31
+ const count = seen.get(id);
32
+ if (count === void 0) {
33
+ seen.set(id, 1);
34
+ result.push(id);
35
+ } else {
36
+ seen.set(id, count + 1);
37
+ result.push(`${id}-${String(count)}`);
38
+ }
39
+ }
40
+ return result;
41
+ }
42
+
43
+ // src/validate.ts
44
+ var VALID_LAYOUT_MODES = /* @__PURE__ */ new Set(["document", "dashboard", "presentation"]);
45
+ function validateIR(ir) {
46
+ const diagnostics = [];
47
+ if (!ir.version || typeof ir.version !== "string") {
48
+ diagnostics.push({
49
+ severity: "error",
50
+ code: "IR_MISSING_VERSION",
51
+ message: 'IR document must have a non-empty "version" string.',
52
+ source: "compiler"
53
+ });
54
+ }
55
+ if (!ir.id || typeof ir.id !== "string") {
56
+ diagnostics.push({
57
+ severity: "error",
58
+ code: "IR_MISSING_ID",
59
+ message: 'IR document must have a non-empty "id" string.',
60
+ source: "compiler"
61
+ });
62
+ }
63
+ const blockIds = /* @__PURE__ */ new Set();
64
+ for (const block of ir.blocks) {
65
+ if (!block.id || typeof block.id !== "string") {
66
+ diagnostics.push({
67
+ severity: "error",
68
+ code: "BLOCK_MISSING_ID",
69
+ message: 'Block is missing a non-empty "id" field.',
70
+ source: "compiler"
71
+ });
72
+ }
73
+ if (!block.type || typeof block.type !== "string") {
74
+ diagnostics.push({
75
+ severity: "error",
76
+ code: "BLOCK_MISSING_TYPE",
77
+ message: `Block "${String(block.id)}" is missing a non-empty "type" field.`,
78
+ source: "compiler"
79
+ });
80
+ }
81
+ if (block.data === void 0 || block.data === null) {
82
+ diagnostics.push({
83
+ severity: "error",
84
+ code: "BLOCK_MISSING_DATA",
85
+ message: `Block "${String(block.id)}" is missing a "data" field.`,
86
+ source: "compiler"
87
+ });
88
+ }
89
+ if (!block.position) {
90
+ diagnostics.push({
91
+ severity: "error",
92
+ code: "BLOCK_MISSING_POSITION",
93
+ message: `Block "${String(block.id)}" is missing a "position" field.`,
94
+ source: "compiler"
95
+ });
96
+ }
97
+ if (block.id) {
98
+ if (blockIds.has(block.id)) {
99
+ diagnostics.push({
100
+ severity: "error",
101
+ code: "BLOCK_DUPLICATE_ID",
102
+ message: `Duplicate block ID: "${block.id}".`,
103
+ source: "compiler"
104
+ });
105
+ }
106
+ blockIds.add(block.id);
107
+ }
108
+ }
109
+ for (const ref of ir.references) {
110
+ if (!blockIds.has(ref.sourceBlockId)) {
111
+ diagnostics.push({
112
+ severity: "error",
113
+ code: "REF_MISSING_SOURCE",
114
+ message: `Reference "${ref.id}" has sourceBlockId "${ref.sourceBlockId}" which does not exist in blocks.`,
115
+ source: "compiler"
116
+ });
117
+ }
118
+ if (!blockIds.has(ref.targetBlockId)) {
119
+ diagnostics.push({
120
+ severity: "error",
121
+ code: "REF_MISSING_TARGET",
122
+ message: `Reference "${ref.id}" has targetBlockId "${ref.targetBlockId}" which does not exist in blocks.`,
123
+ source: "compiler"
124
+ });
125
+ }
126
+ }
127
+ if (ir.layout && !VALID_LAYOUT_MODES.has(ir.layout.mode)) {
128
+ diagnostics.push({
129
+ severity: "error",
130
+ code: "LAYOUT_INVALID_MODE",
131
+ message: `Invalid layout mode: "${ir.layout.mode}". Expected one of: ${[...VALID_LAYOUT_MODES].join(", ")}.`,
132
+ source: "compiler"
133
+ });
134
+ }
135
+ return diagnostics;
136
+ }
137
+
138
+ // src/diff.ts
139
+ function deepEqual(a, b) {
140
+ if (a === b) return true;
141
+ if (a === null || b === null) return false;
142
+ if (typeof a !== typeof b) return false;
143
+ if (Array.isArray(a)) {
144
+ if (!Array.isArray(b)) return false;
145
+ if (a.length !== b.length) return false;
146
+ return a.every((val, idx) => deepEqual(val, b[idx]));
147
+ }
148
+ if (typeof a === "object" && typeof b === "object") {
149
+ const objA = a;
150
+ const objB = b;
151
+ const keysA = Object.keys(objA);
152
+ const keysB = Object.keys(objB);
153
+ if (keysA.length !== keysB.length) return false;
154
+ return keysA.every((key) => deepEqual(objA[key], objB[key]));
155
+ }
156
+ return false;
157
+ }
158
+ function blockContentEqual(a, b) {
159
+ return a.type === b.type && deepEqual(a.data, b.data) && deepEqual(a.position, b.position) && deepEqual(a.children, b.children) && deepEqual(a.diagnostics, b.diagnostics) && deepEqual(a.metadata, b.metadata);
160
+ }
161
+ function referenceEqual(a, b) {
162
+ return deepEqual(a, b);
163
+ }
164
+ function getBlockAt(blocks, index) {
165
+ return blocks[index];
166
+ }
167
+ function diffIR(before, after) {
168
+ const ops = [];
169
+ if (!deepEqual(before.metadata, after.metadata)) {
170
+ ops.push({
171
+ op: "updateMetadata",
172
+ metadata: after.metadata
173
+ });
174
+ }
175
+ if (!deepEqual(before.layout, after.layout)) {
176
+ ops.push({
177
+ op: "updateLayout",
178
+ layout: after.layout
179
+ });
180
+ }
181
+ const beforeBlockMap = /* @__PURE__ */ new Map();
182
+ for (const block of before.blocks) {
183
+ beforeBlockMap.set(block.id, block);
184
+ }
185
+ const afterBlockMap = /* @__PURE__ */ new Map();
186
+ for (const block of after.blocks) {
187
+ afterBlockMap.set(block.id, block);
188
+ }
189
+ for (const block of before.blocks) {
190
+ if (!afterBlockMap.has(block.id)) {
191
+ ops.push({ op: "removeBlock", blockId: block.id });
192
+ }
193
+ }
194
+ for (let i = 0; i < after.blocks.length; i++) {
195
+ const afterBlock = getBlockAt(after.blocks, i);
196
+ if (!afterBlock) continue;
197
+ const beforeBlock = beforeBlockMap.get(afterBlock.id);
198
+ const prevBlock = i > 0 ? getBlockAt(after.blocks, i - 1) : void 0;
199
+ const afterBlockId = prevBlock?.id;
200
+ if (!beforeBlock) {
201
+ ops.push({
202
+ op: "addBlock",
203
+ block: afterBlock,
204
+ afterBlockId
205
+ });
206
+ } else {
207
+ if (!blockContentEqual(beforeBlock, afterBlock)) {
208
+ const data = {};
209
+ if (afterBlock.type !== beforeBlock.type) {
210
+ data.type = afterBlock.type;
211
+ }
212
+ if (!deepEqual(afterBlock.data, beforeBlock.data)) {
213
+ data.data = afterBlock.data;
214
+ }
215
+ if (!deepEqual(afterBlock.position, beforeBlock.position)) {
216
+ data.position = afterBlock.position;
217
+ }
218
+ if (!deepEqual(afterBlock.children, beforeBlock.children)) {
219
+ data.children = afterBlock.children;
220
+ }
221
+ if (!deepEqual(afterBlock.diagnostics, beforeBlock.diagnostics)) {
222
+ data.diagnostics = afterBlock.diagnostics;
223
+ }
224
+ if (!deepEqual(afterBlock.metadata, beforeBlock.metadata)) {
225
+ data.metadata = afterBlock.metadata;
226
+ }
227
+ ops.push({
228
+ op: "updateBlock",
229
+ blockId: afterBlock.id,
230
+ data
231
+ });
232
+ }
233
+ const beforeIndex = before.blocks.findIndex(
234
+ (b) => b.id === afterBlock.id
235
+ );
236
+ const prevBeforeBlock = beforeIndex > 0 ? getBlockAt(before.blocks, beforeIndex - 1) : void 0;
237
+ const expectedAfterBlockId = prevBeforeBlock?.id;
238
+ if (afterBlockId !== expectedAfterBlockId) {
239
+ ops.push({
240
+ op: "moveBlock",
241
+ blockId: afterBlock.id,
242
+ afterBlockId
243
+ });
244
+ }
245
+ }
246
+ }
247
+ const beforeRefMap = /* @__PURE__ */ new Map();
248
+ for (const ref of before.references) {
249
+ beforeRefMap.set(ref.id, ref);
250
+ }
251
+ const afterRefMap = /* @__PURE__ */ new Map();
252
+ for (const ref of after.references) {
253
+ afterRefMap.set(ref.id, ref);
254
+ }
255
+ for (const ref of before.references) {
256
+ if (!afterRefMap.has(ref.id)) {
257
+ ops.push({ op: "removeReference", referenceId: ref.id });
258
+ }
259
+ }
260
+ for (const ref of after.references) {
261
+ const beforeRef = beforeRefMap.get(ref.id);
262
+ if (!beforeRef) {
263
+ ops.push({ op: "addReference", reference: ref });
264
+ } else if (!referenceEqual(beforeRef, ref)) {
265
+ ops.push({ op: "removeReference", referenceId: ref.id });
266
+ ops.push({ op: "addReference", reference: ref });
267
+ }
268
+ }
269
+ return ops;
270
+ }
271
+
272
+ // src/patch.ts
273
+ function deepClone(value) {
274
+ return JSON.parse(JSON.stringify(value));
275
+ }
276
+ function applyOperation(ir, op) {
277
+ switch (op.op) {
278
+ case "addBlock": {
279
+ const blocks = [...ir.blocks];
280
+ if (op.afterBlockId) {
281
+ const idx = blocks.findIndex((b) => b.id === op.afterBlockId);
282
+ if (idx !== -1) {
283
+ blocks.splice(idx + 1, 0, deepClone(op.block));
284
+ } else {
285
+ blocks.push(deepClone(op.block));
286
+ }
287
+ } else {
288
+ blocks.unshift(deepClone(op.block));
289
+ }
290
+ return { ...ir, blocks };
291
+ }
292
+ case "removeBlock": {
293
+ const blocks = ir.blocks.filter((b) => b.id !== op.blockId);
294
+ return { ...ir, blocks };
295
+ }
296
+ case "updateBlock": {
297
+ const blocks = ir.blocks.map((b) => {
298
+ if (b.id !== op.blockId) return b;
299
+ const updated = { ...b };
300
+ if (op.data.type !== void 0) updated.type = op.data.type;
301
+ if (op.data.data !== void 0) updated.data = deepClone(op.data.data);
302
+ if (op.data.position !== void 0)
303
+ updated.position = deepClone(op.data.position);
304
+ if (op.data.children !== void 0)
305
+ updated.children = deepClone(op.data.children);
306
+ if (op.data.diagnostics !== void 0)
307
+ updated.diagnostics = deepClone(op.data.diagnostics);
308
+ if (op.data.metadata !== void 0)
309
+ updated.metadata = deepClone(op.data.metadata);
310
+ return updated;
311
+ });
312
+ return { ...ir, blocks };
313
+ }
314
+ case "moveBlock": {
315
+ const blockToMove = ir.blocks.find((b) => b.id === op.blockId);
316
+ if (!blockToMove) return ir;
317
+ const blocks = ir.blocks.filter((b) => b.id !== op.blockId);
318
+ if (op.afterBlockId) {
319
+ const idx = blocks.findIndex((b) => b.id === op.afterBlockId);
320
+ if (idx !== -1) {
321
+ blocks.splice(idx + 1, 0, blockToMove);
322
+ } else {
323
+ blocks.push(blockToMove);
324
+ }
325
+ } else {
326
+ blocks.unshift(blockToMove);
327
+ }
328
+ return { ...ir, blocks };
329
+ }
330
+ case "addReference": {
331
+ return {
332
+ ...ir,
333
+ references: [...ir.references, deepClone(op.reference)]
334
+ };
335
+ }
336
+ case "removeReference": {
337
+ return {
338
+ ...ir,
339
+ references: ir.references.filter((r) => r.id !== op.referenceId)
340
+ };
341
+ }
342
+ case "updateMetadata": {
343
+ return { ...ir, metadata: deepClone(op.metadata) };
344
+ }
345
+ case "updateLayout": {
346
+ const newLayout = {
347
+ mode: ir.layout.mode,
348
+ ...deepClone(op.layout)
349
+ };
350
+ return { ...ir, layout: newLayout };
351
+ }
352
+ }
353
+ }
354
+ function applyPatch(ir, patch) {
355
+ if (patch.length === 0) return ir;
356
+ let result = deepClone(ir);
357
+ for (const op of patch) {
358
+ result = applyOperation(result, op);
359
+ }
360
+ return result;
361
+ }
362
+ function composePatch(a, b) {
363
+ return [...a, ...b];
364
+ }
365
+
366
+ // src/migrate.ts
367
+ var migrations = [];
368
+ function registerMigration(migration) {
369
+ migrations.push(migration);
370
+ }
371
+ function compareVersions(a, b) {
372
+ const partsA = a.split(".").map(Number);
373
+ const partsB = b.split(".").map(Number);
374
+ const len = Math.max(partsA.length, partsB.length);
375
+ for (let i = 0; i < len; i++) {
376
+ const numA = partsA[i] ?? 0;
377
+ const numB = partsB[i] ?? 0;
378
+ if (numA < numB) return -1;
379
+ if (numA > numB) return 1;
380
+ }
381
+ return 0;
382
+ }
383
+ function migrateIR(ir, targetVersion) {
384
+ let current = { ...ir };
385
+ if (current.version === targetVersion) {
386
+ return current;
387
+ }
388
+ if (compareVersions(current.version, targetVersion) > 0) {
389
+ throw new Error(
390
+ `Cannot downgrade IR from version "${current.version}" to "${targetVersion}". Only forward migrations are supported.`
391
+ );
392
+ }
393
+ const migrationMap = /* @__PURE__ */ new Map();
394
+ for (const m of migrations) {
395
+ migrationMap.set(m.from, m);
396
+ }
397
+ let iterations = 0;
398
+ const maxIterations = migrations.length + 1;
399
+ while (current.version !== targetVersion && iterations < maxIterations) {
400
+ const migration = migrationMap.get(current.version);
401
+ if (!migration) {
402
+ throw new Error(
403
+ `No migration registered from version "${current.version}" toward target "${targetVersion}".`
404
+ );
405
+ }
406
+ if (compareVersions(migration.to, targetVersion) > 0) {
407
+ throw new Error(
408
+ `Migration from "${migration.from}" to "${migration.to}" would overshoot target version "${targetVersion}".`
409
+ );
410
+ }
411
+ current = migration.migrate(current);
412
+ current = { ...current, version: migration.to };
413
+ iterations++;
414
+ }
415
+ if (current.version !== targetVersion) {
416
+ throw new Error(
417
+ `Could not reach target version "${targetVersion}" from "${ir.version}". Stopped at "${current.version}".`
418
+ );
419
+ }
420
+ return current;
421
+ }
422
+ function clearMigrations() {
423
+ migrations.length = 0;
424
+ }
425
+
426
+ // src/factory.ts
427
+ function createEmptyIR(id) {
428
+ return {
429
+ version: "1.0.0",
430
+ id,
431
+ metadata: {},
432
+ blocks: [],
433
+ references: [],
434
+ layout: {
435
+ mode: "document",
436
+ spacing: "normal"
437
+ }
438
+ };
439
+ }
440
+
441
+ exports.applyPatch = applyPatch;
442
+ exports.clearMigrations = clearMigrations;
443
+ exports.composePatch = composePatch;
444
+ exports.createEmptyIR = createEmptyIR;
445
+ exports.diffIR = diffIR;
446
+ exports.generateBlockId = generateBlockId;
447
+ exports.generateDocumentId = generateDocumentId;
448
+ exports.migrateIR = migrateIR;
449
+ exports.registerMigration = registerMigration;
450
+ exports.resolveBlockIdCollisions = resolveBlockIdCollisions;
451
+ exports.validateIR = validateIR;
452
+ //# sourceMappingURL=index.cjs.map
453
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ids.ts","../src/validate.ts","../src/diff.ts","../src/patch.ts","../src/migrate.ts","../src/factory.ts"],"names":["createHash"],"mappings":"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","file":"index.cjs","sourcesContent":["import { createHash } from 'node:crypto';\n\n// ─── Hashing Helpers ─────────────────────────────────────────\n\nfunction sha256Hex(input: string): string {\n return createHash('sha256').update(input).digest('hex');\n}\n\n// ─── Block ID Generation ─────────────────────────────────────\n\n/**\n * Generates a content-addressed block ID.\n *\n * Returns `\"b-\"` + first 12 hex chars of SHA-256(documentId + blockType + SHA-256(content)).\n */\nexport function generateBlockId(\n documentId: string,\n blockType: string,\n content: string,\n): string {\n const contentFingerprint = sha256Hex(content);\n const hash = sha256Hex(documentId + blockType + contentFingerprint);\n return `b-${hash.slice(0, 12)}`;\n}\n\n// ─── Document ID Generation ──────────────────────────────────\n\n/**\n * Generates a document ID from the given options.\n *\n * Priority:\n * 1. If `glyphId` is present, return it directly.\n * 2. If `filePath` is present, normalize to forward slashes and return.\n * 3. If `content` is present, return `\"doc-\"` + first 16 hex chars of SHA-256(content).\n */\nexport function generateDocumentId(options: {\n glyphId?: string;\n filePath?: string;\n content?: string;\n}): string {\n if (options.glyphId) {\n return options.glyphId;\n }\n\n if (options.filePath) {\n return options.filePath.replace(/\\\\/g, '/');\n }\n\n if (options.content) {\n const hash = sha256Hex(options.content);\n return `doc-${hash.slice(0, 16)}`;\n }\n\n return `doc-${sha256Hex('')}`.slice(0, 20);\n}\n\n// ─── Collision Resolution ────────────────────────────────────\n\n/**\n * Resolves duplicate block IDs by appending `-1`, `-2`, etc. to collisions.\n *\n * The first occurrence of an ID is left unchanged; subsequent duplicates\n * receive numeric suffixes.\n */\nexport function resolveBlockIdCollisions(ids: string[]): string[] {\n const seen = new Map<string, number>();\n const result: string[] = [];\n\n for (const id of ids) {\n const count = seen.get(id);\n if (count === undefined) {\n seen.set(id, 1);\n result.push(id);\n } else {\n seen.set(id, count + 1);\n result.push(`${id}-${String(count)}`);\n }\n }\n\n return result;\n}\n","import type { Diagnostic, GlyphIR } from '@glyphjs/types';\n\n// ─── Valid Layout Modes ──────────────────────────────────────\n\nconst VALID_LAYOUT_MODES = new Set(['document', 'dashboard', 'presentation']);\n\n// ─── IR Validation ───────────────────────────────────────────\n\n/**\n * Validates a GlyphIR document for structural integrity.\n *\n * Checks:\n * - `version` is a non-empty string\n * - `id` is a non-empty string\n * - all block IDs are unique\n * - all reference `sourceBlockId` / `targetBlockId` exist in blocks\n * - blocks have required fields (`id`, `type`, `data`, `position`)\n * - layout mode is valid\n *\n * Returns an array of `Diagnostic` objects describing any issues found.\n */\nexport function validateIR(ir: GlyphIR): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n\n // ── version ────────────────────────────────────────────────\n if (!ir.version || typeof ir.version !== 'string') {\n diagnostics.push({\n severity: 'error',\n code: 'IR_MISSING_VERSION',\n message: 'IR document must have a non-empty \"version\" string.',\n source: 'compiler',\n });\n }\n\n // ── id ─────────────────────────────────────────────────────\n if (!ir.id || typeof ir.id !== 'string') {\n diagnostics.push({\n severity: 'error',\n code: 'IR_MISSING_ID',\n message: 'IR document must have a non-empty \"id\" string.',\n source: 'compiler',\n });\n }\n\n // ── blocks: required fields ────────────────────────────────\n const blockIds = new Set<string>();\n for (const block of ir.blocks) {\n // id\n if (!block.id || typeof block.id !== 'string') {\n diagnostics.push({\n severity: 'error',\n code: 'BLOCK_MISSING_ID',\n message: 'Block is missing a non-empty \"id\" field.',\n source: 'compiler',\n });\n }\n\n // type\n if (!block.type || typeof block.type !== 'string') {\n diagnostics.push({\n severity: 'error',\n code: 'BLOCK_MISSING_TYPE',\n message: `Block \"${String(block.id)}\" is missing a non-empty \"type\" field.`,\n source: 'compiler',\n });\n }\n\n // data\n if (block.data === undefined || block.data === null) {\n diagnostics.push({\n severity: 'error',\n code: 'BLOCK_MISSING_DATA',\n message: `Block \"${String(block.id)}\" is missing a \"data\" field.`,\n source: 'compiler',\n });\n }\n\n // position\n if (!block.position) {\n diagnostics.push({\n severity: 'error',\n code: 'BLOCK_MISSING_POSITION',\n message: `Block \"${String(block.id)}\" is missing a \"position\" field.`,\n source: 'compiler',\n });\n }\n\n // Track IDs for uniqueness check\n if (block.id) {\n if (blockIds.has(block.id)) {\n diagnostics.push({\n severity: 'error',\n code: 'BLOCK_DUPLICATE_ID',\n message: `Duplicate block ID: \"${block.id}\".`,\n source: 'compiler',\n });\n }\n blockIds.add(block.id);\n }\n }\n\n // ── references: source/target exist ────────────────────────\n for (const ref of ir.references) {\n if (!blockIds.has(ref.sourceBlockId)) {\n diagnostics.push({\n severity: 'error',\n code: 'REF_MISSING_SOURCE',\n message: `Reference \"${ref.id}\" has sourceBlockId \"${ref.sourceBlockId}\" which does not exist in blocks.`,\n source: 'compiler',\n });\n }\n if (!blockIds.has(ref.targetBlockId)) {\n diagnostics.push({\n severity: 'error',\n code: 'REF_MISSING_TARGET',\n message: `Reference \"${ref.id}\" has targetBlockId \"${ref.targetBlockId}\" which does not exist in blocks.`,\n source: 'compiler',\n });\n }\n }\n\n // ── layout mode ────────────────────────────────────────────\n if (ir.layout && !VALID_LAYOUT_MODES.has(ir.layout.mode)) {\n diagnostics.push({\n severity: 'error',\n code: 'LAYOUT_INVALID_MODE',\n message: `Invalid layout mode: \"${ir.layout.mode}\". Expected one of: ${[...VALID_LAYOUT_MODES].join(', ')}.`,\n source: 'compiler',\n });\n }\n\n return diagnostics;\n}\n","import type {\n Block,\n GlyphIR,\n GlyphPatch,\n GlyphPatchOperation,\n Reference,\n} from '@glyphjs/types';\n\n// ─── Deep Equality ───────────────────────────────────────────\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== typeof b) return false;\n\n if (Array.isArray(a)) {\n if (!Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n return a.every((val, idx) => deepEqual(val, b[idx]));\n }\n\n if (typeof a === 'object' && typeof b === 'object') {\n const objA = a as Record<string, unknown>;\n const objB = b as Record<string, unknown>;\n const keysA = Object.keys(objA);\n const keysB = Object.keys(objB);\n if (keysA.length !== keysB.length) return false;\n return keysA.every((key) => deepEqual(objA[key], objB[key]));\n }\n\n return false;\n}\n\n// ─── Block Diffing Helpers ───────────────────────────────────\n\nfunction blockContentEqual(a: Block, b: Block): boolean {\n return (\n a.type === b.type &&\n deepEqual(a.data, b.data) &&\n deepEqual(a.position, b.position) &&\n deepEqual(a.children, b.children) &&\n deepEqual(a.diagnostics, b.diagnostics) &&\n deepEqual(a.metadata, b.metadata)\n );\n}\n\nfunction referenceEqual(a: Reference, b: Reference): boolean {\n return deepEqual(a, b);\n}\n\n// ─── Helpers ─────────────────────────────────────────────────\n\nfunction getBlockAt(blocks: Block[], index: number): Block | undefined {\n return blocks[index];\n}\n\n// ─── IR Diffing ──────────────────────────────────────────────\n\n/**\n * Computes a patch that transforms `before` into `after`.\n *\n * Compares blocks (added, removed, updated, moved), references (added, removed),\n * metadata changes, and layout changes.\n *\n * Invariant: `applyPatch(before, diffIR(before, after))` deep-equals `after`.\n */\nexport function diffIR(before: GlyphIR, after: GlyphIR): GlyphPatch {\n const ops: GlyphPatchOperation[] = [];\n\n // ── Metadata diff ──────────────────────────────────────────\n // Full replacement: applyPatch replaces metadata entirely.\n if (!deepEqual(before.metadata, after.metadata)) {\n ops.push({\n op: 'updateMetadata',\n metadata: after.metadata,\n });\n }\n\n // ── Layout diff ────────────────────────────────────────────\n // Full replacement: applyPatch builds a new layout from the patch.\n if (!deepEqual(before.layout, after.layout)) {\n ops.push({\n op: 'updateLayout',\n layout: after.layout,\n });\n }\n\n // ── Block diff ─────────────────────────────────────────────\n const beforeBlockMap = new Map<string, Block>();\n for (const block of before.blocks) {\n beforeBlockMap.set(block.id, block);\n }\n\n const afterBlockMap = new Map<string, Block>();\n for (const block of after.blocks) {\n afterBlockMap.set(block.id, block);\n }\n\n // Removed blocks: in before but not in after\n for (const block of before.blocks) {\n if (!afterBlockMap.has(block.id)) {\n ops.push({ op: 'removeBlock', blockId: block.id });\n }\n }\n\n // Added and updated blocks (process in after-order for correct positioning)\n for (let i = 0; i < after.blocks.length; i++) {\n const afterBlock = getBlockAt(after.blocks, i);\n if (!afterBlock) continue;\n\n const beforeBlock = beforeBlockMap.get(afterBlock.id);\n const prevBlock = i > 0 ? getBlockAt(after.blocks, i - 1) : undefined;\n const afterBlockId = prevBlock?.id;\n\n if (!beforeBlock) {\n // Added block\n ops.push({\n op: 'addBlock',\n block: afterBlock,\n afterBlockId,\n });\n } else {\n // Existing block: check content changes\n if (!blockContentEqual(beforeBlock, afterBlock)) {\n // Build the partial update containing only changed fields\n const data: Partial<Block> = {};\n if (afterBlock.type !== beforeBlock.type) {\n data.type = afterBlock.type;\n }\n if (!deepEqual(afterBlock.data, beforeBlock.data)) {\n data.data = afterBlock.data;\n }\n if (!deepEqual(afterBlock.position, beforeBlock.position)) {\n data.position = afterBlock.position;\n }\n if (!deepEqual(afterBlock.children, beforeBlock.children)) {\n data.children = afterBlock.children;\n }\n if (!deepEqual(afterBlock.diagnostics, beforeBlock.diagnostics)) {\n data.diagnostics = afterBlock.diagnostics;\n }\n if (!deepEqual(afterBlock.metadata, beforeBlock.metadata)) {\n data.metadata = afterBlock.metadata;\n }\n ops.push({\n op: 'updateBlock',\n blockId: afterBlock.id,\n data,\n });\n }\n\n // Check if position in array changed (moved)\n const beforeIndex = before.blocks.findIndex(\n (b) => b.id === afterBlock.id,\n );\n const prevBeforeBlock =\n beforeIndex > 0\n ? getBlockAt(before.blocks, beforeIndex - 1)\n : undefined;\n const expectedAfterBlockId = prevBeforeBlock?.id;\n if (afterBlockId !== expectedAfterBlockId) {\n // Emit move when the predecessor in the after-array differs from\n // the predecessor in the before-array.\n ops.push({\n op: 'moveBlock',\n blockId: afterBlock.id,\n afterBlockId,\n });\n }\n }\n }\n\n // ── Reference diff ─────────────────────────────────────────\n const beforeRefMap = new Map<string, Reference>();\n for (const ref of before.references) {\n beforeRefMap.set(ref.id, ref);\n }\n\n const afterRefMap = new Map<string, Reference>();\n for (const ref of after.references) {\n afterRefMap.set(ref.id, ref);\n }\n\n // Removed references\n for (const ref of before.references) {\n if (!afterRefMap.has(ref.id)) {\n ops.push({ op: 'removeReference', referenceId: ref.id });\n }\n }\n\n // Added references (and implicitly updated — references are replaced entirely)\n for (const ref of after.references) {\n const beforeRef = beforeRefMap.get(ref.id);\n if (!beforeRef) {\n ops.push({ op: 'addReference', reference: ref });\n } else if (!referenceEqual(beforeRef, ref)) {\n // Replace: remove then add\n ops.push({ op: 'removeReference', referenceId: ref.id });\n ops.push({ op: 'addReference', reference: ref });\n }\n }\n\n return ops;\n}\n","import type {\n Block,\n GlyphIR,\n GlyphPatch,\n GlyphPatchOperation,\n LayoutHints,\n} from '@glyphjs/types';\n\n// ─── Deep Clone ──────────────────────────────────────────────\n\nfunction deepClone<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\n// ─── Single Operation Application ────────────────────────────\n\nfunction applyOperation(ir: GlyphIR, op: GlyphPatchOperation): GlyphIR {\n switch (op.op) {\n case 'addBlock': {\n const blocks = [...ir.blocks];\n if (op.afterBlockId) {\n const idx = blocks.findIndex((b) => b.id === op.afterBlockId);\n if (idx !== -1) {\n blocks.splice(idx + 1, 0, deepClone(op.block));\n } else {\n // afterBlockId not found — append at end\n blocks.push(deepClone(op.block));\n }\n } else {\n // No afterBlockId — insert at beginning\n blocks.unshift(deepClone(op.block));\n }\n return { ...ir, blocks };\n }\n\n case 'removeBlock': {\n const blocks = ir.blocks.filter((b) => b.id !== op.blockId);\n return { ...ir, blocks };\n }\n\n case 'updateBlock': {\n const blocks = ir.blocks.map((b) => {\n if (b.id !== op.blockId) return b;\n const updated: Block = { ...b };\n if (op.data.type !== undefined) updated.type = op.data.type;\n if (op.data.data !== undefined) updated.data = deepClone(op.data.data);\n if (op.data.position !== undefined)\n updated.position = deepClone(op.data.position);\n if (op.data.children !== undefined)\n updated.children = deepClone(op.data.children);\n if (op.data.diagnostics !== undefined)\n updated.diagnostics = deepClone(op.data.diagnostics);\n if (op.data.metadata !== undefined)\n updated.metadata = deepClone(op.data.metadata);\n return updated;\n });\n return { ...ir, blocks };\n }\n\n case 'moveBlock': {\n const blockToMove = ir.blocks.find((b) => b.id === op.blockId);\n if (!blockToMove) return ir;\n // Remove the block from its current position\n const blocks = ir.blocks.filter((b) => b.id !== op.blockId);\n // Insert at new position\n if (op.afterBlockId) {\n const idx = blocks.findIndex((b) => b.id === op.afterBlockId);\n if (idx !== -1) {\n blocks.splice(idx + 1, 0, blockToMove);\n } else {\n blocks.push(blockToMove);\n }\n } else {\n // No afterBlockId — move to beginning\n blocks.unshift(blockToMove);\n }\n return { ...ir, blocks };\n }\n\n case 'addReference': {\n return {\n ...ir,\n references: [...ir.references, deepClone(op.reference)],\n };\n }\n\n case 'removeReference': {\n return {\n ...ir,\n references: ir.references.filter((r) => r.id !== op.referenceId),\n };\n }\n\n case 'updateMetadata': {\n // Full replacement: all DocumentMetadata fields are optional,\n // so Partial<DocumentMetadata> is structurally identical.\n return { ...ir, metadata: deepClone(op.metadata) };\n }\n\n case 'updateLayout': {\n // Full replacement using the provided layout. Falls back to the\n // current mode if not provided (mode is required on LayoutHints).\n const newLayout: LayoutHints = {\n mode: ir.layout.mode,\n ...deepClone(op.layout),\n };\n return { ...ir, layout: newLayout };\n }\n }\n}\n\n// ─── Patch Application ───────────────────────────────────────\n\n/**\n * Applies a patch to an IR document, returning a new IR (immutable).\n *\n * Invariant: `applyPatch(ir, [])` returns `ir` unchanged (identity).\n */\nexport function applyPatch(ir: GlyphIR, patch: GlyphPatch): GlyphIR {\n if (patch.length === 0) return ir;\n\n let result = deepClone(ir);\n for (const op of patch) {\n result = applyOperation(result, op);\n }\n return result;\n}\n\n// ─── Patch Composition ───────────────────────────────────────\n\n/**\n * Composes two patches into a single patch.\n *\n * Simplistic but correct: concatenates the operations of `a` followed by `b`.\n *\n * Invariant: `composePatch` is associative —\n * `compose(compose(a, b), c)` equals `compose(a, compose(b, c))`.\n */\nexport function composePatch(a: GlyphPatch, b: GlyphPatch): GlyphPatch {\n return [...a, ...b];\n}\n","import type { GlyphIR, IRMigration } from '@glyphjs/types';\n\n// ─── Migration Registry ──────────────────────────────────────\n\nconst migrations: IRMigration[] = [];\n\n/**\n * Registers a migration that transforms IR from one version to another.\n *\n * Migrations are pure functions registered in `@glyphjs/ir`.\n * They are applied in sequence by `migrateIR`.\n */\nexport function registerMigration(migration: IRMigration): void {\n migrations.push(migration);\n}\n\n// ─── Version Comparison ──────────────────────────────────────\n\nfunction compareVersions(a: string, b: string): number {\n const partsA = a.split('.').map(Number);\n const partsB = b.split('.').map(Number);\n const len = Math.max(partsA.length, partsB.length);\n\n for (let i = 0; i < len; i++) {\n const numA = partsA[i] ?? 0;\n const numB = partsB[i] ?? 0;\n if (numA < numB) return -1;\n if (numA > numB) return 1;\n }\n\n return 0;\n}\n\n// ─── IR Migration ────────────────────────────────────────────\n\n/**\n * Migrates an IR document from its current version to the target version\n * by applying registered migrations in sequence.\n *\n * Throws an error if:\n * - The IR version is already at or ahead of the target version\n * and no migration path exists.\n * - No migration is registered for a required intermediate version.\n * - The target version is older than the current version (downgrade).\n */\nexport function migrateIR(ir: GlyphIR, targetVersion: string): GlyphIR {\n let current = { ...ir };\n\n if (current.version === targetVersion) {\n return current;\n }\n\n if (compareVersions(current.version, targetVersion) > 0) {\n throw new Error(\n `Cannot downgrade IR from version \"${current.version}\" to \"${targetVersion}\". Only forward migrations are supported.`,\n );\n }\n\n // Build a map of migrations keyed by \"from\" version\n const migrationMap = new Map<string, IRMigration>();\n for (const m of migrations) {\n migrationMap.set(m.from, m);\n }\n\n // Apply migrations in sequence\n let iterations = 0;\n const maxIterations = migrations.length + 1;\n\n while (\n current.version !== targetVersion &&\n iterations < maxIterations\n ) {\n const migration = migrationMap.get(current.version);\n if (!migration) {\n throw new Error(\n `No migration registered from version \"${current.version}\" toward target \"${targetVersion}\".`,\n );\n }\n\n if (compareVersions(migration.to, targetVersion) > 0) {\n throw new Error(\n `Migration from \"${migration.from}\" to \"${migration.to}\" would overshoot target version \"${targetVersion}\".`,\n );\n }\n\n current = migration.migrate(current);\n current = { ...current, version: migration.to };\n iterations++;\n }\n\n if (current.version !== targetVersion) {\n throw new Error(\n `Could not reach target version \"${targetVersion}\" from \"${ir.version}\". Stopped at \"${current.version}\".`,\n );\n }\n\n return current;\n}\n\n/**\n * Clears all registered migrations. Useful for testing.\n */\nexport function clearMigrations(): void {\n migrations.length = 0;\n}\n","import type { GlyphIR } from '@glyphjs/types';\n\n// ─── IR Factory Helpers ──────────────────────────────────────\n\n/**\n * Creates a valid empty GlyphIR document with default values.\n *\n * Returns:\n * ```json\n * {\n * \"version\": \"1.0.0\",\n * \"id\": id,\n * \"metadata\": {},\n * \"blocks\": [],\n * \"references\": [],\n * \"layout\": { \"mode\": \"document\", \"spacing\": \"normal\" }\n * }\n * ```\n */\nexport function createEmptyIR(id: string): GlyphIR {\n return {\n version: '1.0.0',\n id,\n metadata: {},\n blocks: [],\n references: [],\n layout: {\n mode: 'document',\n spacing: 'normal',\n },\n };\n}\n"]}