@remix-run/data-table 0.1.0 → 0.2.1

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 (111) hide show
  1. package/README.md +307 -56
  2. package/dist/index.d.ts +9 -5
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +4 -2
  5. package/dist/lib/adapter.d.ts +386 -16
  6. package/dist/lib/adapter.d.ts.map +1 -1
  7. package/dist/lib/column.d.ts +193 -0
  8. package/dist/lib/column.d.ts.map +1 -0
  9. package/dist/lib/column.js +302 -0
  10. package/dist/lib/database/execution-context.d.ts +10 -0
  11. package/dist/lib/database/execution-context.d.ts.map +1 -0
  12. package/dist/lib/database/execution-context.js +1 -0
  13. package/dist/lib/database/helpers.d.ts +26 -0
  14. package/dist/lib/database/helpers.d.ts.map +1 -0
  15. package/dist/lib/database/helpers.js +116 -0
  16. package/dist/lib/database/query-execution.d.ts +7 -0
  17. package/dist/lib/database/query-execution.d.ts.map +1 -0
  18. package/dist/lib/database/query-execution.js +401 -0
  19. package/dist/lib/database/relations.d.ts +4 -0
  20. package/dist/lib/database/relations.d.ts.map +1 -0
  21. package/dist/lib/database/relations.js +207 -0
  22. package/dist/lib/database/write-lifecycle.d.ts +13 -0
  23. package/dist/lib/database/write-lifecycle.d.ts.map +1 -0
  24. package/dist/lib/database/write-lifecycle.js +279 -0
  25. package/dist/lib/database.d.ts +141 -238
  26. package/dist/lib/database.d.ts.map +1 -1
  27. package/dist/lib/database.js +73 -1122
  28. package/dist/lib/errors.d.ts +9 -0
  29. package/dist/lib/errors.d.ts.map +1 -1
  30. package/dist/lib/errors.js +9 -0
  31. package/dist/lib/migrations/filename.d.ts +12 -0
  32. package/dist/lib/migrations/filename.d.ts.map +1 -0
  33. package/dist/lib/migrations/filename.js +20 -0
  34. package/dist/lib/migrations/helpers.d.ts +11 -0
  35. package/dist/lib/migrations/helpers.d.ts.map +1 -0
  36. package/dist/lib/migrations/helpers.js +77 -0
  37. package/dist/lib/migrations/journal-store.d.ts +15 -0
  38. package/dist/lib/migrations/journal-store.d.ts.map +1 -0
  39. package/dist/lib/migrations/journal-store.js +83 -0
  40. package/dist/lib/migrations/registry.d.ts +27 -0
  41. package/dist/lib/migrations/registry.d.ts.map +1 -0
  42. package/dist/lib/migrations/registry.js +51 -0
  43. package/dist/lib/migrations/runner.d.ts +20 -0
  44. package/dist/lib/migrations/runner.d.ts.map +1 -0
  45. package/dist/lib/migrations/runner.js +273 -0
  46. package/dist/lib/migrations/schema-api.d.ts +7 -0
  47. package/dist/lib/migrations/schema-api.d.ts.map +1 -0
  48. package/dist/lib/migrations/schema-api.js +326 -0
  49. package/dist/lib/migrations-node.d.ts +17 -0
  50. package/dist/lib/migrations-node.d.ts.map +1 -0
  51. package/dist/lib/migrations-node.js +65 -0
  52. package/dist/lib/migrations.d.ts +292 -0
  53. package/dist/lib/migrations.d.ts.map +1 -0
  54. package/dist/lib/migrations.js +38 -0
  55. package/dist/lib/operators.d.ts +3 -0
  56. package/dist/lib/operators.d.ts.map +1 -1
  57. package/dist/lib/query.d.ts +159 -0
  58. package/dist/lib/query.d.ts.map +1 -0
  59. package/dist/lib/query.js +401 -0
  60. package/dist/lib/references.d.ts +0 -1
  61. package/dist/lib/references.d.ts.map +1 -1
  62. package/dist/lib/sql-helpers.d.ts +50 -0
  63. package/dist/lib/sql-helpers.d.ts.map +1 -0
  64. package/dist/lib/sql-helpers.js +111 -0
  65. package/dist/lib/sql.d.ts +23 -6
  66. package/dist/lib/sql.d.ts.map +1 -1
  67. package/dist/lib/sql.js +19 -5
  68. package/dist/lib/table.d.ts +355 -40
  69. package/dist/lib/table.d.ts.map +1 -1
  70. package/dist/lib/table.js +113 -90
  71. package/dist/migrations/node.d.ts +2 -0
  72. package/dist/migrations/node.d.ts.map +1 -0
  73. package/dist/migrations/node.js +1 -0
  74. package/dist/migrations.d.ts +8 -0
  75. package/dist/migrations.d.ts.map +1 -0
  76. package/dist/migrations.js +5 -0
  77. package/dist/operators.d.ts +3 -0
  78. package/dist/operators.d.ts.map +1 -0
  79. package/dist/operators.js +1 -0
  80. package/dist/sql-helpers.d.ts +3 -0
  81. package/dist/sql-helpers.d.ts.map +1 -0
  82. package/dist/sql-helpers.js +1 -0
  83. package/package.json +24 -10
  84. package/src/index.ts +93 -10
  85. package/src/lib/adapter.ts +469 -25
  86. package/src/lib/column.ts +384 -0
  87. package/src/lib/database/execution-context.ts +15 -0
  88. package/src/lib/database/helpers.ts +216 -0
  89. package/src/lib/database/query-execution.ts +638 -0
  90. package/src/lib/database/relations.ts +332 -0
  91. package/src/lib/database/write-lifecycle.ts +487 -0
  92. package/src/lib/database.ts +246 -1848
  93. package/src/lib/errors.ts +10 -0
  94. package/src/lib/migrations/filename.ts +25 -0
  95. package/src/lib/migrations/helpers.ts +108 -0
  96. package/src/lib/migrations/journal-store.ts +122 -0
  97. package/src/lib/migrations/registry.ts +62 -0
  98. package/src/lib/migrations/runner.ts +374 -0
  99. package/src/lib/migrations/schema-api.ts +417 -0
  100. package/src/lib/migrations-node.ts +71 -0
  101. package/src/lib/migrations.ts +328 -0
  102. package/src/lib/operators.ts +3 -0
  103. package/src/lib/query.ts +958 -0
  104. package/src/lib/references.ts +0 -1
  105. package/src/lib/sql-helpers.ts +146 -0
  106. package/src/lib/sql.ts +23 -6
  107. package/src/lib/table.ts +484 -156
  108. package/src/migrations/node.ts +1 -0
  109. package/src/migrations.ts +26 -0
  110. package/src/operators.ts +18 -0
  111. package/src/sql-helpers.ts +9 -0
@@ -0,0 +1,958 @@
1
+ import type { JoinClause, JoinType, SelectColumn } from './adapter.ts'
2
+ import { DataTableQueryError, DataTableValidationError } from './errors.ts'
3
+ import type {
4
+ MergeColumnTypeMaps,
5
+ PrimaryKeyInputForRow,
6
+ QueryColumnInput,
7
+ QueryColumnName,
8
+ QueryColumnTypeMap,
9
+ QueryColumnTypeMapFromRow,
10
+ QueryColumns,
11
+ QueryTableInput,
12
+ RelationMapForSourceName,
13
+ ReturningInput,
14
+ SelectedAliasRow,
15
+ WriteResult,
16
+ WriteRowResult,
17
+ WriteRowsResult,
18
+ } from './database.ts'
19
+ import type { Predicate, WhereInput } from './operators.ts'
20
+ import { normalizeWhereInput } from './operators.ts'
21
+ import { normalizeColumnInput } from './references.ts'
22
+ import type { AnyRelation, AnyTable, LoadedRelationMap, OrderByClause } from './table.ts'
23
+ import { getTableColumns, getTableName } from './table.ts'
24
+
25
+ type QueryBindingState = 'bound' | 'unbound'
26
+
27
+ type InsertQueryOptions<row extends Record<string, unknown>> = {
28
+ returning?: ReturningInput<row>
29
+ touch?: boolean
30
+ }
31
+
32
+ type DeleteQueryOptions<row extends Record<string, unknown>> = {
33
+ returning?: ReturningInput<row>
34
+ }
35
+
36
+ type UpsertQueryOptions<row extends Record<string, unknown>> = {
37
+ returning?: ReturningInput<row>
38
+ touch?: boolean
39
+ conflictTarget?: (keyof row & string)[]
40
+ update?: Partial<row>
41
+ }
42
+
43
+ export type QueryState = {
44
+ select: '*' | SelectColumn[]
45
+ distinct: boolean
46
+ joins: JoinClause[]
47
+ where: Predicate<string>[]
48
+ groupBy: string[]
49
+ having: Predicate<string>[]
50
+ orderBy: OrderByClause[]
51
+ limit?: number
52
+ offset?: number
53
+ with: Record<string, AnyRelation>
54
+ }
55
+
56
+ type QueryPlanMap<row extends Record<string, unknown>, primaryKey extends readonly string[]> = {
57
+ all: { kind: 'all' }
58
+ first: { kind: 'first' }
59
+ find: {
60
+ kind: 'find'
61
+ value: PrimaryKeyInputForRow<row, primaryKey>
62
+ }
63
+ count: { kind: 'count' }
64
+ exists: { kind: 'exists' }
65
+ insert: {
66
+ kind: 'insert'
67
+ values: Partial<row>
68
+ options?: InsertQueryOptions<row>
69
+ }
70
+ insertMany: {
71
+ kind: 'insertMany'
72
+ values: Partial<row>[]
73
+ options?: InsertQueryOptions<row>
74
+ }
75
+ update: {
76
+ kind: 'update'
77
+ changes: Partial<row>
78
+ options?: InsertQueryOptions<row>
79
+ }
80
+ delete: {
81
+ kind: 'delete'
82
+ options?: DeleteQueryOptions<row>
83
+ }
84
+ upsert: {
85
+ kind: 'upsert'
86
+ values: Partial<row>
87
+ options?: UpsertQueryOptions<row>
88
+ }
89
+ }
90
+
91
+ type QueryExecutionMode = keyof QueryPlanMap<Record<string, unknown>, readonly string[]>
92
+
93
+ type QueryPhase<
94
+ binding extends QueryBindingState = QueryBindingState,
95
+ mode extends QueryExecutionMode = QueryExecutionMode,
96
+ > = {
97
+ binding: binding
98
+ mode: mode
99
+ }
100
+
101
+ export type BoundQueryPhase<mode extends QueryExecutionMode = QueryExecutionMode> = QueryPhase<
102
+ 'bound',
103
+ mode
104
+ >
105
+
106
+ export type UnboundQueryPhase<mode extends QueryExecutionMode = QueryExecutionMode> = QueryPhase<
107
+ 'unbound',
108
+ mode
109
+ >
110
+
111
+ type AnyQuerySource = QueryTableInput<string, Record<string, unknown>, readonly string[]>
112
+
113
+ type QuerySourceTableName<source extends AnyQuerySource> =
114
+ source extends QueryTableInput<infer tableName, any, any> ? tableName : never
115
+
116
+ type QuerySourceRow<source extends AnyQuerySource> =
117
+ source extends QueryTableInput<any, infer row, any> ? row : never
118
+
119
+ type QuerySourcePrimaryKey<source extends AnyQuerySource> =
120
+ source extends QueryTableInput<any, any, infer primaryKey> ? primaryKey : never
121
+
122
+ type QuerySourceColumnTypes<source extends AnyQuerySource> = QueryColumnTypeMapFromRow<
123
+ QuerySourceTableName<source>,
124
+ QuerySourceRow<source>
125
+ >
126
+
127
+ type QueryPlan<
128
+ row extends Record<string, unknown>,
129
+ primaryKey extends readonly string[],
130
+ mode extends QueryExecutionMode = QueryExecutionMode,
131
+ > = QueryPlanMap<row, primaryKey>[mode]
132
+
133
+ type QueryResultMap<row extends Record<string, unknown>, loaded extends Record<string, unknown>> = {
134
+ all: Array<row & loaded>
135
+ first: (row & loaded) | null
136
+ find: (row & loaded) | null
137
+ count: number
138
+ exists: boolean
139
+ insert: WriteResult | WriteRowResult<row>
140
+ insertMany: WriteResult | WriteRowsResult<row>
141
+ update: WriteResult | WriteRowsResult<row>
142
+ delete: WriteResult | WriteRowsResult<row>
143
+ upsert: WriteResult | WriteRowResult<row>
144
+ }
145
+
146
+ export type AnyQuery = Query<any, any, any, any, any>
147
+
148
+ type QuerySource<input extends AnyQuery> =
149
+ input extends Query<infer source, any, any, any, any> ? source : never
150
+
151
+ type QueryColumnTypes<input extends AnyQuery> =
152
+ input extends Query<any, infer columnTypes, any, any, any> ? columnTypes : never
153
+
154
+ type QueryRow<input extends AnyQuery> =
155
+ input extends Query<any, any, infer row, any, any> ? row : never
156
+
157
+ type QueryLoaded<input extends AnyQuery> =
158
+ input extends Query<any, any, any, infer loaded, any> ? loaded : never
159
+
160
+ type QueryPhaseOf<input extends AnyQuery> =
161
+ input extends Query<any, any, any, any, infer phase> ? phase : never
162
+
163
+ type QueryBinding<input extends AnyQuery> = QueryPhaseOf<input>['binding']
164
+
165
+ type QueryMode<input extends AnyQuery> = QueryPhaseOf<input>['mode']
166
+
167
+ type QueryPhaseBinding<phase extends QueryPhase> = phase['binding']
168
+
169
+ type QueryPhaseMode<phase extends QueryPhase> = phase['mode']
170
+
171
+ type QueryAllPhase<phase extends QueryPhase> = QueryPhase<QueryPhaseBinding<phase>, 'all'>
172
+
173
+ type QueryNextPhase<phase extends QueryPhase, mode extends QueryExecutionMode> = QueryPhase<
174
+ QueryPhaseBinding<phase>,
175
+ mode
176
+ >
177
+
178
+ type QueryWith<input extends AnyQuery, phase extends QueryPhase> = Query<
179
+ QuerySource<input>,
180
+ QueryColumnTypes<input>,
181
+ QueryRow<input>,
182
+ QueryLoaded<input>,
183
+ phase
184
+ >
185
+
186
+ type QueryTerminalResult<input extends AnyQuery, mode extends QueryExecutionMode, result> =
187
+ QueryBinding<input> extends 'bound' ? Promise<result> : QueryWith<input, UnboundQueryPhase<mode>>
188
+
189
+ export type QueryExecutionResult<input> = input extends AnyQuery
190
+ ? QueryResultMap<QueryRow<input>, QueryLoaded<input>>[Extract<
191
+ QueryMode<input>,
192
+ QueryExecutionMode
193
+ >]
194
+ : never
195
+
196
+ type QueryRuntime = {
197
+ exec<input extends AnyQuery>(input: input): Promise<QueryExecutionResult<input>>
198
+ }
199
+
200
+ type QuerySnapshot<
201
+ source extends AnyQuerySource = AnyQuerySource,
202
+ row extends Record<string, unknown> = Record<string, unknown>,
203
+ mode extends QueryExecutionMode = QueryExecutionMode,
204
+ > = {
205
+ table: source
206
+ state: QueryState
207
+ plan: QueryPlan<row, QuerySourcePrimaryKey<source>, mode>
208
+ }
209
+
210
+ export const bindQueryRuntime = Symbol('bindQueryRuntime')
211
+ export const querySnapshot = Symbol('querySnapshot')
212
+
213
+ declare const queryTypeBrand: unique symbol
214
+
215
+ export class Query<
216
+ source extends AnyQuerySource,
217
+ columnTypes extends Record<string, unknown> = QuerySourceColumnTypes<source>,
218
+ row extends Record<string, unknown> = QuerySourceRow<source>,
219
+ loaded extends Record<string, unknown> = {},
220
+ phase extends QueryPhase = UnboundQueryPhase<'all'>,
221
+ > {
222
+ declare readonly [queryTypeBrand]: {
223
+ binding: QueryPhaseBinding<phase>
224
+ mode: QueryPhaseMode<phase>
225
+ }
226
+
227
+ #table: source
228
+ #state: QueryState
229
+ #plan: QueryPlan<row, QuerySourcePrimaryKey<source>, QueryPhaseMode<phase>>
230
+ #runtime?: QueryRuntime
231
+
232
+ constructor(table: source) {
233
+ this.#table = table
234
+ this.#state = createInitialQueryState()
235
+ this.#plan = { kind: 'all' } as QueryPlan<
236
+ row,
237
+ QuerySourcePrimaryKey<source>,
238
+ QueryPhaseMode<phase>
239
+ >
240
+ }
241
+
242
+ static #createInternal<
243
+ source extends AnyQuerySource,
244
+ columnTypes extends Record<string, unknown>,
245
+ row extends Record<string, unknown>,
246
+ loaded extends Record<string, unknown>,
247
+ phase extends QueryPhase,
248
+ >(
249
+ table: source,
250
+ state: QueryState,
251
+ plan: QueryPlan<row, QuerySourcePrimaryKey<source>, QueryPhaseMode<phase>>,
252
+ runtime?: QueryRuntime,
253
+ ): Query<source, columnTypes, row, loaded, phase> {
254
+ let output = new Query(table) as Query<source, columnTypes, row, loaded, phase>
255
+
256
+ output.#state = cloneQueryState(state)
257
+ output.#plan = cloneQueryPlan(
258
+ plan as QueryPlan<row, QuerySourcePrimaryKey<source>>,
259
+ ) as QueryPlan<row, QuerySourcePrimaryKey<source>, QueryPhaseMode<phase>>
260
+ output.#runtime = runtime
261
+
262
+ return output
263
+ }
264
+
265
+ select<selection extends (keyof row & string)[]>(
266
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
267
+ ...columns: selection
268
+ ): Query<source, columnTypes, Pick<row, selection[number]>, loaded, QueryAllPhase<phase>>
269
+ select<selection extends Record<string, QueryColumnInput<columnTypes>>>(
270
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
271
+ selection: selection,
272
+ ): Query<
273
+ source,
274
+ columnTypes,
275
+ SelectedAliasRow<columnTypes, selection>,
276
+ loaded,
277
+ QueryAllPhase<phase>
278
+ >
279
+ select(
280
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
281
+ ...input: [Record<string, QueryColumnInput<columnTypes>>] | (keyof row & string)[]
282
+ ): Query<source, columnTypes, any, loaded, QueryAllPhase<phase>> {
283
+ if (
284
+ input.length === 1 &&
285
+ typeof input[0] === 'object' &&
286
+ input[0] !== null &&
287
+ !Array.isArray(input[0])
288
+ ) {
289
+ let selection = input[0] as Record<string, QueryColumnInput<columnTypes>>
290
+ let aliases = Object.keys(selection)
291
+ let select = aliases.map((alias) => ({
292
+ column: normalizeColumnInput(selection[alias]),
293
+ alias,
294
+ }))
295
+
296
+ return this.#clone({ select }) as Query<
297
+ source,
298
+ columnTypes,
299
+ any,
300
+ loaded,
301
+ QueryAllPhase<phase>
302
+ >
303
+ }
304
+
305
+ let columns = input as (keyof row & string)[]
306
+
307
+ return this.#clone({
308
+ select: columns.map((column) => ({ column, alias: column })),
309
+ }) as Query<source, columnTypes, any, loaded, QueryAllPhase<phase>>
310
+ }
311
+
312
+ distinct(
313
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
314
+ value = true,
315
+ ): Query<source, columnTypes, row, loaded, QueryAllPhase<phase>> {
316
+ return this.#clone({ distinct: value })
317
+ }
318
+
319
+ where(
320
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
321
+ input: WhereInput<QueryColumns<columnTypes>>,
322
+ ): Query<source, columnTypes, row, loaded, QueryAllPhase<phase>> {
323
+ let predicate = normalizeWhereInput(input)
324
+ let normalizedPredicate = normalizePredicateValues(
325
+ predicate,
326
+ createPredicateColumnResolver([this.#table, ...this.#state.joins.map((join) => join.table)]),
327
+ )
328
+
329
+ return this.#clone({
330
+ where: [...this.#state.where, normalizedPredicate],
331
+ })
332
+ }
333
+
334
+ having(
335
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
336
+ input: WhereInput<QueryColumns<columnTypes>>,
337
+ ): Query<source, columnTypes, row, loaded, QueryAllPhase<phase>> {
338
+ let predicate = normalizeWhereInput(input)
339
+ let normalizedPredicate = normalizePredicateValues(
340
+ predicate,
341
+ createPredicateColumnResolver([this.#table, ...this.#state.joins.map((join) => join.table)]),
342
+ )
343
+
344
+ return this.#clone({
345
+ having: [...this.#state.having, normalizedPredicate],
346
+ })
347
+ }
348
+
349
+ join<target extends AnyTable>(
350
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
351
+ target: target,
352
+ on: Predicate<QueryColumns<columnTypes> | QueryColumnName<target>>,
353
+ type: JoinType = 'inner',
354
+ ): Query<
355
+ source,
356
+ MergeColumnTypeMaps<columnTypes, QueryColumnTypeMap<target>>,
357
+ row,
358
+ loaded,
359
+ QueryAllPhase<phase>
360
+ > {
361
+ let normalizedOn = normalizePredicateValues(
362
+ on,
363
+ createPredicateColumnResolver([
364
+ this.#table,
365
+ ...this.#state.joins.map((join) => join.table),
366
+ target,
367
+ ]),
368
+ ) as Predicate<QueryColumns<columnTypes> | QueryColumnName<target>>
369
+
370
+ return this.#clone({
371
+ joins: [...this.#state.joins, { type, table: target, on: normalizedOn }],
372
+ }) as Query<
373
+ source,
374
+ MergeColumnTypeMaps<columnTypes, QueryColumnTypeMap<target>>,
375
+ row,
376
+ loaded,
377
+ QueryAllPhase<phase>
378
+ >
379
+ }
380
+
381
+ leftJoin<target extends AnyTable>(
382
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
383
+ target: target,
384
+ on: Predicate<QueryColumns<columnTypes> | QueryColumnName<target>>,
385
+ ): Query<
386
+ source,
387
+ MergeColumnTypeMaps<columnTypes, QueryColumnTypeMap<target>>,
388
+ row,
389
+ loaded,
390
+ QueryAllPhase<phase>
391
+ > {
392
+ return this.join(target, on, 'left')
393
+ }
394
+
395
+ rightJoin<target extends AnyTable>(
396
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
397
+ target: target,
398
+ on: Predicate<QueryColumns<columnTypes> | QueryColumnName<target>>,
399
+ ): Query<
400
+ source,
401
+ MergeColumnTypeMaps<columnTypes, QueryColumnTypeMap<target>>,
402
+ row,
403
+ loaded,
404
+ QueryAllPhase<phase>
405
+ > {
406
+ return this.join(target, on, 'right')
407
+ }
408
+
409
+ orderBy(
410
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
411
+ column: QueryColumnInput<columnTypes>,
412
+ direction: 'asc' | 'desc' = 'asc',
413
+ ): Query<source, columnTypes, row, loaded, QueryAllPhase<phase>> {
414
+ return this.#clone({
415
+ orderBy: [...this.#state.orderBy, { column: normalizeColumnInput(column), direction }],
416
+ })
417
+ }
418
+
419
+ groupBy(
420
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
421
+ ...columns: QueryColumnInput<columnTypes>[]
422
+ ): Query<source, columnTypes, row, loaded, QueryAllPhase<phase>> {
423
+ return this.#clone({
424
+ groupBy: [...this.#state.groupBy, ...columns.map((column) => normalizeColumnInput(column))],
425
+ })
426
+ }
427
+
428
+ limit(
429
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
430
+ value: number,
431
+ ): Query<source, columnTypes, row, loaded, QueryAllPhase<phase>> {
432
+ return this.#clone({ limit: value })
433
+ }
434
+
435
+ offset(
436
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
437
+ value: number,
438
+ ): Query<source, columnTypes, row, loaded, QueryAllPhase<phase>> {
439
+ return this.#clone({ offset: value })
440
+ }
441
+
442
+ with<relations extends RelationMapForSourceName<QuerySourceTableName<source>>>(
443
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
444
+ relations: relations,
445
+ ): Query<source, columnTypes, row, loaded & LoadedRelationMap<relations>, QueryAllPhase<phase>> {
446
+ return this.#clone({
447
+ with: {
448
+ ...this.#state.with,
449
+ ...relations,
450
+ },
451
+ }) as Query<
452
+ source,
453
+ columnTypes,
454
+ row,
455
+ loaded & LoadedRelationMap<relations>,
456
+ QueryAllPhase<phase>
457
+ >
458
+ }
459
+
460
+ all(
461
+ this: Query<source, columnTypes, row, loaded, BoundQueryPhase<'all'>>,
462
+ ): Promise<Array<row & loaded>> {
463
+ return this.#boundRuntime().exec(this) as Promise<Array<row & loaded>>
464
+ }
465
+
466
+ first(
467
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
468
+ ): QueryTerminalResult<
469
+ Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
470
+ 'first',
471
+ (row & loaded) | null
472
+ > {
473
+ return this.#resolveTerminal({ kind: 'first' })
474
+ }
475
+
476
+ find(
477
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
478
+ value: PrimaryKeyInputForRow<row, QuerySourcePrimaryKey<source>>,
479
+ ): QueryTerminalResult<
480
+ Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
481
+ 'find',
482
+ (row & loaded) | null
483
+ > {
484
+ return this.#resolveTerminal({ kind: 'find', value })
485
+ }
486
+
487
+ count(
488
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
489
+ ): QueryTerminalResult<
490
+ Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
491
+ 'count',
492
+ number
493
+ > {
494
+ return this.#resolveTerminal({ kind: 'count' })
495
+ }
496
+
497
+ exists(
498
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
499
+ ): QueryTerminalResult<
500
+ Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
501
+ 'exists',
502
+ boolean
503
+ > {
504
+ return this.#resolveTerminal({ kind: 'exists' })
505
+ }
506
+
507
+ insert(
508
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
509
+ values: Partial<row>,
510
+ options?: InsertQueryOptions<row>,
511
+ ): QueryTerminalResult<
512
+ Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
513
+ 'insert',
514
+ WriteResult | WriteRowResult<row>
515
+ > {
516
+ assertWriteState(this.#state, 'insert', {
517
+ where: false,
518
+ orderBy: false,
519
+ limit: false,
520
+ offset: false,
521
+ })
522
+
523
+ return this.#resolveTerminal({ kind: 'insert', values, options })
524
+ }
525
+
526
+ insertMany(
527
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
528
+ values: Partial<row>[],
529
+ options?: InsertQueryOptions<row>,
530
+ ): QueryTerminalResult<
531
+ Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
532
+ 'insertMany',
533
+ WriteResult | WriteRowsResult<row>
534
+ > {
535
+ assertWriteState(this.#state, 'insertMany', {
536
+ where: false,
537
+ orderBy: false,
538
+ limit: false,
539
+ offset: false,
540
+ })
541
+
542
+ return this.#resolveTerminal({ kind: 'insertMany', values, options })
543
+ }
544
+
545
+ update(
546
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
547
+ changes: Partial<row>,
548
+ options?: InsertQueryOptions<row>,
549
+ ): QueryTerminalResult<
550
+ Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
551
+ 'update',
552
+ WriteResult | WriteRowsResult<row>
553
+ > {
554
+ assertWriteState(this.#state, 'update', {
555
+ where: true,
556
+ orderBy: true,
557
+ limit: true,
558
+ offset: true,
559
+ })
560
+
561
+ return this.#resolveTerminal({ kind: 'update', changes, options })
562
+ }
563
+
564
+ delete(
565
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
566
+ options?: DeleteQueryOptions<row>,
567
+ ): QueryTerminalResult<
568
+ Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
569
+ 'delete',
570
+ WriteResult | WriteRowsResult<row>
571
+ > {
572
+ assertWriteState(this.#state, 'delete', {
573
+ where: true,
574
+ orderBy: true,
575
+ limit: true,
576
+ offset: true,
577
+ })
578
+
579
+ return this.#resolveTerminal({ kind: 'delete', options })
580
+ }
581
+
582
+ upsert(
583
+ this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
584
+ values: Partial<row>,
585
+ options?: UpsertQueryOptions<row>,
586
+ ): QueryTerminalResult<
587
+ Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
588
+ 'upsert',
589
+ WriteResult | WriteRowResult<row>
590
+ > {
591
+ assertWriteState(this.#state, 'upsert', {
592
+ where: false,
593
+ orderBy: false,
594
+ limit: false,
595
+ offset: false,
596
+ })
597
+
598
+ return this.#resolveTerminal({ kind: 'upsert', values, options })
599
+ }
600
+
601
+ [querySnapshot](): QuerySnapshot<source, row, QueryPhaseMode<phase>> {
602
+ return this.#snapshot()
603
+ }
604
+
605
+ #resolveTerminal<nextMode extends QueryExecutionMode, result>(
606
+ plan: QueryPlan<row, QuerySourcePrimaryKey<source>, nextMode>,
607
+ ): QueryTerminalResult<
608
+ Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
609
+ nextMode,
610
+ result
611
+ > {
612
+ let next = this.#withPlan(plan)
613
+ return (this.#runtime ? this.#runtime.exec(next) : next) as QueryTerminalResult<
614
+ Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
615
+ nextMode,
616
+ result
617
+ >
618
+ }
619
+
620
+ [bindQueryRuntime](
621
+ runtime: QueryRuntime,
622
+ ): Query<source, columnTypes, row, loaded, BoundQueryPhase<QueryPhaseMode<phase>>> {
623
+ return Query.#createInternal<
624
+ source,
625
+ columnTypes,
626
+ row,
627
+ loaded,
628
+ BoundQueryPhase<QueryPhaseMode<phase>>
629
+ >(
630
+ this.#table,
631
+ this.#state,
632
+ this.#plan as QueryPlan<row, QuerySourcePrimaryKey<source>, QueryPhaseMode<phase>>,
633
+ runtime,
634
+ )
635
+ }
636
+
637
+ #clone(patch: Partial<QueryState>): Query<source, columnTypes, row, loaded, phase> {
638
+ return Query.#createInternal<source, columnTypes, row, loaded, phase>(
639
+ this.#table,
640
+ {
641
+ select: patch.select ?? cloneSelection(this.#state.select),
642
+ distinct: patch.distinct ?? this.#state.distinct,
643
+ joins: patch.joins ? [...patch.joins] : [...this.#state.joins],
644
+ where: patch.where ? [...patch.where] : [...this.#state.where],
645
+ groupBy: patch.groupBy ? [...patch.groupBy] : [...this.#state.groupBy],
646
+ having: patch.having ? [...patch.having] : [...this.#state.having],
647
+ orderBy: patch.orderBy ? [...patch.orderBy] : [...this.#state.orderBy],
648
+ limit: patch.limit === undefined ? this.#state.limit : patch.limit,
649
+ offset: patch.offset === undefined ? this.#state.offset : patch.offset,
650
+ with: patch.with ? { ...patch.with } : { ...this.#state.with },
651
+ },
652
+ this.#plan as QueryPlan<row, QuerySourcePrimaryKey<source>, QueryPhaseMode<phase>>,
653
+ this.#runtime,
654
+ )
655
+ }
656
+
657
+ #withPlan<nextMode extends QueryExecutionMode>(
658
+ plan: QueryPlan<row, QuerySourcePrimaryKey<source>, nextMode>,
659
+ ): Query<source, columnTypes, row, loaded, QueryNextPhase<phase, nextMode>> {
660
+ return Query.#createInternal<source, columnTypes, row, loaded, QueryNextPhase<phase, nextMode>>(
661
+ this.#table,
662
+ this.#state,
663
+ plan,
664
+ this.#runtime,
665
+ )
666
+ }
667
+
668
+ #snapshot(): QuerySnapshot<source, row, QueryPhaseMode<phase>> {
669
+ return {
670
+ table: this.#table,
671
+ state: cloneQueryState(this.#state),
672
+ plan: cloneQueryPlan(
673
+ this.#plan as QueryPlan<row, QuerySourcePrimaryKey<source>>,
674
+ ) as QueryPlan<row, QuerySourcePrimaryKey<source>, QueryPhaseMode<phase>>,
675
+ }
676
+ }
677
+
678
+ #boundRuntime(): QueryRuntime {
679
+ if (!this.#runtime) {
680
+ throw new DataTableQueryError('Use db.exec(query) to execute an unbound Query')
681
+ }
682
+
683
+ return this.#runtime
684
+ }
685
+ }
686
+
687
+ export function query<
688
+ tableName extends string,
689
+ row extends Record<string, unknown>,
690
+ primaryKey extends readonly (keyof row & string)[],
691
+ >(
692
+ table: QueryTableInput<tableName, row, primaryKey>,
693
+ ): Query<
694
+ QueryTableInput<tableName, row, primaryKey>,
695
+ QueryColumnTypeMapFromRow<tableName, row>,
696
+ row,
697
+ {},
698
+ UnboundQueryPhase<'all'>
699
+ > {
700
+ return new Query(table) as Query<
701
+ QueryTableInput<tableName, row, primaryKey>,
702
+ QueryColumnTypeMapFromRow<tableName, row>,
703
+ row,
704
+ {},
705
+ UnboundQueryPhase<'all'>
706
+ >
707
+ }
708
+
709
+ export function cloneQueryState(state: QueryState): QueryState {
710
+ return {
711
+ select: cloneSelection(state.select),
712
+ distinct: state.distinct,
713
+ joins: [...state.joins],
714
+ where: [...state.where],
715
+ groupBy: [...state.groupBy],
716
+ having: [...state.having],
717
+ orderBy: [...state.orderBy],
718
+ limit: state.limit,
719
+ offset: state.offset,
720
+ with: { ...state.with },
721
+ }
722
+ }
723
+
724
+ function createInitialQueryState(): QueryState {
725
+ return {
726
+ select: '*',
727
+ distinct: false,
728
+ joins: [],
729
+ where: [],
730
+ groupBy: [],
731
+ having: [],
732
+ orderBy: [],
733
+ with: {},
734
+ }
735
+ }
736
+
737
+ function cloneQueryPlan<row extends Record<string, unknown>, primaryKey extends readonly string[]>(
738
+ plan: QueryPlan<row, primaryKey>,
739
+ ): QueryPlan<row, primaryKey> {
740
+ switch (plan.kind) {
741
+ case 'all':
742
+ return { kind: 'all' } as QueryPlan<row, primaryKey>
743
+ case 'first':
744
+ return { kind: 'first' } as QueryPlan<row, primaryKey>
745
+ case 'find':
746
+ return {
747
+ kind: 'find',
748
+ value: clonePrimaryKeyValue(plan.value) as PrimaryKeyInputForRow<row, primaryKey>,
749
+ } as QueryPlan<row, primaryKey>
750
+ case 'count':
751
+ return { kind: 'count' } as QueryPlan<row, primaryKey>
752
+ case 'exists':
753
+ return { kind: 'exists' } as QueryPlan<row, primaryKey>
754
+ case 'insert':
755
+ return {
756
+ kind: 'insert',
757
+ values: { ...plan.values },
758
+ options: plan.options ? { ...plan.options } : undefined,
759
+ } as QueryPlan<row, primaryKey>
760
+ case 'insertMany':
761
+ return {
762
+ kind: 'insertMany',
763
+ values: plan.values.map((value: Partial<row>) => ({ ...value })),
764
+ options: plan.options ? { ...plan.options } : undefined,
765
+ } as QueryPlan<row, primaryKey>
766
+ case 'update':
767
+ return {
768
+ kind: 'update',
769
+ changes: { ...plan.changes },
770
+ options: plan.options ? { ...plan.options } : undefined,
771
+ } as QueryPlan<row, primaryKey>
772
+ case 'delete':
773
+ return {
774
+ kind: 'delete',
775
+ options: plan.options ? { ...plan.options } : undefined,
776
+ } as QueryPlan<row, primaryKey>
777
+ case 'upsert':
778
+ return {
779
+ kind: 'upsert',
780
+ values: { ...plan.values },
781
+ options: plan.options
782
+ ? {
783
+ ...plan.options,
784
+ conflictTarget: plan.options.conflictTarget
785
+ ? [...plan.options.conflictTarget]
786
+ : undefined,
787
+ update: plan.options.update ? { ...plan.options.update } : undefined,
788
+ }
789
+ : undefined,
790
+ } as QueryPlan<row, primaryKey>
791
+ }
792
+ }
793
+
794
+ function clonePrimaryKeyValue(value: unknown): unknown {
795
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
796
+ return value
797
+ }
798
+
799
+ return { ...value }
800
+ }
801
+
802
+ function cloneSelection(selection: '*' | SelectColumn[]): '*' | SelectColumn[] {
803
+ if (selection === '*') {
804
+ return '*'
805
+ }
806
+
807
+ return selection.map((column) => ({ ...column }))
808
+ }
809
+
810
+ type WriteStatePolicy = {
811
+ where: boolean
812
+ orderBy: boolean
813
+ limit: boolean
814
+ offset: boolean
815
+ }
816
+
817
+ function assertWriteState(
818
+ state: QueryState,
819
+ operation: 'insert' | 'insertMany' | 'update' | 'delete' | 'upsert',
820
+ policy: WriteStatePolicy,
821
+ ): void {
822
+ let unsupported: string[] = []
823
+
824
+ if (state.select !== '*') unsupported.push('select()')
825
+ if (state.distinct) unsupported.push('distinct()')
826
+ if (state.joins.length > 0) unsupported.push('join()')
827
+ if (state.groupBy.length > 0) unsupported.push('groupBy()')
828
+ if (state.having.length > 0) unsupported.push('having()')
829
+ if (Object.keys(state.with).length > 0) unsupported.push('with()')
830
+ if (!policy.where && state.where.length > 0) unsupported.push('where()')
831
+ if (!policy.orderBy && state.orderBy.length > 0) unsupported.push('orderBy()')
832
+ if (!policy.limit && state.limit !== undefined) unsupported.push('limit()')
833
+ if (!policy.offset && state.offset !== undefined) unsupported.push('offset()')
834
+
835
+ if (unsupported.length > 0) {
836
+ throw new DataTableQueryError(
837
+ operation + '() does not support these query modifiers: ' + unsupported.join(', '),
838
+ )
839
+ }
840
+ }
841
+
842
+ type ResolvedPredicateColumn = {
843
+ tableName: string
844
+ columnName: string
845
+ }
846
+
847
+ function createPredicateColumnResolver(
848
+ tables: AnyTable[],
849
+ ): (column: string) => ResolvedPredicateColumn {
850
+ let qualifiedColumns = new Map<string, ResolvedPredicateColumn>()
851
+ let unqualifiedColumns = new Map<string, ResolvedPredicateColumn>()
852
+ let ambiguousColumns = new Set<string>()
853
+
854
+ for (let table of tables) {
855
+ let tableColumns = getTableColumns(table)
856
+ let tableName = getTableName(table)
857
+
858
+ for (let columnName in tableColumns) {
859
+ if (!Object.prototype.hasOwnProperty.call(tableColumns, columnName)) {
860
+ continue
861
+ }
862
+
863
+ let resolvedColumn: ResolvedPredicateColumn = {
864
+ tableName,
865
+ columnName,
866
+ }
867
+
868
+ qualifiedColumns.set(tableName + '.' + columnName, resolvedColumn)
869
+
870
+ if (ambiguousColumns.has(columnName)) {
871
+ continue
872
+ }
873
+
874
+ if (unqualifiedColumns.has(columnName)) {
875
+ unqualifiedColumns.delete(columnName)
876
+ ambiguousColumns.add(columnName)
877
+ continue
878
+ }
879
+
880
+ unqualifiedColumns.set(columnName, resolvedColumn)
881
+ }
882
+ }
883
+
884
+ return function resolveColumn(column: string): ResolvedPredicateColumn {
885
+ let qualified = qualifiedColumns.get(column)
886
+ if (qualified) return qualified
887
+
888
+ if (column.includes('.')) {
889
+ throw new DataTableQueryError('Unknown predicate column "' + column + '"')
890
+ }
891
+
892
+ if (ambiguousColumns.has(column)) {
893
+ throw new DataTableQueryError(
894
+ 'Ambiguous predicate column "' + column + '". Use a qualified column name',
895
+ )
896
+ }
897
+
898
+ let unqualified = unqualifiedColumns.get(column)
899
+
900
+ if (!unqualified) {
901
+ throw new DataTableQueryError('Unknown predicate column "' + column + '"')
902
+ }
903
+
904
+ return unqualified
905
+ }
906
+ }
907
+
908
+ function normalizePredicateValues(
909
+ predicate: Predicate,
910
+ resolveColumn: (column: string) => ResolvedPredicateColumn,
911
+ ): Predicate {
912
+ if (predicate.type === 'comparison') {
913
+ let column = resolveColumn(predicate.column)
914
+
915
+ if (predicate.valueType === 'column') {
916
+ resolveColumn(predicate.value)
917
+ return predicate
918
+ }
919
+
920
+ if (predicate.operator === 'in' || predicate.operator === 'notIn') {
921
+ if (!Array.isArray(predicate.value)) {
922
+ throw new DataTableValidationError(
923
+ 'Invalid filter value for column "' +
924
+ column.columnName +
925
+ '" in table "' +
926
+ column.tableName +
927
+ '"',
928
+ [{ message: 'Expected an array value for "' + predicate.operator + '" predicate' }],
929
+ {
930
+ metadata: {
931
+ table: column.tableName,
932
+ column: column.columnName,
933
+ },
934
+ },
935
+ )
936
+ }
937
+
938
+ return predicate
939
+ }
940
+
941
+ return predicate
942
+ }
943
+
944
+ if (predicate.type === 'between') {
945
+ resolveColumn(predicate.column)
946
+ return predicate
947
+ }
948
+
949
+ if (predicate.type === 'null') {
950
+ resolveColumn(predicate.column)
951
+ return predicate
952
+ }
953
+
954
+ return {
955
+ ...predicate,
956
+ predicates: predicate.predicates.map((child) => normalizePredicateValues(child, resolveColumn)),
957
+ }
958
+ }