@shapeshift-labs/frontier-lang 0.1.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,608 +1,9 @@
1
- export function moduleNode(input) {
2
- return { ...input, kind: "module" };
3
- }
4
-
5
- export function entityNode(input) {
6
- return { ...input, kind: "entity" };
7
- }
8
-
9
- export function stateNode(input) {
10
- return { ...input, kind: "state" };
11
- }
12
-
13
- export function actionNode(input) {
14
- return { ...input, kind: "action" };
15
- }
16
-
17
- export function viewNode(input) {
18
- return { ...input, kind: "view" };
19
- }
20
-
21
- export function migrationNode(input) {
22
- return { ...input, kind: "migration" };
23
- }
24
-
25
- export function effectNode(input) {
26
- return { ...input, kind: "effect" };
27
- }
28
-
29
- export function targetNode(input) {
30
- return { ...input, kind: "target" };
31
- }
32
-
33
- export function createPatch(input) {
34
- return {
35
- ...input,
36
- kind: "frontier.lang.patch",
37
- version: 1
38
- };
39
- }
40
-
41
- export function createDocument(input) {
42
- const nodes = {};
43
- for (const node of input.nodes) {
44
- if (nodes[node.id]) {
45
- throw new Error(`Duplicate semantic node id: ${node.id}`);
46
- }
47
- nodes[node.id] = node;
48
- }
49
-
50
- return {
51
- kind: "frontier.lang.document",
52
- version: 1,
53
- id: input.id,
54
- name: input.name,
55
- rootIds: input.rootIds ?? input.nodes.filter((node) => !node.parentId).map((node) => node.id),
56
- nodes,
57
- history: input.history,
58
- metadata: input.metadata
59
- };
60
- }
61
-
62
- export function stableStringify(value) {
63
- if (value === null || typeof value !== "object") {
64
- return JSON.stringify(value);
65
- }
66
-
67
- if (Array.isArray(value)) {
68
- return `[${value.map((item) => stableStringify(item)).join(",")}]`;
69
- }
70
-
71
- const entries = Object.entries(value)
72
- .filter(([, item]) => item !== undefined)
73
- .sort(([left], [right]) => ordinalCompare(left, right));
74
-
75
- return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(",")}}`;
76
- }
77
-
78
- export function hashSemanticValue(value) {
79
- const serialized = stableStringify(value);
80
- let hash = 0x811c9dc5;
81
- for (let index = 0; index < serialized.length; index += 1) {
82
- hash ^= serialized.charCodeAt(index);
83
- hash = Math.imul(hash, 0x01000193);
84
- }
85
- return `fnv1a32:${(hash >>> 0).toString(16).padStart(8, "0")}`;
86
- }
87
-
88
- export function hashDocumentBase(document) {
89
- return hashSemanticValue(stripHistory(document));
90
- }
91
-
92
- export function validateDocument(document) {
93
- const issues = [];
94
- const rootSet = new Set();
95
-
96
- for (const rootId of document.rootIds) {
97
- if (rootSet.has(rootId)) {
98
- issues.push(`Duplicate root node: ${rootId}`);
99
- }
100
- rootSet.add(rootId);
101
- if (!document.nodes[rootId]) {
102
- issues.push(`Missing root node: ${rootId}`);
103
- }
104
- }
105
-
106
- for (const [nodeId, node] of Object.entries(document.nodes)) {
107
- if (node.id !== nodeId) {
108
- issues.push(`Node record key ${nodeId} does not match node id ${node.id}`);
109
- }
110
-
111
- if (node.parentId && !document.nodes[node.parentId]) {
112
- issues.push(`Node ${node.id} references missing parent ${node.parentId}`);
113
- }
114
-
115
- if (node.parentId === node.id) {
116
- issues.push(`Node ${node.id} cannot be its own parent`);
117
- }
118
-
119
- if (!node.parentId && !rootSet.has(node.id)) {
120
- issues.push(`Parentless node ${node.id} is missing from rootIds`);
121
- }
122
-
123
- if (node.parentId && rootSet.has(node.id)) {
124
- issues.push(`Node ${node.id} has a parent and cannot be a root`);
125
- }
126
-
127
- if (hasAncestorCycle(document.nodes, node.id)) {
128
- issues.push(`Node ${node.id} is part of a parent cycle`);
129
- }
130
-
131
- if (node.kind === "entity") {
132
- const fieldIds = new Set();
133
- for (const field of node.fields) {
134
- if (fieldIds.has(field.id)) {
135
- issues.push(`Entity ${node.id} has duplicate field id ${field.id}`);
136
- }
137
- fieldIds.add(field.id);
138
- }
139
- }
140
- }
141
-
142
- return issues;
143
- }
144
-
145
- export function applySemanticPatch(document, patch, event) {
146
- if (patch.baseHash && patch.baseHash !== hashDocumentBase(document)) {
147
- throw new Error(`Patch ${patch.id} base hash does not match document`);
148
- }
149
-
150
- let nodes = { ...document.nodes };
151
- let rootIds = [...document.rootIds];
152
-
153
- for (const operation of patch.operations) {
154
- switch (operation.op) {
155
- case "upsertNode": {
156
- nodes = { ...nodes, [operation.node.id]: operation.node };
157
- if (!operation.node.parentId && !rootIds.includes(operation.node.id)) {
158
- rootIds = [...rootIds, operation.node.id];
159
- }
160
- break;
161
- }
162
- case "removeNode": {
163
- const nextNodes = { ...nodes };
164
- delete nextNodes[operation.id];
165
- nodes = nextNodes;
166
- rootIds = rootIds.filter((id) => id !== operation.id);
167
- break;
168
- }
169
- case "renameNode": {
170
- const node = requireNode(nodes, operation.id);
171
- nodes = { ...nodes, [operation.id]: { ...node, name: operation.name } };
172
- break;
173
- }
174
- case "moveNode": {
175
- const node = requireNode(nodes, operation.id);
176
- nodes = { ...nodes, [operation.id]: { ...node, parentId: operation.parentId } };
177
- rootIds = operation.parentId
178
- ? rootIds.filter((id) => id !== operation.id)
179
- : unique([...rootIds, operation.id]);
180
- break;
181
- }
182
- case "updateNode": {
183
- if (
184
- Object.hasOwn(operation.set, "id") ||
185
- Object.hasOwn(operation.set, "kind") ||
186
- Object.hasOwn(operation.set, "parentId")
187
- ) {
188
- throw new Error(`Patch ${patch.id} cannot update semantic node identity, kind, or parent`);
189
- }
190
- const node = requireNode(nodes, operation.id);
191
- nodes = { ...nodes, [operation.id]: { ...node, ...operation.set } };
192
- break;
193
- }
194
- case "addEvidence":
195
- break;
196
- default:
197
- throw new Error(`Unexpected operation: ${operation.op}`);
198
- }
199
- }
200
-
201
- const next = {
202
- ...document,
203
- rootIds,
204
- nodes
205
- };
206
-
207
- const issues = validateDocument(next);
208
- if (issues.length > 0) {
209
- throw new Error(`Invalid document after patch ${patch.id}: ${issues.join("; ")}`);
210
- }
211
-
212
- if (patch.targetHash && patch.targetHash !== hashDocumentBase(next)) {
213
- throw new Error(`Patch ${patch.id} target hash does not match result`);
214
- }
215
-
216
- return {
217
- ...next,
218
- history: [
219
- ...(document.history ?? []),
220
- event ?? {
221
- id: `event:${patch.id}`,
222
- patch
223
- }
224
- ]
225
- };
226
- }
227
-
228
- export function replayDocument(initial, events) {
229
- return events.reduce((document, event) => applySemanticPatch(document, event.patch, event), initial);
230
- }
231
-
232
- export function classifyMerge(base, left, right) {
233
- const baseHash = hashDocumentBase(base);
234
- const reasons = [];
235
-
236
- if (left.baseHash && left.baseHash !== baseHash) {
237
- reasons.push(`Left patch base hash ${left.baseHash} does not match current base ${baseHash}`);
238
- }
239
- if (right.baseHash && right.baseHash !== baseHash) {
240
- reasons.push(`Right patch base hash ${right.baseHash} does not match current base ${baseHash}`);
241
- }
242
-
243
- const leftSummary = summarizePatch(left);
244
- const rightSummary = summarizePatch(right);
245
- const overlappingNodeIds = intersection(leftSummary.nodeIds, rightSummary.nodeIds);
246
- const overlappingRegions = intersection(leftSummary.regions, rightSummary.regions);
247
- const overlappingEffects = intersection(leftSummary.effects, rightSummary.effects);
248
- const evidence = [...(left.evidence ?? []), ...(right.evidence ?? [])];
249
- const failedEvidence = evidence.filter((record) => record.status === "failed");
250
-
251
- if (reasons.length > 0) {
252
- return {
253
- status: "unknown-needs-review",
254
- autoMergeable: false,
255
- reasons,
256
- overlappingNodeIds,
257
- overlappingRegions,
258
- overlappingEffects,
259
- evidence
260
- };
261
- }
262
-
263
- if (failedEvidence.length > 0) {
264
- return {
265
- status: "unknown-needs-review",
266
- autoMergeable: false,
267
- reasons: [`Failed evidence prevents auto-merge: ${failedEvidence.map((record) => record.id).join(", ")}`],
268
- overlappingNodeIds,
269
- overlappingRegions,
270
- overlappingEffects,
271
- evidence
272
- };
273
- }
274
-
275
- if (stableStringify(left.operations) === stableStringify(right.operations)) {
276
- return {
277
- status: "safe-by-same-change",
278
- autoMergeable: true,
279
- reasons: ["Both patches contain the same semantic operations."],
280
- overlappingNodeIds,
281
- overlappingRegions,
282
- overlappingEffects,
283
- evidence
284
- };
285
- }
286
-
287
- const dynamicEffects = new Set(["dynamic", "eval", "unsafeEval", "ffi", "reflection", "proxy"]);
288
- const hasDynamic = [...leftSummary.effects, ...rightSummary.effects].some((effect) =>
289
- dynamicEffects.has(effect)
290
- );
291
- if (hasDynamic) {
292
- return {
293
- status: "unknown-by-dynamic-effect",
294
- autoMergeable: false,
295
- reasons: ["At least one patch touches a dynamic or opaque effect boundary."],
296
- overlappingNodeIds,
297
- overlappingRegions,
298
- overlappingEffects,
299
- evidence
300
- };
301
- }
302
-
303
- if (overlappingEffects.length > 0) {
304
- return {
305
- status: "conflict-by-effect-overlap",
306
- autoMergeable: false,
307
- reasons: [`Both patches touch effect(s): ${overlappingEffects.join(", ")}`],
308
- overlappingNodeIds,
309
- overlappingRegions,
310
- overlappingEffects,
311
- evidence
312
- };
313
- }
314
-
315
- if (overlappingNodeIds.length > 0 || overlappingRegions.length > 0) {
316
- const laws = collectMergeLaws(base, [...overlappingNodeIds, ...overlappingRegions]);
317
- if (laws.length > 0 && laws.every((law) => law === "semilattice" || law === "commutative")) {
318
- return withReplayGate(base, left, right, {
319
- status: "safe-by-merge-law",
320
- autoMergeable: true,
321
- reasons: [`Overlaps are covered by merge law(s): ${unique(laws).join(", ")}`],
322
- overlappingNodeIds,
323
- overlappingRegions,
324
- overlappingEffects,
325
- evidence
326
- });
327
- }
328
-
329
- return {
330
- status: "conflict-by-overlap",
331
- autoMergeable: false,
332
- reasons: ["Patches touch the same semantic node or region without a known merge law."],
333
- overlappingNodeIds,
334
- overlappingRegions,
335
- overlappingEffects,
336
- evidence
337
- };
338
- }
339
-
340
- return withReplayGate(base, left, right, {
341
- status: "safe-by-disjoint-region",
342
- autoMergeable: true,
343
- reasons: ["Patches touch disjoint semantic nodes, regions, and effects."],
344
- overlappingNodeIds,
345
- overlappingRegions,
346
- overlappingEffects,
347
- evidence
348
- });
349
- }
350
-
351
- export function emitTypeScript(document, options = {}) {
352
- const lines = [];
353
- const banner = options.banner ?? "Generated by @shapeshift-labs/frontier-lang.";
354
- lines.push(`// ${banner}`);
355
- lines.push("");
356
-
357
- if (options.includeRuntimeTypes ?? true) {
358
- lines.push("export type FrontierPatchOperation =");
359
- lines.push(" | { op: 'set'; path: string; value: unknown }");
360
- lines.push(" | { op: 'remove'; path: string }");
361
- lines.push(" | { op: 'insert'; path: string; value: unknown }");
362
- lines.push(" | { op: 'merge'; path: string; value: unknown };");
363
- lines.push("");
364
- }
365
-
366
- for (const node of Object.values(document.nodes)) {
367
- if (node.kind === "entity") {
368
- lines.push(`export interface ${safeIdentifier(node.name)} {`);
369
- for (const field of node.fields) {
370
- lines.push(` ${safeIdentifier(field.name)}: ${toTypeScriptType(field.type)};`);
371
- }
372
- lines.push("}");
373
- lines.push("");
374
- }
375
- }
376
-
377
- for (const node of Object.values(document.nodes)) {
378
- if (node.kind === "action") {
379
- const inputType = node.input ? toTypeScriptType(node.input) : "unknown";
380
- const returnType = node.returns ? toTypeScriptType(node.returns) : "FrontierPatchOperation[]";
381
- lines.push(`export function ${safeIdentifier(node.name)}(`);
382
- lines.push(" state: unknown,");
383
- lines.push(` input: ${inputType},`);
384
- lines.push(" env: Record<string, unknown> = {}");
385
- lines.push(`): ${returnType} {`);
386
- lines.push(" void state;");
387
- lines.push(" void input;");
388
- lines.push(" void env;");
389
- lines.push(` return [] as ${returnType};`);
390
- lines.push("}");
391
- lines.push("");
392
- }
393
- }
394
-
395
- return `${lines.join("\n").trimEnd()}\n`;
396
- }
397
-
398
- function stripHistory(document) {
399
- const { history: _history, ...rest } = document;
400
- return rest;
401
- }
402
-
403
- function requireNode(nodes, id) {
404
- const node = nodes[id];
405
- if (!node) {
406
- throw new Error(`Unknown semantic node id: ${id}`);
407
- }
408
- return node;
409
- }
410
-
411
- function hasAncestorCycle(nodes, startId) {
412
- const seen = new Set();
413
- let current = nodes[startId];
414
- while (current?.parentId) {
415
- if (seen.has(current.parentId)) {
416
- return true;
417
- }
418
- seen.add(current.parentId);
419
- current = nodes[current.parentId];
420
- }
421
- return false;
422
- }
423
-
424
- function summarizePatch(patch) {
425
- const nodeIds = [];
426
- const regions = [];
427
- const effects = [];
428
-
429
- for (const operation of patch.operations) {
430
- if ("id" in operation && typeof operation.id === "string") {
431
- nodeIds.push(operation.id);
432
- }
433
- if (operation.op === "upsertNode") {
434
- nodeIds.push(operation.node.id);
435
- for (const region of operation.node.regions ?? []) {
436
- regions.push(region.id);
437
- }
438
- if (operation.node.kind === "action") {
439
- effects.push(...(operation.node.uses ?? []));
440
- }
441
- if (operation.node.kind === "effect") {
442
- effects.push(operation.node.capability);
443
- }
444
- }
445
- if (operation.op === "updateNode") {
446
- if (Array.isArray(operation.set.uses)) {
447
- effects.push(...operation.set.uses.filter((value) => typeof value === "string"));
448
- }
449
- if (typeof operation.set.capability === "string") {
450
- effects.push(operation.set.capability);
451
- }
452
- if (Array.isArray(operation.set.regions)) {
453
- for (const region of operation.set.regions) {
454
- if (region && typeof region === "object" && region.access === "effect" && typeof region.id === "string") {
455
- effects.push(region.id);
456
- }
457
- }
458
- }
459
- }
460
-
461
- for (const region of operation.touches ?? []) {
462
- regions.push(region.id);
463
- if (region.access === "effect") {
464
- effects.push(region.id);
465
- }
466
- }
467
- }
468
-
469
- return {
470
- nodeIds: unique(nodeIds),
471
- regions: unique(regions),
472
- effects: unique(effects)
473
- };
474
- }
475
-
476
- function withReplayGate(base, left, right, admission) {
477
- if (!admission.autoMergeable) {
478
- return admission;
479
- }
480
-
481
- try {
482
- const leftForReplay = stripPatchAdmissionHashes(left);
483
- const rightForReplay = stripPatchAdmissionHashes(right);
484
- const leftThenRight = applySemanticPatch(applySemanticPatch(base, leftForReplay), rightForReplay);
485
- const rightThenLeft = applySemanticPatch(applySemanticPatch(base, rightForReplay), leftForReplay);
486
- const leftHash = hashDocumentBase(leftThenRight);
487
- const rightHash = hashDocumentBase(rightThenLeft);
488
- if (leftHash !== rightHash) {
489
- return {
490
- ...admission,
491
- status: "unknown-needs-review",
492
- autoMergeable: false,
493
- reasons: [
494
- ...admission.reasons,
495
- `Replay order is not commutative: left-right ${leftHash}, right-left ${rightHash}`
496
- ]
497
- };
498
- }
499
- return admission;
500
- } catch (error) {
501
- return {
502
- ...admission,
503
- status: "unknown-needs-review",
504
- autoMergeable: false,
505
- reasons: [...admission.reasons, `Replay gate failed: ${error instanceof Error ? error.message : String(error)}`]
506
- };
507
- }
508
- }
509
-
510
- function stripPatchAdmissionHashes(patch) {
511
- const { baseHash: _baseHash, targetHash: _targetHash, ...rest } = patch;
512
- return rest;
513
- }
514
-
515
- function collectMergeLaws(document, overlaps) {
516
- const overlapSet = new Set(overlaps);
517
- const laws = [];
518
-
519
- for (const node of Object.values(document.nodes)) {
520
- if (node.kind === "entity") {
521
- for (const field of node.fields) {
522
- if (overlapSet.has(field.id) || overlapSet.has(`${node.name}.${field.name}`)) {
523
- if (field.merge?.law) {
524
- laws.push(field.merge.law);
525
- }
526
- }
527
- }
528
- }
529
- if (node.kind === "state") {
530
- for (const collection of node.collections) {
531
- if (overlapSet.has(collection.id) || overlapSet.has(`${node.name}.${collection.name}`)) {
532
- if (collection.merge?.law) {
533
- laws.push(collection.merge.law);
534
- }
535
- }
536
- }
537
- }
538
- }
539
-
540
- return laws;
541
- }
542
-
543
- function intersection(left, right) {
544
- const rightSet = new Set(right);
545
- return unique(left.filter((item) => rightSet.has(item)));
546
- }
547
-
548
- function unique(items) {
549
- return [...new Set(items)];
550
- }
551
-
552
- function ordinalCompare(left, right) {
553
- if (left < right) {
554
- return -1;
555
- }
556
- if (left > right) {
557
- return 1;
558
- }
559
- return 0;
560
- }
561
-
562
- function safeIdentifier(name) {
563
- const identifier = name.replace(/[^A-Za-z0-9_$]/g, "_");
564
- if (/^[A-Za-z_$]/.test(identifier)) {
565
- return identifier;
566
- }
567
- return `_${identifier}`;
568
- }
569
-
570
- function toTypeScriptType(type) {
571
- const primitive = {
572
- Text: "string",
573
- String: "string",
574
- Bool: "boolean",
575
- Boolean: "boolean",
576
- Int: "number",
577
- Float: "number",
578
- Number: "number",
579
- Instant: "string",
580
- Json: "unknown",
581
- Patch: "FrontierPatchOperation[]"
582
- };
583
-
584
- if (primitive[type]) {
585
- return primitive[type];
586
- }
587
-
588
- const setMatch = /^Set<(.+)>$/.exec(type);
589
- if (setMatch) {
590
- return `ReadonlySet<${toTypeScriptType(setMatch[1].trim())}>`;
591
- }
592
-
593
- const listMatch = /^(List|MoveList)<(.+)>$/.exec(type);
594
- if (listMatch) {
595
- return `readonly ${toTypeScriptType(listMatch[2].trim())}[]`;
596
- }
597
-
598
- const mapMatch = /^Map<(.+),(.+)>$/.exec(type);
599
- if (mapMatch) {
600
- return `ReadonlyMap<${toTypeScriptType(mapMatch[1].trim())}, ${toTypeScriptType(mapMatch[2].trim())}>`;
601
- }
602
-
603
- if (/^\{.*\}$/.test(type)) {
604
- return "Record<string, unknown>";
605
- }
606
-
607
- return safeIdentifier(type);
608
- }
1
+ export * from "@shapeshift-labs/frontier-lang-kernel";
2
+ export * from "@shapeshift-labs/frontier-lang-parser";
3
+ export * from "@shapeshift-labs/frontier-lang-checker";
4
+ export * from "@shapeshift-labs/frontier-lang-typescript";
5
+ export * from "@shapeshift-labs/frontier-lang-javascript";
6
+ export * from "@shapeshift-labs/frontier-lang-rust";
7
+ export * from "@shapeshift-labs/frontier-lang-python";
8
+ export * from "@shapeshift-labs/frontier-lang-c";
9
+ export * from "@shapeshift-labs/frontier-lang-compiler";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang",
3
- "version": "0.1.0",
4
- "description": "Patch-native semantic source kernel for replayable programs, merge admission, and generated TypeScript projections.",
3
+ "version": "0.4.0",
4
+ "description": "Umbrella package for Frontier Lang kernel, parser, checker, compiler, and projection packages.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -21,8 +21,13 @@
21
21
  "scripts": {
22
22
  "build": "node scripts/build.mjs",
23
23
  "test": "npm run build && node test/smoke.mjs",
24
+ "fuzz": "npm run build && node fuzz/smoke.mjs",
25
+ "bench": "npm run build && node bench/smoke.mjs",
24
26
  "prepare": "npm run build",
25
- "prepack": "npm test"
27
+ "prepack": "npm test",
28
+ "pack:dry": "npm pack --dry-run",
29
+ "readme:packages": "node benchmarks/package-readme-sections.mjs",
30
+ "readme:packages:check": "node benchmarks/package-readme-sections.mjs --check"
26
31
  },
27
32
  "keywords": [
28
33
  "frontier",
@@ -30,7 +35,9 @@
30
35
  "patch",
31
36
  "replay",
32
37
  "compiler",
33
- "typescript"
38
+ "typescript",
39
+ "parser",
40
+ "checker"
34
41
  ],
35
42
  "author": "ShapeShift Labs",
36
43
  "license": "MIT",
@@ -44,5 +51,16 @@
44
51
  "homepage": "https://github.com/siliconjungle/-shapeshift-labs-frontier-lang#readme",
45
52
  "publishConfig": {
46
53
  "access": "public"
54
+ },
55
+ "dependencies": {
56
+ "@shapeshift-labs/frontier-lang-kernel": "0.3.0",
57
+ "@shapeshift-labs/frontier-lang-parser": "0.3.0",
58
+ "@shapeshift-labs/frontier-lang-checker": "0.3.0",
59
+ "@shapeshift-labs/frontier-lang-typescript": "0.3.0",
60
+ "@shapeshift-labs/frontier-lang-javascript": "0.2.0",
61
+ "@shapeshift-labs/frontier-lang-rust": "0.2.0",
62
+ "@shapeshift-labs/frontier-lang-python": "0.2.0",
63
+ "@shapeshift-labs/frontier-lang-c": "0.2.0",
64
+ "@shapeshift-labs/frontier-lang-compiler": "0.2.0"
47
65
  }
48
66
  }