@ebbjs/storage 0.3.0 → 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/dist/indexeddb/index.d.ts +55 -20
- package/dist/indexeddb/index.js +202 -179
- package/dist/memory/index.d.ts +56 -21
- package/dist/memory/index.js +105 -87
- package/dist/replica/index.d.ts +49 -11
- package/dist/replica/index.js +209 -200
- package/dist/replica-record-BaptWabD.js +243 -0
- package/dist/types/index.d.ts +92 -23
- package/package.json +2 -2
- package/dist/replica-record-D3lkFACh.js +0 -167
package/dist/memory/index.d.ts
CHANGED
|
@@ -122,9 +122,10 @@ interface MembershipLink {
|
|
|
122
122
|
* handled by the DirtyTracker.
|
|
123
123
|
*
|
|
124
124
|
* ## Merge Semantics
|
|
125
|
-
* - **
|
|
126
|
-
* - **
|
|
127
|
-
* - **
|
|
125
|
+
* - **merge** — Last-Writer-Wins (LWW) at the component's field: higher HLC wins; tiebreak by lexicographic `update_id`
|
|
126
|
+
* - **replace** — the component's field is set whole; the first write to an absent row creates it
|
|
127
|
+
* - **drop** — the component's field is removed; the row survives
|
|
128
|
+
* - **delete** — soft delete (sets `deleted_hlc`); the row keeps its type
|
|
128
129
|
*
|
|
129
130
|
* A field value is either a leaf merged by the LWW rule above, or a
|
|
130
131
|
* `{ map }` of nested field values merged key by key under the same
|
|
@@ -136,6 +137,9 @@ interface MembershipLink {
|
|
|
136
137
|
* - `get(id)` — returns entity by ID, materializes if dirty
|
|
137
138
|
* - `set(entity)` — directly sets an entity (bypasses materialization)
|
|
138
139
|
* - `query(type)` — returns all materialized entities of a type
|
|
140
|
+
* - `queryByComponent(component)` — returns the live ids carrying that
|
|
141
|
+
* `<model>/<field>` component in ascending order, materializing dirty rows
|
|
142
|
+
* of the component's model first
|
|
139
143
|
* - `queryByRelationship({ as, type, targetId })` — returns the distinct
|
|
140
144
|
* source ids indexed under that triple in ascending order,
|
|
141
145
|
* materializing dirty relationship rows first
|
|
@@ -154,6 +158,18 @@ interface EntityStore {
|
|
|
154
158
|
get(id: string): Promise<Entity | null>;
|
|
155
159
|
set(entity: Entity): Promise<void>;
|
|
156
160
|
query(type: string): Promise<readonly Entity[]>;
|
|
161
|
+
/**
|
|
162
|
+
* Live rows currently carrying `component` (`<model>/<field>`), ascending by
|
|
163
|
+
* id. Existence is the set of present fields on a row, so this is an index
|
|
164
|
+
* read rather than a row filter; a tombstoned row's components are not
|
|
165
|
+
* existence.
|
|
166
|
+
*
|
|
167
|
+
* The dirty rows materialized first are the component prefix's model. That is
|
|
168
|
+
* the current `<model>/<field>` addressing rule (#797); a component namespace
|
|
169
|
+
* that is not the row's model (#805's `ebb/*` components on ordinary rows)
|
|
170
|
+
* has to decode the model differently and revisit this step.
|
|
171
|
+
*/
|
|
172
|
+
queryByComponent(component: string): Promise<readonly string[]>;
|
|
157
173
|
queryByRelationship(query: RelationshipIndexQuery): Promise<readonly string[]>;
|
|
158
174
|
queryByRelationshipSource(query: RelationshipSourceQuery): Promise<readonly RelationshipLink[]>;
|
|
159
175
|
queryByMembership(groupId: string): Promise<readonly string[]>;
|
|
@@ -229,44 +245,51 @@ interface CursorStore {
|
|
|
229
245
|
* `conflicted` is a terminal-until-resolved hold: a catch-up pass
|
|
230
246
|
* classified the Action as a loser, so it is structurally excluded from
|
|
231
247
|
* `pending()` and never submitted. The application resolves it by
|
|
232
|
-
*
|
|
248
|
+
* sending the Action unchanged (back to `pending`) or clearing it
|
|
249
|
+
* (removed).
|
|
233
250
|
*/
|
|
234
251
|
type OutboxStatus = "pending" | "acknowledged" | "error" | "conflicted";
|
|
235
252
|
/**
|
|
236
253
|
* One side of a contested slot: the buffered local write, or the value
|
|
237
254
|
* the server holds once the catch-up window is applied. A leaf carries
|
|
238
|
-
* its LWW triple; a whole-
|
|
255
|
+
* its LWW triple; a whole-component map replacement has no single leaf, so
|
|
239
256
|
* `update_id` / `hlc` are absent and `value` is the projected map. A
|
|
240
|
-
*
|
|
257
|
+
* component an inbound `merge` or `delete` reverted also has no triple, and
|
|
241
258
|
* `value` is `undefined` because the server holds no value.
|
|
242
259
|
*/
|
|
243
260
|
interface ConflictSide {
|
|
244
261
|
/** Projected leaf or map value. `undefined` when the server holds nothing. */
|
|
245
262
|
readonly value: unknown;
|
|
246
|
-
/** `update_id` of the FieldValue; absent for a map side or a reverted
|
|
263
|
+
/** `update_id` of the FieldValue; absent for a map side or a reverted component. */
|
|
247
264
|
readonly update_id?: string;
|
|
248
|
-
/** HLC carried by the FieldValue; absent for a map side or a reverted
|
|
265
|
+
/** HLC carried by the FieldValue; absent for a map side or a reverted component. */
|
|
249
266
|
readonly hlc?: string;
|
|
250
267
|
}
|
|
251
268
|
/**
|
|
252
|
-
* One contested position on a losing Action: a
|
|
253
|
-
* path from the
|
|
254
|
-
* leaf
|
|
255
|
-
*
|
|
269
|
+
* One contested position on a losing Action: a component plus the map-key
|
|
270
|
+
* path from the component's value root to the position. `path` is empty for a
|
|
271
|
+
* plain leaf component, an entity-level touch that overlaps no position, or a
|
|
272
|
+
* component an inbound `merge` / `delete` reverted. Concurrent writes to
|
|
256
273
|
* different keys are different slots and so are not conflicts.
|
|
257
274
|
*/
|
|
258
275
|
interface ConflictSlot {
|
|
259
276
|
/** The `subject_id` of the Update that carried the write. */
|
|
260
277
|
readonly subjectId: string;
|
|
261
|
-
/** The field name on that subject. */
|
|
262
|
-
readonly
|
|
263
|
-
/** Ordered map keys from the
|
|
278
|
+
/** The `<model>/<field>` component name on that subject. */
|
|
279
|
+
readonly component: string;
|
|
280
|
+
/** Ordered map keys from the component root to the position; empty at the root. */
|
|
264
281
|
readonly path: readonly string[];
|
|
265
282
|
/** The buffered local value at the position. */
|
|
266
283
|
readonly local: ConflictSide;
|
|
267
284
|
/** The value the server holds at the position after the missed window. */
|
|
268
285
|
readonly remote: ConflictSide;
|
|
269
286
|
}
|
|
287
|
+
/** One missed Action that out-dated a buffered write and made it a conflict. */
|
|
288
|
+
interface ConflictRemoteAction {
|
|
289
|
+
readonly actorId: string;
|
|
290
|
+
readonly actionId: string;
|
|
291
|
+
readonly gsn: number;
|
|
292
|
+
}
|
|
270
293
|
/**
|
|
271
294
|
* The before/after entity rows for one losing subject. `before` is the
|
|
272
295
|
* row as the client had it immediately before the catch-up window was
|
|
@@ -278,7 +301,8 @@ interface ConflictSlot {
|
|
|
278
301
|
*/
|
|
279
302
|
interface ConflictSubjectState {
|
|
280
303
|
readonly subjectId: string;
|
|
281
|
-
|
|
304
|
+
/** The model the row is committed under. */
|
|
305
|
+
readonly model: string;
|
|
282
306
|
readonly before: Entity | null;
|
|
283
307
|
readonly after: Entity | null;
|
|
284
308
|
}
|
|
@@ -305,6 +329,18 @@ interface OutboxConflict {
|
|
|
305
329
|
* application reads that as "nothing of mine would be lost".
|
|
306
330
|
*/
|
|
307
331
|
readonly slots: readonly ConflictSlot[];
|
|
332
|
+
/**
|
|
333
|
+
* The looser of the losing subjects' declared scopes: `"entity"` when
|
|
334
|
+
* any losing subject declared entity scope, else `"field"`. The union is
|
|
335
|
+
* spelled inline so storage stays independent of the schema package.
|
|
336
|
+
*/
|
|
337
|
+
readonly scope: "field" | "component" | "entity";
|
|
338
|
+
/**
|
|
339
|
+
* The concurrent missed Actions that touched the losing subjects,
|
|
340
|
+
* deduped by action id, in window order. The remote half of the
|
|
341
|
+
* report an application builds a policy from.
|
|
342
|
+
*/
|
|
343
|
+
readonly remoteActions: readonly ConflictRemoteAction[];
|
|
308
344
|
/** The catch-up pass that produced this entry. */
|
|
309
345
|
readonly catchUpId: string;
|
|
310
346
|
/**
|
|
@@ -353,10 +389,9 @@ interface OutboxEntry {
|
|
|
353
389
|
interface OutboxStore {
|
|
354
390
|
put(entry: OutboxEntry): Promise<void>;
|
|
355
391
|
/**
|
|
356
|
-
* Persist `entry` and delete `deleteId` in one durable operation
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
* without the re-assert persisted.
|
|
392
|
+
* Persist `entry` and delete `deleteId` in one durable operation: the
|
|
393
|
+
* replacement must never exist without the replaced row being gone,
|
|
394
|
+
* nor the row be gone without the replacement persisted.
|
|
360
395
|
*/
|
|
361
396
|
putAndDelete(entry: OutboxEntry, deleteId: string): Promise<void>;
|
|
362
397
|
list(): Promise<readonly OutboxEntry[]>;
|
|
@@ -454,7 +489,7 @@ interface StorageAdapter {
|
|
|
454
489
|
* // On action receipt:
|
|
455
490
|
* await storage.actions.append(action);
|
|
456
491
|
* for (const update of action.updates) {
|
|
457
|
-
* await storage.dirtyTracker.mark(update.subject_id, update
|
|
492
|
+
* await storage.dirtyTracker.mark(update.subject_id, updateModel(update, row));
|
|
458
493
|
* }
|
|
459
494
|
*
|
|
460
495
|
* // On entity read:
|
package/dist/memory/index.js
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
const
|
|
1
|
+
import { l as E, c as D, a as b, f as A, d as _, b as T, n as u, p as h, o as v, q as R, s as j, z as C, M as m, m as q, A as x, r as k, i as F, u as w } from "../replica-record-BaptWabD.js";
|
|
2
|
+
import { componentModel as M, compare as O } from "@ebbjs/core";
|
|
3
|
+
import { m as B, a as P, i as L, c as f } from "../cursor-record-D39emd6R.js";
|
|
4
|
+
const N = (t, n) => {
|
|
5
|
+
for (const [e, s] of t)
|
|
6
|
+
if (s.has(n)) return e;
|
|
7
|
+
}, H = (t, n) => {
|
|
5
8
|
const e = /* @__PURE__ */ new Map();
|
|
6
9
|
for (const [s, a] of t)
|
|
7
10
|
e.set(s, new Set(a));
|
|
8
11
|
for (const s of n.updates) {
|
|
9
|
-
const a = e.
|
|
10
|
-
|
|
12
|
+
const a = s.method === "delete" ? N(e, s.subject_id) : M(s.component);
|
|
13
|
+
if (a === void 0) continue;
|
|
14
|
+
const i = e.get(a);
|
|
15
|
+
i ? i.add(s.subject_id) : e.set(a, /* @__PURE__ */ new Set([s.subject_id]));
|
|
11
16
|
}
|
|
12
17
|
return Array.from(e.entries()).map(([s, a]) => [s, Array.from(a)]);
|
|
13
|
-
},
|
|
18
|
+
}, K = () => {
|
|
14
19
|
let t = {
|
|
15
20
|
actions: [],
|
|
16
21
|
typeIndex: []
|
|
17
22
|
};
|
|
18
23
|
const n = async (e) => {
|
|
19
|
-
const s = t.actions.findIndex((d) => d.id === e.id), a = s === -1 ? void 0 : t.actions[s],
|
|
24
|
+
const s = t.actions.findIndex((d) => d.id === e.id), a = s === -1 ? void 0 : t.actions[s], i = a === void 0 ? e : { ...a, updates: B(a.updates, e.updates) }, o = a === void 0, r = o ? t.actions.length + 1 : s + 1, c = P(a?.updates ?? [], e.updates);
|
|
20
25
|
return t = {
|
|
21
|
-
actions:
|
|
22
|
-
typeIndex:
|
|
23
|
-
}, { action:
|
|
26
|
+
actions: o ? [...t.actions, i] : t.actions.map((d) => d.id === e.id ? i : d),
|
|
27
|
+
typeIndex: H(t.typeIndex, i)
|
|
28
|
+
}, { action: i, position: r, added: c };
|
|
24
29
|
};
|
|
25
30
|
return {
|
|
26
31
|
append: n,
|
|
@@ -44,14 +49,14 @@ const O = (t, n) => {
|
|
|
44
49
|
},
|
|
45
50
|
async getForEntity(e) {
|
|
46
51
|
return t.actions.filter(
|
|
47
|
-
(a) => a.updates.some((
|
|
48
|
-
).sort((a,
|
|
52
|
+
(a) => a.updates.some((i) => i.subject_id === e)
|
|
53
|
+
).sort((a, i) => a.gsn - i.gsn);
|
|
49
54
|
},
|
|
50
55
|
async clear() {
|
|
51
56
|
t = { actions: [], typeIndex: [] };
|
|
52
57
|
}
|
|
53
58
|
};
|
|
54
|
-
},
|
|
59
|
+
}, Y = () => {
|
|
55
60
|
let t = {
|
|
56
61
|
entities: {},
|
|
57
62
|
typeIndex: {}
|
|
@@ -76,12 +81,12 @@ const O = (t, n) => {
|
|
|
76
81
|
async clear(n) {
|
|
77
82
|
const e = t.entities[n];
|
|
78
83
|
if (!e) return;
|
|
79
|
-
const { [n]: s, ...a } = t.entities,
|
|
80
|
-
|
|
84
|
+
const { [n]: s, ...a } = t.entities, i = t.typeIndex[e], o = new Set(i);
|
|
85
|
+
o.delete(n), t = {
|
|
81
86
|
entities: a,
|
|
82
87
|
typeIndex: {
|
|
83
88
|
...t.typeIndex,
|
|
84
|
-
[e]:
|
|
89
|
+
[e]: o
|
|
85
90
|
}
|
|
86
91
|
};
|
|
87
92
|
},
|
|
@@ -89,79 +94,86 @@ const O = (t, n) => {
|
|
|
89
94
|
t = { entities: {}, typeIndex: {} };
|
|
90
95
|
}
|
|
91
96
|
};
|
|
92
|
-
},
|
|
97
|
+
}, l = (t) => JSON.parse(JSON.stringify(t)), I = (t, n, e) => {
|
|
93
98
|
const s = { ...t };
|
|
94
99
|
if (e && e !== n.type) {
|
|
95
100
|
const a = new Set(s[e] ?? []);
|
|
96
101
|
a.delete(n.id), s[e] = a;
|
|
97
102
|
}
|
|
98
103
|
return s[n.type] || (s[n.type] = /* @__PURE__ */ new Set()), s[n.type] = new Set(s[n.type]).add(n.id), s;
|
|
99
|
-
},
|
|
100
|
-
|
|
101
|
-
j(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
}, g = (t, n, e) => {
|
|
105
|
+
const s = R(n, e);
|
|
106
|
+
return s === null ? t : j(t, s);
|
|
107
|
+
}, S = (t, n, e) => u(
|
|
108
|
+
u(t, v(n, e)),
|
|
109
|
+
h(n, e)
|
|
110
|
+
), z = (t, n) => {
|
|
111
|
+
let e = { entities: {}, typeIndex: {}, components: /* @__PURE__ */ new Map(), index: {} };
|
|
104
112
|
const s = async (r) => {
|
|
105
113
|
if (!await n.isDirty(r)) return;
|
|
106
114
|
const d = await t.getForEntity(r);
|
|
107
115
|
if (d.length === 0) return;
|
|
108
|
-
|
|
109
|
-
for (const p of d)
|
|
110
|
-
for (const x of p.updates)
|
|
111
|
-
x.subject_id === r && (y = R(y, x, p.gsn, p.hlc));
|
|
116
|
+
const y = C(d, r);
|
|
112
117
|
if (y === null) return;
|
|
113
|
-
const
|
|
118
|
+
const p = e.entities[r];
|
|
114
119
|
e = {
|
|
115
|
-
entities: { ...e.entities, [r]:
|
|
116
|
-
typeIndex:
|
|
117
|
-
|
|
120
|
+
entities: { ...e.entities, [r]: l(y) },
|
|
121
|
+
typeIndex: I(e.typeIndex, y, p?.type),
|
|
122
|
+
components: g(e.components, p, y),
|
|
123
|
+
index: S(e.index, p, y)
|
|
118
124
|
}, await n.clear(r);
|
|
119
125
|
}, a = async (r) => {
|
|
120
|
-
for (const
|
|
121
|
-
await s(
|
|
122
|
-
},
|
|
126
|
+
for (const c of await n.getDirtyForType(r))
|
|
127
|
+
await s(c);
|
|
128
|
+
}, i = (r) => T(e.index[r]);
|
|
123
129
|
return {
|
|
124
130
|
async get(r) {
|
|
125
131
|
await s(r);
|
|
126
|
-
const
|
|
127
|
-
return
|
|
132
|
+
const c = e.entities[r];
|
|
133
|
+
return c ? l(c) : null;
|
|
128
134
|
},
|
|
129
135
|
async set(r) {
|
|
130
|
-
const
|
|
136
|
+
const c = e.entities[r.id];
|
|
131
137
|
e = {
|
|
132
|
-
entities: { ...e.entities, [r.id]:
|
|
133
|
-
typeIndex:
|
|
134
|
-
|
|
138
|
+
entities: { ...e.entities, [r.id]: l(r) },
|
|
139
|
+
typeIndex: I(e.typeIndex, r, c?.type),
|
|
140
|
+
components: g(e.components, c, r),
|
|
141
|
+
index: S(e.index, c, r)
|
|
135
142
|
};
|
|
136
143
|
},
|
|
137
144
|
async query(r) {
|
|
138
|
-
return await a(r), [...e.typeIndex[r] ?? /* @__PURE__ */ new Set()].map((d) =>
|
|
145
|
+
return await a(r), [...e.typeIndex[r] ?? /* @__PURE__ */ new Set()].map((d) => l(e.entities[d]));
|
|
146
|
+
},
|
|
147
|
+
async queryByComponent(r) {
|
|
148
|
+
return await a(M(r)), _(e.components, r).filter(
|
|
149
|
+
(c) => e.entities[c]?.deleted_hlc === null
|
|
150
|
+
);
|
|
139
151
|
},
|
|
140
152
|
async queryByRelationship({
|
|
141
153
|
as: r,
|
|
142
|
-
type:
|
|
154
|
+
type: c,
|
|
143
155
|
targetId: d
|
|
144
156
|
}) {
|
|
145
|
-
return await a(
|
|
157
|
+
return await a(x), i(k(r, c, d));
|
|
146
158
|
},
|
|
147
159
|
async queryByRelationshipSource({
|
|
148
160
|
as: r,
|
|
149
|
-
type:
|
|
161
|
+
type: c,
|
|
150
162
|
sourceId: d
|
|
151
163
|
}) {
|
|
152
|
-
return await a(
|
|
164
|
+
return await a(x), b(e.index[A(r, c, d)]);
|
|
153
165
|
},
|
|
154
166
|
async queryByMembership(r) {
|
|
155
|
-
return await a(
|
|
167
|
+
return await a(m), i(q(r));
|
|
156
168
|
},
|
|
157
169
|
async queryByMembershipEntity(r) {
|
|
158
|
-
return await a(
|
|
170
|
+
return await a(m), E(e.index[D(r)]);
|
|
159
171
|
},
|
|
160
172
|
async reset() {
|
|
161
|
-
e = { entities: {}, typeIndex: {}, index: {} };
|
|
173
|
+
e = { entities: {}, typeIndex: {}, components: /* @__PURE__ */ new Map(), index: {} };
|
|
162
174
|
}
|
|
163
175
|
};
|
|
164
|
-
},
|
|
176
|
+
}, J = () => {
|
|
165
177
|
let t = { records: {} };
|
|
166
178
|
const n = (e) => t.records[e] ?? null;
|
|
167
179
|
return {
|
|
@@ -170,19 +182,19 @@ const O = (t, n) => {
|
|
|
170
182
|
},
|
|
171
183
|
async set(e, s) {
|
|
172
184
|
t = {
|
|
173
|
-
records: { ...t.records, [e]:
|
|
185
|
+
records: { ...t.records, [e]: f({ ...n(e), cursor: s }) }
|
|
174
186
|
};
|
|
175
187
|
},
|
|
176
188
|
async getRecord(e) {
|
|
177
189
|
return n(e);
|
|
178
190
|
},
|
|
179
191
|
async setRecord(e, s) {
|
|
180
|
-
if (
|
|
181
|
-
const { [e]: a, ...
|
|
182
|
-
t = { records:
|
|
192
|
+
if (L(s)) {
|
|
193
|
+
const { [e]: a, ...i } = t.records;
|
|
194
|
+
t = { records: i };
|
|
183
195
|
return;
|
|
184
196
|
}
|
|
185
|
-
t = { records: { ...t.records, [e]:
|
|
197
|
+
t = { records: { ...t.records, [e]: f(s) } };
|
|
186
198
|
},
|
|
187
199
|
async records() {
|
|
188
200
|
return new Map(Object.entries(t.records));
|
|
@@ -191,7 +203,7 @@ const O = (t, n) => {
|
|
|
191
203
|
t = { records: {} };
|
|
192
204
|
}
|
|
193
205
|
};
|
|
194
|
-
},
|
|
206
|
+
}, U = () => {
|
|
195
207
|
let t = {};
|
|
196
208
|
return {
|
|
197
209
|
async put(n) {
|
|
@@ -202,7 +214,7 @@ const O = (t, n) => {
|
|
|
202
214
|
t = { ...a, [n.action.id]: structuredClone(n) };
|
|
203
215
|
},
|
|
204
216
|
async list() {
|
|
205
|
-
return Object.values(t).map((n) => structuredClone(n)).sort((n, e) =>
|
|
217
|
+
return Object.values(t).map((n) => structuredClone(n)).sort((n, e) => O(n.enqueuedAtHlc, e.enqueuedAtHlc));
|
|
206
218
|
},
|
|
207
219
|
async get(n) {
|
|
208
220
|
const e = t[n];
|
|
@@ -216,11 +228,11 @@ const O = (t, n) => {
|
|
|
216
228
|
t = {};
|
|
217
229
|
}
|
|
218
230
|
};
|
|
219
|
-
},
|
|
231
|
+
}, V = () => {
|
|
220
232
|
let t = null;
|
|
221
233
|
return {
|
|
222
234
|
async load() {
|
|
223
|
-
return
|
|
235
|
+
return F(t) ? t : null;
|
|
224
236
|
},
|
|
225
237
|
async save(n) {
|
|
226
238
|
t = n;
|
|
@@ -229,25 +241,31 @@ const O = (t, n) => {
|
|
|
229
241
|
t = null;
|
|
230
242
|
}
|
|
231
243
|
};
|
|
232
|
-
},
|
|
233
|
-
const t =
|
|
244
|
+
}, X = () => {
|
|
245
|
+
const t = K(), n = Y(), e = z(t, n), s = J(), a = U(), i = V();
|
|
234
246
|
return {
|
|
235
247
|
actions: {
|
|
236
|
-
async append(
|
|
237
|
-
const r = await t.append(
|
|
238
|
-
for (const
|
|
239
|
-
await n.mark(
|
|
248
|
+
async append(o) {
|
|
249
|
+
const r = await t.append(o);
|
|
250
|
+
for (const c of o.updates)
|
|
251
|
+
await n.mark(
|
|
252
|
+
c.subject_id,
|
|
253
|
+
w(c, await e.get(c.subject_id))
|
|
254
|
+
);
|
|
240
255
|
return r;
|
|
241
256
|
},
|
|
242
|
-
async appendMany(
|
|
243
|
-
const r = await t.appendMany(
|
|
244
|
-
for (const
|
|
245
|
-
for (const d of
|
|
246
|
-
await n.mark(
|
|
257
|
+
async appendMany(o) {
|
|
258
|
+
const r = await t.appendMany(o);
|
|
259
|
+
for (const c of o)
|
|
260
|
+
for (const d of c.updates)
|
|
261
|
+
await n.mark(
|
|
262
|
+
d.subject_id,
|
|
263
|
+
w(d, await e.get(d.subject_id))
|
|
264
|
+
);
|
|
247
265
|
return r;
|
|
248
266
|
},
|
|
249
|
-
async readTail(
|
|
250
|
-
return t.readTail(
|
|
267
|
+
async readTail(o) {
|
|
268
|
+
return t.readTail(o);
|
|
251
269
|
},
|
|
252
270
|
async lastPosition() {
|
|
253
271
|
return t.lastPosition();
|
|
@@ -255,26 +273,26 @@ const O = (t, n) => {
|
|
|
255
273
|
async getAll() {
|
|
256
274
|
return t.getAll();
|
|
257
275
|
},
|
|
258
|
-
async getForEntity(
|
|
259
|
-
return t.getForEntity(
|
|
276
|
+
async getForEntity(o) {
|
|
277
|
+
return t.getForEntity(o);
|
|
260
278
|
},
|
|
261
279
|
async clear() {
|
|
262
|
-
await t.clear(), await n.clearAll(), await e.reset(), await
|
|
280
|
+
await t.clear(), await n.clearAll(), await e.reset(), await i.clear();
|
|
263
281
|
}
|
|
264
282
|
},
|
|
265
283
|
entities: e,
|
|
266
284
|
dirtyTracker: {
|
|
267
|
-
async mark(
|
|
268
|
-
await n.mark(
|
|
285
|
+
async mark(o, r) {
|
|
286
|
+
await n.mark(o, r);
|
|
269
287
|
},
|
|
270
|
-
async isDirty(
|
|
271
|
-
return n.isDirty(
|
|
288
|
+
async isDirty(o) {
|
|
289
|
+
return n.isDirty(o);
|
|
272
290
|
},
|
|
273
|
-
async getDirtyForType(
|
|
274
|
-
return n.getDirtyForType(
|
|
291
|
+
async getDirtyForType(o) {
|
|
292
|
+
return n.getDirtyForType(o);
|
|
275
293
|
},
|
|
276
|
-
async clear(
|
|
277
|
-
await n.clear(
|
|
294
|
+
async clear(o) {
|
|
295
|
+
await n.clear(o);
|
|
278
296
|
},
|
|
279
297
|
async clearAll() {
|
|
280
298
|
await n.clearAll();
|
|
@@ -282,15 +300,15 @@ const O = (t, n) => {
|
|
|
282
300
|
},
|
|
283
301
|
cursors: s,
|
|
284
302
|
outbox: a,
|
|
285
|
-
replica:
|
|
286
|
-
async isDirty(
|
|
287
|
-
return n.isDirty(
|
|
303
|
+
replica: i,
|
|
304
|
+
async isDirty(o) {
|
|
305
|
+
return n.isDirty(o);
|
|
288
306
|
},
|
|
289
307
|
async reset() {
|
|
290
|
-
await t.clear(), await n.clearAll(), await e.reset(), await
|
|
308
|
+
await t.clear(), await n.clearAll(), await e.reset(), await i.clear(), await s.clear();
|
|
291
309
|
}
|
|
292
310
|
};
|
|
293
311
|
};
|
|
294
312
|
export {
|
|
295
|
-
|
|
313
|
+
X as createMemoryAdapter
|
|
296
314
|
};
|
package/dist/replica/index.d.ts
CHANGED
|
@@ -10,15 +10,34 @@ interface MaterializeOptions {
|
|
|
10
10
|
readonly residentMapFields?: boolean;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* Applies a single
|
|
13
|
+
* Applies a single component Update to a row during materialization.
|
|
14
14
|
*
|
|
15
|
-
* -
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* `
|
|
19
|
-
*
|
|
15
|
+
* - `merge` / `replace`: apply the component's value at its field under the
|
|
16
|
+
* leaf-level LWW compare. The first such write on a row that does not exist
|
|
17
|
+
* creates it; `replace` is the row's create, while `merge` leaves
|
|
18
|
+
* `exists: false` and `created_hlc: null` because the create has not been
|
|
19
|
+
* seen, so the row is a backfill candidate.
|
|
20
|
+
* - `drop`: remove the field from an existing row; the row survives with its
|
|
21
|
+
* existence and creation time, even when the last component is detached.
|
|
22
|
+
* A tombstone stays a tombstone — detaching a component is not a
|
|
23
|
+
* resurrection.
|
|
24
|
+
* - `delete`: tombstone the row, keeping its type and existence bit. A delete
|
|
25
|
+
* for a row that was never materialized is a no-op, so a per-Update delivery
|
|
26
|
+
* (#473) cannot break the drain.
|
|
27
|
+
*
|
|
28
|
+
* A component write to a tombstoned row resurrects it, exactly as the server's
|
|
29
|
+
* materialization does.
|
|
20
30
|
*/
|
|
21
31
|
declare const applyUpdate: (entity: Entity | null, update: Update, gsn: number, hlc: HLCTimestamp, options?: MaterializeOptions) => Entity | null;
|
|
32
|
+
/**
|
|
33
|
+
* Fold one Action's Updates for `entityId` into a row: every Update addressed
|
|
34
|
+
* to that subject, in `updates` order, under the Action's `gsn` and `hlc`.
|
|
35
|
+
* An Update for another subject belongs to a different row and is skipped.
|
|
36
|
+
*
|
|
37
|
+
* This is the replay step every EntityStore adapter runs for the row it is
|
|
38
|
+
* materializing.
|
|
39
|
+
*/
|
|
40
|
+
declare const foldEntityAction: (entity: Entity | null, entityId: string, action: Action) => Entity | null;
|
|
22
41
|
|
|
23
42
|
/**
|
|
24
43
|
* True when `next` differs from `prev` in anything a reader can observe.
|
|
@@ -80,8 +99,15 @@ type Revision = number;
|
|
|
80
99
|
interface ReplicaChange {
|
|
81
100
|
readonly revision: Revision;
|
|
82
101
|
readonly id: string;
|
|
83
|
-
readonly type: string;
|
|
84
102
|
readonly deleted: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* The components this change touched, so a derived reader invalidates on what
|
|
105
|
+
* it read rather than on the row's whole model. A tombstone transition touches
|
|
106
|
+
* every component the row carries, and a write that moves a row between models
|
|
107
|
+
* names each field under both prefixes. Empty only for a row that carried no
|
|
108
|
+
* component.
|
|
109
|
+
*/
|
|
110
|
+
readonly components: readonly string[];
|
|
85
111
|
}
|
|
86
112
|
/**
|
|
87
113
|
* Whether the replica retains a type's rows. The seam #500 builds on-demand
|
|
@@ -181,7 +207,11 @@ interface Replica {
|
|
|
181
207
|
* with gsn 0, which would hide the row's folded position.
|
|
182
208
|
*/
|
|
183
209
|
base(id: string): Entity | undefined;
|
|
184
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Every row of `model`, whatever optional components it carries. Served by
|
|
212
|
+
* the model index with the overlay's model overrides layered on top.
|
|
213
|
+
*/
|
|
214
|
+
iterate(model: string): readonly Entity[];
|
|
185
215
|
/** Whether the materialization policy holds `type` in full. */
|
|
186
216
|
isHeld(type: string): boolean;
|
|
187
217
|
/**
|
|
@@ -212,6 +242,12 @@ interface Replica {
|
|
|
212
242
|
* live sources pointing at `(as, type, targetId)`, ascending.
|
|
213
243
|
*/
|
|
214
244
|
relationships(query: RelationshipQuery): readonly string[];
|
|
245
|
+
/**
|
|
246
|
+
* Ids currently carrying `component` (`<type>/<field>`) in the composed
|
|
247
|
+
* view, ascending. Component existence is the set of present fields on a
|
|
248
|
+
* row, so a query reading it costs its index rather than a row filter.
|
|
249
|
+
*/
|
|
250
|
+
components(component: string): readonly string[];
|
|
215
251
|
/**
|
|
216
252
|
* Distinct live member entity ids of a group, or distinct live group
|
|
217
253
|
* ids of an entity, ascending.
|
|
@@ -262,10 +298,12 @@ declare const DEFAULT_PINNED_TTL_MS = 30000;
|
|
|
262
298
|
* persists, once pinned on-demand rows are filtered out), `overlay` composes
|
|
263
299
|
* optimistic writes over it, and `effective` caches the composed view for
|
|
264
300
|
* overlaid ids so a read never recomposes.
|
|
265
|
-
* `
|
|
266
|
-
* on top of it.
|
|
301
|
+
* `modelIndex` indexes the base by model; `iterate` layers the overlay's model
|
|
302
|
+
* overrides on top of it. `componentIndex` tracks the composed view's component
|
|
303
|
+
* set alongside the adjacency indices, and is likewise derived rather than
|
|
304
|
+
* persisted.
|
|
267
305
|
*/
|
|
268
306
|
declare const createReplica: (options?: ReplicaOptions) => Replica;
|
|
269
307
|
|
|
270
|
-
export { DEFAULT_PINNED_CAPACITY, DEFAULT_PINNED_TTL_MS, applyUpdate, contentChanged, createReplica, holdAll };
|
|
308
|
+
export { DEFAULT_PINNED_CAPACITY, DEFAULT_PINNED_TTL_MS, applyUpdate, contentChanged, createReplica, foldEntityAction, holdAll };
|
|
271
309
|
export type { MaterializationPolicy, MembershipQuery, RelationshipQuery, Replica, ReplicaChange, ReplicaOptions, ReplicaStats, Revision };
|