@earendil-works/chord 0.86.0 → 0.87.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 +22 -16
- package/dist/delta/index.d.ts +6 -4
- package/dist/delta/index.d.ts.map +1 -1
- package/dist/delta/index.js +44 -2
- package/dist/delta/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/provider.d.ts.map +1 -1
- package/dist/services/provider.js +46 -16
- package/dist/services/provider.js.map +1 -1
- package/dist/services/state-internals.d.ts +0 -1
- package/dist/services/state-internals.d.ts.map +1 -1
- package/dist/services/state-internals.js.map +1 -1
- package/dist/services/state.d.ts +2 -2
- package/dist/services/state.d.ts.map +1 -1
- package/dist/services/state.js +105 -32
- package/dist/services/state.js.map +1 -1
- package/dist/state/diff.d.ts +5 -0
- package/dist/state/diff.d.ts.map +1 -0
- package/dist/state/diff.js +259 -0
- package/dist/state/diff.js.map +1 -0
- package/dist/state/draft.d.ts +13 -0
- package/dist/state/draft.d.ts.map +1 -0
- package/dist/state/draft.js +540 -0
- package/dist/state/draft.js.map +1 -0
- package/dist/state/value.d.ts +9 -0
- package/dist/state/value.d.ts.map +1 -0
- package/dist/state/value.js +175 -0
- package/dist/state/value.js.map +1 -0
- package/dist/types.d.ts +8 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/delta/README.md +9 -5
package/dist/services/state.js
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
import { BACKGROUND_CONTEXT } from "../context/index.js";
|
|
2
|
-
import { applyImmutable, isBase
|
|
2
|
+
import { applyImmutable, isBase } from "../delta/index.js";
|
|
3
|
+
import { diffRevisions } from "../state/diff.js";
|
|
4
|
+
import { produceWithMetadata } from "../state/draft.js";
|
|
5
|
+
import { JsonRevisionStore } from "../state/value.js";
|
|
3
6
|
import { registerReplicatedStateInternals } from "./state-internals.js";
|
|
4
7
|
export class MutableReplicatedStateImpl {
|
|
5
|
-
#listeners = new
|
|
8
|
+
#listeners = new Map();
|
|
6
9
|
#sourceListeners = new Set();
|
|
7
|
-
#
|
|
8
|
-
#
|
|
10
|
+
#store = new JsonRevisionStore();
|
|
11
|
+
#publications = [];
|
|
12
|
+
#value;
|
|
9
13
|
#sequence = 0;
|
|
14
|
+
#changing = false;
|
|
15
|
+
#delivering = false;
|
|
10
16
|
constructor(initial) {
|
|
11
|
-
this.#
|
|
12
|
-
this.#publishedValue = applyImmutable(undefined, this.#tracker.flush());
|
|
17
|
+
this.#value = this.#store.import(initial);
|
|
13
18
|
const thisSource = this;
|
|
14
19
|
registerReplicatedStateInternals(this, {
|
|
15
20
|
get sequence() {
|
|
16
21
|
return thisSource.#sequence;
|
|
17
22
|
},
|
|
18
23
|
get value() {
|
|
19
|
-
return thisSource.#
|
|
24
|
+
return thisSource.#value;
|
|
20
25
|
},
|
|
21
|
-
publish: (context) => thisSource.publish(context),
|
|
22
26
|
subscribe: (listener) => {
|
|
23
27
|
thisSource.#sourceListeners.add(listener);
|
|
24
28
|
return () => thisSource.#sourceListeners.delete(listener);
|
|
@@ -26,35 +30,90 @@ export class MutableReplicatedStateImpl {
|
|
|
26
30
|
});
|
|
27
31
|
}
|
|
28
32
|
get value() {
|
|
29
|
-
return this.#
|
|
33
|
+
return this.#value;
|
|
30
34
|
}
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
change(context, mutate) {
|
|
36
|
+
if (this.#changing)
|
|
37
|
+
throw new Error("Replicated state cannot be changed reentrantly from a change callback");
|
|
38
|
+
this.#changing = true;
|
|
39
|
+
let next;
|
|
40
|
+
try {
|
|
41
|
+
const produced = produceWithMetadata(this.#value, mutate);
|
|
42
|
+
if (produced.value === this.#value)
|
|
43
|
+
return;
|
|
44
|
+
next = this.#store.commit(produced.value, produced.owned);
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
this.#changing = false;
|
|
48
|
+
}
|
|
49
|
+
this.#commit(next, context);
|
|
33
50
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this.#sequence += 1;
|
|
39
|
-
this.#publishedValue = applyImmutable(this.#publishedValue, ops);
|
|
40
|
-
for (const listener of [...this.#sourceListeners])
|
|
41
|
-
listener(ops, this.#sequence, context);
|
|
42
|
-
const delivery = { kind: "update", sequence: this.#sequence };
|
|
43
|
-
for (const listener of [...this.#listeners])
|
|
44
|
-
listener(this.#publishedValue, context, delivery);
|
|
51
|
+
replace(context, value) {
|
|
52
|
+
if (this.#changing)
|
|
53
|
+
throw new Error("Replicated state cannot be replaced from a change callback");
|
|
54
|
+
this.#commit(this.#store.import(value), context);
|
|
45
55
|
}
|
|
46
56
|
subscribe(listener) {
|
|
47
|
-
const
|
|
48
|
-
this
|
|
49
|
-
this.#listeners.
|
|
50
|
-
|
|
57
|
+
const sequence = this.#sequence;
|
|
58
|
+
const value = this.#value;
|
|
59
|
+
this.#listeners.set(listener, sequence);
|
|
60
|
+
try {
|
|
61
|
+
listener(value, serviceDeliveryContext(), { kind: "hydrate", sequence });
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
this.#listeners.delete(listener);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
51
67
|
return () => this.#listeners.delete(listener);
|
|
52
68
|
}
|
|
69
|
+
#commit(next, context) {
|
|
70
|
+
const ops = diffRevisions(this.#value, next);
|
|
71
|
+
if (ops.length === 0)
|
|
72
|
+
return;
|
|
73
|
+
this.#value = next;
|
|
74
|
+
this.#sequence += 1;
|
|
75
|
+
this.#publications.push({ value: next, ops, sequence: this.#sequence, context });
|
|
76
|
+
if (this.#delivering)
|
|
77
|
+
return;
|
|
78
|
+
this.#delivering = true;
|
|
79
|
+
const errors = [];
|
|
80
|
+
try {
|
|
81
|
+
for (let publication = this.#publications.shift(); publication !== undefined; publication = this.#publications.shift()) {
|
|
82
|
+
for (const listener of [...this.#sourceListeners]) {
|
|
83
|
+
try {
|
|
84
|
+
listener(publication.ops, publication.sequence, publication.context);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
errors.push(error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const delivery = { kind: "update", sequence: publication.sequence };
|
|
91
|
+
for (const [listener, hydratedSequence] of [...this.#listeners]) {
|
|
92
|
+
if (publication.sequence <= hydratedSequence)
|
|
93
|
+
continue;
|
|
94
|
+
try {
|
|
95
|
+
listener(publication.value, publication.context, delivery);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
errors.push(error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
this.#delivering = false;
|
|
105
|
+
}
|
|
106
|
+
if (errors.length === 1)
|
|
107
|
+
throw errors[0];
|
|
108
|
+
if (errors.length > 1)
|
|
109
|
+
throw new AggregateError(errors, "Replicated state listeners failed");
|
|
110
|
+
}
|
|
53
111
|
}
|
|
54
112
|
/** A cold read-only state used by service consumers until a complete snapshot arrives. */
|
|
55
113
|
export class ReplicatedStateReplica {
|
|
56
114
|
#listeners = new Set();
|
|
57
115
|
#reportError;
|
|
116
|
+
#store = new JsonRevisionStore();
|
|
58
117
|
#value;
|
|
59
118
|
#sequence;
|
|
60
119
|
constructor(reportError) {
|
|
@@ -74,11 +133,18 @@ export class ReplicatedStateReplica {
|
|
|
74
133
|
return () => this.#listeners.delete(listener);
|
|
75
134
|
}
|
|
76
135
|
hydrate(sequence, ops, context) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
136
|
+
let next;
|
|
137
|
+
try {
|
|
138
|
+
if (!isBase(ops))
|
|
139
|
+
throw new Error("Replicated state snapshot is not a base operation batch");
|
|
140
|
+
next = this.#store.adopt(applyImmutable(undefined, ops));
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
this.clear();
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
80
146
|
this.#sequence = sequence;
|
|
81
|
-
this.#value =
|
|
147
|
+
this.#value = next;
|
|
82
148
|
this.#deliverAll(context, { kind: "hydrate", sequence });
|
|
83
149
|
}
|
|
84
150
|
update(sequence, ops, context) {
|
|
@@ -89,9 +155,16 @@ export class ReplicatedStateReplica {
|
|
|
89
155
|
this.clear();
|
|
90
156
|
throw new Error("Replicated state update sequence has a gap");
|
|
91
157
|
}
|
|
92
|
-
|
|
158
|
+
let next;
|
|
159
|
+
try {
|
|
160
|
+
next = this.#store.adopt(applyImmutable(this.#value, ops));
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
this.clear();
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
93
166
|
this.#sequence = sequence;
|
|
94
|
-
this.#value =
|
|
167
|
+
this.#value = next;
|
|
95
168
|
this.#deliverAll(context, { kind: "update", sequence });
|
|
96
169
|
}
|
|
97
170
|
clear() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/services/state.ts"],"names":[],"mappings":"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","sourcesContent":["import { BACKGROUND_CONTEXT } from \"../context/index.ts\";\nimport { applyImmutable, isBase, type Op, type Tracker, track } from \"../delta/index.ts\";\nimport type { Context, JsonValue, MutableReplicatedState, ReplicatedState, ReplicatedStateDelivery } from \"../types.ts\";\nimport { registerReplicatedStateInternals } from \"./state-internals.ts\";\n\nexport class MutableReplicatedStateImpl<T extends object> implements MutableReplicatedState<T> {\n\treadonly #listeners = new Set<(value: T, context: Context, delivery: ReplicatedStateDelivery) => void>();\n\treadonly #sourceListeners = new Set<(ops: readonly Op[], sequence: number, context: Context) => void>();\n\treadonly #tracker: Tracker<T>;\n\t#publishedValue: T;\n\t#sequence = 0;\n\n\tconstructor(initial: T) {\n\t\tthis.#tracker = track(initial);\n\t\tthis.#publishedValue = applyImmutable(undefined, this.#tracker.flush()) as unknown as T;\n\t\tconst thisSource = this;\n\t\tregisterReplicatedStateInternals(this, {\n\t\t\tget sequence() {\n\t\t\t\treturn thisSource.#sequence;\n\t\t\t},\n\t\t\tget value() {\n\t\t\t\treturn thisSource.#publishedValue;\n\t\t\t},\n\t\t\tpublish: (context) => thisSource.publish(context),\n\t\t\tsubscribe: (listener) => {\n\t\t\t\tthisSource.#sourceListeners.add(listener);\n\t\t\t\treturn () => thisSource.#sourceListeners.delete(listener);\n\t\t\t},\n\t\t});\n\t}\n\n\tget value(): T {\n\t\treturn this.#publishedValue;\n\t}\n\n\tget state(): T {\n\t\treturn this.#tracker.state;\n\t}\n\n\tpublish(context: Context): void {\n\t\tconst ops = this.#tracker.flush();\n\t\tif (ops.length === 0) return;\n\t\tthis.#sequence += 1;\n\t\tthis.#publishedValue = applyImmutable(this.#publishedValue as unknown as JsonValue, ops) as unknown as T;\n\t\tfor (const listener of [...this.#sourceListeners]) listener(ops, this.#sequence, context);\n\t\tconst delivery = { kind: \"update\", sequence: this.#sequence } as const;\n\t\tfor (const listener of [...this.#listeners]) listener(this.#publishedValue, context, delivery);\n\t}\n\n\tsubscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void): () => void {\n\t\tconst context = serviceDeliveryContext();\n\t\tthis.publish(context);\n\t\tthis.#listeners.add(listener);\n\t\tlistener(this.#publishedValue, context, { kind: \"hydrate\", sequence: this.#sequence });\n\t\treturn () => this.#listeners.delete(listener);\n\t}\n}\n\n/** A cold read-only state used by service consumers until a complete snapshot arrives. */\nexport class ReplicatedStateReplica<T extends JsonValue = JsonValue> implements ReplicatedState<T> {\n\treadonly #listeners = new Set<(value: T, context: Context, delivery: ReplicatedStateDelivery) => void>();\n\treadonly #reportError: (error: Error) => void;\n\t#value: T | undefined;\n\t#sequence: number | undefined;\n\n\tconstructor(reportError: (error: Error) => void) {\n\t\tthis.#reportError = reportError;\n\t}\n\n\tget value(): T | undefined {\n\t\treturn this.#value;\n\t}\n\n\tsubscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void): () => void {\n\t\tthis.#listeners.add(listener);\n\t\tif (this.#value !== undefined) {\n\t\t\tthis.#deliver(listener, this.#value, serviceDeliveryContext(), {\n\t\t\t\tkind: \"hydrate\",\n\t\t\t\tsequence: this.#sequence!,\n\t\t\t});\n\t\t}\n\t\treturn () => this.#listeners.delete(listener);\n\t}\n\n\thydrate(sequence: number, ops: readonly Op[], context: Context): void {\n\t\tif (!isBase(ops)) throw new Error(\"Replicated state snapshot is not a base operation batch\");\n\t\tconst value = applyImmutable<T>(undefined, ops);\n\t\tthis.#sequence = sequence;\n\t\tthis.#value = value;\n\t\tthis.#deliverAll(context, { kind: \"hydrate\", sequence });\n\t}\n\n\tupdate(sequence: number, ops: readonly Op[], context: Context): void {\n\t\tif (this.#sequence === undefined || this.#value === undefined) {\n\t\t\tthrow new Error(\"Replicated state received an update before hydration\");\n\t\t}\n\t\tif (sequence !== this.#sequence + 1) {\n\t\t\tthis.clear();\n\t\t\tthrow new Error(\"Replicated state update sequence has a gap\");\n\t\t}\n\t\tconst value = applyImmutable(this.#value, ops);\n\t\tthis.#sequence = sequence;\n\t\tthis.#value = value;\n\t\tthis.#deliverAll(context, { kind: \"update\", sequence });\n\t}\n\n\tclear(): void {\n\t\tthis.#value = undefined;\n\t\tthis.#sequence = undefined;\n\t}\n\n\t#deliverAll(context: Context, delivery: ReplicatedStateDelivery): void {\n\t\tif (this.#value === undefined) return;\n\t\tfor (const listener of this.#listeners) this.#deliver(listener, this.#value, context, delivery);\n\t}\n\n\t#deliver(\n\t\tlistener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void,\n\t\tvalue: T,\n\t\tcontext: Context,\n\t\tdelivery: ReplicatedStateDelivery,\n\t): void {\n\t\ttry {\n\t\t\tlistener(value, context, delivery);\n\t\t} catch (error) {\n\t\t\tthis.#reportError(toError(error));\n\t\t}\n\t}\n}\n\n/** @internal Context for synthetic service deliveries without a caller. */\nexport function serviceDeliveryContext(): Context {\n\t// TODO: Add delivery-scoped cancellation or metadata if deliveries gain an owned lifecycle.\n\treturn BACKGROUND_CONTEXT;\n}\n\nfunction toError(error: unknown): Error {\n\treturn error instanceof Error ? error : new Error(String(error));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/services/state.ts"],"names":[],"mappings":"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","sourcesContent":["import { BACKGROUND_CONTEXT } from \"../context/index.ts\";\nimport { applyImmutable, isBase, type Op } from \"../delta/index.ts\";\nimport { diffRevisions } from \"../state/diff.ts\";\nimport { produceWithMetadata } from \"../state/draft.ts\";\nimport { JsonRevisionStore } from \"../state/value.ts\";\nimport type { Context, JsonValue, MutableReplicatedState, ReplicatedState, ReplicatedStateDelivery } from \"../types.ts\";\nimport { registerReplicatedStateInternals } from \"./state-internals.ts\";\n\ntype Publication<T> = {\n\tvalue: T;\n\tops: readonly Op[];\n\tsequence: number;\n\tcontext: Context;\n};\n\nexport class MutableReplicatedStateImpl<T extends object> implements MutableReplicatedState<T> {\n\treadonly #listeners = new Map<(value: T, context: Context, delivery: ReplicatedStateDelivery) => void, number>();\n\treadonly #sourceListeners = new Set<(ops: readonly Op[], sequence: number, context: Context) => void>();\n\treadonly #store = new JsonRevisionStore();\n\treadonly #publications: Publication<T>[] = [];\n\t#value: T;\n\t#sequence = 0;\n\t#changing = false;\n\t#delivering = false;\n\n\tconstructor(initial: T) {\n\t\tthis.#value = this.#store.import(initial);\n\t\tconst thisSource = this;\n\t\tregisterReplicatedStateInternals(this, {\n\t\t\tget sequence() {\n\t\t\t\treturn thisSource.#sequence;\n\t\t\t},\n\t\t\tget value() {\n\t\t\t\treturn thisSource.#value;\n\t\t\t},\n\t\t\tsubscribe: (listener) => {\n\t\t\t\tthisSource.#sourceListeners.add(listener);\n\t\t\t\treturn () => thisSource.#sourceListeners.delete(listener);\n\t\t\t},\n\t\t});\n\t}\n\n\tget value(): T {\n\t\treturn this.#value;\n\t}\n\n\tchange(context: Context, mutate: Parameters<MutableReplicatedState<T>[\"change\"]>[1]): void {\n\t\tif (this.#changing) throw new Error(\"Replicated state cannot be changed reentrantly from a change callback\");\n\t\tthis.#changing = true;\n\t\tlet next: T;\n\t\ttry {\n\t\t\tconst produced = produceWithMetadata(this.#value, mutate);\n\t\t\tif (produced.value === this.#value) return;\n\t\t\tnext = this.#store.commit(produced.value, produced.owned);\n\t\t} finally {\n\t\t\tthis.#changing = false;\n\t\t}\n\t\tthis.#commit(next, context);\n\t}\n\n\treplace(context: Context, value: T): void {\n\t\tif (this.#changing) throw new Error(\"Replicated state cannot be replaced from a change callback\");\n\t\tthis.#commit(this.#store.import(value), context);\n\t}\n\n\tsubscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void): () => void {\n\t\tconst sequence = this.#sequence;\n\t\tconst value = this.#value;\n\t\tthis.#listeners.set(listener, sequence);\n\t\ttry {\n\t\t\tlistener(value, serviceDeliveryContext(), { kind: \"hydrate\", sequence });\n\t\t} catch (error) {\n\t\t\tthis.#listeners.delete(listener);\n\t\t\tthrow error;\n\t\t}\n\t\treturn () => this.#listeners.delete(listener);\n\t}\n\n\t#commit(next: T, context: Context): void {\n\t\tconst ops = diffRevisions(this.#value as unknown as JsonValue, next as unknown as JsonValue);\n\t\tif (ops.length === 0) return;\n\t\tthis.#value = next;\n\t\tthis.#sequence += 1;\n\t\tthis.#publications.push({ value: next, ops, sequence: this.#sequence, context });\n\t\tif (this.#delivering) return;\n\t\tthis.#delivering = true;\n\t\tconst errors: unknown[] = [];\n\t\ttry {\n\t\t\tfor (\n\t\t\t\tlet publication = this.#publications.shift();\n\t\t\t\tpublication !== undefined;\n\t\t\t\tpublication = this.#publications.shift()\n\t\t\t) {\n\t\t\t\tfor (const listener of [...this.#sourceListeners]) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlistener(publication.ops, publication.sequence, publication.context);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\terrors.push(error);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst delivery = { kind: \"update\", sequence: publication.sequence } as const;\n\t\t\t\tfor (const [listener, hydratedSequence] of [...this.#listeners]) {\n\t\t\t\t\tif (publication.sequence <= hydratedSequence) continue;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlistener(publication.value, publication.context, delivery);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\terrors.push(error);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.#delivering = false;\n\t\t}\n\t\tif (errors.length === 1) throw errors[0];\n\t\tif (errors.length > 1) throw new AggregateError(errors, \"Replicated state listeners failed\");\n\t}\n}\n\n/** A cold read-only state used by service consumers until a complete snapshot arrives. */\nexport class ReplicatedStateReplica<T extends JsonValue = JsonValue> implements ReplicatedState<T> {\n\treadonly #listeners = new Set<(value: T, context: Context, delivery: ReplicatedStateDelivery) => void>();\n\treadonly #reportError: (error: Error) => void;\n\treadonly #store = new JsonRevisionStore();\n\t#value: T | undefined;\n\t#sequence: number | undefined;\n\n\tconstructor(reportError: (error: Error) => void) {\n\t\tthis.#reportError = reportError;\n\t}\n\n\tget value(): T | undefined {\n\t\treturn this.#value;\n\t}\n\n\tsubscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void): () => void {\n\t\tthis.#listeners.add(listener);\n\t\tif (this.#value !== undefined) {\n\t\t\tthis.#deliver(listener, this.#value, serviceDeliveryContext(), {\n\t\t\t\tkind: \"hydrate\",\n\t\t\t\tsequence: this.#sequence!,\n\t\t\t});\n\t\t}\n\t\treturn () => this.#listeners.delete(listener);\n\t}\n\n\thydrate(sequence: number, ops: readonly Op[], context: Context): void {\n\t\tlet next: T;\n\t\ttry {\n\t\t\tif (!isBase(ops)) throw new Error(\"Replicated state snapshot is not a base operation batch\");\n\t\t\tnext = this.#store.adopt(applyImmutable<T>(undefined, ops));\n\t\t} catch (error) {\n\t\t\tthis.clear();\n\t\t\tthrow error;\n\t\t}\n\t\tthis.#sequence = sequence;\n\t\tthis.#value = next;\n\t\tthis.#deliverAll(context, { kind: \"hydrate\", sequence });\n\t}\n\n\tupdate(sequence: number, ops: readonly Op[], context: Context): void {\n\t\tif (this.#sequence === undefined || this.#value === undefined) {\n\t\t\tthrow new Error(\"Replicated state received an update before hydration\");\n\t\t}\n\t\tif (sequence !== this.#sequence + 1) {\n\t\t\tthis.clear();\n\t\t\tthrow new Error(\"Replicated state update sequence has a gap\");\n\t\t}\n\t\tlet next: T;\n\t\ttry {\n\t\t\tnext = this.#store.adopt(applyImmutable(this.#value, ops));\n\t\t} catch (error) {\n\t\t\tthis.clear();\n\t\t\tthrow error;\n\t\t}\n\t\tthis.#sequence = sequence;\n\t\tthis.#value = next;\n\t\tthis.#deliverAll(context, { kind: \"update\", sequence });\n\t}\n\n\tclear(): void {\n\t\tthis.#value = undefined;\n\t\tthis.#sequence = undefined;\n\t}\n\n\t#deliverAll(context: Context, delivery: ReplicatedStateDelivery): void {\n\t\tif (this.#value === undefined) return;\n\t\tfor (const listener of this.#listeners) this.#deliver(listener, this.#value, context, delivery);\n\t}\n\n\t#deliver(\n\t\tlistener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void,\n\t\tvalue: T,\n\t\tcontext: Context,\n\t\tdelivery: ReplicatedStateDelivery,\n\t): void {\n\t\ttry {\n\t\t\tlistener(value, context, delivery);\n\t\t} catch (error) {\n\t\t\tthis.#reportError(toError(error));\n\t\t}\n\t}\n}\n\n/** @internal Context for synthetic service deliveries without a caller. */\nexport function serviceDeliveryContext(): Context {\n\t// TODO: Add delivery-scoped cancellation or metadata if deliveries gain an owned lifecycle.\n\treturn BACKGROUND_CONTEXT;\n}\n\nfunction toError(error: unknown): Error {\n\treturn error instanceof Error ? error : new Error(String(error));\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Op } from "../delta/index.ts";
|
|
2
|
+
import type { JsonValue } from "../types.ts";
|
|
3
|
+
/** Compute a compact operation batch from two immutable JSON revisions. */
|
|
4
|
+
export declare function diffRevisions(before: JsonValue, after: JsonValue): Op[];
|
|
5
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/state/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,EAAE,EAAmD,MAAM,mBAAmB,CAAC;AAChH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AA4O7C,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,GAAG,EAAE,EAAE,CAUvE","sourcesContent":["import { type NonEmptyPath, type Op, overlap, type Path, RESERVED_SEGMENTS, type Seg } from \"../delta/index.ts\";\nimport type { JsonValue } from \"../types.ts\";\n\nconst DEFAULT_OVERLAP_SCAN = 65_536;\nconst MAX_DELTA_OPERATIONS = 4_096;\n\nconst isContainer = (value: JsonValue): value is JsonValue[] | Record<string, JsonValue> =>\n\tvalue !== null && typeof value === \"object\";\n\nconst emitSet = (path: Path, value: JsonValue, operations: Op[]): void => {\n\tif (path.length === 0) operations.push([\"r\", value]);\n\telse operations.push([\"s\", path as NonEmptyPath, value]);\n};\n\nconst equalJson = (left: JsonValue, right: JsonValue): boolean => {\n\tif (left === right) return true;\n\tif (!isContainer(left) || !isContainer(right) || Array.isArray(left) !== Array.isArray(right)) return false;\n\tif (Array.isArray(left)) {\n\t\tconst other = right as JsonValue[];\n\t\tif (left.length !== other.length) return false;\n\t\tfor (let index = 0; index < left.length; index++) {\n\t\t\tif (!equalJson(left[index]!, other[index]!)) return false;\n\t\t}\n\t\treturn true;\n\t}\n\tconst other = right as Record<string, JsonValue>;\n\tconst keys = Object.keys(left);\n\tif (keys.length !== Object.keys(other).length) return false;\n\tfor (const key of keys) {\n\t\tif (!Object.hasOwn(other, key) || !equalJson(left[key]!, other[key]!)) return false;\n\t}\n\treturn true;\n};\n\nconst permutation = (before: readonly JsonValue[], after: readonly JsonValue[]): number[] | undefined => {\n\tif (before.length !== after.length) return undefined;\n\tconst positions = new Map<JsonValue, { indices: number[]; used: number }>();\n\tfor (let index = 0; index < before.length; index++) {\n\t\tconst value = before[index]!;\n\t\tconst entry = positions.get(value);\n\t\tif (entry === undefined) positions.set(value, { indices: [index], used: 0 });\n\t\telse entry.indices.push(index);\n\t}\n\tconst result = new Array<number>(after.length);\n\tfor (let index = 0; index < after.length; index++) {\n\t\tconst entry = positions.get(after[index]!);\n\t\tif (entry === undefined || entry.used === entry.indices.length) return undefined;\n\t\tresult[index] = entry.indices[entry.used++]!;\n\t}\n\treturn result;\n};\n\nconst emitString = (before: string, after: string, path: NonEmptyPath, operations: Op[]): void => {\n\tif (before === after) return;\n\tif (after.length > before.length && after.slice(0, before.length) === before) {\n\t\toperations.push([\"a\", path, after.slice(before.length)]);\n\t\treturn;\n\t}\n\tconst shared = overlap(before, after, DEFAULT_OVERLAP_SCAN);\n\tif (shared === 0) {\n\t\toperations.push([\"s\", path, after]);\n\t\treturn;\n\t}\n\toperations.push([\"t\", path, before.length - shared]);\n\tif (after.length > shared) operations.push([\"a\", path, after.slice(shared)]);\n};\n\nconst emitSameLengthShift = (\n\tbefore: readonly JsonValue[],\n\tafter: readonly JsonValue[],\n\tpath: Path,\n\toperations: Op[],\n): boolean => {\n\tconst length = before.length;\n\tif (length < 2) return false;\n\tlet candidates = 0;\n\tfor (let removed = 1; removed < length && candidates < 16; removed++) {\n\t\tif (before[removed] !== after[0]) continue;\n\t\tcandidates += 1;\n\t\tlet matches = true;\n\t\tfor (let index = removed; index < length; index++) {\n\t\t\tif (before[index] !== after[index - removed]) {\n\t\t\t\tmatches = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!matches) continue;\n\t\toperations.push([\"p\", path, 0, removed, []]);\n\t\toperations.push([\"p\", path, length - removed, 0, after.slice(length - removed)]);\n\t\treturn true;\n\t}\n\tcandidates = 0;\n\tfor (let inserted = 1; inserted < length && candidates < 16; inserted++) {\n\t\tif (after[inserted] !== before[0]) continue;\n\t\tcandidates += 1;\n\t\tlet matches = true;\n\t\tfor (let index = inserted; index < length; index++) {\n\t\t\tif (after[index] !== before[index - inserted]) {\n\t\t\t\tmatches = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!matches) continue;\n\t\toperations.push([\"p\", path, 0, 0, after.slice(0, inserted)]);\n\t\toperations.push([\"p\", path, length, inserted, []]);\n\t\treturn true;\n\t}\n\treturn false;\n};\n\nconst diffArray = (before: JsonValue[], after: JsonValue[], path: Path, operations: Op[]): void => {\n\tif (before.length === after.length) {\n\t\tlet mismatches = 0;\n\t\tfor (let index = 0; index < after.length; index++) {\n\t\t\tif (before[index] !== after[index]) mismatches += 1;\n\t\t}\n\t\tif (mismatches === 0) return;\n\t\tif (mismatches > 1) {\n\t\t\tconst order = permutation(before, after);\n\t\t\tif (order !== undefined) {\n\t\t\t\toperations.push([\"m\", path, order]);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (emitSameLengthShift(before, after, path, operations)) return;\n\t\t}\n\t\tfor (let index = 0; index < after.length; index++) {\n\t\t\tif (operations.length > MAX_DELTA_OPERATIONS) return;\n\t\t\tdiffValue(before[index]!, after[index]!, [...path, index], operations);\n\t\t}\n\t\treturn;\n\t}\n\n\tlet prefix = 0;\n\tconst shortest = Math.min(before.length, after.length);\n\twhile (prefix < shortest && equalJson(before[prefix]!, after[prefix]!)) prefix += 1;\n\tlet suffix = 0;\n\twhile (\n\t\tsuffix < shortest - prefix &&\n\t\tequalJson(before[before.length - 1 - suffix]!, after[after.length - 1 - suffix]!)\n\t) {\n\t\tsuffix += 1;\n\t}\n\toperations.push([\"p\", path, prefix, before.length - prefix - suffix, after.slice(prefix, after.length - suffix)]);\n};\n\nconst diffObject = (\n\tbefore: Record<string, JsonValue>,\n\tafter: Record<string, JsonValue>,\n\tpath: Path,\n\toperations: Op[],\n): void => {\n\tconst beforeKeys = Object.keys(before);\n\tconst afterKeys = Object.keys(after);\n\tif ([...beforeKeys, ...afterKeys].some((key) => RESERVED_SEGMENTS.has(key))) {\n\t\tif (!equalJson(before, after)) emitSet(path, after, operations);\n\t\treturn;\n\t}\n\tfor (const key of afterKeys) {\n\t\tif (operations.length > MAX_DELTA_OPERATIONS) return;\n\t\tif (Object.hasOwn(before, key)) diffValue(before[key]!, after[key]!, [...path, key], operations);\n\t\telse emitSet([...path, key], after[key]!, operations);\n\t}\n\tfor (const key of beforeKeys) {\n\t\tif (operations.length > MAX_DELTA_OPERATIONS) return;\n\t\tif (!Object.hasOwn(after, key)) operations.push([\"d\", [...path, key] as unknown as NonEmptyPath]);\n\t}\n};\n\nconst diffValue = (before: JsonValue, after: JsonValue, path: Path, operations: Op[]): void => {\n\tif (before === after || operations.length > MAX_DELTA_OPERATIONS) return;\n\tif (typeof before === \"string\" && typeof after === \"string\" && path.length > 0) {\n\t\temitString(before, after, path as NonEmptyPath, operations);\n\t\treturn;\n\t}\n\tif (Array.isArray(before) && Array.isArray(after)) {\n\t\tdiffArray(before, after, path, operations);\n\t\treturn;\n\t}\n\tif (isContainer(before) && isContainer(after) && !Array.isArray(before) && !Array.isArray(after)) {\n\t\tdiffObject(before, after, path, operations);\n\t\treturn;\n\t}\n\temitSet(path, after, operations);\n};\n\nconst jsonCost = (value: JsonValue): number => {\n\tif (value === null) return 4;\n\tif (typeof value === \"string\") return value.length + 2;\n\tif (typeof value === \"number\") return String(value).length;\n\tif (typeof value === \"boolean\") return value ? 4 : 5;\n\tif (Array.isArray(value)) {\n\t\tlet cost = 2;\n\t\tfor (let index = 0; index < value.length; index++) cost += jsonCost(value[index]!) + (index === 0 ? 0 : 1);\n\t\treturn cost;\n\t}\n\tlet cost = 2;\n\tlet index = 0;\n\tfor (const key of Object.keys(value)) {\n\t\tcost += key.length + 3 + jsonCost(value[key]!) + (index++ === 0 ? 0 : 1);\n\t}\n\treturn cost;\n};\n\nconst pathCost = (path: Path): number => {\n\tlet cost = 2;\n\tfor (let index = 0; index < path.length; index++) {\n\t\tconst segment: Seg = path[index]!;\n\t\tcost += (typeof segment === \"string\" ? segment.length + 2 : String(segment).length) + (index === 0 ? 0 : 1);\n\t}\n\treturn cost;\n};\n\nconst operationCost = (operation: Op): number => {\n\tswitch (operation[0]) {\n\t\tcase \"r\":\n\t\t\treturn 6 + jsonCost(operation[1]);\n\t\tcase \"s\":\n\t\t\treturn 7 + pathCost(operation[1]) + jsonCost(operation[2]);\n\t\tcase \"d\":\n\t\t\treturn 6 + pathCost(operation[1]);\n\t\tcase \"a\":\n\t\t\treturn 7 + pathCost(operation[1]) + operation[2].length + 2;\n\t\tcase \"t\":\n\t\t\treturn 7 + pathCost(operation[1]) + String(operation[2]).length;\n\t\tcase \"p\":\n\t\t\treturn (\n\t\t\t\t10 +\n\t\t\t\tpathCost(operation[1]) +\n\t\t\t\tString(operation[2]).length +\n\t\t\t\tString(operation[3]).length +\n\t\t\t\tjsonCost(operation[4])\n\t\t\t);\n\t\tcase \"m\":\n\t\t\treturn 7 + pathCost(operation[1]) + jsonCost(operation[2]);\n\t}\n};\n\n/** Compute a compact operation batch from two immutable JSON revisions. */\nexport function diffRevisions(before: JsonValue, after: JsonValue): Op[] {\n\tconst operations: Op[] = [];\n\tdiffValue(before, after, [], operations);\n\tif (operations.length > MAX_DELTA_OPERATIONS) return [[\"r\", after]];\n\tif (operations.length === 0 || operations[0]?.[0] === \"r\") return operations;\n\tlet deltaCost = 2;\n\tfor (const operation of operations) deltaCost += operationCost(operation) + 1;\n\tif (deltaCost < 65_536) return operations;\n\tconst snapshotCost = jsonCost(after) + 6;\n\treturn deltaCost >= snapshotCost ? [[\"r\", after]] : operations;\n}\n"]}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { overlap, RESERVED_SEGMENTS } from "../delta/index.js";
|
|
2
|
+
const DEFAULT_OVERLAP_SCAN = 65_536;
|
|
3
|
+
const MAX_DELTA_OPERATIONS = 4_096;
|
|
4
|
+
const isContainer = (value) => value !== null && typeof value === "object";
|
|
5
|
+
const emitSet = (path, value, operations) => {
|
|
6
|
+
if (path.length === 0)
|
|
7
|
+
operations.push(["r", value]);
|
|
8
|
+
else
|
|
9
|
+
operations.push(["s", path, value]);
|
|
10
|
+
};
|
|
11
|
+
const equalJson = (left, right) => {
|
|
12
|
+
if (left === right)
|
|
13
|
+
return true;
|
|
14
|
+
if (!isContainer(left) || !isContainer(right) || Array.isArray(left) !== Array.isArray(right))
|
|
15
|
+
return false;
|
|
16
|
+
if (Array.isArray(left)) {
|
|
17
|
+
const other = right;
|
|
18
|
+
if (left.length !== other.length)
|
|
19
|
+
return false;
|
|
20
|
+
for (let index = 0; index < left.length; index++) {
|
|
21
|
+
if (!equalJson(left[index], other[index]))
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
const other = right;
|
|
27
|
+
const keys = Object.keys(left);
|
|
28
|
+
if (keys.length !== Object.keys(other).length)
|
|
29
|
+
return false;
|
|
30
|
+
for (const key of keys) {
|
|
31
|
+
if (!Object.hasOwn(other, key) || !equalJson(left[key], other[key]))
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
};
|
|
36
|
+
const permutation = (before, after) => {
|
|
37
|
+
if (before.length !== after.length)
|
|
38
|
+
return undefined;
|
|
39
|
+
const positions = new Map();
|
|
40
|
+
for (let index = 0; index < before.length; index++) {
|
|
41
|
+
const value = before[index];
|
|
42
|
+
const entry = positions.get(value);
|
|
43
|
+
if (entry === undefined)
|
|
44
|
+
positions.set(value, { indices: [index], used: 0 });
|
|
45
|
+
else
|
|
46
|
+
entry.indices.push(index);
|
|
47
|
+
}
|
|
48
|
+
const result = new Array(after.length);
|
|
49
|
+
for (let index = 0; index < after.length; index++) {
|
|
50
|
+
const entry = positions.get(after[index]);
|
|
51
|
+
if (entry === undefined || entry.used === entry.indices.length)
|
|
52
|
+
return undefined;
|
|
53
|
+
result[index] = entry.indices[entry.used++];
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
};
|
|
57
|
+
const emitString = (before, after, path, operations) => {
|
|
58
|
+
if (before === after)
|
|
59
|
+
return;
|
|
60
|
+
if (after.length > before.length && after.slice(0, before.length) === before) {
|
|
61
|
+
operations.push(["a", path, after.slice(before.length)]);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const shared = overlap(before, after, DEFAULT_OVERLAP_SCAN);
|
|
65
|
+
if (shared === 0) {
|
|
66
|
+
operations.push(["s", path, after]);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
operations.push(["t", path, before.length - shared]);
|
|
70
|
+
if (after.length > shared)
|
|
71
|
+
operations.push(["a", path, after.slice(shared)]);
|
|
72
|
+
};
|
|
73
|
+
const emitSameLengthShift = (before, after, path, operations) => {
|
|
74
|
+
const length = before.length;
|
|
75
|
+
if (length < 2)
|
|
76
|
+
return false;
|
|
77
|
+
let candidates = 0;
|
|
78
|
+
for (let removed = 1; removed < length && candidates < 16; removed++) {
|
|
79
|
+
if (before[removed] !== after[0])
|
|
80
|
+
continue;
|
|
81
|
+
candidates += 1;
|
|
82
|
+
let matches = true;
|
|
83
|
+
for (let index = removed; index < length; index++) {
|
|
84
|
+
if (before[index] !== after[index - removed]) {
|
|
85
|
+
matches = false;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!matches)
|
|
90
|
+
continue;
|
|
91
|
+
operations.push(["p", path, 0, removed, []]);
|
|
92
|
+
operations.push(["p", path, length - removed, 0, after.slice(length - removed)]);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
candidates = 0;
|
|
96
|
+
for (let inserted = 1; inserted < length && candidates < 16; inserted++) {
|
|
97
|
+
if (after[inserted] !== before[0])
|
|
98
|
+
continue;
|
|
99
|
+
candidates += 1;
|
|
100
|
+
let matches = true;
|
|
101
|
+
for (let index = inserted; index < length; index++) {
|
|
102
|
+
if (after[index] !== before[index - inserted]) {
|
|
103
|
+
matches = false;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!matches)
|
|
108
|
+
continue;
|
|
109
|
+
operations.push(["p", path, 0, 0, after.slice(0, inserted)]);
|
|
110
|
+
operations.push(["p", path, length, inserted, []]);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
};
|
|
115
|
+
const diffArray = (before, after, path, operations) => {
|
|
116
|
+
if (before.length === after.length) {
|
|
117
|
+
let mismatches = 0;
|
|
118
|
+
for (let index = 0; index < after.length; index++) {
|
|
119
|
+
if (before[index] !== after[index])
|
|
120
|
+
mismatches += 1;
|
|
121
|
+
}
|
|
122
|
+
if (mismatches === 0)
|
|
123
|
+
return;
|
|
124
|
+
if (mismatches > 1) {
|
|
125
|
+
const order = permutation(before, after);
|
|
126
|
+
if (order !== undefined) {
|
|
127
|
+
operations.push(["m", path, order]);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (emitSameLengthShift(before, after, path, operations))
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
for (let index = 0; index < after.length; index++) {
|
|
134
|
+
if (operations.length > MAX_DELTA_OPERATIONS)
|
|
135
|
+
return;
|
|
136
|
+
diffValue(before[index], after[index], [...path, index], operations);
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
let prefix = 0;
|
|
141
|
+
const shortest = Math.min(before.length, after.length);
|
|
142
|
+
while (prefix < shortest && equalJson(before[prefix], after[prefix]))
|
|
143
|
+
prefix += 1;
|
|
144
|
+
let suffix = 0;
|
|
145
|
+
while (suffix < shortest - prefix &&
|
|
146
|
+
equalJson(before[before.length - 1 - suffix], after[after.length - 1 - suffix])) {
|
|
147
|
+
suffix += 1;
|
|
148
|
+
}
|
|
149
|
+
operations.push(["p", path, prefix, before.length - prefix - suffix, after.slice(prefix, after.length - suffix)]);
|
|
150
|
+
};
|
|
151
|
+
const diffObject = (before, after, path, operations) => {
|
|
152
|
+
const beforeKeys = Object.keys(before);
|
|
153
|
+
const afterKeys = Object.keys(after);
|
|
154
|
+
if ([...beforeKeys, ...afterKeys].some((key) => RESERVED_SEGMENTS.has(key))) {
|
|
155
|
+
if (!equalJson(before, after))
|
|
156
|
+
emitSet(path, after, operations);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
for (const key of afterKeys) {
|
|
160
|
+
if (operations.length > MAX_DELTA_OPERATIONS)
|
|
161
|
+
return;
|
|
162
|
+
if (Object.hasOwn(before, key))
|
|
163
|
+
diffValue(before[key], after[key], [...path, key], operations);
|
|
164
|
+
else
|
|
165
|
+
emitSet([...path, key], after[key], operations);
|
|
166
|
+
}
|
|
167
|
+
for (const key of beforeKeys) {
|
|
168
|
+
if (operations.length > MAX_DELTA_OPERATIONS)
|
|
169
|
+
return;
|
|
170
|
+
if (!Object.hasOwn(after, key))
|
|
171
|
+
operations.push(["d", [...path, key]]);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const diffValue = (before, after, path, operations) => {
|
|
175
|
+
if (before === after || operations.length > MAX_DELTA_OPERATIONS)
|
|
176
|
+
return;
|
|
177
|
+
if (typeof before === "string" && typeof after === "string" && path.length > 0) {
|
|
178
|
+
emitString(before, after, path, operations);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (Array.isArray(before) && Array.isArray(after)) {
|
|
182
|
+
diffArray(before, after, path, operations);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (isContainer(before) && isContainer(after) && !Array.isArray(before) && !Array.isArray(after)) {
|
|
186
|
+
diffObject(before, after, path, operations);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
emitSet(path, after, operations);
|
|
190
|
+
};
|
|
191
|
+
const jsonCost = (value) => {
|
|
192
|
+
if (value === null)
|
|
193
|
+
return 4;
|
|
194
|
+
if (typeof value === "string")
|
|
195
|
+
return value.length + 2;
|
|
196
|
+
if (typeof value === "number")
|
|
197
|
+
return String(value).length;
|
|
198
|
+
if (typeof value === "boolean")
|
|
199
|
+
return value ? 4 : 5;
|
|
200
|
+
if (Array.isArray(value)) {
|
|
201
|
+
let cost = 2;
|
|
202
|
+
for (let index = 0; index < value.length; index++)
|
|
203
|
+
cost += jsonCost(value[index]) + (index === 0 ? 0 : 1);
|
|
204
|
+
return cost;
|
|
205
|
+
}
|
|
206
|
+
let cost = 2;
|
|
207
|
+
let index = 0;
|
|
208
|
+
for (const key of Object.keys(value)) {
|
|
209
|
+
cost += key.length + 3 + jsonCost(value[key]) + (index++ === 0 ? 0 : 1);
|
|
210
|
+
}
|
|
211
|
+
return cost;
|
|
212
|
+
};
|
|
213
|
+
const pathCost = (path) => {
|
|
214
|
+
let cost = 2;
|
|
215
|
+
for (let index = 0; index < path.length; index++) {
|
|
216
|
+
const segment = path[index];
|
|
217
|
+
cost += (typeof segment === "string" ? segment.length + 2 : String(segment).length) + (index === 0 ? 0 : 1);
|
|
218
|
+
}
|
|
219
|
+
return cost;
|
|
220
|
+
};
|
|
221
|
+
const operationCost = (operation) => {
|
|
222
|
+
switch (operation[0]) {
|
|
223
|
+
case "r":
|
|
224
|
+
return 6 + jsonCost(operation[1]);
|
|
225
|
+
case "s":
|
|
226
|
+
return 7 + pathCost(operation[1]) + jsonCost(operation[2]);
|
|
227
|
+
case "d":
|
|
228
|
+
return 6 + pathCost(operation[1]);
|
|
229
|
+
case "a":
|
|
230
|
+
return 7 + pathCost(operation[1]) + operation[2].length + 2;
|
|
231
|
+
case "t":
|
|
232
|
+
return 7 + pathCost(operation[1]) + String(operation[2]).length;
|
|
233
|
+
case "p":
|
|
234
|
+
return (10 +
|
|
235
|
+
pathCost(operation[1]) +
|
|
236
|
+
String(operation[2]).length +
|
|
237
|
+
String(operation[3]).length +
|
|
238
|
+
jsonCost(operation[4]));
|
|
239
|
+
case "m":
|
|
240
|
+
return 7 + pathCost(operation[1]) + jsonCost(operation[2]);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
/** Compute a compact operation batch from two immutable JSON revisions. */
|
|
244
|
+
export function diffRevisions(before, after) {
|
|
245
|
+
const operations = [];
|
|
246
|
+
diffValue(before, after, [], operations);
|
|
247
|
+
if (operations.length > MAX_DELTA_OPERATIONS)
|
|
248
|
+
return [["r", after]];
|
|
249
|
+
if (operations.length === 0 || operations[0]?.[0] === "r")
|
|
250
|
+
return operations;
|
|
251
|
+
let deltaCost = 2;
|
|
252
|
+
for (const operation of operations)
|
|
253
|
+
deltaCost += operationCost(operation) + 1;
|
|
254
|
+
if (deltaCost < 65_536)
|
|
255
|
+
return operations;
|
|
256
|
+
const snapshotCost = jsonCost(after) + 6;
|
|
257
|
+
return deltaCost >= snapshotCost ? [["r", after]] : operations;
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/state/diff.ts"],"names":[],"mappings":"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","sourcesContent":["import { type NonEmptyPath, type Op, overlap, type Path, RESERVED_SEGMENTS, type Seg } from \"../delta/index.ts\";\nimport type { JsonValue } from \"../types.ts\";\n\nconst DEFAULT_OVERLAP_SCAN = 65_536;\nconst MAX_DELTA_OPERATIONS = 4_096;\n\nconst isContainer = (value: JsonValue): value is JsonValue[] | Record<string, JsonValue> =>\n\tvalue !== null && typeof value === \"object\";\n\nconst emitSet = (path: Path, value: JsonValue, operations: Op[]): void => {\n\tif (path.length === 0) operations.push([\"r\", value]);\n\telse operations.push([\"s\", path as NonEmptyPath, value]);\n};\n\nconst equalJson = (left: JsonValue, right: JsonValue): boolean => {\n\tif (left === right) return true;\n\tif (!isContainer(left) || !isContainer(right) || Array.isArray(left) !== Array.isArray(right)) return false;\n\tif (Array.isArray(left)) {\n\t\tconst other = right as JsonValue[];\n\t\tif (left.length !== other.length) return false;\n\t\tfor (let index = 0; index < left.length; index++) {\n\t\t\tif (!equalJson(left[index]!, other[index]!)) return false;\n\t\t}\n\t\treturn true;\n\t}\n\tconst other = right as Record<string, JsonValue>;\n\tconst keys = Object.keys(left);\n\tif (keys.length !== Object.keys(other).length) return false;\n\tfor (const key of keys) {\n\t\tif (!Object.hasOwn(other, key) || !equalJson(left[key]!, other[key]!)) return false;\n\t}\n\treturn true;\n};\n\nconst permutation = (before: readonly JsonValue[], after: readonly JsonValue[]): number[] | undefined => {\n\tif (before.length !== after.length) return undefined;\n\tconst positions = new Map<JsonValue, { indices: number[]; used: number }>();\n\tfor (let index = 0; index < before.length; index++) {\n\t\tconst value = before[index]!;\n\t\tconst entry = positions.get(value);\n\t\tif (entry === undefined) positions.set(value, { indices: [index], used: 0 });\n\t\telse entry.indices.push(index);\n\t}\n\tconst result = new Array<number>(after.length);\n\tfor (let index = 0; index < after.length; index++) {\n\t\tconst entry = positions.get(after[index]!);\n\t\tif (entry === undefined || entry.used === entry.indices.length) return undefined;\n\t\tresult[index] = entry.indices[entry.used++]!;\n\t}\n\treturn result;\n};\n\nconst emitString = (before: string, after: string, path: NonEmptyPath, operations: Op[]): void => {\n\tif (before === after) return;\n\tif (after.length > before.length && after.slice(0, before.length) === before) {\n\t\toperations.push([\"a\", path, after.slice(before.length)]);\n\t\treturn;\n\t}\n\tconst shared = overlap(before, after, DEFAULT_OVERLAP_SCAN);\n\tif (shared === 0) {\n\t\toperations.push([\"s\", path, after]);\n\t\treturn;\n\t}\n\toperations.push([\"t\", path, before.length - shared]);\n\tif (after.length > shared) operations.push([\"a\", path, after.slice(shared)]);\n};\n\nconst emitSameLengthShift = (\n\tbefore: readonly JsonValue[],\n\tafter: readonly JsonValue[],\n\tpath: Path,\n\toperations: Op[],\n): boolean => {\n\tconst length = before.length;\n\tif (length < 2) return false;\n\tlet candidates = 0;\n\tfor (let removed = 1; removed < length && candidates < 16; removed++) {\n\t\tif (before[removed] !== after[0]) continue;\n\t\tcandidates += 1;\n\t\tlet matches = true;\n\t\tfor (let index = removed; index < length; index++) {\n\t\t\tif (before[index] !== after[index - removed]) {\n\t\t\t\tmatches = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!matches) continue;\n\t\toperations.push([\"p\", path, 0, removed, []]);\n\t\toperations.push([\"p\", path, length - removed, 0, after.slice(length - removed)]);\n\t\treturn true;\n\t}\n\tcandidates = 0;\n\tfor (let inserted = 1; inserted < length && candidates < 16; inserted++) {\n\t\tif (after[inserted] !== before[0]) continue;\n\t\tcandidates += 1;\n\t\tlet matches = true;\n\t\tfor (let index = inserted; index < length; index++) {\n\t\t\tif (after[index] !== before[index - inserted]) {\n\t\t\t\tmatches = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!matches) continue;\n\t\toperations.push([\"p\", path, 0, 0, after.slice(0, inserted)]);\n\t\toperations.push([\"p\", path, length, inserted, []]);\n\t\treturn true;\n\t}\n\treturn false;\n};\n\nconst diffArray = (before: JsonValue[], after: JsonValue[], path: Path, operations: Op[]): void => {\n\tif (before.length === after.length) {\n\t\tlet mismatches = 0;\n\t\tfor (let index = 0; index < after.length; index++) {\n\t\t\tif (before[index] !== after[index]) mismatches += 1;\n\t\t}\n\t\tif (mismatches === 0) return;\n\t\tif (mismatches > 1) {\n\t\t\tconst order = permutation(before, after);\n\t\t\tif (order !== undefined) {\n\t\t\t\toperations.push([\"m\", path, order]);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (emitSameLengthShift(before, after, path, operations)) return;\n\t\t}\n\t\tfor (let index = 0; index < after.length; index++) {\n\t\t\tif (operations.length > MAX_DELTA_OPERATIONS) return;\n\t\t\tdiffValue(before[index]!, after[index]!, [...path, index], operations);\n\t\t}\n\t\treturn;\n\t}\n\n\tlet prefix = 0;\n\tconst shortest = Math.min(before.length, after.length);\n\twhile (prefix < shortest && equalJson(before[prefix]!, after[prefix]!)) prefix += 1;\n\tlet suffix = 0;\n\twhile (\n\t\tsuffix < shortest - prefix &&\n\t\tequalJson(before[before.length - 1 - suffix]!, after[after.length - 1 - suffix]!)\n\t) {\n\t\tsuffix += 1;\n\t}\n\toperations.push([\"p\", path, prefix, before.length - prefix - suffix, after.slice(prefix, after.length - suffix)]);\n};\n\nconst diffObject = (\n\tbefore: Record<string, JsonValue>,\n\tafter: Record<string, JsonValue>,\n\tpath: Path,\n\toperations: Op[],\n): void => {\n\tconst beforeKeys = Object.keys(before);\n\tconst afterKeys = Object.keys(after);\n\tif ([...beforeKeys, ...afterKeys].some((key) => RESERVED_SEGMENTS.has(key))) {\n\t\tif (!equalJson(before, after)) emitSet(path, after, operations);\n\t\treturn;\n\t}\n\tfor (const key of afterKeys) {\n\t\tif (operations.length > MAX_DELTA_OPERATIONS) return;\n\t\tif (Object.hasOwn(before, key)) diffValue(before[key]!, after[key]!, [...path, key], operations);\n\t\telse emitSet([...path, key], after[key]!, operations);\n\t}\n\tfor (const key of beforeKeys) {\n\t\tif (operations.length > MAX_DELTA_OPERATIONS) return;\n\t\tif (!Object.hasOwn(after, key)) operations.push([\"d\", [...path, key] as unknown as NonEmptyPath]);\n\t}\n};\n\nconst diffValue = (before: JsonValue, after: JsonValue, path: Path, operations: Op[]): void => {\n\tif (before === after || operations.length > MAX_DELTA_OPERATIONS) return;\n\tif (typeof before === \"string\" && typeof after === \"string\" && path.length > 0) {\n\t\temitString(before, after, path as NonEmptyPath, operations);\n\t\treturn;\n\t}\n\tif (Array.isArray(before) && Array.isArray(after)) {\n\t\tdiffArray(before, after, path, operations);\n\t\treturn;\n\t}\n\tif (isContainer(before) && isContainer(after) && !Array.isArray(before) && !Array.isArray(after)) {\n\t\tdiffObject(before, after, path, operations);\n\t\treturn;\n\t}\n\temitSet(path, after, operations);\n};\n\nconst jsonCost = (value: JsonValue): number => {\n\tif (value === null) return 4;\n\tif (typeof value === \"string\") return value.length + 2;\n\tif (typeof value === \"number\") return String(value).length;\n\tif (typeof value === \"boolean\") return value ? 4 : 5;\n\tif (Array.isArray(value)) {\n\t\tlet cost = 2;\n\t\tfor (let index = 0; index < value.length; index++) cost += jsonCost(value[index]!) + (index === 0 ? 0 : 1);\n\t\treturn cost;\n\t}\n\tlet cost = 2;\n\tlet index = 0;\n\tfor (const key of Object.keys(value)) {\n\t\tcost += key.length + 3 + jsonCost(value[key]!) + (index++ === 0 ? 0 : 1);\n\t}\n\treturn cost;\n};\n\nconst pathCost = (path: Path): number => {\n\tlet cost = 2;\n\tfor (let index = 0; index < path.length; index++) {\n\t\tconst segment: Seg = path[index]!;\n\t\tcost += (typeof segment === \"string\" ? segment.length + 2 : String(segment).length) + (index === 0 ? 0 : 1);\n\t}\n\treturn cost;\n};\n\nconst operationCost = (operation: Op): number => {\n\tswitch (operation[0]) {\n\t\tcase \"r\":\n\t\t\treturn 6 + jsonCost(operation[1]);\n\t\tcase \"s\":\n\t\t\treturn 7 + pathCost(operation[1]) + jsonCost(operation[2]);\n\t\tcase \"d\":\n\t\t\treturn 6 + pathCost(operation[1]);\n\t\tcase \"a\":\n\t\t\treturn 7 + pathCost(operation[1]) + operation[2].length + 2;\n\t\tcase \"t\":\n\t\t\treturn 7 + pathCost(operation[1]) + String(operation[2]).length;\n\t\tcase \"p\":\n\t\t\treturn (\n\t\t\t\t10 +\n\t\t\t\tpathCost(operation[1]) +\n\t\t\t\tString(operation[2]).length +\n\t\t\t\tString(operation[3]).length +\n\t\t\t\tjsonCost(operation[4])\n\t\t\t);\n\t\tcase \"m\":\n\t\t\treturn 7 + pathCost(operation[1]) + jsonCost(operation[2]);\n\t}\n};\n\n/** Compute a compact operation batch from two immutable JSON revisions. */\nexport function diffRevisions(before: JsonValue, after: JsonValue): Op[] {\n\tconst operations: Op[] = [];\n\tdiffValue(before, after, [], operations);\n\tif (operations.length > MAX_DELTA_OPERATIONS) return [[\"r\", after]];\n\tif (operations.length === 0 || operations[0]?.[0] === \"r\") return operations;\n\tlet deltaCost = 2;\n\tfor (const operation of operations) deltaCost += operationCost(operation) + 1;\n\tif (deltaCost < 65_536) return operations;\n\tconst snapshotCost = jsonCost(after) + 6;\n\treturn deltaCost >= snapshotCost ? [[\"r\", after]] : operations;\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** A mutable transaction-scoped view of a JSON value, preserving tuple positions. */
|
|
2
|
+
export type Draft<T, Depth extends readonly unknown[] = []> = Depth["length"] extends 8 ? T : T extends null | boolean | number | string ? T : T extends (...args: never[]) => unknown ? T : T extends object ? {
|
|
3
|
+
-readonly [Key in keyof T]: Draft<T[Key], [...Depth, unknown]>;
|
|
4
|
+
} : T;
|
|
5
|
+
export type ProduceMetadata<T> = {
|
|
6
|
+
value: T;
|
|
7
|
+
owned: WeakSet<object>;
|
|
8
|
+
};
|
|
9
|
+
/** Run a mutation recipe and return its value with transaction ownership metadata. */
|
|
10
|
+
export declare function produceWithMetadata<T extends object>(base: T, recipe: (draft: Draft<T>) => void): ProduceMetadata<T>;
|
|
11
|
+
/** Run a mutation recipe against a copy-on-write draft of `base`. */
|
|
12
|
+
export declare function produce<T extends object>(base: T, recipe: (draft: Draft<T>) => void): T;
|
|
13
|
+
//# sourceMappingURL=draft.d.ts.map
|