@tanstack/db 0.0.1
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/README.md +37 -0
- package/dist/cjs/SortedMap.cjs +140 -0
- package/dist/cjs/SortedMap.cjs.map +1 -0
- package/dist/cjs/SortedMap.d.cts +91 -0
- package/dist/cjs/collection.cjs +597 -0
- package/dist/cjs/collection.cjs.map +1 -0
- package/dist/cjs/collection.d.cts +176 -0
- package/dist/cjs/deferred.cjs +25 -0
- package/dist/cjs/deferred.cjs.map +1 -0
- package/dist/cjs/deferred.d.cts +20 -0
- package/dist/cjs/errors.cjs +10 -0
- package/dist/cjs/errors.cjs.map +1 -0
- package/dist/cjs/errors.d.cts +3 -0
- package/dist/cjs/index.cjs +33 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +9 -0
- package/dist/cjs/proxy.cjs +654 -0
- package/dist/cjs/proxy.cjs.map +1 -0
- package/dist/cjs/proxy.d.cts +59 -0
- package/dist/cjs/query/compiled-query.cjs +162 -0
- package/dist/cjs/query/compiled-query.cjs.map +1 -0
- package/dist/cjs/query/compiled-query.d.cts +22 -0
- package/dist/cjs/query/evaluators.cjs +146 -0
- package/dist/cjs/query/evaluators.cjs.map +1 -0
- package/dist/cjs/query/evaluators.d.cts +9 -0
- package/dist/cjs/query/extractors.cjs +122 -0
- package/dist/cjs/query/extractors.cjs.map +1 -0
- package/dist/cjs/query/extractors.d.cts +22 -0
- package/dist/cjs/query/functions.cjs +152 -0
- package/dist/cjs/query/functions.cjs.map +1 -0
- package/dist/cjs/query/functions.d.cts +21 -0
- package/dist/cjs/query/group-by.cjs +91 -0
- package/dist/cjs/query/group-by.cjs.map +1 -0
- package/dist/cjs/query/group-by.d.cts +40 -0
- package/dist/cjs/query/index.d.cts +5 -0
- package/dist/cjs/query/joins.cjs +155 -0
- package/dist/cjs/query/joins.cjs.map +1 -0
- package/dist/cjs/query/joins.d.cts +14 -0
- package/dist/cjs/query/key-by.cjs +43 -0
- package/dist/cjs/query/key-by.cjs.map +1 -0
- package/dist/cjs/query/key-by.d.cts +3 -0
- package/dist/cjs/query/order-by.cjs +229 -0
- package/dist/cjs/query/order-by.cjs.map +1 -0
- package/dist/cjs/query/order-by.d.cts +3 -0
- package/dist/cjs/query/pipeline-compiler.cjs +94 -0
- package/dist/cjs/query/pipeline-compiler.cjs.map +1 -0
- package/dist/cjs/query/pipeline-compiler.d.cts +9 -0
- package/dist/cjs/query/query-builder.cjs +314 -0
- package/dist/cjs/query/query-builder.cjs.map +1 -0
- package/dist/cjs/query/query-builder.d.cts +219 -0
- package/dist/cjs/query/schema.d.cts +98 -0
- package/dist/cjs/query/select.cjs +107 -0
- package/dist/cjs/query/select.cjs.map +1 -0
- package/dist/cjs/query/select.d.cts +3 -0
- package/dist/cjs/query/types.d.cts +188 -0
- package/dist/cjs/query/utils.cjs +154 -0
- package/dist/cjs/query/utils.cjs.map +1 -0
- package/dist/cjs/query/utils.d.cts +37 -0
- package/dist/cjs/transactions.cjs +137 -0
- package/dist/cjs/transactions.cjs.map +1 -0
- package/dist/cjs/transactions.d.cts +27 -0
- package/dist/cjs/types.d.cts +94 -0
- package/dist/cjs/utils.cjs +17 -0
- package/dist/cjs/utils.cjs.map +1 -0
- package/dist/cjs/utils.d.cts +3 -0
- package/dist/esm/SortedMap.d.ts +91 -0
- package/dist/esm/SortedMap.js +140 -0
- package/dist/esm/SortedMap.js.map +1 -0
- package/dist/esm/collection.d.ts +176 -0
- package/dist/esm/collection.js +597 -0
- package/dist/esm/collection.js.map +1 -0
- package/dist/esm/deferred.d.ts +20 -0
- package/dist/esm/deferred.js +25 -0
- package/dist/esm/deferred.js.map +1 -0
- package/dist/esm/errors.d.ts +3 -0
- package/dist/esm/errors.js +10 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.d.ts +9 -0
- package/dist/esm/index.js +33 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/proxy.d.ts +59 -0
- package/dist/esm/proxy.js +654 -0
- package/dist/esm/proxy.js.map +1 -0
- package/dist/esm/query/compiled-query.d.ts +22 -0
- package/dist/esm/query/compiled-query.js +162 -0
- package/dist/esm/query/compiled-query.js.map +1 -0
- package/dist/esm/query/evaluators.d.ts +9 -0
- package/dist/esm/query/evaluators.js +146 -0
- package/dist/esm/query/evaluators.js.map +1 -0
- package/dist/esm/query/extractors.d.ts +22 -0
- package/dist/esm/query/extractors.js +122 -0
- package/dist/esm/query/extractors.js.map +1 -0
- package/dist/esm/query/functions.d.ts +21 -0
- package/dist/esm/query/functions.js +152 -0
- package/dist/esm/query/functions.js.map +1 -0
- package/dist/esm/query/group-by.d.ts +40 -0
- package/dist/esm/query/group-by.js +91 -0
- package/dist/esm/query/group-by.js.map +1 -0
- package/dist/esm/query/index.d.ts +5 -0
- package/dist/esm/query/joins.d.ts +14 -0
- package/dist/esm/query/joins.js +155 -0
- package/dist/esm/query/joins.js.map +1 -0
- package/dist/esm/query/key-by.d.ts +3 -0
- package/dist/esm/query/key-by.js +43 -0
- package/dist/esm/query/key-by.js.map +1 -0
- package/dist/esm/query/order-by.d.ts +3 -0
- package/dist/esm/query/order-by.js +229 -0
- package/dist/esm/query/order-by.js.map +1 -0
- package/dist/esm/query/pipeline-compiler.d.ts +9 -0
- package/dist/esm/query/pipeline-compiler.js +94 -0
- package/dist/esm/query/pipeline-compiler.js.map +1 -0
- package/dist/esm/query/query-builder.d.ts +219 -0
- package/dist/esm/query/query-builder.js +314 -0
- package/dist/esm/query/query-builder.js.map +1 -0
- package/dist/esm/query/schema.d.ts +98 -0
- package/dist/esm/query/select.d.ts +3 -0
- package/dist/esm/query/select.js +107 -0
- package/dist/esm/query/select.js.map +1 -0
- package/dist/esm/query/types.d.ts +188 -0
- package/dist/esm/query/utils.d.ts +37 -0
- package/dist/esm/query/utils.js +154 -0
- package/dist/esm/query/utils.js.map +1 -0
- package/dist/esm/transactions.d.ts +27 -0
- package/dist/esm/transactions.js +137 -0
- package/dist/esm/transactions.js.map +1 -0
- package/dist/esm/types.d.ts +94 -0
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.js +17 -0
- package/dist/esm/utils.js.map +1 -0
- package/package.json +57 -0
- package/src/SortedMap.ts +163 -0
- package/src/collection.ts +919 -0
- package/src/deferred.ts +47 -0
- package/src/errors.ts +6 -0
- package/src/index.ts +12 -0
- package/src/proxy.ts +1104 -0
- package/src/query/compiled-query.ts +193 -0
- package/src/query/evaluators.ts +222 -0
- package/src/query/extractors.ts +211 -0
- package/src/query/functions.ts +297 -0
- package/src/query/group-by.ts +137 -0
- package/src/query/index.ts +5 -0
- package/src/query/joins.ts +247 -0
- package/src/query/key-by.ts +61 -0
- package/src/query/order-by.ts +312 -0
- package/src/query/pipeline-compiler.ts +152 -0
- package/src/query/query-builder.ts +898 -0
- package/src/query/schema.ts +255 -0
- package/src/query/select.ts +173 -0
- package/src/query/types.ts +417 -0
- package/src/query/utils.ts +245 -0
- package/src/transactions.ts +198 -0
- package/src/types.ts +125 -0
- package/src/utils.ts +15 -0
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
import { Store, batch, Derived } from "@tanstack/store";
|
|
2
|
+
import { withArrayChangeTracking, withChangeTracking } from "./proxy.js";
|
|
3
|
+
import { getActiveTransaction } from "./transactions.js";
|
|
4
|
+
import { SortedMap } from "./SortedMap.js";
|
|
5
|
+
const collectionsStore = new Store(/* @__PURE__ */ new Map());
|
|
6
|
+
const loadingCollections = /* @__PURE__ */ new Map();
|
|
7
|
+
function preloadCollection(config) {
|
|
8
|
+
if (collectionsStore.state.has(config.id) && !loadingCollections.has(config.id)) {
|
|
9
|
+
return Promise.resolve(
|
|
10
|
+
collectionsStore.state.get(config.id)
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
if (loadingCollections.has(config.id)) {
|
|
14
|
+
return loadingCollections.get(config.id);
|
|
15
|
+
}
|
|
16
|
+
if (!collectionsStore.state.has(config.id)) {
|
|
17
|
+
collectionsStore.setState((prev) => {
|
|
18
|
+
const next = new Map(prev);
|
|
19
|
+
next.set(
|
|
20
|
+
config.id,
|
|
21
|
+
new Collection({
|
|
22
|
+
id: config.id,
|
|
23
|
+
sync: config.sync,
|
|
24
|
+
schema: config.schema
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
return next;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const collection = collectionsStore.state.get(config.id);
|
|
31
|
+
let resolveFirstCommit;
|
|
32
|
+
const firstCommitPromise = new Promise((resolve) => {
|
|
33
|
+
resolveFirstCommit = () => {
|
|
34
|
+
resolve(collection);
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
collection.onFirstCommit(() => {
|
|
38
|
+
if (loadingCollections.has(config.id)) {
|
|
39
|
+
loadingCollections.delete(config.id);
|
|
40
|
+
resolveFirstCommit();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
loadingCollections.set(
|
|
44
|
+
config.id,
|
|
45
|
+
firstCommitPromise
|
|
46
|
+
);
|
|
47
|
+
return firstCommitPromise;
|
|
48
|
+
}
|
|
49
|
+
class SchemaValidationError extends Error {
|
|
50
|
+
constructor(type, issues, message) {
|
|
51
|
+
const defaultMessage = `${type === `insert` ? `Insert` : `Update`} validation failed: ${issues.map((issue) => issue.message).join(`, `)}`;
|
|
52
|
+
super(message || defaultMessage);
|
|
53
|
+
this.name = `SchemaValidationError`;
|
|
54
|
+
this.type = type;
|
|
55
|
+
this.issues = issues;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
class Collection {
|
|
59
|
+
/**
|
|
60
|
+
* Creates a new Collection instance
|
|
61
|
+
*
|
|
62
|
+
* @param config - Configuration object for the collection
|
|
63
|
+
* @throws Error if sync config is missing
|
|
64
|
+
*/
|
|
65
|
+
constructor(config) {
|
|
66
|
+
this.syncedData = new Store(/* @__PURE__ */ new Map());
|
|
67
|
+
this.syncedMetadata = new Store(/* @__PURE__ */ new Map());
|
|
68
|
+
this.pendingSyncedTransactions = [];
|
|
69
|
+
this.syncedKeys = /* @__PURE__ */ new Set();
|
|
70
|
+
this.hasReceivedFirstCommit = false;
|
|
71
|
+
this.objectKeyMap = /* @__PURE__ */ new WeakMap();
|
|
72
|
+
this.onFirstCommitCallbacks = [];
|
|
73
|
+
this.id = crypto.randomUUID();
|
|
74
|
+
this.commitPendingTransactions = () => {
|
|
75
|
+
if (!Array.from(this.transactions.state.values()).some(
|
|
76
|
+
({ state }) => state === `persisting`
|
|
77
|
+
)) {
|
|
78
|
+
const keys = /* @__PURE__ */ new Set();
|
|
79
|
+
batch(() => {
|
|
80
|
+
for (const transaction of this.pendingSyncedTransactions) {
|
|
81
|
+
for (const operation of transaction.operations) {
|
|
82
|
+
keys.add(operation.key);
|
|
83
|
+
this.syncedKeys.add(operation.key);
|
|
84
|
+
this.syncedMetadata.setState((prevData) => {
|
|
85
|
+
switch (operation.type) {
|
|
86
|
+
case `insert`:
|
|
87
|
+
prevData.set(operation.key, operation.metadata);
|
|
88
|
+
break;
|
|
89
|
+
case `update`:
|
|
90
|
+
prevData.set(operation.key, {
|
|
91
|
+
...prevData.get(operation.key),
|
|
92
|
+
...operation.metadata
|
|
93
|
+
});
|
|
94
|
+
break;
|
|
95
|
+
case `delete`:
|
|
96
|
+
prevData.delete(operation.key);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
return prevData;
|
|
100
|
+
});
|
|
101
|
+
this.syncedData.setState((prevData) => {
|
|
102
|
+
switch (operation.type) {
|
|
103
|
+
case `insert`:
|
|
104
|
+
prevData.set(operation.key, operation.value);
|
|
105
|
+
break;
|
|
106
|
+
case `update`:
|
|
107
|
+
prevData.set(operation.key, {
|
|
108
|
+
...prevData.get(operation.key),
|
|
109
|
+
...operation.value
|
|
110
|
+
});
|
|
111
|
+
break;
|
|
112
|
+
case `delete`:
|
|
113
|
+
prevData.delete(operation.key);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
return prevData;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
keys.forEach((key) => {
|
|
122
|
+
const curValue = this.state.get(key);
|
|
123
|
+
if (curValue) {
|
|
124
|
+
this.objectKeyMap.set(curValue, key);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
this.pendingSyncedTransactions = [];
|
|
128
|
+
if (!this.hasReceivedFirstCommit) {
|
|
129
|
+
this.hasReceivedFirstCommit = true;
|
|
130
|
+
const callbacks = [...this.onFirstCommitCallbacks];
|
|
131
|
+
this.onFirstCommitCallbacks = [];
|
|
132
|
+
callbacks.forEach((callback) => callback());
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
this.insert = (data, config2) => {
|
|
137
|
+
const transaction = getActiveTransaction();
|
|
138
|
+
if (typeof transaction === `undefined`) {
|
|
139
|
+
throw `no transaction found when calling collection.insert`;
|
|
140
|
+
}
|
|
141
|
+
const items = Array.isArray(data) ? data : [data];
|
|
142
|
+
const mutations = [];
|
|
143
|
+
let keys;
|
|
144
|
+
if (config2 == null ? void 0 : config2.key) {
|
|
145
|
+
const configKeys = Array.isArray(config2.key) ? config2.key : [config2.key];
|
|
146
|
+
if (Array.isArray(config2.key) && configKeys.length > items.length) {
|
|
147
|
+
throw new Error(`More keys provided than items to insert`);
|
|
148
|
+
}
|
|
149
|
+
keys = items.map((_, i) => configKeys[i] ?? this.generateKey(items[i]));
|
|
150
|
+
} else {
|
|
151
|
+
keys = items.map((item) => this.generateKey(item));
|
|
152
|
+
}
|
|
153
|
+
items.forEach((item, index) => {
|
|
154
|
+
var _a, _b;
|
|
155
|
+
const validatedData = this.validateData(item, `insert`);
|
|
156
|
+
const key = keys[index];
|
|
157
|
+
const mutation = {
|
|
158
|
+
mutationId: crypto.randomUUID(),
|
|
159
|
+
original: {},
|
|
160
|
+
modified: validatedData,
|
|
161
|
+
changes: validatedData,
|
|
162
|
+
key,
|
|
163
|
+
metadata: config2 == null ? void 0 : config2.metadata,
|
|
164
|
+
syncMetadata: ((_b = (_a = this.config.sync).getSyncMetadata) == null ? void 0 : _b.call(_a)) || {},
|
|
165
|
+
type: `insert`,
|
|
166
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
167
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
168
|
+
collection: this
|
|
169
|
+
};
|
|
170
|
+
mutations.push(mutation);
|
|
171
|
+
});
|
|
172
|
+
transaction.applyMutations(mutations);
|
|
173
|
+
this.transactions.setState((sortedMap) => {
|
|
174
|
+
sortedMap.set(transaction.id, transaction);
|
|
175
|
+
return sortedMap;
|
|
176
|
+
});
|
|
177
|
+
return transaction;
|
|
178
|
+
};
|
|
179
|
+
this.delete = (items, config2) => {
|
|
180
|
+
const transaction = getActiveTransaction();
|
|
181
|
+
if (typeof transaction === `undefined`) {
|
|
182
|
+
throw `no transaction found when calling collection.delete`;
|
|
183
|
+
}
|
|
184
|
+
const itemsArray = Array.isArray(items) ? items : [items];
|
|
185
|
+
const mutations = [];
|
|
186
|
+
for (const item of itemsArray) {
|
|
187
|
+
let key;
|
|
188
|
+
if (typeof item === `object` && item !== null) {
|
|
189
|
+
const objectKey = this.objectKeyMap.get(item);
|
|
190
|
+
if (objectKey === void 0) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Object not found in collection: ${JSON.stringify(item)}`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
key = objectKey;
|
|
196
|
+
} else if (typeof item === `string`) {
|
|
197
|
+
key = item;
|
|
198
|
+
} else {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`Invalid item type for delete - must be an object or string key`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
const mutation = {
|
|
204
|
+
mutationId: crypto.randomUUID(),
|
|
205
|
+
original: this.state.get(key) || {},
|
|
206
|
+
modified: { _deleted: true },
|
|
207
|
+
changes: { _deleted: true },
|
|
208
|
+
key,
|
|
209
|
+
metadata: config2 == null ? void 0 : config2.metadata,
|
|
210
|
+
syncMetadata: this.syncedMetadata.state.get(key) || {},
|
|
211
|
+
type: `delete`,
|
|
212
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
213
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
214
|
+
collection: this
|
|
215
|
+
};
|
|
216
|
+
mutations.push(mutation);
|
|
217
|
+
}
|
|
218
|
+
mutations.forEach((mutation) => {
|
|
219
|
+
const curValue = this.state.get(mutation.key);
|
|
220
|
+
if (curValue) {
|
|
221
|
+
this.objectKeyMap.delete(curValue);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
transaction.applyMutations(mutations);
|
|
225
|
+
this.transactions.setState((sortedMap) => {
|
|
226
|
+
sortedMap.set(transaction.id, transaction);
|
|
227
|
+
return sortedMap;
|
|
228
|
+
});
|
|
229
|
+
return transaction;
|
|
230
|
+
};
|
|
231
|
+
if (!(config == null ? void 0 : config.sync)) {
|
|
232
|
+
throw new Error(`Collection requires a sync config`);
|
|
233
|
+
}
|
|
234
|
+
this.transactions = new Store(
|
|
235
|
+
new SortedMap(
|
|
236
|
+
(a, b) => a.createdAt.getTime() - b.createdAt.getTime()
|
|
237
|
+
)
|
|
238
|
+
);
|
|
239
|
+
this.optimisticOperations = new Derived({
|
|
240
|
+
fn: ({ currDepVals: [transactions] }) => {
|
|
241
|
+
const result = Array.from(transactions.values()).map((transaction) => {
|
|
242
|
+
const isActive = ![`completed`, `failed`].includes(
|
|
243
|
+
transaction.state
|
|
244
|
+
);
|
|
245
|
+
return transaction.mutations.map((mutation) => {
|
|
246
|
+
const message = {
|
|
247
|
+
type: mutation.type,
|
|
248
|
+
key: mutation.key,
|
|
249
|
+
value: mutation.modified,
|
|
250
|
+
isActive
|
|
251
|
+
};
|
|
252
|
+
if (mutation.metadata !== void 0 && mutation.metadata !== null) {
|
|
253
|
+
message.metadata = mutation.metadata;
|
|
254
|
+
}
|
|
255
|
+
return message;
|
|
256
|
+
});
|
|
257
|
+
}).flat();
|
|
258
|
+
return result;
|
|
259
|
+
},
|
|
260
|
+
deps: [this.transactions]
|
|
261
|
+
});
|
|
262
|
+
this.optimisticOperations.mount();
|
|
263
|
+
this.derivedState = new Derived({
|
|
264
|
+
fn: ({ currDepVals: [syncedData, operations] }) => {
|
|
265
|
+
const combined = new Map(syncedData);
|
|
266
|
+
const optimisticKeys = /* @__PURE__ */ new Set();
|
|
267
|
+
for (const operation of operations) {
|
|
268
|
+
optimisticKeys.add(operation.key);
|
|
269
|
+
if (operation.isActive) {
|
|
270
|
+
switch (operation.type) {
|
|
271
|
+
case `insert`:
|
|
272
|
+
combined.set(operation.key, operation.value);
|
|
273
|
+
break;
|
|
274
|
+
case `update`:
|
|
275
|
+
combined.set(operation.key, operation.value);
|
|
276
|
+
break;
|
|
277
|
+
case `delete`:
|
|
278
|
+
combined.delete(operation.key);
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
optimisticKeys.forEach((key) => {
|
|
284
|
+
if (combined.has(key)) {
|
|
285
|
+
this.objectKeyMap.set(combined.get(key), key);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
return combined;
|
|
289
|
+
},
|
|
290
|
+
deps: [this.syncedData, this.optimisticOperations]
|
|
291
|
+
});
|
|
292
|
+
this.derivedArray = new Derived({
|
|
293
|
+
fn: ({ currDepVals: [stateMap] }) => {
|
|
294
|
+
const array = Array.from(
|
|
295
|
+
stateMap.values()
|
|
296
|
+
);
|
|
297
|
+
if (array[0] && `_orderByIndex` in array[0]) {
|
|
298
|
+
array.sort((a, b) => {
|
|
299
|
+
if (a._orderByIndex === b._orderByIndex) {
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
return a._orderByIndex < b._orderByIndex ? -1 : 1;
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
return array;
|
|
306
|
+
},
|
|
307
|
+
deps: [this.derivedState]
|
|
308
|
+
});
|
|
309
|
+
this.derivedArray.mount();
|
|
310
|
+
this.derivedChanges = new Derived({
|
|
311
|
+
fn: ({
|
|
312
|
+
currDepVals: [derivedState, optimisticOperations],
|
|
313
|
+
prevDepVals
|
|
314
|
+
}) => {
|
|
315
|
+
const prevDerivedState = (prevDepVals == null ? void 0 : prevDepVals[0]) ?? /* @__PURE__ */ new Map();
|
|
316
|
+
const changedKeys = new Set(this.syncedKeys);
|
|
317
|
+
optimisticOperations.flat().filter((op) => op.isActive).forEach((op) => changedKeys.add(op.key));
|
|
318
|
+
if (changedKeys.size === 0) {
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
const changes = [];
|
|
322
|
+
for (const key of changedKeys) {
|
|
323
|
+
if (prevDerivedState.has(key) && !derivedState.has(key)) {
|
|
324
|
+
changes.push({
|
|
325
|
+
type: `delete`,
|
|
326
|
+
key,
|
|
327
|
+
value: prevDerivedState.get(key)
|
|
328
|
+
});
|
|
329
|
+
} else if (!prevDerivedState.has(key) && derivedState.has(key)) {
|
|
330
|
+
changes.push({ type: `insert`, key, value: derivedState.get(key) });
|
|
331
|
+
} else if (prevDerivedState.has(key) && derivedState.has(key)) {
|
|
332
|
+
changes.push({
|
|
333
|
+
type: `update`,
|
|
334
|
+
key,
|
|
335
|
+
value: derivedState.get(key),
|
|
336
|
+
previousValue: prevDerivedState.get(key)
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
this.syncedKeys.clear();
|
|
341
|
+
return changes;
|
|
342
|
+
},
|
|
343
|
+
deps: [this.derivedState, this.optimisticOperations]
|
|
344
|
+
});
|
|
345
|
+
this.derivedChanges.mount();
|
|
346
|
+
this.config = config;
|
|
347
|
+
this.derivedState.mount();
|
|
348
|
+
config.sync.sync({
|
|
349
|
+
collection: this,
|
|
350
|
+
begin: () => {
|
|
351
|
+
this.pendingSyncedTransactions.push({
|
|
352
|
+
committed: false,
|
|
353
|
+
operations: []
|
|
354
|
+
});
|
|
355
|
+
},
|
|
356
|
+
write: (message) => {
|
|
357
|
+
const pendingTransaction = this.pendingSyncedTransactions[this.pendingSyncedTransactions.length - 1];
|
|
358
|
+
if (!pendingTransaction) {
|
|
359
|
+
throw new Error(`No pending sync transaction to write to`);
|
|
360
|
+
}
|
|
361
|
+
if (pendingTransaction.committed) {
|
|
362
|
+
throw new Error(
|
|
363
|
+
`The pending sync transaction is already committed, you can't still write to it.`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
pendingTransaction.operations.push(message);
|
|
367
|
+
},
|
|
368
|
+
commit: () => {
|
|
369
|
+
const pendingTransaction = this.pendingSyncedTransactions[this.pendingSyncedTransactions.length - 1];
|
|
370
|
+
if (!pendingTransaction) {
|
|
371
|
+
throw new Error(`No pending sync transaction to commit`);
|
|
372
|
+
}
|
|
373
|
+
if (pendingTransaction.committed) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
`The pending sync transaction is already committed, you can't commit it again.`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
pendingTransaction.committed = true;
|
|
379
|
+
this.commitPendingTransactions();
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Register a callback to be executed on the next commit
|
|
385
|
+
* Useful for preloading collections
|
|
386
|
+
* @param callback Function to call after the next commit
|
|
387
|
+
*/
|
|
388
|
+
onFirstCommit(callback) {
|
|
389
|
+
this.onFirstCommitCallbacks.push(callback);
|
|
390
|
+
}
|
|
391
|
+
ensureStandardSchema(schema) {
|
|
392
|
+
if (schema && typeof schema === `object` && `~standard` in schema) {
|
|
393
|
+
return schema;
|
|
394
|
+
}
|
|
395
|
+
throw new Error(
|
|
396
|
+
`Schema must either implement the standard-schema interface or be a Zod schema`
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
validateData(data, type, key) {
|
|
400
|
+
if (!this.config.schema) return data;
|
|
401
|
+
const standardSchema = this.ensureStandardSchema(this.config.schema);
|
|
402
|
+
if (type === `update` && key) {
|
|
403
|
+
const existingData = this.state.get(key);
|
|
404
|
+
if (existingData && data && typeof data === `object` && typeof existingData === `object`) {
|
|
405
|
+
const mergedData = { ...existingData, ...data };
|
|
406
|
+
const result2 = standardSchema[`~standard`].validate(mergedData);
|
|
407
|
+
if (result2 instanceof Promise) {
|
|
408
|
+
throw new TypeError(`Schema validation must be synchronous`);
|
|
409
|
+
}
|
|
410
|
+
if (`issues` in result2 && result2.issues) {
|
|
411
|
+
const typedIssues = result2.issues.map((issue) => {
|
|
412
|
+
var _a;
|
|
413
|
+
return {
|
|
414
|
+
message: issue.message,
|
|
415
|
+
path: (_a = issue.path) == null ? void 0 : _a.map((p) => String(p))
|
|
416
|
+
};
|
|
417
|
+
});
|
|
418
|
+
throw new SchemaValidationError(type, typedIssues);
|
|
419
|
+
}
|
|
420
|
+
return data;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const result = standardSchema[`~standard`].validate(data);
|
|
424
|
+
if (result instanceof Promise) {
|
|
425
|
+
throw new TypeError(`Schema validation must be synchronous`);
|
|
426
|
+
}
|
|
427
|
+
if (`issues` in result && result.issues) {
|
|
428
|
+
const typedIssues = result.issues.map((issue) => {
|
|
429
|
+
var _a;
|
|
430
|
+
return {
|
|
431
|
+
message: issue.message,
|
|
432
|
+
path: (_a = issue.path) == null ? void 0 : _a.map((p) => String(p))
|
|
433
|
+
};
|
|
434
|
+
});
|
|
435
|
+
throw new SchemaValidationError(type, typedIssues);
|
|
436
|
+
}
|
|
437
|
+
return result.value;
|
|
438
|
+
}
|
|
439
|
+
generateKey(data) {
|
|
440
|
+
const str = JSON.stringify(data);
|
|
441
|
+
let h = 0;
|
|
442
|
+
for (let i = 0; i < str.length; i++) {
|
|
443
|
+
h = Math.imul(31, h) + str.charCodeAt(i) | 0;
|
|
444
|
+
}
|
|
445
|
+
return `${this.id}/${Math.abs(h).toString(36)}`;
|
|
446
|
+
}
|
|
447
|
+
update(items, configOrCallback, maybeCallback) {
|
|
448
|
+
if (typeof items === `undefined`) {
|
|
449
|
+
throw new Error(`The first argument to update is missing`);
|
|
450
|
+
}
|
|
451
|
+
const transaction = getActiveTransaction();
|
|
452
|
+
if (typeof transaction === `undefined`) {
|
|
453
|
+
throw `no transaction found when calling collection.update`;
|
|
454
|
+
}
|
|
455
|
+
const isArray = Array.isArray(items);
|
|
456
|
+
const itemsArray = Array.isArray(items) ? items : [items];
|
|
457
|
+
const callback = typeof configOrCallback === `function` ? configOrCallback : maybeCallback;
|
|
458
|
+
const config = typeof configOrCallback === `function` ? {} : configOrCallback;
|
|
459
|
+
const keys = itemsArray.map((item) => {
|
|
460
|
+
if (typeof item === `object` && item !== null) {
|
|
461
|
+
const key = this.objectKeyMap.get(item);
|
|
462
|
+
if (key === void 0) {
|
|
463
|
+
throw new Error(`Object not found in collection`);
|
|
464
|
+
}
|
|
465
|
+
return key;
|
|
466
|
+
}
|
|
467
|
+
throw new Error(`Invalid item type for update - must be an object`);
|
|
468
|
+
});
|
|
469
|
+
const currentObjects = keys.map((key) => ({
|
|
470
|
+
...this.state.get(key) || {}
|
|
471
|
+
}));
|
|
472
|
+
let changesArray;
|
|
473
|
+
if (isArray) {
|
|
474
|
+
changesArray = withArrayChangeTracking(
|
|
475
|
+
currentObjects,
|
|
476
|
+
callback
|
|
477
|
+
);
|
|
478
|
+
} else {
|
|
479
|
+
const result = withChangeTracking(
|
|
480
|
+
currentObjects[0],
|
|
481
|
+
callback
|
|
482
|
+
);
|
|
483
|
+
changesArray = [result];
|
|
484
|
+
}
|
|
485
|
+
const mutations = keys.map((key, index) => {
|
|
486
|
+
const changes = changesArray[index];
|
|
487
|
+
if (!changes || Object.keys(changes).length === 0) {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
const validatedData = this.validateData(changes, `update`, key);
|
|
491
|
+
return {
|
|
492
|
+
mutationId: crypto.randomUUID(),
|
|
493
|
+
original: this.state.get(key) || {},
|
|
494
|
+
modified: {
|
|
495
|
+
...this.state.get(key) || {},
|
|
496
|
+
...validatedData
|
|
497
|
+
},
|
|
498
|
+
changes: validatedData,
|
|
499
|
+
key,
|
|
500
|
+
metadata: config.metadata,
|
|
501
|
+
syncMetadata: this.syncedMetadata.state.get(key) || {},
|
|
502
|
+
type: `update`,
|
|
503
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
504
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
505
|
+
collection: this
|
|
506
|
+
};
|
|
507
|
+
}).filter(Boolean);
|
|
508
|
+
if (mutations.length === 0) {
|
|
509
|
+
throw new Error(`No changes were made to any of the objects`);
|
|
510
|
+
}
|
|
511
|
+
transaction.applyMutations(mutations);
|
|
512
|
+
this.transactions.setState((sortedMap) => {
|
|
513
|
+
sortedMap.set(transaction.id, transaction);
|
|
514
|
+
return sortedMap;
|
|
515
|
+
});
|
|
516
|
+
return transaction;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Gets the current state of the collection as a Map
|
|
520
|
+
*
|
|
521
|
+
* @returns A Map containing all items in the collection, with keys as identifiers
|
|
522
|
+
*/
|
|
523
|
+
get state() {
|
|
524
|
+
return this.derivedState.state;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Gets the current state of the collection as a Map, but only resolves when data is available
|
|
528
|
+
* Waits for the first sync commit to complete before resolving
|
|
529
|
+
*
|
|
530
|
+
* @returns Promise that resolves to a Map containing all items in the collection
|
|
531
|
+
*/
|
|
532
|
+
stateWhenReady() {
|
|
533
|
+
if (this.state.size > 0 || this.hasReceivedFirstCommit === true) {
|
|
534
|
+
return Promise.resolve(this.state);
|
|
535
|
+
}
|
|
536
|
+
return new Promise((resolve) => {
|
|
537
|
+
this.onFirstCommit(() => {
|
|
538
|
+
resolve(this.state);
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Gets the current state of the collection as an Array
|
|
544
|
+
*
|
|
545
|
+
* @returns An Array containing all items in the collection
|
|
546
|
+
*/
|
|
547
|
+
get toArray() {
|
|
548
|
+
return this.derivedArray.state;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Gets the current state of the collection as an Array, but only resolves when data is available
|
|
552
|
+
* Waits for the first sync commit to complete before resolving
|
|
553
|
+
*
|
|
554
|
+
* @returns Promise that resolves to an Array containing all items in the collection
|
|
555
|
+
*/
|
|
556
|
+
toArrayWhenReady() {
|
|
557
|
+
if (this.toArray.length > 0 || this.hasReceivedFirstCommit === true) {
|
|
558
|
+
return Promise.resolve(this.toArray);
|
|
559
|
+
}
|
|
560
|
+
return new Promise((resolve) => {
|
|
561
|
+
this.onFirstCommit(() => {
|
|
562
|
+
resolve(this.toArray);
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Returns the current state of the collection as an array of changes
|
|
568
|
+
* @returns An array of changes
|
|
569
|
+
*/
|
|
570
|
+
currentStateAsChanges() {
|
|
571
|
+
return [...this.state.entries()].map(([key, value]) => ({
|
|
572
|
+
type: `insert`,
|
|
573
|
+
key,
|
|
574
|
+
value
|
|
575
|
+
}));
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Subscribe to changes in the collection
|
|
579
|
+
* @param callback - A function that will be called with the changes in the collection
|
|
580
|
+
* @returns A function that can be called to unsubscribe from the changes
|
|
581
|
+
*/
|
|
582
|
+
subscribeChanges(callback) {
|
|
583
|
+
callback(this.currentStateAsChanges());
|
|
584
|
+
return this.derivedChanges.subscribe((changes) => {
|
|
585
|
+
if (changes.currentVal.length > 0) {
|
|
586
|
+
callback(changes.currentVal);
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
export {
|
|
592
|
+
Collection,
|
|
593
|
+
SchemaValidationError,
|
|
594
|
+
collectionsStore,
|
|
595
|
+
preloadCollection
|
|
596
|
+
};
|
|
597
|
+
//# sourceMappingURL=collection.js.map
|