@pothos/plugin-drizzle 0.17.6 → 0.18.0
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/CHANGELOG.md +33 -0
- package/dts/connection-helpers.d.ts.map +1 -1
- package/dts/drizzle-field-builder.d.ts.map +1 -1
- package/dts/types.d.ts +5 -2
- package/dts/types.d.ts.map +1 -1
- package/dts/utils/config.d.ts +3 -0
- package/dts/utils/config.d.ts.map +1 -1
- package/dts/utils/cursors.d.ts +11 -7
- package/dts/utils/cursors.d.ts.map +1 -1
- package/esm/connection-helpers.d.ts.map +1 -1
- package/esm/connection-helpers.js +5 -4
- package/esm/connection-helpers.js.map +1 -1
- package/esm/drizzle-field-builder.d.ts.map +1 -1
- package/esm/drizzle-field-builder.js +5 -4
- package/esm/drizzle-field-builder.js.map +1 -1
- package/esm/types.d.ts +5 -2
- package/esm/types.d.ts.map +1 -1
- package/esm/types.js.map +1 -1
- package/esm/utils/config.d.ts +3 -0
- package/esm/utils/config.d.ts.map +1 -1
- package/esm/utils/config.js +95 -17
- package/esm/utils/config.js.map +1 -1
- package/esm/utils/cursors.d.ts +11 -7
- package/esm/utils/cursors.d.ts.map +1 -1
- package/esm/utils/cursors.js +194 -113
- package/esm/utils/cursors.js.map +1 -1
- package/lib/connection-helpers.js +5 -4
- package/lib/connection-helpers.js.map +1 -1
- package/lib/drizzle-field-builder.js +5 -4
- package/lib/drizzle-field-builder.js.map +1 -1
- package/lib/types.js.map +1 -1
- package/lib/utils/config.js +95 -17
- package/lib/utils/config.js.map +1 -1
- package/lib/utils/cursors.js +196 -112
- package/lib/utils/cursors.js.map +1 -1
- package/package.json +5 -5
- package/src/connection-helpers.ts +6 -4
- package/src/drizzle-field-builder.ts +7 -4
- package/src/types.ts +8 -4
- package/src/utils/config.ts +117 -20
- package/src/utils/cursors.ts +248 -87
package/src/utils/cursors.ts
CHANGED
|
@@ -6,10 +6,17 @@ import {
|
|
|
6
6
|
type SchemaTypes,
|
|
7
7
|
} from '@pothos/core';
|
|
8
8
|
import {
|
|
9
|
+
asc,
|
|
9
10
|
type Column,
|
|
10
11
|
type DBQueryConfig,
|
|
12
|
+
desc,
|
|
13
|
+
eq,
|
|
11
14
|
getColumns,
|
|
15
|
+
gt,
|
|
16
|
+
isNull,
|
|
17
|
+
lt,
|
|
12
18
|
type SQL,
|
|
19
|
+
sql,
|
|
13
20
|
type Table,
|
|
14
21
|
type TableRelationalConfig,
|
|
15
22
|
} from 'drizzle-orm';
|
|
@@ -81,21 +88,40 @@ export function getIDSerializer(fields: Column[], config: PothosDrizzleSchemaCon
|
|
|
81
88
|
};
|
|
82
89
|
}
|
|
83
90
|
|
|
84
|
-
|
|
91
|
+
// A cursor is built from the ordering, and the ordering can name a column or an
|
|
92
|
+
// extra the query selected. Either way what the cursor needs is the key the
|
|
93
|
+
// value arrives under on the row.
|
|
94
|
+
export type CursorField = Column | string;
|
|
95
|
+
|
|
96
|
+
export function cursorFieldKey(field: CursorField, config: PothosDrizzleSchemaConfig) {
|
|
97
|
+
return typeof field === 'string' ? field : config.columnToTsName(field);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function getColumnSerializer(
|
|
101
|
+
fields: readonly CursorField[],
|
|
102
|
+
config: PothosDrizzleSchemaConfig,
|
|
103
|
+
) {
|
|
85
104
|
if (fields.length === 0) {
|
|
86
105
|
throw new PothosValidationError('Column serializer must have at least one field');
|
|
87
106
|
}
|
|
88
107
|
|
|
89
108
|
return (value: Record<string, unknown>) => {
|
|
90
109
|
if (fields.length > 1) {
|
|
91
|
-
|
|
110
|
+
// each value carries its own type tag, the same ones a single value gets.
|
|
111
|
+
// Plain JSON would turn a Date into a string and refuse a bigint outright.
|
|
112
|
+
return `T:${JSON.stringify(
|
|
113
|
+
fields.map((field) => formatCursorChunk(value[cursorFieldKey(field, config)])),
|
|
114
|
+
)}`;
|
|
92
115
|
}
|
|
93
116
|
|
|
94
|
-
return formatCursorChunk(value[
|
|
117
|
+
return formatCursorChunk(value[cursorFieldKey(fields[0], config)]);
|
|
95
118
|
};
|
|
96
119
|
}
|
|
97
120
|
|
|
98
|
-
export function getCursorFormatter(
|
|
121
|
+
export function getCursorFormatter(
|
|
122
|
+
fields: readonly CursorField[],
|
|
123
|
+
config: PothosDrizzleSchemaConfig,
|
|
124
|
+
) {
|
|
99
125
|
if (fields.length === 0) {
|
|
100
126
|
throw new PothosValidationError('Cursor must have at least one field');
|
|
101
127
|
}
|
|
@@ -124,13 +150,13 @@ export function parseDrizzleCursor(cursor: unknown) {
|
|
|
124
150
|
}
|
|
125
151
|
}
|
|
126
152
|
|
|
127
|
-
export function parseSerializedDrizzleColumn(value: unknown) {
|
|
153
|
+
export function parseSerializedDrizzleColumn(value: unknown): unknown {
|
|
128
154
|
if (typeof value !== 'string') {
|
|
129
155
|
throw new PothosValidationError('value must be a string');
|
|
130
156
|
}
|
|
131
157
|
|
|
132
158
|
try {
|
|
133
|
-
const [, type, rawValue] = value.match(/^(S|N|D|J|I):(.*)/) as [string, string, string];
|
|
159
|
+
const [, type, rawValue] = value.match(/^(S|N|D|J|I|T):(.*)/) as [string, string, string];
|
|
134
160
|
|
|
135
161
|
switch (type) {
|
|
136
162
|
case 'S':
|
|
@@ -140,7 +166,13 @@ export function parseSerializedDrizzleColumn(value: unknown) {
|
|
|
140
166
|
case 'D':
|
|
141
167
|
return new Date(Number.parseInt(rawValue, 10));
|
|
142
168
|
case 'J':
|
|
169
|
+
// compound values from before each one carried a tag: whatever JSON
|
|
170
|
+
// preserved is the best that can be recovered
|
|
143
171
|
return JSON.parse(rawValue) as unknown;
|
|
172
|
+
case 'T':
|
|
173
|
+
return (JSON.parse(rawValue) as (string | null)[]).map((chunk) =>
|
|
174
|
+
chunk === null ? null : parseSerializedDrizzleColumn(chunk),
|
|
175
|
+
);
|
|
144
176
|
case 'I':
|
|
145
177
|
return BigInt(rawValue);
|
|
146
178
|
default:
|
|
@@ -259,28 +291,38 @@ export function getColumnParser(fields: readonly Column[]) {
|
|
|
259
291
|
};
|
|
260
292
|
}
|
|
261
293
|
|
|
262
|
-
export function getCursorParser(
|
|
263
|
-
if (
|
|
294
|
+
export function getCursorParser(keys: readonly string[]) {
|
|
295
|
+
if (keys.length === 0) {
|
|
264
296
|
throw new PothosValidationError('Cursor must have at least one field');
|
|
265
297
|
}
|
|
266
298
|
|
|
267
299
|
return (cursor: unknown) => {
|
|
268
|
-
const parsed = parseDrizzleCursor(cursor)
|
|
300
|
+
const parsed = parseDrizzleCursor(cursor);
|
|
269
301
|
|
|
270
|
-
if (
|
|
271
|
-
return { [
|
|
302
|
+
if (keys.length === 1) {
|
|
303
|
+
return { [keys[0]]: parsed };
|
|
272
304
|
}
|
|
273
305
|
|
|
274
|
-
|
|
306
|
+
// A cursor issued before a column joined the ordering only holds values for
|
|
307
|
+
// the columns that came before it. Those still describe a position, just a
|
|
308
|
+
// less precise one, so the page is keyed off the prefix the cursor covers
|
|
309
|
+
// rather than rejected. Cursors returned by that page carry every column.
|
|
310
|
+
const values = Array.isArray(parsed) ? parsed : [parsed];
|
|
311
|
+
|
|
312
|
+
if (values.length === 0) {
|
|
313
|
+
throw new PothosValidationError('Cursor contains no values');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (values.length > keys.length) {
|
|
275
317
|
throw new PothosValidationError(
|
|
276
|
-
`Expected
|
|
318
|
+
`Expected cursor to contain at most ${keys.length} values, but got ${values.length}`,
|
|
277
319
|
);
|
|
278
320
|
}
|
|
279
321
|
|
|
280
322
|
const record: Record<string, unknown> = {};
|
|
281
323
|
|
|
282
|
-
|
|
283
|
-
record[
|
|
324
|
+
values.forEach((value, i) => {
|
|
325
|
+
record[keys[i]] = value;
|
|
284
326
|
});
|
|
285
327
|
|
|
286
328
|
return record;
|
|
@@ -293,33 +335,113 @@ export interface DrizzleCursorConnectionQueryOptions {
|
|
|
293
335
|
defaultSize?: number | ((args: {}, ctx: {}) => number);
|
|
294
336
|
maxSize?: number | ((args: {}, ctx: {}) => number);
|
|
295
337
|
orderBy: ConnectionOrderBy<TableRelationalConfig>;
|
|
338
|
+
extras?: Record<string, OrderByExpression | undefined>;
|
|
296
339
|
where?: SQL;
|
|
297
340
|
config: PothosDrizzleSchemaConfig;
|
|
298
341
|
table: TableRelationalConfig;
|
|
299
342
|
}
|
|
300
343
|
|
|
344
|
+
export type OrderByExpression = SQL | ((table: never, operators: never) => SQL);
|
|
345
|
+
|
|
346
|
+
// An ordering entry is either a column or an expression the query selected as
|
|
347
|
+
// an extra. `key` is where the value lands on the row, and so what the cursor
|
|
348
|
+
// is built from; `target` is what SQL orders and compares.
|
|
349
|
+
type OrderByEntry = {
|
|
350
|
+
direction: 'asc' | 'desc';
|
|
351
|
+
key: string;
|
|
352
|
+
column?: Column;
|
|
353
|
+
expression?: OrderByExpression;
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
function resolveExpression(expression: OrderByExpression, table: Table) {
|
|
357
|
+
return typeof expression === 'function'
|
|
358
|
+
? (expression as (table: Table, operators: { sql: typeof sql }) => SQL)(table, { sql })
|
|
359
|
+
: expression;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Drizzle aliases the table it is querying (`d0`), so ordering and comparing
|
|
363
|
+
// have to reach for the column on that alias rather than the original table.
|
|
364
|
+
// Expressions get the alias handed to them and resolve themselves.
|
|
365
|
+
function orderTarget(entry: OrderByEntry, table: Table) {
|
|
366
|
+
if (entry.column) {
|
|
367
|
+
return (table as unknown as Record<string, Column>)[entry.key] ?? entry.column;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return resolveExpression(entry.expression!, table);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function flipDirection(direction: 'asc' | 'desc') {
|
|
374
|
+
return direction === 'asc' ? 'desc' : 'asc';
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function ordersBy(entries: OrderByEntry[], column: Column) {
|
|
378
|
+
return entries.some((entry) => entry.column?.name === column.name);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// The ordering fixes a row's position only if some set of columns the database
|
|
382
|
+
// keeps unique is fully covered by it. A nullable column can't contribute:
|
|
383
|
+
// rows sharing a null tie with each other, and a cursor compared against null
|
|
384
|
+
// matches nothing.
|
|
385
|
+
function orderIsUnique(
|
|
386
|
+
entries: OrderByEntry[],
|
|
387
|
+
config: PothosDrizzleSchemaConfig,
|
|
388
|
+
table: TableRelationalConfig,
|
|
389
|
+
) {
|
|
390
|
+
return config
|
|
391
|
+
.getUniqueConstraints(table.name)
|
|
392
|
+
.some((columns) => columns.every((column) => column.notNull && ordersBy(entries, column)));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// A cursor names a row's position in the ordering, which only works if no two
|
|
396
|
+
// rows can share a position. Ordering by a column with duplicate values (a
|
|
397
|
+
// timestamp, a status) leaves ties to be broken arbitrarily, so a row can move
|
|
398
|
+
// between pages and end up returned twice or skipped entirely. Appending a key
|
|
399
|
+
// the database keeps unique fixes that. Key columns already in the ordering are
|
|
400
|
+
// left where the user put them.
|
|
401
|
+
function appendTieBreaker(
|
|
402
|
+
entries: OrderByEntry[],
|
|
403
|
+
config: PothosDrizzleSchemaConfig,
|
|
404
|
+
table: TableRelationalConfig,
|
|
405
|
+
) {
|
|
406
|
+
if (orderIsUnique(entries, config, table)) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const tieBreaker = config.findTieBreaker(table.name);
|
|
411
|
+
|
|
412
|
+
if (!tieBreaker) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const direction = entries[entries.length - 1]?.direction ?? 'asc';
|
|
417
|
+
|
|
418
|
+
for (const column of tieBreaker) {
|
|
419
|
+
if (!ordersBy(entries, column)) {
|
|
420
|
+
entries.push({ direction, key: config.columnToTsName(column), column });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
301
425
|
function parseOrderBy(
|
|
302
426
|
config: PothosDrizzleSchemaConfig,
|
|
303
427
|
table: TableRelationalConfig,
|
|
304
428
|
orderBy: ConnectionOrderBy<TableRelationalConfig>,
|
|
305
429
|
invert: boolean,
|
|
430
|
+
extras?: Record<string, OrderByExpression | undefined>,
|
|
306
431
|
) {
|
|
307
|
-
const
|
|
308
|
-
const columns: Column[] = [];
|
|
309
|
-
const normalized: { direction: 'asc' | 'desc'; column: Column }[] = [];
|
|
432
|
+
const normalized: OrderByEntry[] = [];
|
|
310
433
|
|
|
311
|
-
const
|
|
312
|
-
|
|
434
|
+
const columnEntry = (column: Column, direction: 'asc' | 'desc') => ({
|
|
435
|
+
direction,
|
|
436
|
+
key: config.columnToTsName(column),
|
|
437
|
+
column,
|
|
438
|
+
});
|
|
313
439
|
|
|
314
440
|
if ('table' in orderBy && orderBy.table && typeof orderBy.table === 'object') {
|
|
315
|
-
|
|
316
|
-
columns.push(orderBy as Column);
|
|
317
|
-
normalized.push({ direction: 'asc', column: orderBy as Column });
|
|
441
|
+
normalized.push(columnEntry(orderBy as unknown as Column, 'asc'));
|
|
318
442
|
} else if (Array.isArray(orderBy)) {
|
|
319
443
|
for (const field of orderBy) {
|
|
320
|
-
|
|
321
|
-
columns.push(field);
|
|
322
|
-
normalized.push({ direction: 'asc', column: field });
|
|
444
|
+
normalized.push(columnEntry(field, 'asc'));
|
|
323
445
|
}
|
|
324
446
|
} else {
|
|
325
447
|
const tableColumns = getColumns(table.table as Table);
|
|
@@ -328,28 +450,113 @@ function parseOrderBy(
|
|
|
328
450
|
[k: string]: 'asc' | 'desc' | undefined;
|
|
329
451
|
},
|
|
330
452
|
).forEach(([name, direction]) => {
|
|
331
|
-
if (direction) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
453
|
+
if (!direction) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const column = tableColumns[name];
|
|
458
|
+
|
|
459
|
+
if (column) {
|
|
460
|
+
normalized.push(columnEntry(column, direction));
|
|
461
|
+
return;
|
|
336
462
|
}
|
|
463
|
+
|
|
464
|
+
const expression = extras?.[name];
|
|
465
|
+
|
|
466
|
+
if (!expression) {
|
|
467
|
+
throw new PothosValidationError(
|
|
468
|
+
`Can't order by "${name}": ${table.name} has no such column, and the query passed to the connection declares no extra with that name`,
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
normalized.push({ direction, key: name, expression });
|
|
337
473
|
});
|
|
338
474
|
}
|
|
339
475
|
|
|
476
|
+
appendTieBreaker(normalized, config, table);
|
|
477
|
+
|
|
478
|
+
const directionFor = ({ direction }: OrderByEntry) =>
|
|
479
|
+
invert ? flipDirection(direction) : direction;
|
|
480
|
+
|
|
340
481
|
return {
|
|
341
482
|
normalized,
|
|
342
|
-
columns
|
|
343
|
-
|
|
483
|
+
// only real columns need adding to the selection; extras are already selected
|
|
484
|
+
columns: normalized.flatMap(({ column }) => (column ? [column] : [])),
|
|
485
|
+
cursorFields: normalized.map(({ column, key }) => column ?? key),
|
|
486
|
+
// the object form can only name columns, so an expression forces the
|
|
487
|
+
// callback form -- which renders identically for the columns beside it
|
|
488
|
+
orderBy: normalized.some(({ expression }) => expression)
|
|
489
|
+
? (t: Table) =>
|
|
490
|
+
normalized.map((entry) =>
|
|
491
|
+
(directionFor(entry) === 'asc' ? asc : desc)(orderTarget(entry, t)),
|
|
492
|
+
)
|
|
493
|
+
: Object.fromEntries(normalized.map((entry) => [entry.key, directionFor(entry)])),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Rows past the cursor are those where the first ordering column that differs
|
|
498
|
+
// from the cursor differs in the direction being paged. Columns the cursor
|
|
499
|
+
// doesn't cover are left out: it was issued for a shorter ordering, and its
|
|
500
|
+
// prefix still describes a position.
|
|
501
|
+
function compareTo(entry: OrderByEntry, operator: 'gt' | 'lt' | 'eq', value: unknown) {
|
|
502
|
+
const isNullish = value === null || value === undefined;
|
|
503
|
+
|
|
504
|
+
if (entry.column) {
|
|
505
|
+
if (operator === 'eq' && isNullish) {
|
|
506
|
+
return { [entry.key]: { isNull: true } };
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// never the `{ column: value }` shorthand: drizzle reads any object value
|
|
510
|
+
// as a nested filter, so a null throws and a Date silently drops the
|
|
511
|
+
// comparison. Naming the operator works for every value a cursor can hold
|
|
512
|
+
return { [entry.key]: { [operator]: value } };
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (operator === 'eq' && isNullish) {
|
|
516
|
+
return { RAW: (table: Table) => isNull(orderTarget(entry, table) as SQL) };
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const operators = { gt, lt, eq };
|
|
520
|
+
|
|
521
|
+
// RAW hands the filter the table it is being built against, which is what an
|
|
522
|
+
// expression needs to resolve
|
|
523
|
+
return {
|
|
524
|
+
RAW: (table: Table) => operators[operator](orderTarget(entry, table) as SQL, value),
|
|
344
525
|
};
|
|
345
526
|
}
|
|
346
527
|
|
|
528
|
+
function keysetFilter(entries: OrderByEntry[], cursor: string, paging: 'after' | 'before') {
|
|
529
|
+
const parsedCursor = getCursorParser(entries.map(({ key }) => key))(cursor);
|
|
530
|
+
const covered = entries.filter(({ key }) => key in parsedCursor);
|
|
531
|
+
|
|
532
|
+
const parts = covered.map((entry, index) => {
|
|
533
|
+
const ascending = paging === 'after' ? entry.direction === 'asc' : entry.direction === 'desc';
|
|
534
|
+
const compare = compareTo(entry, ascending ? 'gt' : 'lt', parsedCursor[entry.key]);
|
|
535
|
+
|
|
536
|
+
if (index === 0) {
|
|
537
|
+
return compare;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return {
|
|
541
|
+
AND: [
|
|
542
|
+
...covered
|
|
543
|
+
.slice(0, index)
|
|
544
|
+
.map((previous) => compareTo(previous, 'eq', parsedCursor[previous.key])),
|
|
545
|
+
compare,
|
|
546
|
+
],
|
|
547
|
+
};
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
return parts.length > 1 ? { OR: parts } : parts[0];
|
|
551
|
+
}
|
|
552
|
+
|
|
347
553
|
export function drizzleCursorConnectionQuery({
|
|
348
554
|
args,
|
|
349
555
|
ctx,
|
|
350
556
|
maxSize = DEFAULT_MAX_SIZE,
|
|
351
557
|
defaultSize = DEFAULT_SIZE,
|
|
352
558
|
orderBy,
|
|
559
|
+
extras,
|
|
353
560
|
where,
|
|
354
561
|
config,
|
|
355
562
|
table,
|
|
@@ -376,7 +583,7 @@ export function drizzleCursorConnectionQuery({
|
|
|
376
583
|
const limit = Math.min(first ?? last ?? defaultSizeForConnection, maxSizeForConnection) + 1;
|
|
377
584
|
const inverted = !first && !!last;
|
|
378
585
|
|
|
379
|
-
const parsedOrderBy = parseOrderBy(config, table, orderBy, inverted);
|
|
586
|
+
const parsedOrderBy = parseOrderBy(config, table, orderBy, inverted, extras);
|
|
380
587
|
|
|
381
588
|
const columns: Record<string, boolean> = {};
|
|
382
589
|
|
|
@@ -391,63 +598,15 @@ export function drizzleCursorConnectionQuery({
|
|
|
391
598
|
}
|
|
392
599
|
|
|
393
600
|
if (after) {
|
|
394
|
-
|
|
395
|
-
const parsedCursor = cursorParser(after);
|
|
396
|
-
|
|
397
|
-
const parts = parsedOrderBy.normalized.map(({ direction, column }, index) => {
|
|
398
|
-
const columnName = config.columnToTsName(column);
|
|
399
|
-
const compare =
|
|
400
|
-
direction === 'asc'
|
|
401
|
-
? { [columnName]: { gt: parsedCursor[column.name] } }
|
|
402
|
-
: { [columnName]: { lt: parsedCursor[column.name] } };
|
|
403
|
-
|
|
404
|
-
if (index === 0) {
|
|
405
|
-
return compare;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return {
|
|
409
|
-
AND: [
|
|
410
|
-
...parsedOrderBy.normalized.slice(0, index).map(({ column: c }) => {
|
|
411
|
-
return { [config.columnToTsName(c)]: parsedCursor[c.name] };
|
|
412
|
-
}),
|
|
413
|
-
compare,
|
|
414
|
-
],
|
|
415
|
-
};
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
whereClauses.push(parts.length > 1 ? { OR: parts } : parts[0]);
|
|
601
|
+
whereClauses.push(keysetFilter(parsedOrderBy.normalized, after, 'after'));
|
|
419
602
|
}
|
|
420
603
|
|
|
421
604
|
if (before) {
|
|
422
|
-
|
|
423
|
-
const parsedCursor = cursorParser(before);
|
|
424
|
-
|
|
425
|
-
const parts = parsedOrderBy.normalized.map(({ direction, column }, index) => {
|
|
426
|
-
const columnName = config.columnToTsName(column);
|
|
427
|
-
const compare =
|
|
428
|
-
direction === 'desc'
|
|
429
|
-
? { [columnName]: { gt: parsedCursor[column.name] } }
|
|
430
|
-
: { [columnName]: { lt: parsedCursor[column.name] } };
|
|
431
|
-
|
|
432
|
-
if (index === 0) {
|
|
433
|
-
return compare;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
return {
|
|
437
|
-
AND: [
|
|
438
|
-
...parsedOrderBy.normalized.slice(0, index).map(({ column: c }) => {
|
|
439
|
-
return { [config.columnToTsName(c)]: parsedCursor[c.name] };
|
|
440
|
-
}),
|
|
441
|
-
compare,
|
|
442
|
-
],
|
|
443
|
-
};
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
whereClauses.push(parts.length > 1 ? { OR: parts } : parts[0]);
|
|
605
|
+
whereClauses.push(keysetFilter(parsedOrderBy.normalized, before, 'before'));
|
|
447
606
|
}
|
|
448
607
|
|
|
449
608
|
return omitUndefinedKeys({
|
|
450
|
-
|
|
609
|
+
cursorFields: parsedOrderBy.cursorFields,
|
|
451
610
|
columns,
|
|
452
611
|
orderBy: parsedOrderBy.orderBy,
|
|
453
612
|
limit,
|
|
@@ -527,21 +686,23 @@ export async function resolveDrizzleCursorConnection<T extends {}>(
|
|
|
527
686
|
let query: DBQueryConfig<'many'> | undefined;
|
|
528
687
|
let formatter: (node: Record<string, unknown>) => string;
|
|
529
688
|
const results = await resolve((q = {}) => {
|
|
530
|
-
const {
|
|
689
|
+
const { cursorFields, ...connectionQuery } = drizzleCursorConnectionQuery({
|
|
531
690
|
...options,
|
|
532
691
|
config,
|
|
533
692
|
orderBy:
|
|
534
693
|
(typeof q.orderBy === 'function' ? q.orderBy(table.table as Table) : q.orderBy) ??
|
|
535
694
|
config.getPrimaryKey(table.name),
|
|
695
|
+
extras: q.extras as DrizzleCursorConnectionQueryOptions['extras'],
|
|
536
696
|
table,
|
|
537
697
|
});
|
|
538
|
-
formatter = getCursorFormatter(
|
|
698
|
+
formatter = getCursorFormatter(cursorFields, config);
|
|
539
699
|
|
|
540
700
|
query = queryFromInfo({
|
|
541
701
|
context: options.ctx,
|
|
542
702
|
info,
|
|
543
703
|
select: omitUndefinedKeys({
|
|
544
704
|
...connectionQuery,
|
|
705
|
+
extras: q.extras,
|
|
545
706
|
columns: {
|
|
546
707
|
...q.columns,
|
|
547
708
|
...connectionQuery.columns,
|