@pumped-fn/lite-extension-sync 0.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/README.md +28 -0
- package/dist/index.cjs +347 -0
- package/dist/index.d.cts +93 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +93 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +348 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# @pumped-fn/lite-extension-sync
|
|
2
|
+
|
|
3
|
+
Strict replicated state for Lite scopes.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createScope } from "@pumped-fn/lite"
|
|
7
|
+
import { sync } from "@pumped-fn/lite-extension-sync"
|
|
8
|
+
|
|
9
|
+
const draft = sync({
|
|
10
|
+
id: "draft",
|
|
11
|
+
factory: () => ({ title: "", body: "" }),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const wire = sync.memory()
|
|
15
|
+
|
|
16
|
+
const left = createScope({
|
|
17
|
+
extensions: [sync.extension()],
|
|
18
|
+
tags: [sync.runtime({ peer: "left", transport: wire })],
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const right = createScope({
|
|
22
|
+
extensions: [sync.extension()],
|
|
23
|
+
tags: [sync.runtime({ peer: "right", transport: wire })],
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`sync(...)` returns an atom-like value. Plain JSON-safe state is inferred. Non-JSON state must use a
|
|
28
|
+
codec, and every inbound value is decoded before it can be applied.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _pumped_fn_lite = require("@pumped-fn/lite");
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
const meta = (0, _pumped_fn_lite.tag)({ label: "sync.meta" });
|
|
5
|
+
const runtime = (0, _pumped_fn_lite.tag)({ label: "sync.runtime" });
|
|
6
|
+
const bound = Symbol("@pumped-fn/lite-extension-sync/bound");
|
|
7
|
+
const json = {
|
|
8
|
+
encode: assertValue,
|
|
9
|
+
decode: assertValue
|
|
10
|
+
};
|
|
11
|
+
function create(config) {
|
|
12
|
+
const spec = {
|
|
13
|
+
id: config.id,
|
|
14
|
+
codec: config.codec ?? json,
|
|
15
|
+
conflict: config.conflict
|
|
16
|
+
};
|
|
17
|
+
if (config.deps) return (0, _pumped_fn_lite.atom)({
|
|
18
|
+
deps: config.deps,
|
|
19
|
+
factory: (ctx, deps) => {
|
|
20
|
+
const value = config.factory(ctx, deps);
|
|
21
|
+
if (isPromise(value)) return value.then((resolved) => validate(resolved, spec));
|
|
22
|
+
return validate(value, spec);
|
|
23
|
+
},
|
|
24
|
+
tags: [meta(spec), ...config.tags ?? []],
|
|
25
|
+
keepAlive: config.keepAlive ?? true
|
|
26
|
+
});
|
|
27
|
+
return (0, _pumped_fn_lite.atom)({
|
|
28
|
+
factory: (ctx) => {
|
|
29
|
+
const value = config.factory(ctx);
|
|
30
|
+
if (isPromise(value)) return value.then((resolved) => validate(resolved, spec));
|
|
31
|
+
return validate(value, spec);
|
|
32
|
+
},
|
|
33
|
+
tags: [meta(spec), ...config.tags ?? []],
|
|
34
|
+
keepAlive: config.keepAlive ?? true
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function extension(options) {
|
|
38
|
+
return {
|
|
39
|
+
name: options?.name ?? "sync",
|
|
40
|
+
async wrapResolve(run, event) {
|
|
41
|
+
if (event.kind !== "atom") return run();
|
|
42
|
+
const spec = meta.find(event.target);
|
|
43
|
+
if (!spec) return run();
|
|
44
|
+
const local = await run();
|
|
45
|
+
const current = active(event.scope);
|
|
46
|
+
if (!current) return local;
|
|
47
|
+
if (event.ctx.data.get(bound)) return local;
|
|
48
|
+
const key = scopedKey(current, spec.id);
|
|
49
|
+
const remote = await read(current, key);
|
|
50
|
+
const value = remote ? decode(current, spec, remote, local) : local;
|
|
51
|
+
const last = encode(current, spec, value, false);
|
|
52
|
+
if (last !== void 0) await bind(event.scope, event.target, event.ctx, current, spec, key, last);
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function memory() {
|
|
58
|
+
const values = /* @__PURE__ */ new Map();
|
|
59
|
+
const messages = [];
|
|
60
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
61
|
+
return {
|
|
62
|
+
read(key) {
|
|
63
|
+
return values.get(key);
|
|
64
|
+
},
|
|
65
|
+
write(message) {
|
|
66
|
+
values.set(message.key, message);
|
|
67
|
+
messages.push(message);
|
|
68
|
+
const found = listeners.get(message.key);
|
|
69
|
+
if (found) for (const listener of found) listener(message);
|
|
70
|
+
},
|
|
71
|
+
subscribe(key, listener) {
|
|
72
|
+
let found = listeners.get(key);
|
|
73
|
+
if (!found) {
|
|
74
|
+
found = /* @__PURE__ */ new Set();
|
|
75
|
+
listeners.set(key, found);
|
|
76
|
+
}
|
|
77
|
+
found.add(listener);
|
|
78
|
+
return () => {
|
|
79
|
+
found.delete(listener);
|
|
80
|
+
if (found.size === 0) listeners.delete(key);
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
records() {
|
|
84
|
+
return messages.slice();
|
|
85
|
+
},
|
|
86
|
+
clear() {
|
|
87
|
+
values.clear();
|
|
88
|
+
messages.length = 0;
|
|
89
|
+
},
|
|
90
|
+
size() {
|
|
91
|
+
return messages.length;
|
|
92
|
+
},
|
|
93
|
+
close() {
|
|
94
|
+
listeners.clear();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function codec(value) {
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
function revision(read) {
|
|
102
|
+
const readVersion = versionReader(read);
|
|
103
|
+
return { resolve(current, incoming) {
|
|
104
|
+
const currentVersion = readVersion(current);
|
|
105
|
+
const incomingVersion = readVersion(incoming);
|
|
106
|
+
if (incomingVersion > currentVersion) return incoming;
|
|
107
|
+
if (incomingVersion < currentVersion || Object.is(current, incoming)) return current;
|
|
108
|
+
return {
|
|
109
|
+
current,
|
|
110
|
+
incoming
|
|
111
|
+
};
|
|
112
|
+
} };
|
|
113
|
+
}
|
|
114
|
+
function lww(read) {
|
|
115
|
+
const readVersion = versionReader(read);
|
|
116
|
+
return { resolve(current, incoming) {
|
|
117
|
+
return readVersion(incoming) >= readVersion(current) ? incoming : current;
|
|
118
|
+
} };
|
|
119
|
+
}
|
|
120
|
+
const sync = Object.assign(create, {
|
|
121
|
+
runtime,
|
|
122
|
+
extension,
|
|
123
|
+
memory,
|
|
124
|
+
codec,
|
|
125
|
+
revision,
|
|
126
|
+
lww
|
|
127
|
+
});
|
|
128
|
+
function validate(value, spec) {
|
|
129
|
+
assertValue(spec.codec.encode(value));
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
function active(scope) {
|
|
133
|
+
const value = runtime.find(scope);
|
|
134
|
+
if (!value) return void 0;
|
|
135
|
+
return {
|
|
136
|
+
peer: value.peer,
|
|
137
|
+
transport: value.transport,
|
|
138
|
+
namespace: value.namespace,
|
|
139
|
+
failure: value.failure ?? "isolate",
|
|
140
|
+
onError: value.onError,
|
|
141
|
+
onConflict: value.onConflict
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async function read(current, key) {
|
|
145
|
+
try {
|
|
146
|
+
return await current.transport.read(key);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
handleError(current, error, "read", void 0);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function bind(scope, target, ctx, current, spec, key, last) {
|
|
153
|
+
const ctrl = scope.controller(target);
|
|
154
|
+
const state = {
|
|
155
|
+
last,
|
|
156
|
+
desired: void 0,
|
|
157
|
+
version: 0,
|
|
158
|
+
writing: false,
|
|
159
|
+
applying: false
|
|
160
|
+
};
|
|
161
|
+
const offRemote = await subscribe(current, key, (message) => {
|
|
162
|
+
if (message.peer === current.peer) return;
|
|
163
|
+
apply(current, spec, ctrl, state, message);
|
|
164
|
+
});
|
|
165
|
+
const offChange = ctrl.on("resolved", () => {
|
|
166
|
+
if (state.applying) return;
|
|
167
|
+
const encoded = encode(current, spec, ctrl.get());
|
|
168
|
+
if (encoded === void 0) return;
|
|
169
|
+
if (!state.writing && state.desired === void 0 && sameValue(encoded, state.last)) return;
|
|
170
|
+
state.desired = encoded;
|
|
171
|
+
flush(current, spec, ctrl, state, key);
|
|
172
|
+
});
|
|
173
|
+
const close = () => {
|
|
174
|
+
offRemote();
|
|
175
|
+
offChange();
|
|
176
|
+
};
|
|
177
|
+
ctx.cleanup(close);
|
|
178
|
+
ctx.data.set(bound, state);
|
|
179
|
+
}
|
|
180
|
+
async function flush(current, spec, ctrl, state, key) {
|
|
181
|
+
if (state.writing) return;
|
|
182
|
+
state.writing = true;
|
|
183
|
+
while (state.desired !== void 0) {
|
|
184
|
+
const encoded = state.desired;
|
|
185
|
+
if (sameValue(encoded, state.last)) {
|
|
186
|
+
state.desired = void 0;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
state.desired = void 0;
|
|
190
|
+
state.version += 1;
|
|
191
|
+
const version = state.version;
|
|
192
|
+
const ack = await write(current, {
|
|
193
|
+
key,
|
|
194
|
+
peer: current.peer,
|
|
195
|
+
version,
|
|
196
|
+
value: encoded
|
|
197
|
+
});
|
|
198
|
+
if (ack && isWriteConflict(ack)) {
|
|
199
|
+
apply(current, spec, ctrl, state, ack.conflict);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (!ack || state.version !== version) continue;
|
|
203
|
+
state.last = encoded;
|
|
204
|
+
state.version = Math.max(version, ack === true ? version : ack.version);
|
|
205
|
+
}
|
|
206
|
+
state.writing = false;
|
|
207
|
+
}
|
|
208
|
+
async function write(current, message) {
|
|
209
|
+
try {
|
|
210
|
+
return await current.transport.write(message) ?? true;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
handleError(current, error, "write", message, false);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function subscribe(current, key, listener) {
|
|
217
|
+
try {
|
|
218
|
+
return await current.transport.subscribe(key, (message) => {
|
|
219
|
+
try {
|
|
220
|
+
listener(message);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
handleError(current, error, "subscribe", void 0, false);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
} catch (error) {
|
|
226
|
+
handleError(current, error, "subscribe", void 0);
|
|
227
|
+
return () => {};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function apply(current, spec, ctrl, state, message) {
|
|
231
|
+
const incoming = decode(current, spec, message, void 0, false);
|
|
232
|
+
if (incoming === void 0) return;
|
|
233
|
+
const next = resolveConflict(current, spec, ctrl.get(), incoming, message, false);
|
|
234
|
+
if (next === void 0) return;
|
|
235
|
+
if (isConflict(next)) {
|
|
236
|
+
current.onConflict?.(next, message);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const encoded = encode(current, spec, next, false);
|
|
240
|
+
if (encoded === void 0 || sameValue(encoded, state.last)) return;
|
|
241
|
+
state.applying = true;
|
|
242
|
+
state.last = encoded;
|
|
243
|
+
state.version = Math.max(state.version, message.version);
|
|
244
|
+
ctrl.set(next);
|
|
245
|
+
state.applying = false;
|
|
246
|
+
}
|
|
247
|
+
function encode(current, spec, value, throwOnFailure = true) {
|
|
248
|
+
try {
|
|
249
|
+
return assertValue(spec.codec.encode(value));
|
|
250
|
+
} catch (error) {
|
|
251
|
+
handleError(current, error, "encode", void 0, throwOnFailure);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function decode(current, spec, message, fallback, throwOnFailure = true) {
|
|
256
|
+
try {
|
|
257
|
+
const wire = assertValue(message.value);
|
|
258
|
+
return spec.codec.decode(wire);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
handleError(current, error, "decode", message, throwOnFailure);
|
|
261
|
+
return fallback;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function resolveConflict(current, spec, local, incoming, message, throwOnFailure = true) {
|
|
265
|
+
if (!spec.conflict) return incoming;
|
|
266
|
+
try {
|
|
267
|
+
return spec.conflict.resolve(local, incoming);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
handleError(current, error, "conflict", message, throwOnFailure);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function handleError(current, error, phase, message, throwOnFailure = true) {
|
|
274
|
+
current.onError?.(error, phase, message);
|
|
275
|
+
if (throwOnFailure && current.failure === "throw") throw error;
|
|
276
|
+
}
|
|
277
|
+
function versionReader(read) {
|
|
278
|
+
if (typeof read === "function") {
|
|
279
|
+
const readVersion = read;
|
|
280
|
+
return (value) => assertVersion(readVersion(value));
|
|
281
|
+
}
|
|
282
|
+
return (value) => {
|
|
283
|
+
if (value === null || typeof value !== "object") throw new Error("Sync revision value must be an object");
|
|
284
|
+
const version = value[read];
|
|
285
|
+
if (typeof version !== "number") throw new Error("Sync revision field must be a number");
|
|
286
|
+
return assertVersion(version);
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function assertVersion(value) {
|
|
290
|
+
if (Number.isFinite(value)) return value;
|
|
291
|
+
throw new Error("Sync revision field must be finite");
|
|
292
|
+
}
|
|
293
|
+
function isConflict(value) {
|
|
294
|
+
return typeof value === "object" && value !== null && "current" in value && "incoming" in value;
|
|
295
|
+
}
|
|
296
|
+
function isWriteConflict(value) {
|
|
297
|
+
return typeof value === "object" && "conflict" in value;
|
|
298
|
+
}
|
|
299
|
+
function scopedKey(current, id) {
|
|
300
|
+
return current.namespace ? `${current.namespace}:${id}` : id;
|
|
301
|
+
}
|
|
302
|
+
function isPromise(value) {
|
|
303
|
+
return value != null && typeof value.then === "function";
|
|
304
|
+
}
|
|
305
|
+
function assertValue(value) {
|
|
306
|
+
if (value === null) return null;
|
|
307
|
+
switch (typeof value) {
|
|
308
|
+
case "string":
|
|
309
|
+
case "boolean": return value;
|
|
310
|
+
case "number":
|
|
311
|
+
if (Number.isFinite(value)) return value;
|
|
312
|
+
throw new Error("Sync value number must be finite");
|
|
313
|
+
case "object":
|
|
314
|
+
if (Array.isArray(value)) return value.map(assertValue);
|
|
315
|
+
if (Object.getPrototypeOf(value) !== Object.prototype) throw new Error("Sync value must be plain JSON");
|
|
316
|
+
return assertRecord(value);
|
|
317
|
+
default: throw new Error("Sync value must be plain JSON");
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function assertRecord(value) {
|
|
321
|
+
const result = {};
|
|
322
|
+
for (const key of Object.keys(value)) result[key] = assertValue(value[key]);
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
function sameValue(left, right) {
|
|
326
|
+
if (Object.is(left, right)) return true;
|
|
327
|
+
if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) return false;
|
|
328
|
+
if (Array.isArray(left) || Array.isArray(right)) return sameArray(left, right);
|
|
329
|
+
const leftKeys = Object.keys(left);
|
|
330
|
+
const rightKeys = Object.keys(right);
|
|
331
|
+
if (leftKeys.length !== rightKeys.length) return false;
|
|
332
|
+
const leftRecord = left;
|
|
333
|
+
const rightRecord = right;
|
|
334
|
+
for (const key of leftKeys) {
|
|
335
|
+
if (!Object.hasOwn(rightRecord, key)) return false;
|
|
336
|
+
if (!sameValue(leftRecord[key], rightRecord[key])) return false;
|
|
337
|
+
}
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
function sameArray(left, right) {
|
|
341
|
+
if (!Array.isArray(left) || !Array.isArray(right)) return false;
|
|
342
|
+
if (left.length !== right.length) return false;
|
|
343
|
+
for (let i = 0; i < left.length; i++) if (!sameValue(left[i], right[i])) return false;
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
//#endregion
|
|
347
|
+
exports.sync = sync;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Lite } from "@pumped-fn/lite";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
5
|
+
declare namespace Sync {
|
|
6
|
+
type Value = Lite.JsonValue;
|
|
7
|
+
type Failure = "isolate" | "throw";
|
|
8
|
+
type ErrorPhase = "read" | "write" | "subscribe" | "decode" | "encode" | "conflict";
|
|
9
|
+
interface Codec<T, Wire extends Value> {
|
|
10
|
+
encode(value: T): Wire;
|
|
11
|
+
decode(raw: Wire): T;
|
|
12
|
+
}
|
|
13
|
+
interface Conflict<T> {
|
|
14
|
+
readonly current: T;
|
|
15
|
+
readonly incoming: T;
|
|
16
|
+
}
|
|
17
|
+
interface Policy<T> {
|
|
18
|
+
resolve(current: T, incoming: T): T | Conflict<T>;
|
|
19
|
+
}
|
|
20
|
+
interface Message {
|
|
21
|
+
readonly key: string;
|
|
22
|
+
readonly peer: string;
|
|
23
|
+
readonly version: number;
|
|
24
|
+
readonly value: Value;
|
|
25
|
+
}
|
|
26
|
+
interface WriteAck {
|
|
27
|
+
readonly version: number;
|
|
28
|
+
}
|
|
29
|
+
interface WriteConflict {
|
|
30
|
+
readonly conflict: Message;
|
|
31
|
+
}
|
|
32
|
+
type WriteResult = WriteAck | WriteConflict | void;
|
|
33
|
+
interface Transport {
|
|
34
|
+
read(key: string): MaybePromise<Message | undefined>;
|
|
35
|
+
write(message: Message): MaybePromise<WriteResult>;
|
|
36
|
+
subscribe(key: string, listener: (message: Message) => void): MaybePromise<() => void>;
|
|
37
|
+
close?(): MaybePromise<void>;
|
|
38
|
+
}
|
|
39
|
+
interface Runtime {
|
|
40
|
+
readonly peer: string;
|
|
41
|
+
readonly transport: Transport;
|
|
42
|
+
readonly namespace?: string;
|
|
43
|
+
readonly failure?: Failure;
|
|
44
|
+
readonly onError?: (error: unknown, phase: ErrorPhase, message: Message | undefined) => void;
|
|
45
|
+
readonly onConflict?: (conflict: Conflict<unknown>, message: Message) => void;
|
|
46
|
+
}
|
|
47
|
+
interface Options {
|
|
48
|
+
readonly name?: string;
|
|
49
|
+
}
|
|
50
|
+
interface Base<T, D extends Record<string, Lite.AtomDependency> | undefined> {
|
|
51
|
+
readonly id: string;
|
|
52
|
+
readonly deps?: D;
|
|
53
|
+
readonly factory: D extends Record<string, Lite.AtomDependency> ? (ctx: Lite.ResolveContext, deps: Lite.InferDeps<D>) => MaybePromise<T> : (ctx: Lite.ResolveContext) => MaybePromise<T>;
|
|
54
|
+
readonly tags?: Lite.Tagged<any>[];
|
|
55
|
+
readonly keepAlive?: boolean;
|
|
56
|
+
readonly conflict?: Policy<NoInfer<T>>;
|
|
57
|
+
}
|
|
58
|
+
type Json<T extends Value, D extends Record<string, Lite.AtomDependency> | undefined> = Base<T, D>;
|
|
59
|
+
type Encoded<T, Wire extends Value, D extends Record<string, Lite.AtomDependency> | undefined> = Base<T, D> & {
|
|
60
|
+
readonly codec: Codec<T, Wire>;
|
|
61
|
+
};
|
|
62
|
+
interface Memory extends Transport {
|
|
63
|
+
records(): readonly Message[];
|
|
64
|
+
clear(): void;
|
|
65
|
+
size(): number;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
declare function create<T extends Sync.Value>(config: Sync.Json<T, undefined>): Lite.Atom<T>;
|
|
69
|
+
declare function create<T extends Sync.Value, const D extends Record<string, Lite.AtomDependency>>(config: Sync.Json<T, D>): Lite.Atom<T>;
|
|
70
|
+
declare function create<T, const Wire extends Sync.Value>(config: Sync.Encoded<T, Wire, undefined>): Lite.Atom<T>;
|
|
71
|
+
declare function create<T, const Wire extends Sync.Value, const D extends Record<string, Lite.AtomDependency>>(config: Sync.Encoded<T, Wire, D>): Lite.Atom<T>;
|
|
72
|
+
declare function extension(options?: Sync.Options): Lite.Extension;
|
|
73
|
+
declare function memory(): Sync.Memory;
|
|
74
|
+
declare function codec<T, const Wire extends Sync.Value>(value: Sync.Codec<T, Wire>): Sync.Codec<T, Wire>;
|
|
75
|
+
declare function revision<const K extends string>(read: K): {
|
|
76
|
+
resolve<T extends Record<K, number>>(current: T, incoming: T): T | Sync.Conflict<T>;
|
|
77
|
+
};
|
|
78
|
+
declare function revision<T>(read: (value: T) => number): Sync.Policy<T>;
|
|
79
|
+
declare function lww<const K extends string>(read: K): {
|
|
80
|
+
resolve<T extends Record<K, number>>(current: T, incoming: T): T | Sync.Conflict<T>;
|
|
81
|
+
};
|
|
82
|
+
declare function lww<T>(read: (value: T) => number): Sync.Policy<T>;
|
|
83
|
+
declare const sync: typeof create & {
|
|
84
|
+
runtime: Lite.Tag<Sync.Runtime, false>;
|
|
85
|
+
extension: typeof extension;
|
|
86
|
+
memory: typeof memory;
|
|
87
|
+
codec: typeof codec;
|
|
88
|
+
revision: typeof revision;
|
|
89
|
+
lww: typeof lww;
|
|
90
|
+
};
|
|
91
|
+
//#endregion
|
|
92
|
+
export { Sync, sync };
|
|
93
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":"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"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Lite } from "@pumped-fn/lite";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
5
|
+
declare namespace Sync {
|
|
6
|
+
type Value = Lite.JsonValue;
|
|
7
|
+
type Failure = "isolate" | "throw";
|
|
8
|
+
type ErrorPhase = "read" | "write" | "subscribe" | "decode" | "encode" | "conflict";
|
|
9
|
+
interface Codec<T, Wire extends Value> {
|
|
10
|
+
encode(value: T): Wire;
|
|
11
|
+
decode(raw: Wire): T;
|
|
12
|
+
}
|
|
13
|
+
interface Conflict<T> {
|
|
14
|
+
readonly current: T;
|
|
15
|
+
readonly incoming: T;
|
|
16
|
+
}
|
|
17
|
+
interface Policy<T> {
|
|
18
|
+
resolve(current: T, incoming: T): T | Conflict<T>;
|
|
19
|
+
}
|
|
20
|
+
interface Message {
|
|
21
|
+
readonly key: string;
|
|
22
|
+
readonly peer: string;
|
|
23
|
+
readonly version: number;
|
|
24
|
+
readonly value: Value;
|
|
25
|
+
}
|
|
26
|
+
interface WriteAck {
|
|
27
|
+
readonly version: number;
|
|
28
|
+
}
|
|
29
|
+
interface WriteConflict {
|
|
30
|
+
readonly conflict: Message;
|
|
31
|
+
}
|
|
32
|
+
type WriteResult = WriteAck | WriteConflict | void;
|
|
33
|
+
interface Transport {
|
|
34
|
+
read(key: string): MaybePromise<Message | undefined>;
|
|
35
|
+
write(message: Message): MaybePromise<WriteResult>;
|
|
36
|
+
subscribe(key: string, listener: (message: Message) => void): MaybePromise<() => void>;
|
|
37
|
+
close?(): MaybePromise<void>;
|
|
38
|
+
}
|
|
39
|
+
interface Runtime {
|
|
40
|
+
readonly peer: string;
|
|
41
|
+
readonly transport: Transport;
|
|
42
|
+
readonly namespace?: string;
|
|
43
|
+
readonly failure?: Failure;
|
|
44
|
+
readonly onError?: (error: unknown, phase: ErrorPhase, message: Message | undefined) => void;
|
|
45
|
+
readonly onConflict?: (conflict: Conflict<unknown>, message: Message) => void;
|
|
46
|
+
}
|
|
47
|
+
interface Options {
|
|
48
|
+
readonly name?: string;
|
|
49
|
+
}
|
|
50
|
+
interface Base<T, D extends Record<string, Lite.AtomDependency> | undefined> {
|
|
51
|
+
readonly id: string;
|
|
52
|
+
readonly deps?: D;
|
|
53
|
+
readonly factory: D extends Record<string, Lite.AtomDependency> ? (ctx: Lite.ResolveContext, deps: Lite.InferDeps<D>) => MaybePromise<T> : (ctx: Lite.ResolveContext) => MaybePromise<T>;
|
|
54
|
+
readonly tags?: Lite.Tagged<any>[];
|
|
55
|
+
readonly keepAlive?: boolean;
|
|
56
|
+
readonly conflict?: Policy<NoInfer<T>>;
|
|
57
|
+
}
|
|
58
|
+
type Json<T extends Value, D extends Record<string, Lite.AtomDependency> | undefined> = Base<T, D>;
|
|
59
|
+
type Encoded<T, Wire extends Value, D extends Record<string, Lite.AtomDependency> | undefined> = Base<T, D> & {
|
|
60
|
+
readonly codec: Codec<T, Wire>;
|
|
61
|
+
};
|
|
62
|
+
interface Memory extends Transport {
|
|
63
|
+
records(): readonly Message[];
|
|
64
|
+
clear(): void;
|
|
65
|
+
size(): number;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
declare function create<T extends Sync.Value>(config: Sync.Json<T, undefined>): Lite.Atom<T>;
|
|
69
|
+
declare function create<T extends Sync.Value, const D extends Record<string, Lite.AtomDependency>>(config: Sync.Json<T, D>): Lite.Atom<T>;
|
|
70
|
+
declare function create<T, const Wire extends Sync.Value>(config: Sync.Encoded<T, Wire, undefined>): Lite.Atom<T>;
|
|
71
|
+
declare function create<T, const Wire extends Sync.Value, const D extends Record<string, Lite.AtomDependency>>(config: Sync.Encoded<T, Wire, D>): Lite.Atom<T>;
|
|
72
|
+
declare function extension(options?: Sync.Options): Lite.Extension;
|
|
73
|
+
declare function memory(): Sync.Memory;
|
|
74
|
+
declare function codec<T, const Wire extends Sync.Value>(value: Sync.Codec<T, Wire>): Sync.Codec<T, Wire>;
|
|
75
|
+
declare function revision<const K extends string>(read: K): {
|
|
76
|
+
resolve<T extends Record<K, number>>(current: T, incoming: T): T | Sync.Conflict<T>;
|
|
77
|
+
};
|
|
78
|
+
declare function revision<T>(read: (value: T) => number): Sync.Policy<T>;
|
|
79
|
+
declare function lww<const K extends string>(read: K): {
|
|
80
|
+
resolve<T extends Record<K, number>>(current: T, incoming: T): T | Sync.Conflict<T>;
|
|
81
|
+
};
|
|
82
|
+
declare function lww<T>(read: (value: T) => number): Sync.Policy<T>;
|
|
83
|
+
declare const sync: typeof create & {
|
|
84
|
+
runtime: Lite.Tag<Sync.Runtime, false>;
|
|
85
|
+
extension: typeof extension;
|
|
86
|
+
memory: typeof memory;
|
|
87
|
+
codec: typeof codec;
|
|
88
|
+
revision: typeof revision;
|
|
89
|
+
lww: typeof lww;
|
|
90
|
+
};
|
|
91
|
+
//#endregion
|
|
92
|
+
export { Sync, sync };
|
|
93
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":"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"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { atom, tag } from "@pumped-fn/lite";
|
|
2
|
+
//#region src/index.ts
|
|
3
|
+
const meta = tag({ label: "sync.meta" });
|
|
4
|
+
const runtime = tag({ label: "sync.runtime" });
|
|
5
|
+
const bound = Symbol("@pumped-fn/lite-extension-sync/bound");
|
|
6
|
+
const json = {
|
|
7
|
+
encode: assertValue,
|
|
8
|
+
decode: assertValue
|
|
9
|
+
};
|
|
10
|
+
function create(config) {
|
|
11
|
+
const spec = {
|
|
12
|
+
id: config.id,
|
|
13
|
+
codec: config.codec ?? json,
|
|
14
|
+
conflict: config.conflict
|
|
15
|
+
};
|
|
16
|
+
if (config.deps) return atom({
|
|
17
|
+
deps: config.deps,
|
|
18
|
+
factory: (ctx, deps) => {
|
|
19
|
+
const value = config.factory(ctx, deps);
|
|
20
|
+
if (isPromise(value)) return value.then((resolved) => validate(resolved, spec));
|
|
21
|
+
return validate(value, spec);
|
|
22
|
+
},
|
|
23
|
+
tags: [meta(spec), ...config.tags ?? []],
|
|
24
|
+
keepAlive: config.keepAlive ?? true
|
|
25
|
+
});
|
|
26
|
+
return atom({
|
|
27
|
+
factory: (ctx) => {
|
|
28
|
+
const value = config.factory(ctx);
|
|
29
|
+
if (isPromise(value)) return value.then((resolved) => validate(resolved, spec));
|
|
30
|
+
return validate(value, spec);
|
|
31
|
+
},
|
|
32
|
+
tags: [meta(spec), ...config.tags ?? []],
|
|
33
|
+
keepAlive: config.keepAlive ?? true
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function extension(options) {
|
|
37
|
+
return {
|
|
38
|
+
name: options?.name ?? "sync",
|
|
39
|
+
async wrapResolve(run, event) {
|
|
40
|
+
if (event.kind !== "atom") return run();
|
|
41
|
+
const spec = meta.find(event.target);
|
|
42
|
+
if (!spec) return run();
|
|
43
|
+
const local = await run();
|
|
44
|
+
const current = active(event.scope);
|
|
45
|
+
if (!current) return local;
|
|
46
|
+
if (event.ctx.data.get(bound)) return local;
|
|
47
|
+
const key = scopedKey(current, spec.id);
|
|
48
|
+
const remote = await read(current, key);
|
|
49
|
+
const value = remote ? decode(current, spec, remote, local) : local;
|
|
50
|
+
const last = encode(current, spec, value, false);
|
|
51
|
+
if (last !== void 0) await bind(event.scope, event.target, event.ctx, current, spec, key, last);
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function memory() {
|
|
57
|
+
const values = /* @__PURE__ */ new Map();
|
|
58
|
+
const messages = [];
|
|
59
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
60
|
+
return {
|
|
61
|
+
read(key) {
|
|
62
|
+
return values.get(key);
|
|
63
|
+
},
|
|
64
|
+
write(message) {
|
|
65
|
+
values.set(message.key, message);
|
|
66
|
+
messages.push(message);
|
|
67
|
+
const found = listeners.get(message.key);
|
|
68
|
+
if (found) for (const listener of found) listener(message);
|
|
69
|
+
},
|
|
70
|
+
subscribe(key, listener) {
|
|
71
|
+
let found = listeners.get(key);
|
|
72
|
+
if (!found) {
|
|
73
|
+
found = /* @__PURE__ */ new Set();
|
|
74
|
+
listeners.set(key, found);
|
|
75
|
+
}
|
|
76
|
+
found.add(listener);
|
|
77
|
+
return () => {
|
|
78
|
+
found.delete(listener);
|
|
79
|
+
if (found.size === 0) listeners.delete(key);
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
records() {
|
|
83
|
+
return messages.slice();
|
|
84
|
+
},
|
|
85
|
+
clear() {
|
|
86
|
+
values.clear();
|
|
87
|
+
messages.length = 0;
|
|
88
|
+
},
|
|
89
|
+
size() {
|
|
90
|
+
return messages.length;
|
|
91
|
+
},
|
|
92
|
+
close() {
|
|
93
|
+
listeners.clear();
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function codec(value) {
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
function revision(read) {
|
|
101
|
+
const readVersion = versionReader(read);
|
|
102
|
+
return { resolve(current, incoming) {
|
|
103
|
+
const currentVersion = readVersion(current);
|
|
104
|
+
const incomingVersion = readVersion(incoming);
|
|
105
|
+
if (incomingVersion > currentVersion) return incoming;
|
|
106
|
+
if (incomingVersion < currentVersion || Object.is(current, incoming)) return current;
|
|
107
|
+
return {
|
|
108
|
+
current,
|
|
109
|
+
incoming
|
|
110
|
+
};
|
|
111
|
+
} };
|
|
112
|
+
}
|
|
113
|
+
function lww(read) {
|
|
114
|
+
const readVersion = versionReader(read);
|
|
115
|
+
return { resolve(current, incoming) {
|
|
116
|
+
return readVersion(incoming) >= readVersion(current) ? incoming : current;
|
|
117
|
+
} };
|
|
118
|
+
}
|
|
119
|
+
const sync = Object.assign(create, {
|
|
120
|
+
runtime,
|
|
121
|
+
extension,
|
|
122
|
+
memory,
|
|
123
|
+
codec,
|
|
124
|
+
revision,
|
|
125
|
+
lww
|
|
126
|
+
});
|
|
127
|
+
function validate(value, spec) {
|
|
128
|
+
assertValue(spec.codec.encode(value));
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
function active(scope) {
|
|
132
|
+
const value = runtime.find(scope);
|
|
133
|
+
if (!value) return void 0;
|
|
134
|
+
return {
|
|
135
|
+
peer: value.peer,
|
|
136
|
+
transport: value.transport,
|
|
137
|
+
namespace: value.namespace,
|
|
138
|
+
failure: value.failure ?? "isolate",
|
|
139
|
+
onError: value.onError,
|
|
140
|
+
onConflict: value.onConflict
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async function read(current, key) {
|
|
144
|
+
try {
|
|
145
|
+
return await current.transport.read(key);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
handleError(current, error, "read", void 0);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function bind(scope, target, ctx, current, spec, key, last) {
|
|
152
|
+
const ctrl = scope.controller(target);
|
|
153
|
+
const state = {
|
|
154
|
+
last,
|
|
155
|
+
desired: void 0,
|
|
156
|
+
version: 0,
|
|
157
|
+
writing: false,
|
|
158
|
+
applying: false
|
|
159
|
+
};
|
|
160
|
+
const offRemote = await subscribe(current, key, (message) => {
|
|
161
|
+
if (message.peer === current.peer) return;
|
|
162
|
+
apply(current, spec, ctrl, state, message);
|
|
163
|
+
});
|
|
164
|
+
const offChange = ctrl.on("resolved", () => {
|
|
165
|
+
if (state.applying) return;
|
|
166
|
+
const encoded = encode(current, spec, ctrl.get());
|
|
167
|
+
if (encoded === void 0) return;
|
|
168
|
+
if (!state.writing && state.desired === void 0 && sameValue(encoded, state.last)) return;
|
|
169
|
+
state.desired = encoded;
|
|
170
|
+
flush(current, spec, ctrl, state, key);
|
|
171
|
+
});
|
|
172
|
+
const close = () => {
|
|
173
|
+
offRemote();
|
|
174
|
+
offChange();
|
|
175
|
+
};
|
|
176
|
+
ctx.cleanup(close);
|
|
177
|
+
ctx.data.set(bound, state);
|
|
178
|
+
}
|
|
179
|
+
async function flush(current, spec, ctrl, state, key) {
|
|
180
|
+
if (state.writing) return;
|
|
181
|
+
state.writing = true;
|
|
182
|
+
while (state.desired !== void 0) {
|
|
183
|
+
const encoded = state.desired;
|
|
184
|
+
if (sameValue(encoded, state.last)) {
|
|
185
|
+
state.desired = void 0;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
state.desired = void 0;
|
|
189
|
+
state.version += 1;
|
|
190
|
+
const version = state.version;
|
|
191
|
+
const ack = await write(current, {
|
|
192
|
+
key,
|
|
193
|
+
peer: current.peer,
|
|
194
|
+
version,
|
|
195
|
+
value: encoded
|
|
196
|
+
});
|
|
197
|
+
if (ack && isWriteConflict(ack)) {
|
|
198
|
+
apply(current, spec, ctrl, state, ack.conflict);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (!ack || state.version !== version) continue;
|
|
202
|
+
state.last = encoded;
|
|
203
|
+
state.version = Math.max(version, ack === true ? version : ack.version);
|
|
204
|
+
}
|
|
205
|
+
state.writing = false;
|
|
206
|
+
}
|
|
207
|
+
async function write(current, message) {
|
|
208
|
+
try {
|
|
209
|
+
return await current.transport.write(message) ?? true;
|
|
210
|
+
} catch (error) {
|
|
211
|
+
handleError(current, error, "write", message, false);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async function subscribe(current, key, listener) {
|
|
216
|
+
try {
|
|
217
|
+
return await current.transport.subscribe(key, (message) => {
|
|
218
|
+
try {
|
|
219
|
+
listener(message);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
handleError(current, error, "subscribe", void 0, false);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
} catch (error) {
|
|
225
|
+
handleError(current, error, "subscribe", void 0);
|
|
226
|
+
return () => {};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function apply(current, spec, ctrl, state, message) {
|
|
230
|
+
const incoming = decode(current, spec, message, void 0, false);
|
|
231
|
+
if (incoming === void 0) return;
|
|
232
|
+
const next = resolveConflict(current, spec, ctrl.get(), incoming, message, false);
|
|
233
|
+
if (next === void 0) return;
|
|
234
|
+
if (isConflict(next)) {
|
|
235
|
+
current.onConflict?.(next, message);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const encoded = encode(current, spec, next, false);
|
|
239
|
+
if (encoded === void 0 || sameValue(encoded, state.last)) return;
|
|
240
|
+
state.applying = true;
|
|
241
|
+
state.last = encoded;
|
|
242
|
+
state.version = Math.max(state.version, message.version);
|
|
243
|
+
ctrl.set(next);
|
|
244
|
+
state.applying = false;
|
|
245
|
+
}
|
|
246
|
+
function encode(current, spec, value, throwOnFailure = true) {
|
|
247
|
+
try {
|
|
248
|
+
return assertValue(spec.codec.encode(value));
|
|
249
|
+
} catch (error) {
|
|
250
|
+
handleError(current, error, "encode", void 0, throwOnFailure);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function decode(current, spec, message, fallback, throwOnFailure = true) {
|
|
255
|
+
try {
|
|
256
|
+
const wire = assertValue(message.value);
|
|
257
|
+
return spec.codec.decode(wire);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
handleError(current, error, "decode", message, throwOnFailure);
|
|
260
|
+
return fallback;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function resolveConflict(current, spec, local, incoming, message, throwOnFailure = true) {
|
|
264
|
+
if (!spec.conflict) return incoming;
|
|
265
|
+
try {
|
|
266
|
+
return spec.conflict.resolve(local, incoming);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
handleError(current, error, "conflict", message, throwOnFailure);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function handleError(current, error, phase, message, throwOnFailure = true) {
|
|
273
|
+
current.onError?.(error, phase, message);
|
|
274
|
+
if (throwOnFailure && current.failure === "throw") throw error;
|
|
275
|
+
}
|
|
276
|
+
function versionReader(read) {
|
|
277
|
+
if (typeof read === "function") {
|
|
278
|
+
const readVersion = read;
|
|
279
|
+
return (value) => assertVersion(readVersion(value));
|
|
280
|
+
}
|
|
281
|
+
return (value) => {
|
|
282
|
+
if (value === null || typeof value !== "object") throw new Error("Sync revision value must be an object");
|
|
283
|
+
const version = value[read];
|
|
284
|
+
if (typeof version !== "number") throw new Error("Sync revision field must be a number");
|
|
285
|
+
return assertVersion(version);
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function assertVersion(value) {
|
|
289
|
+
if (Number.isFinite(value)) return value;
|
|
290
|
+
throw new Error("Sync revision field must be finite");
|
|
291
|
+
}
|
|
292
|
+
function isConflict(value) {
|
|
293
|
+
return typeof value === "object" && value !== null && "current" in value && "incoming" in value;
|
|
294
|
+
}
|
|
295
|
+
function isWriteConflict(value) {
|
|
296
|
+
return typeof value === "object" && "conflict" in value;
|
|
297
|
+
}
|
|
298
|
+
function scopedKey(current, id) {
|
|
299
|
+
return current.namespace ? `${current.namespace}:${id}` : id;
|
|
300
|
+
}
|
|
301
|
+
function isPromise(value) {
|
|
302
|
+
return value != null && typeof value.then === "function";
|
|
303
|
+
}
|
|
304
|
+
function assertValue(value) {
|
|
305
|
+
if (value === null) return null;
|
|
306
|
+
switch (typeof value) {
|
|
307
|
+
case "string":
|
|
308
|
+
case "boolean": return value;
|
|
309
|
+
case "number":
|
|
310
|
+
if (Number.isFinite(value)) return value;
|
|
311
|
+
throw new Error("Sync value number must be finite");
|
|
312
|
+
case "object":
|
|
313
|
+
if (Array.isArray(value)) return value.map(assertValue);
|
|
314
|
+
if (Object.getPrototypeOf(value) !== Object.prototype) throw new Error("Sync value must be plain JSON");
|
|
315
|
+
return assertRecord(value);
|
|
316
|
+
default: throw new Error("Sync value must be plain JSON");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function assertRecord(value) {
|
|
320
|
+
const result = {};
|
|
321
|
+
for (const key of Object.keys(value)) result[key] = assertValue(value[key]);
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
function sameValue(left, right) {
|
|
325
|
+
if (Object.is(left, right)) return true;
|
|
326
|
+
if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) return false;
|
|
327
|
+
if (Array.isArray(left) || Array.isArray(right)) return sameArray(left, right);
|
|
328
|
+
const leftKeys = Object.keys(left);
|
|
329
|
+
const rightKeys = Object.keys(right);
|
|
330
|
+
if (leftKeys.length !== rightKeys.length) return false;
|
|
331
|
+
const leftRecord = left;
|
|
332
|
+
const rightRecord = right;
|
|
333
|
+
for (const key of leftKeys) {
|
|
334
|
+
if (!Object.hasOwn(rightRecord, key)) return false;
|
|
335
|
+
if (!sameValue(leftRecord[key], rightRecord[key])) return false;
|
|
336
|
+
}
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
function sameArray(left, right) {
|
|
340
|
+
if (!Array.isArray(left) || !Array.isArray(right)) return false;
|
|
341
|
+
if (left.length !== right.length) return false;
|
|
342
|
+
for (let i = 0; i < left.length; i++) if (!sameValue(left[i], right[i])) return false;
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
//#endregion
|
|
346
|
+
export { sync };
|
|
347
|
+
|
|
348
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { atom, tag, type Lite } from \"@pumped-fn/lite\"\n\ntype MaybePromise<T> = T | Promise<T>\n\nexport namespace Sync {\n export type Value = Lite.JsonValue\n export type Failure = \"isolate\" | \"throw\"\n export type ErrorPhase = \"read\" | \"write\" | \"subscribe\" | \"decode\" | \"encode\" | \"conflict\"\n\n export interface Codec<T, Wire extends Value> {\n encode(value: T): Wire\n decode(raw: Wire): T\n }\n\n export interface Conflict<T> {\n readonly current: T\n readonly incoming: T\n }\n\n export interface Policy<T> {\n resolve(current: T, incoming: T): T | Conflict<T>\n }\n\n export interface Message {\n readonly key: string\n readonly peer: string\n readonly version: number\n readonly value: Value\n }\n\n export interface WriteAck {\n readonly version: number\n }\n\n export interface WriteConflict {\n readonly conflict: Message\n }\n\n export type WriteResult = WriteAck | WriteConflict | void\n\n export interface Transport {\n read(key: string): MaybePromise<Message | undefined>\n write(message: Message): MaybePromise<WriteResult>\n subscribe(key: string, listener: (message: Message) => void): MaybePromise<() => void>\n close?(): MaybePromise<void>\n }\n\n export interface Runtime {\n readonly peer: string\n readonly transport: Transport\n readonly namespace?: string\n readonly failure?: Failure\n readonly onError?: (error: unknown, phase: ErrorPhase, message: Message | undefined) => void\n readonly onConflict?: (conflict: Conflict<unknown>, message: Message) => void\n }\n\n export interface Options {\n readonly name?: string\n }\n\n export interface Base<T, D extends Record<string, Lite.AtomDependency> | undefined> {\n readonly id: string\n readonly deps?: D\n readonly factory: D extends Record<string, Lite.AtomDependency>\n ? (ctx: Lite.ResolveContext, deps: Lite.InferDeps<D>) => MaybePromise<T>\n : (ctx: Lite.ResolveContext) => MaybePromise<T>\n readonly tags?: Lite.Tagged<any>[]\n readonly keepAlive?: boolean\n readonly conflict?: Policy<NoInfer<T>>\n }\n\n export type Json<T extends Value, D extends Record<string, Lite.AtomDependency> | undefined> = Base<T, D>\n\n export type Encoded<\n T,\n Wire extends Value,\n D extends Record<string, Lite.AtomDependency> | undefined,\n > = Base<T, D> & {\n readonly codec: Codec<T, Wire>\n }\n\n export interface Memory extends Transport {\n records(): readonly Message[]\n clear(): void\n size(): number\n }\n}\n\ninterface Active {\n readonly peer: string\n readonly transport: Sync.Transport\n readonly namespace: string | undefined\n readonly failure: Sync.Failure\n readonly onError: ((error: unknown, phase: Sync.ErrorPhase, message: Sync.Message | undefined) => void) | undefined\n readonly onConflict: ((conflict: Sync.Conflict<unknown>, message: Sync.Message) => void) | undefined\n}\n\ninterface Spec<T, Wire extends Sync.Value> {\n readonly id: string\n readonly codec: Sync.Codec<T, Wire>\n readonly conflict: Sync.Policy<T> | undefined\n}\n\ninterface Bound<T> {\n last: Sync.Value\n desired: Sync.Value | undefined\n version: number\n writing: boolean\n applying: boolean\n}\n\nconst meta = tag<Spec<any, Sync.Value>>({ label: \"sync.meta\" })\nconst runtime = tag<Sync.Runtime>({ label: \"sync.runtime\" })\nconst bound = Symbol(\"@pumped-fn/lite-extension-sync/bound\")\n\nconst json: Sync.Codec<Sync.Value, Sync.Value> = {\n encode: assertValue,\n decode: assertValue,\n}\n\nfunction create<T extends Sync.Value>(config: Sync.Json<T, undefined>): Lite.Atom<T>\nfunction create<T extends Sync.Value, const D extends Record<string, Lite.AtomDependency>>(config: Sync.Json<T, D>): Lite.Atom<T>\nfunction create<T, const Wire extends Sync.Value>(config: Sync.Encoded<T, Wire, undefined>): Lite.Atom<T>\nfunction create<T, const Wire extends Sync.Value, const D extends Record<string, Lite.AtomDependency>>(config: Sync.Encoded<T, Wire, D>): Lite.Atom<T>\nfunction create(config: Sync.Base<unknown, Record<string, Lite.AtomDependency> | undefined> & { readonly codec?: Sync.Codec<unknown, Sync.Value> }): Lite.Atom<unknown> {\n const spec = {\n id: config.id,\n codec: config.codec ?? json,\n conflict: config.conflict,\n }\n\n if (config.deps) {\n return atom({\n deps: config.deps,\n factory: (ctx: Lite.ResolveContext, deps: Lite.InferDeps<Record<string, Lite.AtomDependency>>) => {\n const value = config.factory(ctx, deps)\n if (isPromise(value)) return value.then((resolved) => validate(resolved, spec))\n return validate(value, spec)\n },\n tags: [meta(spec), ...(config.tags ?? [])],\n keepAlive: config.keepAlive ?? true,\n })\n }\n\n return atom({\n factory: (ctx: Lite.ResolveContext) => {\n const value = (config.factory as (ctx: Lite.ResolveContext) => MaybePromise<unknown>)(ctx)\n if (isPromise(value)) return value.then((resolved) => validate(resolved, spec))\n return validate(value, spec)\n },\n tags: [meta(spec), ...(config.tags ?? [])],\n keepAlive: config.keepAlive ?? true,\n })\n}\n\nfunction extension(options?: Sync.Options): Lite.Extension {\n return {\n name: options?.name ?? \"sync\",\n async wrapResolve(run, event) {\n if (event.kind !== \"atom\") return run()\n const spec = meta.find(event.target)\n if (!spec) return run()\n\n const local = await run()\n const current = active(event.scope)\n if (!current) return local\n if (event.ctx.data.get(bound)) return local\n\n const key = scopedKey(current, spec.id)\n const remote = await read(current, key)\n const value = remote ? decode(current, spec, remote, local) : local\n const last = encode(current, spec, value, false)\n if (last !== undefined) await bind(event.scope, event.target, event.ctx, current, spec, key, last)\n return value\n },\n }\n}\n\nfunction memory(): Sync.Memory {\n const values = new Map<string, Sync.Message>()\n const messages: Sync.Message[] = []\n const listeners = new Map<string, Set<(message: Sync.Message) => void>>()\n\n return {\n read(key) {\n return values.get(key)\n },\n write(message) {\n values.set(message.key, message)\n messages.push(message)\n const found = listeners.get(message.key)\n if (found) {\n for (const listener of found) listener(message)\n }\n },\n subscribe(key, listener) {\n let found = listeners.get(key)\n if (!found) {\n found = new Set()\n listeners.set(key, found)\n }\n found.add(listener)\n return () => {\n found.delete(listener)\n if (found.size === 0) listeners.delete(key)\n }\n },\n records() {\n return messages.slice()\n },\n clear() {\n values.clear()\n messages.length = 0\n },\n size() {\n return messages.length\n },\n close() {\n listeners.clear()\n },\n }\n}\n\nfunction codec<T, const Wire extends Sync.Value>(value: Sync.Codec<T, Wire>): Sync.Codec<T, Wire> {\n return value\n}\n\nfunction revision<const K extends string>(read: K): {\n resolve<T extends Record<K, number>>(current: T, incoming: T): T | Sync.Conflict<T>\n}\nfunction revision<T>(read: (value: T) => number): Sync.Policy<T>\nfunction revision(read: string | ((value: never) => number)) {\n const readVersion = versionReader(read)\n return {\n resolve(current: unknown, incoming: unknown) {\n const currentVersion = readVersion(current)\n const incomingVersion = readVersion(incoming)\n if (incomingVersion > currentVersion) return incoming\n if (incomingVersion < currentVersion || Object.is(current, incoming)) return current\n return { current, incoming }\n },\n }\n}\n\nfunction lww<const K extends string>(read: K): {\n resolve<T extends Record<K, number>>(current: T, incoming: T): T | Sync.Conflict<T>\n}\nfunction lww<T>(read: (value: T) => number): Sync.Policy<T>\nfunction lww(read: string | ((value: never) => number)) {\n const readVersion = versionReader(read)\n return {\n resolve(current: unknown, incoming: unknown) {\n return readVersion(incoming) >= readVersion(current) ? incoming : current\n },\n }\n}\n\nexport const sync = Object.assign(create, {\n runtime,\n extension,\n memory,\n codec,\n revision,\n lww,\n})\n\nfunction validate<T>(value: T, spec: Spec<T, Sync.Value>): T {\n assertValue(spec.codec.encode(value))\n return value\n}\n\nfunction active(scope: Lite.Scope): Active | undefined {\n const value = runtime.find(scope as unknown as Lite.TagSource)\n if (!value) return undefined\n return {\n peer: value.peer,\n transport: value.transport,\n namespace: value.namespace,\n failure: value.failure ?? \"isolate\",\n onError: value.onError,\n onConflict: value.onConflict,\n }\n}\n\nasync function read(current: Active, key: string): Promise<Sync.Message | undefined> {\n try {\n return await current.transport.read(key)\n } catch (error) {\n handleError(current, error, \"read\", undefined)\n return undefined\n }\n}\n\nasync function bind<T>(\n scope: Lite.Scope,\n target: Lite.Atom<unknown>,\n ctx: Lite.ResolveContext,\n current: Active,\n spec: Spec<T, Sync.Value>,\n key: string,\n last: Sync.Value\n): Promise<void> {\n const ctrl = scope.controller(target as Lite.Atom<T>)\n const state: Bound<T> = {\n last,\n desired: undefined,\n version: 0,\n writing: false,\n applying: false,\n }\n\n const offRemote = await subscribe(current, key, (message) => {\n if (message.peer === current.peer) return\n apply(current, spec, ctrl, state, message)\n })\n\n const offChange = ctrl.on(\"resolved\", () => {\n if (state.applying) return\n const value = ctrl.get()\n const encoded = encode(current, spec, value)\n if (encoded === undefined) return\n if (!state.writing && state.desired === undefined && sameValue(encoded, state.last)) return\n state.desired = encoded\n void flush(current, spec, ctrl, state, key)\n })\n\n const close = () => {\n offRemote()\n offChange()\n }\n ctx.cleanup(close)\n ctx.data.set(bound, state)\n}\n\nasync function flush<T>(\n current: Active,\n spec: Spec<T, Sync.Value>,\n ctrl: Lite.Controller<T>,\n state: Bound<T>,\n key: string\n): Promise<void> {\n if (state.writing) return\n state.writing = true\n while (state.desired !== undefined) {\n const encoded = state.desired\n if (sameValue(encoded, state.last)) {\n state.desired = undefined\n continue\n }\n\n state.desired = undefined\n state.version += 1\n const version = state.version\n const ack = await write(current, {\n key,\n peer: current.peer,\n version,\n value: encoded,\n })\n if (ack && isWriteConflict(ack)) {\n apply(current, spec, ctrl, state, ack.conflict)\n continue\n }\n if (!ack || state.version !== version) continue\n state.last = encoded\n state.version = Math.max(version, ack === true ? version : ack.version)\n }\n state.writing = false\n}\n\nasync function write(current: Active, message: Sync.Message): Promise<Sync.WriteAck | Sync.WriteConflict | true | undefined> {\n try {\n return await current.transport.write(message) ?? true\n } catch (error) {\n handleError(current, error, \"write\", message, false)\n return undefined\n }\n}\n\nasync function subscribe(current: Active, key: string, listener: (message: Sync.Message) => void): Promise<() => void> {\n try {\n return await current.transport.subscribe(key, (message) => {\n try {\n listener(message)\n } catch (error) {\n handleError(current, error, \"subscribe\", undefined, false)\n }\n })\n } catch (error) {\n handleError(current, error, \"subscribe\", undefined)\n return () => {}\n }\n}\n\nfunction apply<T>(\n current: Active,\n spec: Spec<T, Sync.Value>,\n ctrl: Lite.Controller<T>,\n state: Bound<T>,\n message: Sync.Message\n): void {\n const incoming = decode(current, spec, message, undefined, false)\n if (incoming === undefined) return\n\n const next = resolveConflict(current, spec, ctrl.get(), incoming, message, false)\n if (next === undefined) return\n if (isConflict(next)) {\n current.onConflict?.(next, message)\n return\n }\n\n const encoded = encode(current, spec, next, false)\n if (encoded === undefined || sameValue(encoded, state.last)) return\n\n state.applying = true\n state.last = encoded\n state.version = Math.max(state.version, message.version)\n ctrl.set(next)\n state.applying = false\n}\n\nfunction encode<T>(current: Active, spec: Spec<T, Sync.Value>, value: T, throwOnFailure = true): Sync.Value | undefined {\n try {\n return assertValue(spec.codec.encode(value))\n } catch (error) {\n handleError(current, error, \"encode\", undefined, throwOnFailure)\n return undefined\n }\n}\n\nfunction decode<T>(current: Active, spec: Spec<T, Sync.Value>, message: Sync.Message, fallback: T): T\nfunction decode<T>(current: Active, spec: Spec<T, Sync.Value>, message: Sync.Message, fallback: undefined): T | undefined\nfunction decode<T>(current: Active, spec: Spec<T, Sync.Value>, message: Sync.Message, fallback: T, throwOnFailure: boolean): T\nfunction decode<T>(\n current: Active,\n spec: Spec<T, Sync.Value>,\n message: Sync.Message,\n fallback: undefined,\n throwOnFailure: boolean\n): T | undefined\nfunction decode<T>(\n current: Active,\n spec: Spec<T, Sync.Value>,\n message: Sync.Message,\n fallback: T | undefined,\n throwOnFailure = true\n): T | undefined {\n try {\n const wire = assertValue(message.value)\n return spec.codec.decode(wire)\n } catch (error) {\n handleError(current, error, \"decode\", message, throwOnFailure)\n return fallback\n }\n}\n\nfunction resolveConflict<T>(\n current: Active,\n spec: Spec<T, Sync.Value>,\n local: T,\n incoming: T,\n message: Sync.Message,\n throwOnFailure = true\n): T | Sync.Conflict<T> | undefined {\n if (!spec.conflict) return incoming\n try {\n return spec.conflict.resolve(local, incoming)\n } catch (error) {\n handleError(current, error, \"conflict\", message, throwOnFailure)\n return undefined\n }\n}\n\nfunction handleError(\n current: Active,\n error: unknown,\n phase: Sync.ErrorPhase,\n message: Sync.Message | undefined,\n throwOnFailure = true\n): void {\n current.onError?.(error, phase, message)\n if (throwOnFailure && current.failure === \"throw\") throw error\n}\n\nfunction versionReader(read: string | ((value: never) => number)): (value: unknown) => number {\n if (typeof read === \"function\") {\n const readVersion = read as (value: unknown) => number\n return (value) => assertVersion(readVersion(value))\n }\n\n return (value) => {\n if (value === null || typeof value !== \"object\") throw new Error(\"Sync revision value must be an object\")\n const version = (value as Record<string, unknown>)[read]\n if (typeof version !== \"number\") throw new Error(\"Sync revision field must be a number\")\n return assertVersion(version)\n }\n}\n\nfunction assertVersion(value: number): number {\n if (Number.isFinite(value)) return value\n throw new Error(\"Sync revision field must be finite\")\n}\n\nfunction isConflict<T>(value: T | Sync.Conflict<T>): value is Sync.Conflict<T> {\n return typeof value === \"object\"\n && value !== null\n && \"current\" in value\n && \"incoming\" in value\n}\n\nfunction isWriteConflict(value: Sync.WriteAck | Sync.WriteConflict | true): value is Sync.WriteConflict {\n return typeof value === \"object\" && \"conflict\" in value\n}\n\nfunction scopedKey(current: Active, id: string): string {\n return current.namespace ? `${current.namespace}:${id}` : id\n}\n\nfunction isPromise<T>(value: MaybePromise<T>): value is Promise<T> {\n return value != null && typeof (value as Promise<T>).then === \"function\"\n}\n\nfunction assertValue(value: unknown): Sync.Value {\n if (value === null) return null\n\n switch (typeof value) {\n case \"string\":\n case \"boolean\":\n return value\n case \"number\":\n if (Number.isFinite(value)) return value\n throw new Error(\"Sync value number must be finite\")\n case \"object\":\n if (Array.isArray(value)) return value.map(assertValue)\n if (Object.getPrototypeOf(value) !== Object.prototype) throw new Error(\"Sync value must be plain JSON\")\n return assertRecord(value as Record<string, unknown>)\n default:\n throw new Error(\"Sync value must be plain JSON\")\n }\n}\n\nfunction assertRecord(value: Record<string, unknown>): Sync.Value {\n const result: Record<string, Sync.Value> = {}\n for (const key of Object.keys(value)) {\n result[key] = assertValue(value[key])\n }\n return result\n}\n\nfunction sameValue(left: Sync.Value, right: Sync.Value): boolean {\n if (Object.is(left, right)) return true\n if (typeof left !== \"object\" || typeof right !== \"object\" || left === null || right === null) return false\n if (Array.isArray(left) || Array.isArray(right)) return sameArray(left, right)\n\n const leftKeys = Object.keys(left)\n const rightKeys = Object.keys(right)\n if (leftKeys.length !== rightKeys.length) return false\n const leftRecord = left as { readonly [key: string]: Sync.Value }\n const rightRecord = right as { readonly [key: string]: Sync.Value }\n for (const key of leftKeys) {\n if (!Object.hasOwn(rightRecord, key)) return false\n if (!sameValue(leftRecord[key]!, rightRecord[key]!)) return false\n }\n return true\n}\n\nfunction sameArray(left: Sync.Value, right: Sync.Value): boolean {\n if (!Array.isArray(left) || !Array.isArray(right)) return false\n if (left.length !== right.length) return false\n for (let i = 0; i < left.length; i++) {\n if (!sameValue(left[i]!, right[i]!)) return false\n }\n return true\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pumped-fn/lite-extension-sync",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Strict replicated state primitive and sync extension for @pumped-fn/lite",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.mjs",
|
|
9
|
+
"types": "./dist/index.d.cts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.mts",
|
|
14
|
+
"default": "./dist/index.mjs"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.cts",
|
|
18
|
+
"default": "./dist/index.cjs"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsdown",
|
|
31
|
+
"typecheck": "tsc --noEmit -p tsconfig.test.json",
|
|
32
|
+
"test": "vitest run --coverage",
|
|
33
|
+
"test:watch": "vitest"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@pumped-fn/lite": "^3.2.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@pumped-fn/lite": "workspace:*",
|
|
40
|
+
"@vitest/coverage-v8": "catalog:",
|
|
41
|
+
"tsdown": "catalog:",
|
|
42
|
+
"typescript": "catalog:",
|
|
43
|
+
"vite": "catalog:",
|
|
44
|
+
"vitest": "catalog:"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"pumped-fn",
|
|
48
|
+
"sync",
|
|
49
|
+
"replication",
|
|
50
|
+
"extension"
|
|
51
|
+
],
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"directory": "pkg/ext/sync",
|
|
56
|
+
"url": "git+https://github.com/pumped-fn/pumped-fn.git"
|
|
57
|
+
}
|
|
58
|
+
}
|