@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/dist/index.js
CHANGED
|
@@ -1020,6 +1020,13 @@ var generateTableSelectTypeFieldsCached = (table, tableName) => {
|
|
|
1020
1020
|
drizzleColumnToGraphQLType(columnDescription, columnName, tableName)
|
|
1021
1021
|
])
|
|
1022
1022
|
);
|
|
1023
|
+
if (!remapped[CURSOR_FIELD_NAME]) {
|
|
1024
|
+
remapped[CURSOR_FIELD_NAME] = {
|
|
1025
|
+
type: GraphQLString2,
|
|
1026
|
+
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.",
|
|
1027
|
+
resolve: rowCursorResolver
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1023
1030
|
fieldMap.set(table, remapped);
|
|
1024
1031
|
return remapped;
|
|
1025
1032
|
};
|
|
@@ -1847,6 +1854,117 @@ var primaryKeyOrderExprs = (table, pkNames) => {
|
|
|
1847
1854
|
const cols = getColumns(table);
|
|
1848
1855
|
return pkNames.map((n) => cols[n]).filter(Boolean).map((col) => asc(col));
|
|
1849
1856
|
};
|
|
1857
|
+
var CURSOR_FIELD_NAME = "cursor";
|
|
1858
|
+
var ROW_CURSOR_PROP = "__drizzle_graphql_cursor";
|
|
1859
|
+
var orderByHasRelationEntry = (orderBy) => !!orderBy && Object.values(orderBy).some((config) => config && typeof config === "object" && config.direction === void 0);
|
|
1860
|
+
var cursorOrderingEntries = (orderBy, pkNames) => {
|
|
1861
|
+
const entries = orderBy ? orderByEntries(orderBy) : [];
|
|
1862
|
+
const seen = new Set(entries.map(([column]) => column));
|
|
1863
|
+
for (const pk of pkNames) {
|
|
1864
|
+
if (!seen.has(pk)) {
|
|
1865
|
+
entries.push([pk, "asc"]);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return entries;
|
|
1869
|
+
};
|
|
1870
|
+
var cursorOrderExprs = (table, entries) => {
|
|
1871
|
+
const cols = getColumns(table);
|
|
1872
|
+
return entries.flatMap(
|
|
1873
|
+
([column, direction, nulls]) => orderExpressions(cols[column], direction, nulls ?? void 0, cols[column])
|
|
1874
|
+
);
|
|
1875
|
+
};
|
|
1876
|
+
var encodeCursorValue = (value) => {
|
|
1877
|
+
if (value instanceof Date) {
|
|
1878
|
+
return { $type: "date", value: value.toISOString() };
|
|
1879
|
+
}
|
|
1880
|
+
if (typeof value === "bigint") {
|
|
1881
|
+
return { $type: "bigint", value: value.toString() };
|
|
1882
|
+
}
|
|
1883
|
+
return value;
|
|
1884
|
+
};
|
|
1885
|
+
var decodeCursorValue = (value) => {
|
|
1886
|
+
if (value && typeof value === "object" && typeof value.$type === "string") {
|
|
1887
|
+
if (value.$type === "date") {
|
|
1888
|
+
return new Date(value.value);
|
|
1889
|
+
}
|
|
1890
|
+
if (value.$type === "bigint") {
|
|
1891
|
+
return BigInt(value.value);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
return value;
|
|
1895
|
+
};
|
|
1896
|
+
var encodeCursor = (entries, row) => {
|
|
1897
|
+
const payload = {
|
|
1898
|
+
o: entries,
|
|
1899
|
+
v: entries.map(([column]) => encodeCursorValue(row[column] ?? null))
|
|
1900
|
+
};
|
|
1901
|
+
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
1902
|
+
};
|
|
1903
|
+
var decodeCursor = (after, entries) => {
|
|
1904
|
+
let payload;
|
|
1905
|
+
try {
|
|
1906
|
+
payload = JSON.parse(Buffer.from(after, "base64url").toString("utf8"));
|
|
1907
|
+
} catch (_e) {
|
|
1908
|
+
throw new GraphQLError3("Invalid cursor: unable to decode it. Pass a cursor returned by a previous page.");
|
|
1909
|
+
}
|
|
1910
|
+
if (!payload || typeof payload !== "object" || !Array.isArray(payload.o) || !Array.isArray(payload.v) || payload.o.length !== payload.v.length) {
|
|
1911
|
+
throw new GraphQLError3("Invalid cursor: malformed payload. Pass a cursor returned by a previous page.");
|
|
1912
|
+
}
|
|
1913
|
+
const matchesOrdering = payload.o.length === entries.length && payload.o.every(
|
|
1914
|
+
(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.
|
|
1915
|
+
// JSON round-trips undefined as null — compare the normalized forms.
|
|
1916
|
+
(entry[2] ?? null) === (entries[i][2] ?? null)
|
|
1917
|
+
);
|
|
1918
|
+
if (!matchesOrdering) {
|
|
1919
|
+
throw new GraphQLError3(
|
|
1920
|
+
"Invalid cursor: it was issued for a different ordering. Pass the same orderBy the cursor's page used."
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
return payload.v.map(decodeCursorValue);
|
|
1924
|
+
};
|
|
1925
|
+
var buildCursorCondition = (table, entries, values, nullOrdering) => {
|
|
1926
|
+
const cols = getColumns(table);
|
|
1927
|
+
const disjuncts = [];
|
|
1928
|
+
const equalities = [];
|
|
1929
|
+
entries.forEach(([column, direction, nulls], i) => {
|
|
1930
|
+
const col = cols[column];
|
|
1931
|
+
if (!col) {
|
|
1932
|
+
throw new GraphQLError3(`Invalid cursor: unknown column '${column}'.`);
|
|
1933
|
+
}
|
|
1934
|
+
const value = values[i];
|
|
1935
|
+
const nullsSortLast = nulls != null ? nulls === "last" : direction === "asc" === (nullOrdering === "nulls-largest");
|
|
1936
|
+
let strictlyAfter;
|
|
1937
|
+
if (value === null || value === void 0) {
|
|
1938
|
+
strictlyAfter = nullsSortLast ? void 0 : isNotNull(col);
|
|
1939
|
+
} else {
|
|
1940
|
+
const comparison = direction === "asc" ? gt(col, value) : lt(col, value);
|
|
1941
|
+
strictlyAfter = nullsSortLast ? or(comparison, isNull(col)) : comparison;
|
|
1942
|
+
}
|
|
1943
|
+
if (strictlyAfter) {
|
|
1944
|
+
disjuncts.push(equalities.length ? and(...equalities, strictlyAfter) : strictlyAfter);
|
|
1945
|
+
}
|
|
1946
|
+
equalities.push(value === null || value === void 0 ? isNull(col) : eq(col, value));
|
|
1947
|
+
});
|
|
1948
|
+
if (!disjuncts.length) {
|
|
1949
|
+
return sql`1 = 0`;
|
|
1950
|
+
}
|
|
1951
|
+
return disjuncts.length > 1 ? or(...disjuncts) : disjuncts[0];
|
|
1952
|
+
};
|
|
1953
|
+
var isCursorFieldSelected = (tree, table) => {
|
|
1954
|
+
if (!tree) {
|
|
1955
|
+
return false;
|
|
1956
|
+
}
|
|
1957
|
+
if (getColumns(table)[CURSOR_FIELD_NAME]) {
|
|
1958
|
+
return false;
|
|
1959
|
+
}
|
|
1960
|
+
return Object.values(tree).some((field) => field.name === CURSOR_FIELD_NAME);
|
|
1961
|
+
};
|
|
1962
|
+
var attachRowCursors = (rows, entries) => {
|
|
1963
|
+
for (const row of rows) {
|
|
1964
|
+
row[ROW_CURSOR_PROP] = encodeCursor(entries, row);
|
|
1965
|
+
}
|
|
1966
|
+
};
|
|
1967
|
+
var rowCursorResolver = (source) => source?.[ROW_CURSOR_PROP] ?? null;
|
|
1850
1968
|
var getUniqueColumnSets = (table, getTableConfig4) => {
|
|
1851
1969
|
const cols = getColumns(table);
|
|
1852
1970
|
const propNameByColumnName = new Map(Object.entries(cols).map(([propName, col]) => [col.name, propName]));
|
|
@@ -2171,6 +2289,10 @@ var selectArrayArgs = (orderArgs, filterArgs, distinctEnum) => ({
|
|
|
2171
2289
|
limit: { type: GraphQLInt2 },
|
|
2172
2290
|
orderBy: { type: orderArgs },
|
|
2173
2291
|
where: { type: filterArgs },
|
|
2292
|
+
after: {
|
|
2293
|
+
type: GraphQLString2,
|
|
2294
|
+
description: "Keyset pagination: only return rows strictly after this cursor (a row's `cursor` field from a previous page, under the same orderBy)."
|
|
2295
|
+
},
|
|
2174
2296
|
...distinctEnum ? { distinct: { type: new GraphQLList2(new GraphQLNonNull2(distinctEnum)) } } : {}
|
|
2175
2297
|
});
|
|
2176
2298
|
var selectSingleArgs = (orderArgs, filterArgs) => ({
|
|
@@ -2193,9 +2315,31 @@ var runRelationalSelect = async (opts) => {
|
|
|
2193
2315
|
where,
|
|
2194
2316
|
single,
|
|
2195
2317
|
filterCtx,
|
|
2196
|
-
pkNames
|
|
2318
|
+
pkNames,
|
|
2319
|
+
after
|
|
2197
2320
|
} = opts;
|
|
2198
2321
|
const distinct = opts.distinct?.length ? opts.distinct : void 0;
|
|
2322
|
+
const cursorSelected = !single && isCursorFieldSelected(parsedInfo.fieldsByTypeName[typeName], table);
|
|
2323
|
+
let cursorEntries;
|
|
2324
|
+
if (!single && (after != null || cursorSelected)) {
|
|
2325
|
+
if (after != null && distinct) {
|
|
2326
|
+
throw new GraphQLError3("'after' cannot be combined with 'distinct'.");
|
|
2327
|
+
}
|
|
2328
|
+
if (orderByHasRelationEntry(orderBy)) {
|
|
2329
|
+
if (after != null) {
|
|
2330
|
+
throw new GraphQLError3(
|
|
2331
|
+
"'after' cannot be combined with an orderBy that orders through a relation \u2014 a related row's value cannot be encoded into a cursor."
|
|
2332
|
+
);
|
|
2333
|
+
}
|
|
2334
|
+
} else if (!pkNames?.length) {
|
|
2335
|
+
if (after != null) {
|
|
2336
|
+
throw new GraphQLError3(`Table ${tableName} has no primary key, so cursor pagination cannot be used on it.`);
|
|
2337
|
+
}
|
|
2338
|
+
} else {
|
|
2339
|
+
cursorEntries = cursorOrderingEntries(orderBy, pkNames);
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
const cursorValues = after != null && cursorEntries ? decodeCursor(after, cursorEntries) : void 0;
|
|
2199
2343
|
const needsDefaultOrder = single || offset != null || opts.limit != null;
|
|
2200
2344
|
let distinctKeys;
|
|
2201
2345
|
if (distinct) {
|
|
@@ -2226,9 +2370,21 @@ var runRelationalSelect = async (opts) => {
|
|
|
2226
2370
|
orderBy: distinctKeys ? (aliasedTable2) => [
|
|
2227
2371
|
...orderBy ? extractOrderBy(aliasedTable2, orderBy, relationFilterCtx(filterCtx, tableName)) : [],
|
|
2228
2372
|
...primaryKeyOrderExprs(aliasedTable2, pkNames)
|
|
2229
|
-
] :
|
|
2230
|
-
|
|
2231
|
-
|
|
2373
|
+
] : cursorEntries ? (
|
|
2374
|
+
// Cursor pagination needs a total order: the request's orderBy plus the PK tiebreak,
|
|
2375
|
+
// exactly the ordering the cursor encodes and the keyset predicate compares against.
|
|
2376
|
+
(aliasedTable2) => cursorOrderExprs(aliasedTable2, cursorEntries)
|
|
2377
|
+
) : orderBy ? (aliasedTable2) => extractOrderBy(aliasedTable2, orderBy, relationFilterCtx(filterCtx, tableName)) : needsDefaultOrder && pkNames?.length ? (aliasedTable2) => primaryKeyOrderExprs(aliasedTable2, pkNames) : void 0,
|
|
2378
|
+
where: distinctKeys ? { RAW: (aliasedTable2) => primaryKeyRestriction(aliasedTable2, pkNames, distinctKeys) } : where || cursorValues ? {
|
|
2379
|
+
RAW: (aliasedTable2) => and(
|
|
2380
|
+
where ? extractFilters(aliasedTable2, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0,
|
|
2381
|
+
cursorValues ? buildCursorCondition(
|
|
2382
|
+
aliasedTable2,
|
|
2383
|
+
cursorEntries,
|
|
2384
|
+
cursorValues,
|
|
2385
|
+
opts.nullOrdering ?? "nulls-smallest"
|
|
2386
|
+
) : void 0
|
|
2387
|
+
)
|
|
2232
2388
|
} : void 0,
|
|
2233
2389
|
with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, typeNameMapper, filterCtx) : void 0
|
|
2234
2390
|
};
|
|
@@ -2236,8 +2392,16 @@ var runRelationalSelect = async (opts) => {
|
|
|
2236
2392
|
const result2 = await queryBase.findFirst(params);
|
|
2237
2393
|
return result2 ? remapToGraphQLSingleOutput(result2, tableName, table, relationMap) : void 0;
|
|
2238
2394
|
}
|
|
2395
|
+
if (cursorEntries && cursorSelected) {
|
|
2396
|
+
for (const [column] of cursorEntries) {
|
|
2397
|
+
params.columns[column] = true;
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2239
2400
|
params.limit = distinctKeys ? void 0 : opts.limit;
|
|
2240
2401
|
const result = await queryBase.findMany(params);
|
|
2402
|
+
if (cursorEntries && cursorSelected) {
|
|
2403
|
+
attachRowCursors(result, cursorEntries);
|
|
2404
|
+
}
|
|
2241
2405
|
return remapToGraphQLArrayOutput(result, tableName, table, relationMap);
|
|
2242
2406
|
};
|
|
2243
2407
|
var eagerLoadMutationRelations = async (db, tableName, rows, pkNames, withParams) => {
|
|
@@ -2816,7 +2980,9 @@ var generateSelectArray = (db, tableName, tables, relationMap, orderArgs, filter
|
|
|
2816
2980
|
single: false,
|
|
2817
2981
|
filterCtx,
|
|
2818
2982
|
pkNames,
|
|
2819
|
-
db: executor
|
|
2983
|
+
db: executor,
|
|
2984
|
+
// MySQL sorts NULLs as the smallest values (first in ASC).
|
|
2985
|
+
nullOrdering: "nulls-smallest"
|
|
2820
2986
|
});
|
|
2821
2987
|
} catch (e) {
|
|
2822
2988
|
throw toGraphQLError(e);
|
|
@@ -3281,7 +3447,7 @@ var generateSchemaData = (db, schema, relations, options) => {
|
|
|
3281
3447
|
};
|
|
3282
3448
|
|
|
3283
3449
|
// src/util/builders/pg.ts
|
|
3284
|
-
import { is as is5, One as One4 } from "drizzle-orm";
|
|
3450
|
+
import { and as and3, getColumns as getColumns3, is as is5, One as One4 } from "drizzle-orm";
|
|
3285
3451
|
import { getTableConfig as getTableConfig2, PgTable } from "drizzle-orm/pg-core";
|
|
3286
3452
|
import {
|
|
3287
3453
|
GraphQLError as GraphQLError6,
|
|
@@ -3318,16 +3484,48 @@ var generateSelectArray2 = (db, tableName, tables, relationMap, orderArgs, filte
|
|
|
3318
3484
|
single: false,
|
|
3319
3485
|
filterCtx,
|
|
3320
3486
|
pkNames,
|
|
3321
|
-
db: executor
|
|
3487
|
+
db: executor,
|
|
3488
|
+
// PostgreSQL sorts NULLs as the largest values (last in ASC).
|
|
3489
|
+
nullOrdering: "nulls-largest"
|
|
3322
3490
|
});
|
|
3323
3491
|
}
|
|
3324
|
-
const { offset, limit, orderBy, where, distinct } = args;
|
|
3492
|
+
const { offset, limit, orderBy, where, distinct, after } = args;
|
|
3325
3493
|
const selectedColumnsSql = extractSelectedColumnsFromTreeSQLFormat(
|
|
3326
3494
|
parsedInfo.fieldsByTypeName[typeName],
|
|
3327
3495
|
table,
|
|
3328
3496
|
{ tableName, relationMap, tables }
|
|
3329
3497
|
);
|
|
3330
|
-
const
|
|
3498
|
+
const cursorSelected = isCursorFieldSelected(parsedInfo.fieldsByTypeName[typeName], table);
|
|
3499
|
+
let cursorEntries;
|
|
3500
|
+
if (after != null || cursorSelected) {
|
|
3501
|
+
if (after != null && distinct?.length) {
|
|
3502
|
+
throw new GraphQLError6("'after' cannot be combined with 'distinct'.");
|
|
3503
|
+
}
|
|
3504
|
+
if (orderByHasRelationEntry(orderBy)) {
|
|
3505
|
+
if (after != null) {
|
|
3506
|
+
throw new GraphQLError6(
|
|
3507
|
+
"'after' cannot be combined with an orderBy that orders through a relation \u2014 a related row's value cannot be encoded into a cursor."
|
|
3508
|
+
);
|
|
3509
|
+
}
|
|
3510
|
+
} else if (!pkNames.length) {
|
|
3511
|
+
if (after != null) {
|
|
3512
|
+
throw new GraphQLError6(
|
|
3513
|
+
`Table ${tableName} has no primary key, so cursor pagination cannot be used on it.`
|
|
3514
|
+
);
|
|
3515
|
+
}
|
|
3516
|
+
} else {
|
|
3517
|
+
cursorEntries = cursorOrderingEntries(orderBy, pkNames);
|
|
3518
|
+
if (cursorSelected) {
|
|
3519
|
+
const allColumns = getColumns3(table);
|
|
3520
|
+
for (const [column] of cursorEntries) {
|
|
3521
|
+
selectedColumnsSql[column] ??= allColumns[column];
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
const cursorCondition = after != null && cursorEntries ? buildCursorCondition(table, cursorEntries, decodeCursor(after, cursorEntries), "nulls-largest") : void 0;
|
|
3527
|
+
const baseWhereSql = where ? extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0;
|
|
3528
|
+
const whereSql = cursorCondition ? and3(baseWhereSql, cursorCondition) : baseWhereSql;
|
|
3331
3529
|
let distinctKeys;
|
|
3332
3530
|
if (distinct?.length) {
|
|
3333
3531
|
distinctKeys = await selectDistinctKeys({
|
|
@@ -3351,7 +3549,9 @@ var generateSelectArray2 = (db, tableName, tables, relationMap, orderArgs, filte
|
|
|
3351
3549
|
} else if (whereSql) {
|
|
3352
3550
|
q = q.where(whereSql);
|
|
3353
3551
|
}
|
|
3354
|
-
if (
|
|
3552
|
+
if (cursorEntries && !distinctKeys) {
|
|
3553
|
+
q = q.orderBy(...cursorOrderExprs(table, cursorEntries));
|
|
3554
|
+
} else if (orderBy) {
|
|
3355
3555
|
q = q.orderBy(
|
|
3356
3556
|
...extractOrderBy(table, orderBy, relationFilterCtx(filterCtx, tableName)),
|
|
3357
3557
|
...distinctKeys ? primaryKeyOrderExprs(table, pkNames) : []
|
|
@@ -3367,7 +3567,11 @@ var generateSelectArray2 = (db, tableName, tables, relationMap, orderArgs, filte
|
|
|
3367
3567
|
q = q.limit(limit);
|
|
3368
3568
|
}
|
|
3369
3569
|
}
|
|
3370
|
-
|
|
3570
|
+
const rows = await q;
|
|
3571
|
+
if (cursorEntries && cursorSelected) {
|
|
3572
|
+
attachRowCursors(rows, cursorEntries);
|
|
3573
|
+
}
|
|
3574
|
+
return remapToGraphQLArrayOutput(rows, tableName, table, relationMap);
|
|
3371
3575
|
} catch (e) {
|
|
3372
3576
|
throw toGraphQLError(e);
|
|
3373
3577
|
}
|
|
@@ -4083,7 +4287,9 @@ var generateSelectArray3 = (db, tableName, tables, relationMap, orderArgs, filte
|
|
|
4083
4287
|
single: false,
|
|
4084
4288
|
filterCtx,
|
|
4085
4289
|
pkNames,
|
|
4086
|
-
db: executor
|
|
4290
|
+
db: executor,
|
|
4291
|
+
// SQLite sorts NULLs as the smallest values (first in ASC).
|
|
4292
|
+
nullOrdering: "nulls-smallest"
|
|
4087
4293
|
});
|
|
4088
4294
|
} catch (e) {
|
|
4089
4295
|
throw toGraphQLError(e);
|