@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.
@@ -1,10 +1,10 @@
1
1
  import { isFieldMap as u, mergeFieldValue as m, latestHlc as P, isFieldLeaf as D } from "@ebbjs/core";
2
- const L = 500, M = 64, I = /* @__PURE__ */ new WeakMap(), N = (e) => {
2
+ const L = 500, E = 64, I = /* @__PURE__ */ new WeakMap(), N = (e) => {
3
3
  let r = 2166136261;
4
4
  for (let t = 0; t < e.length; t += 1)
5
5
  r ^= e.charCodeAt(t), r = Math.imul(r, 16777619) >>> 0;
6
6
  return r >>> 0;
7
- }, E = (e) => N(e) % M, C = () => Array.from({ length: M }, () => ({})), R = (e, r) => {
7
+ }, M = (e) => N(e) % E, C = () => Array.from({ length: E }, () => ({})), R = (e, r) => {
8
8
  let t = r;
9
9
  const n = {
10
10
  get map() {
@@ -24,9 +24,9 @@ const L = 500, M = 64, I = /* @__PURE__ */ new WeakMap(), N = (e) => {
24
24
  }, V = (e) => e.map, W = (e) => {
25
25
  const r = C(), t = {};
26
26
  for (const [n, s] of Object.entries(e))
27
- r[E(n)][n] = s, t[n] = s;
27
+ r[M(n)][n] = s, t[n] = s;
28
28
  return R(r, t);
29
- }, b = (e) => !u(e) || _(e) ? e : Object.keys(e.map).length >= L ? W(e.map) : e, k = (e) => {
29
+ }, b = (e) => !u(e) || _(e) ? e : Object.keys(e.map).length >= L ? W(e.map) : e, y = (e) => {
30
30
  let r;
31
31
  for (const [t, n] of Object.entries(e)) {
32
32
  const s = b(n);
@@ -41,7 +41,7 @@ const L = 500, M = 64, I = /* @__PURE__ */ new WeakMap(), N = (e) => {
41
41
  }, Y = (e, r) => {
42
42
  const t = H(e), n = [...t], s = /* @__PURE__ */ new Map();
43
43
  for (const [o, d] of Object.entries(r.map)) {
44
- const a = E(o), c = (s.get(a) ?? t[a])[o], F = m(c, d);
44
+ const a = M(o), c = (s.get(a) ?? t[a])[o], F = m(c, d);
45
45
  if (F === c) continue;
46
46
  let i = s.get(a);
47
47
  i === void 0 && (i = { ...t[a] }, s.set(a, i)), i[o] = F;
@@ -54,7 +54,7 @@ const L = 500, M = 64, I = /* @__PURE__ */ new WeakMap(), N = (e) => {
54
54
  return u(r) ? Y(e, r) : r;
55
55
  const t = m(e, r);
56
56
  return t === e ? t : b(t);
57
- }, h = (e) => e.data?.fields ?? {}, de = { residentMapFields: !0 }, B = (e, r, t) => {
57
+ }, h = (e) => e.data?.fields ?? {}, ce = { residentMapFields: !0 }, B = (e, r, t) => {
58
58
  const n = h(r), s = t?.residentMapFields ? $ : m;
59
59
  let o;
60
60
  for (const [d, a] of Object.entries(n)) {
@@ -62,17 +62,17 @@ const L = 500, M = 64, I = /* @__PURE__ */ new WeakMap(), N = (e) => {
62
62
  c !== p && (o === void 0 && (o = { ...e.fields }), o[d] = c);
63
63
  }
64
64
  return o === void 0 ? e : { fields: o };
65
- }, y = (e, r) => {
65
+ }, k = (e, r) => {
66
66
  const t = r(e.data.fields);
67
67
  return t === e.data.fields ? e : { ...e, data: { ...e.data, fields: t } };
68
- }, ae = (e) => y(e, k), ce = (e) => y(e, K), ue = (e, r, t, n, s) => {
68
+ }, ue = (e) => k(e, y), ie = (e) => k(e, K), U = (e, r, t, n, s) => {
69
69
  switch (r.method) {
70
70
  case "put":
71
71
  return {
72
72
  id: r.subject_id,
73
73
  type: r.subject_type,
74
74
  data: {
75
- fields: s?.residentMapFields ? k(h(r)) : h(r)
75
+ fields: s?.residentMapFields ? y(h(r)) : h(r)
76
76
  },
77
77
  created_hlc: n,
78
78
  updated_hlc: n,
@@ -94,74 +94,86 @@ const L = 500, M = 64, I = /* @__PURE__ */ new WeakMap(), N = (e) => {
94
94
  last_gsn: Math.max(e.last_gsn, t)
95
95
  } : null;
96
96
  }
97
- }, S = "relationship", f = "entityGroup", O = "groups", v = (e, r, t) => `${e}\0${r}\0${t}`, j = (e, r, t) => `${v(e, r, t)}`, U = (e) => v(O, f, e), q = (e) => j(O, f, e), l = (e, r) => {
97
+ }, q = (e, r, t) => {
98
+ let n = e;
99
+ for (const s of t.updates)
100
+ s.subject_id === r && (n = U(n, s, t.gsn, t.hlc));
101
+ return n;
102
+ }, fe = (e, r) => {
103
+ let t = null;
104
+ for (const n of e)
105
+ t = q(t, r, n);
106
+ return t;
107
+ }, S = "relationship", f = "entityGroup", j = "groups", v = (e, r, t) => `${e}\0${r}\0${t}`, O = (e, r, t) => `${v(e, r, t)}`, z = (e) => v(j, f, e), G = (e) => O(j, f, e), l = (e, r) => {
98
108
  const t = e.data?.fields?.[r];
99
109
  if (t === void 0 || !D(t)) return null;
100
110
  const n = t.value;
101
111
  return typeof n == "string" ? n : null;
102
- }, z = (e) => {
112
+ }, J = (e) => {
103
113
  if (e.type !== S || e.deleted_hlc !== null) return null;
104
114
  const r = l(e, "field"), t = l(e, "type"), n = l(e, "target_id"), s = l(e, "source_id");
105
115
  return r === null || t === null || n === null || s === null ? null : { key: v(r, t, n), rowId: e.id, value: s };
106
- }, G = (e) => {
116
+ }, Q = (e) => {
107
117
  if (e.type !== S || e.deleted_hlc !== null) return null;
108
118
  const r = l(e, "field"), t = l(e, "type"), n = l(e, "source_id"), s = l(e, "target_id");
109
- return r === null || t === null || n === null || s === null ? null : { key: j(r, t, n), rowId: e.id, value: s };
110
- }, J = (e) => {
119
+ return r === null || t === null || n === null || s === null ? null : { key: O(r, t, n), rowId: e.id, value: s };
120
+ }, X = (e) => {
111
121
  if (e.type !== f || e.deleted_hlc !== null) return null;
112
122
  const r = l(e, "group_id"), t = l(e, "entity_id");
113
- return r === null || t === null ? null : { key: U(r), rowId: e.id, value: t };
114
- }, Q = (e) => {
123
+ return r === null || t === null ? null : { key: z(r), rowId: e.id, value: t };
124
+ }, Z = (e) => {
115
125
  if (e.type !== f || e.deleted_hlc !== null) return null;
116
126
  const r = l(e, "group_id"), t = l(e, "entity_id");
117
- return r === null || t === null ? null : { key: q(t), rowId: e.id, value: r };
118
- }, X = (e) => z(e) ?? J(e), Z = (e) => G(e) ?? Q(e), ee = (e) => [...e].sort(), ie = (e) => ee(new Set(Object.values(e ?? {}))), x = (e) => Object.entries(e ?? {}).map(([r, t]) => ({ rowId: r, value: t })).sort(
127
+ return r === null || t === null ? null : { key: G(t), rowId: e.id, value: r };
128
+ }, ee = (e) => J(e) ?? X(e), re = (e) => Q(e) ?? Z(e), te = (e) => [...e].sort(), pe = (e) => te(new Set(Object.values(e ?? {}))), x = (e) => Object.entries(e ?? {}).map(([r, t]) => ({ rowId: r, value: t })).sort(
119
129
  (r, t) => r.value === t.value ? r.rowId.localeCompare(t.rowId) : r.value.localeCompare(t.value)
120
- ), fe = (e) => x(e).map(({ rowId: r, value: t }) => ({ relationshipId: r, targetId: t })), pe = (e) => x(e).map(({ rowId: r, value: t }) => ({ membershipId: r, groupId: t })), A = (e, r, t) => {
130
+ ), he = (e) => x(e).map(({ rowId: r, value: t }) => ({ relationshipId: r, targetId: t })), me = (e) => x(e).map(({ rowId: r, value: t }) => ({ membershipId: r, groupId: t })), A = (e, r, t) => {
121
131
  const n = { ...e };
122
132
  return t ? n[r.rowId] = r.value : delete n[r.rowId], n;
123
- }, re = (e, r) => A(e, r, !0), te = (e, r) => {
133
+ }, ne = (e, r) => A(e, r, !0), se = (e, r) => {
124
134
  const t = A(e, r, !1);
125
135
  return Object.keys(t).length === 0 ? null : t;
126
- }, ne = (e, r) => e?.key === r?.key && e?.rowId === r?.rowId && e?.value === r?.value, T = (e) => (r, t) => {
136
+ }, oe = (e, r) => e?.key === r?.key && e?.rowId === r?.rowId && e?.value === r?.value, T = (e) => (r, t) => {
127
137
  const n = r === void 0 ? null : e(r), s = t === void 0 ? null : e(t);
128
- return ne(n, s) ? { remove: null, add: null } : { remove: n, add: s };
129
- }, he = T(X), me = T(Z), w = (e, r, t) => {
138
+ return oe(n, s) ? { remove: null, add: null } : { remove: n, add: s };
139
+ }, Ie = T(ee), _e = T(re), w = (e, r, t) => {
130
140
  if (t === null) {
131
141
  const n = { ...e };
132
142
  return delete n[r.key], n;
133
143
  }
134
144
  return { ...e, [r.key]: t };
135
- }, Ie = (e, r) => {
145
+ }, ve = (e, r) => {
136
146
  if (r.remove === null && r.add === null) return e;
137
147
  let t = e;
138
- return r.remove !== null && (t = w(t, r.remove, te(t[r.remove.key], r.remove))), r.add !== null && (t = w(t, r.add, re(t[r.add.key], r.add))), t;
139
- }, se = 1, g = (e) => typeof e == "number" && Number.isInteger(e) && e >= 0, oe = (e) => typeof e == "object" && e !== null && typeof e.id == "string" && typeof e.type == "string", _e = (e) => {
148
+ return r.remove !== null && (t = w(t, r.remove, se(t[r.remove.key], r.remove))), r.add !== null && (t = w(t, r.add, ne(t[r.add.key], r.add))), t;
149
+ }, le = 1, g = (e) => typeof e == "number" && Number.isInteger(e) && e >= 0, de = (e) => typeof e == "object" && e !== null && typeof e.id == "string" && typeof e.type == "string", Fe = (e) => {
140
150
  if (typeof e != "object" || e === null) return !1;
141
151
  const r = e;
142
- return r.format !== se || !g(r.position) || !g(r.revision) || !Array.isArray(r.entities) ? !1 : r.entities.every(oe);
152
+ return r.format !== le || !g(r.position) || !g(r.revision) || !Array.isArray(r.entities) ? !1 : r.entities.every(de);
143
153
  };
144
154
  export {
145
155
  f as M,
146
- se as R,
147
- fe as a,
148
- ie as b,
149
- q as c,
150
- ae as d,
151
- X as e,
152
- j as f,
153
- Z as g,
154
- Ie as h,
155
- _e as i,
156
- he as j,
157
- me as k,
158
- pe as l,
159
- U as m,
160
- ue as n,
161
- de as o,
162
- S as p,
163
- te as q,
156
+ le as R,
157
+ he as a,
158
+ pe as b,
159
+ G as c,
160
+ ue as d,
161
+ ee as e,
162
+ O as f,
163
+ re as g,
164
+ ve as h,
165
+ Fe as i,
166
+ Ie as j,
167
+ _e as k,
168
+ me as l,
169
+ z as m,
170
+ U as n,
171
+ ce as o,
172
+ q as p,
173
+ fe as q,
164
174
  v as r,
165
- re as s,
166
- ce as w
175
+ S as s,
176
+ se as t,
177
+ ne as u,
178
+ ie as w
167
179
  };
@@ -445,7 +445,8 @@ interface Replica {
445
445
  * `conflicted` is a terminal-until-resolved hold: a catch-up pass
446
446
  * classified the Action as a loser, so it is structurally excluded from
447
447
  * `pending()` and never submitted. The application resolves it by
448
- * retrying (back to `pending`) or discarding (removed).
448
+ * sending the Action unchanged (back to `pending`) or clearing it
449
+ * (removed).
449
450
  */
450
451
  type OutboxStatus = "pending" | "acknowledged" | "error" | "conflicted";
451
452
  /**
@@ -483,6 +484,12 @@ interface ConflictSlot {
483
484
  /** The value the server holds at the position after the missed window. */
484
485
  readonly remote: ConflictSide;
485
486
  }
487
+ /** One missed Action that out-dated a buffered write and made it a conflict. */
488
+ interface ConflictRemoteAction {
489
+ readonly actorId: string;
490
+ readonly actionId: string;
491
+ readonly gsn: number;
492
+ }
486
493
  /**
487
494
  * The before/after entity rows for one losing subject. `before` is the
488
495
  * row as the client had it immediately before the catch-up window was
@@ -521,6 +528,18 @@ interface OutboxConflict {
521
528
  * application reads that as "nothing of mine would be lost".
522
529
  */
523
530
  readonly slots: readonly ConflictSlot[];
531
+ /**
532
+ * The looser of the losing subjects' declared scopes: `"entity"` when
533
+ * any losing subject declared entity scope, else `"field"`. The union is
534
+ * spelled inline so storage stays independent of the schema package.
535
+ */
536
+ readonly scope: "field" | "component" | "entity";
537
+ /**
538
+ * The concurrent missed Actions that touched the losing subjects,
539
+ * deduped by action id, in window order. The remote half of the
540
+ * report an application builds a policy from.
541
+ */
542
+ readonly remoteActions: readonly ConflictRemoteAction[];
524
543
  /** The catch-up pass that produced this entry. */
525
544
  readonly catchUpId: string;
526
545
  /**
@@ -532,6 +551,16 @@ interface OutboxConflict {
532
551
  /** Client HLC stamped at detection. The conflict view's ordering key. */
533
552
  readonly detectedAtHlc: string;
534
553
  }
554
+ /**
555
+ * The conflict payload as a row written before IndexedDB schema v10
556
+ * persisted it: `scope` and `remoteActions` were added at v10. The store's
557
+ * read path fills both in; a legacy row is still a field-scope conflict
558
+ * with no recorded remote context.
559
+ */
560
+ interface StoredOutboxConflict extends Omit<OutboxConflict, "scope" | "remoteActions"> {
561
+ readonly scope?: "field" | "component" | "entity";
562
+ readonly remoteActions?: readonly ConflictRemoteAction[];
563
+ }
535
564
  /**
536
565
  * One locally-authored Action awaiting acknowledgement, plus the HLC at
537
566
  * which it entered the outbox. A `conflicted` entry carries its
@@ -557,6 +586,13 @@ interface OutboxEntry {
557
586
  /** Present iff `status === "conflicted"`. */
558
587
  readonly conflict?: OutboxConflict;
559
588
  }
589
+ /**
590
+ * The persisted form of an outbox entry. Identical to {@link OutboxEntry}
591
+ * except that a conflict payload may predate the v10 fields.
592
+ */
593
+ interface StoredOutboxEntry extends Omit<OutboxEntry, "conflict"> {
594
+ readonly conflict?: StoredOutboxConflict;
595
+ }
560
596
  /**
561
597
  * OutboxStore — durable buffer of pending local Actions.
562
598
  *
@@ -569,10 +605,9 @@ interface OutboxEntry {
569
605
  interface OutboxStore {
570
606
  put(entry: OutboxEntry): Promise<void>;
571
607
  /**
572
- * Persist `entry` and delete `deleteId` in one durable operation. Used
573
- * to resolve a conflict by retrying: the fresh re-assert must never
574
- * exist without the conflicted row being gone, nor the row be gone
575
- * without the re-assert persisted.
608
+ * Persist `entry` and delete `deleteId` in one durable operation: the
609
+ * replacement must never exist without the replaced row being gone,
610
+ * nor the row be gone without the replacement persisted.
576
611
  */
577
612
  putAndDelete(entry: OutboxEntry, deleteId: string): Promise<void>;
578
613
  list(): Promise<readonly OutboxEntry[]>;
@@ -618,4 +653,4 @@ interface StorageAdapter {
618
653
  reset(): Promise<void>;
619
654
  }
620
655
 
621
- export type { ActionLog, ConflictSide, ConflictSlot, ConflictSubjectState, CursorRecord, CursorStore, DirtyTracker, EntityStore, LoggedAction, MaterializationPolicy, MembershipLink, MembershipQuery, OutboxConflict, OutboxEntry, OutboxStatus, OutboxStore, RelationshipIndexQuery, RelationshipLink, RelationshipQuery, RelationshipSourceQuery, Replica, ReplicaChange, ReplicaOptions, ReplicaRecord, ReplicaStats, ReplicaStore, Revision, SeedMarker, StorageAdapter };
656
+ export type { ActionLog, ConflictRemoteAction, ConflictSide, ConflictSlot, ConflictSubjectState, CursorRecord, CursorStore, DirtyTracker, EntityStore, LoggedAction, MaterializationPolicy, MembershipLink, MembershipQuery, OutboxConflict, OutboxEntry, OutboxStatus, OutboxStore, RelationshipIndexQuery, RelationshipLink, RelationshipQuery, RelationshipSourceQuery, Replica, ReplicaChange, ReplicaOptions, ReplicaRecord, ReplicaStats, ReplicaStore, Revision, SeedMarker, StorageAdapter, StoredOutboxConflict, StoredOutboxEntry };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ebbjs/storage",
3
- "version": "0.2.0",
3
+ "version": "1.0.0",
4
4
  "private": false,
5
5
  "description": "Storage components for ebbjs",
6
6
  "homepage": "https://ebbjs.com",
@@ -44,7 +44,7 @@
44
44
  "access": "public"
45
45
  },
46
46
  "dependencies": {
47
- "@ebbjs/core": "^0.2.0"
47
+ "@ebbjs/core": "^1.0.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "fake-indexeddb": "^6.2.5",