@ebbjs/storage 0.2.0 → 1.0.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.
@@ -229,7 +229,8 @@ interface CursorStore {
229
229
  * `conflicted` is a terminal-until-resolved hold: a catch-up pass
230
230
  * classified the Action as a loser, so it is structurally excluded from
231
231
  * `pending()` and never submitted. The application resolves it by
232
- * retrying (back to `pending`) or discarding (removed).
232
+ * sending the Action unchanged (back to `pending`) or clearing it
233
+ * (removed).
233
234
  */
234
235
  type OutboxStatus = "pending" | "acknowledged" | "error" | "conflicted";
235
236
  /**
@@ -267,6 +268,12 @@ interface ConflictSlot {
267
268
  /** The value the server holds at the position after the missed window. */
268
269
  readonly remote: ConflictSide;
269
270
  }
271
+ /** One missed Action that out-dated a buffered write and made it a conflict. */
272
+ interface ConflictRemoteAction {
273
+ readonly actorId: string;
274
+ readonly actionId: string;
275
+ readonly gsn: number;
276
+ }
270
277
  /**
271
278
  * The before/after entity rows for one losing subject. `before` is the
272
279
  * row as the client had it immediately before the catch-up window was
@@ -305,6 +312,18 @@ interface OutboxConflict {
305
312
  * application reads that as "nothing of mine would be lost".
306
313
  */
307
314
  readonly slots: readonly ConflictSlot[];
315
+ /**
316
+ * The looser of the losing subjects' declared scopes: `"entity"` when
317
+ * any losing subject declared entity scope, else `"field"`. The union is
318
+ * spelled inline so storage stays independent of the schema package.
319
+ */
320
+ readonly scope: "field" | "component" | "entity";
321
+ /**
322
+ * The concurrent missed Actions that touched the losing subjects,
323
+ * deduped by action id, in window order. The remote half of the
324
+ * report an application builds a policy from.
325
+ */
326
+ readonly remoteActions: readonly ConflictRemoteAction[];
308
327
  /** The catch-up pass that produced this entry. */
309
328
  readonly catchUpId: string;
310
329
  /**
@@ -353,10 +372,9 @@ interface OutboxEntry {
353
372
  interface OutboxStore {
354
373
  put(entry: OutboxEntry): Promise<void>;
355
374
  /**
356
- * Persist `entry` and delete `deleteId` in one durable operation. Used
357
- * to resolve a conflict by retrying: the fresh re-assert must never
358
- * exist without the conflicted row being gone, nor the row be gone
359
- * without the re-assert persisted.
375
+ * Persist `entry` and delete `deleteId` in one durable operation: the
376
+ * replacement must never exist without the replaced row being gone,
377
+ * nor the row be gone without the replacement persisted.
360
378
  */
361
379
  putAndDelete(entry: OutboxEntry, deleteId: string): Promise<void>;
362
380
  list(): Promise<readonly OutboxEntry[]>;
@@ -1,113 +1,117 @@
1
- import { unwrap as _, openDB as b } from "idb";
2
- import { m as k, a as O, i as T, c as v } from "../cursor-record-D39emd6R.js";
3
- import { c as N, l as P, f as R, a as C, b as B, j as M, k as H, n as F, q as G, s as L, M as j, m as K, p as I, r as U, i as Y } from "../replica-record-D3lkFACh.js";
4
- import { compare as V } from "@ebbjs/core";
5
- const z = 8, m = "actionPosition", A = "replica", Q = (t, e, a, n) => {
1
+ import { unwrap as h, openDB as D } from "idb";
2
+ import { m as _, a as T, i as O, c as v } from "../cursor-record-D39emd6R.js";
3
+ import { c as R, l as b, f as k, a as N, b as P, j as C, k as B, q as M, t as F, u as H, M as g, m as L, s as x, r as G, i as K } from "../replica-record-D5gGEoVQ.js";
4
+ import { compare as U } from "@ebbjs/core";
5
+ const Y = 10, V = 9, w = "actionPosition", j = "replica", z = (t, e, a, o) => {
6
+ const s = e > 0 && e < V;
7
+ if (s)
8
+ for (const r of Array.from(t.objectStoreNames))
9
+ t.deleteObjectStore(r);
6
10
  if (t.objectStoreNames.contains("actions"))
7
- n.objectStore("actions").indexNames.contains("position") || n.objectStore("actions").createIndex("position", "position");
11
+ o.objectStore("actions").indexNames.contains("position") || o.objectStore("actions").createIndex("position", "position");
8
12
  else {
9
- const s = t.createObjectStore("actions", { keyPath: "id" });
10
- s.createIndex("subject_id", "subject_ids", { multiEntry: !0 }), s.createIndex("position", "position");
13
+ const r = t.createObjectStore("actions", { keyPath: "id" });
14
+ r.createIndex("subject_id", "subject_ids", { multiEntry: !0 }), r.createIndex("position", "position");
11
15
  }
12
16
  if (!t.objectStoreNames.contains("entities")) {
13
- const s = t.createObjectStore("entities", { keyPath: "id" });
14
- s.indexNames.contains("type") || s.createIndex("type", "type");
17
+ const r = t.createObjectStore("entities", { keyPath: "id" });
18
+ r.indexNames.contains("type") || r.createIndex("type", "type");
15
19
  }
16
20
  if (t.objectStoreNames.contains("relationships") || t.createObjectStore("relationships", { keyPath: "key" }), !t.objectStoreNames.contains("dirty")) {
17
- const s = t.createObjectStore("dirty", { keyPath: "entityId" });
18
- s.indexNames.contains("entityType") || s.createIndex("entityType", "entityType");
21
+ const r = t.createObjectStore("dirty", { keyPath: "entityId" });
22
+ r.indexNames.contains("entityType") || r.createIndex("entityType", "entityType");
19
23
  }
20
- t.objectStoreNames.contains("cursors") || t.createObjectStore("cursors", { keyPath: "groupId" }), t.objectStoreNames.contains("outbox") || t.createObjectStore("outbox", { keyPath: "action.id" }), t.objectStoreNames.contains("sequence") || t.createObjectStore("sequence", { keyPath: "id" }), t.objectStoreNames.contains("replica") || t.createObjectStore("replica", { keyPath: ["type", "id"] }), t.objectStoreNames.contains("replicaCursor") || t.createObjectStore("replicaCursor", { keyPath: "id" }), e < 8 && J(n);
21
- }, J = (t) => {
22
- const e = _(t.objectStore("actions")), a = t.objectStore("sequence");
23
- let n = 0;
24
+ t.objectStoreNames.contains("cursors") || t.createObjectStore("cursors", { keyPath: "groupId" }), t.objectStoreNames.contains("outbox") || t.createObjectStore("outbox", { keyPath: "action.id" }), t.objectStoreNames.contains("sequence") || t.createObjectStore("sequence", { keyPath: "id" }), t.objectStoreNames.contains("replica") || t.createObjectStore("replica", { keyPath: ["type", "id"] }), t.objectStoreNames.contains("replicaCursor") || t.createObjectStore("replicaCursor", { keyPath: "id" }), s ? o.objectStore("sequence").put({ id: w, value: 0 }) : e < 8 && Q(o);
25
+ }, Q = (t) => {
26
+ const e = h(t.objectStore("actions")), a = t.objectStore("sequence");
27
+ let o = 0;
24
28
  const s = e.openCursor();
25
29
  s.onsuccess = () => {
26
- const c = s.result;
27
- if (c === null) {
28
- a.put({ id: m, value: n });
30
+ const r = s.result;
31
+ if (r === null) {
32
+ a.put({ id: w, value: o });
29
33
  return;
30
34
  }
31
- n += 1, c.update({ ...c.value, position: n }), c.continue();
35
+ o += 1, r.update({ ...r.value, position: o }), r.continue();
32
36
  };
33
- }, W = (t) => {
37
+ }, X = (t) => {
34
38
  const e = /* @__PURE__ */ new Set();
35
39
  for (const a of t.updates)
36
40
  e.add(a.subject_id);
37
41
  return Array.from(e);
38
- }, x = (t) => {
39
- const { subject_ids: e, position: a, ...n } = t;
40
- return n;
41
- }, h = async (t, e) => {
42
- const a = t.objectStore("actions"), n = await a.get(e.id), s = await t.objectStore("sequence").get(m), c = n === void 0, l = c ? (s?.value ?? 0) + 1 : n.position, p = n === void 0 ? e : { ...e, updates: k(n.updates, e.updates) }, y = O(n?.updates ?? [], e.updates);
43
- return await a.put({ ...p, position: l, subject_ids: W(p) }), c && await t.objectStore("sequence").put({ id: m, value: l }), { action: p, position: l, added: y };
44
- }, X = (t) => ({
42
+ }, S = (t) => {
43
+ const { subject_ids: e, position: a, ...o } = t;
44
+ return o;
45
+ }, I = async (t, e) => {
46
+ const a = t.objectStore("actions"), o = await a.get(e.id), s = await t.objectStore("sequence").get(w), r = o === void 0, l = r ? (s?.value ?? 0) + 1 : o.position, p = o === void 0 ? e : { ...e, updates: _(o.updates, e.updates) }, y = T(o?.updates ?? [], e.updates);
47
+ return await a.put({ ...p, position: l, subject_ids: X(p) }), r && await t.objectStore("sequence").put({ id: w, value: l }), { action: p, position: l, added: y };
48
+ }, J = (t) => ({
45
49
  async append(e) {
46
- const a = t.transaction(["actions", "sequence"], "readwrite"), n = await h(a, e);
47
- return await a.done, n;
50
+ const a = t.transaction(["actions", "sequence"], "readwrite"), o = await I(a, e);
51
+ return await a.done, o;
48
52
  },
49
53
  async appendMany(e) {
50
- const a = t.transaction(["actions", "sequence"], "readwrite"), n = [];
54
+ const a = t.transaction(["actions", "sequence"], "readwrite"), o = [];
51
55
  for (const s of e)
52
- n.push(await h(a, s));
53
- return await a.done, n;
56
+ o.push(await I(a, s));
57
+ return await a.done, o;
54
58
  },
55
59
  async readTail(e) {
56
- const a = t.transaction("actions", "readonly"), n = await a.store.index("position").getAll(IDBKeyRange.lowerBound(e, !0));
57
- return await a.done, n.map((s) => ({
58
- action: x(s),
60
+ const a = t.transaction("actions", "readonly"), o = await a.store.index("position").getAll(IDBKeyRange.lowerBound(e, !0));
61
+ return await a.done, o.map((s) => ({
62
+ action: S(s),
59
63
  position: s.position,
60
64
  added: s.updates
61
65
  }));
62
66
  },
63
67
  async lastPosition() {
64
- return (await t.get("sequence", m))?.value ?? 0;
68
+ return (await t.get("sequence", w))?.value ?? 0;
65
69
  },
66
70
  async getAll() {
67
- return (await t.getAll("actions")).map(x);
71
+ return (await t.getAll("actions")).map(S);
68
72
  },
69
73
  async getForEntity(e) {
70
- const a = t.transaction("actions", "readonly"), n = await a.store.index("subject_id").getAll(e);
71
- return await a.done, n.map(x).sort((c, l) => c.gsn - l.gsn);
74
+ const a = t.transaction("actions", "readonly"), o = await a.store.index("subject_id").getAll(e);
75
+ return await a.done, o.map(S).sort((r, l) => r.gsn - l.gsn);
72
76
  },
73
77
  async clear() {
74
78
  const e = t.transaction(["actions", "sequence"], "readwrite");
75
- await e.objectStore("actions").clear(), await e.objectStore("sequence").put({ id: m, value: 0 }), await e.done;
79
+ await e.objectStore("actions").clear(), await e.objectStore("sequence").put({ id: w, value: 0 }), await e.done;
76
80
  }
77
- }), q = (t) => t === void 0 ? null : v(t), Z = (t) => {
78
- const e = async (a) => q(await t.get("cursors", a));
81
+ }), A = (t) => t === void 0 ? null : v(t), W = (t) => {
82
+ const e = async (a) => A(await t.get("cursors", a));
79
83
  return {
80
84
  async get(a) {
81
85
  return (await t.get("cursors", a))?.cursor ?? null;
82
86
  },
83
- async set(a, n) {
87
+ async set(a, o) {
84
88
  const s = await e(a);
85
- await t.put("cursors", { groupId: a, ...s, cursor: n });
89
+ await t.put("cursors", { groupId: a, ...s, cursor: o });
86
90
  },
87
91
  async getRecord(a) {
88
92
  return e(a);
89
93
  },
90
- async setRecord(a, n) {
91
- if (T(n)) {
94
+ async setRecord(a, o) {
95
+ if (O(o)) {
92
96
  await t.delete("cursors", a);
93
97
  return;
94
98
  }
95
99
  await t.put("cursors", {
96
100
  groupId: a,
97
- ...v(n)
101
+ ...v(o)
98
102
  });
99
103
  },
100
104
  async records() {
101
- const a = await t.getAll("cursors"), n = /* @__PURE__ */ new Map();
105
+ const a = await t.getAll("cursors"), o = /* @__PURE__ */ new Map();
102
106
  for (const s of a)
103
- n.set(s.groupId, q(s) ?? {});
104
- return n;
107
+ o.set(s.groupId, A(s) ?? {});
108
+ return o;
105
109
  },
106
110
  async clear() {
107
111
  await t.clear("cursors");
108
112
  }
109
113
  };
110
- }, $ = (t) => ({
114
+ }, Z = (t) => ({
111
115
  async mark(e, a) {
112
116
  await t.get("dirty", e) || await t.put("dirty", { entityId: e, entityType: a });
113
117
  },
@@ -115,7 +119,7 @@ const z = 8, m = "actionPosition", A = "replica", Q = (t, e, a, n) => {
115
119
  return await t.get("dirty", e) !== void 0;
116
120
  },
117
121
  async getDirtyForType(e) {
118
- return (await t.getAllFromIndex("dirty", "entityType", e)).map((n) => n.entityId);
122
+ return (await t.getAllFromIndex("dirty", "entityType", e)).map((o) => o.entityId);
119
123
  },
120
124
  async clear(e) {
121
125
  await t.delete("dirty", e);
@@ -123,85 +127,86 @@ const z = 8, m = "actionPosition", A = "replica", Q = (t, e, a, n) => {
123
127
  async clearAll() {
124
128
  await t.clear("dirty");
125
129
  }
126
- }), tt = (t, e, a) => {
127
- const n = (o) => structuredClone(o), s = async (o, r) => {
128
- r === null ? await t.delete("relationships", o) : await t.put("relationships", { key: o, rows: r });
129
- }, c = async (o) => {
130
- const r = await t.get("relationships", o.key);
131
- await s(o.key, L(r?.rows, o));
132
- }, l = async (o) => {
133
- const r = await t.get("relationships", o.key);
134
- await s(o.key, G(r?.rows, o));
135
- }, p = async (o, r) => {
136
- for (const u of [M(o, r), H(o, r)])
137
- u.remove !== null && await l(u.remove), u.add !== null && await c(u.add);
138
- }, y = async (o) => {
139
- if (!await a.isDirty(o)) return;
140
- const u = await e.getForEntity(o);
130
+ }), $ = (t, e, a) => {
131
+ const o = (n) => structuredClone(n), s = async (n, i) => {
132
+ i === null ? await t.delete("relationships", n) : await t.put("relationships", { key: n, rows: i });
133
+ }, r = async (n) => {
134
+ const i = await t.get("relationships", n.key);
135
+ await s(n.key, H(i?.rows, n));
136
+ }, l = async (n) => {
137
+ const i = await t.get("relationships", n.key);
138
+ await s(n.key, F(i?.rows, n));
139
+ }, p = async (n, i) => {
140
+ for (const u of [C(n, i), B(n, i)])
141
+ u.remove !== null && await l(u.remove), u.add !== null && await r(u.add);
142
+ }, y = async (n) => {
143
+ if (!await a.isDirty(n)) return;
144
+ const u = await e.getForEntity(n);
141
145
  if (u.length === 0) return;
142
- let w = null;
143
- for (const g of u)
144
- for (const S of g.updates)
145
- S.subject_id === o && (w = F(w, S, g.gsn, g.hlc));
146
- if (w === null) return;
147
- const D = await t.get("entities", o);
148
- await t.put("entities", n(w)), await p(D, w), await a.clear(o);
149
- }, i = async (o) => {
150
- for (const r of await a.getDirtyForType(o))
151
- await y(r);
152
- }, d = async (o) => {
153
- const r = await t.get("relationships", o);
154
- return B(r?.rows);
146
+ const m = M(u, n);
147
+ if (m === null) return;
148
+ const q = await t.get("entities", n);
149
+ await t.put("entities", o(m)), await p(q, m), await a.clear(n);
150
+ }, c = async (n) => {
151
+ for (const i of await a.getDirtyForType(n))
152
+ await y(i);
153
+ }, d = async (n) => {
154
+ const i = await t.get("relationships", n);
155
+ return P(i?.rows);
155
156
  };
156
157
  return {
157
- async get(o) {
158
- await y(o);
159
- const r = await t.get("entities", o);
160
- return r ? n(r) : null;
158
+ async get(n) {
159
+ await y(n);
160
+ const i = await t.get("entities", n);
161
+ return i ? o(i) : null;
161
162
  },
162
- async set(o) {
163
- const r = await t.get("entities", o.id);
164
- await t.put("entities", n(o)), await p(r, o);
163
+ async set(n) {
164
+ const i = await t.get("entities", n.id);
165
+ await t.put("entities", o(n)), await p(i, n);
165
166
  },
166
- async query(o) {
167
- return await i(o), (await t.getAllFromIndex("entities", "type", o)).map(n);
167
+ async query(n) {
168
+ return await c(n), (await t.getAllFromIndex("entities", "type", n)).map(o);
168
169
  },
169
170
  async queryByRelationship({
170
- as: o,
171
- type: r,
171
+ as: n,
172
+ type: i,
172
173
  targetId: u
173
174
  }) {
174
- return await i(I), d(U(o, r, u));
175
+ return await c(x), d(G(n, i, u));
175
176
  },
176
177
  async queryByRelationshipSource({
177
- as: o,
178
- type: r,
178
+ as: n,
179
+ type: i,
179
180
  sourceId: u
180
181
  }) {
181
- await i(I);
182
- const w = await t.get("relationships", R(o, r, u));
183
- return C(w?.rows);
182
+ await c(x);
183
+ const m = await t.get("relationships", k(n, i, u));
184
+ return N(m?.rows);
184
185
  },
185
- async queryByMembership(o) {
186
- return await i(j), d(K(o));
186
+ async queryByMembership(n) {
187
+ return await c(g), d(L(n));
187
188
  },
188
- async queryByMembershipEntity(o) {
189
- await i(j);
190
- const r = await t.get("relationships", N(o));
191
- return P(r?.rows);
189
+ async queryByMembershipEntity(n) {
190
+ await c(g);
191
+ const i = await t.get("relationships", R(n));
192
+ return b(i?.rows);
192
193
  },
193
194
  async reset() {
194
195
  await t.clear("relationships"), await t.clear("entities");
195
196
  }
196
197
  };
197
- }, E = (t) => ({
198
+ }, tt = (t) => ({
199
+ ...t,
200
+ scope: t.scope ?? "field",
201
+ remoteActions: t.remoteActions ?? []
202
+ }), E = (t) => ({
198
203
  action: t.action,
199
204
  status: t.status,
200
205
  enqueuedAtHlc: t.enqueuedAtHlc,
201
206
  // A row predating the field reads as "always concurrent", never
202
207
  // silently dropped.
203
208
  basedOnGsn: t.basedOnGsn ?? 0,
204
- conflict: t.conflict
209
+ conflict: t.conflict === void 0 ? void 0 : tt(t.conflict)
205
210
  }), et = (t) => ({
206
211
  async put(e) {
207
212
  await t.put("outbox", {
@@ -213,17 +218,17 @@ const z = 8, m = "actionPosition", A = "replica", Q = (t, e, a, n) => {
213
218
  });
214
219
  },
215
220
  async putAndDelete(e, a) {
216
- const n = t.transaction("outbox", "readwrite");
217
- n.store.put({
221
+ const o = t.transaction("outbox", "readwrite");
222
+ o.store.put({
218
223
  action: e.action,
219
224
  status: e.status,
220
225
  enqueuedAtHlc: e.enqueuedAtHlc,
221
226
  basedOnGsn: e.basedOnGsn,
222
227
  conflict: e.conflict
223
- }), n.store.delete(a), await n.done;
228
+ }), o.store.delete(a), await o.done;
224
229
  },
225
230
  async list() {
226
- return (await t.getAll("outbox")).map(E).sort((a, n) => V(a.enqueuedAtHlc, n.enqueuedAtHlc));
231
+ return (await t.getAll("outbox")).map(E).sort((a, o) => U(a.enqueuedAtHlc, o.enqueuedAtHlc));
227
232
  },
228
233
  async get(e) {
229
234
  const a = await t.get("outbox", e);
@@ -237,26 +242,26 @@ const z = 8, m = "actionPosition", A = "replica", Q = (t, e, a, n) => {
237
242
  }
238
243
  }), at = (t) => ({
239
244
  async load() {
240
- const e = t.transaction(["replica", "replicaCursor"], "readonly"), a = await e.objectStore("replicaCursor").get(A);
245
+ const e = t.transaction(["replica", "replicaCursor"], "readonly"), a = await e.objectStore("replicaCursor").get(j);
241
246
  if (a === void 0)
242
247
  return await e.done, null;
243
- const n = await e.objectStore("replica").getAll();
248
+ const o = await e.objectStore("replica").getAll();
244
249
  await e.done;
245
250
  const s = {
246
251
  format: a.format,
247
252
  position: a.position,
248
253
  revision: a.revision,
249
- entities: n
254
+ entities: o
250
255
  };
251
- return Y(s) ? s : null;
256
+ return K(s) ? s : null;
252
257
  },
253
258
  async save(e) {
254
- const a = t.transaction(["replica", "replicaCursor"], "readwrite"), n = a.objectStore("replica");
255
- await n.clear();
259
+ const a = t.transaction(["replica", "replicaCursor"], "readwrite"), o = a.objectStore("replica");
260
+ await o.clear();
256
261
  for (const s of e.entities)
257
- await n.put(s);
262
+ await o.put(s);
258
263
  await a.objectStore("replicaCursor").put({
259
- id: A,
264
+ id: j,
260
265
  format: e.format,
261
266
  position: e.position,
262
267
  revision: e.revision
@@ -267,53 +272,53 @@ const z = 8, m = "actionPosition", A = "replica", Q = (t, e, a, n) => {
267
272
  await e.objectStore("replica").clear(), await e.objectStore("replicaCursor").clear(), await e.done;
268
273
  }
269
274
  }), it = async (t = {}) => {
270
- const e = t.dbName ?? "ebb-storage", a = await b(e, z, {
271
- upgrade: Q
272
- }), n = X(a), s = $(a), c = tt(a, n, s), l = Z(a), p = et(a), y = at(a);
275
+ const e = t.dbName ?? "ebb-storage", a = await D(e, Y, {
276
+ upgrade: z
277
+ }), o = J(a), s = Z(a), r = $(a, o, s), l = W(a), p = et(a), y = at(a);
273
278
  return {
274
279
  actions: {
275
- async append(i) {
276
- const d = await n.append(i);
277
- for (const f of i.updates)
280
+ async append(c) {
281
+ const d = await o.append(c);
282
+ for (const f of c.updates)
278
283
  await s.mark(f.subject_id, f.subject_type);
279
284
  return d;
280
285
  },
281
- async appendMany(i) {
282
- const d = await n.appendMany(i);
283
- for (const f of i)
284
- for (const o of f.updates)
285
- await s.mark(o.subject_id, o.subject_type);
286
+ async appendMany(c) {
287
+ const d = await o.appendMany(c);
288
+ for (const f of c)
289
+ for (const n of f.updates)
290
+ await s.mark(n.subject_id, n.subject_type);
286
291
  return d;
287
292
  },
288
- async readTail(i) {
289
- return n.readTail(i);
293
+ async readTail(c) {
294
+ return o.readTail(c);
290
295
  },
291
296
  async lastPosition() {
292
- return n.lastPosition();
297
+ return o.lastPosition();
293
298
  },
294
299
  async getAll() {
295
- return n.getAll();
300
+ return o.getAll();
296
301
  },
297
- async getForEntity(i) {
298
- return n.getForEntity(i);
302
+ async getForEntity(c) {
303
+ return o.getForEntity(c);
299
304
  },
300
305
  async clear() {
301
- await n.clear(), await s.clearAll(), await c.reset(), await y.clear();
306
+ await o.clear(), await s.clearAll(), await r.reset(), await y.clear();
302
307
  }
303
308
  },
304
- entities: c,
309
+ entities: r,
305
310
  dirtyTracker: {
306
- async mark(i, d) {
307
- await s.mark(i, d);
311
+ async mark(c, d) {
312
+ await s.mark(c, d);
308
313
  },
309
- async isDirty(i) {
310
- return s.isDirty(i);
314
+ async isDirty(c) {
315
+ return s.isDirty(c);
311
316
  },
312
- async getDirtyForType(i) {
313
- return s.getDirtyForType(i);
317
+ async getDirtyForType(c) {
318
+ return s.getDirtyForType(c);
314
319
  },
315
- async clear(i) {
316
- await s.clear(i);
320
+ async clear(c) {
321
+ await s.clear(c);
317
322
  },
318
323
  async clearAll() {
319
324
  await s.clearAll();
@@ -322,11 +327,11 @@ const z = 8, m = "actionPosition", A = "replica", Q = (t, e, a, n) => {
322
327
  cursors: l,
323
328
  outbox: p,
324
329
  replica: y,
325
- async isDirty(i) {
326
- return s.isDirty(i);
330
+ async isDirty(c) {
331
+ return s.isDirty(c);
327
332
  },
328
333
  async reset() {
329
- await n.clear(), await s.clearAll(), await c.reset(), await y.clear(), await l.clear();
334
+ await o.clear(), await s.clearAll(), await r.reset(), await y.clear(), await l.clear();
330
335
  }
331
336
  };
332
337
  };
@@ -229,7 +229,8 @@ interface CursorStore {
229
229
  * `conflicted` is a terminal-until-resolved hold: a catch-up pass
230
230
  * classified the Action as a loser, so it is structurally excluded from
231
231
  * `pending()` and never submitted. The application resolves it by
232
- * retrying (back to `pending`) or discarding (removed).
232
+ * sending the Action unchanged (back to `pending`) or clearing it
233
+ * (removed).
233
234
  */
234
235
  type OutboxStatus = "pending" | "acknowledged" | "error" | "conflicted";
235
236
  /**
@@ -267,6 +268,12 @@ interface ConflictSlot {
267
268
  /** The value the server holds at the position after the missed window. */
268
269
  readonly remote: ConflictSide;
269
270
  }
271
+ /** One missed Action that out-dated a buffered write and made it a conflict. */
272
+ interface ConflictRemoteAction {
273
+ readonly actorId: string;
274
+ readonly actionId: string;
275
+ readonly gsn: number;
276
+ }
270
277
  /**
271
278
  * The before/after entity rows for one losing subject. `before` is the
272
279
  * row as the client had it immediately before the catch-up window was
@@ -305,6 +312,18 @@ interface OutboxConflict {
305
312
  * application reads that as "nothing of mine would be lost".
306
313
  */
307
314
  readonly slots: readonly ConflictSlot[];
315
+ /**
316
+ * The looser of the losing subjects' declared scopes: `"entity"` when
317
+ * any losing subject declared entity scope, else `"field"`. The union is
318
+ * spelled inline so storage stays independent of the schema package.
319
+ */
320
+ readonly scope: "field" | "component" | "entity";
321
+ /**
322
+ * The concurrent missed Actions that touched the losing subjects,
323
+ * deduped by action id, in window order. The remote half of the
324
+ * report an application builds a policy from.
325
+ */
326
+ readonly remoteActions: readonly ConflictRemoteAction[];
308
327
  /** The catch-up pass that produced this entry. */
309
328
  readonly catchUpId: string;
310
329
  /**
@@ -353,10 +372,9 @@ interface OutboxEntry {
353
372
  interface OutboxStore {
354
373
  put(entry: OutboxEntry): Promise<void>;
355
374
  /**
356
- * Persist `entry` and delete `deleteId` in one durable operation. Used
357
- * to resolve a conflict by retrying: the fresh re-assert must never
358
- * exist without the conflicted row being gone, nor the row be gone
359
- * without the re-assert persisted.
375
+ * Persist `entry` and delete `deleteId` in one durable operation: the
376
+ * replacement must never exist without the replaced row being gone,
377
+ * nor the row be gone without the replacement persisted.
360
378
  */
361
379
  putAndDelete(entry: OutboxEntry, deleteId: string): Promise<void>;
362
380
  list(): Promise<readonly OutboxEntry[]>;