@tanstack/db 0.4.6 → 0.4.8
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/index.cjs.map +1 -1
- package/dist/cjs/collection/index.d.cts +2 -1
- package/dist/cjs/collection/lifecycle.cjs +2 -3
- package/dist/cjs/collection/lifecycle.cjs.map +1 -1
- package/dist/cjs/collection/mutations.cjs +4 -4
- package/dist/cjs/collection/mutations.cjs.map +1 -1
- package/dist/cjs/collection/state.cjs +22 -33
- package/dist/cjs/collection/state.cjs.map +1 -1
- package/dist/cjs/collection/state.d.cts +6 -2
- package/dist/cjs/collection/sync.cjs +4 -3
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/indexes/auto-index.cjs +0 -3
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/local-only.cjs +21 -2
- package/dist/cjs/local-only.cjs.map +1 -1
- package/dist/cjs/local-only.d.cts +64 -7
- package/dist/cjs/local-storage.cjs +71 -3
- package/dist/cjs/local-storage.cjs.map +1 -1
- package/dist/cjs/local-storage.d.cts +55 -2
- package/dist/cjs/query/live/collection-config-builder.cjs +54 -12
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +17 -2
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/types.d.cts +3 -5
- package/dist/esm/collection/index.d.ts +2 -1
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/lifecycle.js +2 -3
- package/dist/esm/collection/lifecycle.js.map +1 -1
- package/dist/esm/collection/mutations.js +4 -4
- package/dist/esm/collection/mutations.js.map +1 -1
- package/dist/esm/collection/state.d.ts +6 -2
- package/dist/esm/collection/state.js +22 -33
- package/dist/esm/collection/state.js.map +1 -1
- package/dist/esm/collection/sync.js +4 -3
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/indexes/auto-index.js +0 -3
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/local-only.d.ts +64 -7
- package/dist/esm/local-only.js +21 -2
- package/dist/esm/local-only.js.map +1 -1
- package/dist/esm/local-storage.d.ts +55 -2
- package/dist/esm/local-storage.js +72 -4
- package/dist/esm/local-storage.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.d.ts +17 -2
- package/dist/esm/query/live/collection-config-builder.js +54 -12
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/types.d.ts +3 -5
- package/package.json +1 -1
- package/src/collection/index.ts +1 -1
- package/src/collection/lifecycle.ts +3 -4
- package/src/collection/mutations.ts +8 -4
- package/src/collection/state.ts +52 -48
- package/src/collection/sync.ts +7 -6
- package/src/indexes/auto-index.ts +0 -8
- package/src/local-only.ts +119 -30
- package/src/local-storage.ts +170 -5
- package/src/query/live/collection-config-builder.ts +103 -24
- package/src/query/live/collection-subscriber.ts +3 -3
- package/src/types.ts +3 -5
package/src/local-only.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
BaseCollectionConfig,
|
|
3
3
|
CollectionConfig,
|
|
4
|
+
DeleteMutationFn,
|
|
4
5
|
DeleteMutationFnParams,
|
|
5
6
|
InferSchemaOutput,
|
|
7
|
+
InsertMutationFn,
|
|
6
8
|
InsertMutationFnParams,
|
|
7
9
|
OperationType,
|
|
10
|
+
PendingMutation,
|
|
8
11
|
SyncConfig,
|
|
12
|
+
UpdateMutationFn,
|
|
9
13
|
UpdateMutationFnParams,
|
|
10
14
|
UtilsRecord,
|
|
11
15
|
} from "./types"
|
|
16
|
+
import type { Collection } from "./collection/index"
|
|
12
17
|
import type { StandardSchemaV1 } from "@standard-schema/spec"
|
|
13
18
|
|
|
14
19
|
/**
|
|
@@ -33,9 +38,44 @@ export interface LocalOnlyCollectionConfig<
|
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
/**
|
|
36
|
-
* Local-only collection utilities type
|
|
41
|
+
* Local-only collection utilities type
|
|
37
42
|
*/
|
|
38
|
-
export interface LocalOnlyCollectionUtils extends UtilsRecord {
|
|
43
|
+
export interface LocalOnlyCollectionUtils extends UtilsRecord {
|
|
44
|
+
/**
|
|
45
|
+
* Accepts mutations from a transaction that belong to this collection and persists them.
|
|
46
|
+
* This should be called in your transaction's mutationFn to persist local-only data.
|
|
47
|
+
*
|
|
48
|
+
* @param transaction - The transaction containing mutations to accept
|
|
49
|
+
* @example
|
|
50
|
+
* const localData = createCollection(localOnlyCollectionOptions({...}))
|
|
51
|
+
*
|
|
52
|
+
* const tx = createTransaction({
|
|
53
|
+
* mutationFn: async ({ transaction }) => {
|
|
54
|
+
* // Make API call first
|
|
55
|
+
* await api.save(...)
|
|
56
|
+
* // Then persist local-only mutations after success
|
|
57
|
+
* localData.utils.acceptMutations(transaction)
|
|
58
|
+
* }
|
|
59
|
+
* })
|
|
60
|
+
*/
|
|
61
|
+
acceptMutations: (transaction: {
|
|
62
|
+
mutations: Array<PendingMutation<Record<string, unknown>>>
|
|
63
|
+
}) => void
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type LocalOnlyCollectionOptionsResult<
|
|
67
|
+
T extends object,
|
|
68
|
+
TKey extends string | number,
|
|
69
|
+
TSchema extends StandardSchemaV1 | never = never,
|
|
70
|
+
> = Omit<
|
|
71
|
+
CollectionConfig<T, TKey, TSchema>,
|
|
72
|
+
`onInsert` | `onUpdate` | `onDelete`
|
|
73
|
+
> & {
|
|
74
|
+
onInsert?: InsertMutationFn<T, TKey, LocalOnlyCollectionUtils>
|
|
75
|
+
onUpdate?: UpdateMutationFn<T, TKey, LocalOnlyCollectionUtils>
|
|
76
|
+
onDelete?: DeleteMutationFn<T, TKey, LocalOnlyCollectionUtils>
|
|
77
|
+
utils: LocalOnlyCollectionUtils
|
|
78
|
+
}
|
|
39
79
|
|
|
40
80
|
/**
|
|
41
81
|
* Creates Local-only collection options for use with a standard Collection
|
|
@@ -44,10 +84,16 @@ export interface LocalOnlyCollectionUtils extends UtilsRecord {}
|
|
|
44
84
|
* that immediately "syncs" all optimistic changes to the collection, making them permanent.
|
|
45
85
|
* Perfect for local-only data that doesn't need persistence or external synchronization.
|
|
46
86
|
*
|
|
87
|
+
* **Using with Manual Transactions:**
|
|
88
|
+
*
|
|
89
|
+
* For manual transactions, you must call `utils.acceptMutations()` in your transaction's `mutationFn`
|
|
90
|
+
* to persist changes made during `tx.mutate()`. This is necessary because local-only collections
|
|
91
|
+
* don't participate in the standard mutation handler flow for manual transactions.
|
|
92
|
+
*
|
|
47
93
|
* @template T - The schema type if a schema is provided, otherwise the type of items in the collection
|
|
48
94
|
* @template TKey - The type of the key returned by getKey
|
|
49
95
|
* @param config - Configuration options for the Local-only collection
|
|
50
|
-
* @returns Collection options with utilities
|
|
96
|
+
* @returns Collection options with utilities including acceptMutations
|
|
51
97
|
*
|
|
52
98
|
* @example
|
|
53
99
|
* // Basic local-only collection
|
|
@@ -80,6 +126,32 @@ export interface LocalOnlyCollectionUtils extends UtilsRecord {}
|
|
|
80
126
|
* },
|
|
81
127
|
* })
|
|
82
128
|
* )
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* // Using with manual transactions
|
|
132
|
+
* const localData = createCollection(
|
|
133
|
+
* localOnlyCollectionOptions({
|
|
134
|
+
* getKey: (item) => item.id,
|
|
135
|
+
* })
|
|
136
|
+
* )
|
|
137
|
+
*
|
|
138
|
+
* const tx = createTransaction({
|
|
139
|
+
* mutationFn: async ({ transaction }) => {
|
|
140
|
+
* // Use local data in API call
|
|
141
|
+
* const localMutations = transaction.mutations.filter(m => m.collection === localData)
|
|
142
|
+
* await api.save({ metadata: localMutations[0]?.modified })
|
|
143
|
+
*
|
|
144
|
+
* // Persist local-only mutations after API success
|
|
145
|
+
* localData.utils.acceptMutations(transaction)
|
|
146
|
+
* }
|
|
147
|
+
* })
|
|
148
|
+
*
|
|
149
|
+
* tx.mutate(() => {
|
|
150
|
+
* localData.insert({ id: 1, data: 'metadata' })
|
|
151
|
+
* apiCollection.insert({ id: 2, data: 'main data' })
|
|
152
|
+
* })
|
|
153
|
+
*
|
|
154
|
+
* await tx.commit()
|
|
83
155
|
*/
|
|
84
156
|
|
|
85
157
|
// Overload for when schema is provided
|
|
@@ -90,8 +162,7 @@ export function localOnlyCollectionOptions<
|
|
|
90
162
|
config: LocalOnlyCollectionConfig<InferSchemaOutput<T>, T, TKey> & {
|
|
91
163
|
schema: T
|
|
92
164
|
}
|
|
93
|
-
):
|
|
94
|
-
utils: LocalOnlyCollectionUtils
|
|
165
|
+
): LocalOnlyCollectionOptionsResult<InferSchemaOutput<T>, TKey, T> & {
|
|
95
166
|
schema: T
|
|
96
167
|
}
|
|
97
168
|
|
|
@@ -104,15 +175,17 @@ export function localOnlyCollectionOptions<
|
|
|
104
175
|
config: LocalOnlyCollectionConfig<T, never, TKey> & {
|
|
105
176
|
schema?: never // prohibit schema
|
|
106
177
|
}
|
|
107
|
-
):
|
|
108
|
-
utils: LocalOnlyCollectionUtils
|
|
178
|
+
): LocalOnlyCollectionOptionsResult<T, TKey> & {
|
|
109
179
|
schema?: never // no schema in the result
|
|
110
180
|
}
|
|
111
181
|
|
|
112
|
-
export function localOnlyCollectionOptions
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
182
|
+
export function localOnlyCollectionOptions<
|
|
183
|
+
T extends object = object,
|
|
184
|
+
TSchema extends StandardSchemaV1 = never,
|
|
185
|
+
TKey extends string | number = string | number,
|
|
186
|
+
>(
|
|
187
|
+
config: LocalOnlyCollectionConfig<T, TSchema, TKey>
|
|
188
|
+
): LocalOnlyCollectionOptionsResult<T, TKey, TSchema> & {
|
|
116
189
|
schema?: StandardSchemaV1
|
|
117
190
|
} {
|
|
118
191
|
const { initialData, onInsert, onUpdate, onDelete, ...restConfig } = config
|
|
@@ -125,11 +198,7 @@ export function localOnlyCollectionOptions(
|
|
|
125
198
|
* Wraps the user's onInsert handler to also confirm the transaction immediately
|
|
126
199
|
*/
|
|
127
200
|
const wrappedOnInsert = async (
|
|
128
|
-
params: InsertMutationFnParams<
|
|
129
|
-
any,
|
|
130
|
-
string | number,
|
|
131
|
-
LocalOnlyCollectionUtils
|
|
132
|
-
>
|
|
201
|
+
params: InsertMutationFnParams<T, TKey, LocalOnlyCollectionUtils>
|
|
133
202
|
) => {
|
|
134
203
|
// Call user handler first if provided
|
|
135
204
|
let handlerResult
|
|
@@ -147,11 +216,7 @@ export function localOnlyCollectionOptions(
|
|
|
147
216
|
* Wrapper for onUpdate handler that also confirms the transaction immediately
|
|
148
217
|
*/
|
|
149
218
|
const wrappedOnUpdate = async (
|
|
150
|
-
params: UpdateMutationFnParams<
|
|
151
|
-
any,
|
|
152
|
-
string | number,
|
|
153
|
-
LocalOnlyCollectionUtils
|
|
154
|
-
>
|
|
219
|
+
params: UpdateMutationFnParams<T, TKey, LocalOnlyCollectionUtils>
|
|
155
220
|
) => {
|
|
156
221
|
// Call user handler first if provided
|
|
157
222
|
let handlerResult
|
|
@@ -169,11 +234,7 @@ export function localOnlyCollectionOptions(
|
|
|
169
234
|
* Wrapper for onDelete handler that also confirms the transaction immediately
|
|
170
235
|
*/
|
|
171
236
|
const wrappedOnDelete = async (
|
|
172
|
-
params: DeleteMutationFnParams<
|
|
173
|
-
any,
|
|
174
|
-
string | number,
|
|
175
|
-
LocalOnlyCollectionUtils
|
|
176
|
-
>
|
|
237
|
+
params: DeleteMutationFnParams<T, TKey, LocalOnlyCollectionUtils>
|
|
177
238
|
) => {
|
|
178
239
|
// Call user handler first if provided
|
|
179
240
|
let handlerResult
|
|
@@ -187,13 +248,38 @@ export function localOnlyCollectionOptions(
|
|
|
187
248
|
return handlerResult
|
|
188
249
|
}
|
|
189
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Accepts mutations from a transaction that belong to this collection and persists them
|
|
253
|
+
*/
|
|
254
|
+
const acceptMutations = (transaction: {
|
|
255
|
+
mutations: Array<PendingMutation<Record<string, unknown>>>
|
|
256
|
+
}) => {
|
|
257
|
+
// Filter mutations that belong to this collection
|
|
258
|
+
const collectionMutations = transaction.mutations.filter(
|
|
259
|
+
(m) =>
|
|
260
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
261
|
+
m.collection === syncResult.collection
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if (collectionMutations.length === 0) {
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Persist the mutations through sync
|
|
269
|
+
syncResult.confirmOperationsSync(
|
|
270
|
+
collectionMutations as Array<PendingMutation<T>>
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
|
|
190
274
|
return {
|
|
191
275
|
...restConfig,
|
|
192
276
|
sync: syncResult.sync,
|
|
193
277
|
onInsert: wrappedOnInsert,
|
|
194
278
|
onUpdate: wrappedOnUpdate,
|
|
195
279
|
onDelete: wrappedOnDelete,
|
|
196
|
-
utils: {
|
|
280
|
+
utils: {
|
|
281
|
+
acceptMutations,
|
|
282
|
+
} as LocalOnlyCollectionUtils,
|
|
197
283
|
startSync: true,
|
|
198
284
|
gcTime: 0,
|
|
199
285
|
}
|
|
@@ -212,11 +298,12 @@ export function localOnlyCollectionOptions(
|
|
|
212
298
|
function createLocalOnlySync<T extends object, TKey extends string | number>(
|
|
213
299
|
initialData?: Array<T>
|
|
214
300
|
) {
|
|
215
|
-
// Capture sync functions for transaction confirmation
|
|
301
|
+
// Capture sync functions and collection for transaction confirmation
|
|
216
302
|
let syncBegin: (() => void) | null = null
|
|
217
303
|
let syncWrite: ((message: { type: OperationType; value: T }) => void) | null =
|
|
218
304
|
null
|
|
219
305
|
let syncCommit: (() => void) | null = null
|
|
306
|
+
let collection: Collection<T, TKey, LocalOnlyCollectionUtils> | null = null
|
|
220
307
|
|
|
221
308
|
const sync: SyncConfig<T, TKey> = {
|
|
222
309
|
/**
|
|
@@ -227,10 +314,11 @@ function createLocalOnlySync<T extends object, TKey extends string | number>(
|
|
|
227
314
|
sync: (params) => {
|
|
228
315
|
const { begin, write, commit, markReady } = params
|
|
229
316
|
|
|
230
|
-
// Capture sync functions for later use
|
|
317
|
+
// Capture sync functions and collection for later use
|
|
231
318
|
syncBegin = begin
|
|
232
319
|
syncWrite = write
|
|
233
320
|
syncCommit = commit
|
|
321
|
+
collection = params.collection
|
|
234
322
|
|
|
235
323
|
// Apply initial data if provided
|
|
236
324
|
if (initialData && initialData.length > 0) {
|
|
@@ -265,7 +353,7 @@ function createLocalOnlySync<T extends object, TKey extends string | number>(
|
|
|
265
353
|
*
|
|
266
354
|
* @param mutations - Array of mutation objects from the transaction
|
|
267
355
|
*/
|
|
268
|
-
const confirmOperationsSync = (mutations: Array<
|
|
356
|
+
const confirmOperationsSync = (mutations: Array<PendingMutation<T>>) => {
|
|
269
357
|
if (!syncBegin || !syncWrite || !syncCommit) {
|
|
270
358
|
return // Sync not initialized yet, which is fine
|
|
271
359
|
}
|
|
@@ -286,5 +374,6 @@ function createLocalOnlySync<T extends object, TKey extends string | number>(
|
|
|
286
374
|
return {
|
|
287
375
|
sync,
|
|
288
376
|
confirmOperationsSync,
|
|
377
|
+
collection,
|
|
289
378
|
}
|
|
290
379
|
}
|
package/src/local-storage.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
DeleteMutationFnParams,
|
|
13
13
|
InferSchemaOutput,
|
|
14
14
|
InsertMutationFnParams,
|
|
15
|
+
PendingMutation,
|
|
15
16
|
SyncConfig,
|
|
16
17
|
UpdateMutationFnParams,
|
|
17
18
|
UtilsRecord,
|
|
@@ -90,6 +91,26 @@ export type GetStorageSizeFn = () => number
|
|
|
90
91
|
export interface LocalStorageCollectionUtils extends UtilsRecord {
|
|
91
92
|
clearStorage: ClearStorageFn
|
|
92
93
|
getStorageSize: GetStorageSizeFn
|
|
94
|
+
/**
|
|
95
|
+
* Accepts mutations from a transaction that belong to this collection and persists them to localStorage.
|
|
96
|
+
* This should be called in your transaction's mutationFn to persist local-storage data.
|
|
97
|
+
*
|
|
98
|
+
* @param transaction - The transaction containing mutations to accept
|
|
99
|
+
* @example
|
|
100
|
+
* const localSettings = createCollection(localStorageCollectionOptions({...}))
|
|
101
|
+
*
|
|
102
|
+
* const tx = createTransaction({
|
|
103
|
+
* mutationFn: async ({ transaction }) => {
|
|
104
|
+
* // Make API call first
|
|
105
|
+
* await api.save(...)
|
|
106
|
+
* // Then persist local-storage mutations after success
|
|
107
|
+
* localSettings.utils.acceptMutations(transaction)
|
|
108
|
+
* }
|
|
109
|
+
* })
|
|
110
|
+
*/
|
|
111
|
+
acceptMutations: (transaction: {
|
|
112
|
+
mutations: Array<PendingMutation<Record<string, unknown>>>
|
|
113
|
+
}) => void
|
|
93
114
|
}
|
|
94
115
|
|
|
95
116
|
/**
|
|
@@ -123,11 +144,17 @@ function generateUuid(): string {
|
|
|
123
144
|
* This function creates a collection that persists data to localStorage/sessionStorage
|
|
124
145
|
* and synchronizes changes across browser tabs using storage events.
|
|
125
146
|
*
|
|
147
|
+
* **Using with Manual Transactions:**
|
|
148
|
+
*
|
|
149
|
+
* For manual transactions, you must call `utils.acceptMutations()` in your transaction's `mutationFn`
|
|
150
|
+
* to persist changes made during `tx.mutate()`. This is necessary because local-storage collections
|
|
151
|
+
* don't participate in the standard mutation handler flow for manual transactions.
|
|
152
|
+
*
|
|
126
153
|
* @template TExplicit - The explicit type of items in the collection (highest priority)
|
|
127
154
|
* @template TSchema - The schema type for validation and type inference (second priority)
|
|
128
155
|
* @template TFallback - The fallback type if no explicit or schema type is provided
|
|
129
156
|
* @param config - Configuration options for the localStorage collection
|
|
130
|
-
* @returns Collection options with utilities including clearStorage and
|
|
157
|
+
* @returns Collection options with utilities including clearStorage, getStorageSize, and acceptMutations
|
|
131
158
|
*
|
|
132
159
|
* @example
|
|
133
160
|
* // Basic localStorage collection
|
|
@@ -159,6 +186,33 @@ function generateUuid(): string {
|
|
|
159
186
|
* },
|
|
160
187
|
* })
|
|
161
188
|
* )
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* // Using with manual transactions
|
|
192
|
+
* const localSettings = createCollection(
|
|
193
|
+
* localStorageCollectionOptions({
|
|
194
|
+
* storageKey: 'user-settings',
|
|
195
|
+
* getKey: (item) => item.id,
|
|
196
|
+
* })
|
|
197
|
+
* )
|
|
198
|
+
*
|
|
199
|
+
* const tx = createTransaction({
|
|
200
|
+
* mutationFn: async ({ transaction }) => {
|
|
201
|
+
* // Use settings data in API call
|
|
202
|
+
* const settingsMutations = transaction.mutations.filter(m => m.collection === localSettings)
|
|
203
|
+
* await api.updateUserProfile({ settings: settingsMutations[0]?.modified })
|
|
204
|
+
*
|
|
205
|
+
* // Persist local-storage mutations after API success
|
|
206
|
+
* localSettings.utils.acceptMutations(transaction)
|
|
207
|
+
* }
|
|
208
|
+
* })
|
|
209
|
+
*
|
|
210
|
+
* tx.mutate(() => {
|
|
211
|
+
* localSettings.insert({ id: 'theme', value: 'dark' })
|
|
212
|
+
* apiCollection.insert({ id: 2, data: 'profile data' })
|
|
213
|
+
* })
|
|
214
|
+
*
|
|
215
|
+
* await tx.commit()
|
|
162
216
|
*/
|
|
163
217
|
|
|
164
218
|
// Overload for when schema is provided
|
|
@@ -397,6 +451,76 @@ export function localStorageCollectionOptions(
|
|
|
397
451
|
// Default id to a pattern based on storage key if not provided
|
|
398
452
|
const collectionId = id ?? `local-collection:${config.storageKey}`
|
|
399
453
|
|
|
454
|
+
/**
|
|
455
|
+
* Accepts mutations from a transaction that belong to this collection and persists them to storage
|
|
456
|
+
*/
|
|
457
|
+
const acceptMutations = (transaction: {
|
|
458
|
+
mutations: Array<PendingMutation<Record<string, unknown>>>
|
|
459
|
+
}) => {
|
|
460
|
+
// Filter mutations that belong to this collection
|
|
461
|
+
// Use collection ID for filtering if collection reference isn't available yet
|
|
462
|
+
const collectionMutations = transaction.mutations.filter((m) => {
|
|
463
|
+
// Try to match by collection reference first
|
|
464
|
+
if (sync.collection && m.collection === sync.collection) {
|
|
465
|
+
return true
|
|
466
|
+
}
|
|
467
|
+
// Fall back to matching by collection ID
|
|
468
|
+
return m.collection.id === collectionId
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
if (collectionMutations.length === 0) {
|
|
472
|
+
return
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Validate all mutations can be serialized before modifying storage
|
|
476
|
+
for (const mutation of collectionMutations) {
|
|
477
|
+
switch (mutation.type) {
|
|
478
|
+
case `insert`:
|
|
479
|
+
case `update`:
|
|
480
|
+
validateJsonSerializable(mutation.modified, mutation.type)
|
|
481
|
+
break
|
|
482
|
+
case `delete`:
|
|
483
|
+
validateJsonSerializable(mutation.original, mutation.type)
|
|
484
|
+
break
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Load current data from storage
|
|
489
|
+
const currentData = loadFromStorage<Record<string, unknown>>(
|
|
490
|
+
config.storageKey,
|
|
491
|
+
storage
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
// Apply each mutation
|
|
495
|
+
for (const mutation of collectionMutations) {
|
|
496
|
+
// Use the engine's pre-computed key to avoid key derivation issues
|
|
497
|
+
const key = mutation.key
|
|
498
|
+
|
|
499
|
+
switch (mutation.type) {
|
|
500
|
+
case `insert`:
|
|
501
|
+
case `update`: {
|
|
502
|
+
const storedItem: StoredItem<Record<string, unknown>> = {
|
|
503
|
+
versionKey: generateUuid(),
|
|
504
|
+
data: mutation.modified,
|
|
505
|
+
}
|
|
506
|
+
currentData.set(key, storedItem)
|
|
507
|
+
break
|
|
508
|
+
}
|
|
509
|
+
case `delete`: {
|
|
510
|
+
currentData.delete(key)
|
|
511
|
+
break
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Save to storage
|
|
517
|
+
saveToStorage(currentData)
|
|
518
|
+
|
|
519
|
+
// Confirm the mutations in the collection to move them from optimistic to synced state
|
|
520
|
+
// This writes them through the sync interface to make them "synced" instead of "optimistic"
|
|
521
|
+
sync.confirmOperationsSync(collectionMutations)
|
|
522
|
+
}
|
|
523
|
+
|
|
400
524
|
return {
|
|
401
525
|
...restConfig,
|
|
402
526
|
id: collectionId,
|
|
@@ -407,6 +531,7 @@ export function localStorageCollectionOptions(
|
|
|
407
531
|
utils: {
|
|
408
532
|
clearStorage,
|
|
409
533
|
getStorageSize,
|
|
534
|
+
acceptMutations,
|
|
410
535
|
},
|
|
411
536
|
}
|
|
412
537
|
}
|
|
@@ -480,8 +605,13 @@ function createLocalStorageSync<T extends object>(
|
|
|
480
605
|
storageEventApi: StorageEventApi,
|
|
481
606
|
_getKey: (item: T) => string | number,
|
|
482
607
|
lastKnownData: Map<string | number, StoredItem<T>>
|
|
483
|
-
): SyncConfig<T> & {
|
|
608
|
+
): SyncConfig<T> & {
|
|
609
|
+
manualTrigger?: () => void
|
|
610
|
+
collection: any
|
|
611
|
+
confirmOperationsSync: (mutations: Array<any>) => void
|
|
612
|
+
} {
|
|
484
613
|
let syncParams: Parameters<SyncConfig<T>[`sync`]>[0] | null = null
|
|
614
|
+
let collection: any = null
|
|
485
615
|
|
|
486
616
|
/**
|
|
487
617
|
* Compare two Maps to find differences using version keys
|
|
@@ -556,12 +686,16 @@ function createLocalStorageSync<T extends object>(
|
|
|
556
686
|
}
|
|
557
687
|
}
|
|
558
688
|
|
|
559
|
-
const syncConfig: SyncConfig<T> & {
|
|
689
|
+
const syncConfig: SyncConfig<T> & {
|
|
690
|
+
manualTrigger?: () => void
|
|
691
|
+
collection: any
|
|
692
|
+
} = {
|
|
560
693
|
sync: (params: Parameters<SyncConfig<T>[`sync`]>[0]) => {
|
|
561
694
|
const { begin, write, commit, markReady } = params
|
|
562
695
|
|
|
563
|
-
// Store sync params for later use
|
|
696
|
+
// Store sync params and collection for later use
|
|
564
697
|
syncParams = params
|
|
698
|
+
collection = params.collection
|
|
565
699
|
|
|
566
700
|
// Initial load
|
|
567
701
|
const initialData = loadFromStorage<T>(storageKey, storage)
|
|
@@ -613,7 +747,38 @@ function createLocalStorageSync<T extends object>(
|
|
|
613
747
|
|
|
614
748
|
// Manual trigger function for local updates
|
|
615
749
|
manualTrigger: processStorageChanges,
|
|
750
|
+
|
|
751
|
+
// Collection instance reference
|
|
752
|
+
collection,
|
|
616
753
|
}
|
|
617
754
|
|
|
618
|
-
|
|
755
|
+
/**
|
|
756
|
+
* Confirms mutations by writing them through the sync interface
|
|
757
|
+
* This moves mutations from optimistic to synced state
|
|
758
|
+
* @param mutations - Array of mutation objects to confirm
|
|
759
|
+
*/
|
|
760
|
+
const confirmOperationsSync = (mutations: Array<any>) => {
|
|
761
|
+
if (!syncParams) {
|
|
762
|
+
// Sync not initialized yet, mutations will be handled on next sync
|
|
763
|
+
return
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const { begin, write, commit } = syncParams
|
|
767
|
+
|
|
768
|
+
// Write the mutations through sync to confirm them
|
|
769
|
+
begin()
|
|
770
|
+
mutations.forEach((mutation: any) => {
|
|
771
|
+
write({
|
|
772
|
+
type: mutation.type,
|
|
773
|
+
value:
|
|
774
|
+
mutation.type === `delete` ? mutation.original : mutation.modified,
|
|
775
|
+
})
|
|
776
|
+
})
|
|
777
|
+
commit()
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return {
|
|
781
|
+
...syncConfig,
|
|
782
|
+
confirmOperationsSync,
|
|
783
|
+
}
|
|
619
784
|
}
|