@absolutejs/sync 0.9.0 → 0.11.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 +58 -41
- package/dist/angular/index.js +259 -1
- package/dist/angular/index.js.map +7 -5
- package/dist/angular/sync-collection.service.d.ts +14 -0
- package/dist/client/collaborativeText.d.ts +51 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +236 -2
- package/dist/client/index.js.map +5 -3
- package/dist/crdt/index.d.ts +113 -0
- package/dist/crdt/index.js +256 -0
- package/dist/crdt/index.js.map +10 -0
- package/dist/engine/index.d.ts +2 -1
- package/dist/engine/index.js +45 -12
- package/dist/engine/index.js.map +3 -3
- package/dist/engine/syncEngine.d.ts +20 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +260 -2
- package/dist/react/index.js.map +7 -4
- package/dist/react/useCollaborativeText.d.ts +17 -0
- package/dist/svelte/createCollaborativeTextStore.d.ts +15 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.js +269 -2
- package/dist/svelte/index.js.map +7 -4
- package/dist/vue/index.d.ts +1 -0
- package/dist/vue/index.js +263 -2
- package/dist/vue/index.js.map +7 -4
- package/dist/vue/useCollaborativeText.d.ts +18 -0
- package/package.json +6 -1
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __name = (target, name) => {
|
|
5
|
+
Object.defineProperty(target, "name", {
|
|
6
|
+
value: name,
|
|
7
|
+
enumerable: false,
|
|
8
|
+
configurable: true
|
|
9
|
+
});
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
13
|
+
var __typeError = (msg) => {
|
|
14
|
+
throw TypeError(msg);
|
|
15
|
+
};
|
|
16
|
+
var __defNormalProp = (obj, key, value) => (key in obj) ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
17
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
18
|
+
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
|
19
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
20
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
21
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
22
|
+
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
|
|
23
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
24
|
+
var __expectFn = (fn) => fn !== undefined && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
25
|
+
var __decoratorContext = (kind, name, done, metadata, fns) => ({
|
|
26
|
+
kind: __decoratorStrings[kind],
|
|
27
|
+
name,
|
|
28
|
+
metadata,
|
|
29
|
+
addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null))
|
|
30
|
+
});
|
|
31
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
32
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
33
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length;i < n; i++)
|
|
34
|
+
flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
|
35
|
+
return value;
|
|
36
|
+
};
|
|
37
|
+
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
38
|
+
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
|
39
|
+
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
|
40
|
+
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
|
41
|
+
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : {
|
|
42
|
+
get [name]() {
|
|
43
|
+
return __privateGet(this, extra);
|
|
44
|
+
},
|
|
45
|
+
set [name](x) {
|
|
46
|
+
__privateSet(this, extra, x);
|
|
47
|
+
}
|
|
48
|
+
}, name));
|
|
49
|
+
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
|
50
|
+
for (var i = decorators.length - 1;i >= 0; i--) {
|
|
51
|
+
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
52
|
+
if (k) {
|
|
53
|
+
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => (name in x) };
|
|
54
|
+
if (k ^ 3)
|
|
55
|
+
access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
|
56
|
+
if (k > 2)
|
|
57
|
+
access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
|
58
|
+
}
|
|
59
|
+
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? undefined : { get: desc.get, set: desc.set } : target, ctx);
|
|
60
|
+
done._ = 1;
|
|
61
|
+
if (k ^ 4 || it === undefined)
|
|
62
|
+
__expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
|
63
|
+
else if (typeof it !== "object" || it === null)
|
|
64
|
+
__typeError("Object expected");
|
|
65
|
+
else
|
|
66
|
+
__expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
|
67
|
+
}
|
|
68
|
+
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// src/crdt/index.ts
|
|
72
|
+
var sumValues = (counts) => Object.values(counts).reduce((total, value) => total + value, 0);
|
|
73
|
+
var mergeMax = (a, b) => {
|
|
74
|
+
const merged = { ...a };
|
|
75
|
+
for (const [replica, value] of Object.entries(b)) {
|
|
76
|
+
merged[replica] = Math.max(merged[replica] ?? 0, value);
|
|
77
|
+
}
|
|
78
|
+
return merged;
|
|
79
|
+
};
|
|
80
|
+
var counter = {
|
|
81
|
+
create: () => ({ increments: {}, decrements: {} }),
|
|
82
|
+
value: (state) => sumValues(state.increments) - sumValues(state.decrements),
|
|
83
|
+
increment: (state, replica, by = 1) => ({
|
|
84
|
+
increments: {
|
|
85
|
+
...state.increments,
|
|
86
|
+
[replica]: (state.increments[replica] ?? 0) + by
|
|
87
|
+
},
|
|
88
|
+
decrements: state.decrements
|
|
89
|
+
}),
|
|
90
|
+
decrement: (state, replica, by = 1) => ({
|
|
91
|
+
increments: state.increments,
|
|
92
|
+
decrements: {
|
|
93
|
+
...state.decrements,
|
|
94
|
+
[replica]: (state.decrements[replica] ?? 0) + by
|
|
95
|
+
}
|
|
96
|
+
}),
|
|
97
|
+
merge: (a, b) => ({
|
|
98
|
+
increments: mergeMax(a.increments, b.increments),
|
|
99
|
+
decrements: mergeMax(a.decrements, b.decrements)
|
|
100
|
+
})
|
|
101
|
+
};
|
|
102
|
+
var lww = {
|
|
103
|
+
create: (value, replica, timestamp = Date.now()) => ({ value, timestamp, replica }),
|
|
104
|
+
set: (value, replica, timestamp = Date.now()) => ({
|
|
105
|
+
value,
|
|
106
|
+
timestamp,
|
|
107
|
+
replica
|
|
108
|
+
}),
|
|
109
|
+
merge: (a, b) => {
|
|
110
|
+
if (b.timestamp > a.timestamp) {
|
|
111
|
+
return b;
|
|
112
|
+
}
|
|
113
|
+
if (b.timestamp < a.timestamp) {
|
|
114
|
+
return a;
|
|
115
|
+
}
|
|
116
|
+
return b.replica > a.replica ? b : a;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
var compare = (a, b) => {
|
|
120
|
+
if (a.clock !== b.clock) {
|
|
121
|
+
return b.clock - a.clock;
|
|
122
|
+
}
|
|
123
|
+
if (a.replica === b.replica) {
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
return a.replica > b.replica ? -1 : 1;
|
|
127
|
+
};
|
|
128
|
+
var linearize = (elements) => {
|
|
129
|
+
const children = new Map;
|
|
130
|
+
for (const element of elements) {
|
|
131
|
+
const list = children.get(element.after);
|
|
132
|
+
if (list === undefined) {
|
|
133
|
+
children.set(element.after, [element]);
|
|
134
|
+
} else {
|
|
135
|
+
list.push(element);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
for (const list of children.values()) {
|
|
139
|
+
list.sort(compare);
|
|
140
|
+
}
|
|
141
|
+
const ordered = [];
|
|
142
|
+
const stack = [...children.get(null) ?? []].reverse();
|
|
143
|
+
while (stack.length > 0) {
|
|
144
|
+
const element = stack.pop();
|
|
145
|
+
ordered.push(element);
|
|
146
|
+
const kids = children.get(element.id);
|
|
147
|
+
if (kids !== undefined) {
|
|
148
|
+
for (let index = kids.length - 1;index >= 0; index -= 1) {
|
|
149
|
+
stack.push(kids[index]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return ordered;
|
|
154
|
+
};
|
|
155
|
+
var textOf = (state) => linearize(state.elements).filter((element) => !element.deleted).map((element) => element.value).join("");
|
|
156
|
+
var mergeTextState = (a, b) => {
|
|
157
|
+
const byId = new Map;
|
|
158
|
+
for (const element of [...a.elements, ...b.elements]) {
|
|
159
|
+
const existing = byId.get(element.id);
|
|
160
|
+
byId.set(element.id, existing === undefined ? element : { ...existing, deleted: existing.deleted || element.deleted });
|
|
161
|
+
}
|
|
162
|
+
return { elements: [...byId.values()] };
|
|
163
|
+
};
|
|
164
|
+
var createTextCrdt = (replica, initial) => {
|
|
165
|
+
const elements = new Map;
|
|
166
|
+
let clock = 0;
|
|
167
|
+
if (initial !== undefined) {
|
|
168
|
+
for (const element of initial.elements) {
|
|
169
|
+
elements.set(element.id, element);
|
|
170
|
+
clock = Math.max(clock, element.clock);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const visible = () => linearize([...elements.values()]).filter((element) => !element.deleted);
|
|
174
|
+
const insert = (index, value) => {
|
|
175
|
+
const seen = visible();
|
|
176
|
+
let after = index <= 0 ? null : seen[index - 1]?.id ?? null;
|
|
177
|
+
for (const char of [...value]) {
|
|
178
|
+
clock += 1;
|
|
179
|
+
const element = {
|
|
180
|
+
id: `${replica}:${clock}`,
|
|
181
|
+
replica,
|
|
182
|
+
clock,
|
|
183
|
+
after,
|
|
184
|
+
value: char,
|
|
185
|
+
deleted: false
|
|
186
|
+
};
|
|
187
|
+
elements.set(element.id, element);
|
|
188
|
+
after = element.id;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const remove = (index, count) => {
|
|
192
|
+
const seen = visible();
|
|
193
|
+
for (let offset = 0;offset < count; offset += 1) {
|
|
194
|
+
const target = seen[index + offset];
|
|
195
|
+
if (target !== undefined) {
|
|
196
|
+
elements.set(target.id, { ...target, deleted: true });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
return {
|
|
201
|
+
text: () => textOf({ elements: [...elements.values()] }),
|
|
202
|
+
insert,
|
|
203
|
+
delete: remove,
|
|
204
|
+
merge: (state) => {
|
|
205
|
+
for (const element of state.elements) {
|
|
206
|
+
const existing = elements.get(element.id);
|
|
207
|
+
elements.set(element.id, existing === undefined ? element : {
|
|
208
|
+
...existing,
|
|
209
|
+
deleted: existing.deleted || element.deleted
|
|
210
|
+
});
|
|
211
|
+
clock = Math.max(clock, element.clock);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
setText: (next) => {
|
|
215
|
+
const current = textOf({ elements: [...elements.values()] });
|
|
216
|
+
if (current === next) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
let prefix = 0;
|
|
220
|
+
const maxPrefix = Math.min(current.length, next.length);
|
|
221
|
+
while (prefix < maxPrefix && current[prefix] === next[prefix]) {
|
|
222
|
+
prefix += 1;
|
|
223
|
+
}
|
|
224
|
+
let suffix = 0;
|
|
225
|
+
while (suffix < maxPrefix - prefix && current[current.length - 1 - suffix] === next[next.length - 1 - suffix]) {
|
|
226
|
+
suffix += 1;
|
|
227
|
+
}
|
|
228
|
+
const removed = current.length - prefix - suffix;
|
|
229
|
+
if (removed > 0) {
|
|
230
|
+
remove(prefix, removed);
|
|
231
|
+
}
|
|
232
|
+
const inserted = next.slice(prefix, next.length - suffix);
|
|
233
|
+
if (inserted.length > 0) {
|
|
234
|
+
insert(prefix, inserted);
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
state: () => ({ elements: [...elements.values()] })
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
var rgaText = {
|
|
241
|
+
create: createTextCrdt,
|
|
242
|
+
empty: () => ({ elements: [] }),
|
|
243
|
+
merge: mergeTextState,
|
|
244
|
+
textOf
|
|
245
|
+
};
|
|
246
|
+
export {
|
|
247
|
+
textOf,
|
|
248
|
+
rgaText,
|
|
249
|
+
mergeTextState,
|
|
250
|
+
lww,
|
|
251
|
+
createTextCrdt,
|
|
252
|
+
counter
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
//# debugId=21FF50AD5A6B235464756E2164756E21
|
|
256
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/crdt/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Conflict-free replicated data types (CRDTs) for multiplayer/offline editing —\n * pure, dependency-free, and isomorphic (use the same code client and server).\n *\n * These are *state-based* CRDTs (CvRDTs): every `merge` is commutative,\n * associative, and idempotent, so replicas that exchange state in any order\n * converge to the same value. That fits the sync engine without engine changes:\n * store the CRDT state as a row field, have a mutation `merge` the incoming\n * state into the stored one (concurrent writes combine instead of clobbering),\n * and have each client merge the broadcast state into its local edits.\n */\n\nconst sumValues = (counts: Record<string, number>) =>\n\tObject.values(counts).reduce((total, value) => total + value, 0);\n\nconst mergeMax = (\n\ta: Record<string, number>,\n\tb: Record<string, number>\n): Record<string, number> => {\n\tconst merged: Record<string, number> = { ...a };\n\tfor (const [replica, value] of Object.entries(b)) {\n\t\tmerged[replica] = Math.max(merged[replica] ?? 0, value);\n\t}\n\treturn merged;\n};\n\n/* ─── PN-counter ─── */\n\n/** A counter that survives concurrent increments/decrements across replicas. */\nexport type CounterState = {\n\tincrements: Record<string, number>;\n\tdecrements: Record<string, number>;\n};\n\nexport const counter = {\n\tcreate: (): CounterState => ({ increments: {}, decrements: {} }),\n\t/** Current value: total increments minus total decrements. */\n\tvalue: (state: CounterState) =>\n\t\tsumValues(state.increments) - sumValues(state.decrements),\n\tincrement: (\n\t\tstate: CounterState,\n\t\treplica: string,\n\t\tby = 1\n\t): CounterState => ({\n\t\tincrements: {\n\t\t\t...state.increments,\n\t\t\t[replica]: (state.increments[replica] ?? 0) + by\n\t\t},\n\t\tdecrements: state.decrements\n\t}),\n\tdecrement: (\n\t\tstate: CounterState,\n\t\treplica: string,\n\t\tby = 1\n\t): CounterState => ({\n\t\tincrements: state.increments,\n\t\tdecrements: {\n\t\t\t...state.decrements,\n\t\t\t[replica]: (state.decrements[replica] ?? 0) + by\n\t\t}\n\t}),\n\t/** Merge by taking the max count seen per replica (monotonic). */\n\tmerge: (a: CounterState, b: CounterState): CounterState => ({\n\t\tincrements: mergeMax(a.increments, b.increments),\n\t\tdecrements: mergeMax(a.decrements, b.decrements)\n\t})\n};\n\n/* ─── LWW register ─── */\n\n/** A single value where the latest write wins (ties broken by replica id). */\nexport type LwwState<T> = { value: T; timestamp: number; replica: string };\n\nexport const lww = {\n\tcreate: <T>(\n\t\tvalue: T,\n\t\treplica: string,\n\t\ttimestamp = Date.now()\n\t): LwwState<T> => ({ value, timestamp, replica }),\n\tset: <T>(\n\t\tvalue: T,\n\t\treplica: string,\n\t\ttimestamp = Date.now()\n\t): LwwState<T> => ({\n\t\tvalue,\n\t\ttimestamp,\n\t\treplica\n\t}),\n\t/** Keep the entry with the higher timestamp (replica id breaks ties). */\n\tmerge: <T>(a: LwwState<T>, b: LwwState<T>): LwwState<T> => {\n\t\tif (b.timestamp > a.timestamp) {\n\t\t\treturn b;\n\t\t}\n\t\tif (b.timestamp < a.timestamp) {\n\t\t\treturn a;\n\t\t}\n\t\treturn b.replica > a.replica ? b : a;\n\t}\n};\n\n/* ─── Collaborative text ─── */\n\n/**\n * The contract a collaborative-text CRDT exposes, independent of the algorithm\n * behind it. Implemented first-party by the RGA below ({@link createTextCrdt})\n * and by third-party backends in the `sync-adapters` repo (e.g.\n * `@absolutejs/sync-yjs`). `State` is whatever that backend persists and\n * broadcasts — JSON ({@link TextState}) for the RGA, a base64 update for Yjs.\n */\nexport type CrdtText<State> = {\n\t/** The current visible text. */\n\ttext: () => string;\n\t/** Reconcile the local text to `next` (the backend computes the edit). */\n\tsetText: (next: string) => void;\n\t/** Merge another replica's state in (e.g. a broadcast from the server). */\n\tmerge: (state: State) => void;\n\t/** The serializable state to persist/broadcast. */\n\tstate: () => State;\n};\n\n/**\n * The minimal server-side surface the engine needs to auto-merge a CRDT field on\n * write (see `engine.registerCrdt`): combine two states and produce an empty one.\n * Both the first-party {@link rgaText} and `@absolutejs/sync-yjs`'s `yjsText`\n * satisfy it, as does any {@link TextCrdtAdapter}.\n */\nexport type CrdtMergeable<State> = {\n\tempty: () => State;\n\tmerge: (a: State, b: State) => State;\n};\n\n/**\n * A pluggable collaborative-text backend. `create` mints a live doc for a\n * replica; `merge` combines two persisted states server-side (no live instance\n * needed — for the merge-on-write mutation); `empty`/`textOf` are conveniences.\n * Swap the first-party {@link rgaText} for an adapter to get a different engine\n * (e.g. Yjs) behind the exact same call sites.\n */\nexport type TextCrdtAdapter<State> = CrdtMergeable<State> & {\n\tcreate: (replica: string, initial?: State) => CrdtText<State>;\n\ttextOf: (state: State) => string;\n};\n\n/* ─── Collaborative text (RGA) — the first-party backend ─── */\n\n/** One inserted character in the replicated sequence (kept as a tombstone if deleted). */\nexport type TextElement = {\n\tid: string;\n\treplica: string;\n\tclock: number;\n\t/** Id of the element this was inserted after (`null` = start of document). */\n\tafter: string | null;\n\tvalue: string;\n\tdeleted: boolean;\n};\n\n/** Serializable state of a {@link TextCrdt} — safe to store as a row field. */\nexport type TextState = { elements: TextElement[] };\n\n// Sibling order (same `after`): higher clock first, then higher replica id.\nconst compare = (a: TextElement, b: TextElement) => {\n\tif (a.clock !== b.clock) {\n\t\treturn b.clock - a.clock;\n\t}\n\tif (a.replica === b.replica) {\n\t\treturn 0;\n\t}\n\treturn a.replica > b.replica ? -1 : 1;\n};\n\n/** Flatten the sequence into document order (an iterative RGA pre-order walk). */\nconst linearize = (elements: TextElement[]): TextElement[] => {\n\tconst children = new Map<string | null, TextElement[]>();\n\tfor (const element of elements) {\n\t\tconst list = children.get(element.after);\n\t\tif (list === undefined) {\n\t\t\tchildren.set(element.after, [element]);\n\t\t} else {\n\t\t\tlist.push(element);\n\t\t}\n\t}\n\tfor (const list of children.values()) {\n\t\tlist.sort(compare);\n\t}\n\tconst ordered: TextElement[] = [];\n\tconst stack = [...(children.get(null) ?? [])].reverse();\n\twhile (stack.length > 0) {\n\t\tconst element = stack.pop()!;\n\t\tordered.push(element);\n\t\tconst kids = children.get(element.id);\n\t\tif (kids !== undefined) {\n\t\t\tfor (let index = kids.length - 1; index >= 0; index -= 1) {\n\t\t\t\tstack.push(kids[index]!);\n\t\t\t}\n\t\t}\n\t}\n\treturn ordered;\n};\n\n/** The visible string of a text-CRDT state. Pure — use it server-side too. */\nexport const textOf = (state: TextState): string =>\n\tlinearize(state.elements)\n\t\t.filter((element) => !element.deleted)\n\t\t.map((element) => element.value)\n\t\t.join('');\n\n/** Merge two text-CRDT states (commutative/idempotent). Pure — for server mutations. */\nexport const mergeTextState = (a: TextState, b: TextState): TextState => {\n\tconst byId = new Map<string, TextElement>();\n\tfor (const element of [...a.elements, ...b.elements]) {\n\t\tconst existing = byId.get(element.id);\n\t\tbyId.set(\n\t\t\telement.id,\n\t\t\texisting === undefined\n\t\t\t\t? element\n\t\t\t\t: { ...existing, deleted: existing.deleted || element.deleted }\n\t\t);\n\t}\n\treturn { elements: [...byId.values()] };\n};\n\n/** The RGA text CRDT — {@link CrdtText} plus direct positional edits. */\nexport type TextCrdt = CrdtText<TextState> & {\n\t/** Insert `value` at visible `index`. */\n\tinsert: (index: number, value: string) => void;\n\t/** Tombstone `count` visible characters from `index`. */\n\tdelete: (index: number, count: number) => void;\n};\n\n/**\n * A collaborative text buffer backed by an RGA sequence CRDT. Concurrent inserts\n * and deletes from different replicas merge without conflict and converge. Drive\n * it from an input via {@link TextCrdt.setText}; persist/broadcast\n * {@link TextCrdt.state}; apply remote state via {@link TextCrdt.merge}.\n */\nexport const createTextCrdt = (\n\treplica: string,\n\tinitial?: TextState\n): TextCrdt => {\n\tconst elements = new Map<string, TextElement>();\n\tlet clock = 0;\n\tif (initial !== undefined) {\n\t\tfor (const element of initial.elements) {\n\t\t\telements.set(element.id, element);\n\t\t\tclock = Math.max(clock, element.clock);\n\t\t}\n\t}\n\n\tconst visible = () =>\n\t\tlinearize([...elements.values()]).filter((element) => !element.deleted);\n\n\tconst insert = (index: number, value: string) => {\n\t\tconst seen = visible();\n\t\tlet after = index <= 0 ? null : (seen[index - 1]?.id ?? null);\n\t\tfor (const char of [...value]) {\n\t\t\tclock += 1;\n\t\t\tconst element: TextElement = {\n\t\t\t\tid: `${replica}:${clock}`,\n\t\t\t\treplica,\n\t\t\t\tclock,\n\t\t\t\tafter,\n\t\t\t\tvalue: char,\n\t\t\t\tdeleted: false\n\t\t\t};\n\t\t\telements.set(element.id, element);\n\t\t\tafter = element.id;\n\t\t}\n\t};\n\n\tconst remove = (index: number, count: number) => {\n\t\tconst seen = visible();\n\t\tfor (let offset = 0; offset < count; offset += 1) {\n\t\t\tconst target = seen[index + offset];\n\t\t\tif (target !== undefined) {\n\t\t\t\telements.set(target.id, { ...target, deleted: true });\n\t\t\t}\n\t\t}\n\t};\n\n\treturn {\n\t\ttext: () => textOf({ elements: [...elements.values()] }),\n\t\tinsert,\n\t\tdelete: remove,\n\t\tmerge: (state) => {\n\t\t\tfor (const element of state.elements) {\n\t\t\t\tconst existing = elements.get(element.id);\n\t\t\t\telements.set(\n\t\t\t\t\telement.id,\n\t\t\t\t\texisting === undefined\n\t\t\t\t\t\t? element\n\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t...existing,\n\t\t\t\t\t\t\t\tdeleted: existing.deleted || element.deleted\n\t\t\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t\tclock = Math.max(clock, element.clock);\n\t\t\t}\n\t\t},\n\t\t// Reconcile to `next` by editing only the changed middle: keep the common\n\t\t// prefix/suffix, delete the old middle, insert the new — so two clients\n\t\t// typing in different places merge instead of overwriting.\n\t\tsetText: (next) => {\n\t\t\tconst current = textOf({ elements: [...elements.values()] });\n\t\t\tif (current === next) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet prefix = 0;\n\t\t\tconst maxPrefix = Math.min(current.length, next.length);\n\t\t\twhile (prefix < maxPrefix && current[prefix] === next[prefix]) {\n\t\t\t\tprefix += 1;\n\t\t\t}\n\t\t\tlet suffix = 0;\n\t\t\twhile (\n\t\t\t\tsuffix < maxPrefix - prefix &&\n\t\t\t\tcurrent[current.length - 1 - suffix] ===\n\t\t\t\t\tnext[next.length - 1 - suffix]\n\t\t\t) {\n\t\t\t\tsuffix += 1;\n\t\t\t}\n\t\t\tconst removed = current.length - prefix - suffix;\n\t\t\tif (removed > 0) {\n\t\t\t\tremove(prefix, removed);\n\t\t\t}\n\t\t\tconst inserted = next.slice(prefix, next.length - suffix);\n\t\t\tif (inserted.length > 0) {\n\t\t\t\tinsert(prefix, inserted);\n\t\t\t}\n\t\t},\n\t\tstate: () => ({ elements: [...elements.values()] })\n\t};\n};\n\n/**\n * The first-party collaborative-text backend (the RGA above) packaged as a\n * {@link TextCrdtAdapter}. Zero dependencies. Use it directly, or swap in an\n * adapter from `sync-adapters` (e.g. `@absolutejs/sync-yjs`) for the same shape.\n */\nexport const rgaText: TextCrdtAdapter<TextState> = {\n\tcreate: createTextCrdt,\n\tempty: () => ({ elements: [] }),\n\tmerge: mergeTextState,\n\ttextOf\n};\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "21FF50AD5A6B235464756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -43,7 +43,8 @@ export type { ScheduleContext, ScheduleDefinition } from './schedule';
|
|
|
43
43
|
export { defineMutation } from './mutation';
|
|
44
44
|
export type { MutationActions, MutationDefinition, MutationHandler, TableWriter, TransactionRunner } from './mutation';
|
|
45
45
|
export { createSyncEngine, SchemaError, UnauthorizedError } from './syncEngine';
|
|
46
|
-
export type { SubscribeArgs, Subscription, SyncEngine } from './syncEngine';
|
|
46
|
+
export type { CrdtFields, SubscribeArgs, Subscription, SyncEngine } from './syncEngine';
|
|
47
|
+
export type { CrdtMergeable } from '../crdt';
|
|
47
48
|
export { defineSchema, field } from './schema';
|
|
48
49
|
export type { FieldValidator, SchemaDefinition, TableSchema } from './schema';
|
|
49
50
|
export type { CollectionInspection, CollectionKind, EngineActivity, EngineInspection } from './devtools';
|
package/dist/engine/index.js
CHANGED
|
@@ -1093,6 +1093,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
1093
1093
|
for (const [table, schema] of Object.entries(options.schemas ?? {})) {
|
|
1094
1094
|
schemas.set(table, schema);
|
|
1095
1095
|
}
|
|
1096
|
+
const crdtFields = new Map;
|
|
1096
1097
|
const validateWrite = (table, op, row) => {
|
|
1097
1098
|
const schema = schemas.get(table);
|
|
1098
1099
|
if (schema === undefined || typeof row !== "object" || row === null) {
|
|
@@ -1298,6 +1299,14 @@ var createSyncEngine = (options = {}) => {
|
|
|
1298
1299
|
}
|
|
1299
1300
|
return writer;
|
|
1300
1301
|
};
|
|
1302
|
+
const readExisting = async (table, value, ctx) => {
|
|
1303
|
+
const reader = readers.get(table);
|
|
1304
|
+
if (reader?.get === undefined) {
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
const id = reader.key ? reader.key(value) : value.id;
|
|
1308
|
+
return id === undefined ? undefined : reader.get(id, ctx);
|
|
1309
|
+
};
|
|
1301
1310
|
const authorizeWrite = async (table, op, value, ctx) => {
|
|
1302
1311
|
const rule = writeRuleFor(table, op);
|
|
1303
1312
|
if (rule === undefined) {
|
|
@@ -1305,21 +1314,32 @@ var createSyncEngine = (options = {}) => {
|
|
|
1305
1314
|
}
|
|
1306
1315
|
let subject = value;
|
|
1307
1316
|
if (op !== "insert") {
|
|
1308
|
-
const
|
|
1309
|
-
if (
|
|
1310
|
-
|
|
1311
|
-
if (id !== undefined) {
|
|
1312
|
-
const existing = await reader.get(id, ctx);
|
|
1313
|
-
if (existing !== undefined) {
|
|
1314
|
-
subject = existing;
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
+
const existing = await readExisting(table, value, ctx);
|
|
1318
|
+
if (existing !== undefined) {
|
|
1319
|
+
subject = existing;
|
|
1317
1320
|
}
|
|
1318
1321
|
}
|
|
1319
1322
|
if (!rule(ctx, subject)) {
|
|
1320
1323
|
throw new UnauthorizedError(`${op} on table "${table}"`);
|
|
1321
1324
|
}
|
|
1322
1325
|
};
|
|
1326
|
+
const mergeCrdtFields = async (table, op, data, ctx) => {
|
|
1327
|
+
const fields = crdtFields.get(table);
|
|
1328
|
+
if (fields === undefined || data === null || typeof data !== "object") {
|
|
1329
|
+
return data;
|
|
1330
|
+
}
|
|
1331
|
+
const incoming = data;
|
|
1332
|
+
const existing = op === "update" ? await readExisting(table, data, ctx) : undefined;
|
|
1333
|
+
const base = existing !== null && typeof existing === "object" ? existing : undefined;
|
|
1334
|
+
const merged = { ...incoming };
|
|
1335
|
+
for (const [field, adapter] of Object.entries(fields)) {
|
|
1336
|
+
if (incoming[field] === undefined) {
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
1339
|
+
merged[field] = adapter.merge(base?.[field] ?? adapter.empty(), incoming[field]);
|
|
1340
|
+
}
|
|
1341
|
+
return merged;
|
|
1342
|
+
};
|
|
1323
1343
|
const makeActions = (tx, ctx, enforce) => {
|
|
1324
1344
|
const buffered = [];
|
|
1325
1345
|
const actions = {
|
|
@@ -1335,7 +1355,8 @@ var createSyncEngine = (options = {}) => {
|
|
|
1335
1355
|
if (enforce) {
|
|
1336
1356
|
await authorizeWrite(table, "insert", data, ctx);
|
|
1337
1357
|
}
|
|
1338
|
-
const
|
|
1358
|
+
const merged = await mergeCrdtFields(table, "insert", data, ctx);
|
|
1359
|
+
const row = await writerFor(table).insert(merged, ctx, tx);
|
|
1339
1360
|
buffered.push({ table, change: { op: "insert", row } });
|
|
1340
1361
|
return row;
|
|
1341
1362
|
},
|
|
@@ -1344,7 +1365,8 @@ var createSyncEngine = (options = {}) => {
|
|
|
1344
1365
|
if (enforce) {
|
|
1345
1366
|
await authorizeWrite(table, "update", data, ctx);
|
|
1346
1367
|
}
|
|
1347
|
-
const
|
|
1368
|
+
const merged = await mergeCrdtFields(table, "update", data, ctx);
|
|
1369
|
+
const row = await writerFor(table).update(merged, ctx, tx);
|
|
1348
1370
|
buffered.push({ table, change: { op: "update", row } });
|
|
1349
1371
|
return row;
|
|
1350
1372
|
},
|
|
@@ -1868,6 +1890,17 @@ var createSyncEngine = (options = {}) => {
|
|
|
1868
1890
|
registerSchema: (table, schema) => {
|
|
1869
1891
|
schemas.set(table, schema);
|
|
1870
1892
|
},
|
|
1893
|
+
registerCrdt: (table, fields) => {
|
|
1894
|
+
crdtFields.set(table, fields);
|
|
1895
|
+
const name = `${table}:merge`;
|
|
1896
|
+
mutations.set(name, {
|
|
1897
|
+
handler: async (args, ctx, actions) => {
|
|
1898
|
+
const existing = await readExisting(table, args, ctx);
|
|
1899
|
+
return existing === undefined ? actions.insert(table, args) : actions.update(table, args);
|
|
1900
|
+
},
|
|
1901
|
+
name
|
|
1902
|
+
});
|
|
1903
|
+
},
|
|
1871
1904
|
migrate: (table, row) => migrateRow(table, row),
|
|
1872
1905
|
runMutation: async (name, args, ctx) => {
|
|
1873
1906
|
const mutation = mutations.get(name);
|
|
@@ -2258,5 +2291,5 @@ export {
|
|
|
2258
2291
|
SEARCH_SCORE_FIELD
|
|
2259
2292
|
};
|
|
2260
2293
|
|
|
2261
|
-
//# debugId=
|
|
2294
|
+
//# debugId=50FA0D17837EB63364756E2164756E21
|
|
2262
2295
|
//# sourceMappingURL=index.js.map
|