@spexcode/spec-core 0.6.3 → 0.6.5

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.
@@ -0,0 +1,44 @@
1
+ export type Units = Map<string, {
2
+ j: string;
3
+ v: unknown;
4
+ }>;
5
+ export type Delta = {
6
+ from: string;
7
+ to: string;
8
+ set: Record<string, unknown>;
9
+ del: string[];
10
+ };
11
+ type Boardish = {
12
+ nodes?: unknown;
13
+ sessions?: unknown;
14
+ [k: string]: unknown;
15
+ };
16
+ export declare function unitize(board: Boardish): {
17
+ units: Units;
18
+ ok: boolean;
19
+ };
20
+ export type UnitKeyKind = {
21
+ kind: 'node';
22
+ id: string;
23
+ } | {
24
+ kind: 'nodes-order';
25
+ } | {
26
+ kind: 'session';
27
+ id: string;
28
+ } | {
29
+ kind: 'sessions-order';
30
+ } | {
31
+ kind: 'meta';
32
+ } | {
33
+ kind: 'unknown';
34
+ key: string;
35
+ };
36
+ export declare function unitKeyKind(key: string): UnitKeyKind;
37
+ export declare function applyDelta(values: Map<string, unknown>, d: Pick<Delta, 'set' | 'del'>): Map<string, unknown>;
38
+ export declare function boardFromUnits(values: Map<string, unknown>): Boardish;
39
+ export declare function diffUnits(prev: Units, next: Units): {
40
+ set: Record<string, unknown>;
41
+ del: string[];
42
+ };
43
+ export declare const unitValues: (units: Units) => Map<string, unknown>;
44
+ export {};
@@ -0,0 +1,72 @@
1
+ // decompose a board into units. `ok` = the bijection precondition held (arrays are arrays, ids unique &
2
+ // non-empty); when false the map is still returned (usable for tagging) but must not seed a delta chain.
3
+ export function unitize(board) {
4
+ const units = new Map();
5
+ let ok = true;
6
+ const keyed = (arr, prefix, orderKey) => {
7
+ const list = Array.isArray(arr) ? arr : (ok = false, []);
8
+ const order = [];
9
+ for (const item of list) {
10
+ const id = item?.id;
11
+ if (typeof id !== 'string' || !id || units.has(`${prefix}${id}`)) {
12
+ ok = false;
13
+ continue;
14
+ }
15
+ units.set(`${prefix}${id}`, { j: JSON.stringify(item), v: item });
16
+ order.push(id);
17
+ }
18
+ units.set(orderKey, { j: JSON.stringify(order), v: order });
19
+ };
20
+ const { nodes, sessions, ...meta } = board;
21
+ keyed(nodes, 'node:', 'nodes#order');
22
+ keyed(sessions, 'sess:', 'sess#order');
23
+ units.set('meta', { j: JSON.stringify(meta), v: meta });
24
+ return { units, ok };
25
+ }
26
+ export function unitKeyKind(key) {
27
+ if (key === 'nodes#order')
28
+ return { kind: 'nodes-order' };
29
+ if (key === 'sess#order')
30
+ return { kind: 'sessions-order' };
31
+ if (key === 'meta')
32
+ return { kind: 'meta' };
33
+ if (key.startsWith('node:') && key.length > 'node:'.length)
34
+ return { kind: 'node', id: key.slice(5) };
35
+ if (key.startsWith('sess:') && key.length > 'sess:'.length)
36
+ return { kind: 'session', id: key.slice(5) };
37
+ return { kind: 'unknown', key };
38
+ }
39
+ // apply a patch to a unit-value map — the exact algorithm the dashboard mirrors in data.js, kept here so
40
+ // the round-trip property is provable against the real shape, not a paraphrase of it.
41
+ export function applyDelta(values, d) {
42
+ const out = new Map(values);
43
+ for (const key of d.del)
44
+ out.delete(key);
45
+ for (const [key, v] of Object.entries(d.set))
46
+ out.set(key, v);
47
+ return out;
48
+ }
49
+ // reconstruct the board from unit values — R(U(B)) = B on the P-satisfying subspace (the client's render
50
+ // input after every applied patch). Order rides the #order units, so array order survives the round trip.
51
+ export function boardFromUnits(values) {
52
+ const pick = (prefix, orderKey) => {
53
+ const order = values.get(orderKey) || [];
54
+ return order.map((id) => values.get(`${prefix}${id}`));
55
+ };
56
+ const meta = values.get('meta') || {};
57
+ return { ...meta, nodes: pick('node:', 'nodes#order'), sessions: pick('sess:', 'sess#order') };
58
+ }
59
+ export function diffUnits(prev, next) {
60
+ const set = {};
61
+ const del = [];
62
+ for (const [key, u] of next) {
63
+ const p = prev.get(key);
64
+ if (!p || p.j !== u.j)
65
+ set[key] = u.v;
66
+ }
67
+ for (const key of prev.keys())
68
+ if (!next.has(key))
69
+ del.push(key);
70
+ return { set, del };
71
+ }
72
+ export const unitValues = (units) => new Map([...units].map(([k, u]) => [k, u.v]));
@@ -1,45 +1,3 @@
1
- export type Units = Map<string, {
2
- j: string;
3
- v: unknown;
4
- }>;
5
- export type Delta = {
6
- from: string;
7
- to: string;
8
- set: Record<string, unknown>;
9
- del: string[];
10
- };
11
- type Boardish = {
12
- nodes?: unknown;
13
- sessions?: unknown;
14
- [k: string]: unknown;
15
- };
16
- export declare function unitize(board: Boardish): {
17
- units: Units;
18
- ok: boolean;
19
- };
20
- export type UnitKeyKind = {
21
- kind: 'node';
22
- id: string;
23
- } | {
24
- kind: 'nodes-order';
25
- } | {
26
- kind: 'session';
27
- id: string;
28
- } | {
29
- kind: 'sessions-order';
30
- } | {
31
- kind: 'meta';
32
- } | {
33
- kind: 'unknown';
34
- key: string;
35
- };
36
- export declare function unitKeyKind(key: string): UnitKeyKind;
1
+ import type { Units } from './graph-delta.js';
2
+ export * from './graph-delta.js';
37
3
  export declare function tagOf(units: Units): string;
38
- export declare function diffUnits(prev: Units, next: Units): {
39
- set: Record<string, unknown>;
40
- del: string[];
41
- };
42
- export declare function applyDelta(values: Map<string, unknown>, d: Pick<Delta, 'set' | 'del'>): Map<string, unknown>;
43
- export declare function boardFromUnits(values: Map<string, unknown>): Boardish;
44
- export declare const unitValues: (units: Units) => Map<string, unknown>;
45
- export {};
@@ -1,42 +1,8 @@
1
1
  import { createHash } from 'node:crypto';
2
- // decompose a board into units. `ok` = the bijection precondition held (arrays are arrays, ids unique &
3
- // non-empty); when false the map is still returned (usable for tagging) but must not seed a delta chain.
4
- export function unitize(board) {
5
- const units = new Map();
6
- let ok = true;
7
- const keyed = (arr, prefix, orderKey) => {
8
- const list = Array.isArray(arr) ? arr : (ok = false, []);
9
- const order = [];
10
- for (const item of list) {
11
- const id = item?.id;
12
- if (typeof id !== 'string' || !id || units.has(`${prefix}${id}`)) {
13
- ok = false;
14
- continue;
15
- }
16
- units.set(`${prefix}${id}`, { j: JSON.stringify(item), v: item });
17
- order.push(id);
18
- }
19
- units.set(orderKey, { j: JSON.stringify(order), v: order });
20
- };
21
- const { nodes, sessions, ...meta } = board;
22
- keyed(nodes, 'node:', 'nodes#order');
23
- keyed(sessions, 'sess:', 'sess#order');
24
- units.set('meta', { j: JSON.stringify(meta), v: meta });
25
- return { units, ok };
26
- }
27
- export function unitKeyKind(key) {
28
- if (key === 'nodes#order')
29
- return { kind: 'nodes-order' };
30
- if (key === 'sess#order')
31
- return { kind: 'sessions-order' };
32
- if (key === 'meta')
33
- return { kind: 'meta' };
34
- if (key.startsWith('node:') && key.length > 'node:'.length)
35
- return { kind: 'node', id: key.slice(5) };
36
- if (key.startsWith('sess:') && key.length > 'sess:'.length)
37
- return { kind: 'session', id: key.slice(5) };
38
- return { kind: 'unknown', key };
39
- }
2
+ export * from './graph-delta.js';
3
+ // the snapshot tag: a digest over every unit's key + content hash, order-independent (keys sorted). Two
4
+ // builds serializing equal content get equal tags; JSON.stringify equality is conservative (equal strings ⇒
5
+ // equal values; a key-order difference at worst re-sends an unchanged unit, never misses a changed one).
40
6
  export function tagOf(units) {
41
7
  const h = createHash('sha1');
42
8
  for (const key of [...units.keys()].sort()) {
@@ -45,40 +11,3 @@ export function tagOf(units) {
45
11
  }
46
12
  return h.digest('hex');
47
13
  }
48
- // diff two unit maps into the minimal patch: units whose serialized content moved land in `set` (with the
49
- // NEW value), units that vanished land in `del`. apply(prev, diff(prev, next)) = next — the round-trip
50
- // lemma the property tests pin down.
51
- export function diffUnits(prev, next) {
52
- const set = {};
53
- const del = [];
54
- for (const [key, u] of next) {
55
- const p = prev.get(key);
56
- if (!p || p.j !== u.j)
57
- set[key] = u.v;
58
- }
59
- for (const key of prev.keys())
60
- if (!next.has(key))
61
- del.push(key);
62
- return { set, del };
63
- }
64
- // apply a patch to a unit-value map — the exact algorithm the dashboard mirrors in data.js, kept here so
65
- // the round-trip property is provable against the real shape, not a paraphrase of it.
66
- export function applyDelta(values, d) {
67
- const out = new Map(values);
68
- for (const key of d.del)
69
- out.delete(key);
70
- for (const [key, v] of Object.entries(d.set))
71
- out.set(key, v);
72
- return out;
73
- }
74
- // reconstruct the board from unit values — R(U(B)) = B on the P-satisfying subspace (the client's render
75
- // input after every applied patch). Order rides the #order units, so array order survives the round trip.
76
- export function boardFromUnits(values) {
77
- const pick = (prefix, orderKey) => {
78
- const order = values.get(orderKey) || [];
79
- return order.map((id) => values.get(`${prefix}${id}`));
80
- };
81
- const meta = values.get('meta') || {};
82
- return { ...meta, nodes: pick('node:', 'nodes#order'), sessions: pick('sess:', 'sess#order') };
83
- }
84
- export const unitValues = (units) => new Map([...units].map(([k, u]) => [k, u.v]));
package/package.json CHANGED
@@ -1,29 +1,28 @@
1
1
  {
2
2
  "name": "@spexcode/spec-core",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "type": "module",
5
5
  "description": "SpexCode's dependency-minimal spec graph core.",
6
- "engines": {
7
- "node": ">=22"
8
- },
9
- "dependencies": {},
10
6
  "files": [
11
7
  "dist",
12
8
  "templates"
13
9
  ],
14
- "types": "./dist/index.d.ts",
15
10
  "exports": {
16
- ".": {
17
- "types": "./dist/index.d.ts",
18
- "default": "./dist/index.js"
19
- },
20
- "./review": {
21
- "types": "./dist/review/index.d.ts",
22
- "default": "./dist/review/index.js"
23
- },
24
- "./identity": {
25
- "types": "./dist/identity-presets.d.ts",
26
- "default": "./dist/identity-presets.js"
27
- }
11
+ ".": "./dist/index.js",
12
+ "./graph-delta": "./dist/graph-delta.js",
13
+ "./review": "./dist/review/index.js",
14
+ "./identity": "./dist/identity-presets.js",
15
+ "./package.json": "./package.json"
16
+ },
17
+ "engines": {
18
+ "node": ">=22"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "scripts": {
24
+ "build": "node ../../scripts/build-dist.mjs",
25
+ "prepublishOnly": "node ../../scripts/release-publish.mjs --from-package-publish",
26
+ "pack:publishable": "node scripts/pack-publishable.mjs"
28
27
  }
29
28
  }