@vantreeseba/drizzle-graphql 5.1.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 +328 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +331 -56
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -57,12 +57,24 @@ type FilterColumnOperatorsCore<TColumn extends Column, TColType = GetColumnGqlDa
|
|
|
57
57
|
notIlike: string;
|
|
58
58
|
startsWith: string;
|
|
59
59
|
endsWith: string;
|
|
60
|
-
|
|
60
|
+
/**
|
|
61
|
+
* String columns: safe substring match (`%`, `_` and `\` matched literally).
|
|
62
|
+
* JSON columns: structural containment (Postgres `@>` / MySQL JSON_CONTAINS).
|
|
63
|
+
*/
|
|
64
|
+
contains: string | TColType;
|
|
61
65
|
iStartsWith: string;
|
|
62
66
|
iEndsWith: string;
|
|
63
67
|
iContains: string;
|
|
64
68
|
inArray: Array<TColType>;
|
|
65
69
|
notInArray: Array<TColType>;
|
|
70
|
+
/** Array columns only: the array contains this element. */
|
|
71
|
+
has: TColType extends Array<infer TElement> ? TElement : never;
|
|
72
|
+
/** Array columns only: the array overlaps these elements (`&&`). */
|
|
73
|
+
hasSome: TColType extends Array<infer TElement> ? Array<TElement> : never;
|
|
74
|
+
/** Array columns only: the array contains all of these elements (`@>`). */
|
|
75
|
+
hasEvery: TColType extends Array<infer TElement> ? Array<TElement> : never;
|
|
76
|
+
/** Array columns only: when true, matches arrays with no elements. */
|
|
77
|
+
isEmpty: boolean;
|
|
66
78
|
isNull: boolean;
|
|
67
79
|
isNotNull: boolean;
|
|
68
80
|
}>;
|
package/dist/index.d.ts
CHANGED
|
@@ -57,12 +57,24 @@ type FilterColumnOperatorsCore<TColumn extends Column, TColType = GetColumnGqlDa
|
|
|
57
57
|
notIlike: string;
|
|
58
58
|
startsWith: string;
|
|
59
59
|
endsWith: string;
|
|
60
|
-
|
|
60
|
+
/**
|
|
61
|
+
* String columns: safe substring match (`%`, `_` and `\` matched literally).
|
|
62
|
+
* JSON columns: structural containment (Postgres `@>` / MySQL JSON_CONTAINS).
|
|
63
|
+
*/
|
|
64
|
+
contains: string | TColType;
|
|
61
65
|
iStartsWith: string;
|
|
62
66
|
iEndsWith: string;
|
|
63
67
|
iContains: string;
|
|
64
68
|
inArray: Array<TColType>;
|
|
65
69
|
notInArray: Array<TColType>;
|
|
70
|
+
/** Array columns only: the array contains this element. */
|
|
71
|
+
has: TColType extends Array<infer TElement> ? TElement : never;
|
|
72
|
+
/** Array columns only: the array overlaps these elements (`&&`). */
|
|
73
|
+
hasSome: TColType extends Array<infer TElement> ? Array<TElement> : never;
|
|
74
|
+
/** Array columns only: the array contains all of these elements (`@>`). */
|
|
75
|
+
hasEvery: TColType extends Array<infer TElement> ? Array<TElement> : never;
|
|
76
|
+
/** Array columns only: when true, matches arrays with no elements. */
|
|
77
|
+
isEmpty: boolean;
|
|
66
78
|
isNull: boolean;
|
|
67
79
|
isNotNull: boolean;
|
|
68
80
|
}>;
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
aliasedTable,
|
|
14
14
|
and,
|
|
15
|
+
arrayContains,
|
|
16
|
+
arrayOverlaps,
|
|
15
17
|
asc,
|
|
16
18
|
desc,
|
|
17
19
|
eq,
|
|
@@ -416,6 +418,21 @@ var geoXyInputType = new GraphQLInputObjectType({
|
|
|
416
418
|
}
|
|
417
419
|
});
|
|
418
420
|
var columnToGraphQLCore = (column, columnName, tableName, isInput) => {
|
|
421
|
+
const dimensions = column.dimensions;
|
|
422
|
+
if (dimensions !== void 0 && dimensions > 0) {
|
|
423
|
+
const inner = columnBaseToGraphQLCore(column, columnName, tableName, isInput);
|
|
424
|
+
const innerDesc = inner.description ?? inner.type.name;
|
|
425
|
+
let type = inner.type;
|
|
426
|
+
let description = innerDesc;
|
|
427
|
+
for (let i = 0; i < dimensions; i++) {
|
|
428
|
+
type = new GraphQLList(new GraphQLNonNull(type));
|
|
429
|
+
description = `Array<${description}>`;
|
|
430
|
+
}
|
|
431
|
+
return { type, description };
|
|
432
|
+
}
|
|
433
|
+
return columnBaseToGraphQLCore(column, columnName, tableName, isInput);
|
|
434
|
+
};
|
|
435
|
+
var columnBaseToGraphQLCore = (column, columnName, tableName, isInput) => {
|
|
419
436
|
const { type: baseType, constraint } = extractExtendedColumnType(column);
|
|
420
437
|
switch (baseType) {
|
|
421
438
|
case "boolean":
|
|
@@ -447,28 +464,10 @@ var columnToGraphQLCore = (column, columnName, tableName, isInput) => {
|
|
|
447
464
|
if (column instanceof PgDateString) {
|
|
448
465
|
return isInput ? { type: GraphQLString, description: "Date" } : { type: GraphQLDate, description: "Date" };
|
|
449
466
|
}
|
|
450
|
-
{
|
|
451
|
-
const dims = column.dimensions;
|
|
452
|
-
if (dims !== void 0 && dims > 0) {
|
|
453
|
-
return {
|
|
454
|
-
type: new GraphQLList(new GraphQLNonNull(GraphQLString)),
|
|
455
|
-
description: "Array<String>"
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
467
|
return { type: GraphQLString, description: "String" };
|
|
460
468
|
case "bigint":
|
|
461
469
|
return { type: GraphQLBigIntString, description: "BigInt" };
|
|
462
470
|
case "number": {
|
|
463
|
-
const dims = column.dimensions;
|
|
464
|
-
if (dims !== void 0 && dims > 0) {
|
|
465
|
-
const baseDesc = is2(column, PgInteger) || is2(column, PgSerial) ? "Integer" : "Float";
|
|
466
|
-
const baseType2 = baseDesc === "Integer" ? GraphQLInt : GraphQLFloat;
|
|
467
|
-
return {
|
|
468
|
-
type: new GraphQLList(new GraphQLNonNull(baseType2)),
|
|
469
|
-
description: `Array<${baseDesc}>`
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
471
|
return is2(column, PgInteger) || is2(column, PgSerial) || is2(column, MySqlInt) || is2(column, MySqlSerial) || is2(column, SQLiteInteger) ? { type: GraphQLInt, description: "Integer" } : { type: GraphQLFloat, description: "Float" };
|
|
473
472
|
}
|
|
474
473
|
case "array": {
|
|
@@ -813,52 +812,95 @@ var innerOrder = new GraphQLInputObjectType2({
|
|
|
813
812
|
}
|
|
814
813
|
}
|
|
815
814
|
});
|
|
816
|
-
var
|
|
815
|
+
var columnDialect = (column) => {
|
|
816
|
+
const ct = column.columnType ?? "";
|
|
817
|
+
if (ct.startsWith("Pg")) {
|
|
818
|
+
return "pg";
|
|
819
|
+
}
|
|
820
|
+
if (ct.startsWith("MySql")) {
|
|
821
|
+
return "mysql";
|
|
822
|
+
}
|
|
823
|
+
if (ct.startsWith("SQLite")) {
|
|
824
|
+
return "sqlite";
|
|
825
|
+
}
|
|
826
|
+
return void 0;
|
|
827
|
+
};
|
|
828
|
+
var resolveGenericFilterDescriptor = (column, columnGraphQLType) => {
|
|
829
|
+
if (columnGraphQLType.type === GraphQLJSON) {
|
|
830
|
+
return { name: "JSON", kind: "json" };
|
|
831
|
+
}
|
|
832
|
+
if (columnGraphQLType.type instanceof GraphQLList2) {
|
|
833
|
+
let element = columnGraphQLType.type.ofType;
|
|
834
|
+
if (element instanceof GraphQLNonNull2) {
|
|
835
|
+
element = element.ofType;
|
|
836
|
+
}
|
|
837
|
+
return { name: `${element.name}Array`, kind: "array" };
|
|
838
|
+
}
|
|
817
839
|
if (columnGraphQLType.type === GraphQLUUID) {
|
|
818
|
-
return "Id";
|
|
840
|
+
return { name: "Id", kind: "scalar" };
|
|
819
841
|
}
|
|
820
842
|
if (columnGraphQLType.type === GraphQLBoolean2) {
|
|
821
|
-
return "Boolean";
|
|
843
|
+
return { name: "Boolean", kind: "scalar" };
|
|
822
844
|
}
|
|
823
845
|
if (columnGraphQLType.type instanceof GraphQLEnumType2) {
|
|
824
|
-
return columnGraphQLType.type.name;
|
|
846
|
+
return { name: columnGraphQLType.type.name, kind: "scalar" };
|
|
825
847
|
}
|
|
826
848
|
if (columnGraphQLType.type === GraphQLBigIntString) {
|
|
827
|
-
return "BigInt";
|
|
849
|
+
return { name: "BigInt", kind: "scalar" };
|
|
828
850
|
}
|
|
829
851
|
if (columnGraphQLType.type === GraphQLDecimalString) {
|
|
830
|
-
return "Decimal";
|
|
831
|
-
}
|
|
832
|
-
if (columnGraphQLType.type instanceof GraphQLList2) {
|
|
833
|
-
const desc2 = columnGraphQLType.description ?? "";
|
|
834
|
-
let inner = columnGraphQLType.type.ofType;
|
|
835
|
-
if (inner instanceof GraphQLNonNull2) {
|
|
836
|
-
inner = inner.ofType;
|
|
837
|
-
}
|
|
838
|
-
if (desc2.includes("Integer") || inner === GraphQLInt2) {
|
|
839
|
-
return "IntArray";
|
|
840
|
-
}
|
|
841
|
-
if (desc2.includes("Float") || inner === GraphQLFloat2) {
|
|
842
|
-
return "FloatArray";
|
|
843
|
-
}
|
|
844
|
-
return "StringArray";
|
|
852
|
+
return { name: "Decimal", kind: "scalar" };
|
|
845
853
|
}
|
|
846
854
|
const ct = column.columnType ?? "";
|
|
847
855
|
if (ct === "PgTimestamp" || ct === "PgTimestampString" || ct === "PgDate") {
|
|
848
|
-
return "DateTime";
|
|
856
|
+
return { name: "DateTime", kind: "scalar" };
|
|
849
857
|
}
|
|
850
858
|
if (columnGraphQLType.type === GraphQLInt2) {
|
|
851
|
-
return "Int";
|
|
859
|
+
return { name: "Int", kind: "scalar" };
|
|
852
860
|
}
|
|
853
861
|
if (columnGraphQLType.type === GraphQLFloat2) {
|
|
854
|
-
return "Float";
|
|
862
|
+
return { name: "Float", kind: "scalar" };
|
|
855
863
|
}
|
|
856
|
-
return "String";
|
|
864
|
+
return { name: "String", kind: "scalar" };
|
|
865
|
+
};
|
|
866
|
+
var jsonFilterFields = (column, colType) => {
|
|
867
|
+
const dialect = columnDialect(column);
|
|
868
|
+
return {
|
|
869
|
+
eq: { type: colType, description: "JSON equality on the whole value" },
|
|
870
|
+
ne: { type: colType, description: "JSON inequality on the whole value" },
|
|
871
|
+
...dialect === "pg" || dialect === "mysql" ? {
|
|
872
|
+
contains: {
|
|
873
|
+
type: colType,
|
|
874
|
+
description: "Value structurally contains this JSON (Postgres `@>` / MySQL JSON_CONTAINS)"
|
|
875
|
+
}
|
|
876
|
+
} : {},
|
|
877
|
+
isNull: { type: GraphQLBoolean2 },
|
|
878
|
+
isNotNull: { type: GraphQLBoolean2 }
|
|
879
|
+
};
|
|
880
|
+
};
|
|
881
|
+
var arrayFilterFields = (colType, colArr, colDesc) => {
|
|
882
|
+
let element = colType.ofType;
|
|
883
|
+
if (element instanceof GraphQLNonNull2) {
|
|
884
|
+
element = element.ofType;
|
|
885
|
+
}
|
|
886
|
+
const elementList = new GraphQLList2(new GraphQLNonNull2(element));
|
|
887
|
+
return {
|
|
888
|
+
eq: { type: colType, description: colDesc },
|
|
889
|
+
ne: { type: colType, description: colDesc },
|
|
890
|
+
has: { type: element, description: "Array contains this element" },
|
|
891
|
+
hasSome: { type: elementList, description: "Array contains at least one of these elements (overlap, `&&`)" },
|
|
892
|
+
hasEvery: { type: elementList, description: "Array contains every one of these elements (containment, `@>`)" },
|
|
893
|
+
isEmpty: { type: GraphQLBoolean2, description: "When true, matches arrays with no elements" },
|
|
894
|
+
inArray: { type: colArr, description: `Array<${colDesc}>` },
|
|
895
|
+
notInArray: { type: colArr, description: `Array<${colDesc}>` },
|
|
896
|
+
isNull: { type: GraphQLBoolean2 },
|
|
897
|
+
isNotNull: { type: GraphQLBoolean2 }
|
|
898
|
+
};
|
|
857
899
|
};
|
|
858
900
|
var FILTERS_WITHOUT_STRING_OPS = /* @__PURE__ */ new Set(["Id", "Int", "Float", "BigInt", "Decimal"]);
|
|
859
901
|
var generateColumnFilterValues = (column, tableName, columnName, cacheCtx) => {
|
|
860
902
|
const columnGraphQLType = drizzleColumnToGraphQLType(column, columnName, tableName, true, false, true);
|
|
861
|
-
const genericName =
|
|
903
|
+
const { name: genericName, kind } = resolveGenericFilterDescriptor(column, columnGraphQLType);
|
|
862
904
|
const cached = cacheCtx.genericFilterCache.get(genericName);
|
|
863
905
|
if (cached) {
|
|
864
906
|
return cached;
|
|
@@ -867,7 +909,7 @@ var generateColumnFilterValues = (column, tableName, columnName, cacheCtx) => {
|
|
|
867
909
|
const colDesc = columnGraphQLType.description;
|
|
868
910
|
const colArr = new GraphQLList2(new GraphQLNonNull2(colType));
|
|
869
911
|
const omitStringOps = FILTERS_WITHOUT_STRING_OPS.has(genericName);
|
|
870
|
-
const baseFields = {
|
|
912
|
+
const baseFields = kind === "json" ? jsonFilterFields(column, colType) : kind === "array" ? arrayFilterFields(colType, colArr, colDesc) : {
|
|
871
913
|
eq: { type: colType, description: colDesc },
|
|
872
914
|
ne: { type: colType, description: colDesc },
|
|
873
915
|
lt: { type: colType, description: colDesc },
|
|
@@ -978,6 +1020,13 @@ var generateTableSelectTypeFieldsCached = (table, tableName) => {
|
|
|
978
1020
|
drizzleColumnToGraphQLType(columnDescription, columnName, tableName)
|
|
979
1021
|
])
|
|
980
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
|
+
}
|
|
981
1030
|
fieldMap.set(table, remapped);
|
|
982
1031
|
return remapped;
|
|
983
1032
|
};
|
|
@@ -1440,12 +1489,28 @@ var safeLikeCondition = (column, pattern, insensitive) => {
|
|
|
1440
1489
|
const isPg = (column.columnType ?? "").startsWith("Pg");
|
|
1441
1490
|
return isPg ? sql`${column} ilike ${pattern} escape ${LIKE_ESCAPE_CHAR}` : sql`lower(${column}) like ${pattern.toLowerCase()} escape ${LIKE_ESCAPE_CHAR}`;
|
|
1442
1491
|
};
|
|
1492
|
+
var isJsonColumn2 = (column) => (column.columnType ?? "").includes("Json");
|
|
1493
|
+
var jsonContains = (column, columnName, value) => {
|
|
1494
|
+
const serialized = JSON.stringify(value);
|
|
1495
|
+
switch (columnDialect(column)) {
|
|
1496
|
+
case "pg":
|
|
1497
|
+
return column.columnType === "PgJson" ? sql`${column}::jsonb @> ${serialized}::jsonb` : sql`${column} @> ${serialized}::jsonb`;
|
|
1498
|
+
case "mysql":
|
|
1499
|
+
return sql`json_contains(${column}, ${serialized})`;
|
|
1500
|
+
default:
|
|
1501
|
+
throw new GraphQLError3(`WHERE ${columnName}: Operator 'contains' is not supported for this dialect!`);
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1443
1504
|
var extractFiltersColumn = (column, columnName, operators) => {
|
|
1444
1505
|
const { OR, AND, NOT, ...restOperators } = operators;
|
|
1445
1506
|
const entries = Object.entries(restOperators);
|
|
1446
1507
|
const singleValueOps = { eq, ne, gt, gte, lt, lte };
|
|
1447
1508
|
const stringValueOps = { like, notLike, ilike, notIlike };
|
|
1448
1509
|
const arrayValueOps = { inArray, notInArray };
|
|
1510
|
+
const arrayMembershipOps = {
|
|
1511
|
+
hasSome: arrayOverlaps,
|
|
1512
|
+
hasEvery: arrayContains
|
|
1513
|
+
};
|
|
1449
1514
|
const nullableOps = { isNull, isNotNull };
|
|
1450
1515
|
const variants = [];
|
|
1451
1516
|
for (const [operatorName, operatorValue] of entries) {
|
|
@@ -1457,6 +1522,8 @@ var extractFiltersColumn = (column, columnName, operators) => {
|
|
|
1457
1522
|
variants.push(singleValueOps[operatorName](column, singleValue));
|
|
1458
1523
|
} else if (operatorName in stringValueOps) {
|
|
1459
1524
|
variants.push(stringValueOps[operatorName](column, operatorValue));
|
|
1525
|
+
} else if (operatorName === "contains" && isJsonColumn2(column)) {
|
|
1526
|
+
variants.push(jsonContains(column, columnName, operatorValue));
|
|
1460
1527
|
} else if (operatorName in safeLikeOps) {
|
|
1461
1528
|
const { buildPattern, insensitive } = safeLikeOps[operatorName];
|
|
1462
1529
|
variants.push(safeLikeCondition(column, buildPattern(operatorValue), insensitive));
|
|
@@ -1466,6 +1533,15 @@ var extractFiltersColumn = (column, columnName, operators) => {
|
|
|
1466
1533
|
}
|
|
1467
1534
|
const arrayValue = operatorValue.map((val) => remapFromGraphQLCore(val, column, columnName));
|
|
1468
1535
|
variants.push(arrayValueOps[operatorName](column, arrayValue));
|
|
1536
|
+
} else if (operatorName === "has") {
|
|
1537
|
+
variants.push(arrayContains(column, [operatorValue]));
|
|
1538
|
+
} else if (operatorName in arrayMembershipOps) {
|
|
1539
|
+
if (!operatorValue.length) {
|
|
1540
|
+
throw new GraphQLError3(`WHERE ${columnName}: Unable to use operator ${operatorName} with an empty array!`);
|
|
1541
|
+
}
|
|
1542
|
+
variants.push(arrayMembershipOps[operatorName](column, operatorValue));
|
|
1543
|
+
} else if (operatorName === "isEmpty") {
|
|
1544
|
+
variants.push(sql`cardinality(${column}) = 0`);
|
|
1469
1545
|
} else if (operatorName in nullableOps) {
|
|
1470
1546
|
variants.push(nullableOps[operatorName](column));
|
|
1471
1547
|
} else {
|
|
@@ -1778,6 +1854,117 @@ var primaryKeyOrderExprs = (table, pkNames) => {
|
|
|
1778
1854
|
const cols = getColumns(table);
|
|
1779
1855
|
return pkNames.map((n) => cols[n]).filter(Boolean).map((col) => asc(col));
|
|
1780
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;
|
|
1781
1968
|
var getUniqueColumnSets = (table, getTableConfig4) => {
|
|
1782
1969
|
const cols = getColumns(table);
|
|
1783
1970
|
const propNameByColumnName = new Map(Object.entries(cols).map(([propName, col]) => [col.name, propName]));
|
|
@@ -2102,6 +2289,10 @@ var selectArrayArgs = (orderArgs, filterArgs, distinctEnum) => ({
|
|
|
2102
2289
|
limit: { type: GraphQLInt2 },
|
|
2103
2290
|
orderBy: { type: orderArgs },
|
|
2104
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
|
+
},
|
|
2105
2296
|
...distinctEnum ? { distinct: { type: new GraphQLList2(new GraphQLNonNull2(distinctEnum)) } } : {}
|
|
2106
2297
|
});
|
|
2107
2298
|
var selectSingleArgs = (orderArgs, filterArgs) => ({
|
|
@@ -2124,9 +2315,31 @@ var runRelationalSelect = async (opts) => {
|
|
|
2124
2315
|
where,
|
|
2125
2316
|
single,
|
|
2126
2317
|
filterCtx,
|
|
2127
|
-
pkNames
|
|
2318
|
+
pkNames,
|
|
2319
|
+
after
|
|
2128
2320
|
} = opts;
|
|
2129
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;
|
|
2130
2343
|
const needsDefaultOrder = single || offset != null || opts.limit != null;
|
|
2131
2344
|
let distinctKeys;
|
|
2132
2345
|
if (distinct) {
|
|
@@ -2157,9 +2370,21 @@ var runRelationalSelect = async (opts) => {
|
|
|
2157
2370
|
orderBy: distinctKeys ? (aliasedTable2) => [
|
|
2158
2371
|
...orderBy ? extractOrderBy(aliasedTable2, orderBy, relationFilterCtx(filterCtx, tableName)) : [],
|
|
2159
2372
|
...primaryKeyOrderExprs(aliasedTable2, pkNames)
|
|
2160
|
-
] :
|
|
2161
|
-
|
|
2162
|
-
|
|
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
|
+
)
|
|
2163
2388
|
} : void 0,
|
|
2164
2389
|
with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, typeNameMapper, filterCtx) : void 0
|
|
2165
2390
|
};
|
|
@@ -2167,8 +2392,16 @@ var runRelationalSelect = async (opts) => {
|
|
|
2167
2392
|
const result2 = await queryBase.findFirst(params);
|
|
2168
2393
|
return result2 ? remapToGraphQLSingleOutput(result2, tableName, table, relationMap) : void 0;
|
|
2169
2394
|
}
|
|
2395
|
+
if (cursorEntries && cursorSelected) {
|
|
2396
|
+
for (const [column] of cursorEntries) {
|
|
2397
|
+
params.columns[column] = true;
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2170
2400
|
params.limit = distinctKeys ? void 0 : opts.limit;
|
|
2171
2401
|
const result = await queryBase.findMany(params);
|
|
2402
|
+
if (cursorEntries && cursorSelected) {
|
|
2403
|
+
attachRowCursors(result, cursorEntries);
|
|
2404
|
+
}
|
|
2172
2405
|
return remapToGraphQLArrayOutput(result, tableName, table, relationMap);
|
|
2173
2406
|
};
|
|
2174
2407
|
var eagerLoadMutationRelations = async (db, tableName, rows, pkNames, withParams) => {
|
|
@@ -2747,7 +2980,9 @@ var generateSelectArray = (db, tableName, tables, relationMap, orderArgs, filter
|
|
|
2747
2980
|
single: false,
|
|
2748
2981
|
filterCtx,
|
|
2749
2982
|
pkNames,
|
|
2750
|
-
db: executor
|
|
2983
|
+
db: executor,
|
|
2984
|
+
// MySQL sorts NULLs as the smallest values (first in ASC).
|
|
2985
|
+
nullOrdering: "nulls-smallest"
|
|
2751
2986
|
});
|
|
2752
2987
|
} catch (e) {
|
|
2753
2988
|
throw toGraphQLError(e);
|
|
@@ -3212,7 +3447,7 @@ var generateSchemaData = (db, schema, relations, options) => {
|
|
|
3212
3447
|
};
|
|
3213
3448
|
|
|
3214
3449
|
// src/util/builders/pg.ts
|
|
3215
|
-
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";
|
|
3216
3451
|
import { getTableConfig as getTableConfig2, PgTable } from "drizzle-orm/pg-core";
|
|
3217
3452
|
import {
|
|
3218
3453
|
GraphQLError as GraphQLError6,
|
|
@@ -3249,16 +3484,48 @@ var generateSelectArray2 = (db, tableName, tables, relationMap, orderArgs, filte
|
|
|
3249
3484
|
single: false,
|
|
3250
3485
|
filterCtx,
|
|
3251
3486
|
pkNames,
|
|
3252
|
-
db: executor
|
|
3487
|
+
db: executor,
|
|
3488
|
+
// PostgreSQL sorts NULLs as the largest values (last in ASC).
|
|
3489
|
+
nullOrdering: "nulls-largest"
|
|
3253
3490
|
});
|
|
3254
3491
|
}
|
|
3255
|
-
const { offset, limit, orderBy, where, distinct } = args;
|
|
3492
|
+
const { offset, limit, orderBy, where, distinct, after } = args;
|
|
3256
3493
|
const selectedColumnsSql = extractSelectedColumnsFromTreeSQLFormat(
|
|
3257
3494
|
parsedInfo.fieldsByTypeName[typeName],
|
|
3258
3495
|
table,
|
|
3259
3496
|
{ tableName, relationMap, tables }
|
|
3260
3497
|
);
|
|
3261
|
-
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;
|
|
3262
3529
|
let distinctKeys;
|
|
3263
3530
|
if (distinct?.length) {
|
|
3264
3531
|
distinctKeys = await selectDistinctKeys({
|
|
@@ -3282,7 +3549,9 @@ var generateSelectArray2 = (db, tableName, tables, relationMap, orderArgs, filte
|
|
|
3282
3549
|
} else if (whereSql) {
|
|
3283
3550
|
q = q.where(whereSql);
|
|
3284
3551
|
}
|
|
3285
|
-
if (
|
|
3552
|
+
if (cursorEntries && !distinctKeys) {
|
|
3553
|
+
q = q.orderBy(...cursorOrderExprs(table, cursorEntries));
|
|
3554
|
+
} else if (orderBy) {
|
|
3286
3555
|
q = q.orderBy(
|
|
3287
3556
|
...extractOrderBy(table, orderBy, relationFilterCtx(filterCtx, tableName)),
|
|
3288
3557
|
...distinctKeys ? primaryKeyOrderExprs(table, pkNames) : []
|
|
@@ -3298,7 +3567,11 @@ var generateSelectArray2 = (db, tableName, tables, relationMap, orderArgs, filte
|
|
|
3298
3567
|
q = q.limit(limit);
|
|
3299
3568
|
}
|
|
3300
3569
|
}
|
|
3301
|
-
|
|
3570
|
+
const rows = await q;
|
|
3571
|
+
if (cursorEntries && cursorSelected) {
|
|
3572
|
+
attachRowCursors(rows, cursorEntries);
|
|
3573
|
+
}
|
|
3574
|
+
return remapToGraphQLArrayOutput(rows, tableName, table, relationMap);
|
|
3302
3575
|
} catch (e) {
|
|
3303
3576
|
throw toGraphQLError(e);
|
|
3304
3577
|
}
|
|
@@ -4014,7 +4287,9 @@ var generateSelectArray3 = (db, tableName, tables, relationMap, orderArgs, filte
|
|
|
4014
4287
|
single: false,
|
|
4015
4288
|
filterCtx,
|
|
4016
4289
|
pkNames,
|
|
4017
|
-
db: executor
|
|
4290
|
+
db: executor,
|
|
4291
|
+
// SQLite sorts NULLs as the smallest values (first in ASC).
|
|
4292
|
+
nullOrdering: "nulls-smallest"
|
|
4018
4293
|
});
|
|
4019
4294
|
} catch (e) {
|
|
4020
4295
|
throw toGraphQLError(e);
|