@tanstack/db 0.0.4 → 0.0.6
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/collection.cjs +182 -113
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +43 -15
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/proxy.cjs +87 -248
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/proxy.d.cts +5 -5
- package/dist/cjs/query/compiled-query.cjs +23 -14
- package/dist/cjs/query/compiled-query.cjs.map +1 -1
- package/dist/cjs/query/compiled-query.d.cts +3 -1
- package/dist/cjs/query/evaluators.cjs +35 -20
- package/dist/cjs/query/evaluators.cjs.map +1 -1
- package/dist/cjs/query/evaluators.d.cts +8 -3
- package/dist/cjs/query/extractors.cjs +20 -20
- package/dist/cjs/query/extractors.cjs.map +1 -1
- package/dist/cjs/query/extractors.d.cts +3 -3
- package/dist/cjs/query/group-by.cjs +12 -15
- package/dist/cjs/query/group-by.cjs.map +1 -1
- package/dist/cjs/query/group-by.d.cts +7 -7
- package/dist/cjs/query/joins.cjs +41 -55
- package/dist/cjs/query/joins.cjs.map +1 -1
- package/dist/cjs/query/joins.d.cts +3 -3
- package/dist/cjs/query/order-by.cjs +37 -84
- package/dist/cjs/query/order-by.cjs.map +1 -1
- package/dist/cjs/query/order-by.d.cts +2 -2
- package/dist/cjs/query/pipeline-compiler.cjs +13 -18
- package/dist/cjs/query/pipeline-compiler.cjs.map +1 -1
- package/dist/cjs/query/pipeline-compiler.d.cts +2 -1
- package/dist/cjs/query/query-builder.cjs +22 -29
- package/dist/cjs/query/query-builder.cjs.map +1 -1
- package/dist/cjs/query/query-builder.d.cts +16 -10
- package/dist/cjs/query/schema.d.cts +12 -11
- package/dist/cjs/query/select.cjs +47 -24
- package/dist/cjs/query/select.cjs.map +1 -1
- package/dist/cjs/query/select.d.cts +2 -2
- package/dist/cjs/query/types.d.cts +1 -0
- package/dist/cjs/transactions.cjs +20 -9
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +66 -7
- package/dist/esm/collection.d.ts +43 -15
- package/dist/esm/collection.js +183 -114
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/proxy.d.ts +5 -5
- package/dist/esm/proxy.js +87 -248
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/compiled-query.d.ts +3 -1
- package/dist/esm/query/compiled-query.js +23 -14
- package/dist/esm/query/compiled-query.js.map +1 -1
- package/dist/esm/query/evaluators.d.ts +8 -3
- package/dist/esm/query/evaluators.js +36 -21
- package/dist/esm/query/evaluators.js.map +1 -1
- package/dist/esm/query/extractors.d.ts +3 -3
- package/dist/esm/query/extractors.js +20 -20
- package/dist/esm/query/extractors.js.map +1 -1
- package/dist/esm/query/group-by.d.ts +7 -7
- package/dist/esm/query/group-by.js +14 -17
- package/dist/esm/query/group-by.js.map +1 -1
- package/dist/esm/query/joins.d.ts +3 -3
- package/dist/esm/query/joins.js +42 -56
- package/dist/esm/query/joins.js.map +1 -1
- package/dist/esm/query/order-by.d.ts +2 -2
- package/dist/esm/query/order-by.js +39 -86
- package/dist/esm/query/order-by.js.map +1 -1
- package/dist/esm/query/pipeline-compiler.d.ts +2 -1
- package/dist/esm/query/pipeline-compiler.js +14 -19
- package/dist/esm/query/pipeline-compiler.js.map +1 -1
- package/dist/esm/query/query-builder.d.ts +16 -10
- package/dist/esm/query/query-builder.js +22 -29
- package/dist/esm/query/query-builder.js.map +1 -1
- package/dist/esm/query/schema.d.ts +12 -11
- package/dist/esm/query/select.d.ts +2 -2
- package/dist/esm/query/select.js +48 -25
- package/dist/esm/query/select.js.map +1 -1
- package/dist/esm/query/types.d.ts +1 -0
- package/dist/esm/transactions.js +20 -9
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +66 -7
- package/package.json +2 -2
- package/src/collection.ts +286 -146
- package/src/proxy.ts +141 -358
- package/src/query/compiled-query.ts +30 -15
- package/src/query/evaluators.ts +49 -21
- package/src/query/extractors.ts +24 -21
- package/src/query/group-by.ts +24 -22
- package/src/query/joins.ts +88 -75
- package/src/query/order-by.ts +56 -106
- package/src/query/pipeline-compiler.ts +34 -37
- package/src/query/query-builder.ts +49 -46
- package/src/query/schema.ts +18 -15
- package/src/query/select.ts +68 -33
- package/src/query/types.ts +1 -0
- package/src/transactions.ts +30 -14
- package/src/types.ts +76 -7
- package/dist/cjs/query/key-by.cjs +0 -43
- package/dist/cjs/query/key-by.cjs.map +0 -1
- package/dist/cjs/query/key-by.d.cts +0 -3
- package/dist/esm/query/key-by.d.ts +0 -3
- package/dist/esm/query/key-by.js +0 -43
- package/dist/esm/query/key-by.js.map +0 -1
- package/src/query/key-by.ts +0 -61
package/dist/esm/collection.d.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { Derived, Store } from '@tanstack/store';
|
|
2
|
+
import { Transaction } from './transactions.js';
|
|
2
3
|
import { SortedMap } from './SortedMap.js';
|
|
3
|
-
import { ChangeMessage, CollectionConfig, InsertConfig, OperationConfig, OptimisticChangeMessage, Transaction } from './types.js';
|
|
4
|
+
import { ChangeMessage, CollectionConfig, InsertConfig, OperationConfig, OptimisticChangeMessage, Transaction as TransactionType } from './types.js';
|
|
4
5
|
export declare const collectionsStore: Store<Map<string, Collection<any>>, (cb: Map<string, Collection<any>>) => Map<string, Collection<any>>>;
|
|
6
|
+
/**
|
|
7
|
+
* Creates a new Collection instance with the given configuration
|
|
8
|
+
*
|
|
9
|
+
* @template T - The type of items in the collection
|
|
10
|
+
* @param config - Configuration for the collection, including id and sync
|
|
11
|
+
* @returns A new Collection instance
|
|
12
|
+
*/
|
|
13
|
+
export declare function createCollection<T extends object = Record<string, unknown>>(config: CollectionConfig<T>): Collection<T>;
|
|
5
14
|
/**
|
|
6
15
|
* Preloads a collection with the given configuration
|
|
7
16
|
* Returns a promise that resolves once the sync tool has done its first commit (initial sync is finished)
|
|
@@ -44,7 +53,7 @@ export declare class SchemaValidationError extends Error {
|
|
|
44
53
|
}>, message?: string);
|
|
45
54
|
}
|
|
46
55
|
export declare class Collection<T extends object = Record<string, unknown>> {
|
|
47
|
-
transactions: Store<SortedMap<string,
|
|
56
|
+
transactions: Store<SortedMap<string, TransactionType>>;
|
|
48
57
|
optimisticOperations: Derived<Array<OptimisticChangeMessage<T>>>;
|
|
49
58
|
derivedState: Derived<Map<string, T>>;
|
|
50
59
|
derivedArray: Derived<Array<T>>;
|
|
@@ -55,7 +64,6 @@ export declare class Collection<T extends object = Record<string, unknown>> {
|
|
|
55
64
|
private syncedKeys;
|
|
56
65
|
config: CollectionConfig<T>;
|
|
57
66
|
private hasReceivedFirstCommit;
|
|
58
|
-
objectKeyMap: WeakMap<object, string>;
|
|
59
67
|
private onFirstCommitCallbacks;
|
|
60
68
|
/**
|
|
61
69
|
* Register a callback to be executed on the next commit
|
|
@@ -63,27 +71,28 @@ export declare class Collection<T extends object = Record<string, unknown>> {
|
|
|
63
71
|
* @param callback Function to call after the next commit
|
|
64
72
|
*/
|
|
65
73
|
onFirstCommit(callback: () => void): void;
|
|
66
|
-
id:
|
|
74
|
+
id: string;
|
|
67
75
|
/**
|
|
68
76
|
* Creates a new Collection instance
|
|
69
77
|
*
|
|
70
78
|
* @param config - Configuration object for the collection
|
|
71
79
|
* @throws Error if sync config is missing
|
|
72
80
|
*/
|
|
73
|
-
constructor(config
|
|
81
|
+
constructor(config: CollectionConfig<T>);
|
|
74
82
|
/**
|
|
75
83
|
* Attempts to commit pending synced transactions if there are no active transactions
|
|
76
84
|
* This method processes operations from pending transactions and applies them to the synced data
|
|
77
85
|
*/
|
|
78
86
|
commitPendingTransactions: () => void;
|
|
79
87
|
private ensureStandardSchema;
|
|
88
|
+
private getKeyFromId;
|
|
89
|
+
generateObjectKey(id: any, item: any): string;
|
|
80
90
|
private validateData;
|
|
81
|
-
private generateKey;
|
|
82
91
|
/**
|
|
83
92
|
* Inserts one or more items into the collection
|
|
84
93
|
* @param items - Single item or array of items to insert
|
|
85
94
|
* @param config - Optional configuration including metadata and custom keys
|
|
86
|
-
* @returns A
|
|
95
|
+
* @returns A TransactionType object representing the insert operation(s)
|
|
87
96
|
* @throws {SchemaValidationError} If the data fails schema validation
|
|
88
97
|
* @example
|
|
89
98
|
* // Insert a single item
|
|
@@ -118,24 +127,43 @@ export declare class Collection<T extends object = Record<string, unknown>> {
|
|
|
118
127
|
* // Update with metadata
|
|
119
128
|
* update(todo, { metadata: { reason: "user update" } }, (draft) => { draft.text = "Updated text" })
|
|
120
129
|
*/
|
|
121
|
-
|
|
122
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Updates one or more items in the collection using a callback function
|
|
132
|
+
* @param ids - Single ID or array of IDs to update
|
|
133
|
+
* @param configOrCallback - Either update configuration or update callback
|
|
134
|
+
* @param maybeCallback - Update callback if config was provided
|
|
135
|
+
* @returns A Transaction object representing the update operation(s)
|
|
136
|
+
* @throws {SchemaValidationError} If the updated data fails schema validation
|
|
137
|
+
* @example
|
|
138
|
+
* // Update a single item
|
|
139
|
+
* update("todo-1", (draft) => { draft.completed = true })
|
|
140
|
+
*
|
|
141
|
+
* // Update multiple items
|
|
142
|
+
* update(["todo-1", "todo-2"], (drafts) => {
|
|
143
|
+
* drafts.forEach(draft => { draft.completed = true })
|
|
144
|
+
* })
|
|
145
|
+
*
|
|
146
|
+
* // Update with metadata
|
|
147
|
+
* update("todo-1", { metadata: { reason: "user update" } }, (draft) => { draft.text = "Updated text" })
|
|
148
|
+
*/
|
|
149
|
+
update<TItem extends object = T>(id: unknown, configOrCallback: ((draft: TItem) => void) | OperationConfig, maybeCallback?: (draft: TItem) => void): TransactionType;
|
|
150
|
+
update<TItem extends object = T>(ids: Array<unknown>, configOrCallback: ((draft: Array<TItem>) => void) | OperationConfig, maybeCallback?: (draft: Array<TItem>) => void): TransactionType;
|
|
123
151
|
/**
|
|
124
152
|
* Deletes one or more items from the collection
|
|
125
|
-
* @param
|
|
153
|
+
* @param ids - Single ID or array of IDs to delete
|
|
126
154
|
* @param config - Optional configuration including metadata
|
|
127
|
-
* @returns A
|
|
155
|
+
* @returns A TransactionType object representing the delete operation(s)
|
|
128
156
|
* @example
|
|
129
157
|
* // Delete a single item
|
|
130
|
-
* delete(todo)
|
|
158
|
+
* delete("todo-1")
|
|
131
159
|
*
|
|
132
160
|
* // Delete multiple items
|
|
133
|
-
* delete([
|
|
161
|
+
* delete(["todo-1", "todo-2"])
|
|
134
162
|
*
|
|
135
163
|
* // Delete with metadata
|
|
136
|
-
* delete(todo, { metadata: { reason: "completed" } })
|
|
164
|
+
* delete("todo-1", { metadata: { reason: "completed" } })
|
|
137
165
|
*/
|
|
138
|
-
delete: (
|
|
166
|
+
delete: (ids: Array<string> | string, config?: OperationConfig) => TransactionType;
|
|
139
167
|
/**
|
|
140
168
|
* Gets the current state of the collection as a Map
|
|
141
169
|
*
|
package/dist/esm/collection.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { Store, batch, Derived } from "@tanstack/store";
|
|
2
2
|
import { withArrayChangeTracking, withChangeTracking } from "./proxy.js";
|
|
3
|
-
import { getActiveTransaction } from "./transactions.js";
|
|
3
|
+
import { getActiveTransaction, Transaction } from "./transactions.js";
|
|
4
4
|
import { SortedMap } from "./SortedMap.js";
|
|
5
5
|
const collectionsStore = new Store(/* @__PURE__ */ new Map());
|
|
6
6
|
const loadingCollections = /* @__PURE__ */ new Map();
|
|
7
|
+
function createCollection(config) {
|
|
8
|
+
return new Collection(config);
|
|
9
|
+
}
|
|
7
10
|
function preloadCollection(config) {
|
|
11
|
+
if (!config.id) {
|
|
12
|
+
throw new Error(`The id property is required for preloadCollection`);
|
|
13
|
+
}
|
|
8
14
|
if (collectionsStore.state.has(config.id) && !loadingCollections.has(config.id)) {
|
|
9
15
|
return Promise.resolve(
|
|
10
16
|
collectionsStore.state.get(config.id)
|
|
@@ -16,10 +22,14 @@ function preloadCollection(config) {
|
|
|
16
22
|
if (!collectionsStore.state.has(config.id)) {
|
|
17
23
|
collectionsStore.setState((prev) => {
|
|
18
24
|
const next = new Map(prev);
|
|
25
|
+
if (!config.id) {
|
|
26
|
+
throw new Error(`The id property is required for preloadCollection`);
|
|
27
|
+
}
|
|
19
28
|
next.set(
|
|
20
29
|
config.id,
|
|
21
30
|
new Collection({
|
|
22
31
|
id: config.id,
|
|
32
|
+
getId: config.getId,
|
|
23
33
|
sync: config.sync,
|
|
24
34
|
schema: config.schema
|
|
25
35
|
})
|
|
@@ -35,6 +45,9 @@ function preloadCollection(config) {
|
|
|
35
45
|
};
|
|
36
46
|
});
|
|
37
47
|
collection.onFirstCommit(() => {
|
|
48
|
+
if (!config.id) {
|
|
49
|
+
throw new Error(`The id property is required for preloadCollection`);
|
|
50
|
+
}
|
|
38
51
|
if (loadingCollections.has(config.id)) {
|
|
39
52
|
loadingCollections.delete(config.id);
|
|
40
53
|
resolveFirstCommit();
|
|
@@ -68,18 +81,15 @@ class Collection {
|
|
|
68
81
|
this.pendingSyncedTransactions = [];
|
|
69
82
|
this.syncedKeys = /* @__PURE__ */ new Set();
|
|
70
83
|
this.hasReceivedFirstCommit = false;
|
|
71
|
-
this.objectKeyMap = /* @__PURE__ */ new WeakMap();
|
|
72
84
|
this.onFirstCommitCallbacks = [];
|
|
73
|
-
this.id =
|
|
85
|
+
this.id = ``;
|
|
74
86
|
this.commitPendingTransactions = () => {
|
|
75
87
|
if (!Array.from(this.transactions.state.values()).some(
|
|
76
88
|
({ state }) => state === `persisting`
|
|
77
89
|
)) {
|
|
78
|
-
const keys = /* @__PURE__ */ new Set();
|
|
79
90
|
batch(() => {
|
|
80
91
|
for (const transaction of this.pendingSyncedTransactions) {
|
|
81
92
|
for (const operation of transaction.operations) {
|
|
82
|
-
keys.add(operation.key);
|
|
83
93
|
this.syncedKeys.add(operation.key);
|
|
84
94
|
this.syncedMetadata.setState((prevData) => {
|
|
85
95
|
switch (operation.type) {
|
|
@@ -118,12 +128,6 @@ class Collection {
|
|
|
118
128
|
}
|
|
119
129
|
}
|
|
120
130
|
});
|
|
121
|
-
keys.forEach((key) => {
|
|
122
|
-
const curValue = this.state.get(key);
|
|
123
|
-
if (curValue) {
|
|
124
|
-
this.objectKeyMap.set(curValue, key);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
131
|
this.pendingSyncedTransactions = [];
|
|
128
132
|
if (!this.hasReceivedFirstCommit) {
|
|
129
133
|
this.hasReceivedFirstCommit = true;
|
|
@@ -134,26 +138,25 @@ class Collection {
|
|
|
134
138
|
}
|
|
135
139
|
};
|
|
136
140
|
this.insert = (data, config2) => {
|
|
137
|
-
const
|
|
138
|
-
if (
|
|
139
|
-
throw
|
|
141
|
+
const ambientTransaction = getActiveTransaction();
|
|
142
|
+
if (!ambientTransaction && !this.config.onInsert) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured.`
|
|
145
|
+
);
|
|
140
146
|
}
|
|
141
147
|
const items = Array.isArray(data) ? data : [data];
|
|
142
148
|
const mutations = [];
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
}
|
|
149
|
+
const keys = items.map(
|
|
150
|
+
(item) => this.generateObjectKey(this.config.getId(item), item)
|
|
151
|
+
);
|
|
153
152
|
items.forEach((item, index) => {
|
|
154
153
|
var _a, _b;
|
|
155
154
|
const validatedData = this.validateData(item, `insert`);
|
|
156
155
|
const key = keys[index];
|
|
156
|
+
const id = this.config.getId(item);
|
|
157
|
+
if (this.state.has(this.getKeyFromId(id))) {
|
|
158
|
+
throw `Cannot insert document with ID "${id}" because it already exists in the collection`;
|
|
159
|
+
}
|
|
157
160
|
const mutation = {
|
|
158
161
|
mutationId: crypto.randomUUID(),
|
|
159
162
|
original: {},
|
|
@@ -169,45 +172,48 @@ class Collection {
|
|
|
169
172
|
};
|
|
170
173
|
mutations.push(mutation);
|
|
171
174
|
});
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
if (ambientTransaction) {
|
|
176
|
+
ambientTransaction.applyMutations(mutations);
|
|
177
|
+
this.transactions.setState((sortedMap) => {
|
|
178
|
+
sortedMap.set(ambientTransaction.id, ambientTransaction);
|
|
179
|
+
return sortedMap;
|
|
180
|
+
});
|
|
181
|
+
return ambientTransaction;
|
|
182
|
+
} else {
|
|
183
|
+
const directOpTransaction = new Transaction({
|
|
184
|
+
mutationFn: async (params) => {
|
|
185
|
+
return this.config.onInsert(params);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
directOpTransaction.applyMutations(mutations);
|
|
189
|
+
directOpTransaction.commit();
|
|
190
|
+
this.transactions.setState((sortedMap) => {
|
|
191
|
+
sortedMap.set(directOpTransaction.id, directOpTransaction);
|
|
192
|
+
return sortedMap;
|
|
193
|
+
});
|
|
194
|
+
return directOpTransaction;
|
|
195
|
+
}
|
|
178
196
|
};
|
|
179
|
-
this.delete = (
|
|
180
|
-
const
|
|
181
|
-
if (
|
|
182
|
-
throw
|
|
197
|
+
this.delete = (ids, config2) => {
|
|
198
|
+
const ambientTransaction = getActiveTransaction();
|
|
199
|
+
if (!ambientTransaction && !this.config.onDelete) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Collection.delete called directly (not within an explicit transaction) but no 'onDelete' handler is configured.`
|
|
202
|
+
);
|
|
183
203
|
}
|
|
184
|
-
const
|
|
204
|
+
const idsArray = (Array.isArray(ids) ? ids : [ids]).map(
|
|
205
|
+
(id) => this.getKeyFromId(id)
|
|
206
|
+
);
|
|
185
207
|
const mutations = [];
|
|
186
|
-
for (const
|
|
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
|
-
}
|
|
208
|
+
for (const id of idsArray) {
|
|
203
209
|
const mutation = {
|
|
204
210
|
mutationId: crypto.randomUUID(),
|
|
205
|
-
original: this.state.get(
|
|
206
|
-
modified:
|
|
207
|
-
changes:
|
|
208
|
-
key,
|
|
211
|
+
original: this.state.get(id) || {},
|
|
212
|
+
modified: this.state.get(id) || {},
|
|
213
|
+
changes: this.state.get(id) || {},
|
|
214
|
+
key: id,
|
|
209
215
|
metadata: config2 == null ? void 0 : config2.metadata,
|
|
210
|
-
syncMetadata: this.syncedMetadata.state.get(
|
|
216
|
+
syncMetadata: this.syncedMetadata.state.get(id) || {},
|
|
211
217
|
type: `delete`,
|
|
212
218
|
createdAt: /* @__PURE__ */ new Date(),
|
|
213
219
|
updatedAt: /* @__PURE__ */ new Date(),
|
|
@@ -215,20 +221,37 @@ class Collection {
|
|
|
215
221
|
};
|
|
216
222
|
mutations.push(mutation);
|
|
217
223
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
224
|
+
if (ambientTransaction) {
|
|
225
|
+
ambientTransaction.applyMutations(mutations);
|
|
226
|
+
this.transactions.setState((sortedMap) => {
|
|
227
|
+
sortedMap.set(ambientTransaction.id, ambientTransaction);
|
|
228
|
+
return sortedMap;
|
|
229
|
+
});
|
|
230
|
+
return ambientTransaction;
|
|
231
|
+
}
|
|
232
|
+
const directOpTransaction = new Transaction({
|
|
233
|
+
autoCommit: true,
|
|
234
|
+
mutationFn: async (transaction) => {
|
|
235
|
+
return this.config.onDelete(transaction);
|
|
222
236
|
}
|
|
223
237
|
});
|
|
224
|
-
|
|
238
|
+
directOpTransaction.applyMutations(mutations);
|
|
239
|
+
directOpTransaction.commit();
|
|
225
240
|
this.transactions.setState((sortedMap) => {
|
|
226
|
-
sortedMap.set(
|
|
241
|
+
sortedMap.set(directOpTransaction.id, directOpTransaction);
|
|
227
242
|
return sortedMap;
|
|
228
243
|
});
|
|
229
|
-
return
|
|
244
|
+
return directOpTransaction;
|
|
230
245
|
};
|
|
231
|
-
if (!
|
|
246
|
+
if (!config) {
|
|
247
|
+
throw new Error(`Collection requires a config`);
|
|
248
|
+
}
|
|
249
|
+
if (config.id) {
|
|
250
|
+
this.id = config.id;
|
|
251
|
+
} else {
|
|
252
|
+
this.id = crypto.randomUUID();
|
|
253
|
+
}
|
|
254
|
+
if (!config.sync) {
|
|
232
255
|
throw new Error(`Collection requires a sync config`);
|
|
233
256
|
}
|
|
234
257
|
this.transactions = new Store(
|
|
@@ -263,9 +286,7 @@ class Collection {
|
|
|
263
286
|
this.derivedState = new Derived({
|
|
264
287
|
fn: ({ currDepVals: [syncedData, operations] }) => {
|
|
265
288
|
const combined = new Map(syncedData);
|
|
266
|
-
const optimisticKeys = /* @__PURE__ */ new Set();
|
|
267
289
|
for (const operation of operations) {
|
|
268
|
-
optimisticKeys.add(operation.key);
|
|
269
290
|
if (operation.isActive) {
|
|
270
291
|
switch (operation.type) {
|
|
271
292
|
case `insert`:
|
|
@@ -280,11 +301,6 @@ class Collection {
|
|
|
280
301
|
}
|
|
281
302
|
}
|
|
282
303
|
}
|
|
283
|
-
optimisticKeys.forEach((key) => {
|
|
284
|
-
if (combined.has(key)) {
|
|
285
|
-
this.objectKeyMap.set(combined.get(key), key);
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
304
|
return combined;
|
|
289
305
|
},
|
|
290
306
|
deps: [this.syncedData, this.optimisticOperations]
|
|
@@ -361,7 +377,7 @@ class Collection {
|
|
|
361
377
|
operations: []
|
|
362
378
|
});
|
|
363
379
|
},
|
|
364
|
-
write: (
|
|
380
|
+
write: (messageWithoutKey) => {
|
|
365
381
|
const pendingTransaction = this.pendingSyncedTransactions[this.pendingSyncedTransactions.length - 1];
|
|
366
382
|
if (!pendingTransaction) {
|
|
367
383
|
throw new Error(`No pending sync transaction to write to`);
|
|
@@ -371,6 +387,24 @@ class Collection {
|
|
|
371
387
|
`The pending sync transaction is already committed, you can't still write to it.`
|
|
372
388
|
);
|
|
373
389
|
}
|
|
390
|
+
const key = this.generateObjectKey(
|
|
391
|
+
this.config.getId(messageWithoutKey.value),
|
|
392
|
+
messageWithoutKey.value
|
|
393
|
+
);
|
|
394
|
+
if (messageWithoutKey.type === `insert`) {
|
|
395
|
+
if (this.syncedData.state.has(key) && !pendingTransaction.operations.some(
|
|
396
|
+
(op) => op.key === key && op.type === `delete`
|
|
397
|
+
)) {
|
|
398
|
+
const id = this.config.getId(messageWithoutKey.value);
|
|
399
|
+
throw new Error(
|
|
400
|
+
`Cannot insert document with ID "${id}" from sync because it already exists in the collection "${this.id}"`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const message = {
|
|
405
|
+
...messageWithoutKey,
|
|
406
|
+
key
|
|
407
|
+
};
|
|
374
408
|
pendingTransaction.operations.push(message);
|
|
375
409
|
},
|
|
376
410
|
commit: () => {
|
|
@@ -404,6 +438,24 @@ class Collection {
|
|
|
404
438
|
`Schema must either implement the standard-schema interface or be a Zod schema`
|
|
405
439
|
);
|
|
406
440
|
}
|
|
441
|
+
getKeyFromId(id) {
|
|
442
|
+
if (typeof id === `undefined`) {
|
|
443
|
+
throw new Error(`id is undefined`);
|
|
444
|
+
}
|
|
445
|
+
if (typeof id === `string` && id.startsWith(`KEY::`)) {
|
|
446
|
+
return id;
|
|
447
|
+
} else {
|
|
448
|
+
return this.generateObjectKey(id, null);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
generateObjectKey(id, item) {
|
|
452
|
+
if (typeof id === `undefined`) {
|
|
453
|
+
throw new Error(
|
|
454
|
+
`An object was created without a defined id: ${JSON.stringify(item)}`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
return `KEY::${this.id}/${id}`;
|
|
458
|
+
}
|
|
407
459
|
validateData(data, type, key) {
|
|
408
460
|
if (!this.config.schema) return data;
|
|
409
461
|
const standardSchema = this.ensureStandardSchema(this.config.schema);
|
|
@@ -444,39 +496,31 @@ class Collection {
|
|
|
444
496
|
}
|
|
445
497
|
return result.value;
|
|
446
498
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
let h = 0;
|
|
450
|
-
for (let i = 0; i < str.length; i++) {
|
|
451
|
-
h = Math.imul(31, h) + str.charCodeAt(i) | 0;
|
|
452
|
-
}
|
|
453
|
-
return `${this.id}/${Math.abs(h).toString(36)}`;
|
|
454
|
-
}
|
|
455
|
-
update(items, configOrCallback, maybeCallback) {
|
|
456
|
-
if (typeof items === `undefined`) {
|
|
499
|
+
update(ids, configOrCallback, maybeCallback) {
|
|
500
|
+
if (typeof ids === `undefined`) {
|
|
457
501
|
throw new Error(`The first argument to update is missing`);
|
|
458
502
|
}
|
|
459
|
-
const
|
|
460
|
-
if (
|
|
461
|
-
throw
|
|
503
|
+
const ambientTransaction = getActiveTransaction();
|
|
504
|
+
if (!ambientTransaction && !this.config.onUpdate) {
|
|
505
|
+
throw new Error(
|
|
506
|
+
`Collection.update called directly (not within an explicit transaction) but no 'onUpdate' handler is configured.`
|
|
507
|
+
);
|
|
462
508
|
}
|
|
463
|
-
const isArray = Array.isArray(
|
|
464
|
-
const
|
|
509
|
+
const isArray = Array.isArray(ids);
|
|
510
|
+
const idsArray = (Array.isArray(ids) ? ids : [ids]).map(
|
|
511
|
+
(id) => this.getKeyFromId(id)
|
|
512
|
+
);
|
|
465
513
|
const callback = typeof configOrCallback === `function` ? configOrCallback : maybeCallback;
|
|
466
514
|
const config = typeof configOrCallback === `function` ? {} : configOrCallback;
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
return key;
|
|
515
|
+
const currentObjects = idsArray.map((id) => {
|
|
516
|
+
const item = this.state.get(id);
|
|
517
|
+
if (!item) {
|
|
518
|
+
throw new Error(
|
|
519
|
+
`The id "${id}" was passed to update but an object for this ID was not found in the collection`
|
|
520
|
+
);
|
|
474
521
|
}
|
|
475
|
-
|
|
522
|
+
return item;
|
|
476
523
|
});
|
|
477
|
-
const currentObjects = keys.map((key) => ({
|
|
478
|
-
...this.state.get(key) || {}
|
|
479
|
-
}));
|
|
480
524
|
let changesArray;
|
|
481
525
|
if (isArray) {
|
|
482
526
|
changesArray = withArrayChangeTracking(
|
|
@@ -490,23 +534,33 @@ class Collection {
|
|
|
490
534
|
);
|
|
491
535
|
changesArray = [result];
|
|
492
536
|
}
|
|
493
|
-
const mutations =
|
|
494
|
-
const
|
|
495
|
-
if (!
|
|
537
|
+
const mutations = idsArray.map((id, index) => {
|
|
538
|
+
const itemChanges = changesArray[index];
|
|
539
|
+
if (!itemChanges || Object.keys(itemChanges).length === 0) {
|
|
496
540
|
return null;
|
|
497
541
|
}
|
|
498
|
-
const
|
|
542
|
+
const originalItem = currentObjects[index];
|
|
543
|
+
const validatedUpdatePayload = this.validateData(
|
|
544
|
+
itemChanges,
|
|
545
|
+
`update`,
|
|
546
|
+
id
|
|
547
|
+
);
|
|
548
|
+
const modifiedItem = { ...originalItem, ...validatedUpdatePayload };
|
|
549
|
+
const originalItemId = this.config.getId(originalItem);
|
|
550
|
+
const modifiedItemId = this.config.getId(modifiedItem);
|
|
551
|
+
if (originalItemId !== modifiedItemId) {
|
|
552
|
+
throw new Error(
|
|
553
|
+
`Updating the ID of an item is not allowed. Original ID: "${originalItemId}", Attempted new ID: "${modifiedItemId}". Please delete the old item and create a new one if an ID change is necessary.`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
499
556
|
return {
|
|
500
557
|
mutationId: crypto.randomUUID(),
|
|
501
|
-
original:
|
|
502
|
-
modified:
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
},
|
|
506
|
-
changes: validatedData,
|
|
507
|
-
key,
|
|
558
|
+
original: originalItem,
|
|
559
|
+
modified: modifiedItem,
|
|
560
|
+
changes: validatedUpdatePayload,
|
|
561
|
+
key: id,
|
|
508
562
|
metadata: config.metadata,
|
|
509
|
-
syncMetadata: this.syncedMetadata.state.get(
|
|
563
|
+
syncMetadata: this.syncedMetadata.state.get(id) || {},
|
|
510
564
|
type: `update`,
|
|
511
565
|
createdAt: /* @__PURE__ */ new Date(),
|
|
512
566
|
updatedAt: /* @__PURE__ */ new Date(),
|
|
@@ -516,12 +570,26 @@ class Collection {
|
|
|
516
570
|
if (mutations.length === 0) {
|
|
517
571
|
throw new Error(`No changes were made to any of the objects`);
|
|
518
572
|
}
|
|
519
|
-
|
|
573
|
+
if (ambientTransaction) {
|
|
574
|
+
ambientTransaction.applyMutations(mutations);
|
|
575
|
+
this.transactions.setState((sortedMap) => {
|
|
576
|
+
sortedMap.set(ambientTransaction.id, ambientTransaction);
|
|
577
|
+
return sortedMap;
|
|
578
|
+
});
|
|
579
|
+
return ambientTransaction;
|
|
580
|
+
}
|
|
581
|
+
const directOpTransaction = new Transaction({
|
|
582
|
+
mutationFn: async (transaction) => {
|
|
583
|
+
return this.config.onUpdate(transaction);
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
directOpTransaction.applyMutations(mutations);
|
|
587
|
+
directOpTransaction.commit();
|
|
520
588
|
this.transactions.setState((sortedMap) => {
|
|
521
|
-
sortedMap.set(
|
|
589
|
+
sortedMap.set(directOpTransaction.id, directOpTransaction);
|
|
522
590
|
return sortedMap;
|
|
523
591
|
});
|
|
524
|
-
return
|
|
592
|
+
return directOpTransaction;
|
|
525
593
|
}
|
|
526
594
|
/**
|
|
527
595
|
* Gets the current state of the collection as a Map
|
|
@@ -600,6 +668,7 @@ export {
|
|
|
600
668
|
Collection,
|
|
601
669
|
SchemaValidationError,
|
|
602
670
|
collectionsStore,
|
|
671
|
+
createCollection,
|
|
603
672
|
preloadCollection
|
|
604
673
|
};
|
|
605
674
|
//# sourceMappingURL=collection.js.map
|