@earendil-works/chord 1.1.0 → 1.2.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/CHANGELOG.md +20 -0
- package/README.md +77 -194
- package/dist/cell.d.ts +101 -0
- package/dist/cell.d.ts.map +1 -0
- package/dist/cell.js +316 -0
- package/dist/cell.js.map +1 -0
- package/dist/cluster.d.ts +26 -0
- package/dist/cluster.d.ts.map +1 -0
- package/dist/cluster.js +214 -0
- package/dist/cluster.js.map +1 -0
- package/dist/connection.d.ts +157 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +757 -0
- package/dist/connection.js.map +1 -0
- package/dist/context/index.d.ts +12 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js.map +1 -1
- package/dist/contract.d.ts +133 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +157 -0
- package/dist/contract.js.map +1 -0
- package/dist/declare.d.ts +70 -0
- package/dist/declare.d.ts.map +1 -0
- package/dist/declare.js +22 -0
- package/dist/declare.js.map +1 -0
- package/dist/delta/apply-immutable-trusted.js.map +1 -1
- package/dist/delta/diff.d.ts +1 -1
- package/dist/delta/diff.d.ts.map +1 -1
- package/dist/delta/diff.js.map +1 -1
- package/dist/delta/index.d.ts +2 -2
- package/dist/delta/index.d.ts.map +1 -1
- package/dist/delta/index.js +41 -0
- package/dist/delta/index.js.map +1 -1
- package/dist/delta/revision-validator.d.ts +1 -1
- package/dist/delta/revision-validator.d.ts.map +1 -1
- package/dist/delta/revision-validator.js.map +1 -1
- package/dist/delta/tracker.d.ts +1 -1
- package/dist/delta/tracker.d.ts.map +1 -1
- package/dist/delta/tracker.js +40 -4
- package/dist/delta/tracker.js.map +1 -1
- package/dist/errors.d.ts +27 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +44 -0
- package/dist/errors.js.map +1 -0
- package/dist/host.d.ts +254 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +795 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +17 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/json.d.ts +9 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js.map +1 -1
- package/dist/link.d.ts +72 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +169 -0
- package/dist/link.js.map +1 -0
- package/dist/loader.d.ts +4 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +68 -0
- package/dist/loader.js.map +1 -0
- package/dist/manager.d.ts +115 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +319 -0
- package/dist/manager.js.map +1 -0
- package/dist/node.d.ts +3 -4
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +3 -2
- package/dist/node.js.map +1 -1
- package/dist/publish.d.ts +5 -0
- package/dist/publish.d.ts.map +1 -0
- package/dist/publish.js +40 -0
- package/dist/publish.js.map +1 -0
- package/dist/record.d.ts +320 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +1292 -0
- package/dist/record.js.map +1 -0
- package/dist/registry.d.ts +25 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +6 -0
- package/dist/registry.js.map +1 -0
- package/dist/reload.d.ts +118 -0
- package/dist/reload.d.ts.map +1 -0
- package/dist/reload.js +589 -0
- package/dist/reload.js.map +1 -0
- package/dist/remote.d.ts +70 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +308 -0
- package/dist/remote.js.map +1 -0
- package/dist/schema.d.ts +21 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +228 -0
- package/dist/schema.js.map +1 -0
- package/dist/state.d.ts +92 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/{services/state.js → state.js} +28 -138
- package/dist/state.js.map +1 -0
- package/dist/types.d.ts +62 -262
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/websocket.d.ts +16 -0
- package/dist/websocket.d.ts.map +1 -0
- package/dist/websocket.js +15 -0
- package/dist/websocket.js.map +1 -0
- package/dist/wire.d.ts +135 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +112 -0
- package/dist/wire.js.map +1 -0
- package/docs/guide.md +750 -0
- package/docs/spec.md +261 -0
- package/docs/tutorial.md +909 -0
- package/package.json +9 -10
- package/src/delta/README.md +5 -3
- package/dist/api.d.ts +0 -20
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js +0 -77
- package/dist/api.js.map +0 -1
- package/dist/bundler.d.ts +0 -6
- package/dist/bundler.d.ts.map +0 -1
- package/dist/bundler.js +0 -3
- package/dist/bundler.js.map +0 -1
- package/dist/facets/host.d.ts +0 -12
- package/dist/facets/host.d.ts.map +0 -1
- package/dist/facets/host.js +0 -763
- package/dist/facets/host.js.map +0 -1
- package/dist/facets/loader.d.ts +0 -3
- package/dist/facets/loader.d.ts.map +0 -1
- package/dist/facets/loader.js +0 -5
- package/dist/facets/loader.js.map +0 -1
- package/dist/node/bundle-loader.d.ts +0 -30
- package/dist/node/bundle-loader.d.ts.map +0 -1
- package/dist/node/bundle-loader.js +0 -377
- package/dist/node/bundle-loader.js.map +0 -1
- package/dist/node/bundle.d.ts +0 -26
- package/dist/node/bundle.d.ts.map +0 -1
- package/dist/node/bundle.js +0 -183
- package/dist/node/bundle.js.map +0 -1
- package/dist/node/manifest.d.ts +0 -36
- package/dist/node/manifest.d.ts.map +0 -1
- package/dist/node/manifest.js +0 -6
- package/dist/node/manifest.js.map +0 -1
- package/dist/node/package.d.ts +0 -15
- package/dist/node/package.d.ts.map +0 -1
- package/dist/node/package.js +0 -199
- package/dist/node/package.js.map +0 -1
- package/dist/services/consumer.d.ts +0 -11
- package/dist/services/consumer.d.ts.map +0 -1
- package/dist/services/consumer.js +0 -585
- package/dist/services/consumer.js.map +0 -1
- package/dist/services/errors.d.ts +0 -8
- package/dist/services/errors.d.ts.map +0 -1
- package/dist/services/errors.js +0 -22
- package/dist/services/errors.js.map +0 -1
- package/dist/services/handle.d.ts +0 -15
- package/dist/services/handle.d.ts.map +0 -1
- package/dist/services/handle.js +0 -101
- package/dist/services/handle.js.map +0 -1
- package/dist/services/instances.d.ts +0 -26
- package/dist/services/instances.d.ts.map +0 -1
- package/dist/services/instances.js +0 -140
- package/dist/services/instances.js.map +0 -1
- package/dist/services/loopback.d.ts +0 -5
- package/dist/services/loopback.d.ts.map +0 -1
- package/dist/services/loopback.js +0 -15
- package/dist/services/loopback.js.map +0 -1
- package/dist/services/provider.d.ts +0 -37
- package/dist/services/provider.d.ts.map +0 -1
- package/dist/services/provider.js +0 -521
- package/dist/services/provider.js.map +0 -1
- package/dist/services/state-codec.d.ts +0 -15
- package/dist/services/state-codec.d.ts.map +0 -1
- package/dist/services/state-codec.js +0 -133
- package/dist/services/state-codec.js.map +0 -1
- package/dist/services/state-internals.d.ts +0 -13
- package/dist/services/state-internals.d.ts.map +0 -1
- package/dist/services/state-internals.js +0 -10
- package/dist/services/state-internals.js.map +0 -1
- package/dist/services/state.d.ts +0 -28
- package/dist/services/state.d.ts.map +0 -1
- package/dist/services/state.js.map +0 -1
- package/dist/services/wire.d.ts +0 -63
- package/dist/services/wire.d.ts.map +0 -1
- package/dist/services/wire.js +0 -186
- package/dist/services/wire.js.map +0 -1
package/dist/schema.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { Value } from "typebox/value";
|
|
2
|
+
import { invalid } from "./errors.js";
|
|
3
|
+
const asSchema = (schema) => schema !== null && typeof schema === "object" ? schema : schema === false ? { not: {} } : {};
|
|
4
|
+
/** Canonical JSON: sorted object keys, `undefined` members dropped. */
|
|
5
|
+
export function canonicalJson(value) {
|
|
6
|
+
if (value === null || typeof value !== "object")
|
|
7
|
+
return JSON.stringify(value) ?? "null";
|
|
8
|
+
if (Array.isArray(value))
|
|
9
|
+
return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
|
|
10
|
+
const keys = Object.keys(value)
|
|
11
|
+
.filter((key) => value[key] !== undefined)
|
|
12
|
+
.sort();
|
|
13
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
|
|
14
|
+
}
|
|
15
|
+
/** Non-cryptographic 64-bit hash (two FNV-1a lanes). Contracts are trusted, so collisions are not an attack surface. */
|
|
16
|
+
export function hash64(text) {
|
|
17
|
+
let a = 0x811c9dc5;
|
|
18
|
+
let b = 0x01000193 ^ 0x5bd1e995;
|
|
19
|
+
for (let index = 0; index < text.length; index++) {
|
|
20
|
+
const code = text.charCodeAt(index);
|
|
21
|
+
a = Math.imul(a ^ code, 0x01000193);
|
|
22
|
+
b = Math.imul(b ^ code, 0x5bd1e995) ^ (b >>> 13);
|
|
23
|
+
}
|
|
24
|
+
return (a >>> 0).toString(16).padStart(8, "0") + (b >>> 0).toString(16).padStart(8, "0");
|
|
25
|
+
}
|
|
26
|
+
export function deepEqual(a, b) {
|
|
27
|
+
return a === b || canonicalJson(a) === canonicalJson(b);
|
|
28
|
+
}
|
|
29
|
+
/** Throws `invalid` (with `reason`) and the first schema errors when `value` does not match. */
|
|
30
|
+
export function assertValid(reason, what, schema, value) {
|
|
31
|
+
if (Value.Check(schema, value))
|
|
32
|
+
return;
|
|
33
|
+
const errors = [...Value.Errors(schema, value)].slice(0, 3);
|
|
34
|
+
const summary = errors.map((error) => `${error.instancePath || "/"} ${error.message}`).join("; ");
|
|
35
|
+
throw invalid(reason, `${what}: ${summary}`, errors);
|
|
36
|
+
}
|
|
37
|
+
/** Applies defaults, strips unknown fields, validates. Returns a fresh value. */
|
|
38
|
+
export function normalize(schema, value, what) {
|
|
39
|
+
const normalized = Value.Clean(schema, Value.Default(schema, Value.Clone(value ?? {})));
|
|
40
|
+
assertValid("input", what, schema, normalized);
|
|
41
|
+
return normalized;
|
|
42
|
+
}
|
|
43
|
+
/** Canonical identity of a normalized value: fields equal to their schema default are omitted. */
|
|
44
|
+
export function canonicalIdentity(schema, value) {
|
|
45
|
+
return canonicalJson(stripDefaults(asSchema(schema), value));
|
|
46
|
+
}
|
|
47
|
+
function stripDefaults(schema, value) {
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
const items = schema.items;
|
|
50
|
+
if (Array.isArray(items))
|
|
51
|
+
return value.map((item, index) => stripDefaults(asSchema(items[index]), item));
|
|
52
|
+
return value.map((item) => stripDefaults(asSchema(items), item));
|
|
53
|
+
}
|
|
54
|
+
if (value === null || typeof value !== "object")
|
|
55
|
+
return value;
|
|
56
|
+
const properties = asSchema(schema.properties);
|
|
57
|
+
const result = {};
|
|
58
|
+
for (const [key, item] of Object.entries(value)) {
|
|
59
|
+
const property = asSchema(properties[key]);
|
|
60
|
+
if ("default" in property && deepEqual(property.default, item))
|
|
61
|
+
continue;
|
|
62
|
+
result[key] = stripDefaults(property, item);
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
const CONSTRAINTS = [
|
|
67
|
+
"minLength",
|
|
68
|
+
"maxLength",
|
|
69
|
+
"pattern",
|
|
70
|
+
"format",
|
|
71
|
+
"minimum",
|
|
72
|
+
"maximum",
|
|
73
|
+
"exclusiveMinimum",
|
|
74
|
+
"exclusiveMaximum",
|
|
75
|
+
"multipleOf",
|
|
76
|
+
"minItems",
|
|
77
|
+
"maxItems",
|
|
78
|
+
"uniqueItems",
|
|
79
|
+
"minProperties",
|
|
80
|
+
"maxProperties",
|
|
81
|
+
];
|
|
82
|
+
const typesOf = (schema) => typeof schema.type === "string" ? [schema.type] : Array.isArray(schema.type) ? schema.type : undefined;
|
|
83
|
+
/** Keywords `fits` reasons about. Any other validation keyword in the provider's schema forces exact equality. */
|
|
84
|
+
const KNOWN = new Set([
|
|
85
|
+
...CONSTRAINTS,
|
|
86
|
+
"type",
|
|
87
|
+
"properties",
|
|
88
|
+
"required",
|
|
89
|
+
"additionalProperties",
|
|
90
|
+
"patternProperties",
|
|
91
|
+
"items",
|
|
92
|
+
"additionalItems",
|
|
93
|
+
"const",
|
|
94
|
+
"enum",
|
|
95
|
+
"anyOf",
|
|
96
|
+
"parameters",
|
|
97
|
+
"returnType",
|
|
98
|
+
]);
|
|
99
|
+
/** Annotations: no effect on validity. */
|
|
100
|
+
const ANNOTATIONS = new Set([
|
|
101
|
+
"title",
|
|
102
|
+
"description",
|
|
103
|
+
"default",
|
|
104
|
+
"examples",
|
|
105
|
+
"$id",
|
|
106
|
+
"$schema",
|
|
107
|
+
"$comment",
|
|
108
|
+
"readOnly",
|
|
109
|
+
"writeOnly",
|
|
110
|
+
"deprecated",
|
|
111
|
+
]);
|
|
112
|
+
const withoutAnnotations = (schema) => Object.fromEntries(Object.entries(schema).filter(([key]) => !ANNOTATIONS.has(key)));
|
|
113
|
+
/**
|
|
114
|
+
* Conservative structural subtyping: true only if every value valid under `sub` is valid under `sup`, where object
|
|
115
|
+
* fields missing from `sub` but optional in `sup` count as fine (unknown fields are stripped at the provider, see
|
|
116
|
+
* `RecordImpl.call`). A provider schema using a keyword this function does not model (`not`, `allOf`, `contains`,
|
|
117
|
+
* `$ref`, ...) or an `additionalProperties` schema only fits an identical consumer schema.
|
|
118
|
+
*/
|
|
119
|
+
export function fits(subSchema, supSchema) {
|
|
120
|
+
const sub = withoutAnnotations(asSchema(subSchema));
|
|
121
|
+
const sup = withoutAnnotations(asSchema(supSchema));
|
|
122
|
+
if (Object.keys(sup).length === 0)
|
|
123
|
+
return true;
|
|
124
|
+
if (deepEqual(sub, sup))
|
|
125
|
+
return true;
|
|
126
|
+
if (Object.keys(sup).some((key) => !KNOWN.has(key)))
|
|
127
|
+
return false;
|
|
128
|
+
if (sup.additionalProperties !== undefined && typeof sup.additionalProperties !== "boolean")
|
|
129
|
+
return false;
|
|
130
|
+
if (Array.isArray(sub.anyOf))
|
|
131
|
+
return sub.anyOf.every((branch) => fits(branch, sup));
|
|
132
|
+
if (Array.isArray(sup.anyOf))
|
|
133
|
+
return sup.anyOf.some((branch) => fits(sub, branch));
|
|
134
|
+
if ("const" in sup)
|
|
135
|
+
return "const" in sub && deepEqual(sub.const, sup.const);
|
|
136
|
+
if (Array.isArray(sup.enum)) {
|
|
137
|
+
const values = "const" in sub ? [sub.const] : Array.isArray(sub.enum) ? sub.enum : undefined;
|
|
138
|
+
return values?.every((item) => sup.enum.some((allowed) => deepEqual(allowed, item))) ?? false;
|
|
139
|
+
}
|
|
140
|
+
for (const key of CONSTRAINTS)
|
|
141
|
+
if (key in sup && !deepEqual(sub[key], sup[key]))
|
|
142
|
+
return false;
|
|
143
|
+
const supTypes = typesOf(sup);
|
|
144
|
+
const subTypes = typesOf(sub);
|
|
145
|
+
if (supTypes === undefined || subTypes === undefined)
|
|
146
|
+
return false;
|
|
147
|
+
if (!subTypes.every((type) => supTypes.includes(type) || (type === "integer" && supTypes.includes("number")))) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
if ("const" in sub || Array.isArray(sub.enum))
|
|
151
|
+
return true;
|
|
152
|
+
if (subTypes.includes("object") && !objectFits(sub, sup))
|
|
153
|
+
return false;
|
|
154
|
+
if (subTypes.includes("array") && !arrayFits(sub, sup))
|
|
155
|
+
return false;
|
|
156
|
+
if (subTypes.includes("function"))
|
|
157
|
+
return functionFits(sub, sup);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
function objectFits(sub, sup) {
|
|
161
|
+
const subProperties = asSchema(sub.properties);
|
|
162
|
+
const supProperties = asSchema(sup.properties);
|
|
163
|
+
const subRequired = new Set(sub.required ?? []);
|
|
164
|
+
for (const key of sup.required ?? [])
|
|
165
|
+
if (!subRequired.has(key))
|
|
166
|
+
return false;
|
|
167
|
+
for (const [key, property] of Object.entries(supProperties)) {
|
|
168
|
+
if (key in subProperties) {
|
|
169
|
+
if (!fits(subProperties[key], property))
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
else if (typeof sub.additionalProperties === "object" && !fits(sub.additionalProperties, property)) {
|
|
173
|
+
// The consumer may send this name with any value its `additionalProperties` allows. Open consumer objects
|
|
174
|
+
// are fine: unknown names are stripped with the consumer's own schema before the call (`RecordImpl.call`).
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (sup.additionalProperties === false) {
|
|
179
|
+
if (sub.additionalProperties !== false)
|
|
180
|
+
return false;
|
|
181
|
+
if (Object.keys(subProperties).some((key) => !(key in supProperties)))
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
const supPatterns = asSchema(sup.patternProperties);
|
|
185
|
+
if (Object.keys(supPatterns).length > 0) {
|
|
186
|
+
// Every value the consumer may put under a matching name must fit, and the consumer must not allow arbitrary
|
|
187
|
+
// extra names (it would then allow any value under a matching one).
|
|
188
|
+
const subPatterns = asSchema(sub.patternProperties);
|
|
189
|
+
if (sub.additionalProperties !== false && Object.keys(subPatterns).length === 0)
|
|
190
|
+
return false;
|
|
191
|
+
for (const [pattern, property] of Object.entries(supPatterns)) {
|
|
192
|
+
const matcher = new RegExp(pattern, "u");
|
|
193
|
+
for (const [key, subProperty] of Object.entries(subProperties)) {
|
|
194
|
+
if (!(key in supProperties) && matcher.test(key) && !fits(subProperty, property))
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
if (Object.keys(subPatterns).length > 0 &&
|
|
198
|
+
(!(pattern in subPatterns) || !fits(subPatterns[pattern], property))) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
for (const pattern of Object.keys(subPatterns))
|
|
203
|
+
if (!(pattern in supPatterns))
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
function arrayFits(sub, sup) {
|
|
209
|
+
if (Array.isArray(sup.items)) {
|
|
210
|
+
if (!Array.isArray(sub.items) || sub.items.length !== sup.items.length)
|
|
211
|
+
return false;
|
|
212
|
+
return sub.items.every((item, index) => fits(item, sup.items[index]));
|
|
213
|
+
}
|
|
214
|
+
if (Array.isArray(sub.items))
|
|
215
|
+
return sub.items.every((item) => fits(item, sup.items));
|
|
216
|
+
return fits(sub.items, sup.items);
|
|
217
|
+
}
|
|
218
|
+
function functionFits(sub, sup) {
|
|
219
|
+
const subParameters = sub.parameters ?? [];
|
|
220
|
+
const supParameters = sup.parameters ?? [];
|
|
221
|
+
// A function accepting fewer parameters is fine; each shared parameter is contravariant.
|
|
222
|
+
if (subParameters.length > supParameters.length)
|
|
223
|
+
return false;
|
|
224
|
+
if (!subParameters.every((parameter, index) => fits(supParameters[index], parameter)))
|
|
225
|
+
return false;
|
|
226
|
+
return fits(sub.returnType, sup.returnType);
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TSchema } from \"typebox\";\nimport { Value } from \"typebox/value\";\nimport { type ChordErrorReason, invalid } from \"./errors.ts\";\n\n/** JSON Schema as data. TypeBox schemas are plain JSON Schema objects. */\ntype Schema = { readonly [key: string]: unknown };\n\nconst asSchema = (schema: unknown): Schema =>\n\tschema !== null && typeof schema === \"object\" ? (schema as Schema) : schema === false ? { not: {} } : {};\n\n/** Canonical JSON: sorted object keys, `undefined` members dropped. */\nexport function canonicalJson(value: unknown): string {\n\tif (value === null || typeof value !== \"object\") return JSON.stringify(value) ?? \"null\";\n\tif (Array.isArray(value)) return `[${value.map((item) => canonicalJson(item)).join(\",\")}]`;\n\tconst keys = Object.keys(value)\n\t\t.filter((key) => (value as Record<string, unknown>)[key] !== undefined)\n\t\t.sort();\n\treturn `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson((value as Record<string, unknown>)[key])}`).join(\",\")}}`;\n}\n\n/** Non-cryptographic 64-bit hash (two FNV-1a lanes). Contracts are trusted, so collisions are not an attack surface. */\nexport function hash64(text: string): string {\n\tlet a = 0x811c9dc5;\n\tlet b = 0x01000193 ^ 0x5bd1e995;\n\tfor (let index = 0; index < text.length; index++) {\n\t\tconst code = text.charCodeAt(index);\n\t\ta = Math.imul(a ^ code, 0x01000193);\n\t\tb = Math.imul(b ^ code, 0x5bd1e995) ^ (b >>> 13);\n\t}\n\treturn (a >>> 0).toString(16).padStart(8, \"0\") + (b >>> 0).toString(16).padStart(8, \"0\");\n}\n\nexport function deepEqual(a: unknown, b: unknown): boolean {\n\treturn a === b || canonicalJson(a) === canonicalJson(b);\n}\n\n/** Throws `invalid` (with `reason`) and the first schema errors when `value` does not match. */\nexport function assertValid(reason: ChordErrorReason, what: string, schema: TSchema, value: unknown): void {\n\tif (Value.Check(schema, value)) return;\n\tconst errors = [...Value.Errors(schema, value)].slice(0, 3);\n\tconst summary = errors.map((error) => `${error.instancePath || \"/\"} ${error.message}`).join(\"; \");\n\tthrow invalid(reason, `${what}: ${summary}`, errors);\n}\n\n/** Applies defaults, strips unknown fields, validates. Returns a fresh value. */\nexport function normalize(schema: TSchema, value: unknown, what: string): unknown {\n\tconst normalized = Value.Clean(schema, Value.Default(schema, Value.Clone(value ?? {})));\n\tassertValid(\"input\", what, schema, normalized);\n\treturn normalized;\n}\n\n/** Canonical identity of a normalized value: fields equal to their schema default are omitted. */\nexport function canonicalIdentity(schema: TSchema, value: unknown): string {\n\treturn canonicalJson(stripDefaults(asSchema(schema), value));\n}\n\nfunction stripDefaults(schema: Schema, value: unknown): unknown {\n\tif (Array.isArray(value)) {\n\t\tconst items = schema.items;\n\t\tif (Array.isArray(items)) return value.map((item, index) => stripDefaults(asSchema(items[index]), item));\n\t\treturn value.map((item) => stripDefaults(asSchema(items), item));\n\t}\n\tif (value === null || typeof value !== \"object\") return value;\n\tconst properties = asSchema(schema.properties);\n\tconst result: Record<string, unknown> = {};\n\tfor (const [key, item] of Object.entries(value)) {\n\t\tconst property = asSchema(properties[key]);\n\t\tif (\"default\" in property && deepEqual(property.default, item)) continue;\n\t\tresult[key] = stripDefaults(property, item);\n\t}\n\treturn result;\n}\n\nconst CONSTRAINTS = [\n\t\"minLength\",\n\t\"maxLength\",\n\t\"pattern\",\n\t\"format\",\n\t\"minimum\",\n\t\"maximum\",\n\t\"exclusiveMinimum\",\n\t\"exclusiveMaximum\",\n\t\"multipleOf\",\n\t\"minItems\",\n\t\"maxItems\",\n\t\"uniqueItems\",\n\t\"minProperties\",\n\t\"maxProperties\",\n] as const;\n\nconst typesOf = (schema: Schema): string[] | undefined =>\n\ttypeof schema.type === \"string\" ? [schema.type] : Array.isArray(schema.type) ? (schema.type as string[]) : undefined;\n\n/** Keywords `fits` reasons about. Any other validation keyword in the provider's schema forces exact equality. */\nconst KNOWN: ReadonlySet<string> = new Set([\n\t...CONSTRAINTS,\n\t\"type\",\n\t\"properties\",\n\t\"required\",\n\t\"additionalProperties\",\n\t\"patternProperties\",\n\t\"items\",\n\t\"additionalItems\",\n\t\"const\",\n\t\"enum\",\n\t\"anyOf\",\n\t\"parameters\",\n\t\"returnType\",\n]);\n\n/** Annotations: no effect on validity. */\nconst ANNOTATIONS: ReadonlySet<string> = new Set([\n\t\"title\",\n\t\"description\",\n\t\"default\",\n\t\"examples\",\n\t\"$id\",\n\t\"$schema\",\n\t\"$comment\",\n\t\"readOnly\",\n\t\"writeOnly\",\n\t\"deprecated\",\n]);\n\nconst withoutAnnotations = (schema: Schema): Schema =>\n\tObject.fromEntries(Object.entries(schema).filter(([key]) => !ANNOTATIONS.has(key)));\n\n/**\n * Conservative structural subtyping: true only if every value valid under `sub` is valid under `sup`, where object\n * fields missing from `sub` but optional in `sup` count as fine (unknown fields are stripped at the provider, see\n * `RecordImpl.call`). A provider schema using a keyword this function does not model (`not`, `allOf`, `contains`,\n * `$ref`, ...) or an `additionalProperties` schema only fits an identical consumer schema.\n */\nexport function fits(subSchema: unknown, supSchema: unknown): boolean {\n\tconst sub = withoutAnnotations(asSchema(subSchema));\n\tconst sup = withoutAnnotations(asSchema(supSchema));\n\tif (Object.keys(sup).length === 0) return true;\n\tif (deepEqual(sub, sup)) return true;\n\tif (Object.keys(sup).some((key) => !KNOWN.has(key))) return false;\n\tif (sup.additionalProperties !== undefined && typeof sup.additionalProperties !== \"boolean\") return false;\n\tif (Array.isArray(sub.anyOf)) return sub.anyOf.every((branch) => fits(branch, sup));\n\tif (Array.isArray(sup.anyOf)) return sup.anyOf.some((branch) => fits(sub, branch));\n\tif (\"const\" in sup) return \"const\" in sub && deepEqual(sub.const, sup.const);\n\tif (Array.isArray(sup.enum)) {\n\t\tconst values = \"const\" in sub ? [sub.const] : Array.isArray(sub.enum) ? sub.enum : undefined;\n\t\treturn values?.every((item) => (sup.enum as unknown[]).some((allowed) => deepEqual(allowed, item))) ?? false;\n\t}\n\tfor (const key of CONSTRAINTS) if (key in sup && !deepEqual(sub[key], sup[key])) return false;\n\tconst supTypes = typesOf(sup);\n\tconst subTypes = typesOf(sub);\n\tif (supTypes === undefined || subTypes === undefined) return false;\n\tif (!subTypes.every((type) => supTypes.includes(type) || (type === \"integer\" && supTypes.includes(\"number\")))) {\n\t\treturn false;\n\t}\n\tif (\"const\" in sub || Array.isArray(sub.enum)) return true;\n\tif (subTypes.includes(\"object\") && !objectFits(sub, sup)) return false;\n\tif (subTypes.includes(\"array\") && !arrayFits(sub, sup)) return false;\n\tif (subTypes.includes(\"function\")) return functionFits(sub, sup);\n\treturn true;\n}\n\nfunction objectFits(sub: Schema, sup: Schema): boolean {\n\tconst subProperties = asSchema(sub.properties);\n\tconst supProperties = asSchema(sup.properties);\n\tconst subRequired = new Set((sub.required as string[] | undefined) ?? []);\n\tfor (const key of (sup.required as string[] | undefined) ?? []) if (!subRequired.has(key)) return false;\n\tfor (const [key, property] of Object.entries(supProperties)) {\n\t\tif (key in subProperties) {\n\t\t\tif (!fits(subProperties[key], property)) return false;\n\t\t} else if (typeof sub.additionalProperties === \"object\" && !fits(sub.additionalProperties, property)) {\n\t\t\t// The consumer may send this name with any value its `additionalProperties` allows. Open consumer objects\n\t\t\t// are fine: unknown names are stripped with the consumer's own schema before the call (`RecordImpl.call`).\n\t\t\treturn false;\n\t\t}\n\t}\n\tif (sup.additionalProperties === false) {\n\t\tif (sub.additionalProperties !== false) return false;\n\t\tif (Object.keys(subProperties).some((key) => !(key in supProperties))) return false;\n\t}\n\tconst supPatterns = asSchema(sup.patternProperties);\n\tif (Object.keys(supPatterns).length > 0) {\n\t\t// Every value the consumer may put under a matching name must fit, and the consumer must not allow arbitrary\n\t\t// extra names (it would then allow any value under a matching one).\n\t\tconst subPatterns = asSchema(sub.patternProperties);\n\t\tif (sub.additionalProperties !== false && Object.keys(subPatterns).length === 0) return false;\n\t\tfor (const [pattern, property] of Object.entries(supPatterns)) {\n\t\t\tconst matcher = new RegExp(pattern, \"u\");\n\t\t\tfor (const [key, subProperty] of Object.entries(subProperties)) {\n\t\t\t\tif (!(key in supProperties) && matcher.test(key) && !fits(subProperty, property)) return false;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tObject.keys(subPatterns).length > 0 &&\n\t\t\t\t(!(pattern in subPatterns) || !fits(subPatterns[pattern], property))\n\t\t\t) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tfor (const pattern of Object.keys(subPatterns)) if (!(pattern in supPatterns)) return false;\n\t}\n\treturn true;\n}\n\nfunction arrayFits(sub: Schema, sup: Schema): boolean {\n\tif (Array.isArray(sup.items)) {\n\t\tif (!Array.isArray(sub.items) || sub.items.length !== sup.items.length) return false;\n\t\treturn sub.items.every((item, index) => fits(item, (sup.items as unknown[])[index]));\n\t}\n\tif (Array.isArray(sub.items)) return sub.items.every((item) => fits(item, sup.items));\n\treturn fits(sub.items, sup.items);\n}\n\nfunction functionFits(sub: Schema, sup: Schema): boolean {\n\tconst subParameters = (sub.parameters as unknown[] | undefined) ?? [];\n\tconst supParameters = (sup.parameters as unknown[] | undefined) ?? [];\n\t// A function accepting fewer parameters is fine; each shared parameter is contravariant.\n\tif (subParameters.length > supParameters.length) return false;\n\tif (!subParameters.every((parameter, index) => fits(supParameters[index], parameter))) return false;\n\treturn fits(sub.returnType, sup.returnType);\n}\n"]}
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replicated state: an immutable JSON value with ordered, serialized change notifications. Either authoritative and
|
|
3
|
+
* mutable (`replicatedState(initial)`) or a publication-only view of an external revision source
|
|
4
|
+
* (`replicatedState(source)`).
|
|
5
|
+
*/
|
|
6
|
+
import { type Context } from "./context/index.ts";
|
|
7
|
+
import { type Draft, type Op } from "./delta/index.ts";
|
|
8
|
+
export interface ReplicatedStateDelivery {
|
|
9
|
+
readonly kind: "hydrate" | "update";
|
|
10
|
+
readonly sequence: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ReplicatedState<T> {
|
|
13
|
+
/**
|
|
14
|
+
* Contract-immutable value, or undefined until hydration. It is not frozen and may share containers with an
|
|
15
|
+
* in-process provider; consumers must not mutate it. Later updates do not mutate previously returned values.
|
|
16
|
+
*/
|
|
17
|
+
readonly value: T | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Each subscription serializes callbacks, awaiting hydration before updates. Values are immutable and may
|
|
20
|
+
* share unchanged revision data. At most 100 deliveries wait behind the running callback; overflow keeps
|
|
21
|
+
* only the newest pending value/context/delivery, so update sequences may skip. Failures are reported in
|
|
22
|
+
* isolation and delivery continues. Unsubscribe discards pending work without aborting or joining a callback.
|
|
23
|
+
*/
|
|
24
|
+
subscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => Promise<void>): () => void;
|
|
25
|
+
subscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void): () => void;
|
|
26
|
+
}
|
|
27
|
+
export interface MutableReplicatedState<T extends object> extends ReplicatedState<T> {
|
|
28
|
+
readonly value: T;
|
|
29
|
+
/**
|
|
30
|
+
* Atomically publish one synchronous overlay mutation. Draft handles are unusable after the callback returns.
|
|
31
|
+
* Values placed through the draft are cloned by value; assigning `undefined` to an object property deletes it.
|
|
32
|
+
*/
|
|
33
|
+
change(context: Context, mutate: (draft: Draft<T>) => void): void;
|
|
34
|
+
/**
|
|
35
|
+
* Atomically take immutable ownership of an alias-free strict-JSON replacement.
|
|
36
|
+
* The caller must not mutate the transferred root after this call.
|
|
37
|
+
*/
|
|
38
|
+
replace(context: Context, value: T): void;
|
|
39
|
+
}
|
|
40
|
+
/** One immutable authoritative revision committed after an attachment snapshot. */
|
|
41
|
+
export interface ReplicatedStateSourceFrame<T> {
|
|
42
|
+
/** Monotonic source cursor. The first frame after a snapshot must be `snapshot.cursor + 1`. */
|
|
43
|
+
readonly cursor: number;
|
|
44
|
+
/** The exact immutable value produced by this commit. */
|
|
45
|
+
readonly value: T;
|
|
46
|
+
/** The exact immutable operation batch that produced `value` from the preceding source revision. */
|
|
47
|
+
readonly ops: readonly Op[];
|
|
48
|
+
readonly context: Context;
|
|
49
|
+
}
|
|
50
|
+
export interface ReplicatedStateSourceAttachment<T> {
|
|
51
|
+
/** Fixed immutable snapshot captured at the atomic attachment boundary. */
|
|
52
|
+
readonly snapshot: {
|
|
53
|
+
readonly value: T;
|
|
54
|
+
readonly cursor: number;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Install the sole listener and synchronously drain every buffered frame in source commit order.
|
|
58
|
+
* This method is single-use. After it begins, every new committed frame must also be delivered in order
|
|
59
|
+
* until disposal, including commits made reentrantly while a prior frame is being delivered.
|
|
60
|
+
*/
|
|
61
|
+
activate(listener: (frame: ReplicatedStateSourceFrame<T>) => void): void;
|
|
62
|
+
/** Stop delivery and release source resources. Disposal must be idempotent. */
|
|
63
|
+
dispose(): void;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* An authoritative immutable revision source.
|
|
67
|
+
*
|
|
68
|
+
* `attach()` must synchronously and atomically capture one snapshot and register the returned attachment to buffer
|
|
69
|
+
* every later committed frame. The snapshot must include every commit before that boundary; buffered frames must
|
|
70
|
+
* include every commit after it, with no overlap or gap. Snapshot values, frame values, and operation batches are
|
|
71
|
+
* immutable and remain valid after delivery. Chord only publishes these references; it never applies or re-diffs them.
|
|
72
|
+
*/
|
|
73
|
+
export interface ReplicatedStateSource<T> {
|
|
74
|
+
attach(): ReplicatedStateSourceAttachment<T>;
|
|
75
|
+
}
|
|
76
|
+
export interface ReplicatedStateSourceOptions {
|
|
77
|
+
/** Receives source-contract and publication-listener failures without throwing them into the source. */
|
|
78
|
+
readonly onError?: (error: Error) => void;
|
|
79
|
+
}
|
|
80
|
+
/** A synchronously hydrated publication-only state backed by one source attachment. */
|
|
81
|
+
export interface AttachedReplicatedState<T> extends ReplicatedState<T> {
|
|
82
|
+
readonly value: T;
|
|
83
|
+
/** Idempotently release the source attachment. The last published value remains readable. */
|
|
84
|
+
dispose(): void;
|
|
85
|
+
}
|
|
86
|
+
export declare function replicatedState<T>(source: ReplicatedStateSource<T>, options?: ReplicatedStateSourceOptions): AttachedReplicatedState<T>;
|
|
87
|
+
/**
|
|
88
|
+
* Create authoritative state by taking immutable ownership of an alias-free strict-JSON root.
|
|
89
|
+
* The caller must not mutate `initial` after this call.
|
|
90
|
+
*/
|
|
91
|
+
export declare function replicatedState<T extends object>(initial: T): MutableReplicatedState<T>;
|
|
92
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Replicated state: an immutable JSON value with ordered, serialized change notifications. Either authoritative and
|
|
3
|
+
* mutable (`replicatedState(initial)`) or a publication-only view of an external revision source
|
|
4
|
+
* (`replicatedState(source)`).
|
|
5
|
+
*/
|
|
6
|
+
import { BACKGROUND_CONTEXT } from "./context/index.js";
|
|
7
|
+
import { track } from "./delta/index.js";
|
|
5
8
|
/** One public subscription, independent of producer and other subscriber progress. */
|
|
6
9
|
class StateSubscriber {
|
|
7
10
|
#listener;
|
|
@@ -72,7 +75,6 @@ class StateSubscriber {
|
|
|
72
75
|
class ReplicatedStatePublisher {
|
|
73
76
|
#listeners = new Map();
|
|
74
77
|
#reportError;
|
|
75
|
-
#sourceListeners = new Set();
|
|
76
78
|
#publications = [];
|
|
77
79
|
#value;
|
|
78
80
|
#sequence = 0;
|
|
@@ -91,36 +93,24 @@ class ReplicatedStatePublisher {
|
|
|
91
93
|
const { value, sequence } = this.snapshot();
|
|
92
94
|
const subscriber = new StateSubscriber(listener, this.#reportError);
|
|
93
95
|
this.#listeners.set(subscriber, sequence);
|
|
94
|
-
|
|
96
|
+
// Hydration has no caller; it is delivered with the background context.
|
|
97
|
+
subscriber.push({ value, context: BACKGROUND_CONTEXT, delivery: { kind: "hydrate", sequence } });
|
|
95
98
|
subscriber.drain();
|
|
96
99
|
return () => {
|
|
97
100
|
subscriber.close();
|
|
98
101
|
this.#listeners.delete(subscriber);
|
|
99
102
|
};
|
|
100
103
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return () => this.#sourceListeners.delete(listener);
|
|
104
|
-
}
|
|
105
|
-
/** Publish an already-prepared immutable revision and return isolated listener failures. */
|
|
106
|
-
publish(value, ops, context) {
|
|
104
|
+
/** Publish an already-prepared immutable revision. Subscriber failures are reported, never thrown. */
|
|
105
|
+
publish(value, context) {
|
|
107
106
|
this.#value = value;
|
|
108
107
|
this.#sequence += 1;
|
|
109
|
-
this.#publications.push({ value,
|
|
108
|
+
this.#publications.push({ value, sequence: this.#sequence, context });
|
|
110
109
|
if (this.#delivering)
|
|
111
|
-
return
|
|
110
|
+
return;
|
|
112
111
|
this.#delivering = true;
|
|
113
|
-
const errors = [];
|
|
114
112
|
try {
|
|
115
113
|
for (let publication = this.#publications.shift(); publication !== undefined; publication = this.#publications.shift()) {
|
|
116
|
-
for (const listener of [...this.#sourceListeners]) {
|
|
117
|
-
try {
|
|
118
|
-
listener(publication.ops, publication.sequence, publication.context);
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
errors.push(error);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
114
|
const delivery = { kind: "update", sequence: publication.sequence };
|
|
125
115
|
for (const [subscriber, hydratedSequence] of [...this.#listeners]) {
|
|
126
116
|
if (publication.sequence <= hydratedSequence)
|
|
@@ -133,20 +123,15 @@ class ReplicatedStatePublisher {
|
|
|
133
123
|
finally {
|
|
134
124
|
this.#delivering = false;
|
|
135
125
|
}
|
|
136
|
-
return errors;
|
|
137
126
|
}
|
|
138
127
|
}
|
|
139
|
-
|
|
128
|
+
class MutableReplicatedStateImpl {
|
|
140
129
|
#tracker;
|
|
141
130
|
#publisher;
|
|
142
131
|
#changing = false;
|
|
143
132
|
constructor(initial) {
|
|
144
133
|
this.#tracker = track(initial);
|
|
145
134
|
this.#publisher = new ReplicatedStatePublisher(this.#tracker.value);
|
|
146
|
-
registerReplicatedStateInternals(this, {
|
|
147
|
-
snapshot: () => this.#publisher.snapshot(),
|
|
148
|
-
subscribe: (listener) => this.#publisher.subscribeSource(listener),
|
|
149
|
-
});
|
|
150
135
|
}
|
|
151
136
|
get value() {
|
|
152
137
|
return this.#tracker.value;
|
|
@@ -175,18 +160,16 @@ export class MutableReplicatedStateImpl {
|
|
|
175
160
|
this.#changing = false;
|
|
176
161
|
}
|
|
177
162
|
this.#tracker.adopt(prepared);
|
|
178
|
-
if (prepared.ops.length
|
|
179
|
-
|
|
180
|
-
throwCollectedErrors(this.#publisher.publish(prepared.value, prepared.ops, context), "Replicated state listeners failed");
|
|
163
|
+
if (prepared.ops.length > 0)
|
|
164
|
+
this.#publisher.publish(prepared.value, context);
|
|
181
165
|
}
|
|
182
166
|
replace(context, value) {
|
|
183
167
|
if (this.#changing)
|
|
184
168
|
throw new Error("Replicated state cannot be replaced from a change callback");
|
|
185
169
|
const prepared = this.#tracker.prepareReplace(value);
|
|
186
170
|
this.#tracker.adopt(prepared);
|
|
187
|
-
if (prepared.ops.length
|
|
188
|
-
|
|
189
|
-
throwCollectedErrors(this.#publisher.publish(prepared.value, prepared.ops, context), "Replicated state listeners failed");
|
|
171
|
+
if (prepared.ops.length > 0)
|
|
172
|
+
this.#publisher.publish(prepared.value, context);
|
|
190
173
|
}
|
|
191
174
|
subscribe(listener) {
|
|
192
175
|
return this.#publisher.subscribe(listener);
|
|
@@ -205,10 +188,6 @@ class AttachedReplicatedStateImpl {
|
|
|
205
188
|
this.#cursor = snapshot.cursor;
|
|
206
189
|
this.#reportError = options.onError ?? reportErrorAsync;
|
|
207
190
|
this.#publisher = new ReplicatedStatePublisher(snapshot.value, (error) => this.#report(error));
|
|
208
|
-
registerReplicatedStateInternals(this, {
|
|
209
|
-
snapshot: () => this.#publisher.snapshot(),
|
|
210
|
-
subscribe: (listener) => this.#publisher.subscribeSource(listener),
|
|
211
|
-
});
|
|
212
191
|
}
|
|
213
192
|
get value() {
|
|
214
193
|
return this.#publisher.value;
|
|
@@ -235,11 +214,7 @@ class AttachedReplicatedStateImpl {
|
|
|
235
214
|
throw new Error(`Replicated state source cursor has a gap: expected ${expected}, received ${frame.cursor}`);
|
|
236
215
|
}
|
|
237
216
|
this.#cursor = frame.cursor;
|
|
238
|
-
|
|
239
|
-
if (errors.length === 1)
|
|
240
|
-
this.#report(errors[0]);
|
|
241
|
-
else if (errors.length > 1)
|
|
242
|
-
this.#report(new AggregateError(errors, "Replicated state listeners failed"));
|
|
217
|
+
this.#publisher.publish(frame.value, frame.context);
|
|
243
218
|
}
|
|
244
219
|
catch (error) {
|
|
245
220
|
this.#fail(toError(error));
|
|
@@ -268,7 +243,7 @@ class AttachedReplicatedStateImpl {
|
|
|
268
243
|
}
|
|
269
244
|
}
|
|
270
245
|
/** Attach a publication-only replicated state to one authoritative immutable source stream. */
|
|
271
|
-
|
|
246
|
+
function attachReplicatedStateSource(source, options = {}) {
|
|
272
247
|
const attachment = source.attach();
|
|
273
248
|
try {
|
|
274
249
|
const state = new AttachedReplicatedStateImpl(attachment, options);
|
|
@@ -285,93 +260,6 @@ export function attachReplicatedStateSource(source, options = {}) {
|
|
|
285
260
|
throw error;
|
|
286
261
|
}
|
|
287
262
|
}
|
|
288
|
-
/** A cold read-only state used by service consumers until a complete snapshot arrives. */
|
|
289
|
-
export class ReplicatedStateReplica {
|
|
290
|
-
#listeners = new Set();
|
|
291
|
-
#reportError;
|
|
292
|
-
#validator = new JsonRevisionValidator();
|
|
293
|
-
#value;
|
|
294
|
-
#sequence;
|
|
295
|
-
constructor(reportError) {
|
|
296
|
-
this.#reportError = reportError;
|
|
297
|
-
}
|
|
298
|
-
get value() {
|
|
299
|
-
return this.#value;
|
|
300
|
-
}
|
|
301
|
-
subscribe(listener) {
|
|
302
|
-
const subscriber = new StateSubscriber(listener, this.#reportError);
|
|
303
|
-
this.#listeners.add(subscriber);
|
|
304
|
-
if (this.#value !== undefined) {
|
|
305
|
-
subscriber.push({
|
|
306
|
-
value: this.#value,
|
|
307
|
-
context: serviceDeliveryContext(),
|
|
308
|
-
delivery: { kind: "hydrate", sequence: this.#sequence },
|
|
309
|
-
});
|
|
310
|
-
subscriber.drain();
|
|
311
|
-
}
|
|
312
|
-
return () => {
|
|
313
|
-
subscriber.close();
|
|
314
|
-
this.#listeners.delete(subscriber);
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
hydrate(sequence, ops, context) {
|
|
318
|
-
let next;
|
|
319
|
-
try {
|
|
320
|
-
if (!isBase(ops))
|
|
321
|
-
throw new Error("Replicated state snapshot is not a base operation batch");
|
|
322
|
-
next = this.#validator.validate(applyImmutable(undefined, ops));
|
|
323
|
-
}
|
|
324
|
-
catch (error) {
|
|
325
|
-
this.clear();
|
|
326
|
-
throw error;
|
|
327
|
-
}
|
|
328
|
-
this.#sequence = sequence;
|
|
329
|
-
this.#value = next;
|
|
330
|
-
this.#deliverAll(context, { kind: "hydrate", sequence });
|
|
331
|
-
}
|
|
332
|
-
update(sequence, ops, context) {
|
|
333
|
-
if (this.#sequence === undefined || this.#value === undefined) {
|
|
334
|
-
throw new Error("Replicated state received an update before hydration");
|
|
335
|
-
}
|
|
336
|
-
if (sequence !== this.#sequence + 1) {
|
|
337
|
-
this.clear();
|
|
338
|
-
throw new Error("Replicated state update sequence has a gap");
|
|
339
|
-
}
|
|
340
|
-
let next;
|
|
341
|
-
try {
|
|
342
|
-
next = this.#validator.validate(applyImmutable(this.#value, ops));
|
|
343
|
-
}
|
|
344
|
-
catch (error) {
|
|
345
|
-
this.clear();
|
|
346
|
-
throw error;
|
|
347
|
-
}
|
|
348
|
-
this.#sequence = sequence;
|
|
349
|
-
this.#value = next;
|
|
350
|
-
this.#deliverAll(context, { kind: "update", sequence });
|
|
351
|
-
}
|
|
352
|
-
clear() {
|
|
353
|
-
this.#value = undefined;
|
|
354
|
-
this.#sequence = undefined;
|
|
355
|
-
for (const subscriber of this.#listeners)
|
|
356
|
-
subscriber.clear();
|
|
357
|
-
}
|
|
358
|
-
#deliverAll(context, delivery) {
|
|
359
|
-
if (this.#value === undefined)
|
|
360
|
-
return;
|
|
361
|
-
const frame = { value: this.#value, context, delivery };
|
|
362
|
-
const subscribers = [...this.#listeners];
|
|
363
|
-
// Enqueue for everyone before user code can publish another revision reentrantly.
|
|
364
|
-
for (const subscriber of subscribers)
|
|
365
|
-
subscriber.push(frame);
|
|
366
|
-
for (const subscriber of subscribers)
|
|
367
|
-
subscriber.drain();
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
/** @internal Context for synthetic service deliveries without a caller. */
|
|
371
|
-
export function serviceDeliveryContext() {
|
|
372
|
-
// TODO: Add delivery-scoped cancellation or metadata if deliveries gain an owned lifecycle.
|
|
373
|
-
return BACKGROUND_CONTEXT;
|
|
374
|
-
}
|
|
375
263
|
function assertCursor(cursor, kind) {
|
|
376
264
|
if (!Number.isSafeInteger(cursor))
|
|
377
265
|
throw new TypeError(`Replicated state source ${kind} cursor must be a safe integer`);
|
|
@@ -380,12 +268,6 @@ function isPromiseLike(value) {
|
|
|
380
268
|
return (((typeof value === "object" && value !== null) || typeof value === "function") &&
|
|
381
269
|
typeof value.then === "function");
|
|
382
270
|
}
|
|
383
|
-
function throwCollectedErrors(errors, message) {
|
|
384
|
-
if (errors.length === 1)
|
|
385
|
-
throw errors[0];
|
|
386
|
-
if (errors.length > 1)
|
|
387
|
-
throw new AggregateError(errors, message);
|
|
388
|
-
}
|
|
389
271
|
function reportErrorAsync(error) {
|
|
390
272
|
queueMicrotask(() => {
|
|
391
273
|
throw error;
|
|
@@ -394,4 +276,12 @@ function reportErrorAsync(error) {
|
|
|
394
276
|
function toError(error) {
|
|
395
277
|
return error instanceof Error ? error : new Error(String(error));
|
|
396
278
|
}
|
|
279
|
+
export function replicatedState(initialOrSource, options) {
|
|
280
|
+
if (isReplicatedStateSource(initialOrSource))
|
|
281
|
+
return attachReplicatedStateSource(initialOrSource, options);
|
|
282
|
+
return new MutableReplicatedStateImpl(initialOrSource);
|
|
283
|
+
}
|
|
284
|
+
function isReplicatedStateSource(value) {
|
|
285
|
+
return typeof value.attach === "function";
|
|
286
|
+
}
|
|
397
287
|
//# sourceMappingURL=state.js.map
|