@tanstack/db 0.0.22 → 0.0.24

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 (64) hide show
  1. package/dist/cjs/collection.cjs +14 -6
  2. package/dist/cjs/collection.cjs.map +1 -1
  3. package/dist/cjs/collection.d.cts +10 -9
  4. package/dist/cjs/local-storage.cjs +1 -1
  5. package/dist/cjs/local-storage.cjs.map +1 -1
  6. package/dist/cjs/proxy.cjs +21 -0
  7. package/dist/cjs/proxy.cjs.map +1 -1
  8. package/dist/cjs/query/builder/index.cjs +72 -0
  9. package/dist/cjs/query/builder/index.cjs.map +1 -1
  10. package/dist/cjs/query/builder/index.d.cts +64 -0
  11. package/dist/cjs/query/compiler/index.cjs +44 -8
  12. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  13. package/dist/cjs/query/compiler/index.d.cts +4 -7
  14. package/dist/cjs/query/compiler/joins.cjs +14 -6
  15. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  16. package/dist/cjs/query/compiler/joins.d.cts +4 -8
  17. package/dist/cjs/query/compiler/types.d.cts +10 -0
  18. package/dist/cjs/query/optimizer.cjs +283 -0
  19. package/dist/cjs/query/optimizer.cjs.map +1 -0
  20. package/dist/cjs/query/optimizer.d.cts +42 -0
  21. package/dist/cjs/transactions.cjs.map +1 -1
  22. package/dist/cjs/transactions.d.cts +5 -5
  23. package/dist/cjs/types.d.cts +35 -10
  24. package/dist/cjs/utils.cjs +42 -0
  25. package/dist/cjs/utils.cjs.map +1 -0
  26. package/dist/cjs/utils.d.cts +18 -0
  27. package/dist/esm/collection.d.ts +10 -9
  28. package/dist/esm/collection.js +14 -6
  29. package/dist/esm/collection.js.map +1 -1
  30. package/dist/esm/local-storage.js +1 -1
  31. package/dist/esm/local-storage.js.map +1 -1
  32. package/dist/esm/proxy.js +21 -0
  33. package/dist/esm/proxy.js.map +1 -1
  34. package/dist/esm/query/builder/index.d.ts +64 -0
  35. package/dist/esm/query/builder/index.js +72 -0
  36. package/dist/esm/query/builder/index.js.map +1 -1
  37. package/dist/esm/query/compiler/index.d.ts +4 -7
  38. package/dist/esm/query/compiler/index.js +44 -8
  39. package/dist/esm/query/compiler/index.js.map +1 -1
  40. package/dist/esm/query/compiler/joins.d.ts +4 -8
  41. package/dist/esm/query/compiler/joins.js +14 -6
  42. package/dist/esm/query/compiler/joins.js.map +1 -1
  43. package/dist/esm/query/compiler/types.d.ts +10 -0
  44. package/dist/esm/query/optimizer.d.ts +42 -0
  45. package/dist/esm/query/optimizer.js +283 -0
  46. package/dist/esm/query/optimizer.js.map +1 -0
  47. package/dist/esm/transactions.d.ts +5 -5
  48. package/dist/esm/transactions.js.map +1 -1
  49. package/dist/esm/types.d.ts +35 -10
  50. package/dist/esm/utils.d.ts +18 -0
  51. package/dist/esm/utils.js +42 -0
  52. package/dist/esm/utils.js.map +1 -0
  53. package/package.json +1 -1
  54. package/src/collection.ts +62 -21
  55. package/src/local-storage.ts +2 -2
  56. package/src/proxy.ts +24 -0
  57. package/src/query/builder/index.ts +104 -0
  58. package/src/query/compiler/index.ts +85 -18
  59. package/src/query/compiler/joins.ts +21 -13
  60. package/src/query/compiler/types.ts +12 -0
  61. package/src/query/optimizer.ts +738 -0
  62. package/src/transactions.ts +8 -12
  63. package/src/types.ts +69 -14
  64. package/src/utils.ts +86 -0
@@ -2,7 +2,6 @@ import { createDeferred } from "./deferred"
2
2
  import type { Deferred } from "./deferred"
3
3
  import type {
4
4
  MutationFn,
5
- OperationType,
6
5
  PendingMutation,
7
6
  TransactionConfig,
8
7
  TransactionState,
@@ -66,10 +65,10 @@ let sequenceNumber = 0
66
65
  * // Commit later
67
66
  * await tx.commit()
68
67
  */
69
- export function createTransaction<
70
- TData extends object = Record<string, unknown>,
71
- >(config: TransactionConfig<TData>): Transaction<TData> {
72
- const newTransaction = new Transaction<TData>(config)
68
+ export function createTransaction<T extends object = Record<string, unknown>>(
69
+ config: TransactionConfig<T>
70
+ ): Transaction<T> {
71
+ const newTransaction = new Transaction<T>(config)
73
72
  transactions.push(newTransaction)
74
73
  return newTransaction
75
74
  }
@@ -108,15 +107,12 @@ function removeFromPendingList(tx: Transaction<any>) {
108
107
  }
109
108
  }
110
109
 
111
- class Transaction<
112
- T extends object = Record<string, unknown>,
113
- TOperation extends OperationType = OperationType,
114
- > {
110
+ class Transaction<T extends object = Record<string, unknown>> {
115
111
  public id: string
116
112
  public state: TransactionState
117
113
  public mutationFn: MutationFn<T>
118
- public mutations: Array<PendingMutation<T, TOperation>>
119
- public isPersisted: Deferred<Transaction<T, TOperation>>
114
+ public mutations: Array<PendingMutation<T>>
115
+ public isPersisted: Deferred<Transaction<T>>
120
116
  public autoCommit: boolean
121
117
  public createdAt: Date
122
118
  public sequenceNumber: number
@@ -134,7 +130,7 @@ class Transaction<
134
130
  this.mutationFn = config.mutationFn
135
131
  this.state = `pending`
136
132
  this.mutations = []
137
- this.isPersisted = createDeferred<Transaction<T, TOperation>>()
133
+ this.isPersisted = createDeferred<Transaction<T>>()
138
134
  this.autoCommit = config.autoCommit ?? true
139
135
  this.createdAt = new Date()
140
136
  this.sequenceNumber = sequenceNumber++
package/src/types.ts CHANGED
@@ -14,6 +14,40 @@ export type InferSchemaOutput<T> = T extends StandardSchemaV1
14
14
  : Record<string, unknown>
15
15
  : Record<string, unknown>
16
16
 
17
+ /**
18
+ * Helper type to extract the input type from a standard schema
19
+ *
20
+ * @internal This is used for collection insert type inference
21
+ */
22
+ export type InferSchemaInput<T> = T extends StandardSchemaV1
23
+ ? StandardSchemaV1.InferInput<T> extends object
24
+ ? StandardSchemaV1.InferInput<T>
25
+ : Record<string, unknown>
26
+ : Record<string, unknown>
27
+
28
+ /**
29
+ * Helper type to determine the insert input type
30
+ * This takes the raw generics (TExplicit, TSchema, TFallback) instead of the resolved T.
31
+ *
32
+ * Priority:
33
+ * 1. Explicit generic TExplicit (if not 'unknown')
34
+ * 2. Schema input type (if schema provided)
35
+ * 3. Fallback type TFallback
36
+ *
37
+ * @internal This is used for collection insert type inference
38
+ */
39
+ export type ResolveInsertInput<
40
+ TExplicit = unknown,
41
+ TSchema extends StandardSchemaV1 = never,
42
+ TFallback extends object = Record<string, unknown>,
43
+ > = unknown extends TExplicit
44
+ ? [TSchema] extends [never]
45
+ ? TFallback
46
+ : InferSchemaInput<TSchema>
47
+ : TExplicit extends object
48
+ ? TExplicit
49
+ : Record<string, unknown>
50
+
17
51
  /**
18
52
  * Helper type to determine the final type based on priority:
19
53
  * 1. Explicit generic TExplicit (if not 'unknown')
@@ -48,6 +82,17 @@ export type Fn = (...args: Array<any>) => any
48
82
  */
49
83
  export type UtilsRecord = Record<string, Fn>
50
84
 
85
+ /**
86
+ *
87
+ * @remarks `update` and `insert` are both represented as `Partial<T>`, but changes for `insert` could me made more precise by inferring the schema input type. In practice, this has almost 0 real world impact so it's not worth the added type complexity.
88
+ *
89
+ * @see https://github.com/TanStack/db/pull/209#issuecomment-3053001206
90
+ */
91
+ export type ResolveTransactionChanges<
92
+ T extends object = Record<string, unknown>,
93
+ TOperation extends OperationType = OperationType,
94
+ > = TOperation extends `delete` ? T : Partial<T>
95
+
51
96
  /**
52
97
  * Represents a pending mutation within a transaction
53
98
  * Contains information about the original and modified data, as well as metadata
@@ -55,25 +100,32 @@ export type UtilsRecord = Record<string, Fn>
55
100
  export interface PendingMutation<
56
101
  T extends object = Record<string, unknown>,
57
102
  TOperation extends OperationType = OperationType,
103
+ TCollection extends Collection<T, any, any, any, any> = Collection<
104
+ T,
105
+ any,
106
+ any,
107
+ any,
108
+ any
109
+ >,
58
110
  > {
59
111
  mutationId: string
112
+ // The state of the object before the mutation.
60
113
  original: TOperation extends `insert` ? {} : T
114
+ // The result state of the object after all mutations.
61
115
  modified: T
62
- changes: TOperation extends `insert`
63
- ? T
64
- : TOperation extends `delete`
65
- ? T
66
- : Partial<T>
116
+ // Only the actual changes to the object by the mutation.
117
+ changes: ResolveTransactionChanges<T, TOperation>
67
118
  globalKey: string
119
+
68
120
  key: any
69
- type: OperationType
121
+ type: TOperation
70
122
  metadata: unknown
71
123
  syncMetadata: Record<string, unknown>
72
124
  /** Whether this mutation should be applied optimistically (defaults to true) */
73
125
  optimistic: boolean
74
126
  createdAt: Date
75
127
  updatedAt: Date
76
- collection: Collection<T, any, any>
128
+ collection: TCollection
77
129
  }
78
130
 
79
131
  /**
@@ -99,7 +151,7 @@ export type NonEmptyArray<T> = [T, ...Array<T>]
99
151
  export type TransactionWithMutations<
100
152
  T extends object = Record<string, unknown>,
101
153
  TOperation extends OperationType = OperationType,
102
- > = Transaction<T, TOperation> & {
154
+ > = Transaction<T> & {
103
155
  mutations: NonEmptyArray<PendingMutation<T, TOperation>>
104
156
  }
105
157
 
@@ -116,12 +168,14 @@ export interface TransactionConfig<T extends object = Record<string, unknown>> {
116
168
  /**
117
169
  * Options for the createOptimisticAction helper
118
170
  */
119
- export interface CreateOptimisticActionsOptions<TVars = unknown>
120
- extends Omit<TransactionConfig, `mutationFn`> {
171
+ export interface CreateOptimisticActionsOptions<
172
+ TVars = unknown,
173
+ T extends object = Record<string, unknown>,
174
+ > extends Omit<TransactionConfig<T>, `mutationFn`> {
121
175
  /** Function to apply optimistic updates locally before the mutation completes */
122
176
  onMutate: (vars: TVars) => void
123
177
  /** Function to execute the mutation on the server */
124
- mutationFn: (vars: TVars, params: MutationFnParams) => Promise<any>
178
+ mutationFn: (vars: TVars, params: MutationFnParams<T>) => Promise<any>
125
179
  }
126
180
 
127
181
  export type { Transaction }
@@ -145,7 +199,7 @@ export interface SyncConfig<
145
199
  TKey extends string | number = string | number,
146
200
  > {
147
201
  sync: (params: {
148
- collection: Collection<T, TKey>
202
+ collection: Collection<T, TKey, any, any, any>
149
203
  begin: () => void
150
204
  write: (message: Omit<ChangeMessage<T>, `key`>) => void
151
205
  commit: () => void
@@ -232,7 +286,6 @@ export type InsertMutationFnParams<
232
286
  transaction: TransactionWithMutations<T, `insert`>
233
287
  collection: Collection<T, TKey, TUtils>
234
288
  }
235
-
236
289
  export type DeleteMutationFnParams<
237
290
  T extends object = Record<string, unknown>,
238
291
  TKey extends string | number = string | number,
@@ -293,6 +346,7 @@ export interface CollectionConfig<
293
346
  T extends object = Record<string, unknown>,
294
347
  TKey extends string | number = string | number,
295
348
  TSchema extends StandardSchemaV1 = StandardSchemaV1,
349
+ TInsertInput extends object = T,
296
350
  > {
297
351
  // If an id isn't passed in, a UUID will be
298
352
  // generated for it.
@@ -371,7 +425,8 @@ export interface CollectionConfig<
371
425
  * })
372
426
  * }
373
427
  */
374
- onInsert?: InsertMutationFn<T, TKey>
428
+ onInsert?: InsertMutationFn<TInsertInput, TKey>
429
+
375
430
  /**
376
431
  * Optional asynchronous handler function called before an update operation
377
432
  * @param params Object containing transaction and collection information
package/src/utils.ts ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Generic utility functions
3
+ */
4
+
5
+ /**
6
+ * Deep equality function that compares two values recursively
7
+ *
8
+ * @param a - First value to compare
9
+ * @param b - Second value to compare
10
+ * @returns True if the values are deeply equal, false otherwise
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * deepEquals({ a: 1, b: 2 }, { b: 2, a: 1 }) // true (property order doesn't matter)
15
+ * deepEquals([1, { x: 2 }], [1, { x: 2 }]) // true
16
+ * deepEquals({ a: 1 }, { a: 2 }) // false
17
+ * ```
18
+ */
19
+ export function deepEquals(a: any, b: any): boolean {
20
+ return deepEqualsInternal(a, b, new Map())
21
+ }
22
+
23
+ /**
24
+ * Internal implementation with cycle detection to prevent infinite recursion
25
+ */
26
+ function deepEqualsInternal(
27
+ a: any,
28
+ b: any,
29
+ visited: Map<object, object>
30
+ ): boolean {
31
+ // Handle strict equality (primitives, same reference)
32
+ if (a === b) return true
33
+
34
+ // Handle null/undefined
35
+ if (a == null || b == null) return false
36
+
37
+ // Handle different types
38
+ if (typeof a !== typeof b) return false
39
+
40
+ // Handle arrays
41
+ if (Array.isArray(a)) {
42
+ if (!Array.isArray(b) || a.length !== b.length) return false
43
+
44
+ // Check for circular references
45
+ if (visited.has(a)) {
46
+ return visited.get(a) === b
47
+ }
48
+ visited.set(a, b)
49
+
50
+ const result = a.every((item, index) =>
51
+ deepEqualsInternal(item, b[index], visited)
52
+ )
53
+ visited.delete(a)
54
+ return result
55
+ }
56
+
57
+ // Handle objects
58
+ if (typeof a === `object`) {
59
+ // Check for circular references
60
+ if (visited.has(a)) {
61
+ return visited.get(a) === b
62
+ }
63
+ visited.set(a, b)
64
+
65
+ // Get all keys from both objects
66
+ const keysA = Object.keys(a)
67
+ const keysB = Object.keys(b)
68
+
69
+ // Check if they have the same number of keys
70
+ if (keysA.length !== keysB.length) {
71
+ visited.delete(a)
72
+ return false
73
+ }
74
+
75
+ // Check if all keys exist in both objects and their values are equal
76
+ const result = keysA.every(
77
+ (key) => key in b && deepEqualsInternal(a[key], b[key], visited)
78
+ )
79
+
80
+ visited.delete(a)
81
+ return result
82
+ }
83
+
84
+ // For primitives that aren't strictly equal
85
+ return false
86
+ }