@tanstack/db 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/cjs/collection.cjs +23 -4
  2. package/dist/cjs/collection.cjs.map +1 -1
  3. package/dist/cjs/collection.d.cts +35 -41
  4. package/dist/cjs/local-only.cjs.map +1 -1
  5. package/dist/cjs/local-only.d.cts +17 -43
  6. package/dist/cjs/local-storage.cjs +3 -12
  7. package/dist/cjs/local-storage.cjs.map +1 -1
  8. package/dist/cjs/local-storage.d.cts +16 -39
  9. package/dist/cjs/query/builder/types.d.cts +3 -10
  10. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  11. package/dist/cjs/transactions.cjs +76 -5
  12. package/dist/cjs/transactions.cjs.map +1 -1
  13. package/dist/cjs/transactions.d.cts +17 -0
  14. package/dist/cjs/types.d.cts +10 -31
  15. package/dist/esm/collection.d.ts +35 -41
  16. package/dist/esm/collection.js +23 -4
  17. package/dist/esm/collection.js.map +1 -1
  18. package/dist/esm/local-only.d.ts +17 -43
  19. package/dist/esm/local-only.js.map +1 -1
  20. package/dist/esm/local-storage.d.ts +16 -39
  21. package/dist/esm/local-storage.js +3 -12
  22. package/dist/esm/local-storage.js.map +1 -1
  23. package/dist/esm/query/builder/types.d.ts +3 -10
  24. package/dist/esm/query/live-query-collection.js.map +1 -1
  25. package/dist/esm/transactions.d.ts +17 -0
  26. package/dist/esm/transactions.js +76 -5
  27. package/dist/esm/transactions.js.map +1 -1
  28. package/dist/esm/types.d.ts +10 -31
  29. package/package.json +2 -2
  30. package/src/collection.ts +148 -196
  31. package/src/local-only.ts +57 -77
  32. package/src/local-storage.ts +53 -85
  33. package/src/query/builder/types.ts +3 -12
  34. package/src/query/live-query-collection.ts +1 -1
  35. package/src/transactions.ts +121 -6
  36. package/src/types.ts +25 -55
package/src/collection.ts CHANGED
@@ -48,12 +48,12 @@ import type {
48
48
  CollectionStatus,
49
49
  CurrentStateAsChangesOptions,
50
50
  Fn,
51
+ InferSchemaInput,
52
+ InferSchemaOutput,
51
53
  InsertConfig,
52
54
  OperationConfig,
53
55
  OptimisticChangeMessage,
54
56
  PendingMutation,
55
- ResolveInsertInput,
56
- ResolveType,
57
57
  StandardSchema,
58
58
  SubscribeChangesOptions,
59
59
  Transaction as TransactionType,
@@ -91,11 +91,9 @@ export interface Collection<
91
91
  /**
92
92
  * Creates a new Collection instance with the given configuration
93
93
  *
94
- * @template TExplicit - The explicit type of items in the collection (highest priority)
94
+ * @template T - The schema type if a schema is provided, otherwise the type of items in the collection
95
95
  * @template TKey - The type of the key for the collection
96
96
  * @template TUtils - The utilities record type
97
- * @template TSchema - The schema type for validation and type inference (second priority)
98
- * @template TFallback - The fallback type if no explicit or schema type is provided
99
97
  * @param options - Collection options with optional utilities
100
98
  * @returns A new Collection with utilities exposed both at top level and under .utils
101
99
  *
@@ -159,142 +157,72 @@ export interface Collection<
159
157
  * sync: { sync: () => {} }
160
158
  * })
161
159
  *
162
- * // Note: You can provide an explicit type, a schema, or both. When both are provided, the explicit type takes precedence.
163
160
  */
164
161
 
165
- // Overload for when schema is provided - infers schema type
162
+ // Overload for when schema is provided
166
163
  export function createCollection<
167
- TSchema extends StandardSchemaV1,
164
+ T extends StandardSchemaV1,
168
165
  TKey extends string | number = string | number,
169
166
  TUtils extends UtilsRecord = {},
170
- TFallback extends object = Record<string, unknown>,
171
167
  >(
172
- options: CollectionConfig<
173
- ResolveType<unknown, TSchema, TFallback>,
174
- TKey,
175
- TSchema,
176
- ResolveInsertInput<unknown, TSchema, TFallback>
177
- > & {
178
- schema: TSchema
168
+ options: CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
169
+ schema: T
179
170
  utils?: TUtils
180
171
  }
181
- ): Collection<
182
- ResolveType<unknown, TSchema, TFallback>,
183
- TKey,
184
- TUtils,
185
- TSchema,
186
- ResolveInsertInput<unknown, TSchema, TFallback>
187
- >
188
-
189
- // Overload for when explicit type is provided with schema - explicit type takes precedence
172
+ ): Collection<InferSchemaOutput<T>, TKey, TUtils, T, InferSchemaInput<T>>
173
+
174
+ // Overload for when no schema is provided
175
+ // the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config
190
176
  export function createCollection<
191
- TExplicit extends object,
177
+ T extends object,
192
178
  TKey extends string | number = string | number,
193
179
  TUtils extends UtilsRecord = {},
194
- TSchema extends StandardSchemaV1 = StandardSchemaV1,
195
- TFallback extends object = Record<string, unknown>,
196
180
  >(
197
- options: CollectionConfig<
198
- ResolveType<TExplicit, TSchema, TFallback>,
199
- TKey,
200
- TSchema,
201
- ResolveInsertInput<TExplicit, TSchema, TFallback>
202
- > & {
203
- schema: TSchema
181
+ options: CollectionConfig<T, TKey, never> & {
182
+ schema?: never // prohibit schema if an explicit type is provided
204
183
  utils?: TUtils
205
184
  }
206
- ): Collection<
207
- ResolveType<TExplicit, TSchema, TFallback>,
208
- TKey,
209
- TUtils,
210
- TSchema,
211
- ResolveInsertInput<TExplicit, TSchema, TFallback>
212
- >
213
-
214
- // Overload for when explicit type is provided or no schema
215
- export function createCollection<
216
- TExplicit = unknown,
217
- TKey extends string | number = string | number,
218
- TUtils extends UtilsRecord = {},
219
- TSchema extends StandardSchemaV1 = StandardSchemaV1,
220
- TFallback extends object = Record<string, unknown>,
221
- >(
222
- options: CollectionConfig<
223
- ResolveType<TExplicit, TSchema, TFallback>,
224
- TKey,
225
- TSchema,
226
- ResolveInsertInput<TExplicit, TSchema, TFallback>
227
- > & { utils?: TUtils }
228
- ): Collection<
229
- ResolveType<TExplicit, TSchema, TFallback>,
230
- TKey,
231
- TUtils,
232
- TSchema,
233
- ResolveInsertInput<TExplicit, TSchema, TFallback>
234
- >
185
+ ): Collection<T, TKey, TUtils, never, T>
235
186
 
236
187
  // Implementation
237
- export function createCollection<
238
- TExplicit = unknown,
239
- TKey extends string | number = string | number,
240
- TUtils extends UtilsRecord = {},
241
- TSchema extends StandardSchemaV1 = StandardSchemaV1,
242
- TFallback extends object = Record<string, unknown>,
243
- >(
244
- options: CollectionConfig<
245
- ResolveType<TExplicit, TSchema, TFallback>,
246
- TKey,
247
- TSchema,
248
- ResolveInsertInput<TExplicit, TSchema, TFallback>
249
- > & { utils?: TUtils }
250
- ): Collection<
251
- ResolveType<TExplicit, TSchema, TFallback>,
252
- TKey,
253
- TUtils,
254
- TSchema,
255
- ResolveInsertInput<TExplicit, TSchema, TFallback>
256
- > {
257
- const collection = new CollectionImpl<
258
- ResolveType<TExplicit, TSchema, TFallback>,
259
- TKey,
260
- TUtils,
261
- TSchema,
262
- ResolveInsertInput<TExplicit, TSchema, TFallback>
263
- >(options)
188
+ export function createCollection(
189
+ options: CollectionConfig<any, string | number, any> & {
190
+ schema?: StandardSchemaV1
191
+ utils?: UtilsRecord
192
+ }
193
+ ): Collection<any, string | number, UtilsRecord, any, any> {
194
+ const collection = new CollectionImpl<any, string | number, any, any, any>(
195
+ options
196
+ )
264
197
 
265
198
  // Copy utils to both top level and .utils namespace
266
199
  if (options.utils) {
267
200
  collection.utils = { ...options.utils }
268
201
  } else {
269
- collection.utils = {} as TUtils
202
+ collection.utils = {}
270
203
  }
271
204
 
272
- return collection as Collection<
273
- ResolveType<TExplicit, TSchema, TFallback>,
274
- TKey,
275
- TUtils,
276
- TSchema,
277
- ResolveInsertInput<TExplicit, TSchema, TFallback>
278
- >
205
+ return collection
279
206
  }
280
207
 
281
208
  export class CollectionImpl<
282
- T extends object = Record<string, unknown>,
209
+ TOutput extends object = Record<string, unknown>,
283
210
  TKey extends string | number = string | number,
284
211
  TUtils extends UtilsRecord = {},
285
212
  TSchema extends StandardSchemaV1 = StandardSchemaV1,
286
- TInsertInput extends object = T,
213
+ TInput extends object = TOutput,
287
214
  > {
288
- public config: CollectionConfig<T, TKey, TSchema, TInsertInput>
215
+ public config: CollectionConfig<TOutput, TKey, TSchema>
289
216
 
290
217
  // Core state - make public for testing
291
218
  public transactions: SortedMap<string, Transaction<any>>
292
- public pendingSyncedTransactions: Array<PendingSyncedTransaction<T>> = []
293
- public syncedData: Map<TKey, T> | SortedMap<TKey, T>
219
+ public pendingSyncedTransactions: Array<PendingSyncedTransaction<TOutput>> =
220
+ []
221
+ public syncedData: Map<TKey, TOutput> | SortedMap<TKey, TOutput>
294
222
  public syncedMetadata = new Map<TKey, unknown>()
295
223
 
296
224
  // Optimistic state tracking - make public for testing
297
- public optimisticUpserts = new Map<TKey, T>()
225
+ public optimisticUpserts = new Map<TKey, TOutput>()
298
226
  public optimisticDeletes = new Set<TKey>()
299
227
 
300
228
  // Cached size for performance
@@ -307,8 +235,11 @@ export class CollectionImpl<
307
235
  private indexCounter = 0
308
236
 
309
237
  // Event system
310
- private changeListeners = new Set<ChangeListener<T, TKey>>()
311
- private changeKeyListeners = new Map<TKey, Set<ChangeListener<T, TKey>>>()
238
+ private changeListeners = new Set<ChangeListener<TOutput, TKey>>()
239
+ private changeKeyListeners = new Map<
240
+ TKey,
241
+ Set<ChangeListener<TOutput, TKey>>
242
+ >()
312
243
 
313
244
  // Utilities namespace
314
245
  // This is populated by createCollection
@@ -316,7 +247,7 @@ export class CollectionImpl<
316
247
 
317
248
  // State used for computing the change events
318
249
  private syncedKeys = new Set<TKey>()
319
- private preSyncVisibleState = new Map<TKey, T>()
250
+ private preSyncVisibleState = new Map<TKey, TOutput>()
320
251
  private recentlySyncedKeys = new Set<TKey>()
321
252
  private hasReceivedFirstCommit = false
322
253
  private isCommittingSyncTransactions = false
@@ -326,7 +257,7 @@ export class CollectionImpl<
326
257
  private hasBeenReady = false
327
258
 
328
259
  // Event batching for preventing duplicate emissions during transaction flows
329
- private batchedEvents: Array<ChangeMessage<T, TKey>> = []
260
+ private batchedEvents: Array<ChangeMessage<TOutput, TKey>> = []
330
261
  private shouldBatchEvents = false
331
262
 
332
263
  // Lifecycle management
@@ -481,7 +412,7 @@ export class CollectionImpl<
481
412
  * @param config - Configuration object for the collection
482
413
  * @throws Error if sync config is missing
483
414
  */
484
- constructor(config: CollectionConfig<T, TKey, TSchema, TInsertInput>) {
415
+ constructor(config: CollectionConfig<TOutput, TKey, TSchema>) {
485
416
  // eslint-disable-next-line
486
417
  if (!config) {
487
418
  throw new CollectionRequiresConfigError()
@@ -509,9 +440,9 @@ export class CollectionImpl<
509
440
 
510
441
  // Set up data storage with optional comparison function
511
442
  if (this.config.compare) {
512
- this.syncedData = new SortedMap<TKey, T>(this.config.compare)
443
+ this.syncedData = new SortedMap<TKey, TOutput>(this.config.compare)
513
444
  } else {
514
- this.syncedData = new Map<TKey, T>()
445
+ this.syncedData = new Map<TKey, TOutput>()
515
446
  }
516
447
 
517
448
  // Only start sync immediately if explicitly enabled
@@ -549,7 +480,7 @@ export class CollectionImpl<
549
480
  deletedKeys: new Set(),
550
481
  })
551
482
  },
552
- write: (messageWithoutKey: Omit<ChangeMessage<T>, `key`>) => {
483
+ write: (messageWithoutKey: Omit<ChangeMessage<TOutput>, `key`>) => {
553
484
  const pendingTransaction =
554
485
  this.pendingSyncedTransactions[
555
486
  this.pendingSyncedTransactions.length - 1
@@ -578,7 +509,7 @@ export class CollectionImpl<
578
509
  }
579
510
  }
580
511
 
581
- const message: ChangeMessage<T> = {
512
+ const message: ChangeMessage<TOutput> = {
582
513
  ...messageWithoutKey,
583
514
  key,
584
515
  }
@@ -831,7 +762,10 @@ export class CollectionImpl<
831
762
  switch (mutation.type) {
832
763
  case `insert`:
833
764
  case `update`:
834
- this.optimisticUpserts.set(mutation.key, mutation.modified as T)
765
+ this.optimisticUpserts.set(
766
+ mutation.key,
767
+ mutation.modified as TOutput
768
+ )
835
769
  this.optimisticDeletes.delete(mutation.key)
836
770
  break
837
771
  case `delete`:
@@ -847,7 +781,7 @@ export class CollectionImpl<
847
781
  this._size = this.calculateSize()
848
782
 
849
783
  // Collect events for changes
850
- const events: Array<ChangeMessage<T, TKey>> = []
784
+ const events: Array<ChangeMessage<TOutput, TKey>> = []
851
785
  this.collectOptimisticChanges(previousState, previousDeletes, events)
852
786
 
853
787
  // Filter out events for recently synced keys to prevent duplicates
@@ -934,9 +868,9 @@ export class CollectionImpl<
934
868
  * Collect events for optimistic changes
935
869
  */
936
870
  private collectOptimisticChanges(
937
- previousUpserts: Map<TKey, T>,
871
+ previousUpserts: Map<TKey, TOutput>,
938
872
  previousDeletes: Set<TKey>,
939
- events: Array<ChangeMessage<T, TKey>>
873
+ events: Array<ChangeMessage<TOutput, TKey>>
940
874
  ): void {
941
875
  const allKeys = new Set([
942
876
  ...previousUpserts.keys(),
@@ -977,9 +911,9 @@ export class CollectionImpl<
977
911
  */
978
912
  private getPreviousValue(
979
913
  key: TKey,
980
- previousUpserts: Map<TKey, T>,
914
+ previousUpserts: Map<TKey, TOutput>,
981
915
  previousDeletes: Set<TKey>
982
- ): T | undefined {
916
+ ): TOutput | undefined {
983
917
  if (previousDeletes.has(key)) {
984
918
  return undefined
985
919
  }
@@ -1010,7 +944,7 @@ export class CollectionImpl<
1010
944
  * Emit events either immediately or batch them for later emission
1011
945
  */
1012
946
  private emitEvents(
1013
- changes: Array<ChangeMessage<T, TKey>>,
947
+ changes: Array<ChangeMessage<TOutput, TKey>>,
1014
948
  forceEmit = false
1015
949
  ): void {
1016
950
  // Skip batching for user actions (forceEmit=true) to keep UI responsive
@@ -1040,7 +974,7 @@ export class CollectionImpl<
1040
974
  // Emit to key-specific listeners
1041
975
  if (this.changeKeyListeners.size > 0) {
1042
976
  // Group changes by key, but only for keys that have listeners
1043
- const changesByKey = new Map<TKey, Array<ChangeMessage<T, TKey>>>()
977
+ const changesByKey = new Map<TKey, Array<ChangeMessage<TOutput, TKey>>>()
1044
978
  for (const change of eventsToEmit) {
1045
979
  if (this.changeKeyListeners.has(change.key)) {
1046
980
  if (!changesByKey.has(change.key)) {
@@ -1063,7 +997,7 @@ export class CollectionImpl<
1063
997
  /**
1064
998
  * Get the current value for a key (virtual derived state)
1065
999
  */
1066
- public get(key: TKey): T | undefined {
1000
+ public get(key: TKey): TOutput | undefined {
1067
1001
  // Check if optimistically deleted
1068
1002
  if (this.optimisticDeletes.has(key)) {
1069
1003
  return undefined
@@ -1126,7 +1060,7 @@ export class CollectionImpl<
1126
1060
  /**
1127
1061
  * Get all values (virtual derived state)
1128
1062
  */
1129
- public *values(): IterableIterator<T> {
1063
+ public *values(): IterableIterator<TOutput> {
1130
1064
  for (const key of this.keys()) {
1131
1065
  const value = this.get(key)
1132
1066
  if (value !== undefined) {
@@ -1138,7 +1072,7 @@ export class CollectionImpl<
1138
1072
  /**
1139
1073
  * Get all entries (virtual derived state)
1140
1074
  */
1141
- public *entries(): IterableIterator<[TKey, T]> {
1075
+ public *entries(): IterableIterator<[TKey, TOutput]> {
1142
1076
  for (const key of this.keys()) {
1143
1077
  const value = this.get(key)
1144
1078
  if (value !== undefined) {
@@ -1150,7 +1084,7 @@ export class CollectionImpl<
1150
1084
  /**
1151
1085
  * Get all entries (virtual derived state)
1152
1086
  */
1153
- public *[Symbol.iterator](): IterableIterator<[TKey, T]> {
1087
+ public *[Symbol.iterator](): IterableIterator<[TKey, TOutput]> {
1154
1088
  for (const [key, value] of this.entries()) {
1155
1089
  yield [key, value]
1156
1090
  }
@@ -1160,7 +1094,7 @@ export class CollectionImpl<
1160
1094
  * Execute a callback for each entry in the collection
1161
1095
  */
1162
1096
  public forEach(
1163
- callbackfn: (value: T, key: TKey, index: number) => void
1097
+ callbackfn: (value: TOutput, key: TKey, index: number) => void
1164
1098
  ): void {
1165
1099
  let index = 0
1166
1100
  for (const [key, value] of this.entries()) {
@@ -1172,7 +1106,7 @@ export class CollectionImpl<
1172
1106
  * Create a new array with the results of calling a function for each entry in the collection
1173
1107
  */
1174
1108
  public map<U>(
1175
- callbackfn: (value: T, key: TKey, index: number) => U
1109
+ callbackfn: (value: TOutput, key: TKey, index: number) => U
1176
1110
  ): Array<U> {
1177
1111
  const result: Array<U> = []
1178
1112
  let index = 0
@@ -1215,8 +1149,12 @@ export class CollectionImpl<
1215
1149
  return acc
1216
1150
  },
1217
1151
  {
1218
- committedSyncedTransactions: [] as Array<PendingSyncedTransaction<T>>,
1219
- uncommittedSyncedTransactions: [] as Array<PendingSyncedTransaction<T>>,
1152
+ committedSyncedTransactions: [] as Array<
1153
+ PendingSyncedTransaction<TOutput>
1154
+ >,
1155
+ uncommittedSyncedTransactions: [] as Array<
1156
+ PendingSyncedTransaction<TOutput>
1157
+ >,
1220
1158
  hasTruncateSync: false,
1221
1159
  }
1222
1160
  )
@@ -1238,7 +1176,7 @@ export class CollectionImpl<
1238
1176
  let currentVisibleState = this.preSyncVisibleState
1239
1177
  if (currentVisibleState.size === 0) {
1240
1178
  // No pre-captured state, capture it now for pure sync operations
1241
- currentVisibleState = new Map<TKey, T>()
1179
+ currentVisibleState = new Map<TKey, TOutput>()
1242
1180
  for (const key of changedKeys) {
1243
1181
  const currentValue = this.get(key)
1244
1182
  if (currentValue !== undefined) {
@@ -1247,7 +1185,7 @@ export class CollectionImpl<
1247
1185
  }
1248
1186
  }
1249
1187
 
1250
- const events: Array<ChangeMessage<T, TKey>> = []
1188
+ const events: Array<ChangeMessage<TOutput, TKey>> = []
1251
1189
  const rowUpdateMode = this.config.sync.rowUpdateMode || `partial`
1252
1190
 
1253
1191
  for (const transaction of committedSyncedTransactions) {
@@ -1346,7 +1284,7 @@ export class CollectionImpl<
1346
1284
 
1347
1285
  // Build re-apply sets from ACTIVE optimistic transactions against the new synced base
1348
1286
  // We do not copy maps; we compute intent directly from transactions to avoid drift.
1349
- const reapplyUpserts = new Map<TKey, T>()
1287
+ const reapplyUpserts = new Map<TKey, TOutput>()
1350
1288
  const reapplyDeletes = new Set<TKey>()
1351
1289
 
1352
1290
  for (const tx of this.transactions.values()) {
@@ -1356,14 +1294,14 @@ export class CollectionImpl<
1356
1294
  const key = mutation.key as TKey
1357
1295
  switch (mutation.type) {
1358
1296
  case `insert`:
1359
- reapplyUpserts.set(key, mutation.modified as T)
1297
+ reapplyUpserts.set(key, mutation.modified as TOutput)
1360
1298
  reapplyDeletes.delete(key)
1361
1299
  break
1362
1300
  case `update`: {
1363
1301
  const base = this.syncedData.get(key)
1364
1302
  const next = base
1365
- ? (Object.assign({}, base, mutation.changes) as T)
1366
- : (mutation.modified as T)
1303
+ ? (Object.assign({}, base, mutation.changes) as TOutput)
1304
+ : (mutation.modified as TOutput)
1367
1305
  reapplyUpserts.set(key, next)
1368
1306
  reapplyDeletes.delete(key)
1369
1307
  break
@@ -1401,7 +1339,7 @@ export class CollectionImpl<
1401
1339
 
1402
1340
  // Finally, ensure we do NOT insert keys that have an outstanding optimistic delete.
1403
1341
  if (events.length > 0 && reapplyDeletes.size > 0) {
1404
- const filtered: Array<ChangeMessage<T, TKey>> = []
1342
+ const filtered: Array<ChangeMessage<TOutput, TKey>> = []
1405
1343
  for (const evt of events) {
1406
1344
  if (evt.type === `insert` && reapplyDeletes.has(evt.key)) {
1407
1345
  continue
@@ -1435,7 +1373,7 @@ export class CollectionImpl<
1435
1373
  case `update`:
1436
1374
  this.optimisticUpserts.set(
1437
1375
  mutation.key,
1438
- mutation.modified as T
1376
+ mutation.modified as TOutput
1439
1377
  )
1440
1378
  this.optimisticDeletes.delete(mutation.key)
1441
1379
  break
@@ -1566,16 +1504,16 @@ export class CollectionImpl<
1566
1504
  })
1567
1505
  }
1568
1506
 
1569
- private ensureStandardSchema(schema: unknown): StandardSchema<T> {
1507
+ private ensureStandardSchema(schema: unknown): StandardSchema<TOutput> {
1570
1508
  // If the schema already implements the standard-schema interface, return it
1571
1509
  if (schema && `~standard` in (schema as {})) {
1572
- return schema as StandardSchema<T>
1510
+ return schema as StandardSchema<TOutput>
1573
1511
  }
1574
1512
 
1575
1513
  throw new InvalidSchemaError()
1576
1514
  }
1577
1515
 
1578
- public getKeyFromItem(item: T): TKey {
1516
+ public getKeyFromItem(item: TOutput): TKey {
1579
1517
  return this.config.getKey(item)
1580
1518
  }
1581
1519
 
@@ -1618,13 +1556,13 @@ export class CollectionImpl<
1618
1556
  * })
1619
1557
  */
1620
1558
  public createIndex<TResolver extends IndexResolver<TKey> = typeof BTreeIndex>(
1621
- indexCallback: (row: SingleRowRefProxy<T>) => any,
1559
+ indexCallback: (row: SingleRowRefProxy<TOutput>) => any,
1622
1560
  config: IndexOptions<TResolver> = {}
1623
1561
  ): IndexProxy<TKey> {
1624
1562
  this.validateCollectionUsable(`createIndex`)
1625
1563
 
1626
1564
  const indexId = ++this.indexCounter
1627
- const singleRowRefProxy = createSingleRowRefProxy<T>()
1565
+ const singleRowRefProxy = createSingleRowRefProxy<TOutput>()
1628
1566
  const indexExpression = indexCallback(singleRowRefProxy)
1629
1567
  const expression = toExpression(indexExpression)
1630
1568
 
@@ -1720,7 +1658,7 @@ export class CollectionImpl<
1720
1658
  * Updates all indexes when the collection changes
1721
1659
  * @private
1722
1660
  */
1723
- private updateIndexes(changes: Array<ChangeMessage<T, TKey>>): void {
1661
+ private updateIndexes(changes: Array<ChangeMessage<TOutput, TKey>>): void {
1724
1662
  for (const index of this.resolvedIndexes.values()) {
1725
1663
  for (const change of changes) {
1726
1664
  switch (change.type) {
@@ -1746,8 +1684,8 @@ export class CollectionImpl<
1746
1684
  data: unknown,
1747
1685
  type: `insert` | `update`,
1748
1686
  key?: TKey
1749
- ): T | never {
1750
- if (!this.config.schema) return data as T
1687
+ ): TOutput | never {
1688
+ if (!this.config.schema) return data as TOutput
1751
1689
 
1752
1690
  const standardSchema = this.ensureStandardSchema(this.config.schema)
1753
1691
 
@@ -1782,9 +1720,14 @@ export class CollectionImpl<
1782
1720
  throw new SchemaValidationError(type, typedIssues)
1783
1721
  }
1784
1722
 
1785
- // Return the original update data, not the merged data
1786
- // We only used the merged data for validation
1787
- return data as T
1723
+ // Extract only the modified keys from the validated result
1724
+ const validatedMergedData = result.value as TOutput
1725
+ const modifiedKeys = Object.keys(data)
1726
+ const extractedChanges = Object.fromEntries(
1727
+ modifiedKeys.map((k) => [k, validatedMergedData[k as keyof TOutput]])
1728
+ ) as TOutput
1729
+
1730
+ return extractedChanges
1788
1731
  }
1789
1732
  }
1790
1733
 
@@ -1805,7 +1748,7 @@ export class CollectionImpl<
1805
1748
  throw new SchemaValidationError(type, typedIssues)
1806
1749
  }
1807
1750
 
1808
- return result.value as T
1751
+ return result.value as TOutput
1809
1752
  }
1810
1753
 
1811
1754
  /**
@@ -1844,10 +1787,7 @@ export class CollectionImpl<
1844
1787
  * console.log('Insert failed:', error)
1845
1788
  * }
1846
1789
  */
1847
- insert = (
1848
- data: TInsertInput | Array<TInsertInput>,
1849
- config?: InsertConfig
1850
- ) => {
1790
+ insert = (data: TInput | Array<TInput>, config?: InsertConfig) => {
1851
1791
  this.validateCollectionUsable(`insert`)
1852
1792
  const ambientTransaction = getActiveTransaction()
1853
1793
 
@@ -1857,7 +1797,7 @@ export class CollectionImpl<
1857
1797
  }
1858
1798
 
1859
1799
  const items = Array.isArray(data) ? data : [data]
1860
- const mutations: Array<PendingMutation<T>> = []
1800
+ const mutations: Array<PendingMutation<TOutput>> = []
1861
1801
 
1862
1802
  // Create mutations for each item
1863
1803
  items.forEach((item) => {
@@ -1871,7 +1811,7 @@ export class CollectionImpl<
1871
1811
  }
1872
1812
  const globalKey = this.generateGlobalKey(key, item)
1873
1813
 
1874
- const mutation: PendingMutation<T, `insert`> = {
1814
+ const mutation: PendingMutation<TOutput, `insert`> = {
1875
1815
  mutationId: crypto.randomUUID(),
1876
1816
  original: {},
1877
1817
  modified: validatedData,
@@ -1883,7 +1823,7 @@ export class CollectionImpl<
1883
1823
  k,
1884
1824
  validatedData[k as keyof typeof validatedData],
1885
1825
  ])
1886
- ) as TInsertInput,
1826
+ ) as TInput,
1887
1827
  globalKey,
1888
1828
  key,
1889
1829
  metadata: config?.metadata as unknown,
@@ -1909,16 +1849,16 @@ export class CollectionImpl<
1909
1849
  return ambientTransaction
1910
1850
  } else {
1911
1851
  // Create a new transaction with a mutation function that calls the onInsert handler
1912
- const directOpTransaction = createTransaction<T>({
1852
+ const directOpTransaction = createTransaction<TOutput>({
1913
1853
  mutationFn: async (params) => {
1914
1854
  // Call the onInsert handler with the transaction and collection
1915
1855
  return await this.config.onInsert!({
1916
1856
  transaction:
1917
1857
  params.transaction as unknown as TransactionWithMutations<
1918
- TInsertInput,
1858
+ TOutput,
1919
1859
  `insert`
1920
1860
  >,
1921
- collection: this as unknown as Collection<T, TKey, TUtils>,
1861
+ collection: this as unknown as Collection<TOutput, TKey, TUtils>,
1922
1862
  })
1923
1863
  },
1924
1864
  })
@@ -1977,37 +1917,40 @@ export class CollectionImpl<
1977
1917
  */
1978
1918
 
1979
1919
  // Overload 1: Update multiple items with a callback
1980
- update<TItem extends object = T>(
1920
+ update(
1981
1921
  key: Array<TKey | unknown>,
1982
- callback: (drafts: Array<WritableDeep<TItem>>) => void
1922
+ callback: (drafts: Array<WritableDeep<TInput>>) => void
1983
1923
  ): TransactionType
1984
1924
 
1985
1925
  // Overload 2: Update multiple items with config and a callback
1986
- update<TItem extends object = T>(
1926
+ update(
1987
1927
  keys: Array<TKey | unknown>,
1988
1928
  config: OperationConfig,
1989
- callback: (drafts: Array<WritableDeep<TItem>>) => void
1929
+ callback: (drafts: Array<WritableDeep<TInput>>) => void
1990
1930
  ): TransactionType
1991
1931
 
1992
1932
  // Overload 3: Update a single item with a callback
1993
- update<TItem extends object = T>(
1933
+ update(
1994
1934
  id: TKey | unknown,
1995
- callback: (draft: WritableDeep<TItem>) => void
1935
+ callback: (draft: WritableDeep<TInput>) => void
1996
1936
  ): TransactionType
1997
1937
 
1998
1938
  // Overload 4: Update a single item with config and a callback
1999
- update<TItem extends object = T>(
1939
+ update(
2000
1940
  id: TKey | unknown,
2001
1941
  config: OperationConfig,
2002
- callback: (draft: WritableDeep<TItem>) => void
1942
+ callback: (draft: WritableDeep<TInput>) => void
2003
1943
  ): TransactionType
2004
1944
 
2005
- update<TItem extends object = T>(
1945
+ update(
2006
1946
  keys: (TKey | unknown) | Array<TKey | unknown>,
2007
1947
  configOrCallback:
2008
- | ((draft: WritableDeep<TItem> | Array<WritableDeep<TItem>>) => void)
1948
+ | ((draft: WritableDeep<TInput>) => void)
1949
+ | ((drafts: Array<WritableDeep<TInput>>) => void)
2009
1950
  | OperationConfig,
2010
- maybeCallback?: (draft: TItem | Array<TItem>) => void
1951
+ maybeCallback?:
1952
+ | ((draft: WritableDeep<TInput>) => void)
1953
+ | ((drafts: Array<WritableDeep<TInput>>) => void)
2011
1954
  ) {
2012
1955
  if (typeof keys === `undefined`) {
2013
1956
  throw new MissingUpdateArgumentError()
@@ -2042,25 +1985,25 @@ export class CollectionImpl<
2042
1985
  }
2043
1986
 
2044
1987
  return item
2045
- }) as unknown as Array<TItem>
1988
+ }) as unknown as Array<TInput>
2046
1989
 
2047
1990
  let changesArray
2048
1991
  if (isArray) {
2049
1992
  // Use the proxy to track changes for all objects
2050
1993
  changesArray = withArrayChangeTracking(
2051
1994
  currentObjects,
2052
- callback as (draft: Array<TItem>) => void
1995
+ callback as (draft: Array<TInput>) => void
2053
1996
  )
2054
1997
  } else {
2055
1998
  const result = withChangeTracking(
2056
1999
  currentObjects[0]!,
2057
- callback as (draft: TItem) => void
2000
+ callback as (draft: TInput) => void
2058
2001
  )
2059
2002
  changesArray = [result]
2060
2003
  }
2061
2004
 
2062
2005
  // Create mutations for each object that has changes
2063
- const mutations: Array<PendingMutation<T, `update`, this>> = keysArray
2006
+ const mutations: Array<PendingMutation<TOutput, `update`, this>> = keysArray
2064
2007
  .map((key, index) => {
2065
2008
  const itemChanges = changesArray[index] // User-provided changes for this specific item
2066
2009
 
@@ -2069,7 +2012,7 @@ export class CollectionImpl<
2069
2012
  return null
2070
2013
  }
2071
2014
 
2072
- const originalItem = currentObjects[index] as unknown as T
2015
+ const originalItem = currentObjects[index] as unknown as TOutput
2073
2016
  // Validate the user-provided changes for this item
2074
2017
  const validatedUpdatePayload = this.validateData(
2075
2018
  itemChanges,
@@ -2098,7 +2041,16 @@ export class CollectionImpl<
2098
2041
  mutationId: crypto.randomUUID(),
2099
2042
  original: originalItem,
2100
2043
  modified: modifiedItem,
2101
- changes: validatedUpdatePayload as Partial<T>,
2044
+ // Pick the values from modifiedItem based on what's passed in - this is for cases
2045
+ // where a schema has default values or transforms. The modified data has the extra
2046
+ // default or transformed values but for changes, we just want to show the data that
2047
+ // was actually passed in.
2048
+ changes: Object.fromEntries(
2049
+ Object.keys(itemChanges).map((k) => [
2050
+ k,
2051
+ modifiedItem[k as keyof typeof modifiedItem],
2052
+ ])
2053
+ ) as TInput,
2102
2054
  globalKey,
2103
2055
  key,
2104
2056
  metadata: config.metadata as unknown,
@@ -2113,7 +2065,7 @@ export class CollectionImpl<
2113
2065
  collection: this,
2114
2066
  }
2115
2067
  })
2116
- .filter(Boolean) as Array<PendingMutation<T, `update`, this>>
2068
+ .filter(Boolean) as Array<PendingMutation<TOutput, `update`, this>>
2117
2069
 
2118
2070
  // If no changes were made, return an empty transaction early
2119
2071
  if (mutations.length === 0) {
@@ -2140,16 +2092,16 @@ export class CollectionImpl<
2140
2092
  // No need to check for onUpdate handler here as we've already checked at the beginning
2141
2093
 
2142
2094
  // Create a new transaction with a mutation function that calls the onUpdate handler
2143
- const directOpTransaction = createTransaction<T>({
2095
+ const directOpTransaction = createTransaction<TOutput>({
2144
2096
  mutationFn: async (params) => {
2145
2097
  // Call the onUpdate handler with the transaction and collection
2146
2098
  return this.config.onUpdate!({
2147
2099
  transaction:
2148
2100
  params.transaction as unknown as TransactionWithMutations<
2149
- T,
2101
+ TOutput,
2150
2102
  `update`
2151
2103
  >,
2152
- collection: this as unknown as Collection<T, TKey, TUtils>,
2104
+ collection: this as unknown as Collection<TOutput, TKey, TUtils>,
2153
2105
  })
2154
2106
  },
2155
2107
  })
@@ -2215,14 +2167,14 @@ export class CollectionImpl<
2215
2167
  }
2216
2168
 
2217
2169
  const keysArray = Array.isArray(keys) ? keys : [keys]
2218
- const mutations: Array<PendingMutation<T, `delete`, this>> = []
2170
+ const mutations: Array<PendingMutation<TOutput, `delete`, this>> = []
2219
2171
 
2220
2172
  for (const key of keysArray) {
2221
2173
  if (!this.has(key)) {
2222
2174
  throw new DeleteKeyNotFoundError(key)
2223
2175
  }
2224
2176
  const globalKey = this.generateGlobalKey(key, this.get(key)!)
2225
- const mutation: PendingMutation<T, `delete`, this> = {
2177
+ const mutation: PendingMutation<TOutput, `delete`, this> = {
2226
2178
  mutationId: crypto.randomUUID(),
2227
2179
  original: this.get(key)!,
2228
2180
  modified: this.get(key)!,
@@ -2256,17 +2208,17 @@ export class CollectionImpl<
2256
2208
  }
2257
2209
 
2258
2210
  // Create a new transaction with a mutation function that calls the onDelete handler
2259
- const directOpTransaction = createTransaction<T>({
2211
+ const directOpTransaction = createTransaction<TOutput>({
2260
2212
  autoCommit: true,
2261
2213
  mutationFn: async (params) => {
2262
2214
  // Call the onDelete handler with the transaction and collection
2263
2215
  return this.config.onDelete!({
2264
2216
  transaction:
2265
2217
  params.transaction as unknown as TransactionWithMutations<
2266
- T,
2218
+ TOutput,
2267
2219
  `delete`
2268
2220
  >,
2269
- collection: this as unknown as Collection<T, TKey, TUtils>,
2221
+ collection: this as unknown as Collection<TOutput, TKey, TUtils>,
2270
2222
  })
2271
2223
  },
2272
2224
  })
@@ -2299,7 +2251,7 @@ export class CollectionImpl<
2299
2251
  * }
2300
2252
  */
2301
2253
  get state() {
2302
- const result = new Map<TKey, T>()
2254
+ const result = new Map<TKey, TOutput>()
2303
2255
  for (const [key, value] of this.entries()) {
2304
2256
  result.set(key, value)
2305
2257
  }
@@ -2312,14 +2264,14 @@ export class CollectionImpl<
2312
2264
  *
2313
2265
  * @returns Promise that resolves to a Map containing all items in the collection
2314
2266
  */
2315
- stateWhenReady(): Promise<Map<TKey, T>> {
2267
+ stateWhenReady(): Promise<Map<TKey, TOutput>> {
2316
2268
  // If we already have data or collection is ready, resolve immediately
2317
2269
  if (this.size > 0 || this.isReady()) {
2318
2270
  return Promise.resolve(this.state)
2319
2271
  }
2320
2272
 
2321
2273
  // Otherwise, wait for the collection to be ready
2322
- return new Promise<Map<TKey, T>>((resolve) => {
2274
+ return new Promise<Map<TKey, TOutput>>((resolve) => {
2323
2275
  this.onFirstReady(() => {
2324
2276
  resolve(this.state)
2325
2277
  })
@@ -2341,14 +2293,14 @@ export class CollectionImpl<
2341
2293
  *
2342
2294
  * @returns Promise that resolves to an Array containing all items in the collection
2343
2295
  */
2344
- toArrayWhenReady(): Promise<Array<T>> {
2296
+ toArrayWhenReady(): Promise<Array<TOutput>> {
2345
2297
  // If we already have data or collection is ready, resolve immediately
2346
2298
  if (this.size > 0 || this.isReady()) {
2347
2299
  return Promise.resolve(this.toArray)
2348
2300
  }
2349
2301
 
2350
2302
  // Otherwise, wait for the collection to be ready
2351
- return new Promise<Array<T>>((resolve) => {
2303
+ return new Promise<Array<TOutput>>((resolve) => {
2352
2304
  this.onFirstReady(() => {
2353
2305
  resolve(this.toArray)
2354
2306
  })
@@ -2374,8 +2326,8 @@ export class CollectionImpl<
2374
2326
  * })
2375
2327
  */
2376
2328
  public currentStateAsChanges(
2377
- options: CurrentStateAsChangesOptions<T> = {}
2378
- ): Array<ChangeMessage<T>> {
2329
+ options: CurrentStateAsChangesOptions<TOutput> = {}
2330
+ ): Array<ChangeMessage<TOutput>> {
2379
2331
  return currentStateAsChanges(this, options)
2380
2332
  }
2381
2333
 
@@ -2419,8 +2371,8 @@ export class CollectionImpl<
2419
2371
  * })
2420
2372
  */
2421
2373
  public subscribeChanges(
2422
- callback: (changes: Array<ChangeMessage<T>>) => void,
2423
- options: SubscribeChangesOptions<T> = {}
2374
+ callback: (changes: Array<ChangeMessage<TOutput>>) => void,
2375
+ options: SubscribeChangesOptions<TOutput> = {}
2424
2376
  ): () => void {
2425
2377
  // Start sync and track subscriber
2426
2378
  this.addSubscriber()
@@ -2459,7 +2411,7 @@ export class CollectionImpl<
2459
2411
  */
2460
2412
  public subscribeChangesKey(
2461
2413
  key: TKey,
2462
- listener: ChangeListener<T, TKey>,
2414
+ listener: ChangeListener<TOutput, TKey>,
2463
2415
  { includeInitialState = false }: { includeInitialState?: boolean } = {}
2464
2416
  ): () => void {
2465
2417
  // Start sync and track subscriber