@vantreeseba/drizzle-graphql 5.2.0 → 5.3.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/README.md +47 -0
- package/dist/README.md +47 -0
- package/dist/index.cjs +217 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +218 -12
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -405,6 +405,53 @@ supplies no `orderBy`:
|
|
|
405
405
|
An explicit `orderBy` always takes precedence, composite primary keys are ordered by every
|
|
406
406
|
key column, and an unpaginated list query is left unordered so no sort is paid for.
|
|
407
407
|
|
|
408
|
+
## Cursor pagination
|
|
409
|
+
|
|
410
|
+
`OFFSET` pagination degrades linearly and shifts under concurrent writes. List queries also
|
|
411
|
+
support keyset pagination: every row returned by a list query exposes an opaque `cursor`
|
|
412
|
+
field, and passing it back as `after` resumes strictly after that row.
|
|
413
|
+
|
|
414
|
+
```graphql
|
|
415
|
+
{
|
|
416
|
+
posts(orderBy: { createdAt: { direction: desc, priority: 1 } }, limit: 10) {
|
|
417
|
+
id
|
|
418
|
+
createdAt
|
|
419
|
+
cursor
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
# next page — same orderBy, plus the last row's cursor
|
|
424
|
+
{
|
|
425
|
+
posts(
|
|
426
|
+
orderBy: { createdAt: { direction: desc, priority: 1 } }
|
|
427
|
+
limit: 10
|
|
428
|
+
after: "eyJvIjogW1siY3JlYXRlZEF0IiwgImRlc2MiXSwgWyJpZCIsICJhc2MiXV0sIC4uLn0"
|
|
429
|
+
) {
|
|
430
|
+
id
|
|
431
|
+
createdAt
|
|
432
|
+
cursor
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
- The cursor encodes the row's position in the query's **total order** — your `orderBy`
|
|
438
|
+
columns plus the primary key as an ascending tiebreak — so pages are stable even when
|
|
439
|
+
the ordered columns aren't unique, and inserts or deletes mid-scroll don't shift the
|
|
440
|
+
window
|
|
441
|
+
- `after` compiles to a keyset predicate built from drizzle's `and`/`or`/`gt`/`lt`
|
|
442
|
+
comparisons (not SQL row-value syntax), so mixed-direction `orderBy` works
|
|
443
|
+
- `NULL`s in ordered columns follow each dialect's native `ORDER BY` placement —
|
|
444
|
+
PostgreSQL sorts them largest (last in `asc`), MySQL and SQLite smallest (first in
|
|
445
|
+
`asc`) — and the predicate matches, so rows with `NULL` in an ordered column are paged
|
|
446
|
+
through, not skipped
|
|
447
|
+
- Dates and bigints round-trip losslessly through the cursor
|
|
448
|
+
- A cursor is only valid under the ordering it was issued for — reusing it with a
|
|
449
|
+
different `orderBy` (or a malformed/corrupted cursor) returns a `GraphQLError`
|
|
450
|
+
- `after` cannot be combined with `distinct`, and a table with no primary key cannot use
|
|
451
|
+
cursor pagination (its `cursor` field resolves to `null`)
|
|
452
|
+
- If a table has a real column named `cursor`, the column wins and the meta field is
|
|
453
|
+
skipped
|
|
454
|
+
|
|
408
455
|
## Upsert
|
|
409
456
|
|
|
410
457
|
`features.upsert` adds a pair of mutations per table that insert rows, or update the ones
|
package/dist/README.md
CHANGED
|
@@ -405,6 +405,53 @@ supplies no `orderBy`:
|
|
|
405
405
|
An explicit `orderBy` always takes precedence, composite primary keys are ordered by every
|
|
406
406
|
key column, and an unpaginated list query is left unordered so no sort is paid for.
|
|
407
407
|
|
|
408
|
+
## Cursor pagination
|
|
409
|
+
|
|
410
|
+
`OFFSET` pagination degrades linearly and shifts under concurrent writes. List queries also
|
|
411
|
+
support keyset pagination: every row returned by a list query exposes an opaque `cursor`
|
|
412
|
+
field, and passing it back as `after` resumes strictly after that row.
|
|
413
|
+
|
|
414
|
+
```graphql
|
|
415
|
+
{
|
|
416
|
+
posts(orderBy: { createdAt: { direction: desc, priority: 1 } }, limit: 10) {
|
|
417
|
+
id
|
|
418
|
+
createdAt
|
|
419
|
+
cursor
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
# next page — same orderBy, plus the last row's cursor
|
|
424
|
+
{
|
|
425
|
+
posts(
|
|
426
|
+
orderBy: { createdAt: { direction: desc, priority: 1 } }
|
|
427
|
+
limit: 10
|
|
428
|
+
after: "eyJvIjogW1siY3JlYXRlZEF0IiwgImRlc2MiXSwgWyJpZCIsICJhc2MiXV0sIC4uLn0"
|
|
429
|
+
) {
|
|
430
|
+
id
|
|
431
|
+
createdAt
|
|
432
|
+
cursor
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
- The cursor encodes the row's position in the query's **total order** — your `orderBy`
|
|
438
|
+
columns plus the primary key as an ascending tiebreak — so pages are stable even when
|
|
439
|
+
the ordered columns aren't unique, and inserts or deletes mid-scroll don't shift the
|
|
440
|
+
window
|
|
441
|
+
- `after` compiles to a keyset predicate built from drizzle's `and`/`or`/`gt`/`lt`
|
|
442
|
+
comparisons (not SQL row-value syntax), so mixed-direction `orderBy` works
|
|
443
|
+
- `NULL`s in ordered columns follow each dialect's native `ORDER BY` placement —
|
|
444
|
+
PostgreSQL sorts them largest (last in `asc`), MySQL and SQLite smallest (first in
|
|
445
|
+
`asc`) — and the predicate matches, so rows with `NULL` in an ordered column are paged
|
|
446
|
+
through, not skipped
|
|
447
|
+
- Dates and bigints round-trip losslessly through the cursor
|
|
448
|
+
- A cursor is only valid under the ordering it was issued for — reusing it with a
|
|
449
|
+
different `orderBy` (or a malformed/corrupted cursor) returns a `GraphQLError`
|
|
450
|
+
- `after` cannot be combined with `distinct`, and a table with no primary key cannot use
|
|
451
|
+
cursor pagination (its `cursor` field resolves to `null`)
|
|
452
|
+
- If a table has a real column named `cursor`, the column wins and the meta field is
|
|
453
|
+
skipped
|
|
454
|
+
|
|
408
455
|
## Upsert
|
|
409
456
|
|
|
410
457
|
`features.upsert` adds a pair of mutations per table that insert rows, or update the ones
|
package/dist/index.cjs
CHANGED
|
@@ -1013,6 +1013,13 @@ var generateTableSelectTypeFieldsCached = (table, tableName) => {
|
|
|
1013
1013
|
drizzleColumnToGraphQLType(columnDescription, columnName, tableName)
|
|
1014
1014
|
])
|
|
1015
1015
|
);
|
|
1016
|
+
if (!remapped[CURSOR_FIELD_NAME]) {
|
|
1017
|
+
remapped[CURSOR_FIELD_NAME] = {
|
|
1018
|
+
type: import_graphql4.GraphQLString,
|
|
1019
|
+
description: "Opaque cursor of this row's position in the query's ordering. Pass it as `after` to resume from here. Only set on rows returned by a list query.",
|
|
1020
|
+
resolve: rowCursorResolver
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1016
1023
|
fieldMap.set(table, remapped);
|
|
1017
1024
|
return remapped;
|
|
1018
1025
|
};
|
|
@@ -1840,6 +1847,117 @@ var primaryKeyOrderExprs = (table, pkNames) => {
|
|
|
1840
1847
|
const cols = (0, import_drizzle_orm3.getColumns)(table);
|
|
1841
1848
|
return pkNames.map((n) => cols[n]).filter(Boolean).map((col) => (0, import_drizzle_orm3.asc)(col));
|
|
1842
1849
|
};
|
|
1850
|
+
var CURSOR_FIELD_NAME = "cursor";
|
|
1851
|
+
var ROW_CURSOR_PROP = "__drizzle_graphql_cursor";
|
|
1852
|
+
var orderByHasRelationEntry = (orderBy) => !!orderBy && Object.values(orderBy).some((config) => config && typeof config === "object" && config.direction === void 0);
|
|
1853
|
+
var cursorOrderingEntries = (orderBy, pkNames) => {
|
|
1854
|
+
const entries = orderBy ? orderByEntries(orderBy) : [];
|
|
1855
|
+
const seen = new Set(entries.map(([column]) => column));
|
|
1856
|
+
for (const pk of pkNames) {
|
|
1857
|
+
if (!seen.has(pk)) {
|
|
1858
|
+
entries.push([pk, "asc"]);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
return entries;
|
|
1862
|
+
};
|
|
1863
|
+
var cursorOrderExprs = (table, entries) => {
|
|
1864
|
+
const cols = (0, import_drizzle_orm3.getColumns)(table);
|
|
1865
|
+
return entries.flatMap(
|
|
1866
|
+
([column, direction, nulls]) => orderExpressions(cols[column], direction, nulls ?? void 0, cols[column])
|
|
1867
|
+
);
|
|
1868
|
+
};
|
|
1869
|
+
var encodeCursorValue = (value) => {
|
|
1870
|
+
if (value instanceof Date) {
|
|
1871
|
+
return { $type: "date", value: value.toISOString() };
|
|
1872
|
+
}
|
|
1873
|
+
if (typeof value === "bigint") {
|
|
1874
|
+
return { $type: "bigint", value: value.toString() };
|
|
1875
|
+
}
|
|
1876
|
+
return value;
|
|
1877
|
+
};
|
|
1878
|
+
var decodeCursorValue = (value) => {
|
|
1879
|
+
if (value && typeof value === "object" && typeof value.$type === "string") {
|
|
1880
|
+
if (value.$type === "date") {
|
|
1881
|
+
return new Date(value.value);
|
|
1882
|
+
}
|
|
1883
|
+
if (value.$type === "bigint") {
|
|
1884
|
+
return BigInt(value.value);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
return value;
|
|
1888
|
+
};
|
|
1889
|
+
var encodeCursor = (entries, row) => {
|
|
1890
|
+
const payload = {
|
|
1891
|
+
o: entries,
|
|
1892
|
+
v: entries.map(([column]) => encodeCursorValue(row[column] ?? null))
|
|
1893
|
+
};
|
|
1894
|
+
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
1895
|
+
};
|
|
1896
|
+
var decodeCursor = (after, entries) => {
|
|
1897
|
+
let payload;
|
|
1898
|
+
try {
|
|
1899
|
+
payload = JSON.parse(Buffer.from(after, "base64url").toString("utf8"));
|
|
1900
|
+
} catch (_e) {
|
|
1901
|
+
throw new import_graphql4.GraphQLError("Invalid cursor: unable to decode it. Pass a cursor returned by a previous page.");
|
|
1902
|
+
}
|
|
1903
|
+
if (!payload || typeof payload !== "object" || !Array.isArray(payload.o) || !Array.isArray(payload.v) || payload.o.length !== payload.v.length) {
|
|
1904
|
+
throw new import_graphql4.GraphQLError("Invalid cursor: malformed payload. Pass a cursor returned by a previous page.");
|
|
1905
|
+
}
|
|
1906
|
+
const matchesOrdering = payload.o.length === entries.length && payload.o.every(
|
|
1907
|
+
(entry, i) => Array.isArray(entry) && entry[0] === entries[i][0] && entry[1] === entries[i][1] && // The nulls override changes where NULLs sort, so it is part of the ordering identity.
|
|
1908
|
+
// JSON round-trips undefined as null — compare the normalized forms.
|
|
1909
|
+
(entry[2] ?? null) === (entries[i][2] ?? null)
|
|
1910
|
+
);
|
|
1911
|
+
if (!matchesOrdering) {
|
|
1912
|
+
throw new import_graphql4.GraphQLError(
|
|
1913
|
+
"Invalid cursor: it was issued for a different ordering. Pass the same orderBy the cursor's page used."
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
return payload.v.map(decodeCursorValue);
|
|
1917
|
+
};
|
|
1918
|
+
var buildCursorCondition = (table, entries, values, nullOrdering) => {
|
|
1919
|
+
const cols = (0, import_drizzle_orm3.getColumns)(table);
|
|
1920
|
+
const disjuncts = [];
|
|
1921
|
+
const equalities = [];
|
|
1922
|
+
entries.forEach(([column, direction, nulls], i) => {
|
|
1923
|
+
const col = cols[column];
|
|
1924
|
+
if (!col) {
|
|
1925
|
+
throw new import_graphql4.GraphQLError(`Invalid cursor: unknown column '${column}'.`);
|
|
1926
|
+
}
|
|
1927
|
+
const value = values[i];
|
|
1928
|
+
const nullsSortLast = nulls != null ? nulls === "last" : direction === "asc" === (nullOrdering === "nulls-largest");
|
|
1929
|
+
let strictlyAfter;
|
|
1930
|
+
if (value === null || value === void 0) {
|
|
1931
|
+
strictlyAfter = nullsSortLast ? void 0 : (0, import_drizzle_orm3.isNotNull)(col);
|
|
1932
|
+
} else {
|
|
1933
|
+
const comparison = direction === "asc" ? (0, import_drizzle_orm3.gt)(col, value) : (0, import_drizzle_orm3.lt)(col, value);
|
|
1934
|
+
strictlyAfter = nullsSortLast ? (0, import_drizzle_orm3.or)(comparison, (0, import_drizzle_orm3.isNull)(col)) : comparison;
|
|
1935
|
+
}
|
|
1936
|
+
if (strictlyAfter) {
|
|
1937
|
+
disjuncts.push(equalities.length ? (0, import_drizzle_orm3.and)(...equalities, strictlyAfter) : strictlyAfter);
|
|
1938
|
+
}
|
|
1939
|
+
equalities.push(value === null || value === void 0 ? (0, import_drizzle_orm3.isNull)(col) : (0, import_drizzle_orm3.eq)(col, value));
|
|
1940
|
+
});
|
|
1941
|
+
if (!disjuncts.length) {
|
|
1942
|
+
return import_drizzle_orm3.sql`1 = 0`;
|
|
1943
|
+
}
|
|
1944
|
+
return disjuncts.length > 1 ? (0, import_drizzle_orm3.or)(...disjuncts) : disjuncts[0];
|
|
1945
|
+
};
|
|
1946
|
+
var isCursorFieldSelected = (tree, table) => {
|
|
1947
|
+
if (!tree) {
|
|
1948
|
+
return false;
|
|
1949
|
+
}
|
|
1950
|
+
if ((0, import_drizzle_orm3.getColumns)(table)[CURSOR_FIELD_NAME]) {
|
|
1951
|
+
return false;
|
|
1952
|
+
}
|
|
1953
|
+
return Object.values(tree).some((field) => field.name === CURSOR_FIELD_NAME);
|
|
1954
|
+
};
|
|
1955
|
+
var attachRowCursors = (rows, entries) => {
|
|
1956
|
+
for (const row of rows) {
|
|
1957
|
+
row[ROW_CURSOR_PROP] = encodeCursor(entries, row);
|
|
1958
|
+
}
|
|
1959
|
+
};
|
|
1960
|
+
var rowCursorResolver = (source) => source?.[ROW_CURSOR_PROP] ?? null;
|
|
1843
1961
|
var getUniqueColumnSets = (table, getTableConfig4) => {
|
|
1844
1962
|
const cols = (0, import_drizzle_orm3.getColumns)(table);
|
|
1845
1963
|
const propNameByColumnName = new Map(Object.entries(cols).map(([propName, col]) => [col.name, propName]));
|
|
@@ -2164,6 +2282,10 @@ var selectArrayArgs = (orderArgs, filterArgs, distinctEnum) => ({
|
|
|
2164
2282
|
limit: { type: import_graphql4.GraphQLInt },
|
|
2165
2283
|
orderBy: { type: orderArgs },
|
|
2166
2284
|
where: { type: filterArgs },
|
|
2285
|
+
after: {
|
|
2286
|
+
type: import_graphql4.GraphQLString,
|
|
2287
|
+
description: "Keyset pagination: only return rows strictly after this cursor (a row's `cursor` field from a previous page, under the same orderBy)."
|
|
2288
|
+
},
|
|
2167
2289
|
...distinctEnum ? { distinct: { type: new import_graphql4.GraphQLList(new import_graphql4.GraphQLNonNull(distinctEnum)) } } : {}
|
|
2168
2290
|
});
|
|
2169
2291
|
var selectSingleArgs = (orderArgs, filterArgs) => ({
|
|
@@ -2186,9 +2308,31 @@ var runRelationalSelect = async (opts) => {
|
|
|
2186
2308
|
where,
|
|
2187
2309
|
single,
|
|
2188
2310
|
filterCtx,
|
|
2189
|
-
pkNames
|
|
2311
|
+
pkNames,
|
|
2312
|
+
after
|
|
2190
2313
|
} = opts;
|
|
2191
2314
|
const distinct = opts.distinct?.length ? opts.distinct : void 0;
|
|
2315
|
+
const cursorSelected = !single && isCursorFieldSelected(parsedInfo.fieldsByTypeName[typeName], table);
|
|
2316
|
+
let cursorEntries;
|
|
2317
|
+
if (!single && (after != null || cursorSelected)) {
|
|
2318
|
+
if (after != null && distinct) {
|
|
2319
|
+
throw new import_graphql4.GraphQLError("'after' cannot be combined with 'distinct'.");
|
|
2320
|
+
}
|
|
2321
|
+
if (orderByHasRelationEntry(orderBy)) {
|
|
2322
|
+
if (after != null) {
|
|
2323
|
+
throw new import_graphql4.GraphQLError(
|
|
2324
|
+
"'after' cannot be combined with an orderBy that orders through a relation \u2014 a related row's value cannot be encoded into a cursor."
|
|
2325
|
+
);
|
|
2326
|
+
}
|
|
2327
|
+
} else if (!pkNames?.length) {
|
|
2328
|
+
if (after != null) {
|
|
2329
|
+
throw new import_graphql4.GraphQLError(`Table ${tableName} has no primary key, so cursor pagination cannot be used on it.`);
|
|
2330
|
+
}
|
|
2331
|
+
} else {
|
|
2332
|
+
cursorEntries = cursorOrderingEntries(orderBy, pkNames);
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
const cursorValues = after != null && cursorEntries ? decodeCursor(after, cursorEntries) : void 0;
|
|
2192
2336
|
const needsDefaultOrder = single || offset != null || opts.limit != null;
|
|
2193
2337
|
let distinctKeys;
|
|
2194
2338
|
if (distinct) {
|
|
@@ -2219,9 +2363,21 @@ var runRelationalSelect = async (opts) => {
|
|
|
2219
2363
|
orderBy: distinctKeys ? (aliasedTable2) => [
|
|
2220
2364
|
...orderBy ? extractOrderBy(aliasedTable2, orderBy, relationFilterCtx(filterCtx, tableName)) : [],
|
|
2221
2365
|
...primaryKeyOrderExprs(aliasedTable2, pkNames)
|
|
2222
|
-
] :
|
|
2223
|
-
|
|
2224
|
-
|
|
2366
|
+
] : cursorEntries ? (
|
|
2367
|
+
// Cursor pagination needs a total order: the request's orderBy plus the PK tiebreak,
|
|
2368
|
+
// exactly the ordering the cursor encodes and the keyset predicate compares against.
|
|
2369
|
+
(aliasedTable2) => cursorOrderExprs(aliasedTable2, cursorEntries)
|
|
2370
|
+
) : orderBy ? (aliasedTable2) => extractOrderBy(aliasedTable2, orderBy, relationFilterCtx(filterCtx, tableName)) : needsDefaultOrder && pkNames?.length ? (aliasedTable2) => primaryKeyOrderExprs(aliasedTable2, pkNames) : void 0,
|
|
2371
|
+
where: distinctKeys ? { RAW: (aliasedTable2) => primaryKeyRestriction(aliasedTable2, pkNames, distinctKeys) } : where || cursorValues ? {
|
|
2372
|
+
RAW: (aliasedTable2) => (0, import_drizzle_orm3.and)(
|
|
2373
|
+
where ? extractFilters(aliasedTable2, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0,
|
|
2374
|
+
cursorValues ? buildCursorCondition(
|
|
2375
|
+
aliasedTable2,
|
|
2376
|
+
cursorEntries,
|
|
2377
|
+
cursorValues,
|
|
2378
|
+
opts.nullOrdering ?? "nulls-smallest"
|
|
2379
|
+
) : void 0
|
|
2380
|
+
)
|
|
2225
2381
|
} : void 0,
|
|
2226
2382
|
with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, typeNameMapper, filterCtx) : void 0
|
|
2227
2383
|
};
|
|
@@ -2229,8 +2385,16 @@ var runRelationalSelect = async (opts) => {
|
|
|
2229
2385
|
const result2 = await queryBase.findFirst(params);
|
|
2230
2386
|
return result2 ? remapToGraphQLSingleOutput(result2, tableName, table, relationMap) : void 0;
|
|
2231
2387
|
}
|
|
2388
|
+
if (cursorEntries && cursorSelected) {
|
|
2389
|
+
for (const [column] of cursorEntries) {
|
|
2390
|
+
params.columns[column] = true;
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2232
2393
|
params.limit = distinctKeys ? void 0 : opts.limit;
|
|
2233
2394
|
const result = await queryBase.findMany(params);
|
|
2395
|
+
if (cursorEntries && cursorSelected) {
|
|
2396
|
+
attachRowCursors(result, cursorEntries);
|
|
2397
|
+
}
|
|
2234
2398
|
return remapToGraphQLArrayOutput(result, tableName, table, relationMap);
|
|
2235
2399
|
};
|
|
2236
2400
|
var eagerLoadMutationRelations = async (db, tableName, rows, pkNames, withParams) => {
|
|
@@ -2778,7 +2942,9 @@ var generateSelectArray = (db, tableName, tables, relationMap, orderArgs, filter
|
|
|
2778
2942
|
single: false,
|
|
2779
2943
|
filterCtx,
|
|
2780
2944
|
pkNames,
|
|
2781
|
-
db: executor
|
|
2945
|
+
db: executor,
|
|
2946
|
+
// MySQL sorts NULLs as the smallest values (first in ASC).
|
|
2947
|
+
nullOrdering: "nulls-smallest"
|
|
2782
2948
|
});
|
|
2783
2949
|
} catch (e) {
|
|
2784
2950
|
throw toGraphQLError(e);
|
|
@@ -3276,16 +3442,48 @@ var generateSelectArray2 = (db, tableName, tables, relationMap, orderArgs, filte
|
|
|
3276
3442
|
single: false,
|
|
3277
3443
|
filterCtx,
|
|
3278
3444
|
pkNames,
|
|
3279
|
-
db: executor
|
|
3445
|
+
db: executor,
|
|
3446
|
+
// PostgreSQL sorts NULLs as the largest values (last in ASC).
|
|
3447
|
+
nullOrdering: "nulls-largest"
|
|
3280
3448
|
});
|
|
3281
3449
|
}
|
|
3282
|
-
const { offset, limit, orderBy, where, distinct } = args;
|
|
3450
|
+
const { offset, limit, orderBy, where, distinct, after } = args;
|
|
3283
3451
|
const selectedColumnsSql = extractSelectedColumnsFromTreeSQLFormat(
|
|
3284
3452
|
parsedInfo.fieldsByTypeName[typeName],
|
|
3285
3453
|
table,
|
|
3286
3454
|
{ tableName, relationMap, tables }
|
|
3287
3455
|
);
|
|
3288
|
-
const
|
|
3456
|
+
const cursorSelected = isCursorFieldSelected(parsedInfo.fieldsByTypeName[typeName], table);
|
|
3457
|
+
let cursorEntries;
|
|
3458
|
+
if (after != null || cursorSelected) {
|
|
3459
|
+
if (after != null && distinct?.length) {
|
|
3460
|
+
throw new import_graphql7.GraphQLError("'after' cannot be combined with 'distinct'.");
|
|
3461
|
+
}
|
|
3462
|
+
if (orderByHasRelationEntry(orderBy)) {
|
|
3463
|
+
if (after != null) {
|
|
3464
|
+
throw new import_graphql7.GraphQLError(
|
|
3465
|
+
"'after' cannot be combined with an orderBy that orders through a relation \u2014 a related row's value cannot be encoded into a cursor."
|
|
3466
|
+
);
|
|
3467
|
+
}
|
|
3468
|
+
} else if (!pkNames.length) {
|
|
3469
|
+
if (after != null) {
|
|
3470
|
+
throw new import_graphql7.GraphQLError(
|
|
3471
|
+
`Table ${tableName} has no primary key, so cursor pagination cannot be used on it.`
|
|
3472
|
+
);
|
|
3473
|
+
}
|
|
3474
|
+
} else {
|
|
3475
|
+
cursorEntries = cursorOrderingEntries(orderBy, pkNames);
|
|
3476
|
+
if (cursorSelected) {
|
|
3477
|
+
const allColumns = (0, import_drizzle_orm6.getColumns)(table);
|
|
3478
|
+
for (const [column] of cursorEntries) {
|
|
3479
|
+
selectedColumnsSql[column] ??= allColumns[column];
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
const cursorCondition = after != null && cursorEntries ? buildCursorCondition(table, cursorEntries, decodeCursor(after, cursorEntries), "nulls-largest") : void 0;
|
|
3485
|
+
const baseWhereSql = where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0;
|
|
3486
|
+
const whereSql = cursorCondition ? (0, import_drizzle_orm6.and)(baseWhereSql, cursorCondition) : baseWhereSql;
|
|
3289
3487
|
let distinctKeys;
|
|
3290
3488
|
if (distinct?.length) {
|
|
3291
3489
|
distinctKeys = await selectDistinctKeys({
|
|
@@ -3309,7 +3507,9 @@ var generateSelectArray2 = (db, tableName, tables, relationMap, orderArgs, filte
|
|
|
3309
3507
|
} else if (whereSql) {
|
|
3310
3508
|
q = q.where(whereSql);
|
|
3311
3509
|
}
|
|
3312
|
-
if (
|
|
3510
|
+
if (cursorEntries && !distinctKeys) {
|
|
3511
|
+
q = q.orderBy(...cursorOrderExprs(table, cursorEntries));
|
|
3512
|
+
} else if (orderBy) {
|
|
3313
3513
|
q = q.orderBy(
|
|
3314
3514
|
...extractOrderBy(table, orderBy, relationFilterCtx(filterCtx, tableName)),
|
|
3315
3515
|
...distinctKeys ? primaryKeyOrderExprs(table, pkNames) : []
|
|
@@ -3325,7 +3525,11 @@ var generateSelectArray2 = (db, tableName, tables, relationMap, orderArgs, filte
|
|
|
3325
3525
|
q = q.limit(limit);
|
|
3326
3526
|
}
|
|
3327
3527
|
}
|
|
3328
|
-
|
|
3528
|
+
const rows = await q;
|
|
3529
|
+
if (cursorEntries && cursorSelected) {
|
|
3530
|
+
attachRowCursors(rows, cursorEntries);
|
|
3531
|
+
}
|
|
3532
|
+
return remapToGraphQLArrayOutput(rows, tableName, table, relationMap);
|
|
3329
3533
|
} catch (e) {
|
|
3330
3534
|
throw toGraphQLError(e);
|
|
3331
3535
|
}
|
|
@@ -4037,7 +4241,9 @@ var generateSelectArray3 = (db, tableName, tables, relationMap, orderArgs, filte
|
|
|
4037
4241
|
single: false,
|
|
4038
4242
|
filterCtx,
|
|
4039
4243
|
pkNames,
|
|
4040
|
-
db: executor
|
|
4244
|
+
db: executor,
|
|
4245
|
+
// SQLite sorts NULLs as the smallest values (first in ASC).
|
|
4246
|
+
nullOrdering: "nulls-smallest"
|
|
4041
4247
|
});
|
|
4042
4248
|
} catch (e) {
|
|
4043
4249
|
throw toGraphQLError(e);
|