@shapeshift-labs/frontier-engine 0.0.0 → 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/README.md CHANGED
@@ -1,13 +1,202 @@
1
- # @shapeshift-labs/frontier-engine
1
+ # Frontier Engine
2
2
 
3
- Reserved package name for the future Frontier planned diff engine package.
3
+ Stateful planned diff engine, adaptive profiles, history planning, and reusable diff caches for Frontier.
4
4
 
5
- This placeholder is not ready for use. For now, use the staged subpath/API in the main Frontier repo while the package boundary is being finalized.
5
+ This package sits above [`@shapeshift-labs/frontier`](https://www.npmjs.com/package/@shapeshift-labs/frontier), the small JSON diff/apply core package. It uses [`@shapeshift-labs/frontier-codec`](https://www.npmjs.com/package/@shapeshift-labs/frontier-codec) for patch-history byte helpers. Keeping the engine separate keeps core imports small while giving state, history, and CRDT layers a shared planning surface.
6
6
 
7
- Planned ownership:
7
+ - npm: [`@shapeshift-labs/frontier-engine`](https://www.npmjs.com/package/@shapeshift-labs/frontier-engine)
8
+ - source: [`siliconjungle/-shapeshift-labs-frontier-engine`](https://github.com/siliconjungle/-shapeshift-labs-frontier-engine)
9
+ - license: MIT
8
10
 
9
- - createDiffEngine
10
- - adaptive profile and schema diff planning
11
- - reusable diff engine caches
12
- - profile plan snapshots
13
- - engine-level history helpers that delegate binary formats to @shapeshift-labs/frontier-codec
11
+ ## Related Packages
12
+
13
+ - [`@shapeshift-labs/frontier`](https://www.npmjs.com/package/@shapeshift-labs/frontier): core JSON diff/apply primitives.
14
+ - [`@shapeshift-labs/frontier-codec`](https://www.npmjs.com/package/@shapeshift-labs/frontier-codec): patch serialization, binary frames, canonical JSON, and patch-history codecs.
15
+ - [`@shapeshift-labs/frontier-query`](https://www.npmjs.com/package/@shapeshift-labs/frontier-query): shared query-key, selector path, condition, identity, and table-schema primitives.
16
+ - [`@shapeshift-labs/frontier-mutation`](https://www.npmjs.com/package/@shapeshift-labs/frontier-mutation): explicit mutation and selector plans that can use engine-backed diff planning.
17
+
18
+ Package source repositories:
19
+
20
+ - [`siliconjungle/-shapeshift-labs-frontier`](https://github.com/siliconjungle/-shapeshift-labs-frontier)
21
+ - [`siliconjungle/-shapeshift-labs-frontier-codec`](https://github.com/siliconjungle/-shapeshift-labs-frontier-codec)
22
+ - [`siliconjungle/-shapeshift-labs-frontier-query`](https://github.com/siliconjungle/-shapeshift-labs-frontier-query)
23
+ - [`siliconjungle/-shapeshift-labs-frontier-mutation`](https://github.com/siliconjungle/-shapeshift-labs-frontier-mutation)
24
+
25
+ ## Install
26
+
27
+ ```sh
28
+ npm install @shapeshift-labs/frontier @shapeshift-labs/frontier-codec @shapeshift-labs/frontier-engine
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ```ts
34
+ import { applyPatchImmutable } from '@shapeshift-labs/frontier';
35
+ import { createDiffEngine } from '@shapeshift-labs/frontier-engine';
36
+
37
+ const engine = createDiffEngine({
38
+ schema: {
39
+ type: 'array',
40
+ path: ['todos'],
41
+ key: 'id',
42
+ item: {
43
+ type: 'object',
44
+ fields: ['id', 'done', 'title']
45
+ }
46
+ }
47
+ });
48
+
49
+ const before = {
50
+ todos: [{ id: 'a', done: false, title: 'Draft' }]
51
+ };
52
+ const after = {
53
+ todos: [{ id: 'a', done: true, title: 'Draft' }]
54
+ };
55
+
56
+ const patch = engine.diff(before, after);
57
+ const next = applyPatchImmutable(before, patch);
58
+ ```
59
+
60
+ ## API
61
+
62
+ ```ts
63
+ import {
64
+ createDiffEngine,
65
+ cloneProfilePlans,
66
+ createEngineProfilePlansSnapshot,
67
+ mergeProfilePlans,
68
+ readProfilePlans,
69
+ type DiffEngine,
70
+ type DiffProfile,
71
+ type EngineOptions,
72
+ type ProfilePlans
73
+ } from '@shapeshift-labs/frontier-engine';
74
+ ```
75
+
76
+ ### `createDiffEngine(options?)`
77
+
78
+ Creates a reusable diff engine with optional schema, adaptive learning, history planning, and equality/profile helpers.
79
+
80
+ ```ts
81
+ const engine = createDiffEngine({
82
+ adaptive: true,
83
+ adaptiveThreshold: 2,
84
+ arrayKey: 'id'
85
+ });
86
+
87
+ const patch = engine.diff(before, after);
88
+ const reusable = [];
89
+ engine.diffInto(before, after, reusable);
90
+ ```
91
+
92
+ ### Schema Plans
93
+
94
+ Schema plans are trusted shape hints for hot JSON structures. They let the engine skip generic discovery and emit compact patches for common record-array and object shapes.
95
+
96
+ ```ts
97
+ const engine = createDiffEngine({
98
+ schema: {
99
+ type: 'array',
100
+ path: ['rows'],
101
+ key: 'id',
102
+ item: {
103
+ type: 'object',
104
+ fields: ['id', 'score', 'active', 'label']
105
+ }
106
+ }
107
+ });
108
+ ```
109
+
110
+ ### Adaptive Profiles
111
+
112
+ Adaptive engines can learn a profile from representative before/after pairs and replay that profile later.
113
+
114
+ ```ts
115
+ const trainer = createDiffEngine({ adaptive: true });
116
+ const profile = trainer.train([[before, after]]);
117
+
118
+ const profiled = createDiffEngine({ profile });
119
+ const patch = profiled.diff(before, after);
120
+ ```
121
+
122
+ ### History Helpers
123
+
124
+ The engine can plan patch histories, then delegate binary history encoding to `@shapeshift-labs/frontier-codec`.
125
+
126
+ ```ts
127
+ const patches = engine.diffHistory(initial, states);
128
+ const bytes = engine.encodeHistory(patches);
129
+ const final = engine.applyEncodedHistory(initial, bytes);
130
+ ```
131
+
132
+ ### Profile Plan Helpers
133
+
134
+ ```ts
135
+ const plans = createEngineProfilePlansSnapshot(undefined, {
136
+ schemaCount: 1,
137
+ adaptivePlan: false,
138
+ historyStrategy: 'auto'
139
+ });
140
+
141
+ const merged = mergeProfilePlans(plans, { codec: { history: 'binary' } });
142
+ ```
143
+
144
+ ## Subpath Imports
145
+
146
+ ```ts
147
+ import { createDiffEngine } from '@shapeshift-labs/frontier-engine/engine';
148
+ import { mergeProfilePlans } from '@shapeshift-labs/frontier-engine/profile';
149
+ import type { DiffEngine } from '@shapeshift-labs/frontier-engine/types';
150
+ ```
151
+
152
+ ## Package Scope
153
+
154
+ This package owns:
155
+
156
+ - `createDiffEngine()`.
157
+ - Adaptive shape learning.
158
+ - Explicit schema/profile diff planning.
159
+ - Reusable engine caches.
160
+ - Profile plan snapshots shared by state/history/codec/CRDT layers.
161
+ - Engine-level history helpers that delegate byte formats to `frontier-codec`.
162
+
163
+ It does not own:
164
+
165
+ - Stateless diff/apply primitives. Those stay in `@shapeshift-labs/frontier`.
166
+ - Patch wire formats. Those stay in `@shapeshift-labs/frontier-codec`.
167
+ - State subscriptions, routers, or maintained views. Those stay in Frontier state packages.
168
+ - CRDT actors, updates, heads, branches, sync, awareness, rich text, or providers.
169
+
170
+ ## Validation
171
+
172
+ ```sh
173
+ npm test
174
+ npm run fuzz
175
+ npm run bench
176
+ npm run pack:dry
177
+ ```
178
+
179
+ The package test suite covers root and subpath imports, schema diff/apply replay, profile snapshots, history planning, encoded history replay, and the absence of state/CRDT exports. The fuzzer covers schema and adaptive profile round-trips over record-array and object-shaped JSON.
180
+
181
+ ## Benchmarks
182
+
183
+ Run the package-local benchmark:
184
+
185
+ ```sh
186
+ npm run bench
187
+ ```
188
+
189
+ Latest local package-gate run on Node v26.1.0, darwin arm64, 3 rounds:
190
+
191
+ | Fixture | Median | p95 |
192
+ | --- | ---: | ---: |
193
+ | Engine schema diff, 1k rows | 16.67 us | 17.45 us |
194
+ | Engine apply via core patch | 0.58 us | 0.59 us |
195
+ | Engine equality no-op | 9.32 us | 9.44 us |
196
+ | Engine history encode/decode/apply | 3.97 us | 4.22 us |
197
+
198
+ These are Frontier-only package measurements, not competitor comparisons.
199
+
200
+ ## License
201
+
202
+ MIT. See [LICENSE](./LICENSE).
@@ -0,0 +1,71 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { performance } from 'node:perf_hooks';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const rootDir = path.resolve(__dirname, '..');
8
+ const args = parseArgs(process.argv.slice(2));
9
+ const rounds = readPositiveInt(args.rounds, 9);
10
+ const outPath = args.out ? path.resolve(rootDir, args.out) : null;
11
+ let sink = 0;
12
+
13
+ function measure(fn, inner) {
14
+ for (let i = 0; i < inner; i++) fn();
15
+ const samples = new Array(rounds);
16
+ for (let roundIndex = 0; roundIndex < rounds; roundIndex++) {
17
+ const start = performance.now();
18
+ for (let i = 0; i < inner; i++) fn();
19
+ samples[roundIndex] = ((performance.now() - start) * 1000) / inner;
20
+ }
21
+ samples.sort((left, right) => left - right);
22
+ return { median: percentile(samples, 0.5), p95: percentile(samples, 0.95) };
23
+ }
24
+ function runRow(name, inner, fn, extra = {}) {
25
+ const timing = measure(fn, inner);
26
+ return { fixture: name, medianUs: round(timing.median), p95Us: round(timing.p95), ...extra };
27
+ }
28
+ function printReport(report) {
29
+ console.log(report.package + ' package benchmark');
30
+ console.log('Node ' + report.node + ' on ' + report.platform + ', rounds=' + rounds);
31
+ console.log('These are Frontier-only package measurements, not competitor comparisons.');
32
+ console.log('');
33
+ console.log(padRight('Fixture', 44) + padLeft('Median', 12) + padLeft('p95', 11));
34
+ for (const row of report.rows) {
35
+ console.log(padRight(row.fixture, 44) + padLeft(formatUs(row.medianUs), 12) + padLeft(formatUs(row.p95Us), 11));
36
+ }
37
+ if (outPath) console.log('\nwrote ' + path.relative(rootDir, outPath));
38
+ }
39
+ function finish(packageName, rows) {
40
+ const report = { package: packageName, version: readPackageVersion(), generatedAt: new Date().toISOString(), node: process.version, platform: process.platform + ' ' + process.arch, rounds, rows };
41
+ if (outPath) { fs.mkdirSync(path.dirname(outPath), { recursive: true }); fs.writeFileSync(outPath, JSON.stringify(report, null, 2) + '\n'); }
42
+ printReport(report);
43
+ if (sink === 42) console.log('sink=' + sink);
44
+ }
45
+ function percentile(sorted, fraction) { return sorted[Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * fraction) - 1))]; }
46
+ function readPackageVersion() { return JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8')).version; }
47
+ function parseArgs(argv) { const out = {}; for (let i = 0; i < argv.length; i++) { const arg = argv[i]; if (arg === '--rounds') out.rounds = argv[++i]; else if (arg === '--out') out.out = argv[++i]; else if (arg === '--help' || arg === '-h') { console.log('Usage: npm run bench -- [--rounds 9] [--out benchmarks/results/package-bench.json]'); process.exit(0); } else throw new Error('unknown argument: ' + arg); } return out; }
48
+ function readPositiveInt(value, fallback) { if (value === undefined) return fallback; const number = Number(value); if (!Number.isInteger(number) || number <= 0) throw new Error('expected positive integer, got ' + value); return number; }
49
+ function round(value) { return Math.round(value * 100) / 100; }
50
+ function formatUs(value) { return value >= 1000 ? (value / 1000).toFixed(2) + ' ms' : value.toFixed(2) + ' us'; }
51
+ function padRight(value, width) { return String(value).padEnd(width); }
52
+ function padLeft(value, width) { return String(value).padStart(width); }
53
+
54
+ import { applyPatchImmutable } from '@shapeshift-labs/frontier';
55
+ import { createDiffEngine } from '../dist/index.js';
56
+
57
+ const before = { rows: makeRows(1000), meta: { version: 1 } };
58
+ const after = cloneJson(before);
59
+ after.rows[512] = { ...after.rows[512], score: 9999 };
60
+ after.meta.version = 2;
61
+ const engine = createDiffEngine({ schema: { type: 'array', path: ['rows'], key: 'id', item: { type: 'object', fields: ['id', 'score', 'active', 'label'] } } });
62
+ const patch = engine.diff(before, after);
63
+ const rows = [
64
+ runRow('Engine schema diff, 1k rows', 300, () => { sink += engine.diff(before, after).length; }),
65
+ runRow('Engine apply via core patch', 3000, () => { sink += applyPatchImmutable(before, patch).rows.length; }),
66
+ runRow('Engine equality no-op', 3000, () => { if (engine.equals(before, before)) sink++; }),
67
+ runRow('Engine history encode/decode/apply', 1000, () => { const bytes = engine.encodeHistory([patch]); sink += engine.applyEncodedHistory(before, bytes).rows.length; })
68
+ ];
69
+ finish('@shapeshift-labs/frontier-engine', rows);
70
+ function makeRows(count) { const rows = new Array(count); for (let i = 0; i < count; i++) rows[i] = { id: 'row-' + i, score: i, active: (i & 1) === 0, label: 'row ' + i }; return rows; }
71
+ function cloneJson(value) { return JSON.parse(JSON.stringify(value)); }
@@ -0,0 +1,3 @@
1
+ import type { DiffEngine, EngineOptions } from './types.js';
2
+ export declare function createDiffEngine(defaultOptions?: EngineOptions): DiffEngine;
3
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EACV,UAAU,EAGV,aAAa,EAcd,MAAM,YAAY,CAAC;AA8FpB,wBAAgB,gBAAgB,CAAC,cAAc,CAAC,EAAE,aAAa,GAAG,UAAU,CA0O3E"}