@ebbjs/storage 0.3.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.
- package/dist/indexeddb/index.d.ts +23 -5
- package/dist/indexeddb/index.js +153 -148
- package/dist/memory/index.d.ts +23 -5
- package/dist/memory/index.js +35 -38
- package/dist/replica/index.d.ts +10 -1
- package/dist/replica/index.js +102 -100
- package/dist/{replica-record-D3lkFACh.js → replica-record-D5gGEoVQ.js} +59 -47
- package/dist/types/index.d.ts +41 -6
- package/package.json +2 -2
|
@@ -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,
|
|
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
|
-
},
|
|
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[
|
|
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,
|
|
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 =
|
|
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 ?? {},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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 ?
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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:
|
|
110
|
-
},
|
|
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:
|
|
114
|
-
},
|
|
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:
|
|
118
|
-
},
|
|
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
|
-
),
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
|
129
|
-
},
|
|
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
|
-
},
|
|
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,
|
|
139
|
-
},
|
|
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 !==
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
166
|
-
|
|
175
|
+
S as s,
|
|
176
|
+
se as t,
|
|
177
|
+
ne as u,
|
|
178
|
+
ie as w
|
|
167
179
|
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
573
|
-
*
|
|
574
|
-
*
|
|
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.
|
|
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.
|
|
47
|
+
"@ebbjs/core": "^1.0.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"fake-indexeddb": "^6.2.5",
|