@tanstack/db 0.3.2 → 0.4.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/cjs/{change-events.cjs → collection/change-events.cjs} +13 -42
- package/dist/cjs/collection/change-events.cjs.map +1 -0
- package/dist/{esm/change-events.d.ts → cjs/collection/change-events.d.cts} +6 -6
- package/dist/cjs/collection/changes.cjs +108 -0
- package/dist/cjs/collection/changes.cjs.map +1 -0
- package/dist/cjs/collection/changes.d.cts +53 -0
- package/dist/cjs/{collection-events.cjs → collection/events.cjs} +7 -5
- package/dist/cjs/collection/events.cjs.map +1 -0
- package/dist/cjs/{collection-events.d.cts → collection/events.d.cts} +7 -4
- package/dist/cjs/collection/index.cjs +417 -0
- package/dist/cjs/collection/index.cjs.map +1 -0
- package/dist/{esm/collection.d.ts → cjs/collection/index.d.cts} +46 -184
- package/dist/cjs/collection/indexes.cjs +124 -0
- package/dist/cjs/collection/indexes.cjs.map +1 -0
- package/dist/cjs/collection/indexes.d.cts +47 -0
- package/dist/cjs/collection/lifecycle.cjs +150 -0
- package/dist/cjs/collection/lifecycle.cjs.map +1 -0
- package/dist/cjs/collection/lifecycle.d.cts +70 -0
- package/dist/cjs/collection/mutations.cjs +315 -0
- package/dist/cjs/collection/mutations.cjs.map +1 -0
- package/dist/cjs/collection/mutations.d.cts +33 -0
- package/dist/cjs/collection/state.cjs +597 -0
- package/dist/cjs/collection/state.cjs.map +1 -0
- package/dist/cjs/collection/state.d.cts +122 -0
- package/dist/cjs/collection/subscription.cjs +160 -0
- package/dist/cjs/collection/subscription.cjs.map +1 -0
- package/dist/cjs/collection/subscription.d.cts +57 -0
- package/dist/cjs/collection/sync.cjs +154 -0
- package/dist/cjs/collection/sync.cjs.map +1 -0
- package/dist/cjs/collection/sync.d.cts +34 -0
- package/dist/cjs/index.cjs +8 -8
- package/dist/cjs/index.d.cts +2 -2
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/auto-index.d.cts +1 -1
- package/dist/cjs/indexes/base-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.d.cts +2 -2
- package/dist/cjs/indexes/btree-index.cjs +2 -2
- package/dist/cjs/indexes/btree-index.cjs.map +1 -1
- package/dist/cjs/indexes/btree-index.d.cts +1 -1
- package/dist/cjs/query/builder/index.cjs +2 -2
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/types.d.cts +1 -1
- package/dist/cjs/query/compiler/index.cjs +5 -2
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +3 -2
- package/dist/cjs/query/compiler/joins.cjs +22 -24
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.d.cts +3 -2
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +1 -1
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/ir.d.cts +1 -1
- package/dist/cjs/query/live/collection-config-builder.cjs +29 -12
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +3 -0
- package/dist/cjs/query/live/collection-subscriber.cjs +43 -104
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.d.cts +4 -7
- package/dist/cjs/query/live-query-collection.cjs +2 -2
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +1 -1
- package/dist/cjs/transactions.cjs +3 -3
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +12 -10
- package/dist/{cjs/change-events.d.cts → esm/collection/change-events.d.ts} +6 -6
- package/dist/esm/{change-events.js → collection/change-events.js} +13 -42
- package/dist/esm/collection/change-events.js.map +1 -0
- package/dist/esm/collection/changes.d.ts +53 -0
- package/dist/esm/collection/changes.js +108 -0
- package/dist/esm/collection/changes.js.map +1 -0
- package/dist/esm/{collection-events.d.ts → collection/events.d.ts} +7 -4
- package/dist/esm/{collection-events.js → collection/events.js} +7 -5
- package/dist/esm/collection/events.js.map +1 -0
- package/dist/{cjs/collection.d.cts → esm/collection/index.d.ts} +46 -184
- package/dist/esm/collection/index.js +417 -0
- package/dist/esm/collection/index.js.map +1 -0
- package/dist/esm/collection/indexes.d.ts +47 -0
- package/dist/esm/collection/indexes.js +124 -0
- package/dist/esm/collection/indexes.js.map +1 -0
- package/dist/esm/collection/lifecycle.d.ts +70 -0
- package/dist/esm/collection/lifecycle.js +150 -0
- package/dist/esm/collection/lifecycle.js.map +1 -0
- package/dist/esm/collection/mutations.d.ts +33 -0
- package/dist/esm/collection/mutations.js +315 -0
- package/dist/esm/collection/mutations.js.map +1 -0
- package/dist/esm/collection/state.d.ts +122 -0
- package/dist/esm/collection/state.js +597 -0
- package/dist/esm/collection/state.js.map +1 -0
- package/dist/esm/collection/subscription.d.ts +57 -0
- package/dist/esm/collection/subscription.js +160 -0
- package/dist/esm/collection/subscription.js.map +1 -0
- package/dist/esm/collection/sync.d.ts +34 -0
- package/dist/esm/collection/sync.js +154 -0
- package/dist/esm/collection/sync.js.map +1 -0
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/indexes/auto-index.d.ts +1 -1
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/indexes/base-index.d.ts +2 -2
- package/dist/esm/indexes/base-index.js.map +1 -1
- package/dist/esm/indexes/btree-index.d.ts +1 -1
- package/dist/esm/indexes/btree-index.js +2 -2
- package/dist/esm/indexes/btree-index.js.map +1 -1
- package/dist/esm/proxy.js +1 -1
- package/dist/esm/query/builder/index.js +1 -1
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +1 -1
- package/dist/esm/query/compiler/index.d.ts +3 -2
- package/dist/esm/query/compiler/index.js +5 -2
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.d.ts +3 -2
- package/dist/esm/query/compiler/joins.js +22 -24
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +1 -1
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/ir.d.ts +1 -1
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.d.ts +3 -0
- package/dist/esm/query/live/collection-config-builder.js +29 -12
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.d.ts +4 -7
- package/dist/esm/query/live/collection-subscriber.js +43 -104
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live-query-collection.d.ts +1 -1
- package/dist/esm/query/live-query-collection.js +1 -1
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/transactions.js +3 -3
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +12 -10
- package/package.json +2 -2
- package/src/{change-events.ts → collection/change-events.ts} +25 -39
- package/src/collection/changes.ts +163 -0
- package/src/{collection-events.ts → collection/events.ts} +8 -6
- package/src/collection/index.ts +808 -0
- package/src/collection/indexes.ts +172 -0
- package/src/collection/lifecycle.ts +221 -0
- package/src/collection/mutations.ts +535 -0
- package/src/collection/state.ts +866 -0
- package/src/collection/subscription.ts +239 -0
- package/src/collection/sync.ts +235 -0
- package/src/index.ts +2 -2
- package/src/indexes/auto-index.ts +1 -1
- package/src/indexes/base-index.ts +3 -3
- package/src/indexes/btree-index.ts +2 -2
- package/src/query/builder/index.ts +1 -1
- package/src/query/builder/types.ts +1 -1
- package/src/query/compiler/index.ts +7 -1
- package/src/query/compiler/joins.ts +28 -41
- package/src/query/compiler/order-by.ts +1 -1
- package/src/query/ir.ts +1 -1
- package/src/query/live/collection-config-builder.ts +48 -22
- package/src/query/live/collection-subscriber.ts +63 -168
- package/src/query/live-query-collection.ts +2 -2
- package/src/transactions.ts +3 -3
- package/src/types.ts +14 -15
- package/dist/cjs/change-events.cjs.map +0 -1
- package/dist/cjs/collection-events.cjs.map +0 -1
- package/dist/cjs/collection.cjs +0 -1625
- package/dist/cjs/collection.cjs.map +0 -1
- package/dist/esm/change-events.js.map +0 -1
- package/dist/esm/collection-events.js.map +0 -1
- package/dist/esm/collection.js +0 -1625
- package/dist/esm/collection.js.map +0 -1
- package/src/collection.ts +0 -2564
package/dist/cjs/collection.cjs
DELETED
|
@@ -1,1625 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const proxy = require("./proxy.cjs");
|
|
4
|
-
const utils = require("./utils.cjs");
|
|
5
|
-
const SortedMap = require("./SortedMap.cjs");
|
|
6
|
-
const refProxy = require("./query/builder/ref-proxy.cjs");
|
|
7
|
-
const btreeIndex = require("./indexes/btree-index.cjs");
|
|
8
|
-
const lazyIndex = require("./indexes/lazy-index.cjs");
|
|
9
|
-
const autoIndex = require("./indexes/auto-index.cjs");
|
|
10
|
-
const transactions = require("./transactions.cjs");
|
|
11
|
-
const errors = require("./errors.cjs");
|
|
12
|
-
const changeEvents = require("./change-events.cjs");
|
|
13
|
-
const collectionEvents = require("./collection-events.cjs");
|
|
14
|
-
function createCollection(options) {
|
|
15
|
-
const collection = new CollectionImpl(
|
|
16
|
-
options
|
|
17
|
-
);
|
|
18
|
-
if (options.utils) {
|
|
19
|
-
collection.utils = { ...options.utils };
|
|
20
|
-
} else {
|
|
21
|
-
collection.utils = {};
|
|
22
|
-
}
|
|
23
|
-
return collection;
|
|
24
|
-
}
|
|
25
|
-
class CollectionImpl {
|
|
26
|
-
/**
|
|
27
|
-
* Creates a new Collection instance
|
|
28
|
-
*
|
|
29
|
-
* @param config - Configuration object for the collection
|
|
30
|
-
* @throws Error if sync config is missing
|
|
31
|
-
*/
|
|
32
|
-
constructor(config) {
|
|
33
|
-
this.pendingSyncedTransactions = [];
|
|
34
|
-
this.syncedMetadata = /* @__PURE__ */ new Map();
|
|
35
|
-
this.optimisticUpserts = /* @__PURE__ */ new Map();
|
|
36
|
-
this.optimisticDeletes = /* @__PURE__ */ new Set();
|
|
37
|
-
this._size = 0;
|
|
38
|
-
this.lazyIndexes = /* @__PURE__ */ new Map();
|
|
39
|
-
this.resolvedIndexes = /* @__PURE__ */ new Map();
|
|
40
|
-
this.isIndexesResolved = false;
|
|
41
|
-
this.indexCounter = 0;
|
|
42
|
-
this.changeListeners = /* @__PURE__ */ new Set();
|
|
43
|
-
this.changeKeyListeners = /* @__PURE__ */ new Map();
|
|
44
|
-
this.utils = {};
|
|
45
|
-
this.syncedKeys = /* @__PURE__ */ new Set();
|
|
46
|
-
this.preSyncVisibleState = /* @__PURE__ */ new Map();
|
|
47
|
-
this.recentlySyncedKeys = /* @__PURE__ */ new Set();
|
|
48
|
-
this.hasReceivedFirstCommit = false;
|
|
49
|
-
this.isCommittingSyncTransactions = false;
|
|
50
|
-
this.onFirstReadyCallbacks = [];
|
|
51
|
-
this.hasBeenReady = false;
|
|
52
|
-
this.batchedEvents = [];
|
|
53
|
-
this.shouldBatchEvents = false;
|
|
54
|
-
this._status = `idle`;
|
|
55
|
-
this.activeSubscribersCount = 0;
|
|
56
|
-
this.gcTimeoutId = null;
|
|
57
|
-
this.preloadPromise = null;
|
|
58
|
-
this.syncCleanupFn = null;
|
|
59
|
-
this.id = ``;
|
|
60
|
-
this.commitPendingTransactions = () => {
|
|
61
|
-
let hasPersistingTransaction = false;
|
|
62
|
-
for (const transaction of this.transactions.values()) {
|
|
63
|
-
if (transaction.state === `persisting`) {
|
|
64
|
-
hasPersistingTransaction = true;
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const {
|
|
69
|
-
committedSyncedTransactions,
|
|
70
|
-
uncommittedSyncedTransactions,
|
|
71
|
-
hasTruncateSync
|
|
72
|
-
} = this.pendingSyncedTransactions.reduce(
|
|
73
|
-
(acc, t) => {
|
|
74
|
-
if (t.committed) {
|
|
75
|
-
acc.committedSyncedTransactions.push(t);
|
|
76
|
-
if (t.truncate === true) {
|
|
77
|
-
acc.hasTruncateSync = true;
|
|
78
|
-
}
|
|
79
|
-
} else {
|
|
80
|
-
acc.uncommittedSyncedTransactions.push(t);
|
|
81
|
-
}
|
|
82
|
-
return acc;
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
committedSyncedTransactions: [],
|
|
86
|
-
uncommittedSyncedTransactions: [],
|
|
87
|
-
hasTruncateSync: false
|
|
88
|
-
}
|
|
89
|
-
);
|
|
90
|
-
if (!hasPersistingTransaction || hasTruncateSync) {
|
|
91
|
-
this.isCommittingSyncTransactions = true;
|
|
92
|
-
const changedKeys = /* @__PURE__ */ new Set();
|
|
93
|
-
for (const transaction of committedSyncedTransactions) {
|
|
94
|
-
for (const operation of transaction.operations) {
|
|
95
|
-
changedKeys.add(operation.key);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
let currentVisibleState = this.preSyncVisibleState;
|
|
99
|
-
if (currentVisibleState.size === 0) {
|
|
100
|
-
currentVisibleState = /* @__PURE__ */ new Map();
|
|
101
|
-
for (const key of changedKeys) {
|
|
102
|
-
const currentValue = this.get(key);
|
|
103
|
-
if (currentValue !== void 0) {
|
|
104
|
-
currentVisibleState.set(key, currentValue);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
const events = [];
|
|
109
|
-
const rowUpdateMode = this.config.sync.rowUpdateMode || `partial`;
|
|
110
|
-
for (const transaction of committedSyncedTransactions) {
|
|
111
|
-
if (transaction.truncate) {
|
|
112
|
-
for (const key of this.syncedData.keys()) {
|
|
113
|
-
if (this.optimisticDeletes.has(key)) continue;
|
|
114
|
-
const previousValue = this.optimisticUpserts.get(key) || this.syncedData.get(key);
|
|
115
|
-
if (previousValue !== void 0) {
|
|
116
|
-
events.push({ type: `delete`, key, value: previousValue });
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
this.syncedData.clear();
|
|
120
|
-
this.syncedMetadata.clear();
|
|
121
|
-
this.syncedKeys.clear();
|
|
122
|
-
for (const key of changedKeys) {
|
|
123
|
-
currentVisibleState.delete(key);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
for (const operation of transaction.operations) {
|
|
127
|
-
const key = operation.key;
|
|
128
|
-
this.syncedKeys.add(key);
|
|
129
|
-
switch (operation.type) {
|
|
130
|
-
case `insert`:
|
|
131
|
-
this.syncedMetadata.set(key, operation.metadata);
|
|
132
|
-
break;
|
|
133
|
-
case `update`:
|
|
134
|
-
this.syncedMetadata.set(
|
|
135
|
-
key,
|
|
136
|
-
Object.assign(
|
|
137
|
-
{},
|
|
138
|
-
this.syncedMetadata.get(key),
|
|
139
|
-
operation.metadata
|
|
140
|
-
)
|
|
141
|
-
);
|
|
142
|
-
break;
|
|
143
|
-
case `delete`:
|
|
144
|
-
this.syncedMetadata.delete(key);
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
|
-
switch (operation.type) {
|
|
148
|
-
case `insert`:
|
|
149
|
-
this.syncedData.set(key, operation.value);
|
|
150
|
-
break;
|
|
151
|
-
case `update`: {
|
|
152
|
-
if (rowUpdateMode === `partial`) {
|
|
153
|
-
const updatedValue = Object.assign(
|
|
154
|
-
{},
|
|
155
|
-
this.syncedData.get(key),
|
|
156
|
-
operation.value
|
|
157
|
-
);
|
|
158
|
-
this.syncedData.set(key, updatedValue);
|
|
159
|
-
} else {
|
|
160
|
-
this.syncedData.set(key, operation.value);
|
|
161
|
-
}
|
|
162
|
-
break;
|
|
163
|
-
}
|
|
164
|
-
case `delete`:
|
|
165
|
-
this.syncedData.delete(key);
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
if (hasTruncateSync) {
|
|
171
|
-
const syncedInsertedOrUpdatedKeys = /* @__PURE__ */ new Set();
|
|
172
|
-
for (const t of committedSyncedTransactions) {
|
|
173
|
-
for (const op of t.operations) {
|
|
174
|
-
if (op.type === `insert` || op.type === `update`) {
|
|
175
|
-
syncedInsertedOrUpdatedKeys.add(op.key);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
const reapplyUpserts = /* @__PURE__ */ new Map();
|
|
180
|
-
const reapplyDeletes = /* @__PURE__ */ new Set();
|
|
181
|
-
for (const tx of this.transactions.values()) {
|
|
182
|
-
if ([`completed`, `failed`].includes(tx.state)) continue;
|
|
183
|
-
for (const mutation of tx.mutations) {
|
|
184
|
-
if (mutation.collection !== this || !mutation.optimistic) continue;
|
|
185
|
-
const key = mutation.key;
|
|
186
|
-
switch (mutation.type) {
|
|
187
|
-
case `insert`:
|
|
188
|
-
reapplyUpserts.set(key, mutation.modified);
|
|
189
|
-
reapplyDeletes.delete(key);
|
|
190
|
-
break;
|
|
191
|
-
case `update`: {
|
|
192
|
-
const base = this.syncedData.get(key);
|
|
193
|
-
const next = base ? Object.assign({}, base, mutation.changes) : mutation.modified;
|
|
194
|
-
reapplyUpserts.set(key, next);
|
|
195
|
-
reapplyDeletes.delete(key);
|
|
196
|
-
break;
|
|
197
|
-
}
|
|
198
|
-
case `delete`:
|
|
199
|
-
reapplyUpserts.delete(key);
|
|
200
|
-
reapplyDeletes.add(key);
|
|
201
|
-
break;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
for (const [key, value] of reapplyUpserts) {
|
|
206
|
-
if (reapplyDeletes.has(key)) continue;
|
|
207
|
-
if (syncedInsertedOrUpdatedKeys.has(key)) {
|
|
208
|
-
let foundInsert = false;
|
|
209
|
-
for (let i = events.length - 1; i >= 0; i--) {
|
|
210
|
-
const evt = events[i];
|
|
211
|
-
if (evt.key === key && evt.type === `insert`) {
|
|
212
|
-
evt.value = value;
|
|
213
|
-
foundInsert = true;
|
|
214
|
-
break;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
if (!foundInsert) {
|
|
218
|
-
events.push({ type: `insert`, key, value });
|
|
219
|
-
}
|
|
220
|
-
} else {
|
|
221
|
-
events.push({ type: `insert`, key, value });
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
if (events.length > 0 && reapplyDeletes.size > 0) {
|
|
225
|
-
const filtered = [];
|
|
226
|
-
for (const evt of events) {
|
|
227
|
-
if (evt.type === `insert` && reapplyDeletes.has(evt.key)) {
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
filtered.push(evt);
|
|
231
|
-
}
|
|
232
|
-
events.length = 0;
|
|
233
|
-
events.push(...filtered);
|
|
234
|
-
}
|
|
235
|
-
if (!this.isReady()) {
|
|
236
|
-
this.setStatus(`ready`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
this.optimisticUpserts.clear();
|
|
240
|
-
this.optimisticDeletes.clear();
|
|
241
|
-
this.isCommittingSyncTransactions = false;
|
|
242
|
-
for (const transaction of this.transactions.values()) {
|
|
243
|
-
if (![`completed`, `failed`].includes(transaction.state)) {
|
|
244
|
-
for (const mutation of transaction.mutations) {
|
|
245
|
-
if (mutation.collection === this && mutation.optimistic) {
|
|
246
|
-
switch (mutation.type) {
|
|
247
|
-
case `insert`:
|
|
248
|
-
case `update`:
|
|
249
|
-
this.optimisticUpserts.set(
|
|
250
|
-
mutation.key,
|
|
251
|
-
mutation.modified
|
|
252
|
-
);
|
|
253
|
-
this.optimisticDeletes.delete(mutation.key);
|
|
254
|
-
break;
|
|
255
|
-
case `delete`:
|
|
256
|
-
this.optimisticUpserts.delete(mutation.key);
|
|
257
|
-
this.optimisticDeletes.add(mutation.key);
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
const completedOptimisticOps = /* @__PURE__ */ new Map();
|
|
265
|
-
for (const transaction of this.transactions.values()) {
|
|
266
|
-
if (transaction.state === `completed`) {
|
|
267
|
-
for (const mutation of transaction.mutations) {
|
|
268
|
-
if (mutation.collection === this && changedKeys.has(mutation.key)) {
|
|
269
|
-
completedOptimisticOps.set(mutation.key, {
|
|
270
|
-
type: mutation.type,
|
|
271
|
-
value: mutation.modified
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
for (const key of changedKeys) {
|
|
278
|
-
const previousVisibleValue = currentVisibleState.get(key);
|
|
279
|
-
const newVisibleValue = this.get(key);
|
|
280
|
-
const completedOp = completedOptimisticOps.get(key);
|
|
281
|
-
const isRedundantSync = completedOp && newVisibleValue !== void 0 && utils.deepEquals(completedOp.value, newVisibleValue);
|
|
282
|
-
if (!isRedundantSync) {
|
|
283
|
-
if (previousVisibleValue === void 0 && newVisibleValue !== void 0) {
|
|
284
|
-
events.push({
|
|
285
|
-
type: `insert`,
|
|
286
|
-
key,
|
|
287
|
-
value: newVisibleValue
|
|
288
|
-
});
|
|
289
|
-
} else if (previousVisibleValue !== void 0 && newVisibleValue === void 0) {
|
|
290
|
-
events.push({
|
|
291
|
-
type: `delete`,
|
|
292
|
-
key,
|
|
293
|
-
value: previousVisibleValue
|
|
294
|
-
});
|
|
295
|
-
} else if (previousVisibleValue !== void 0 && newVisibleValue !== void 0 && !utils.deepEquals(previousVisibleValue, newVisibleValue)) {
|
|
296
|
-
events.push({
|
|
297
|
-
type: `update`,
|
|
298
|
-
key,
|
|
299
|
-
value: newVisibleValue,
|
|
300
|
-
previousValue: previousVisibleValue
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
this._size = this.calculateSize();
|
|
306
|
-
if (events.length > 0) {
|
|
307
|
-
this.updateIndexes(events);
|
|
308
|
-
}
|
|
309
|
-
this.emitEvents(events, true);
|
|
310
|
-
this.pendingSyncedTransactions = uncommittedSyncedTransactions;
|
|
311
|
-
this.preSyncVisibleState.clear();
|
|
312
|
-
Promise.resolve().then(() => {
|
|
313
|
-
this.recentlySyncedKeys.clear();
|
|
314
|
-
});
|
|
315
|
-
if (!this.hasReceivedFirstCommit) {
|
|
316
|
-
this.hasReceivedFirstCommit = true;
|
|
317
|
-
const callbacks = [...this.onFirstReadyCallbacks];
|
|
318
|
-
this.onFirstReadyCallbacks = [];
|
|
319
|
-
callbacks.forEach((callback) => callback());
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
};
|
|
323
|
-
this.insert = (data, config2) => {
|
|
324
|
-
this.validateCollectionUsable(`insert`);
|
|
325
|
-
const ambientTransaction = transactions.getActiveTransaction();
|
|
326
|
-
if (!ambientTransaction && !this.config.onInsert) {
|
|
327
|
-
throw new errors.MissingInsertHandlerError();
|
|
328
|
-
}
|
|
329
|
-
const items = Array.isArray(data) ? data : [data];
|
|
330
|
-
const mutations = [];
|
|
331
|
-
items.forEach((item) => {
|
|
332
|
-
var _a, _b;
|
|
333
|
-
const validatedData = this.validateData(item, `insert`);
|
|
334
|
-
const key = this.getKeyFromItem(validatedData);
|
|
335
|
-
if (this.has(key)) {
|
|
336
|
-
throw new errors.DuplicateKeyError(key);
|
|
337
|
-
}
|
|
338
|
-
const globalKey = this.generateGlobalKey(key, item);
|
|
339
|
-
const mutation = {
|
|
340
|
-
mutationId: crypto.randomUUID(),
|
|
341
|
-
original: {},
|
|
342
|
-
modified: validatedData,
|
|
343
|
-
// Pick the values from validatedData based on what's passed in - this is for cases
|
|
344
|
-
// where a schema has default values. The validated data has the extra default
|
|
345
|
-
// values but for changes, we just want to show the data that was actually passed in.
|
|
346
|
-
changes: Object.fromEntries(
|
|
347
|
-
Object.keys(item).map((k) => [
|
|
348
|
-
k,
|
|
349
|
-
validatedData[k]
|
|
350
|
-
])
|
|
351
|
-
),
|
|
352
|
-
globalKey,
|
|
353
|
-
key,
|
|
354
|
-
metadata: config2 == null ? void 0 : config2.metadata,
|
|
355
|
-
syncMetadata: ((_b = (_a = this.config.sync).getSyncMetadata) == null ? void 0 : _b.call(_a)) || {},
|
|
356
|
-
optimistic: (config2 == null ? void 0 : config2.optimistic) ?? true,
|
|
357
|
-
type: `insert`,
|
|
358
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
359
|
-
updatedAt: /* @__PURE__ */ new Date(),
|
|
360
|
-
collection: this
|
|
361
|
-
};
|
|
362
|
-
mutations.push(mutation);
|
|
363
|
-
});
|
|
364
|
-
if (ambientTransaction) {
|
|
365
|
-
ambientTransaction.applyMutations(mutations);
|
|
366
|
-
this.transactions.set(ambientTransaction.id, ambientTransaction);
|
|
367
|
-
this.scheduleTransactionCleanup(ambientTransaction);
|
|
368
|
-
this.recomputeOptimisticState(true);
|
|
369
|
-
return ambientTransaction;
|
|
370
|
-
} else {
|
|
371
|
-
const directOpTransaction = transactions.createTransaction({
|
|
372
|
-
mutationFn: async (params) => {
|
|
373
|
-
return await this.config.onInsert({
|
|
374
|
-
transaction: params.transaction,
|
|
375
|
-
collection: this
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
directOpTransaction.applyMutations(mutations);
|
|
380
|
-
directOpTransaction.commit();
|
|
381
|
-
this.transactions.set(directOpTransaction.id, directOpTransaction);
|
|
382
|
-
this.scheduleTransactionCleanup(directOpTransaction);
|
|
383
|
-
this.recomputeOptimisticState(true);
|
|
384
|
-
return directOpTransaction;
|
|
385
|
-
}
|
|
386
|
-
};
|
|
387
|
-
this.delete = (keys, config2) => {
|
|
388
|
-
this.validateCollectionUsable(`delete`);
|
|
389
|
-
const ambientTransaction = transactions.getActiveTransaction();
|
|
390
|
-
if (!ambientTransaction && !this.config.onDelete) {
|
|
391
|
-
throw new errors.MissingDeleteHandlerError();
|
|
392
|
-
}
|
|
393
|
-
if (Array.isArray(keys) && keys.length === 0) {
|
|
394
|
-
throw new errors.NoKeysPassedToDeleteError();
|
|
395
|
-
}
|
|
396
|
-
const keysArray = Array.isArray(keys) ? keys : [keys];
|
|
397
|
-
const mutations = [];
|
|
398
|
-
for (const key of keysArray) {
|
|
399
|
-
if (!this.has(key)) {
|
|
400
|
-
throw new errors.DeleteKeyNotFoundError(key);
|
|
401
|
-
}
|
|
402
|
-
const globalKey = this.generateGlobalKey(key, this.get(key));
|
|
403
|
-
const mutation = {
|
|
404
|
-
mutationId: crypto.randomUUID(),
|
|
405
|
-
original: this.get(key),
|
|
406
|
-
modified: this.get(key),
|
|
407
|
-
changes: this.get(key),
|
|
408
|
-
globalKey,
|
|
409
|
-
key,
|
|
410
|
-
metadata: config2 == null ? void 0 : config2.metadata,
|
|
411
|
-
syncMetadata: this.syncedMetadata.get(key) || {},
|
|
412
|
-
optimistic: (config2 == null ? void 0 : config2.optimistic) ?? true,
|
|
413
|
-
type: `delete`,
|
|
414
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
415
|
-
updatedAt: /* @__PURE__ */ new Date(),
|
|
416
|
-
collection: this
|
|
417
|
-
};
|
|
418
|
-
mutations.push(mutation);
|
|
419
|
-
}
|
|
420
|
-
if (ambientTransaction) {
|
|
421
|
-
ambientTransaction.applyMutations(mutations);
|
|
422
|
-
this.transactions.set(ambientTransaction.id, ambientTransaction);
|
|
423
|
-
this.scheduleTransactionCleanup(ambientTransaction);
|
|
424
|
-
this.recomputeOptimisticState(true);
|
|
425
|
-
return ambientTransaction;
|
|
426
|
-
}
|
|
427
|
-
const directOpTransaction = transactions.createTransaction({
|
|
428
|
-
autoCommit: true,
|
|
429
|
-
mutationFn: async (params) => {
|
|
430
|
-
return this.config.onDelete({
|
|
431
|
-
transaction: params.transaction,
|
|
432
|
-
collection: this
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
});
|
|
436
|
-
directOpTransaction.applyMutations(mutations);
|
|
437
|
-
directOpTransaction.commit();
|
|
438
|
-
this.transactions.set(directOpTransaction.id, directOpTransaction);
|
|
439
|
-
this.scheduleTransactionCleanup(directOpTransaction);
|
|
440
|
-
this.recomputeOptimisticState(true);
|
|
441
|
-
return directOpTransaction;
|
|
442
|
-
};
|
|
443
|
-
if (!config) {
|
|
444
|
-
throw new errors.CollectionRequiresConfigError();
|
|
445
|
-
}
|
|
446
|
-
if (config.id) {
|
|
447
|
-
this.id = config.id;
|
|
448
|
-
} else {
|
|
449
|
-
this.id = crypto.randomUUID();
|
|
450
|
-
}
|
|
451
|
-
if (!config.sync) {
|
|
452
|
-
throw new errors.CollectionRequiresSyncConfigError();
|
|
453
|
-
}
|
|
454
|
-
this.transactions = new SortedMap.SortedMap(
|
|
455
|
-
(a, b) => a.compareCreatedAt(b)
|
|
456
|
-
);
|
|
457
|
-
this.config = {
|
|
458
|
-
...config,
|
|
459
|
-
autoIndex: config.autoIndex ?? `eager`
|
|
460
|
-
};
|
|
461
|
-
if (this.config.compare) {
|
|
462
|
-
this.syncedData = new SortedMap.SortedMap(this.config.compare);
|
|
463
|
-
} else {
|
|
464
|
-
this.syncedData = /* @__PURE__ */ new Map();
|
|
465
|
-
}
|
|
466
|
-
this.events = new collectionEvents.CollectionEvents(this);
|
|
467
|
-
if (config.startSync === true) {
|
|
468
|
-
this.startSync();
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
/**
|
|
472
|
-
* Register a callback to be executed when the collection first becomes ready
|
|
473
|
-
* Useful for preloading collections
|
|
474
|
-
* @param callback Function to call when the collection first becomes ready
|
|
475
|
-
* @example
|
|
476
|
-
* collection.onFirstReady(() => {
|
|
477
|
-
* console.log('Collection is ready for the first time')
|
|
478
|
-
* // Safe to access collection.state now
|
|
479
|
-
* })
|
|
480
|
-
*/
|
|
481
|
-
onFirstReady(callback) {
|
|
482
|
-
if (this.hasBeenReady) {
|
|
483
|
-
callback();
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
this.onFirstReadyCallbacks.push(callback);
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Check if the collection is ready for use
|
|
490
|
-
* Returns true if the collection has been marked as ready by its sync implementation
|
|
491
|
-
* @returns true if the collection is ready, false otherwise
|
|
492
|
-
* @example
|
|
493
|
-
* if (collection.isReady()) {
|
|
494
|
-
* console.log('Collection is ready, data is available')
|
|
495
|
-
* // Safe to access collection.state
|
|
496
|
-
* } else {
|
|
497
|
-
* console.log('Collection is still loading')
|
|
498
|
-
* }
|
|
499
|
-
*/
|
|
500
|
-
isReady() {
|
|
501
|
-
return this._status === `ready`;
|
|
502
|
-
}
|
|
503
|
-
/**
|
|
504
|
-
* Mark the collection as ready for use
|
|
505
|
-
* This is called by sync implementations to explicitly signal that the collection is ready,
|
|
506
|
-
* providing a more intuitive alternative to using commits for readiness signaling
|
|
507
|
-
* @private - Should only be called by sync implementations
|
|
508
|
-
*/
|
|
509
|
-
markReady() {
|
|
510
|
-
if (this._status === `loading` || this._status === `initialCommit`) {
|
|
511
|
-
this.setStatus(`ready`);
|
|
512
|
-
if (!this.hasBeenReady) {
|
|
513
|
-
this.hasBeenReady = true;
|
|
514
|
-
if (!this.hasReceivedFirstCommit) {
|
|
515
|
-
this.hasReceivedFirstCommit = true;
|
|
516
|
-
}
|
|
517
|
-
const callbacks = [...this.onFirstReadyCallbacks];
|
|
518
|
-
this.onFirstReadyCallbacks = [];
|
|
519
|
-
callbacks.forEach((callback) => callback());
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
if (this.changeListeners.size > 0) {
|
|
523
|
-
this.emitEmptyReadyEvent();
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
/**
|
|
527
|
-
* Gets the current status of the collection
|
|
528
|
-
*/
|
|
529
|
-
get status() {
|
|
530
|
-
return this._status;
|
|
531
|
-
}
|
|
532
|
-
/**
|
|
533
|
-
* Get the number of subscribers to the collection
|
|
534
|
-
*/
|
|
535
|
-
get subscriberCount() {
|
|
536
|
-
return this.activeSubscribersCount;
|
|
537
|
-
}
|
|
538
|
-
/**
|
|
539
|
-
* Validates that the collection is in a usable state for data operations
|
|
540
|
-
* @private
|
|
541
|
-
*/
|
|
542
|
-
validateCollectionUsable(operation) {
|
|
543
|
-
switch (this._status) {
|
|
544
|
-
case `error`:
|
|
545
|
-
throw new errors.CollectionInErrorStateError(operation, this.id);
|
|
546
|
-
case `cleaned-up`:
|
|
547
|
-
this.startSync();
|
|
548
|
-
break;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
/**
|
|
552
|
-
* Validates state transitions to prevent invalid status changes
|
|
553
|
-
* @private
|
|
554
|
-
*/
|
|
555
|
-
validateStatusTransition(from, to) {
|
|
556
|
-
if (from === to) {
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
const validTransitions = {
|
|
560
|
-
idle: [`loading`, `error`, `cleaned-up`],
|
|
561
|
-
loading: [`initialCommit`, `ready`, `error`, `cleaned-up`],
|
|
562
|
-
initialCommit: [`ready`, `error`, `cleaned-up`],
|
|
563
|
-
ready: [`cleaned-up`, `error`],
|
|
564
|
-
error: [`cleaned-up`, `idle`],
|
|
565
|
-
"cleaned-up": [`loading`, `error`]
|
|
566
|
-
};
|
|
567
|
-
if (!validTransitions[from].includes(to)) {
|
|
568
|
-
throw new errors.InvalidCollectionStatusTransitionError(from, to, this.id);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
/**
|
|
572
|
-
* Safely update the collection status with validation
|
|
573
|
-
* @private
|
|
574
|
-
*/
|
|
575
|
-
setStatus(newStatus) {
|
|
576
|
-
this.validateStatusTransition(this._status, newStatus);
|
|
577
|
-
const previousStatus = this._status;
|
|
578
|
-
this._status = newStatus;
|
|
579
|
-
if (newStatus === `ready` && !this.isIndexesResolved) {
|
|
580
|
-
this.resolveAllIndexes().catch((error) => {
|
|
581
|
-
console.warn(`Failed to resolve indexes:`, error);
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
this.events.emitStatusChange(newStatus, previousStatus);
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* Start sync immediately - internal method for compiled queries
|
|
588
|
-
* This bypasses lazy loading for special cases like live query results
|
|
589
|
-
*/
|
|
590
|
-
startSyncImmediate() {
|
|
591
|
-
this.startSync();
|
|
592
|
-
}
|
|
593
|
-
/**
|
|
594
|
-
* Start the sync process for this collection
|
|
595
|
-
* This is called when the collection is first accessed or preloaded
|
|
596
|
-
*/
|
|
597
|
-
startSync() {
|
|
598
|
-
if (this._status !== `idle` && this._status !== `cleaned-up`) {
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
this.setStatus(`loading`);
|
|
602
|
-
try {
|
|
603
|
-
const cleanupFn = this.config.sync.sync({
|
|
604
|
-
collection: this,
|
|
605
|
-
begin: () => {
|
|
606
|
-
this.pendingSyncedTransactions.push({
|
|
607
|
-
committed: false,
|
|
608
|
-
operations: [],
|
|
609
|
-
deletedKeys: /* @__PURE__ */ new Set()
|
|
610
|
-
});
|
|
611
|
-
},
|
|
612
|
-
write: (messageWithoutKey) => {
|
|
613
|
-
const pendingTransaction = this.pendingSyncedTransactions[this.pendingSyncedTransactions.length - 1];
|
|
614
|
-
if (!pendingTransaction) {
|
|
615
|
-
throw new errors.NoPendingSyncTransactionWriteError();
|
|
616
|
-
}
|
|
617
|
-
if (pendingTransaction.committed) {
|
|
618
|
-
throw new errors.SyncTransactionAlreadyCommittedWriteError();
|
|
619
|
-
}
|
|
620
|
-
const key = this.getKeyFromItem(messageWithoutKey.value);
|
|
621
|
-
if (messageWithoutKey.type === `insert`) {
|
|
622
|
-
const insertingIntoExistingSynced = this.syncedData.has(key);
|
|
623
|
-
const hasPendingDeleteForKey = pendingTransaction.deletedKeys.has(key);
|
|
624
|
-
const isTruncateTransaction = pendingTransaction.truncate === true;
|
|
625
|
-
if (insertingIntoExistingSynced && !hasPendingDeleteForKey && !isTruncateTransaction) {
|
|
626
|
-
throw new errors.DuplicateKeySyncError(key, this.id);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
const message = {
|
|
630
|
-
...messageWithoutKey,
|
|
631
|
-
key
|
|
632
|
-
};
|
|
633
|
-
pendingTransaction.operations.push(message);
|
|
634
|
-
if (messageWithoutKey.type === `delete`) {
|
|
635
|
-
pendingTransaction.deletedKeys.add(key);
|
|
636
|
-
}
|
|
637
|
-
},
|
|
638
|
-
commit: () => {
|
|
639
|
-
const pendingTransaction = this.pendingSyncedTransactions[this.pendingSyncedTransactions.length - 1];
|
|
640
|
-
if (!pendingTransaction) {
|
|
641
|
-
throw new errors.NoPendingSyncTransactionCommitError();
|
|
642
|
-
}
|
|
643
|
-
if (pendingTransaction.committed) {
|
|
644
|
-
throw new errors.SyncTransactionAlreadyCommittedError();
|
|
645
|
-
}
|
|
646
|
-
pendingTransaction.committed = true;
|
|
647
|
-
if (this._status === `loading`) {
|
|
648
|
-
this.setStatus(`initialCommit`);
|
|
649
|
-
}
|
|
650
|
-
this.commitPendingTransactions();
|
|
651
|
-
},
|
|
652
|
-
markReady: () => {
|
|
653
|
-
this.markReady();
|
|
654
|
-
},
|
|
655
|
-
truncate: () => {
|
|
656
|
-
const pendingTransaction = this.pendingSyncedTransactions[this.pendingSyncedTransactions.length - 1];
|
|
657
|
-
if (!pendingTransaction) {
|
|
658
|
-
throw new errors.NoPendingSyncTransactionWriteError();
|
|
659
|
-
}
|
|
660
|
-
if (pendingTransaction.committed) {
|
|
661
|
-
throw new errors.SyncTransactionAlreadyCommittedWriteError();
|
|
662
|
-
}
|
|
663
|
-
pendingTransaction.operations = [];
|
|
664
|
-
pendingTransaction.deletedKeys.clear();
|
|
665
|
-
pendingTransaction.truncate = true;
|
|
666
|
-
}
|
|
667
|
-
});
|
|
668
|
-
this.syncCleanupFn = typeof cleanupFn === `function` ? cleanupFn : null;
|
|
669
|
-
} catch (error) {
|
|
670
|
-
this.setStatus(`error`);
|
|
671
|
-
throw error;
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
/**
|
|
675
|
-
* Preload the collection data by starting sync if not already started
|
|
676
|
-
* Multiple concurrent calls will share the same promise
|
|
677
|
-
*/
|
|
678
|
-
preload() {
|
|
679
|
-
if (this.preloadPromise) {
|
|
680
|
-
return this.preloadPromise;
|
|
681
|
-
}
|
|
682
|
-
this.preloadPromise = new Promise((resolve, reject) => {
|
|
683
|
-
if (this._status === `ready`) {
|
|
684
|
-
resolve();
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
if (this._status === `error`) {
|
|
688
|
-
reject(new errors.CollectionIsInErrorStateError());
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
this.onFirstReady(() => {
|
|
692
|
-
resolve();
|
|
693
|
-
});
|
|
694
|
-
if (this._status === `idle` || this._status === `cleaned-up`) {
|
|
695
|
-
try {
|
|
696
|
-
this.startSync();
|
|
697
|
-
} catch (error) {
|
|
698
|
-
reject(error);
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
});
|
|
703
|
-
return this.preloadPromise;
|
|
704
|
-
}
|
|
705
|
-
/**
|
|
706
|
-
* Clean up the collection by stopping sync and clearing data
|
|
707
|
-
* This can be called manually or automatically by garbage collection
|
|
708
|
-
*/
|
|
709
|
-
async cleanup() {
|
|
710
|
-
if (this.gcTimeoutId) {
|
|
711
|
-
clearTimeout(this.gcTimeoutId);
|
|
712
|
-
this.gcTimeoutId = null;
|
|
713
|
-
}
|
|
714
|
-
try {
|
|
715
|
-
if (this.syncCleanupFn) {
|
|
716
|
-
this.syncCleanupFn();
|
|
717
|
-
this.syncCleanupFn = null;
|
|
718
|
-
}
|
|
719
|
-
} catch (error) {
|
|
720
|
-
queueMicrotask(() => {
|
|
721
|
-
if (error instanceof Error) {
|
|
722
|
-
const wrappedError = new errors.SyncCleanupError(this.id, error);
|
|
723
|
-
wrappedError.cause = error;
|
|
724
|
-
wrappedError.stack = error.stack;
|
|
725
|
-
throw wrappedError;
|
|
726
|
-
} else {
|
|
727
|
-
throw new errors.SyncCleanupError(this.id, error);
|
|
728
|
-
}
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
this.syncedData.clear();
|
|
732
|
-
this.syncedMetadata.clear();
|
|
733
|
-
this.optimisticUpserts.clear();
|
|
734
|
-
this.optimisticDeletes.clear();
|
|
735
|
-
this._size = 0;
|
|
736
|
-
this.pendingSyncedTransactions = [];
|
|
737
|
-
this.syncedKeys.clear();
|
|
738
|
-
this.hasReceivedFirstCommit = false;
|
|
739
|
-
this.hasBeenReady = false;
|
|
740
|
-
this.onFirstReadyCallbacks = [];
|
|
741
|
-
this.preloadPromise = null;
|
|
742
|
-
this.batchedEvents = [];
|
|
743
|
-
this.shouldBatchEvents = false;
|
|
744
|
-
this.events.cleanup();
|
|
745
|
-
this.setStatus(`cleaned-up`);
|
|
746
|
-
return Promise.resolve();
|
|
747
|
-
}
|
|
748
|
-
/**
|
|
749
|
-
* Start the garbage collection timer
|
|
750
|
-
* Called when the collection becomes inactive (no subscribers)
|
|
751
|
-
*/
|
|
752
|
-
startGCTimer() {
|
|
753
|
-
if (this.gcTimeoutId) {
|
|
754
|
-
clearTimeout(this.gcTimeoutId);
|
|
755
|
-
}
|
|
756
|
-
const gcTime = this.config.gcTime ?? 3e5;
|
|
757
|
-
if (gcTime === 0) {
|
|
758
|
-
return;
|
|
759
|
-
}
|
|
760
|
-
this.gcTimeoutId = setTimeout(() => {
|
|
761
|
-
if (this.activeSubscribersCount === 0) {
|
|
762
|
-
this.cleanup();
|
|
763
|
-
}
|
|
764
|
-
}, gcTime);
|
|
765
|
-
}
|
|
766
|
-
/**
|
|
767
|
-
* Cancel the garbage collection timer
|
|
768
|
-
* Called when the collection becomes active again
|
|
769
|
-
*/
|
|
770
|
-
cancelGCTimer() {
|
|
771
|
-
if (this.gcTimeoutId) {
|
|
772
|
-
clearTimeout(this.gcTimeoutId);
|
|
773
|
-
this.gcTimeoutId = null;
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
/**
|
|
777
|
-
* Increment the active subscribers count and start sync if needed
|
|
778
|
-
*/
|
|
779
|
-
addSubscriber() {
|
|
780
|
-
const previousSubscriberCount = this.activeSubscribersCount;
|
|
781
|
-
this.activeSubscribersCount++;
|
|
782
|
-
this.cancelGCTimer();
|
|
783
|
-
if (this._status === `cleaned-up` || this._status === `idle`) {
|
|
784
|
-
this.startSync();
|
|
785
|
-
}
|
|
786
|
-
this.events.emitSubscribersChange(
|
|
787
|
-
this.activeSubscribersCount,
|
|
788
|
-
previousSubscriberCount
|
|
789
|
-
);
|
|
790
|
-
}
|
|
791
|
-
/**
|
|
792
|
-
* Decrement the active subscribers count and start GC timer if needed
|
|
793
|
-
*/
|
|
794
|
-
removeSubscriber() {
|
|
795
|
-
const previousSubscriberCount = this.activeSubscribersCount;
|
|
796
|
-
this.activeSubscribersCount--;
|
|
797
|
-
if (this.activeSubscribersCount === 0) {
|
|
798
|
-
this.startGCTimer();
|
|
799
|
-
} else if (this.activeSubscribersCount < 0) {
|
|
800
|
-
throw new errors.NegativeActiveSubscribersError();
|
|
801
|
-
}
|
|
802
|
-
this.events.emitSubscribersChange(
|
|
803
|
-
this.activeSubscribersCount,
|
|
804
|
-
previousSubscriberCount
|
|
805
|
-
);
|
|
806
|
-
}
|
|
807
|
-
/**
|
|
808
|
-
* Recompute optimistic state from active transactions
|
|
809
|
-
*/
|
|
810
|
-
recomputeOptimisticState(triggeredByUserAction = false) {
|
|
811
|
-
if (this.isCommittingSyncTransactions) {
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
const previousState = new Map(this.optimisticUpserts);
|
|
815
|
-
const previousDeletes = new Set(this.optimisticDeletes);
|
|
816
|
-
this.optimisticUpserts.clear();
|
|
817
|
-
this.optimisticDeletes.clear();
|
|
818
|
-
const activeTransactions = [];
|
|
819
|
-
for (const transaction of this.transactions.values()) {
|
|
820
|
-
if (![`completed`, `failed`].includes(transaction.state)) {
|
|
821
|
-
activeTransactions.push(transaction);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
for (const transaction of activeTransactions) {
|
|
825
|
-
for (const mutation of transaction.mutations) {
|
|
826
|
-
if (mutation.collection === this && mutation.optimistic) {
|
|
827
|
-
switch (mutation.type) {
|
|
828
|
-
case `insert`:
|
|
829
|
-
case `update`:
|
|
830
|
-
this.optimisticUpserts.set(
|
|
831
|
-
mutation.key,
|
|
832
|
-
mutation.modified
|
|
833
|
-
);
|
|
834
|
-
this.optimisticDeletes.delete(mutation.key);
|
|
835
|
-
break;
|
|
836
|
-
case `delete`:
|
|
837
|
-
this.optimisticUpserts.delete(mutation.key);
|
|
838
|
-
this.optimisticDeletes.add(mutation.key);
|
|
839
|
-
break;
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
this._size = this.calculateSize();
|
|
845
|
-
const events = [];
|
|
846
|
-
this.collectOptimisticChanges(previousState, previousDeletes, events);
|
|
847
|
-
const filteredEventsBySyncStatus = events.filter((event) => {
|
|
848
|
-
if (!this.recentlySyncedKeys.has(event.key)) {
|
|
849
|
-
return true;
|
|
850
|
-
}
|
|
851
|
-
if (triggeredByUserAction) {
|
|
852
|
-
return true;
|
|
853
|
-
}
|
|
854
|
-
return false;
|
|
855
|
-
});
|
|
856
|
-
if (this.pendingSyncedTransactions.length > 0 && !triggeredByUserAction) {
|
|
857
|
-
const pendingSyncKeys = /* @__PURE__ */ new Set();
|
|
858
|
-
for (const transaction of this.pendingSyncedTransactions) {
|
|
859
|
-
for (const operation of transaction.operations) {
|
|
860
|
-
pendingSyncKeys.add(operation.key);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
const filteredEvents = filteredEventsBySyncStatus.filter((event) => {
|
|
864
|
-
if (event.type === `delete` && pendingSyncKeys.has(event.key)) {
|
|
865
|
-
const hasActiveOptimisticMutation = activeTransactions.some(
|
|
866
|
-
(tx) => tx.mutations.some(
|
|
867
|
-
(m) => m.collection === this && m.key === event.key
|
|
868
|
-
)
|
|
869
|
-
);
|
|
870
|
-
if (!hasActiveOptimisticMutation) {
|
|
871
|
-
return false;
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
return true;
|
|
875
|
-
});
|
|
876
|
-
if (filteredEvents.length > 0) {
|
|
877
|
-
this.updateIndexes(filteredEvents);
|
|
878
|
-
}
|
|
879
|
-
this.emitEvents(filteredEvents, triggeredByUserAction);
|
|
880
|
-
} else {
|
|
881
|
-
if (filteredEventsBySyncStatus.length > 0) {
|
|
882
|
-
this.updateIndexes(filteredEventsBySyncStatus);
|
|
883
|
-
}
|
|
884
|
-
this.emitEvents(filteredEventsBySyncStatus, triggeredByUserAction);
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* Calculate the current size based on synced data and optimistic changes
|
|
889
|
-
*/
|
|
890
|
-
calculateSize() {
|
|
891
|
-
const syncedSize = this.syncedData.size;
|
|
892
|
-
const deletesFromSynced = Array.from(this.optimisticDeletes).filter(
|
|
893
|
-
(key) => this.syncedData.has(key) && !this.optimisticUpserts.has(key)
|
|
894
|
-
).length;
|
|
895
|
-
const upsertsNotInSynced = Array.from(this.optimisticUpserts.keys()).filter(
|
|
896
|
-
(key) => !this.syncedData.has(key)
|
|
897
|
-
).length;
|
|
898
|
-
return syncedSize - deletesFromSynced + upsertsNotInSynced;
|
|
899
|
-
}
|
|
900
|
-
/**
|
|
901
|
-
* Collect events for optimistic changes
|
|
902
|
-
*/
|
|
903
|
-
collectOptimisticChanges(previousUpserts, previousDeletes, events) {
|
|
904
|
-
const allKeys = /* @__PURE__ */ new Set([
|
|
905
|
-
...previousUpserts.keys(),
|
|
906
|
-
...this.optimisticUpserts.keys(),
|
|
907
|
-
...previousDeletes,
|
|
908
|
-
...this.optimisticDeletes
|
|
909
|
-
]);
|
|
910
|
-
for (const key of allKeys) {
|
|
911
|
-
const currentValue = this.get(key);
|
|
912
|
-
const previousValue = this.getPreviousValue(
|
|
913
|
-
key,
|
|
914
|
-
previousUpserts,
|
|
915
|
-
previousDeletes
|
|
916
|
-
);
|
|
917
|
-
if (previousValue !== void 0 && currentValue === void 0) {
|
|
918
|
-
events.push({ type: `delete`, key, value: previousValue });
|
|
919
|
-
} else if (previousValue === void 0 && currentValue !== void 0) {
|
|
920
|
-
events.push({ type: `insert`, key, value: currentValue });
|
|
921
|
-
} else if (previousValue !== void 0 && currentValue !== void 0 && previousValue !== currentValue) {
|
|
922
|
-
events.push({
|
|
923
|
-
type: `update`,
|
|
924
|
-
key,
|
|
925
|
-
value: currentValue,
|
|
926
|
-
previousValue
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
/**
|
|
932
|
-
* Get the previous value for a key given previous optimistic state
|
|
933
|
-
*/
|
|
934
|
-
getPreviousValue(key, previousUpserts, previousDeletes) {
|
|
935
|
-
if (previousDeletes.has(key)) {
|
|
936
|
-
return void 0;
|
|
937
|
-
}
|
|
938
|
-
if (previousUpserts.has(key)) {
|
|
939
|
-
return previousUpserts.get(key);
|
|
940
|
-
}
|
|
941
|
-
return this.syncedData.get(key);
|
|
942
|
-
}
|
|
943
|
-
/**
|
|
944
|
-
* Emit an empty ready event to notify subscribers that the collection is ready
|
|
945
|
-
* This bypasses the normal empty array check in emitEvents
|
|
946
|
-
*/
|
|
947
|
-
emitEmptyReadyEvent() {
|
|
948
|
-
for (const listener of this.changeListeners) {
|
|
949
|
-
listener([]);
|
|
950
|
-
}
|
|
951
|
-
for (const [_key, keyListeners] of this.changeKeyListeners) {
|
|
952
|
-
for (const listener of keyListeners) {
|
|
953
|
-
listener([]);
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
/**
|
|
958
|
-
* Emit events either immediately or batch them for later emission
|
|
959
|
-
*/
|
|
960
|
-
emitEvents(changes, forceEmit = false) {
|
|
961
|
-
if (this.shouldBatchEvents && !forceEmit) {
|
|
962
|
-
this.batchedEvents.push(...changes);
|
|
963
|
-
return;
|
|
964
|
-
}
|
|
965
|
-
let eventsToEmit = changes;
|
|
966
|
-
if (this.batchedEvents.length > 0 && forceEmit) {
|
|
967
|
-
eventsToEmit = [...this.batchedEvents, ...changes];
|
|
968
|
-
this.batchedEvents = [];
|
|
969
|
-
this.shouldBatchEvents = false;
|
|
970
|
-
}
|
|
971
|
-
if (eventsToEmit.length === 0) return;
|
|
972
|
-
for (const listener of this.changeListeners) {
|
|
973
|
-
listener(eventsToEmit);
|
|
974
|
-
}
|
|
975
|
-
if (this.changeKeyListeners.size > 0) {
|
|
976
|
-
const changesByKey = /* @__PURE__ */ new Map();
|
|
977
|
-
for (const change of eventsToEmit) {
|
|
978
|
-
if (this.changeKeyListeners.has(change.key)) {
|
|
979
|
-
if (!changesByKey.has(change.key)) {
|
|
980
|
-
changesByKey.set(change.key, []);
|
|
981
|
-
}
|
|
982
|
-
changesByKey.get(change.key).push(change);
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
for (const [key, keyChanges] of changesByKey) {
|
|
986
|
-
const keyListeners = this.changeKeyListeners.get(key);
|
|
987
|
-
for (const listener of keyListeners) {
|
|
988
|
-
listener(keyChanges);
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
/**
|
|
994
|
-
* Get the current value for a key (virtual derived state)
|
|
995
|
-
*/
|
|
996
|
-
get(key) {
|
|
997
|
-
if (this.optimisticDeletes.has(key)) {
|
|
998
|
-
return void 0;
|
|
999
|
-
}
|
|
1000
|
-
if (this.optimisticUpserts.has(key)) {
|
|
1001
|
-
return this.optimisticUpserts.get(key);
|
|
1002
|
-
}
|
|
1003
|
-
return this.syncedData.get(key);
|
|
1004
|
-
}
|
|
1005
|
-
/**
|
|
1006
|
-
* Check if a key exists in the collection (virtual derived state)
|
|
1007
|
-
*/
|
|
1008
|
-
has(key) {
|
|
1009
|
-
if (this.optimisticDeletes.has(key)) {
|
|
1010
|
-
return false;
|
|
1011
|
-
}
|
|
1012
|
-
if (this.optimisticUpserts.has(key)) {
|
|
1013
|
-
return true;
|
|
1014
|
-
}
|
|
1015
|
-
return this.syncedData.has(key);
|
|
1016
|
-
}
|
|
1017
|
-
/**
|
|
1018
|
-
* Get the current size of the collection (cached)
|
|
1019
|
-
*/
|
|
1020
|
-
get size() {
|
|
1021
|
-
return this._size;
|
|
1022
|
-
}
|
|
1023
|
-
/**
|
|
1024
|
-
* Get all keys (virtual derived state)
|
|
1025
|
-
*/
|
|
1026
|
-
*keys() {
|
|
1027
|
-
for (const key of this.syncedData.keys()) {
|
|
1028
|
-
if (!this.optimisticDeletes.has(key)) {
|
|
1029
|
-
yield key;
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
for (const key of this.optimisticUpserts.keys()) {
|
|
1033
|
-
if (!this.syncedData.has(key) && !this.optimisticDeletes.has(key)) {
|
|
1034
|
-
yield key;
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
/**
|
|
1039
|
-
* Get all values (virtual derived state)
|
|
1040
|
-
*/
|
|
1041
|
-
*values() {
|
|
1042
|
-
for (const key of this.keys()) {
|
|
1043
|
-
const value = this.get(key);
|
|
1044
|
-
if (value !== void 0) {
|
|
1045
|
-
yield value;
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
/**
|
|
1050
|
-
* Get all entries (virtual derived state)
|
|
1051
|
-
*/
|
|
1052
|
-
*entries() {
|
|
1053
|
-
for (const key of this.keys()) {
|
|
1054
|
-
const value = this.get(key);
|
|
1055
|
-
if (value !== void 0) {
|
|
1056
|
-
yield [key, value];
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
/**
|
|
1061
|
-
* Get all entries (virtual derived state)
|
|
1062
|
-
*/
|
|
1063
|
-
*[Symbol.iterator]() {
|
|
1064
|
-
for (const [key, value] of this.entries()) {
|
|
1065
|
-
yield [key, value];
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
/**
|
|
1069
|
-
* Execute a callback for each entry in the collection
|
|
1070
|
-
*/
|
|
1071
|
-
forEach(callbackfn) {
|
|
1072
|
-
let index = 0;
|
|
1073
|
-
for (const [key, value] of this.entries()) {
|
|
1074
|
-
callbackfn(value, key, index++);
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
/**
|
|
1078
|
-
* Create a new array with the results of calling a function for each entry in the collection
|
|
1079
|
-
*/
|
|
1080
|
-
map(callbackfn) {
|
|
1081
|
-
const result = [];
|
|
1082
|
-
let index = 0;
|
|
1083
|
-
for (const [key, value] of this.entries()) {
|
|
1084
|
-
result.push(callbackfn(value, key, index++));
|
|
1085
|
-
}
|
|
1086
|
-
return result;
|
|
1087
|
-
}
|
|
1088
|
-
/**
|
|
1089
|
-
* Schedule cleanup of a transaction when it completes
|
|
1090
|
-
* @private
|
|
1091
|
-
*/
|
|
1092
|
-
scheduleTransactionCleanup(transaction) {
|
|
1093
|
-
if (transaction.state === `completed`) {
|
|
1094
|
-
this.transactions.delete(transaction.id);
|
|
1095
|
-
return;
|
|
1096
|
-
}
|
|
1097
|
-
transaction.isPersisted.promise.then(() => {
|
|
1098
|
-
this.transactions.delete(transaction.id);
|
|
1099
|
-
}).catch(() => {
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
ensureStandardSchema(schema) {
|
|
1103
|
-
if (schema && `~standard` in schema) {
|
|
1104
|
-
return schema;
|
|
1105
|
-
}
|
|
1106
|
-
throw new errors.InvalidSchemaError();
|
|
1107
|
-
}
|
|
1108
|
-
getKeyFromItem(item) {
|
|
1109
|
-
return this.config.getKey(item);
|
|
1110
|
-
}
|
|
1111
|
-
generateGlobalKey(key, item) {
|
|
1112
|
-
if (typeof key === `undefined`) {
|
|
1113
|
-
throw new errors.UndefinedKeyError(item);
|
|
1114
|
-
}
|
|
1115
|
-
return `KEY::${this.id}/${key}`;
|
|
1116
|
-
}
|
|
1117
|
-
/**
|
|
1118
|
-
* Creates an index on a collection for faster queries.
|
|
1119
|
-
* Indexes significantly improve query performance by allowing constant time lookups
|
|
1120
|
-
* and logarithmic time range queries instead of full scans.
|
|
1121
|
-
*
|
|
1122
|
-
* @template TResolver - The type of the index resolver (constructor or async loader)
|
|
1123
|
-
* @param indexCallback - Function that extracts the indexed value from each item
|
|
1124
|
-
* @param config - Configuration including index type and type-specific options
|
|
1125
|
-
* @returns An index proxy that provides access to the index when ready
|
|
1126
|
-
*
|
|
1127
|
-
* @example
|
|
1128
|
-
* // Create a default B+ tree index
|
|
1129
|
-
* const ageIndex = collection.createIndex((row) => row.age)
|
|
1130
|
-
*
|
|
1131
|
-
* // Create a ordered index with custom options
|
|
1132
|
-
* const ageIndex = collection.createIndex((row) => row.age, {
|
|
1133
|
-
* indexType: BTreeIndex,
|
|
1134
|
-
* options: { compareFn: customComparator },
|
|
1135
|
-
* name: 'age_btree'
|
|
1136
|
-
* })
|
|
1137
|
-
*
|
|
1138
|
-
* // Create an async-loaded index
|
|
1139
|
-
* const textIndex = collection.createIndex((row) => row.content, {
|
|
1140
|
-
* indexType: async () => {
|
|
1141
|
-
* const { FullTextIndex } = await import('./indexes/fulltext.js')
|
|
1142
|
-
* return FullTextIndex
|
|
1143
|
-
* },
|
|
1144
|
-
* options: { language: 'en' }
|
|
1145
|
-
* })
|
|
1146
|
-
*/
|
|
1147
|
-
createIndex(indexCallback, config = {}) {
|
|
1148
|
-
this.validateCollectionUsable(`createIndex`);
|
|
1149
|
-
const indexId = ++this.indexCounter;
|
|
1150
|
-
const singleRowRefProxy = refProxy.createSingleRowRefProxy();
|
|
1151
|
-
const indexExpression = indexCallback(singleRowRefProxy);
|
|
1152
|
-
const expression = refProxy.toExpression(indexExpression);
|
|
1153
|
-
const resolver = config.indexType ?? btreeIndex.BTreeIndex;
|
|
1154
|
-
const lazyIndex$1 = new lazyIndex.LazyIndexWrapper(
|
|
1155
|
-
indexId,
|
|
1156
|
-
expression,
|
|
1157
|
-
config.name,
|
|
1158
|
-
resolver,
|
|
1159
|
-
config.options,
|
|
1160
|
-
this.entries()
|
|
1161
|
-
);
|
|
1162
|
-
this.lazyIndexes.set(indexId, lazyIndex$1);
|
|
1163
|
-
if (resolver === btreeIndex.BTreeIndex) {
|
|
1164
|
-
try {
|
|
1165
|
-
const resolvedIndex = lazyIndex$1.getResolved();
|
|
1166
|
-
this.resolvedIndexes.set(indexId, resolvedIndex);
|
|
1167
|
-
} catch (error) {
|
|
1168
|
-
console.warn(`Failed to resolve BTreeIndex:`, error);
|
|
1169
|
-
}
|
|
1170
|
-
} else if (typeof resolver === `function` && resolver.prototype) {
|
|
1171
|
-
try {
|
|
1172
|
-
const resolvedIndex = lazyIndex$1.getResolved();
|
|
1173
|
-
this.resolvedIndexes.set(indexId, resolvedIndex);
|
|
1174
|
-
} catch {
|
|
1175
|
-
this.resolveSingleIndex(indexId, lazyIndex$1).catch((error) => {
|
|
1176
|
-
console.warn(`Failed to resolve single index:`, error);
|
|
1177
|
-
});
|
|
1178
|
-
}
|
|
1179
|
-
} else if (this.isIndexesResolved) {
|
|
1180
|
-
this.resolveSingleIndex(indexId, lazyIndex$1).catch((error) => {
|
|
1181
|
-
console.warn(`Failed to resolve single index:`, error);
|
|
1182
|
-
});
|
|
1183
|
-
}
|
|
1184
|
-
return new lazyIndex.IndexProxy(indexId, lazyIndex$1);
|
|
1185
|
-
}
|
|
1186
|
-
/**
|
|
1187
|
-
* Resolve all lazy indexes (called when collection first syncs)
|
|
1188
|
-
* @private
|
|
1189
|
-
*/
|
|
1190
|
-
async resolveAllIndexes() {
|
|
1191
|
-
if (this.isIndexesResolved) return;
|
|
1192
|
-
const resolutionPromises = Array.from(this.lazyIndexes.entries()).map(
|
|
1193
|
-
async ([indexId, lazyIndex2]) => {
|
|
1194
|
-
const resolvedIndex = await lazyIndex2.resolve();
|
|
1195
|
-
resolvedIndex.build(this.entries());
|
|
1196
|
-
this.resolvedIndexes.set(indexId, resolvedIndex);
|
|
1197
|
-
return { indexId, resolvedIndex };
|
|
1198
|
-
}
|
|
1199
|
-
);
|
|
1200
|
-
await Promise.all(resolutionPromises);
|
|
1201
|
-
this.isIndexesResolved = true;
|
|
1202
|
-
}
|
|
1203
|
-
/**
|
|
1204
|
-
* Resolve a single index immediately
|
|
1205
|
-
* @private
|
|
1206
|
-
*/
|
|
1207
|
-
async resolveSingleIndex(indexId, lazyIndex2) {
|
|
1208
|
-
const resolvedIndex = await lazyIndex2.resolve();
|
|
1209
|
-
resolvedIndex.build(this.entries());
|
|
1210
|
-
this.resolvedIndexes.set(indexId, resolvedIndex);
|
|
1211
|
-
return resolvedIndex;
|
|
1212
|
-
}
|
|
1213
|
-
/**
|
|
1214
|
-
* Get resolved indexes for query optimization
|
|
1215
|
-
*/
|
|
1216
|
-
get indexes() {
|
|
1217
|
-
return this.resolvedIndexes;
|
|
1218
|
-
}
|
|
1219
|
-
/**
|
|
1220
|
-
* Updates all indexes when the collection changes
|
|
1221
|
-
* @private
|
|
1222
|
-
*/
|
|
1223
|
-
updateIndexes(changes) {
|
|
1224
|
-
for (const index of this.resolvedIndexes.values()) {
|
|
1225
|
-
for (const change of changes) {
|
|
1226
|
-
switch (change.type) {
|
|
1227
|
-
case `insert`:
|
|
1228
|
-
index.add(change.key, change.value);
|
|
1229
|
-
break;
|
|
1230
|
-
case `update`:
|
|
1231
|
-
if (change.previousValue) {
|
|
1232
|
-
index.update(change.key, change.previousValue, change.value);
|
|
1233
|
-
} else {
|
|
1234
|
-
index.add(change.key, change.value);
|
|
1235
|
-
}
|
|
1236
|
-
break;
|
|
1237
|
-
case `delete`:
|
|
1238
|
-
index.remove(change.key, change.value);
|
|
1239
|
-
break;
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
validateData(data, type, key) {
|
|
1245
|
-
if (!this.config.schema) return data;
|
|
1246
|
-
const standardSchema = this.ensureStandardSchema(this.config.schema);
|
|
1247
|
-
if (type === `update` && key) {
|
|
1248
|
-
const existingData = this.get(key);
|
|
1249
|
-
if (existingData && data && typeof data === `object` && typeof existingData === `object`) {
|
|
1250
|
-
const mergedData = Object.assign({}, existingData, data);
|
|
1251
|
-
const result2 = standardSchema[`~standard`].validate(mergedData);
|
|
1252
|
-
if (result2 instanceof Promise) {
|
|
1253
|
-
throw new errors.SchemaMustBeSynchronousError();
|
|
1254
|
-
}
|
|
1255
|
-
if (`issues` in result2 && result2.issues) {
|
|
1256
|
-
const typedIssues = result2.issues.map((issue) => {
|
|
1257
|
-
var _a;
|
|
1258
|
-
return {
|
|
1259
|
-
message: issue.message,
|
|
1260
|
-
path: (_a = issue.path) == null ? void 0 : _a.map((p) => String(p))
|
|
1261
|
-
};
|
|
1262
|
-
});
|
|
1263
|
-
throw new errors.SchemaValidationError(type, typedIssues);
|
|
1264
|
-
}
|
|
1265
|
-
const validatedMergedData = result2.value;
|
|
1266
|
-
const modifiedKeys = Object.keys(data);
|
|
1267
|
-
const extractedChanges = Object.fromEntries(
|
|
1268
|
-
modifiedKeys.map((k) => [k, validatedMergedData[k]])
|
|
1269
|
-
);
|
|
1270
|
-
return extractedChanges;
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
const result = standardSchema[`~standard`].validate(data);
|
|
1274
|
-
if (result instanceof Promise) {
|
|
1275
|
-
throw new errors.SchemaMustBeSynchronousError();
|
|
1276
|
-
}
|
|
1277
|
-
if (`issues` in result && result.issues) {
|
|
1278
|
-
const typedIssues = result.issues.map((issue) => {
|
|
1279
|
-
var _a;
|
|
1280
|
-
return {
|
|
1281
|
-
message: issue.message,
|
|
1282
|
-
path: (_a = issue.path) == null ? void 0 : _a.map((p) => String(p))
|
|
1283
|
-
};
|
|
1284
|
-
});
|
|
1285
|
-
throw new errors.SchemaValidationError(type, typedIssues);
|
|
1286
|
-
}
|
|
1287
|
-
return result.value;
|
|
1288
|
-
}
|
|
1289
|
-
update(keys, configOrCallback, maybeCallback) {
|
|
1290
|
-
if (typeof keys === `undefined`) {
|
|
1291
|
-
throw new errors.MissingUpdateArgumentError();
|
|
1292
|
-
}
|
|
1293
|
-
this.validateCollectionUsable(`update`);
|
|
1294
|
-
const ambientTransaction = transactions.getActiveTransaction();
|
|
1295
|
-
if (!ambientTransaction && !this.config.onUpdate) {
|
|
1296
|
-
throw new errors.MissingUpdateHandlerError();
|
|
1297
|
-
}
|
|
1298
|
-
const isArray = Array.isArray(keys);
|
|
1299
|
-
const keysArray = isArray ? keys : [keys];
|
|
1300
|
-
if (isArray && keysArray.length === 0) {
|
|
1301
|
-
throw new errors.NoKeysPassedToUpdateError();
|
|
1302
|
-
}
|
|
1303
|
-
const callback = typeof configOrCallback === `function` ? configOrCallback : maybeCallback;
|
|
1304
|
-
const config = typeof configOrCallback === `function` ? {} : configOrCallback;
|
|
1305
|
-
const currentObjects = keysArray.map((key) => {
|
|
1306
|
-
const item = this.get(key);
|
|
1307
|
-
if (!item) {
|
|
1308
|
-
throw new errors.UpdateKeyNotFoundError(key);
|
|
1309
|
-
}
|
|
1310
|
-
return item;
|
|
1311
|
-
});
|
|
1312
|
-
let changesArray;
|
|
1313
|
-
if (isArray) {
|
|
1314
|
-
changesArray = proxy.withArrayChangeTracking(
|
|
1315
|
-
currentObjects,
|
|
1316
|
-
callback
|
|
1317
|
-
);
|
|
1318
|
-
} else {
|
|
1319
|
-
const result = proxy.withChangeTracking(
|
|
1320
|
-
currentObjects[0],
|
|
1321
|
-
callback
|
|
1322
|
-
);
|
|
1323
|
-
changesArray = [result];
|
|
1324
|
-
}
|
|
1325
|
-
const mutations = keysArray.map((key, index) => {
|
|
1326
|
-
const itemChanges = changesArray[index];
|
|
1327
|
-
if (!itemChanges || Object.keys(itemChanges).length === 0) {
|
|
1328
|
-
return null;
|
|
1329
|
-
}
|
|
1330
|
-
const originalItem = currentObjects[index];
|
|
1331
|
-
const validatedUpdatePayload = this.validateData(
|
|
1332
|
-
itemChanges,
|
|
1333
|
-
`update`,
|
|
1334
|
-
key
|
|
1335
|
-
);
|
|
1336
|
-
const modifiedItem = Object.assign(
|
|
1337
|
-
{},
|
|
1338
|
-
originalItem,
|
|
1339
|
-
validatedUpdatePayload
|
|
1340
|
-
);
|
|
1341
|
-
const originalItemId = this.getKeyFromItem(originalItem);
|
|
1342
|
-
const modifiedItemId = this.getKeyFromItem(modifiedItem);
|
|
1343
|
-
if (originalItemId !== modifiedItemId) {
|
|
1344
|
-
throw new errors.KeyUpdateNotAllowedError(originalItemId, modifiedItemId);
|
|
1345
|
-
}
|
|
1346
|
-
const globalKey = this.generateGlobalKey(modifiedItemId, modifiedItem);
|
|
1347
|
-
return {
|
|
1348
|
-
mutationId: crypto.randomUUID(),
|
|
1349
|
-
original: originalItem,
|
|
1350
|
-
modified: modifiedItem,
|
|
1351
|
-
// Pick the values from modifiedItem based on what's passed in - this is for cases
|
|
1352
|
-
// where a schema has default values or transforms. The modified data has the extra
|
|
1353
|
-
// default or transformed values but for changes, we just want to show the data that
|
|
1354
|
-
// was actually passed in.
|
|
1355
|
-
changes: Object.fromEntries(
|
|
1356
|
-
Object.keys(itemChanges).map((k) => [
|
|
1357
|
-
k,
|
|
1358
|
-
modifiedItem[k]
|
|
1359
|
-
])
|
|
1360
|
-
),
|
|
1361
|
-
globalKey,
|
|
1362
|
-
key,
|
|
1363
|
-
metadata: config.metadata,
|
|
1364
|
-
syncMetadata: this.syncedMetadata.get(key) || {},
|
|
1365
|
-
optimistic: config.optimistic ?? true,
|
|
1366
|
-
type: `update`,
|
|
1367
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
1368
|
-
updatedAt: /* @__PURE__ */ new Date(),
|
|
1369
|
-
collection: this
|
|
1370
|
-
};
|
|
1371
|
-
}).filter(Boolean);
|
|
1372
|
-
if (mutations.length === 0) {
|
|
1373
|
-
const emptyTransaction = transactions.createTransaction({
|
|
1374
|
-
mutationFn: async () => {
|
|
1375
|
-
}
|
|
1376
|
-
});
|
|
1377
|
-
emptyTransaction.commit();
|
|
1378
|
-
this.scheduleTransactionCleanup(emptyTransaction);
|
|
1379
|
-
return emptyTransaction;
|
|
1380
|
-
}
|
|
1381
|
-
if (ambientTransaction) {
|
|
1382
|
-
ambientTransaction.applyMutations(mutations);
|
|
1383
|
-
this.transactions.set(ambientTransaction.id, ambientTransaction);
|
|
1384
|
-
this.scheduleTransactionCleanup(ambientTransaction);
|
|
1385
|
-
this.recomputeOptimisticState(true);
|
|
1386
|
-
return ambientTransaction;
|
|
1387
|
-
}
|
|
1388
|
-
const directOpTransaction = transactions.createTransaction({
|
|
1389
|
-
mutationFn: async (params) => {
|
|
1390
|
-
return this.config.onUpdate({
|
|
1391
|
-
transaction: params.transaction,
|
|
1392
|
-
collection: this
|
|
1393
|
-
});
|
|
1394
|
-
}
|
|
1395
|
-
});
|
|
1396
|
-
directOpTransaction.applyMutations(mutations);
|
|
1397
|
-
directOpTransaction.commit();
|
|
1398
|
-
this.transactions.set(directOpTransaction.id, directOpTransaction);
|
|
1399
|
-
this.scheduleTransactionCleanup(directOpTransaction);
|
|
1400
|
-
this.recomputeOptimisticState(true);
|
|
1401
|
-
return directOpTransaction;
|
|
1402
|
-
}
|
|
1403
|
-
/**
|
|
1404
|
-
* Gets the current state of the collection as a Map
|
|
1405
|
-
* @returns Map containing all items in the collection, with keys as identifiers
|
|
1406
|
-
* @example
|
|
1407
|
-
* const itemsMap = collection.state
|
|
1408
|
-
* console.log(`Collection has ${itemsMap.size} items`)
|
|
1409
|
-
*
|
|
1410
|
-
* for (const [key, item] of itemsMap) {
|
|
1411
|
-
* console.log(`${key}: ${item.title}`)
|
|
1412
|
-
* }
|
|
1413
|
-
*
|
|
1414
|
-
* // Check if specific item exists
|
|
1415
|
-
* if (itemsMap.has("todo-1")) {
|
|
1416
|
-
* console.log("Todo 1 exists:", itemsMap.get("todo-1"))
|
|
1417
|
-
* }
|
|
1418
|
-
*/
|
|
1419
|
-
get state() {
|
|
1420
|
-
const result = /* @__PURE__ */ new Map();
|
|
1421
|
-
for (const [key, value] of this.entries()) {
|
|
1422
|
-
result.set(key, value);
|
|
1423
|
-
}
|
|
1424
|
-
return result;
|
|
1425
|
-
}
|
|
1426
|
-
/**
|
|
1427
|
-
* Gets the current state of the collection as a Map, but only resolves when data is available
|
|
1428
|
-
* Waits for the first sync commit to complete before resolving
|
|
1429
|
-
*
|
|
1430
|
-
* @returns Promise that resolves to a Map containing all items in the collection
|
|
1431
|
-
*/
|
|
1432
|
-
stateWhenReady() {
|
|
1433
|
-
if (this.size > 0 || this.isReady()) {
|
|
1434
|
-
return Promise.resolve(this.state);
|
|
1435
|
-
}
|
|
1436
|
-
return this.preload().then(() => this.state);
|
|
1437
|
-
}
|
|
1438
|
-
/**
|
|
1439
|
-
* Gets the current state of the collection as an Array
|
|
1440
|
-
*
|
|
1441
|
-
* @returns An Array containing all items in the collection
|
|
1442
|
-
*/
|
|
1443
|
-
get toArray() {
|
|
1444
|
-
return Array.from(this.values());
|
|
1445
|
-
}
|
|
1446
|
-
/**
|
|
1447
|
-
* Gets the current state of the collection as an Array, but only resolves when data is available
|
|
1448
|
-
* Waits for the first sync commit to complete before resolving
|
|
1449
|
-
*
|
|
1450
|
-
* @returns Promise that resolves to an Array containing all items in the collection
|
|
1451
|
-
*/
|
|
1452
|
-
toArrayWhenReady() {
|
|
1453
|
-
if (this.size > 0 || this.isReady()) {
|
|
1454
|
-
return Promise.resolve(this.toArray);
|
|
1455
|
-
}
|
|
1456
|
-
return this.preload().then(() => this.toArray);
|
|
1457
|
-
}
|
|
1458
|
-
/**
|
|
1459
|
-
* Returns the current state of the collection as an array of changes
|
|
1460
|
-
* @param options - Options including optional where filter
|
|
1461
|
-
* @returns An array of changes
|
|
1462
|
-
* @example
|
|
1463
|
-
* // Get all items as changes
|
|
1464
|
-
* const allChanges = collection.currentStateAsChanges()
|
|
1465
|
-
*
|
|
1466
|
-
* // Get only items matching a condition
|
|
1467
|
-
* const activeChanges = collection.currentStateAsChanges({
|
|
1468
|
-
* where: (row) => row.status === 'active'
|
|
1469
|
-
* })
|
|
1470
|
-
*
|
|
1471
|
-
* // Get only items using a pre-compiled expression
|
|
1472
|
-
* const activeChanges = collection.currentStateAsChanges({
|
|
1473
|
-
* whereExpression: eq(row.status, 'active')
|
|
1474
|
-
* })
|
|
1475
|
-
*/
|
|
1476
|
-
currentStateAsChanges(options = {}) {
|
|
1477
|
-
return changeEvents.currentStateAsChanges(this, options);
|
|
1478
|
-
}
|
|
1479
|
-
/**
|
|
1480
|
-
* Subscribe to changes in the collection
|
|
1481
|
-
* @param callback - Function called when items change
|
|
1482
|
-
* @param options - Subscription options including includeInitialState and where filter
|
|
1483
|
-
* @returns Unsubscribe function - Call this to stop listening for changes
|
|
1484
|
-
* @example
|
|
1485
|
-
* // Basic subscription
|
|
1486
|
-
* const unsubscribe = collection.subscribeChanges((changes) => {
|
|
1487
|
-
* changes.forEach(change => {
|
|
1488
|
-
* console.log(`${change.type}: ${change.key}`, change.value)
|
|
1489
|
-
* })
|
|
1490
|
-
* })
|
|
1491
|
-
*
|
|
1492
|
-
* // Later: unsubscribe()
|
|
1493
|
-
*
|
|
1494
|
-
* @example
|
|
1495
|
-
* // Include current state immediately
|
|
1496
|
-
* const unsubscribe = collection.subscribeChanges((changes) => {
|
|
1497
|
-
* updateUI(changes)
|
|
1498
|
-
* }, { includeInitialState: true })
|
|
1499
|
-
*
|
|
1500
|
-
* @example
|
|
1501
|
-
* // Subscribe only to changes matching a condition
|
|
1502
|
-
* const unsubscribe = collection.subscribeChanges((changes) => {
|
|
1503
|
-
* updateUI(changes)
|
|
1504
|
-
* }, {
|
|
1505
|
-
* includeInitialState: true,
|
|
1506
|
-
* where: (row) => row.status === 'active'
|
|
1507
|
-
* })
|
|
1508
|
-
*
|
|
1509
|
-
* @example
|
|
1510
|
-
* // Subscribe using a pre-compiled expression
|
|
1511
|
-
* const unsubscribe = collection.subscribeChanges((changes) => {
|
|
1512
|
-
* updateUI(changes)
|
|
1513
|
-
* }, {
|
|
1514
|
-
* includeInitialState: true,
|
|
1515
|
-
* whereExpression: eq(row.status, 'active')
|
|
1516
|
-
* })
|
|
1517
|
-
*/
|
|
1518
|
-
subscribeChanges(callback, options = {}) {
|
|
1519
|
-
this.addSubscriber();
|
|
1520
|
-
if (options.whereExpression) {
|
|
1521
|
-
autoIndex.ensureIndexForExpression(options.whereExpression, this);
|
|
1522
|
-
}
|
|
1523
|
-
const filteredCallback = options.where || options.whereExpression ? changeEvents.createFilteredCallback(callback, options) : callback;
|
|
1524
|
-
if (options.includeInitialState) {
|
|
1525
|
-
const initialChanges = this.currentStateAsChanges({
|
|
1526
|
-
where: options.where,
|
|
1527
|
-
whereExpression: options.whereExpression
|
|
1528
|
-
});
|
|
1529
|
-
filteredCallback(initialChanges);
|
|
1530
|
-
}
|
|
1531
|
-
this.changeListeners.add(filteredCallback);
|
|
1532
|
-
return () => {
|
|
1533
|
-
this.changeListeners.delete(filteredCallback);
|
|
1534
|
-
this.removeSubscriber();
|
|
1535
|
-
};
|
|
1536
|
-
}
|
|
1537
|
-
/**
|
|
1538
|
-
* Subscribe to changes for a specific key
|
|
1539
|
-
*/
|
|
1540
|
-
subscribeChangesKey(key, listener, { includeInitialState = false } = {}) {
|
|
1541
|
-
this.addSubscriber();
|
|
1542
|
-
if (!this.changeKeyListeners.has(key)) {
|
|
1543
|
-
this.changeKeyListeners.set(key, /* @__PURE__ */ new Set());
|
|
1544
|
-
}
|
|
1545
|
-
if (includeInitialState) {
|
|
1546
|
-
listener([
|
|
1547
|
-
{
|
|
1548
|
-
type: `insert`,
|
|
1549
|
-
key,
|
|
1550
|
-
value: this.get(key)
|
|
1551
|
-
}
|
|
1552
|
-
]);
|
|
1553
|
-
}
|
|
1554
|
-
this.changeKeyListeners.get(key).add(listener);
|
|
1555
|
-
return () => {
|
|
1556
|
-
const listeners = this.changeKeyListeners.get(key);
|
|
1557
|
-
if (listeners) {
|
|
1558
|
-
listeners.delete(listener);
|
|
1559
|
-
if (listeners.size === 0) {
|
|
1560
|
-
this.changeKeyListeners.delete(key);
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
this.removeSubscriber();
|
|
1564
|
-
};
|
|
1565
|
-
}
|
|
1566
|
-
/**
|
|
1567
|
-
* Capture visible state for keys that will be affected by pending sync operations
|
|
1568
|
-
* This must be called BEFORE onTransactionStateChange clears optimistic state
|
|
1569
|
-
*/
|
|
1570
|
-
capturePreSyncVisibleState() {
|
|
1571
|
-
if (this.pendingSyncedTransactions.length === 0) return;
|
|
1572
|
-
this.preSyncVisibleState.clear();
|
|
1573
|
-
const syncedKeys = /* @__PURE__ */ new Set();
|
|
1574
|
-
for (const transaction of this.pendingSyncedTransactions) {
|
|
1575
|
-
for (const operation of transaction.operations) {
|
|
1576
|
-
syncedKeys.add(operation.key);
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
for (const key of syncedKeys) {
|
|
1580
|
-
this.recentlySyncedKeys.add(key);
|
|
1581
|
-
}
|
|
1582
|
-
for (const key of syncedKeys) {
|
|
1583
|
-
const currentValue = this.get(key);
|
|
1584
|
-
if (currentValue !== void 0) {
|
|
1585
|
-
this.preSyncVisibleState.set(key, currentValue);
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
/**
|
|
1590
|
-
* Trigger a recomputation when transactions change
|
|
1591
|
-
* This method should be called by the Transaction class when state changes
|
|
1592
|
-
*/
|
|
1593
|
-
onTransactionStateChange() {
|
|
1594
|
-
this.shouldBatchEvents = this.pendingSyncedTransactions.length > 0;
|
|
1595
|
-
this.capturePreSyncVisibleState();
|
|
1596
|
-
this.recomputeOptimisticState(false);
|
|
1597
|
-
}
|
|
1598
|
-
/**
|
|
1599
|
-
* Subscribe to a collection event
|
|
1600
|
-
*/
|
|
1601
|
-
on(event, callback) {
|
|
1602
|
-
return this.events.on(event, callback);
|
|
1603
|
-
}
|
|
1604
|
-
/**
|
|
1605
|
-
* Subscribe to a collection event once
|
|
1606
|
-
*/
|
|
1607
|
-
once(event, callback) {
|
|
1608
|
-
return this.events.once(event, callback);
|
|
1609
|
-
}
|
|
1610
|
-
/**
|
|
1611
|
-
* Unsubscribe from a collection event
|
|
1612
|
-
*/
|
|
1613
|
-
off(event, callback) {
|
|
1614
|
-
this.events.off(event, callback);
|
|
1615
|
-
}
|
|
1616
|
-
/**
|
|
1617
|
-
* Wait for a collection event
|
|
1618
|
-
*/
|
|
1619
|
-
waitFor(event, timeout) {
|
|
1620
|
-
return this.events.waitFor(event, timeout);
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
exports.CollectionImpl = CollectionImpl;
|
|
1624
|
-
exports.createCollection = createCollection;
|
|
1625
|
-
//# sourceMappingURL=collection.cjs.map
|