@tanstack/db 0.0.5 → 0.0.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.
Files changed (55) hide show
  1. package/dist/cjs/collection.cjs +86 -27
  2. package/dist/cjs/collection.cjs.map +1 -1
  3. package/dist/cjs/collection.d.cts +30 -13
  4. package/dist/cjs/index.cjs +1 -1
  5. package/dist/cjs/index.d.cts +1 -1
  6. package/dist/cjs/query/compiled-query.cjs +1 -1
  7. package/dist/cjs/query/compiled-query.cjs.map +1 -1
  8. package/dist/cjs/query/compiled-query.d.cts +1 -1
  9. package/dist/cjs/query/evaluators.cjs +15 -0
  10. package/dist/cjs/query/evaluators.cjs.map +1 -1
  11. package/dist/cjs/query/evaluators.d.cts +5 -1
  12. package/dist/cjs/query/pipeline-compiler.cjs +2 -2
  13. package/dist/cjs/query/pipeline-compiler.cjs.map +1 -1
  14. package/dist/cjs/query/query-builder.cjs +22 -17
  15. package/dist/cjs/query/query-builder.cjs.map +1 -1
  16. package/dist/cjs/query/query-builder.d.cts +12 -2
  17. package/dist/cjs/query/schema.d.cts +11 -5
  18. package/dist/cjs/query/select.cjs +12 -0
  19. package/dist/cjs/query/select.cjs.map +1 -1
  20. package/dist/cjs/transactions.cjs +3 -1
  21. package/dist/cjs/transactions.cjs.map +1 -1
  22. package/dist/cjs/types.d.cts +33 -0
  23. package/dist/esm/collection.d.ts +30 -13
  24. package/dist/esm/collection.js +87 -28
  25. package/dist/esm/collection.js.map +1 -1
  26. package/dist/esm/index.d.ts +1 -1
  27. package/dist/esm/index.js +2 -2
  28. package/dist/esm/query/compiled-query.d.ts +1 -1
  29. package/dist/esm/query/compiled-query.js +2 -2
  30. package/dist/esm/query/compiled-query.js.map +1 -1
  31. package/dist/esm/query/evaluators.d.ts +5 -1
  32. package/dist/esm/query/evaluators.js +16 -1
  33. package/dist/esm/query/evaluators.js.map +1 -1
  34. package/dist/esm/query/pipeline-compiler.js +3 -3
  35. package/dist/esm/query/pipeline-compiler.js.map +1 -1
  36. package/dist/esm/query/query-builder.d.ts +12 -2
  37. package/dist/esm/query/query-builder.js +22 -17
  38. package/dist/esm/query/query-builder.js.map +1 -1
  39. package/dist/esm/query/schema.d.ts +11 -5
  40. package/dist/esm/query/select.js +12 -0
  41. package/dist/esm/query/select.js.map +1 -1
  42. package/dist/esm/transactions.js +3 -1
  43. package/dist/esm/transactions.js.map +1 -1
  44. package/dist/esm/types.d.ts +33 -0
  45. package/package.json +1 -1
  46. package/src/collection.ts +164 -45
  47. package/src/index.ts +1 -1
  48. package/src/query/compiled-query.ts +4 -3
  49. package/src/query/evaluators.ts +27 -0
  50. package/src/query/pipeline-compiler.ts +3 -3
  51. package/src/query/query-builder.ts +40 -23
  52. package/src/query/schema.ts +17 -5
  53. package/src/query/select.ts +24 -1
  54. package/src/transactions.ts +8 -1
  55. package/src/types.ts +38 -0
@@ -10,6 +10,7 @@ import type {
10
10
  OrderBy,
11
11
  Query,
12
12
  Select,
13
+ WhereCallback,
13
14
  WithQuery,
14
15
  } from "./schema.js"
15
16
  import type {
@@ -197,8 +198,9 @@ export class BaseQueryBuilder<TContext extends Context<Schema>> {
197
198
  /**
198
199
  * Specify what columns to select.
199
200
  * Overwrites any previous select clause.
201
+ * Also supports callback functions that receive the row context and return selected data.
200
202
  *
201
- * @param selects The columns to select
203
+ * @param selects The columns to select (can include callbacks)
202
204
  * @returns A new QueryBuilder with the select clause set
203
205
  */
204
206
  select<TSelects extends Array<Select<TContext>>>(
@@ -296,17 +298,23 @@ export class BaseQueryBuilder<TContext extends Context<Schema>> {
296
298
  */
297
299
  where(condition: Condition<TContext>): QueryBuilder<TContext>
298
300
 
301
+ /**
302
+ * Add a where clause with a callback function.
303
+ */
304
+ where(callback: WhereCallback<TContext>): QueryBuilder<TContext>
305
+
299
306
  /**
300
307
  * Add a where clause to filter the results.
301
308
  * Can be called multiple times to add AND conditions.
309
+ * Also supports callback functions that receive the row context.
302
310
  *
303
- * @param leftOrCondition The left operand or complete condition
311
+ * @param leftOrConditionOrCallback The left operand, complete condition, or callback function
304
312
  * @param operator Optional comparison operator
305
313
  * @param right Optional right operand
306
314
  * @returns A new QueryBuilder with the where clause added
307
315
  */
308
316
  where(
309
- leftOrCondition: any,
317
+ leftOrConditionOrCallback: any,
310
318
  operator?: any,
311
319
  right?: any
312
320
  ): QueryBuilder<TContext> {
@@ -317,22 +325,23 @@ export class BaseQueryBuilder<TContext extends Context<Schema>> {
317
325
 
318
326
  let condition: any
319
327
 
320
- // Determine if this is a complete condition or individual parts
321
- if (operator !== undefined && right !== undefined) {
328
+ // Determine if this is a callback, complete condition, or individual parts
329
+ if (typeof leftOrConditionOrCallback === `function`) {
330
+ // It's a callback function
331
+ condition = leftOrConditionOrCallback
332
+ } else if (operator !== undefined && right !== undefined) {
322
333
  // Create a condition from parts
323
- condition = [leftOrCondition, operator, right]
334
+ condition = [leftOrConditionOrCallback, operator, right]
324
335
  } else {
325
336
  // Use the provided condition directly
326
- condition = leftOrCondition
337
+ condition = leftOrConditionOrCallback
327
338
  }
328
339
 
340
+ // Where is always an array, so initialize or append
329
341
  if (!newBuilder.query.where) {
330
- newBuilder.query.where = condition
342
+ newBuilder.query.where = [condition]
331
343
  } else {
332
- // Create a composite condition with AND
333
- // Use any to bypass type checking issues
334
- const andArray: any = [newBuilder.query.where, `and`, condition]
335
- newBuilder.query.where = andArray
344
+ newBuilder.query.where = [...newBuilder.query.where, condition]
336
345
  }
337
346
 
338
347
  return newBuilder as unknown as QueryBuilder<TContext>
@@ -354,17 +363,24 @@ export class BaseQueryBuilder<TContext extends Context<Schema>> {
354
363
  */
355
364
  having(condition: Condition<TContext>): QueryBuilder<TContext>
356
365
 
366
+ /**
367
+ * Add a having clause with a callback function.
368
+ * For filtering results after they have been grouped.
369
+ */
370
+ having(callback: WhereCallback<TContext>): QueryBuilder<TContext>
371
+
357
372
  /**
358
373
  * Add a having clause to filter the grouped results.
359
374
  * Can be called multiple times to add AND conditions.
375
+ * Also supports callback functions that receive the row context.
360
376
  *
361
- * @param leftOrCondition The left operand or complete condition
377
+ * @param leftOrConditionOrCallback The left operand, complete condition, or callback function
362
378
  * @param operator Optional comparison operator
363
379
  * @param right Optional right operand
364
380
  * @returns A new QueryBuilder with the having clause added
365
381
  */
366
382
  having(
367
- leftOrCondition: any,
383
+ leftOrConditionOrCallback: any,
368
384
  operator?: any,
369
385
  right?: any
370
386
  ): QueryBuilder<TContext> {
@@ -374,22 +390,23 @@ export class BaseQueryBuilder<TContext extends Context<Schema>> {
374
390
 
375
391
  let condition: any
376
392
 
377
- // Determine if this is a complete condition or individual parts
378
- if (operator !== undefined && right !== undefined) {
393
+ // Determine if this is a callback, complete condition, or individual parts
394
+ if (typeof leftOrConditionOrCallback === `function`) {
395
+ // It's a callback function
396
+ condition = leftOrConditionOrCallback
397
+ } else if (operator !== undefined && right !== undefined) {
379
398
  // Create a condition from parts
380
- condition = [leftOrCondition, operator, right]
399
+ condition = [leftOrConditionOrCallback, operator, right]
381
400
  } else {
382
401
  // Use the provided condition directly
383
- condition = leftOrCondition
402
+ condition = leftOrConditionOrCallback
384
403
  }
385
404
 
405
+ // Having is always an array, so initialize or append
386
406
  if (!newBuilder.query.having) {
387
- newBuilder.query.having = condition
407
+ newBuilder.query.having = [condition]
388
408
  } else {
389
- // Create a composite condition with AND
390
- // Use any to bypass type checking issues
391
- const andArray: any = [newBuilder.query.having, `and`, condition]
392
- newBuilder.query.having = andArray
409
+ newBuilder.query.having = [...newBuilder.query.having, condition]
393
410
  }
394
411
 
395
412
  return newBuilder as QueryBuilder<TContext>
@@ -191,6 +191,11 @@ export type Select<TContext extends Context = Context> =
191
191
  | AggregateFunctionCall<TContext>
192
192
  }
193
193
  | WildcardReferenceString<TContext>
194
+ | SelectCallback<TContext>
195
+
196
+ export type SelectCallback<TContext extends Context = Context> = (
197
+ context: TContext extends { schema: infer S } ? S : any
198
+ ) => any
194
199
 
195
200
  export type As<TContext extends Context = Context> = string
196
201
 
@@ -199,14 +204,21 @@ export type From<TContext extends Context = Context> = InputReference<{
199
204
  schema: TContext[`baseSchema`]
200
205
  }>
201
206
 
202
- export type Where<TContext extends Context = Context> = Condition<TContext>
207
+ export type WhereCallback<TContext extends Context = Context> = (
208
+ context: TContext extends { schema: infer S } ? S : any
209
+ ) => boolean
210
+
211
+ export type Where<TContext extends Context = Context> = Array<
212
+ Condition<TContext> | WhereCallback<TContext>
213
+ >
214
+
215
+ // Having is the same implementation as a where clause, its just run after the group by
216
+ export type Having<TContext extends Context = Context> = Where<TContext>
203
217
 
204
218
  export type GroupBy<TContext extends Context = Context> =
205
219
  | PropertyReference<TContext>
206
220
  | Array<PropertyReference<TContext>>
207
221
 
208
- export type Having<TContext extends Context = Context> = Condition<TContext>
209
-
210
222
  export type Limit<TContext extends Context = Context> = number
211
223
 
212
224
  export type Offset<TContext extends Context = Context> = number
@@ -220,9 +232,9 @@ export interface BaseQuery<TContext extends Context = Context> {
220
232
  as?: As<TContext>
221
233
  from: From<TContext>
222
234
  join?: Array<JoinClause<TContext>>
223
- where?: Condition<TContext>
235
+ where?: Where<TContext>
224
236
  groupBy?: GroupBy<TContext>
225
- having?: Condition<TContext>
237
+ having?: Having<TContext>
226
238
  orderBy?: OrderBy<TContext>
227
239
  limit?: Limit<TContext>
228
240
  offset?: Offset<TContext>
@@ -3,7 +3,7 @@ import {
3
3
  evaluateOperandOnNamespacedRow,
4
4
  extractValueFromNamespacedRow,
5
5
  } from "./extractors"
6
- import type { ConditionOperand, Query } from "./schema"
6
+ import type { ConditionOperand, Query, SelectCallback } from "./schema"
7
7
  import type { KeyedStream, NamespacedAndKeyedStream } from "../types"
8
8
 
9
9
  export function processSelect(
@@ -31,6 +31,29 @@ export function processSelect(
31
31
  }
32
32
 
33
33
  for (const item of query.select) {
34
+ // Handle callback functions
35
+ if (typeof item === `function`) {
36
+ const callback = item as SelectCallback
37
+ const callbackResult = callback(namespacedRow)
38
+
39
+ // If the callback returns an object, merge its properties into the result
40
+ if (
41
+ callbackResult &&
42
+ typeof callbackResult === `object` &&
43
+ !Array.isArray(callbackResult)
44
+ ) {
45
+ Object.assign(result, callbackResult)
46
+ } else {
47
+ // If the callback returns a primitive value, we can't merge it
48
+ // This would need a specific key, but since we don't have one, we'll skip it
49
+ // In practice, select callbacks should return objects with keys
50
+ console.warn(
51
+ `SelectCallback returned a non-object value. SelectCallbacks should return objects with key-value pairs.`
52
+ )
53
+ }
54
+ continue
55
+ }
56
+
34
57
  if (typeof item === `string`) {
35
58
  // Handle wildcard select - all columns from all tables
36
59
  if ((item as string) === `@*`) {
@@ -4,6 +4,7 @@ import type {
4
4
  PendingMutation,
5
5
  TransactionConfig,
6
6
  TransactionState,
7
+ TransactionWithMutations,
7
8
  } from "./types"
8
9
 
9
10
  function generateUUID() {
@@ -181,11 +182,17 @@ export class Transaction {
181
182
 
182
183
  if (this.mutations.length === 0) {
183
184
  this.setState(`completed`)
185
+
186
+ return this
184
187
  }
185
188
 
186
189
  // Run mutationFn
187
190
  try {
188
- await this.mutationFn({ transaction: this })
191
+ // At this point we know there's at least one mutation
192
+ // Use type assertion to tell TypeScript about this guarantee
193
+ const transactionWithMutations =
194
+ this as unknown as TransactionWithMutations
195
+ await this.mutationFn({ transaction: transactionWithMutations })
189
196
 
190
197
  this.setState(`completed`)
191
198
  this.touchCollection()
package/src/types.ts CHANGED
@@ -5,6 +5,16 @@ import type { Transaction } from "./transactions"
5
5
 
6
6
  export type TransactionState = `pending` | `persisting` | `completed` | `failed`
7
7
 
8
+ /**
9
+ * Represents a utility function that can be attached to a collection
10
+ */
11
+ export type Fn = (...args: Array<any>) => any
12
+
13
+ /**
14
+ * A record of utility functions that can be attached to a collection
15
+ */
16
+ export type UtilsRecord = Record<string, Fn>
17
+
8
18
  /**
9
19
  * Represents a pending mutation within a transaction
10
20
  * Contains information about the original and modified data, as well as metadata
@@ -32,6 +42,16 @@ export type MutationFnParams = {
32
42
 
33
43
  export type MutationFn = (params: MutationFnParams) => Promise<any>
34
44
 
45
+ /**
46
+ * Utility type for a Transaction with at least one mutation
47
+ * This is used internally by the Transaction.commit method
48
+ */
49
+ export type TransactionWithMutations<
50
+ T extends object = Record<string, unknown>,
51
+ > = Transaction & {
52
+ mutations: [PendingMutation<T>, ...Array<PendingMutation<T>>]
53
+ }
54
+
35
55
  export interface TransactionConfig {
36
56
  /** Unique identifier for the transaction */
37
57
  id?: string
@@ -130,6 +150,24 @@ export interface CollectionConfig<T extends object = Record<string, unknown>> {
130
150
  * getId: (item) => item.uuid
131
151
  */
132
152
  getId: (item: T) => any
153
+ /**
154
+ * Optional asynchronous handler function called before an insert operation
155
+ * @param params Object containing transaction and mutation information
156
+ * @returns Promise resolving to any value
157
+ */
158
+ onInsert?: MutationFn
159
+ /**
160
+ * Optional asynchronous handler function called before an update operation
161
+ * @param params Object containing transaction and mutation information
162
+ * @returns Promise resolving to any value
163
+ */
164
+ onUpdate?: MutationFn
165
+ /**
166
+ * Optional asynchronous handler function called before a delete operation
167
+ * @param params Object containing transaction and mutation information
168
+ * @returns Promise resolving to any value
169
+ */
170
+ onDelete?: MutationFn
133
171
  }
134
172
 
135
173
  export type ChangesPayload<T extends object = Record<string, unknown>> = Array<