@shapeshift-labs/frontier-engine 0.0.1 → 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,51 +1,201 @@
1
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 package is not ready for production use. It exists so the package and repository names are reserved while stateful diff planning, adaptive profiles, schema plans, and engine/history boundaries are 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
7
  - npm: [`@shapeshift-labs/frontier-engine`](https://www.npmjs.com/package/@shapeshift-labs/frontier-engine)
8
8
  - source: [`siliconjungle/-shapeshift-labs-frontier-engine`](https://github.com/siliconjungle/-shapeshift-labs-frontier-engine)
9
- - core package: [`@shapeshift-labs/frontier`](https://www.npmjs.com/package/@shapeshift-labs/frontier)
10
- - codec package: [`@shapeshift-labs/frontier-codec`](https://www.npmjs.com/package/@shapeshift-labs/frontier-codec)
11
9
  - license: MIT
12
10
 
13
- ## Intended Scope
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
+ ```
14
91
 
15
- When this package graduates from placeholder status, it is expected to contain:
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
16
153
 
17
- - `createDiffEngine()` and stateful planned diff execution;
18
- - adaptive shape learning and schema/profile planning;
19
- - reusable engine caches and failed-plan thresholds;
20
- - profile plan snapshots shared by state, history, codec, and CRDT layers;
21
- - engine-level history helpers that delegate byte formats to `frontier-codec`.
154
+ This package owns:
22
155
 
23
- It should sit above `@shapeshift-labs/frontier` and use `@shapeshift-labs/frontier-codec` where patch-history serialization is needed. It should stay separate from state subscriptions, CRDT actors/updates, sync providers, logging, rich text, and storage-specific event logs.
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:
24
164
 
25
- ## Current Status
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.
26
169
 
27
- Use [`@shapeshift-labs/frontier`](https://www.npmjs.com/package/@shapeshift-labs/frontier) for the stable JSON diff/apply core and [`@shapeshift-labs/frontier-codec`](https://www.npmjs.com/package/@shapeshift-labs/frontier-codec) for patch transport codecs.
170
+ ## Validation
28
171
 
29
- The engine package is reserved only. No runtime API is exported yet.
172
+ ```sh
173
+ npm test
174
+ npm run fuzz
175
+ npm run bench
176
+ npm run pack:dry
177
+ ```
30
178
 
31
- ## Package Family
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.
32
180
 
33
- Published or active packages:
181
+ ## Benchmarks
34
182
 
35
- - [`@shapeshift-labs/frontier`](https://www.npmjs.com/package/@shapeshift-labs/frontier)
36
- - [`@shapeshift-labs/frontier-codec`](https://www.npmjs.com/package/@shapeshift-labs/frontier-codec)
37
- - [`@shapeshift-labs/frontier-mutation`](https://www.npmjs.com/package/@shapeshift-labs/frontier-mutation)
183
+ Run the package-local benchmark:
38
184
 
39
- Reserved future packages:
185
+ ```sh
186
+ npm run bench
187
+ ```
40
188
 
41
- - `@shapeshift-labs/frontier-state`
42
- - `@shapeshift-labs/frontier-crdt`
43
- - `@shapeshift-labs/frontier-crdt-sync`
44
- - `@shapeshift-labs/frontier-richtext`
45
- - `@shapeshift-labs/frontier-logging`
46
- - `@shapeshift-labs/frontier-state-cache`
47
- - `@shapeshift-labs/frontier-event-log`
48
- - `@shapeshift-labs/frontier-schema`
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.
49
199
 
50
200
  ## License
51
201
 
@@ -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"}