@sovereignbase/convergent-replicated-struct 1.0.1 → 1.1.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 +247 -116
- package/dist/index.cjs +432 -275
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +264 -96
- package/dist/index.d.ts +264 -96
- package/dist/index.js +430 -273
- package/dist/index.js.map +1 -1
- package/package.json +10 -12
package/dist/index.js
CHANGED
|
@@ -15,276 +15,399 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
// src/
|
|
18
|
+
// src/.helpers/overwriteAndReturnSnapshotEntry/index.ts
|
|
19
19
|
import { v7 as uuidv7 } from "uuid";
|
|
20
|
+
function overwriteAndReturnSnapshotEntry(key, value, crStructReplica) {
|
|
21
|
+
const target = crStructReplica.entries[key];
|
|
22
|
+
const oldUuidv7 = target.uuidv7;
|
|
23
|
+
target.uuidv7 = uuidv7();
|
|
24
|
+
target.value = value;
|
|
25
|
+
target.predecessor = oldUuidv7;
|
|
26
|
+
target.tombstones.add(oldUuidv7);
|
|
27
|
+
return parseStateEntryToSnapshotEntry(target);
|
|
28
|
+
}
|
|
20
29
|
|
|
21
|
-
// src/.
|
|
22
|
-
var OOStructError = class extends Error {
|
|
23
|
-
/**
|
|
24
|
-
* The semantic error code for the failure.
|
|
25
|
-
*/
|
|
26
|
-
code;
|
|
27
|
-
/**
|
|
28
|
-
* Creates a typed OO-Struct error.
|
|
29
|
-
*
|
|
30
|
-
* @param code - The semantic error code.
|
|
31
|
-
* @param message - An optional human-readable detail message.
|
|
32
|
-
*/
|
|
33
|
-
constructor(code, message) {
|
|
34
|
-
const detail = message ?? code;
|
|
35
|
-
super(`{@sovereignbase/observed-overwrite-struct} ${detail}`);
|
|
36
|
-
this.code = code;
|
|
37
|
-
this.name = "OOStructError";
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// src/OOStruct/parseSnapshotEntryToStateEntry/index.ts
|
|
30
|
+
// src/.helpers/parseSnapshotEntryToStateEntry/index.ts
|
|
42
31
|
import { isUuidV7, prototype, safeStructuredClone } from "@sovereignbase/utils";
|
|
43
32
|
function parseSnapshotEntryToStateEntry(defaultValue, snapshotEntry) {
|
|
44
|
-
if (prototype(snapshotEntry) !== "record" || !Object.hasOwn(snapshotEntry, "
|
|
33
|
+
if (prototype(snapshotEntry) !== "record" || !Object.hasOwn(snapshotEntry, "value") || !isUuidV7(snapshotEntry.uuidv7) || !isUuidV7(snapshotEntry.predecessor) || !Array.isArray(snapshotEntry.tombstones))
|
|
45
34
|
return false;
|
|
46
|
-
const [cloned, copiedValue] = safeStructuredClone(snapshotEntry.
|
|
35
|
+
const [cloned, copiedValue] = safeStructuredClone(snapshotEntry.value);
|
|
47
36
|
if (!cloned || prototype(copiedValue) !== prototype(defaultValue))
|
|
48
37
|
return false;
|
|
49
|
-
const
|
|
50
|
-
for (const overwrite of snapshotEntry.
|
|
51
|
-
if (!isUuidV7(overwrite) || overwrite === snapshotEntry.
|
|
38
|
+
const tombstones = /* @__PURE__ */ new Set([]);
|
|
39
|
+
for (const overwrite of snapshotEntry.tombstones) {
|
|
40
|
+
if (!isUuidV7(overwrite) || overwrite === snapshotEntry.uuidv7)
|
|
52
41
|
continue;
|
|
53
|
-
|
|
42
|
+
tombstones.add(overwrite);
|
|
54
43
|
}
|
|
55
|
-
if (!
|
|
44
|
+
if (!tombstones.has(snapshotEntry.predecessor)) return false;
|
|
56
45
|
return {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
uuidv7: snapshotEntry.uuidv7,
|
|
47
|
+
value: copiedValue,
|
|
48
|
+
predecessor: snapshotEntry.predecessor,
|
|
49
|
+
tombstones
|
|
61
50
|
};
|
|
62
51
|
}
|
|
63
52
|
|
|
64
|
-
// src/
|
|
53
|
+
// src/.helpers/parseStateEntryToSnapshotEntry/index.ts
|
|
65
54
|
function parseStateEntryToSnapshotEntry(stateEntry) {
|
|
66
55
|
return {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
56
|
+
uuidv7: stateEntry.uuidv7,
|
|
57
|
+
value: structuredClone(stateEntry.value),
|
|
58
|
+
predecessor: stateEntry.predecessor,
|
|
59
|
+
tombstones: Array.from(stateEntry.tombstones)
|
|
71
60
|
};
|
|
72
61
|
}
|
|
73
62
|
|
|
74
|
-
// src/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
63
|
+
// src/core/mags/merge/index.ts
|
|
64
|
+
function __merge(crStructDelta, crStructReplica) {
|
|
65
|
+
if (!crStructDelta || typeof crStructDelta !== "object" || Array.isArray(crStructDelta))
|
|
66
|
+
return false;
|
|
67
|
+
const delta = {};
|
|
68
|
+
const change = {};
|
|
69
|
+
let hasDelta = false;
|
|
70
|
+
let hasChange = false;
|
|
71
|
+
for (const [key, value] of Object.entries(crStructDelta)) {
|
|
72
|
+
if (!Object.hasOwn(crStructReplica.defaults, key)) continue;
|
|
73
|
+
const candidate = parseSnapshotEntryToStateEntry(
|
|
74
|
+
crStructReplica.defaults[key],
|
|
75
|
+
value
|
|
76
|
+
);
|
|
77
|
+
if (!candidate) continue;
|
|
78
|
+
const target = crStructReplica.entries[key];
|
|
79
|
+
const current = { ...target };
|
|
80
|
+
let frontier = "";
|
|
81
|
+
for (const overwrite of target.tombstones) {
|
|
82
|
+
if (frontier < overwrite) frontier = overwrite;
|
|
83
|
+
}
|
|
84
|
+
for (const overwrite of candidate.tombstones) {
|
|
85
|
+
if (overwrite <= frontier || target.tombstones.has(overwrite)) continue;
|
|
86
|
+
target.tombstones.add(overwrite);
|
|
87
|
+
}
|
|
88
|
+
if (target.tombstones.has(candidate.uuidv7)) continue;
|
|
89
|
+
if (current.uuidv7 === candidate.uuidv7) {
|
|
90
|
+
if (current.predecessor < candidate.predecessor) {
|
|
91
|
+
target.value = candidate.value;
|
|
92
|
+
target.predecessor = candidate.predecessor;
|
|
93
|
+
target.tombstones.add(candidate.predecessor);
|
|
94
|
+
change[key] = structuredClone(candidate.value);
|
|
95
|
+
hasChange = true;
|
|
96
|
+
} else {
|
|
97
|
+
delta[key] = overwriteAndReturnSnapshotEntry(
|
|
98
|
+
key,
|
|
99
|
+
current.value,
|
|
100
|
+
crStructReplica
|
|
105
101
|
);
|
|
106
|
-
|
|
107
|
-
this.__live[key] = valid.__value;
|
|
108
|
-
this.__state[key] = valid;
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
102
|
+
hasDelta = true;
|
|
111
103
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (current.uuidv7 === candidate.predecessor || target.tombstones.has(current.uuidv7) || candidate.uuidv7 > current.uuidv7) {
|
|
107
|
+
target.uuidv7 = candidate.uuidv7;
|
|
108
|
+
target.value = candidate.value;
|
|
109
|
+
target.predecessor = candidate.predecessor;
|
|
110
|
+
target.tombstones.add(candidate.predecessor);
|
|
111
|
+
target.tombstones.add(current.uuidv7);
|
|
112
|
+
change[key] = structuredClone(candidate.value);
|
|
113
|
+
hasChange = true;
|
|
114
|
+
continue;
|
|
120
115
|
}
|
|
116
|
+
target.tombstones.add(candidate.uuidv7);
|
|
117
|
+
delta[key] = parseStateEntryToSnapshotEntry(target);
|
|
118
|
+
hasDelta = true;
|
|
121
119
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
120
|
+
if (!hasDelta && !hasChange) return false;
|
|
121
|
+
return { change, delta };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/core/mags/acknowledge/index.ts
|
|
125
|
+
function __acknowledge(crStructReplica) {
|
|
126
|
+
const ack = {};
|
|
127
|
+
for (const [key, value] of Object.entries(crStructReplica.entries)) {
|
|
128
|
+
let max = "";
|
|
129
|
+
for (const tombstone of value.tombstones) {
|
|
130
|
+
if (max < tombstone) max = tombstone;
|
|
131
|
+
}
|
|
132
|
+
ack[key] = max;
|
|
132
133
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
134
|
+
return ack;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/core/mags/garbageCollect/index.ts
|
|
138
|
+
import { isUuidV7 as isUuidV72 } from "@sovereignbase/utils";
|
|
139
|
+
function __garbageCollect(frontiers, crStructReplica) {
|
|
140
|
+
if (!Array.isArray(frontiers) || frontiers.length < 1) return;
|
|
141
|
+
const smallestAcknowledgementsPerKey = {};
|
|
142
|
+
for (const frontier of frontiers) {
|
|
143
|
+
for (const [key, value] of Object.entries(frontier)) {
|
|
144
|
+
if (!Object.hasOwn(crStructReplica.entries, key) || !isUuidV72(value))
|
|
145
|
+
continue;
|
|
146
|
+
const current = smallestAcknowledgementsPerKey[key];
|
|
147
|
+
if (typeof current === "string" && current <= value) continue;
|
|
148
|
+
smallestAcknowledgementsPerKey[key] = value;
|
|
149
|
+
}
|
|
141
150
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
throw new OOStructError(
|
|
159
|
-
"VALUE_TYPE_MISMATCH",
|
|
160
|
-
"Updated value must match the default value runtime type."
|
|
161
|
-
);
|
|
162
|
-
const delta = {};
|
|
163
|
-
const change = {};
|
|
164
|
-
delta[key] = this.overwriteAndReturnSnapshotEntry(key, copiedValue);
|
|
165
|
-
change[key] = structuredClone(copiedValue);
|
|
166
|
-
this.__eventTarget.dispatchEvent(
|
|
167
|
-
new CustomEvent("delta", { detail: delta })
|
|
168
|
-
);
|
|
169
|
-
this.__eventTarget.dispatchEvent(
|
|
170
|
-
new CustomEvent("change", { detail: change })
|
|
151
|
+
for (const [key, value] of Object.entries(smallestAcknowledgementsPerKey)) {
|
|
152
|
+
const target = crStructReplica.entries[key];
|
|
153
|
+
const smallest = value;
|
|
154
|
+
for (const uuidv73 of target.tombstones) {
|
|
155
|
+
if (uuidv73 === target.predecessor || uuidv73 > smallest) continue;
|
|
156
|
+
target.tombstones.delete(uuidv73);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/core/mags/snapshot/index.ts
|
|
162
|
+
function __snapshot(crStructReplica) {
|
|
163
|
+
const snapshot = {};
|
|
164
|
+
for (const [key, value] of Object.entries(crStructReplica.entries)) {
|
|
165
|
+
snapshot[key] = parseStateEntryToSnapshotEntry(
|
|
166
|
+
value
|
|
171
167
|
);
|
|
172
168
|
}
|
|
169
|
+
return snapshot;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/core/crud/create/index.ts
|
|
173
|
+
import { safeStructuredClone as safeStructuredClone2, prototype as prototype2 } from "@sovereignbase/utils";
|
|
174
|
+
|
|
175
|
+
// src/.errors/class.ts
|
|
176
|
+
var CRStructError = class extends Error {
|
|
177
|
+
/**
|
|
178
|
+
* The semantic error code for the failure.
|
|
179
|
+
*/
|
|
180
|
+
code;
|
|
173
181
|
/**
|
|
174
|
-
*
|
|
182
|
+
* Creates a typed CR-Struct error.
|
|
175
183
|
*
|
|
176
|
-
* @param
|
|
184
|
+
* @param code - The semantic error code.
|
|
185
|
+
* @param message - An optional human-readable detail message.
|
|
177
186
|
*/
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
187
|
+
constructor(code, message) {
|
|
188
|
+
const detail = message ?? code;
|
|
189
|
+
super(`{@sovereignbase/convergent-replicated-struct} ${detail}`);
|
|
190
|
+
this.code = code;
|
|
191
|
+
this.name = "CRStructError";
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// src/core/crud/create/index.ts
|
|
196
|
+
import { v7 as uuidv72 } from "uuid";
|
|
197
|
+
function __create(defaults, snapshot) {
|
|
198
|
+
const [cloned, copiedDefaults] = safeStructuredClone2(defaults);
|
|
199
|
+
if (!cloned)
|
|
200
|
+
throw new CRStructError(
|
|
201
|
+
"DEFAULTS_NOT_CLONEABLE",
|
|
202
|
+
"Default values must be supported by structuredClone."
|
|
203
|
+
);
|
|
204
|
+
const state = {
|
|
205
|
+
entries: {},
|
|
206
|
+
defaults: copiedDefaults
|
|
207
|
+
};
|
|
208
|
+
const snapshotIsObject = snapshot && prototype2(snapshot) === "record";
|
|
209
|
+
for (const key of Object.keys(defaults)) {
|
|
210
|
+
const defaultValue = copiedDefaults[key];
|
|
211
|
+
if (snapshotIsObject && Object.hasOwn(snapshot, key)) {
|
|
212
|
+
const valid = parseSnapshotEntryToStateEntry(
|
|
213
|
+
defaultValue,
|
|
214
|
+
snapshot[key]
|
|
215
|
+
);
|
|
216
|
+
if (valid) {
|
|
217
|
+
state.entries[key] = valid;
|
|
218
|
+
continue;
|
|
193
219
|
}
|
|
194
220
|
}
|
|
195
|
-
|
|
196
|
-
|
|
221
|
+
const root = uuidv72();
|
|
222
|
+
state.entries[key] = {
|
|
223
|
+
uuidv7: uuidv72(),
|
|
224
|
+
predecessor: root,
|
|
225
|
+
value: defaultValue,
|
|
226
|
+
tombstones: /* @__PURE__ */ new Set([root])
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return state;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/core/crud/read/index.ts
|
|
233
|
+
function __read(key, crStructReplica) {
|
|
234
|
+
return structuredClone(crStructReplica.entries[key].value);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/core/crud/update/index.ts
|
|
238
|
+
import { safeStructuredClone as safeStructuredClone3, prototype as prototype3 } from "@sovereignbase/utils";
|
|
239
|
+
function __update(key, value, crStructReplica) {
|
|
240
|
+
const [cloned, copiedValue] = safeStructuredClone3(value);
|
|
241
|
+
if (!cloned)
|
|
242
|
+
throw new CRStructError(
|
|
243
|
+
"VALUE_NOT_CLONEABLE",
|
|
244
|
+
"Updated values must be supported by structuredClone."
|
|
197
245
|
);
|
|
198
|
-
|
|
199
|
-
|
|
246
|
+
if (prototype3(copiedValue) !== prototype3(crStructReplica.defaults[key]))
|
|
247
|
+
throw new CRStructError(
|
|
248
|
+
"VALUE_TYPE_MISMATCH",
|
|
249
|
+
"Updated value must match the default value runtime type."
|
|
200
250
|
);
|
|
251
|
+
const delta = {};
|
|
252
|
+
const change = {};
|
|
253
|
+
delta[key] = overwriteAndReturnSnapshotEntry(
|
|
254
|
+
key,
|
|
255
|
+
copiedValue,
|
|
256
|
+
crStructReplica
|
|
257
|
+
);
|
|
258
|
+
change[key] = structuredClone(copiedValue);
|
|
259
|
+
return { change, delta };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/core/crud/delete/index.ts
|
|
263
|
+
function __delete(crStructReplica, key) {
|
|
264
|
+
const delta = {};
|
|
265
|
+
const change = {};
|
|
266
|
+
if (key !== void 0) {
|
|
267
|
+
if (!Object.hasOwn(crStructReplica.defaults, key)) return false;
|
|
268
|
+
const value = crStructReplica.defaults[key];
|
|
269
|
+
delta[key] = overwriteAndReturnSnapshotEntry(key, value, crStructReplica);
|
|
270
|
+
change[key] = structuredClone(value);
|
|
271
|
+
} else {
|
|
272
|
+
for (const [key2, value] of Object.entries(crStructReplica.defaults)) {
|
|
273
|
+
delta[key2] = overwriteAndReturnSnapshotEntry(
|
|
274
|
+
key2,
|
|
275
|
+
value,
|
|
276
|
+
crStructReplica
|
|
277
|
+
);
|
|
278
|
+
change[key2] = structuredClone(value);
|
|
279
|
+
}
|
|
201
280
|
}
|
|
202
|
-
|
|
281
|
+
return { change, delta };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/CRStruct/class.ts
|
|
285
|
+
var CRStructRaw = class {
|
|
203
286
|
/**
|
|
204
|
-
*
|
|
287
|
+
* Creates a replica from default values and an optional snapshot.
|
|
288
|
+
*
|
|
289
|
+
* The struct shape is fixed by the provided default values. The returned
|
|
290
|
+
* proxy exposes those fields as direct properties on the instance.
|
|
205
291
|
*
|
|
206
|
-
* @param
|
|
292
|
+
* @param defaults - The default field values that define the struct shape.
|
|
293
|
+
* @param snapshot - An optional serialized snapshot used to hydrate the replica.
|
|
294
|
+
* @throws {CRStructError} Thrown when the default values are not supported by `structuredClone`.
|
|
207
295
|
*/
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (!candidate) continue;
|
|
222
|
-
const target = this.__state[key];
|
|
223
|
-
const current = { ...target };
|
|
224
|
-
let frontier = "";
|
|
225
|
-
for (const overwrite of target.__overwrites) {
|
|
226
|
-
if (frontier < overwrite) frontier = overwrite;
|
|
227
|
-
}
|
|
228
|
-
for (const overwrite of candidate.__overwrites) {
|
|
229
|
-
if (overwrite <= frontier || target.__overwrites.has(overwrite))
|
|
230
|
-
continue;
|
|
231
|
-
target.__overwrites.add(overwrite);
|
|
296
|
+
constructor(defaults, snapshot) {
|
|
297
|
+
Object.defineProperties(this, {
|
|
298
|
+
state: {
|
|
299
|
+
value: __create(defaults, snapshot),
|
|
300
|
+
enumerable: false,
|
|
301
|
+
configurable: false,
|
|
302
|
+
writable: false
|
|
303
|
+
},
|
|
304
|
+
eventTarget: {
|
|
305
|
+
value: new EventTarget(),
|
|
306
|
+
enumerable: false,
|
|
307
|
+
configurable: false,
|
|
308
|
+
writable: false
|
|
232
309
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
310
|
+
});
|
|
311
|
+
const keys = new Set(Object.keys(defaults));
|
|
312
|
+
return new Proxy(this, {
|
|
313
|
+
get(target, key, receiver) {
|
|
314
|
+
if (typeof key !== "string" || !keys.has(key))
|
|
315
|
+
return Reflect.get(target, key, receiver);
|
|
316
|
+
return __read(key, target.state);
|
|
317
|
+
},
|
|
318
|
+
has(target, key) {
|
|
319
|
+
if (typeof key !== "string" || !keys.has(key))
|
|
320
|
+
return Reflect.has(target, key);
|
|
321
|
+
return true;
|
|
322
|
+
},
|
|
323
|
+
set(target, key, value) {
|
|
324
|
+
if (typeof key !== "string" || !keys.has(key)) return false;
|
|
325
|
+
try {
|
|
326
|
+
const result = __update(key, value, target.state);
|
|
327
|
+
if (!result) return false;
|
|
328
|
+
const { delta, change } = result;
|
|
329
|
+
if (delta)
|
|
330
|
+
void target.eventTarget.dispatchEvent(
|
|
331
|
+
new CustomEvent("delta", { detail: delta })
|
|
332
|
+
);
|
|
333
|
+
if (change)
|
|
334
|
+
void target.eventTarget.dispatchEvent(
|
|
335
|
+
new CustomEvent("change", { detail: change })
|
|
336
|
+
);
|
|
337
|
+
return true;
|
|
338
|
+
} catch {
|
|
339
|
+
return false;
|
|
248
340
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
341
|
+
},
|
|
342
|
+
deleteProperty(target, key) {
|
|
343
|
+
if (typeof key !== "string" || !keys.has(key)) return false;
|
|
344
|
+
try {
|
|
345
|
+
const result = __delete(target.state, key);
|
|
346
|
+
if (!result) return false;
|
|
347
|
+
const { delta, change } = result;
|
|
348
|
+
if (delta) {
|
|
349
|
+
void target.eventTarget.dispatchEvent(
|
|
350
|
+
new CustomEvent("delta", { detail: delta })
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
if (change) {
|
|
354
|
+
void target.eventTarget.dispatchEvent(
|
|
355
|
+
new CustomEvent("change", { detail: change })
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
return true;
|
|
359
|
+
} catch {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
ownKeys(target) {
|
|
364
|
+
return [
|
|
365
|
+
...Reflect.ownKeys(target),
|
|
366
|
+
...Reflect.ownKeys(target.state.defaults)
|
|
367
|
+
];
|
|
368
|
+
},
|
|
369
|
+
getOwnPropertyDescriptor(target, key) {
|
|
370
|
+
if (typeof key !== "string" || !keys.has(key))
|
|
371
|
+
return Reflect.getOwnPropertyDescriptor(target, key);
|
|
372
|
+
return {
|
|
373
|
+
value: __read(key, target.state),
|
|
374
|
+
writable: true,
|
|
375
|
+
enumerable: true,
|
|
376
|
+
configurable: true
|
|
377
|
+
};
|
|
261
378
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Applies a remote or local delta to the replica state.
|
|
383
|
+
*
|
|
384
|
+
* @param crStructDelta - The partial serialized field state to merge.
|
|
385
|
+
*/
|
|
386
|
+
merge(crStructDelta) {
|
|
387
|
+
const result = __merge(crStructDelta, this.state);
|
|
388
|
+
if (!result) return;
|
|
389
|
+
const { delta, change } = result;
|
|
390
|
+
if (delta) {
|
|
391
|
+
void this.eventTarget.dispatchEvent(
|
|
268
392
|
new CustomEvent("delta", { detail: delta })
|
|
269
393
|
);
|
|
270
|
-
|
|
271
|
-
|
|
394
|
+
}
|
|
395
|
+
if (change) {
|
|
396
|
+
void this.eventTarget.dispatchEvent(
|
|
272
397
|
new CustomEvent("change", { detail: change })
|
|
273
398
|
);
|
|
399
|
+
}
|
|
274
400
|
}
|
|
275
401
|
/**
|
|
276
402
|
* Emits the current acknowledgement frontier for each field.
|
|
277
403
|
*/
|
|
278
404
|
acknowledge() {
|
|
279
|
-
const ack =
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
ack[key] = max;
|
|
405
|
+
const ack = __acknowledge(this.state);
|
|
406
|
+
if (ack) {
|
|
407
|
+
void this.eventTarget.dispatchEvent(
|
|
408
|
+
new CustomEvent("ack", { detail: ack })
|
|
409
|
+
);
|
|
286
410
|
}
|
|
287
|
-
this.__eventTarget.dispatchEvent(new CustomEvent("ack", { detail: ack }));
|
|
288
411
|
}
|
|
289
412
|
/**
|
|
290
413
|
* Removes overwritten identifiers that every provided frontier has acknowledged.
|
|
@@ -292,47 +415,57 @@ var OOStruct = class _OOStruct {
|
|
|
292
415
|
* @param frontiers - A collection of acknowledgement frontiers to compact against.
|
|
293
416
|
*/
|
|
294
417
|
garbageCollect(frontiers) {
|
|
295
|
-
|
|
296
|
-
const smallestAcknowledgementsPerKey = {};
|
|
297
|
-
for (const frontier of frontiers) {
|
|
298
|
-
for (const [key, value] of Object.entries(frontier)) {
|
|
299
|
-
if (!Object.hasOwn(this.__state, key) || !isUuidV72(value)) continue;
|
|
300
|
-
const current = smallestAcknowledgementsPerKey[key];
|
|
301
|
-
if (typeof current === "string" && current <= value) continue;
|
|
302
|
-
smallestAcknowledgementsPerKey[key] = value;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
for (const [key, value] of Object.entries(smallestAcknowledgementsPerKey)) {
|
|
306
|
-
const target = this.__state[key];
|
|
307
|
-
const smallest = value;
|
|
308
|
-
for (const uuidv72 of target.__overwrites) {
|
|
309
|
-
if (uuidv72 === target.__after || uuidv72 > smallest) continue;
|
|
310
|
-
target.__overwrites.delete(uuidv72);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
418
|
+
void __garbageCollect(frontiers, this.state);
|
|
313
419
|
}
|
|
314
420
|
/**
|
|
315
421
|
* Emits a serialized snapshot of the current replica state.
|
|
316
422
|
*/
|
|
317
423
|
snapshot() {
|
|
318
|
-
const snapshot =
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
424
|
+
const snapshot = __snapshot(this.state);
|
|
425
|
+
if (snapshot) {
|
|
426
|
+
void this.eventTarget.dispatchEvent(
|
|
427
|
+
new CustomEvent("snapshot", { detail: snapshot })
|
|
322
428
|
);
|
|
323
429
|
}
|
|
324
|
-
this.__eventTarget.dispatchEvent(
|
|
325
|
-
new CustomEvent("snapshot", { detail: snapshot })
|
|
326
|
-
);
|
|
327
430
|
}
|
|
328
|
-
/**ADDITIONAL*/
|
|
329
431
|
/**
|
|
330
432
|
* Returns the struct field keys.
|
|
331
433
|
*
|
|
332
434
|
* @returns The field keys in the current replica.
|
|
333
435
|
*/
|
|
334
436
|
keys() {
|
|
335
|
-
return Object.keys(this.
|
|
437
|
+
return Object.keys(this.state.entries);
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Resets every field in the replica back to its default value.
|
|
441
|
+
*/
|
|
442
|
+
clear() {
|
|
443
|
+
const result = __delete(this.state);
|
|
444
|
+
if (result) {
|
|
445
|
+
const { delta, change } = result;
|
|
446
|
+
if (delta) {
|
|
447
|
+
void this.eventTarget.dispatchEvent(
|
|
448
|
+
new CustomEvent("delta", { detail: delta })
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
if (change) {
|
|
452
|
+
void this.eventTarget.dispatchEvent(
|
|
453
|
+
new CustomEvent("change", { detail: change })
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Returns a cloned plain object view of the current replica fields.
|
|
460
|
+
*
|
|
461
|
+
* @returns The current field values keyed by field name.
|
|
462
|
+
*/
|
|
463
|
+
clone() {
|
|
464
|
+
const out = {};
|
|
465
|
+
for (const [key, entry] of Object.entries(this.state.entries)) {
|
|
466
|
+
out[key] = structuredClone(entry.value);
|
|
467
|
+
}
|
|
468
|
+
return out;
|
|
336
469
|
}
|
|
337
470
|
/**
|
|
338
471
|
* Returns cloned copies of the current field values.
|
|
@@ -340,8 +473,8 @@ var OOStruct = class _OOStruct {
|
|
|
340
473
|
* @returns The current field values.
|
|
341
474
|
*/
|
|
342
475
|
values() {
|
|
343
|
-
return Object.values(this.
|
|
344
|
-
(
|
|
476
|
+
return Object.values(this.state.entries).map(
|
|
477
|
+
(entry) => structuredClone(entry.value)
|
|
345
478
|
);
|
|
346
479
|
}
|
|
347
480
|
/**
|
|
@@ -350,12 +483,45 @@ var OOStruct = class _OOStruct {
|
|
|
350
483
|
* @returns The current field entries.
|
|
351
484
|
*/
|
|
352
485
|
entries() {
|
|
353
|
-
return Object.entries(this.
|
|
486
|
+
return Object.entries(this.state.entries).map(([key, entry]) => [
|
|
354
487
|
key,
|
|
355
|
-
structuredClone(value)
|
|
488
|
+
structuredClone(entry.value)
|
|
356
489
|
]);
|
|
357
490
|
}
|
|
358
|
-
/**
|
|
491
|
+
/**
|
|
492
|
+
* Returns a serializable snapshot representation of this replica.
|
|
493
|
+
*
|
|
494
|
+
* Called automatically by `JSON.stringify`.
|
|
495
|
+
*/
|
|
496
|
+
toJSON() {
|
|
497
|
+
return __snapshot(this.state);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Returns this replica as a JSON string.
|
|
501
|
+
*/
|
|
502
|
+
toString() {
|
|
503
|
+
return JSON.stringify(this);
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Returns the Node.js console inspection representation.
|
|
507
|
+
*/
|
|
508
|
+
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
509
|
+
return this.toJSON();
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Returns the Deno console inspection representation.
|
|
513
|
+
*/
|
|
514
|
+
[/* @__PURE__ */ Symbol.for("Deno.customInspect")]() {
|
|
515
|
+
return this.toJSON();
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Iterates over the current live field entries.
|
|
519
|
+
*/
|
|
520
|
+
*[Symbol.iterator]() {
|
|
521
|
+
for (const [key, entry] of Object.entries(this.state.entries)) {
|
|
522
|
+
yield [key, structuredClone(entry.value)];
|
|
523
|
+
}
|
|
524
|
+
}
|
|
359
525
|
/**
|
|
360
526
|
* Registers an event listener.
|
|
361
527
|
*
|
|
@@ -364,7 +530,7 @@ var OOStruct = class _OOStruct {
|
|
|
364
530
|
* @param options - Listener registration options.
|
|
365
531
|
*/
|
|
366
532
|
addEventListener(type, listener, options) {
|
|
367
|
-
this.
|
|
533
|
+
this.eventTarget.addEventListener(
|
|
368
534
|
type,
|
|
369
535
|
listener,
|
|
370
536
|
options
|
|
@@ -378,32 +544,23 @@ var OOStruct = class _OOStruct {
|
|
|
378
544
|
* @param options - Listener removal options.
|
|
379
545
|
*/
|
|
380
546
|
removeEventListener(type, listener, options) {
|
|
381
|
-
this.
|
|
547
|
+
this.eventTarget.removeEventListener(
|
|
382
548
|
type,
|
|
383
549
|
listener,
|
|
384
550
|
options
|
|
385
551
|
);
|
|
386
552
|
}
|
|
387
|
-
/**HELPERS*/
|
|
388
|
-
/**
|
|
389
|
-
* Overwrites a field and returns the serialized delta entry for that overwrite.
|
|
390
|
-
*
|
|
391
|
-
* @param key - The field key to overwrite.
|
|
392
|
-
* @param value - The next value for the field.
|
|
393
|
-
* @returns The serialized snapshot entry for the new winning value.
|
|
394
|
-
*/
|
|
395
|
-
overwriteAndReturnSnapshotEntry(key, value) {
|
|
396
|
-
const target = this.__state[key];
|
|
397
|
-
const old = { ...target };
|
|
398
|
-
target.__uuidv7 = uuidv7();
|
|
399
|
-
target.__value = value;
|
|
400
|
-
target.__after = old.__uuidv7;
|
|
401
|
-
target.__overwrites.add(old.__uuidv7);
|
|
402
|
-
this.__live[key] = value;
|
|
403
|
-
return parseStateEntryToSnapshotEntry(target);
|
|
404
|
-
}
|
|
405
553
|
};
|
|
554
|
+
var CRStruct = CRStructRaw;
|
|
406
555
|
export {
|
|
407
|
-
|
|
556
|
+
CRStruct,
|
|
557
|
+
__acknowledge,
|
|
558
|
+
__create,
|
|
559
|
+
__delete,
|
|
560
|
+
__garbageCollect,
|
|
561
|
+
__merge,
|
|
562
|
+
__read,
|
|
563
|
+
__snapshot,
|
|
564
|
+
__update
|
|
408
565
|
};
|
|
409
566
|
//# sourceMappingURL=index.js.map
|