@tanstack/db 0.4.6 → 0.4.7
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/mutations.cjs +4 -4
- package/dist/cjs/collection/mutations.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/esm/collection/mutations.js +4 -4
- package/dist/esm/collection/mutations.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/package.json +1 -1
- package/src/collection/mutations.ts +8 -4
- package/src/local-only.ts +119 -30
- package/src/local-storage.ts +170 -5
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
|
}
|