@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,332 @@
1
+ import { DataTableQueryError } from '../errors.ts'
2
+ import type { QueryColumnName, QueryColumns, QueryColumnTypeMap } from '../database.ts'
3
+ import type { Predicate } from '../operators.ts'
4
+ import { and, eq, inList, or } from '../operators.ts'
5
+ import type { Query } from '../query.ts'
6
+ import { query as createQuery } from '../query.ts'
7
+ import type { AnyRelation, AnyTable, Relation } from '../table.ts'
8
+ import { getCompositeKey, getTableName, getTablePrimaryKey } from '../table.ts'
9
+
10
+ import type { QueryExecutionContext } from './execution-context.ts'
11
+ import { loadRowsWithRelationsForQuery } from './query-execution.ts'
12
+
13
+ type RelationQuery = Query<
14
+ any,
15
+ Record<string, unknown>,
16
+ Record<string, unknown>,
17
+ any,
18
+ { binding: 'unbound'; mode: 'all' }
19
+ >
20
+
21
+ export async function loadRelationsForRows(
22
+ database: QueryExecutionContext,
23
+ sourceTable: AnyTable,
24
+ rows: Record<string, unknown>[],
25
+ relationMap: Record<string, AnyRelation>,
26
+ ): Promise<Record<string, unknown>[]> {
27
+ let output = rows.map((row) => ({ ...row }))
28
+
29
+ let relationNames = Object.keys(relationMap)
30
+
31
+ for (let relationName of relationNames) {
32
+ let relation = relationMap[relationName]
33
+
34
+ if (relation.sourceTable !== sourceTable) {
35
+ throw new DataTableQueryError(
36
+ 'Relation "' +
37
+ relationName +
38
+ '" is not defined for source table "' +
39
+ getTableName(sourceTable) +
40
+ '"',
41
+ )
42
+ }
43
+
44
+ let values = await resolveRelationValues(database, output, relation)
45
+ let index = 0
46
+
47
+ while (index < output.length) {
48
+ output[index][relationName] = values[index]
49
+ index += 1
50
+ }
51
+ }
52
+
53
+ return output
54
+ }
55
+
56
+ async function resolveRelationValues(
57
+ database: QueryExecutionContext,
58
+ sourceRows: Record<string, unknown>[],
59
+ relation: AnyRelation,
60
+ ): Promise<unknown[]> {
61
+ if (relation.relationKind === 'hasManyThrough') {
62
+ return loadHasManyThroughValues(database, sourceRows, relation)
63
+ }
64
+
65
+ return loadDirectRelationValues(database, sourceRows, relation)
66
+ }
67
+
68
+ async function loadDirectRelationValues(
69
+ database: QueryExecutionContext,
70
+ sourceRows: Record<string, unknown>[],
71
+ relation: AnyRelation,
72
+ ): Promise<unknown[]> {
73
+ if (sourceRows.length === 0) {
74
+ return []
75
+ }
76
+
77
+ let sourceTuples = uniqueTuples(sourceRows, relation.sourceKey)
78
+
79
+ if (sourceTuples.length === 0) {
80
+ return sourceRows.map(() => (relation.cardinality === 'many' ? [] : null))
81
+ }
82
+
83
+ let query = createQuery(relation.targetTable)
84
+ let linkPredicate = buildLinkPredicate(relation.targetKey, sourceTuples)
85
+
86
+ if (linkPredicate) {
87
+ query = query.where(linkPredicate as Predicate<QueryColumnName<typeof relation.targetTable>>)
88
+ }
89
+
90
+ query = applyRelationModifiers(query, relation, {
91
+ includePagination: false,
92
+ })
93
+
94
+ let relatedRows = await loadRowsWithRelationsForQuery(database, query)
95
+ let grouped = groupRowsByTuple(relatedRows, relation.targetKey)
96
+
97
+ return sourceRows.map((sourceRow) => {
98
+ let key = getCompositeKey(sourceRow, relation.sourceKey)
99
+ let matches = grouped.get(key) ?? []
100
+ let pagedMatches = applyPagination(matches, relation.modifiers.limit, relation.modifiers.offset)
101
+
102
+ if (relation.cardinality === 'many') {
103
+ return pagedMatches
104
+ }
105
+
106
+ return pagedMatches[0] ?? null
107
+ })
108
+ }
109
+
110
+ async function loadHasManyThroughValues(
111
+ database: QueryExecutionContext,
112
+ sourceRows: Record<string, unknown>[],
113
+ relation: AnyRelation,
114
+ ): Promise<unknown[]> {
115
+ if (!relation.through) {
116
+ throw new DataTableQueryError('hasManyThrough relation is missing through metadata')
117
+ }
118
+
119
+ if (sourceRows.length === 0) {
120
+ return []
121
+ }
122
+
123
+ let throughRelation = relation.through.relation
124
+ let sourceTuples = uniqueTuples(sourceRows, throughRelation.sourceKey)
125
+
126
+ if (sourceTuples.length === 0) {
127
+ return sourceRows.map(() => [])
128
+ }
129
+
130
+ let throughQuery = createQuery(throughRelation.targetTable)
131
+ let throughPredicate = buildLinkPredicate(throughRelation.targetKey, sourceTuples)
132
+
133
+ if (throughPredicate) {
134
+ throughQuery = throughQuery.where(
135
+ throughPredicate as Predicate<QueryColumnName<typeof throughRelation.targetTable>>,
136
+ )
137
+ }
138
+
139
+ throughQuery = applyRelationModifiers(throughQuery, throughRelation, {
140
+ includePagination: false,
141
+ })
142
+
143
+ let throughRows = await loadRowsWithRelationsForQuery(database, throughQuery)
144
+
145
+ if (throughRows.length === 0) {
146
+ return sourceRows.map(() => [])
147
+ }
148
+
149
+ let throughRowsBySource = groupRowsByTuple(throughRows, throughRelation.targetKey)
150
+ let pagedThroughRowsBySource = new Map<string, Record<string, unknown>[]>()
151
+ let pagedThroughRows: Record<string, unknown>[] = []
152
+
153
+ for (let sourceRow of sourceRows) {
154
+ let sourceKey = getCompositeKey(sourceRow, throughRelation.sourceKey)
155
+ let matchedThroughRows = throughRowsBySource.get(sourceKey) ?? []
156
+ let pagedMatchedRows = applyPagination(
157
+ matchedThroughRows,
158
+ throughRelation.modifiers.limit,
159
+ throughRelation.modifiers.offset,
160
+ )
161
+
162
+ pagedThroughRowsBySource.set(sourceKey, pagedMatchedRows)
163
+ pagedThroughRows.push(...pagedMatchedRows)
164
+ }
165
+
166
+ let throughTuples = uniqueTuples(pagedThroughRows, relation.through.throughSourceKey)
167
+
168
+ if (throughTuples.length === 0) {
169
+ return sourceRows.map(() => [])
170
+ }
171
+
172
+ let targetQuery = createQuery(relation.targetTable)
173
+ let targetPredicate = buildLinkPredicate(relation.through.throughTargetKey, throughTuples)
174
+
175
+ if (targetPredicate) {
176
+ targetQuery = targetQuery.where(
177
+ targetPredicate as Predicate<QueryColumnName<typeof relation.targetTable>>,
178
+ )
179
+ }
180
+
181
+ targetQuery = applyRelationModifiers(targetQuery, relation, {
182
+ includePagination: false,
183
+ })
184
+
185
+ let relatedRows = await loadRowsWithRelationsForQuery(database, targetQuery)
186
+ let targetRowsByThrough = groupRowsByTuple(relatedRows, relation.through.throughTargetKey)
187
+
188
+ return sourceRows.map((sourceRow) => {
189
+ let sourceKey = getCompositeKey(sourceRow, throughRelation.sourceKey)
190
+ let matchedThroughRows = pagedThroughRowsBySource.get(sourceKey) ?? []
191
+ let outputRows: Record<string, unknown>[] = []
192
+ let seen = new Set<string>()
193
+
194
+ for (let throughRow of matchedThroughRows) {
195
+ let throughKey = getCompositeKey(throughRow, relation.through!.throughSourceKey)
196
+ let rowsForThrough = targetRowsByThrough.get(throughKey) ?? []
197
+
198
+ for (let row of rowsForThrough) {
199
+ let rowIdentity = getCompositeKey(row, getTablePrimaryKey(relation.targetTable))
200
+
201
+ if (!seen.has(rowIdentity)) {
202
+ seen.add(rowIdentity)
203
+ outputRows.push(row)
204
+ }
205
+ }
206
+ }
207
+
208
+ return applyPagination(outputRows, relation.modifiers.limit, relation.modifiers.offset)
209
+ })
210
+ }
211
+
212
+ function applyRelationModifiers<table extends AnyTable>(
213
+ query: RelationQuery,
214
+ relation: Relation<any, table, any, any>,
215
+ options: { includePagination: boolean },
216
+ ): RelationQuery {
217
+ let next = query
218
+
219
+ for (let predicate of relation.modifiers.where) {
220
+ next = next.where(predicate)
221
+ }
222
+
223
+ for (let clause of relation.modifiers.orderBy) {
224
+ next = next.orderBy(clause.column as QueryColumns<QueryColumnTypeMap<table>>, clause.direction)
225
+ }
226
+
227
+ if (options.includePagination && relation.modifiers.limit !== undefined) {
228
+ next = next.limit(relation.modifiers.limit)
229
+ }
230
+
231
+ if (options.includePagination && relation.modifiers.offset !== undefined) {
232
+ next = next.offset(relation.modifiers.offset)
233
+ }
234
+
235
+ if (Object.keys(relation.modifiers.with).length > 0) {
236
+ next = next.with(relation.modifiers.with)
237
+ }
238
+
239
+ return next
240
+ }
241
+
242
+ function applyPagination<row>(
243
+ rows: row[],
244
+ limit: number | undefined,
245
+ offset: number | undefined,
246
+ ): row[] {
247
+ let offsetRows = offset === undefined ? rows : rows.slice(offset)
248
+ return limit === undefined ? offsetRows : offsetRows.slice(0, limit)
249
+ }
250
+
251
+ function uniqueTuples(rows: Record<string, unknown>[], columns: string[]): unknown[][] {
252
+ let output: unknown[][] = []
253
+ let seen = new Set<string>()
254
+
255
+ for (let row of rows) {
256
+ let tuple = columns.map((column) => row[column])
257
+ let key = tuple.map(stringifyForKey).join('::')
258
+
259
+ if (!seen.has(key)) {
260
+ seen.add(key)
261
+ output.push(tuple)
262
+ }
263
+ }
264
+
265
+ return output
266
+ }
267
+
268
+ function buildLinkPredicate(targetColumns: string[], tuples: unknown[][]): Predicate | undefined {
269
+ if (tuples.length === 0) {
270
+ return undefined
271
+ }
272
+
273
+ if (targetColumns.length === 1) {
274
+ return inList(
275
+ targetColumns[0],
276
+ tuples.map((tuple) => tuple[0]),
277
+ )
278
+ }
279
+
280
+ let tuplePredicates = tuples.map((tuple) => {
281
+ let comparisons = targetColumns.map((column, index) => eq(column, tuple[index]))
282
+
283
+ return and(...comparisons)
284
+ })
285
+
286
+ return or(...tuplePredicates)
287
+ }
288
+
289
+ function groupRowsByTuple(
290
+ rows: Record<string, unknown>[],
291
+ columns: string[],
292
+ ): Map<string, Record<string, unknown>[]> {
293
+ let output = new Map<string, Record<string, unknown>[]>()
294
+
295
+ for (let row of rows) {
296
+ let key = getCompositeKey(row, columns)
297
+ let group = output.get(key)
298
+
299
+ if (group) {
300
+ group.push(row)
301
+ continue
302
+ }
303
+
304
+ output.set(key, [row])
305
+ }
306
+
307
+ return output
308
+ }
309
+
310
+ function stringifyForKey(value: unknown): string {
311
+ if (value === null) {
312
+ return 'null'
313
+ }
314
+
315
+ if (value === undefined) {
316
+ return 'undefined'
317
+ }
318
+
319
+ if (value instanceof Date) {
320
+ return 'date:' + value.toISOString()
321
+ }
322
+
323
+ if (typeof value === 'string') {
324
+ return JSON.stringify(value)
325
+ }
326
+
327
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
328
+ return String(value)
329
+ }
330
+
331
+ return JSON.stringify(value)
332
+ }