@tanstack/db 0.0.4 → 0.0.6
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.cjs +182 -113
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +43 -15
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/proxy.cjs +87 -248
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/proxy.d.cts +5 -5
- package/dist/cjs/query/compiled-query.cjs +23 -14
- package/dist/cjs/query/compiled-query.cjs.map +1 -1
- package/dist/cjs/query/compiled-query.d.cts +3 -1
- package/dist/cjs/query/evaluators.cjs +35 -20
- package/dist/cjs/query/evaluators.cjs.map +1 -1
- package/dist/cjs/query/evaluators.d.cts +8 -3
- package/dist/cjs/query/extractors.cjs +20 -20
- package/dist/cjs/query/extractors.cjs.map +1 -1
- package/dist/cjs/query/extractors.d.cts +3 -3
- package/dist/cjs/query/group-by.cjs +12 -15
- package/dist/cjs/query/group-by.cjs.map +1 -1
- package/dist/cjs/query/group-by.d.cts +7 -7
- package/dist/cjs/query/joins.cjs +41 -55
- package/dist/cjs/query/joins.cjs.map +1 -1
- package/dist/cjs/query/joins.d.cts +3 -3
- package/dist/cjs/query/order-by.cjs +37 -84
- package/dist/cjs/query/order-by.cjs.map +1 -1
- package/dist/cjs/query/order-by.d.cts +2 -2
- package/dist/cjs/query/pipeline-compiler.cjs +13 -18
- package/dist/cjs/query/pipeline-compiler.cjs.map +1 -1
- package/dist/cjs/query/pipeline-compiler.d.cts +2 -1
- package/dist/cjs/query/query-builder.cjs +22 -29
- package/dist/cjs/query/query-builder.cjs.map +1 -1
- package/dist/cjs/query/query-builder.d.cts +16 -10
- package/dist/cjs/query/schema.d.cts +12 -11
- package/dist/cjs/query/select.cjs +47 -24
- package/dist/cjs/query/select.cjs.map +1 -1
- package/dist/cjs/query/select.d.cts +2 -2
- package/dist/cjs/query/types.d.cts +1 -0
- package/dist/cjs/transactions.cjs +20 -9
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +66 -7
- package/dist/esm/collection.d.ts +43 -15
- package/dist/esm/collection.js +183 -114
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/proxy.d.ts +5 -5
- package/dist/esm/proxy.js +87 -248
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/compiled-query.d.ts +3 -1
- package/dist/esm/query/compiled-query.js +23 -14
- package/dist/esm/query/compiled-query.js.map +1 -1
- package/dist/esm/query/evaluators.d.ts +8 -3
- package/dist/esm/query/evaluators.js +36 -21
- package/dist/esm/query/evaluators.js.map +1 -1
- package/dist/esm/query/extractors.d.ts +3 -3
- package/dist/esm/query/extractors.js +20 -20
- package/dist/esm/query/extractors.js.map +1 -1
- package/dist/esm/query/group-by.d.ts +7 -7
- package/dist/esm/query/group-by.js +14 -17
- package/dist/esm/query/group-by.js.map +1 -1
- package/dist/esm/query/joins.d.ts +3 -3
- package/dist/esm/query/joins.js +42 -56
- package/dist/esm/query/joins.js.map +1 -1
- package/dist/esm/query/order-by.d.ts +2 -2
- package/dist/esm/query/order-by.js +39 -86
- package/dist/esm/query/order-by.js.map +1 -1
- package/dist/esm/query/pipeline-compiler.d.ts +2 -1
- package/dist/esm/query/pipeline-compiler.js +14 -19
- package/dist/esm/query/pipeline-compiler.js.map +1 -1
- package/dist/esm/query/query-builder.d.ts +16 -10
- package/dist/esm/query/query-builder.js +22 -29
- package/dist/esm/query/query-builder.js.map +1 -1
- package/dist/esm/query/schema.d.ts +12 -11
- package/dist/esm/query/select.d.ts +2 -2
- package/dist/esm/query/select.js +48 -25
- package/dist/esm/query/select.js.map +1 -1
- package/dist/esm/query/types.d.ts +1 -0
- package/dist/esm/transactions.js +20 -9
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +66 -7
- package/package.json +2 -2
- package/src/collection.ts +286 -146
- package/src/proxy.ts +141 -358
- package/src/query/compiled-query.ts +30 -15
- package/src/query/evaluators.ts +49 -21
- package/src/query/extractors.ts +24 -21
- package/src/query/group-by.ts +24 -22
- package/src/query/joins.ts +88 -75
- package/src/query/order-by.ts +56 -106
- package/src/query/pipeline-compiler.ts +34 -37
- package/src/query/query-builder.ts +49 -46
- package/src/query/schema.ts +18 -15
- package/src/query/select.ts +68 -33
- package/src/query/types.ts +1 -0
- package/src/transactions.ts +30 -14
- package/src/types.ts +76 -7
- package/dist/cjs/query/key-by.cjs +0 -43
- package/dist/cjs/query/key-by.cjs.map +0 -1
- package/dist/cjs/query/key-by.d.cts +0 -3
- package/dist/esm/query/key-by.d.ts +0 -3
- package/dist/esm/query/key-by.js +0 -43
- package/dist/esm/query/key-by.js.map +0 -1
- package/src/query/key-by.ts +0 -61
|
@@ -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
|
|
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
|
-
|
|
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 (
|
|
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 = [
|
|
334
|
+
condition = [leftOrConditionOrCallback, operator, right]
|
|
324
335
|
} else {
|
|
325
336
|
// Use the provided condition directly
|
|
326
|
-
condition =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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 = [
|
|
399
|
+
condition = [leftOrConditionOrCallback, operator, right]
|
|
381
400
|
} else {
|
|
382
401
|
// Use the provided condition directly
|
|
383
|
-
condition =
|
|
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
|
-
|
|
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>
|
|
@@ -438,6 +455,7 @@ export class BaseQueryBuilder<TContext extends Context<Schema>> {
|
|
|
438
455
|
Input
|
|
439
456
|
>
|
|
440
457
|
}
|
|
458
|
+
hasJoin: true
|
|
441
459
|
}
|
|
442
460
|
>
|
|
443
461
|
>
|
|
@@ -474,6 +492,7 @@ export class BaseQueryBuilder<TContext extends Context<Schema>> {
|
|
|
474
492
|
schema: TContext[`schema`] & {
|
|
475
493
|
[K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>
|
|
476
494
|
}
|
|
495
|
+
hasJoin: true
|
|
477
496
|
}
|
|
478
497
|
>
|
|
479
498
|
>
|
|
@@ -513,6 +532,7 @@ export class BaseQueryBuilder<TContext extends Context<Schema>> {
|
|
|
513
532
|
schema: TContext[`schema`] & {
|
|
514
533
|
[K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>
|
|
515
534
|
}
|
|
535
|
+
hasJoin: true
|
|
516
536
|
}
|
|
517
537
|
>
|
|
518
538
|
>
|
|
@@ -754,25 +774,6 @@ export class BaseQueryBuilder<TContext extends Context<Schema>> {
|
|
|
754
774
|
return newBuilder as QueryBuilder<TContext>
|
|
755
775
|
}
|
|
756
776
|
|
|
757
|
-
/**
|
|
758
|
-
* Specify which column(s) to use as keys in the output keyed stream.
|
|
759
|
-
*
|
|
760
|
-
* @param keyBy The column(s) to use as keys
|
|
761
|
-
* @returns A new QueryBuilder with the keyBy clause set
|
|
762
|
-
*/
|
|
763
|
-
keyBy(
|
|
764
|
-
keyBy: PropertyReference<TContext> | Array<PropertyReference<TContext>>
|
|
765
|
-
): QueryBuilder<TContext> {
|
|
766
|
-
// Create a new builder with a copy of the current query
|
|
767
|
-
const newBuilder = new BaseQueryBuilder<TContext>()
|
|
768
|
-
Object.assign(newBuilder.query, this.query)
|
|
769
|
-
|
|
770
|
-
// Set the keyBy clause
|
|
771
|
-
newBuilder.query.keyBy = keyBy
|
|
772
|
-
|
|
773
|
-
return newBuilder as QueryBuilder<TContext>
|
|
774
|
-
}
|
|
775
|
-
|
|
776
777
|
/**
|
|
777
778
|
* Add a groupBy clause to group the results by one or more columns.
|
|
778
779
|
*
|
|
@@ -883,10 +884,12 @@ export function queryBuilder<TBaseSchema extends Schema = {}>() {
|
|
|
883
884
|
|
|
884
885
|
export type ResultsFromContext<TContext extends Context<Schema>> = Flatten<
|
|
885
886
|
TContext[`result`] extends object
|
|
886
|
-
? TContext[`result`]
|
|
887
|
-
: TContext[`
|
|
888
|
-
? TContext[`schema`]
|
|
889
|
-
:
|
|
887
|
+
? TContext[`result`] // If there is a select we will have a result type
|
|
888
|
+
: TContext[`hasJoin`] extends true
|
|
889
|
+
? TContext[`schema`] // If there is a join, the query returns the namespaced schema
|
|
890
|
+
: TContext[`default`] extends keyof TContext[`schema`]
|
|
891
|
+
? TContext[`schema`][TContext[`default`]] // If there is no join we return the flat default schema
|
|
892
|
+
: never // Should never happen
|
|
890
893
|
>
|
|
891
894
|
|
|
892
895
|
export type ResultFromQueryBuilder<TQueryBuilder> = Flatten<
|
package/src/query/schema.ts
CHANGED
|
@@ -169,7 +169,6 @@ export interface JoinClause<TContext extends Context = Context> {
|
|
|
169
169
|
from: string
|
|
170
170
|
as?: string
|
|
171
171
|
on: Condition<TContext>
|
|
172
|
-
where?: Condition<TContext>
|
|
173
172
|
}
|
|
174
173
|
|
|
175
174
|
// The orderBy clause can be a string, an object mapping a column to "asc" or "desc",
|
|
@@ -192,6 +191,11 @@ export type Select<TContext extends Context = Context> =
|
|
|
192
191
|
| AggregateFunctionCall<TContext>
|
|
193
192
|
}
|
|
194
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
|
|
195
199
|
|
|
196
200
|
export type As<TContext extends Context = Context> = string
|
|
197
201
|
|
|
@@ -200,14 +204,21 @@ export type From<TContext extends Context = Context> = InputReference<{
|
|
|
200
204
|
schema: TContext[`baseSchema`]
|
|
201
205
|
}>
|
|
202
206
|
|
|
203
|
-
export type
|
|
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>
|
|
204
217
|
|
|
205
218
|
export type GroupBy<TContext extends Context = Context> =
|
|
206
219
|
| PropertyReference<TContext>
|
|
207
220
|
| Array<PropertyReference<TContext>>
|
|
208
221
|
|
|
209
|
-
export type Having<TContext extends Context = Context> = Condition<TContext>
|
|
210
|
-
|
|
211
222
|
export type Limit<TContext extends Context = Context> = number
|
|
212
223
|
|
|
213
224
|
export type Offset<TContext extends Context = Context> = number
|
|
@@ -217,13 +228,13 @@ export interface BaseQuery<TContext extends Context = Context> {
|
|
|
217
228
|
// to expressions. Plain strings starting with "@" denote column references.
|
|
218
229
|
// Plain string "@*" denotes all columns from all tables.
|
|
219
230
|
// Plain string "@table.*" denotes all columns from a specific table.
|
|
220
|
-
select
|
|
231
|
+
select?: Array<Select<TContext>>
|
|
221
232
|
as?: As<TContext>
|
|
222
233
|
from: From<TContext>
|
|
223
234
|
join?: Array<JoinClause<TContext>>
|
|
224
|
-
where?:
|
|
235
|
+
where?: Where<TContext>
|
|
225
236
|
groupBy?: GroupBy<TContext>
|
|
226
|
-
having?:
|
|
237
|
+
having?: Having<TContext>
|
|
227
238
|
orderBy?: OrderBy<TContext>
|
|
228
239
|
limit?: Limit<TContext>
|
|
229
240
|
offset?: Offset<TContext>
|
|
@@ -232,7 +243,6 @@ export interface BaseQuery<TContext extends Context = Context> {
|
|
|
232
243
|
// The top-level query interface.
|
|
233
244
|
export interface Query<TContext extends Context = Context>
|
|
234
245
|
extends BaseQuery<TContext> {
|
|
235
|
-
keyBy?: PropertyReference<TContext> | Array<PropertyReference<TContext>>
|
|
236
246
|
with?: Array<WithQuery<TContext>>
|
|
237
247
|
collections?: {
|
|
238
248
|
[K: string]: Collection<any>
|
|
@@ -246,10 +256,3 @@ export interface WithQuery<TContext extends Context = Context>
|
|
|
246
256
|
extends BaseQuery<TContext> {
|
|
247
257
|
as: string
|
|
248
258
|
}
|
|
249
|
-
|
|
250
|
-
// A keyed query is a query that has a keyBy clause, and so the result is always
|
|
251
|
-
// a keyed stream.
|
|
252
|
-
export interface KeyedQuery<TContext extends Context = Context>
|
|
253
|
-
extends Query<TContext> {
|
|
254
|
-
keyBy: PropertyReference<TContext> | Array<PropertyReference<TContext>>
|
|
255
|
-
}
|
package/src/query/select.ts
CHANGED
|
@@ -1,41 +1,71 @@
|
|
|
1
1
|
import { map } from "@electric-sql/d2ts"
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
evaluateOperandOnNamespacedRow,
|
|
4
|
+
extractValueFromNamespacedRow,
|
|
5
5
|
} from "./extractors"
|
|
6
|
-
import type {
|
|
7
|
-
import type {
|
|
6
|
+
import type { ConditionOperand, Query, SelectCallback } from "./schema"
|
|
7
|
+
import type { KeyedStream, NamespacedAndKeyedStream } from "../types"
|
|
8
8
|
|
|
9
9
|
export function processSelect(
|
|
10
|
-
pipeline:
|
|
10
|
+
pipeline: NamespacedAndKeyedStream,
|
|
11
11
|
query: Query,
|
|
12
12
|
mainTableAlias: string,
|
|
13
|
-
inputs: Record<string,
|
|
14
|
-
) {
|
|
13
|
+
inputs: Record<string, KeyedStream>
|
|
14
|
+
): KeyedStream {
|
|
15
15
|
return pipeline.pipe(
|
|
16
|
-
map((
|
|
16
|
+
map(([key, namespacedRow]) => {
|
|
17
17
|
const result: Record<string, unknown> = {}
|
|
18
18
|
|
|
19
19
|
// Check if this is a grouped result (has no nested table structure)
|
|
20
20
|
// If it's a grouped result, we need to handle it differently
|
|
21
21
|
const isGroupedResult =
|
|
22
22
|
query.groupBy &&
|
|
23
|
-
Object.keys(
|
|
24
|
-
(
|
|
25
|
-
!Object.keys(inputs).includes(
|
|
26
|
-
typeof
|
|
23
|
+
Object.keys(namespacedRow).some(
|
|
24
|
+
(namespaceKey) =>
|
|
25
|
+
!Object.keys(inputs).includes(namespaceKey) &&
|
|
26
|
+
typeof namespacedRow[namespaceKey] !== `object`
|
|
27
27
|
)
|
|
28
28
|
|
|
29
|
+
if (!query.select) {
|
|
30
|
+
throw new Error(`Cannot process missing SELECT clause`)
|
|
31
|
+
}
|
|
32
|
+
|
|
29
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
|
+
|
|
30
57
|
if (typeof item === `string`) {
|
|
31
58
|
// Handle wildcard select - all columns from all tables
|
|
32
59
|
if ((item as string) === `@*`) {
|
|
33
60
|
// For grouped results, just return the row as is
|
|
34
61
|
if (isGroupedResult) {
|
|
35
|
-
Object.assign(result,
|
|
62
|
+
Object.assign(result, namespacedRow)
|
|
36
63
|
} else {
|
|
37
64
|
// Extract all columns from all tables
|
|
38
|
-
Object.assign(
|
|
65
|
+
Object.assign(
|
|
66
|
+
result,
|
|
67
|
+
extractAllColumnsFromAllTables(namespacedRow)
|
|
68
|
+
)
|
|
39
69
|
}
|
|
40
70
|
continue
|
|
41
71
|
}
|
|
@@ -56,7 +86,7 @@ export function processSelect(
|
|
|
56
86
|
// Extract all columns from the specified table
|
|
57
87
|
Object.assign(
|
|
58
88
|
result,
|
|
59
|
-
extractAllColumnsFromTable(
|
|
89
|
+
extractAllColumnsFromTable(namespacedRow, tableAlias)
|
|
60
90
|
)
|
|
61
91
|
}
|
|
62
92
|
continue
|
|
@@ -68,12 +98,12 @@ export function processSelect(
|
|
|
68
98
|
const alias = columnRef
|
|
69
99
|
|
|
70
100
|
// For grouped results, check if the column is directly in the row first
|
|
71
|
-
if (isGroupedResult && columnRef in
|
|
72
|
-
result[alias] =
|
|
101
|
+
if (isGroupedResult && columnRef in namespacedRow) {
|
|
102
|
+
result[alias] = namespacedRow[columnRef]
|
|
73
103
|
} else {
|
|
74
104
|
// Extract the value from the nested structure
|
|
75
|
-
result[alias] =
|
|
76
|
-
|
|
105
|
+
result[alias] = extractValueFromNamespacedRow(
|
|
106
|
+
namespacedRow,
|
|
77
107
|
columnRef,
|
|
78
108
|
mainTableAlias,
|
|
79
109
|
undefined
|
|
@@ -95,12 +125,12 @@ export function processSelect(
|
|
|
95
125
|
const columnRef = (expr as string).substring(1)
|
|
96
126
|
|
|
97
127
|
// For grouped results, check if the column is directly in the row first
|
|
98
|
-
if (isGroupedResult && columnRef in
|
|
99
|
-
result[alias] =
|
|
128
|
+
if (isGroupedResult && columnRef in namespacedRow) {
|
|
129
|
+
result[alias] = namespacedRow[columnRef]
|
|
100
130
|
} else {
|
|
101
131
|
// Extract the value from the nested structure
|
|
102
|
-
result[alias] =
|
|
103
|
-
|
|
132
|
+
result[alias] = extractValueFromNamespacedRow(
|
|
133
|
+
namespacedRow,
|
|
104
134
|
columnRef,
|
|
105
135
|
mainTableAlias,
|
|
106
136
|
undefined
|
|
@@ -108,12 +138,14 @@ export function processSelect(
|
|
|
108
138
|
}
|
|
109
139
|
} else if (typeof expr === `object`) {
|
|
110
140
|
// For grouped results, the aggregate results are already in the row
|
|
111
|
-
if (isGroupedResult && alias in
|
|
112
|
-
result[alias] =
|
|
141
|
+
if (isGroupedResult && alias in namespacedRow) {
|
|
142
|
+
result[alias] = namespacedRow[alias]
|
|
143
|
+
} else if ((expr as { ORDER_INDEX: unknown }).ORDER_INDEX) {
|
|
144
|
+
result[alias] = namespacedRow[mainTableAlias]![alias]
|
|
113
145
|
} else {
|
|
114
146
|
// This might be a function call
|
|
115
|
-
result[alias] =
|
|
116
|
-
|
|
147
|
+
result[alias] = evaluateOperandOnNamespacedRow(
|
|
148
|
+
namespacedRow,
|
|
117
149
|
expr as ConditionOperand,
|
|
118
150
|
mainTableAlias,
|
|
119
151
|
undefined
|
|
@@ -124,23 +156,26 @@ export function processSelect(
|
|
|
124
156
|
}
|
|
125
157
|
}
|
|
126
158
|
|
|
127
|
-
return result
|
|
159
|
+
return [key, result] as [string, typeof result]
|
|
128
160
|
})
|
|
129
161
|
)
|
|
130
162
|
}
|
|
131
163
|
|
|
132
164
|
// Helper function to extract all columns from all tables in a nested row
|
|
133
165
|
function extractAllColumnsFromAllTables(
|
|
134
|
-
|
|
166
|
+
namespacedRow: Record<string, unknown>
|
|
135
167
|
): Record<string, unknown> {
|
|
136
168
|
const result: Record<string, unknown> = {}
|
|
137
169
|
|
|
138
170
|
// Process each table in the nested row
|
|
139
|
-
for (const [tableAlias, tableData] of Object.entries(
|
|
171
|
+
for (const [tableAlias, tableData] of Object.entries(namespacedRow)) {
|
|
140
172
|
if (tableData && typeof tableData === `object`) {
|
|
141
173
|
// Add all columns from this table to the result
|
|
142
174
|
// If there are column name conflicts, the last table's columns will overwrite previous ones
|
|
143
|
-
Object.assign(
|
|
175
|
+
Object.assign(
|
|
176
|
+
result,
|
|
177
|
+
extractAllColumnsFromTable(namespacedRow, tableAlias)
|
|
178
|
+
)
|
|
144
179
|
}
|
|
145
180
|
}
|
|
146
181
|
|
|
@@ -149,13 +184,13 @@ function extractAllColumnsFromAllTables(
|
|
|
149
184
|
|
|
150
185
|
// Helper function to extract all columns from a table in a nested row
|
|
151
186
|
function extractAllColumnsFromTable(
|
|
152
|
-
|
|
187
|
+
namespacedRow: Record<string, unknown>,
|
|
153
188
|
tableAlias: string
|
|
154
189
|
): Record<string, unknown> {
|
|
155
190
|
const result: Record<string, unknown> = {}
|
|
156
191
|
|
|
157
192
|
// Get the table data
|
|
158
|
-
const tableData =
|
|
193
|
+
const tableData = namespacedRow[tableAlias] as
|
|
159
194
|
| Record<string, unknown>
|
|
160
195
|
| null
|
|
161
196
|
| undefined
|
package/src/query/types.ts
CHANGED
package/src/transactions.ts
CHANGED
|
@@ -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() {
|
|
@@ -24,6 +25,7 @@ function generateUUID() {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
const transactions: Array<Transaction> = []
|
|
28
|
+
let transactionStack: Array<Transaction> = []
|
|
27
29
|
|
|
28
30
|
export function createTransaction(config: TransactionConfig): Transaction {
|
|
29
31
|
if (typeof config.mutationFn === `undefined`) {
|
|
@@ -35,13 +37,12 @@ export function createTransaction(config: TransactionConfig): Transaction {
|
|
|
35
37
|
transactionId = generateUUID()
|
|
36
38
|
}
|
|
37
39
|
const newTransaction = new Transaction({ ...config, id: transactionId })
|
|
40
|
+
|
|
38
41
|
transactions.push(newTransaction)
|
|
39
42
|
|
|
40
43
|
return newTransaction
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
let transactionStack: Array<Transaction> = []
|
|
44
|
-
|
|
45
46
|
export function getActiveTransaction(): Transaction | undefined {
|
|
46
47
|
if (transactionStack.length > 0) {
|
|
47
48
|
return transactionStack.slice(-1)[0]
|
|
@@ -58,6 +59,13 @@ function unregisterTransaction(tx: Transaction) {
|
|
|
58
59
|
transactionStack = transactionStack.filter((t) => t.id !== tx.id)
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
function removeFromPendingList(tx: Transaction) {
|
|
63
|
+
const index = transactions.findIndex((t) => t.id === tx.id)
|
|
64
|
+
if (index !== -1) {
|
|
65
|
+
transactions.splice(index, 1)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
export class Transaction {
|
|
62
70
|
public id: string
|
|
63
71
|
public state: TransactionState
|
|
@@ -85,6 +93,10 @@ export class Transaction {
|
|
|
85
93
|
|
|
86
94
|
setState(newState: TransactionState) {
|
|
87
95
|
this.state = newState
|
|
96
|
+
|
|
97
|
+
if (newState === `completed` || newState === `failed`) {
|
|
98
|
+
removeFromPendingList(this)
|
|
99
|
+
}
|
|
88
100
|
}
|
|
89
101
|
|
|
90
102
|
mutate(callback: () => void): Transaction {
|
|
@@ -130,22 +142,20 @@ export class Transaction {
|
|
|
130
142
|
|
|
131
143
|
this.setState(`failed`)
|
|
132
144
|
|
|
133
|
-
// See if there's any other transactions w/ mutations on the same
|
|
145
|
+
// See if there's any other transactions w/ mutations on the same ids
|
|
134
146
|
// and roll them back as well.
|
|
135
147
|
if (!isSecondaryRollback) {
|
|
136
|
-
const
|
|
137
|
-
this.mutations.forEach((m) =>
|
|
138
|
-
transactions
|
|
139
|
-
|
|
140
|
-
t.
|
|
141
|
-
t.mutations.some((m) => mutationKeys.has(m.key)) &&
|
|
148
|
+
const mutationIds = new Set()
|
|
149
|
+
this.mutations.forEach((m) => mutationIds.add(m.key))
|
|
150
|
+
for (const t of transactions) {
|
|
151
|
+
t.state === `pending` &&
|
|
152
|
+
t.mutations.some((m) => mutationIds.has(m.key)) &&
|
|
142
153
|
t.rollback({ isSecondaryRollback: true })
|
|
143
|
-
|
|
154
|
+
}
|
|
144
155
|
}
|
|
145
156
|
|
|
146
157
|
// Reject the promise
|
|
147
158
|
this.isPersisted.reject(this.error?.error)
|
|
148
|
-
|
|
149
159
|
this.touchCollection()
|
|
150
160
|
|
|
151
161
|
return this
|
|
@@ -154,13 +164,13 @@ export class Transaction {
|
|
|
154
164
|
// Tell collection that something has changed with the transaction
|
|
155
165
|
touchCollection(): void {
|
|
156
166
|
const hasCalled = new Set()
|
|
157
|
-
this.mutations
|
|
167
|
+
for (const mutation of this.mutations) {
|
|
158
168
|
if (!hasCalled.has(mutation.collection.id)) {
|
|
159
169
|
mutation.collection.transactions.setState((state) => state)
|
|
160
170
|
mutation.collection.commitPendingTransactions()
|
|
161
171
|
hasCalled.add(mutation.collection.id)
|
|
162
172
|
}
|
|
163
|
-
}
|
|
173
|
+
}
|
|
164
174
|
}
|
|
165
175
|
|
|
166
176
|
async commit(): Promise<Transaction> {
|
|
@@ -172,11 +182,17 @@ export class Transaction {
|
|
|
172
182
|
|
|
173
183
|
if (this.mutations.length === 0) {
|
|
174
184
|
this.setState(`completed`)
|
|
185
|
+
|
|
186
|
+
return this
|
|
175
187
|
}
|
|
176
188
|
|
|
177
189
|
// Run mutationFn
|
|
178
190
|
try {
|
|
179
|
-
|
|
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 })
|
|
180
196
|
|
|
181
197
|
this.setState(`completed`)
|
|
182
198
|
this.touchCollection()
|