@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.
- package/README.md +307 -56
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/lib/adapter.d.ts +386 -16
- package/dist/lib/adapter.d.ts.map +1 -1
- package/dist/lib/column.d.ts +193 -0
- package/dist/lib/column.d.ts.map +1 -0
- package/dist/lib/column.js +302 -0
- package/dist/lib/database/execution-context.d.ts +10 -0
- package/dist/lib/database/execution-context.d.ts.map +1 -0
- package/dist/lib/database/execution-context.js +1 -0
- package/dist/lib/database/helpers.d.ts +26 -0
- package/dist/lib/database/helpers.d.ts.map +1 -0
- package/dist/lib/database/helpers.js +116 -0
- package/dist/lib/database/query-execution.d.ts +7 -0
- package/dist/lib/database/query-execution.d.ts.map +1 -0
- package/dist/lib/database/query-execution.js +401 -0
- package/dist/lib/database/relations.d.ts +4 -0
- package/dist/lib/database/relations.d.ts.map +1 -0
- package/dist/lib/database/relations.js +207 -0
- package/dist/lib/database/write-lifecycle.d.ts +13 -0
- package/dist/lib/database/write-lifecycle.d.ts.map +1 -0
- package/dist/lib/database/write-lifecycle.js +279 -0
- package/dist/lib/database.d.ts +141 -238
- package/dist/lib/database.d.ts.map +1 -1
- package/dist/lib/database.js +73 -1122
- package/dist/lib/errors.d.ts +9 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +9 -0
- package/dist/lib/migrations/filename.d.ts +12 -0
- package/dist/lib/migrations/filename.d.ts.map +1 -0
- package/dist/lib/migrations/filename.js +20 -0
- package/dist/lib/migrations/helpers.d.ts +11 -0
- package/dist/lib/migrations/helpers.d.ts.map +1 -0
- package/dist/lib/migrations/helpers.js +77 -0
- package/dist/lib/migrations/journal-store.d.ts +15 -0
- package/dist/lib/migrations/journal-store.d.ts.map +1 -0
- package/dist/lib/migrations/journal-store.js +83 -0
- package/dist/lib/migrations/registry.d.ts +27 -0
- package/dist/lib/migrations/registry.d.ts.map +1 -0
- package/dist/lib/migrations/registry.js +51 -0
- package/dist/lib/migrations/runner.d.ts +20 -0
- package/dist/lib/migrations/runner.d.ts.map +1 -0
- package/dist/lib/migrations/runner.js +273 -0
- package/dist/lib/migrations/schema-api.d.ts +7 -0
- package/dist/lib/migrations/schema-api.d.ts.map +1 -0
- package/dist/lib/migrations/schema-api.js +326 -0
- package/dist/lib/migrations-node.d.ts +17 -0
- package/dist/lib/migrations-node.d.ts.map +1 -0
- package/dist/lib/migrations-node.js +65 -0
- package/dist/lib/migrations.d.ts +292 -0
- package/dist/lib/migrations.d.ts.map +1 -0
- package/dist/lib/migrations.js +38 -0
- package/dist/lib/operators.d.ts +3 -0
- package/dist/lib/operators.d.ts.map +1 -1
- package/dist/lib/query.d.ts +159 -0
- package/dist/lib/query.d.ts.map +1 -0
- package/dist/lib/query.js +401 -0
- package/dist/lib/references.d.ts +0 -1
- package/dist/lib/references.d.ts.map +1 -1
- package/dist/lib/sql-helpers.d.ts +50 -0
- package/dist/lib/sql-helpers.d.ts.map +1 -0
- package/dist/lib/sql-helpers.js +111 -0
- package/dist/lib/sql.d.ts +23 -6
- package/dist/lib/sql.d.ts.map +1 -1
- package/dist/lib/sql.js +19 -5
- package/dist/lib/table.d.ts +355 -40
- package/dist/lib/table.d.ts.map +1 -1
- package/dist/lib/table.js +113 -90
- package/dist/migrations/node.d.ts +2 -0
- package/dist/migrations/node.d.ts.map +1 -0
- package/dist/migrations/node.js +1 -0
- package/dist/migrations.d.ts +8 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +5 -0
- package/dist/operators.d.ts +3 -0
- package/dist/operators.d.ts.map +1 -0
- package/dist/operators.js +1 -0
- package/dist/sql-helpers.d.ts +3 -0
- package/dist/sql-helpers.d.ts.map +1 -0
- package/dist/sql-helpers.js +1 -0
- package/package.json +24 -10
- package/src/index.ts +93 -10
- package/src/lib/adapter.ts +469 -25
- package/src/lib/column.ts +384 -0
- package/src/lib/database/execution-context.ts +15 -0
- package/src/lib/database/helpers.ts +216 -0
- package/src/lib/database/query-execution.ts +638 -0
- package/src/lib/database/relations.ts +332 -0
- package/src/lib/database/write-lifecycle.ts +487 -0
- package/src/lib/database.ts +246 -1848
- package/src/lib/errors.ts +10 -0
- package/src/lib/migrations/filename.ts +25 -0
- package/src/lib/migrations/helpers.ts +108 -0
- package/src/lib/migrations/journal-store.ts +122 -0
- package/src/lib/migrations/registry.ts +62 -0
- package/src/lib/migrations/runner.ts +374 -0
- package/src/lib/migrations/schema-api.ts +417 -0
- package/src/lib/migrations-node.ts +71 -0
- package/src/lib/migrations.ts +328 -0
- package/src/lib/operators.ts +3 -0
- package/src/lib/query.ts +958 -0
- package/src/lib/references.ts +0 -1
- package/src/lib/sql-helpers.ts +146 -0
- package/src/lib/sql.ts +23 -6
- package/src/lib/table.ts +484 -156
- package/src/migrations/node.ts +1 -0
- package/src/migrations.ts +26 -0
- package/src/operators.ts +18 -0
- 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
|
+
}
|