@vantreeseba/drizzle-graphql 10.0.0 → 10.0.2
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 +9 -1
- package/dist/index.cjs +382 -247
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +383 -248
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Column, One, SQL, Table, aliasedTable, and, arrayContains, arrayOverlaps, asc, avg, count, countDistinct, desc, eq, extractExtendedColumnType, getColumns, getTableAsAliasSQL,
|
|
1
|
+
import { Column, One, SQL, Table, aliasedTable, and, arrayContains, arrayOverlaps, asc, avg, count, countDistinct, desc, eq, extractExtendedColumnType, getColumns, getTableAsAliasSQL, gt, gte, ilike, inArray, is, isNotNull, isNull, like, lt, lte, max, min, ne, not, notIlike, notInArray, notLike, or, relationsFilterToSQL, sql, sum } from "drizzle-orm";
|
|
2
2
|
import { MySqlAsyncDatabase, MySqlInt, MySqlSerial, MySqlTable, getTableConfig } from "drizzle-orm/mysql-core";
|
|
3
3
|
import { PgAsyncDatabase, PgDate, PgDateString, PgInteger, PgSerial, PgTable, PgTimestamp, PgTimestampString, PgUUID, getTableConfig as getTableConfig$1 } from "drizzle-orm/pg-core";
|
|
4
4
|
import { SQLiteAsyncDatabase, SQLiteInteger, SQLiteTable, getTableConfig as getTableConfig$2 } from "drizzle-orm/sqlite-core";
|
|
5
5
|
import { GraphQLBoolean, GraphQLEnumType, GraphQLError, GraphQLFloat, GraphQLInputObjectType, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, Kind, getArgumentValues } from "graphql";
|
|
6
|
-
import pluralize from "pluralize";
|
|
7
6
|
import { GraphQLDate, GraphQLDateTime, GraphQLJSON, GraphQLUUID } from "graphql-scalars";
|
|
7
|
+
import pluralize from "pluralize";
|
|
8
8
|
//#region src/util/builders/common/errors.ts
|
|
9
9
|
/**
|
|
10
10
|
* A `GraphQLError` carrying its context as data. The message says what went wrong, in prose
|
|
@@ -91,28 +91,6 @@ const applyErrorMapper = (entities, mapError) => {
|
|
|
91
91
|
for (const tableResolvers of Object.values(entities.fieldResolvers ?? {})) for (const [relationName, resolve] of Object.entries(tableResolvers)) tableResolvers[relationName] = wrap(resolve);
|
|
92
92
|
};
|
|
93
93
|
//#endregion
|
|
94
|
-
//#region src/util/case-ops/index.ts
|
|
95
|
-
const uncapitalize = (input) => input?.length ? `${input[0].toLocaleLowerCase()}${input.length > 1 ? input.slice(1, input.length) : ""}` : input;
|
|
96
|
-
const capitalize = (input) => input?.length ? `${input[0].toLocaleUpperCase()}${input.length > 1 ? input.slice(1, input.length) : ""}` : input;
|
|
97
|
-
/**
|
|
98
|
-
* The `typeNameMapper` preset behind `typeNameMapper: 'singularize'`.
|
|
99
|
-
*
|
|
100
|
-
* Derives the singular and plural forms of a table key with the library's own `pluralize`,
|
|
101
|
-
* which is what nearly every consumer with plural table keys (`export const tasks =
|
|
102
|
-
* pgTable('tasks', …)`) was writing by hand — and adding `pluralize` to their own
|
|
103
|
-
* dependencies to write.
|
|
104
|
-
*
|
|
105
|
-
* `tasks` becomes type `Task`, queries `tasks` / `task`, mutations `createTasks` /
|
|
106
|
-
* `createTask`. A key that is already singular is pluralized for the list side, so `task`
|
|
107
|
-
* yields the same names, and a capitalized key (`Tasks`) yields them too — the field forms
|
|
108
|
-
* are uncapitalized so the generated names follow GraphQL's camelCase convention whatever
|
|
109
|
-
* the schema key looks like. The type name is capitalized by the generator either way.
|
|
110
|
-
*/
|
|
111
|
-
const singularizeMapper = (tableName) => ({
|
|
112
|
-
singular: uncapitalize(pluralize.singular(tableName)),
|
|
113
|
-
plural: uncapitalize(pluralize.plural(tableName))
|
|
114
|
-
});
|
|
115
|
-
//#endregion
|
|
116
94
|
//#region src/util/scalars/index.ts
|
|
117
95
|
const INVALID_INPUT = "DRIZZLE_INVALID_INPUT_VALUE";
|
|
118
96
|
const UNREPRESENTABLE = "DRIZZLE_UNREPRESENTABLE_VALUE";
|
|
@@ -183,6 +161,28 @@ const GraphQLDecimalString = new GraphQLScalarType({
|
|
|
183
161
|
}
|
|
184
162
|
});
|
|
185
163
|
//#endregion
|
|
164
|
+
//#region src/util/case-ops/index.ts
|
|
165
|
+
const uncapitalize = (input) => input?.length ? `${input[0].toLocaleLowerCase()}${input.length > 1 ? input.slice(1, input.length) : ""}` : input;
|
|
166
|
+
const capitalize = (input) => input?.length ? `${input[0].toLocaleUpperCase()}${input.length > 1 ? input.slice(1, input.length) : ""}` : input;
|
|
167
|
+
/**
|
|
168
|
+
* The `typeNameMapper` preset behind `typeNameMapper: 'singularize'`.
|
|
169
|
+
*
|
|
170
|
+
* Derives the singular and plural forms of a table key with the library's own `pluralize`,
|
|
171
|
+
* which is what nearly every consumer with plural table keys (`export const tasks =
|
|
172
|
+
* pgTable('tasks', …)`) was writing by hand — and adding `pluralize` to their own
|
|
173
|
+
* dependencies to write.
|
|
174
|
+
*
|
|
175
|
+
* `tasks` becomes type `Task`, queries `tasks` / `task`, mutations `createTasks` /
|
|
176
|
+
* `createTask`. A key that is already singular is pluralized for the list side, so `task`
|
|
177
|
+
* yields the same names, and a capitalized key (`Tasks`) yields them too — the field forms
|
|
178
|
+
* are uncapitalized so the generated names follow GraphQL's camelCase convention whatever
|
|
179
|
+
* the schema key looks like. The type name is capitalized by the generator either way.
|
|
180
|
+
*/
|
|
181
|
+
const singularizeMapper = (tableName) => ({
|
|
182
|
+
singular: uncapitalize(pluralize.singular(tableName)),
|
|
183
|
+
plural: uncapitalize(pluralize.plural(tableName))
|
|
184
|
+
});
|
|
185
|
+
//#endregion
|
|
186
186
|
//#region src/util/type-converter/index.ts
|
|
187
187
|
const allowedNameChars = /^[a-zA-Z0-9_]+$/;
|
|
188
188
|
/**
|
|
@@ -412,7 +412,7 @@ const normalizeScalarOverride = (override, tableName, columnName) => {
|
|
|
412
412
|
* leaves the column on built-in detection.
|
|
413
413
|
*/
|
|
414
414
|
const registerScalarOverrides = (tables, config) => {
|
|
415
|
-
for (const [tableName, table] of Object.entries(tables)) for (const [columnName, column] of Object.entries(
|
|
415
|
+
for (const [tableName, table] of Object.entries(tables)) for (const [columnName, column] of Object.entries(getColumns(table))) {
|
|
416
416
|
const declared = config.scalars?.[tableName]?.[columnName];
|
|
417
417
|
let resolved;
|
|
418
418
|
if (declared !== void 0) resolved = normalizeScalarOverride(declared, tableName, columnName);
|
|
@@ -451,137 +451,6 @@ const drizzleColumnToGraphQLType = (column, columnName, tableName, forceNullable
|
|
|
451
451
|
return typeDesc;
|
|
452
452
|
};
|
|
453
453
|
//#endregion
|
|
454
|
-
//#region src/util/data-mappers/index.ts
|
|
455
|
-
const isJsonColumn$1 = (column) => (column.dataType ?? "").includes("json") && column.columnType !== "PgGeometryObject";
|
|
456
|
-
const remapToGraphQLCore = (key, value, tableName, column, relationMap) => {
|
|
457
|
-
if (Array.isArray(value)) {
|
|
458
|
-
const relations = relationMap?.[tableName];
|
|
459
|
-
if (relations?.[key]) {
|
|
460
|
-
const rel = relations[key];
|
|
461
|
-
return remapToGraphQLArrayOutput(value, rel.targetTableName, rel.relation?.targetTable ?? rel.relation?.referencedTable, relationMap);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
if (typeof value === "object" && value !== null) {
|
|
465
|
-
const relations = relationMap?.[tableName];
|
|
466
|
-
if (relations?.[key]) {
|
|
467
|
-
const rel = relations[key];
|
|
468
|
-
return remapToGraphQLSingleOutput(value, rel.targetTableName, rel.relation?.targetTable ?? rel.relation?.referencedTable, relationMap);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
if (!column) return value;
|
|
472
|
-
if (getColumnScalarOverride(column, false)) return value;
|
|
473
|
-
if (isJsonColumn$1(column)) return value;
|
|
474
|
-
if (value instanceof Date) return value.toISOString();
|
|
475
|
-
if (value instanceof Buffer) return Array.from(value);
|
|
476
|
-
if (typeof value === "bigint") return value.toString();
|
|
477
|
-
if (Array.isArray(value)) {
|
|
478
|
-
if (column.columnType === "PgGeometry" || column.columnType === "PgVector") return value;
|
|
479
|
-
return value.map((arrVal) => remapToGraphQLCore(key, arrVal, tableName, column, relationMap));
|
|
480
|
-
}
|
|
481
|
-
if (typeof value === "object" && value !== null) {
|
|
482
|
-
if (column.columnType === "PgGeometryObject") return value;
|
|
483
|
-
return JSON.stringify(value);
|
|
484
|
-
}
|
|
485
|
-
return value;
|
|
486
|
-
};
|
|
487
|
-
const remapToGraphQLSingleOutput = (queryOutput, tableName, table, relationMap) => {
|
|
488
|
-
const columns = getTableColumns(table);
|
|
489
|
-
for (const [key, value] of Object.entries(queryOutput)) {
|
|
490
|
-
if (value === void 0 || value === null) {
|
|
491
|
-
const relEntry = value === null ? relationMap?.[tableName]?.[key] : void 0;
|
|
492
|
-
if (relEntry && is(relEntry.relation, One)) {
|
|
493
|
-
queryOutput[key] = null;
|
|
494
|
-
continue;
|
|
495
|
-
}
|
|
496
|
-
delete queryOutput[key];
|
|
497
|
-
continue;
|
|
498
|
-
}
|
|
499
|
-
const column = columns[key];
|
|
500
|
-
if (value === 0n && column && column.columnType === "SQLiteBigInt" && !column.notNull) {
|
|
501
|
-
delete queryOutput[key];
|
|
502
|
-
continue;
|
|
503
|
-
}
|
|
504
|
-
queryOutput[key] = remapToGraphQLCore(key, value, tableName, column, relationMap);
|
|
505
|
-
}
|
|
506
|
-
return queryOutput;
|
|
507
|
-
};
|
|
508
|
-
const remapToGraphQLArrayOutput = (queryOutput, tableName, table, relationMap) => {
|
|
509
|
-
for (const entry of queryOutput) remapToGraphQLSingleOutput(entry, tableName, table, relationMap);
|
|
510
|
-
return queryOutput;
|
|
511
|
-
};
|
|
512
|
-
const remapFromGraphQLCore = (value, column, columnName) => {
|
|
513
|
-
if (getColumnScalarOverride(column, true)) {
|
|
514
|
-
if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === null) return Object.assign({}, value);
|
|
515
|
-
return value;
|
|
516
|
-
}
|
|
517
|
-
const dataType = column.dataType ?? "";
|
|
518
|
-
const columnType = column.columnType ?? "";
|
|
519
|
-
if (columnType === "SQLiteTimestamp" || columnType === "SQLiteTimestampMs" || columnType === "MySqlTimestamp" || columnType === "MySqlDateTime" || columnType === "PgTimestamp" || columnType === "PgTimestampString") {
|
|
520
|
-
const formatted = new Date(value);
|
|
521
|
-
if (Number.isNaN(formatted.getTime())) throw drizzleError(`Field '${columnName}' is not a valid date!`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
522
|
-
return formatted;
|
|
523
|
-
}
|
|
524
|
-
if ((columnType === "MySqlDate" || columnType === "PgDate") && typeof value === "string") {
|
|
525
|
-
const dateOnly = value.includes("T") ? value.split("T")[0] : value;
|
|
526
|
-
const check = new Date(dateOnly);
|
|
527
|
-
if (Number.isNaN(check.getTime())) throw drizzleError(`Field '${columnName}' is not a valid date!`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
528
|
-
return dateOnly;
|
|
529
|
-
}
|
|
530
|
-
if (dataType.includes("bigint")) try {
|
|
531
|
-
return BigInt(value);
|
|
532
|
-
} catch {
|
|
533
|
-
throw drizzleError(`Field '${columnName}' is not a BigInt!`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
534
|
-
}
|
|
535
|
-
if (dataType.includes("json") && column.columnType !== "PgGeometryObject") return value;
|
|
536
|
-
switch (dataType) {
|
|
537
|
-
case "date": {
|
|
538
|
-
const formatted = new Date(value);
|
|
539
|
-
if (Number.isNaN(formatted.getTime())) throw drizzleError(`Field '${columnName}' is not a valid date!`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
540
|
-
return formatted;
|
|
541
|
-
}
|
|
542
|
-
case "buffer":
|
|
543
|
-
if (!Array.isArray(value)) throw drizzleError(`Field '${columnName}' is not an array!`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
544
|
-
return Buffer.from(value);
|
|
545
|
-
case "json":
|
|
546
|
-
if (column.columnType === "PgGeometryObject") return value;
|
|
547
|
-
try {
|
|
548
|
-
return JSON.parse(value);
|
|
549
|
-
} catch (e) {
|
|
550
|
-
throw drizzleError(`Invalid JSON in field '${columnName}':\n${e instanceof Error ? e.message : "Unknown error"}`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
551
|
-
}
|
|
552
|
-
case "array":
|
|
553
|
-
if (!Array.isArray(value)) throw drizzleError(`Field '${columnName}' is not an array!`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
554
|
-
if (column.columnType === "PgGeometry" && value.length !== 2) throw drizzleError(`Invalid float tuple in field '${columnName}': expected array with length of 2, received ${value.length}`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
555
|
-
return value;
|
|
556
|
-
case "bigint": try {
|
|
557
|
-
return BigInt(value);
|
|
558
|
-
} catch (_error) {
|
|
559
|
-
throw drizzleError(`Field '${columnName}' is not a BigInt!`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
560
|
-
}
|
|
561
|
-
default:
|
|
562
|
-
if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === null) return Object.assign({}, value);
|
|
563
|
-
return value;
|
|
564
|
-
}
|
|
565
|
-
};
|
|
566
|
-
const remapFromGraphQLSingleInput = (queryInput, table, operation = "insert") => {
|
|
567
|
-
for (const [key, value] of Object.entries(queryInput)) if (value === void 0) delete queryInput[key];
|
|
568
|
-
else {
|
|
569
|
-
const column = getTableColumns(table)[key];
|
|
570
|
-
if (!column) throw drizzleError(`Unknown column: ${key}`, { code: "DRIZZLE_UNKNOWN_COLUMN" });
|
|
571
|
-
if (value === null && column.notNull) {
|
|
572
|
-
if (operation === "update") throw drizzleError(`Column '${key}' cannot be set to null.`, { code: "DRIZZLE_NOT_NULL" });
|
|
573
|
-
delete queryInput[key];
|
|
574
|
-
continue;
|
|
575
|
-
}
|
|
576
|
-
queryInput[key] = remapFromGraphQLCore(value, column, key);
|
|
577
|
-
}
|
|
578
|
-
return queryInput;
|
|
579
|
-
};
|
|
580
|
-
const remapFromGraphQLArrayInput = (queryInput, table, operation = "insert") => {
|
|
581
|
-
for (const entry of queryInput) remapFromGraphQLSingleInput(entry, table, operation);
|
|
582
|
-
return queryInput;
|
|
583
|
-
};
|
|
584
|
-
//#endregion
|
|
585
454
|
//#region src/util/builders/common/type-names.ts
|
|
586
455
|
/**
|
|
587
456
|
* The build's naming rule: the mapper's answer where it has one, otherwise the default name
|
|
@@ -624,6 +493,23 @@ const columnDialect = (column) => {
|
|
|
624
493
|
if (ct.startsWith("SQLite")) return "sqlite";
|
|
625
494
|
};
|
|
626
495
|
/**
|
|
496
|
+
* Whether the column stores JSON.
|
|
497
|
+
*
|
|
498
|
+
* Decided on drizzle's `dataType`, its semantic marker, rather than on `columnType`, which
|
|
499
|
+
* names the implementing class. drizzle-orm v1 uses compound dataType strings (`"object
|
|
500
|
+
* json"` for every json/jsonb column across the three dialects), hence the inclusion test.
|
|
501
|
+
*
|
|
502
|
+
* `PgGeometryObject` is excluded explicitly. That is currently redundant — it reports
|
|
503
|
+
* `"object geometry"` — but it is stored as json, has its own object type in the generated
|
|
504
|
+
* schema, and must not be reclassified if drizzle ever spells its dataType differently.
|
|
505
|
+
*
|
|
506
|
+
* One predicate for two callers that must agree: the filter builder, whose `path` and
|
|
507
|
+
* `contains` operators mean structural JSON containment rather than the string operators,
|
|
508
|
+
* and the data mappers, which transport a JSON value as-is instead of remapping it. They
|
|
509
|
+
* previously had a copy each, asking different questions of the column.
|
|
510
|
+
*/
|
|
511
|
+
const isJsonColumn = (column) => (column.dataType ?? "").includes("json") && column.columnType !== "PgGeometryObject";
|
|
512
|
+
/**
|
|
627
513
|
* Maps a Drizzle column to the generic filter type to use.
|
|
628
514
|
*
|
|
629
515
|
* Selection is keyed on the column's data type, never its name: a text column named
|
|
@@ -1054,6 +940,137 @@ const generateColumnFilterValues = (column, tableName, columnName, cacheCtx) =>
|
|
|
1054
940
|
return mainType;
|
|
1055
941
|
};
|
|
1056
942
|
//#endregion
|
|
943
|
+
//#region src/util/data-mappers/index.ts
|
|
944
|
+
const remapToGraphQLCore = (key, value, tableName, column, relationMap) => {
|
|
945
|
+
if (Array.isArray(value)) {
|
|
946
|
+
const relations = relationMap?.[tableName];
|
|
947
|
+
if (relations?.[key]) {
|
|
948
|
+
const rel = relations[key];
|
|
949
|
+
return remapToGraphQLArrayOutput(value, rel.targetTableName, rel.relation?.targetTable ?? rel.relation?.referencedTable, relationMap);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
if (typeof value === "object" && value !== null) {
|
|
953
|
+
const relations = relationMap?.[tableName];
|
|
954
|
+
if (relations?.[key]) {
|
|
955
|
+
const rel = relations[key];
|
|
956
|
+
return remapToGraphQLSingleOutput(value, rel.targetTableName, rel.relation?.targetTable ?? rel.relation?.referencedTable, relationMap);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
if (!column) return value;
|
|
960
|
+
if (getColumnScalarOverride(column, false)) return value;
|
|
961
|
+
if (isJsonColumn(column)) return value;
|
|
962
|
+
if (value instanceof Date) return value.toISOString();
|
|
963
|
+
if (value instanceof Buffer) return Array.from(value);
|
|
964
|
+
if (typeof value === "bigint") return value.toString();
|
|
965
|
+
if (Array.isArray(value)) {
|
|
966
|
+
if (column.columnType === "PgGeometry" || column.columnType === "PgVector") return value;
|
|
967
|
+
return value.map((arrVal) => remapToGraphQLCore(key, arrVal, tableName, column, relationMap));
|
|
968
|
+
}
|
|
969
|
+
if (typeof value === "object" && value !== null) {
|
|
970
|
+
if (column.columnType === "PgGeometryObject") return value;
|
|
971
|
+
return JSON.stringify(value);
|
|
972
|
+
}
|
|
973
|
+
return value;
|
|
974
|
+
};
|
|
975
|
+
const remapToGraphQLSingleOutput = (queryOutput, tableName, table, relationMap) => {
|
|
976
|
+
const columns = getColumns(table);
|
|
977
|
+
for (const [key, value] of Object.entries(queryOutput)) {
|
|
978
|
+
if (value === void 0 || value === null) {
|
|
979
|
+
const relEntry = value === null ? relationMap?.[tableName]?.[key] : void 0;
|
|
980
|
+
if (relEntry && is(relEntry.relation, One)) {
|
|
981
|
+
queryOutput[key] = null;
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
delete queryOutput[key];
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
const column = columns[key];
|
|
988
|
+
if (value === 0n && column && column.columnType === "SQLiteBigInt" && !column.notNull) {
|
|
989
|
+
delete queryOutput[key];
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
queryOutput[key] = remapToGraphQLCore(key, value, tableName, column, relationMap);
|
|
993
|
+
}
|
|
994
|
+
return queryOutput;
|
|
995
|
+
};
|
|
996
|
+
const remapToGraphQLArrayOutput = (queryOutput, tableName, table, relationMap) => {
|
|
997
|
+
for (const entry of queryOutput) remapToGraphQLSingleOutput(entry, tableName, table, relationMap);
|
|
998
|
+
return queryOutput;
|
|
999
|
+
};
|
|
1000
|
+
const remapFromGraphQLCore = (value, column, columnName) => {
|
|
1001
|
+
if (getColumnScalarOverride(column, true)) {
|
|
1002
|
+
if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === null) return Object.assign({}, value);
|
|
1003
|
+
return value;
|
|
1004
|
+
}
|
|
1005
|
+
const dataType = column.dataType ?? "";
|
|
1006
|
+
const columnType = column.columnType ?? "";
|
|
1007
|
+
if (columnType === "SQLiteTimestamp" || columnType === "SQLiteTimestampMs" || columnType === "MySqlTimestamp" || columnType === "MySqlDateTime" || columnType === "PgTimestamp" || columnType === "PgTimestampString") {
|
|
1008
|
+
const formatted = new Date(value);
|
|
1009
|
+
if (Number.isNaN(formatted.getTime())) throw drizzleError(`Field '${columnName}' is not a valid date!`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
1010
|
+
return formatted;
|
|
1011
|
+
}
|
|
1012
|
+
if ((columnType === "MySqlDate" || columnType === "PgDate") && typeof value === "string") {
|
|
1013
|
+
const dateOnly = value.includes("T") ? value.split("T")[0] : value;
|
|
1014
|
+
const check = new Date(dateOnly);
|
|
1015
|
+
if (Number.isNaN(check.getTime())) throw drizzleError(`Field '${columnName}' is not a valid date!`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
1016
|
+
return dateOnly;
|
|
1017
|
+
}
|
|
1018
|
+
if (dataType.includes("bigint")) try {
|
|
1019
|
+
return BigInt(value);
|
|
1020
|
+
} catch {
|
|
1021
|
+
throw drizzleError(`Field '${columnName}' is not a BigInt!`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
1022
|
+
}
|
|
1023
|
+
if (dataType.includes("json") && column.columnType !== "PgGeometryObject") return value;
|
|
1024
|
+
switch (dataType) {
|
|
1025
|
+
case "date": {
|
|
1026
|
+
const formatted = new Date(value);
|
|
1027
|
+
if (Number.isNaN(formatted.getTime())) throw drizzleError(`Field '${columnName}' is not a valid date!`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
1028
|
+
return formatted;
|
|
1029
|
+
}
|
|
1030
|
+
case "buffer":
|
|
1031
|
+
if (!Array.isArray(value)) throw drizzleError(`Field '${columnName}' is not an array!`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
1032
|
+
return Buffer.from(value);
|
|
1033
|
+
case "json":
|
|
1034
|
+
if (column.columnType === "PgGeometryObject") return value;
|
|
1035
|
+
try {
|
|
1036
|
+
return JSON.parse(value);
|
|
1037
|
+
} catch (e) {
|
|
1038
|
+
throw drizzleError(`Invalid JSON in field '${columnName}':\n${e instanceof Error ? e.message : "Unknown error"}`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
1039
|
+
}
|
|
1040
|
+
case "array":
|
|
1041
|
+
if (!Array.isArray(value)) throw drizzleError(`Field '${columnName}' is not an array!`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
1042
|
+
if (column.columnType === "PgGeometry" && value.length !== 2) throw drizzleError(`Invalid float tuple in field '${columnName}': expected array with length of 2, received ${value.length}`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
1043
|
+
return value;
|
|
1044
|
+
case "bigint": try {
|
|
1045
|
+
return BigInt(value);
|
|
1046
|
+
} catch (_error) {
|
|
1047
|
+
throw drizzleError(`Field '${columnName}' is not a BigInt!`, { code: "DRIZZLE_INVALID_INPUT_VALUE" });
|
|
1048
|
+
}
|
|
1049
|
+
default:
|
|
1050
|
+
if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === null) return Object.assign({}, value);
|
|
1051
|
+
return value;
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
const remapFromGraphQLSingleInput = (queryInput, table, operation = "insert") => {
|
|
1055
|
+
const columns = getColumns(table);
|
|
1056
|
+
for (const [key, value] of Object.entries(queryInput)) if (value === void 0) delete queryInput[key];
|
|
1057
|
+
else {
|
|
1058
|
+
const column = columns[key];
|
|
1059
|
+
if (!column) throw drizzleError(`Unknown column: ${key}`, { code: "DRIZZLE_UNKNOWN_COLUMN" });
|
|
1060
|
+
if (value === null && column.notNull) {
|
|
1061
|
+
if (operation === "update") throw drizzleError(`Column '${key}' cannot be set to null.`, { code: "DRIZZLE_NOT_NULL" });
|
|
1062
|
+
delete queryInput[key];
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
queryInput[key] = remapFromGraphQLCore(value, column, key);
|
|
1066
|
+
}
|
|
1067
|
+
return queryInput;
|
|
1068
|
+
};
|
|
1069
|
+
const remapFromGraphQLArrayInput = (queryInput, table, operation = "insert") => {
|
|
1070
|
+
for (const entry of queryInput) remapFromGraphQLSingleInput(entry, table, operation);
|
|
1071
|
+
return queryInput;
|
|
1072
|
+
};
|
|
1073
|
+
//#endregion
|
|
1057
1074
|
//#region src/util/builders/common/filters.ts
|
|
1058
1075
|
/**
|
|
1059
1076
|
* Escape character pinned via `ESCAPE` on every generated safe-LIKE predicate. Bound as a
|
|
@@ -1142,11 +1159,6 @@ const lowerMembership = (column, values, negated) => {
|
|
|
1142
1159
|
return negated ? sql`lower(${column}) not in (${lowered})` : sql`lower(${column}) in (${lowered})`;
|
|
1143
1160
|
};
|
|
1144
1161
|
/**
|
|
1145
|
-
* Whether the column stores JSON — its `contains` operator is structural containment,
|
|
1146
|
-
* not the safe substring operator string columns get.
|
|
1147
|
-
*/
|
|
1148
|
-
const isJsonColumn = (column) => (column.columnType ?? "").includes("Json");
|
|
1149
|
-
/**
|
|
1150
1162
|
* Structural JSON containment for the `contains` operator on json/jsonb columns.
|
|
1151
1163
|
* The value is serialized and bound as a parameter (never interpolated into the SQL text):
|
|
1152
1164
|
* - Postgres: `col @> $1::jsonb` (a plain `json` column is cast through jsonb — `json` has
|
|
@@ -1285,36 +1297,37 @@ const jsonPathCondition = (column, columnName, filter) => {
|
|
|
1285
1297
|
}
|
|
1286
1298
|
return variants.length ? variants.length > 1 ? and(...variants) : variants[0] : void 0;
|
|
1287
1299
|
};
|
|
1300
|
+
const singleValueOps = {
|
|
1301
|
+
eq,
|
|
1302
|
+
ne,
|
|
1303
|
+
gt,
|
|
1304
|
+
gte,
|
|
1305
|
+
lt,
|
|
1306
|
+
lte
|
|
1307
|
+
};
|
|
1308
|
+
const stringValueOps = {
|
|
1309
|
+
like,
|
|
1310
|
+
notLike,
|
|
1311
|
+
ilike,
|
|
1312
|
+
notIlike
|
|
1313
|
+
};
|
|
1314
|
+
const arrayValueOps = {
|
|
1315
|
+
inArray,
|
|
1316
|
+
notInArray
|
|
1317
|
+
};
|
|
1318
|
+
/** Membership operators for array columns: element list → SQL. Empty lists are rejected below. */
|
|
1319
|
+
const arrayMembershipOps = {
|
|
1320
|
+
hasSome: arrayOverlaps,
|
|
1321
|
+
hasEvery: arrayContains
|
|
1322
|
+
};
|
|
1323
|
+
const nullableOps = {
|
|
1324
|
+
isNull,
|
|
1325
|
+
isNotNull
|
|
1326
|
+
};
|
|
1288
1327
|
const extractFiltersColumn = (column, columnName, operators) => {
|
|
1289
1328
|
const { OR, AND, NOT, insensitive, ...restOperators } = operators;
|
|
1290
1329
|
const foldCase = insensitive === true;
|
|
1291
1330
|
const entries = Object.entries(restOperators);
|
|
1292
|
-
const singleValueOps = {
|
|
1293
|
-
eq,
|
|
1294
|
-
ne,
|
|
1295
|
-
gt,
|
|
1296
|
-
gte,
|
|
1297
|
-
lt,
|
|
1298
|
-
lte
|
|
1299
|
-
};
|
|
1300
|
-
const stringValueOps = {
|
|
1301
|
-
like,
|
|
1302
|
-
notLike,
|
|
1303
|
-
ilike,
|
|
1304
|
-
notIlike
|
|
1305
|
-
};
|
|
1306
|
-
const arrayValueOps = {
|
|
1307
|
-
inArray,
|
|
1308
|
-
notInArray
|
|
1309
|
-
};
|
|
1310
|
-
const arrayMembershipOps = {
|
|
1311
|
-
hasSome: arrayOverlaps,
|
|
1312
|
-
hasEvery: arrayContains
|
|
1313
|
-
};
|
|
1314
|
-
const nullableOps = {
|
|
1315
|
-
isNull,
|
|
1316
|
-
isNotNull
|
|
1317
|
-
};
|
|
1318
1331
|
const variants = [];
|
|
1319
1332
|
for (const [operatorName, operatorValue] of entries) {
|
|
1320
1333
|
if (operatorValue === null || operatorValue === false) continue;
|
|
@@ -1500,11 +1513,44 @@ const extractUniqueKeyFilter = (table, tableName, fieldName, members, value) =>
|
|
|
1500
1513
|
};
|
|
1501
1514
|
//#endregion
|
|
1502
1515
|
//#region src/util/builders/common/relation-filters.ts
|
|
1503
|
-
/**
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1516
|
+
/**
|
|
1517
|
+
* The narrowed contexts already handed out, keyed by the build-scoped base and then by
|
|
1518
|
+
* table. The base is created once per `buildSchema` and the result depends on nothing else,
|
|
1519
|
+
* so every call for one table produces the same value — a `WeakMap` on the base keeps them
|
|
1520
|
+
* for exactly as long as that build is reachable.
|
|
1521
|
+
*/
|
|
1522
|
+
const narrowedCtxCache = /* @__PURE__ */ new WeakMap();
|
|
1523
|
+
/**
|
|
1524
|
+
* Narrows the build-scoped relation filter context to the table a resolver is filtering.
|
|
1525
|
+
*
|
|
1526
|
+
* Cached rather than rebuilt: the base carries the build's whole `tables` and `relationMap`,
|
|
1527
|
+
* and the spread copied both on every call — three per parent row in the lazy relation
|
|
1528
|
+
* resolver, one per parent row in the relation aggregate, and up to four per select, several
|
|
1529
|
+
* of them inside callbacks drizzle invokes lazily.
|
|
1530
|
+
*
|
|
1531
|
+
* The one field written after the fact is `aliases`, the counter that keeps subquery aliases
|
|
1532
|
+
* apart ({@link extractRelationFilter}). Sharing it is what makes the caching safe rather
|
|
1533
|
+
* than a hazard: the counter now spans the process instead of restarting per extraction, and
|
|
1534
|
+
* aliases only ever have to be unique *within* one statement, which a number that never
|
|
1535
|
+
* repeats gives more surely than one that restarts.
|
|
1536
|
+
*/
|
|
1537
|
+
const relationFilterCtx = (base, tableKey) => {
|
|
1538
|
+
if (!base) return;
|
|
1539
|
+
let byTable = narrowedCtxCache.get(base);
|
|
1540
|
+
if (!byTable) {
|
|
1541
|
+
byTable = /* @__PURE__ */ new Map();
|
|
1542
|
+
narrowedCtxCache.set(base, byTable);
|
|
1543
|
+
}
|
|
1544
|
+
let ctx = byTable.get(tableKey);
|
|
1545
|
+
if (!ctx) {
|
|
1546
|
+
ctx = {
|
|
1547
|
+
...base,
|
|
1548
|
+
tableKey
|
|
1549
|
+
};
|
|
1550
|
+
byTable.set(tableKey, ctx);
|
|
1551
|
+
}
|
|
1552
|
+
return ctx;
|
|
1553
|
+
};
|
|
1508
1554
|
/**
|
|
1509
1555
|
* Correlates the parent row with the aliased target table using the relation's own join
|
|
1510
1556
|
* columns. Columns are matched by SQL name rather than object identity so this also works when
|
|
@@ -2145,7 +2191,7 @@ const isFilterableRelation = (relation) => !relation.through;
|
|
|
2145
2191
|
* (an object without a `direction`) is rejected with a clear error instead of being
|
|
2146
2192
|
* silently dropped.
|
|
2147
2193
|
*/
|
|
2148
|
-
const orderByEntries = (orderArgs) => Object.entries(orderArgs).sort((a, b) => (b[1]?.priority ?? 0) - (a[1]?.priority ?? 0)).
|
|
2194
|
+
const orderByEntries = (orderArgs) => Object.entries(orderArgs).filter(([, config]) => config).sort((a, b) => (b[1]?.priority ?? 0) - (a[1]?.priority ?? 0)).map(([column, config]) => {
|
|
2149
2195
|
if (typeof config === "object" && config.direction === void 0) throw drizzleError(`ORDER BY ${column}: ordering through a relation is not supported in this query`, { code: "DRIZZLE_INVALID_ORDER_BY" });
|
|
2150
2196
|
if (config.matchFilterOrder) throw drizzleError(`ORDER BY ${column}: 'matchFilterOrder' is not supported in this query`, { code: "DRIZZLE_INVALID_ORDER_BY" });
|
|
2151
2197
|
return [
|
|
@@ -2331,16 +2377,20 @@ const decodeCursorValue = (value) => {
|
|
|
2331
2377
|
return value;
|
|
2332
2378
|
};
|
|
2333
2379
|
/**
|
|
2334
|
-
*
|
|
2335
|
-
*
|
|
2336
|
-
*
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2380
|
+
* `encodeCursor` bound to one ordering, for encoding a whole page.
|
|
2381
|
+
*
|
|
2382
|
+
* The ordering spec is the same for every row of a page, so it is serialized once here and
|
|
2383
|
+
* spliced into each row's payload rather than re-serialized per row. The bytes are identical
|
|
2384
|
+
* to what building the payload object and stringifying it produces — same keys, same order —
|
|
2385
|
+
* so cursors stay interchangeable with ones issued before this was split out.
|
|
2386
|
+
*/
|
|
2387
|
+
const cursorEncoder = (entries) => {
|
|
2388
|
+
const spec = JSON.stringify(entries);
|
|
2389
|
+
const columns = entries.map(([column]) => column);
|
|
2390
|
+
return (row) => {
|
|
2391
|
+
const values = JSON.stringify(columns.map((column) => encodeCursorValue(row[column] ?? null)));
|
|
2392
|
+
return Buffer.from(`{"o":${spec},"v":${values}}`, "utf8").toString("base64url");
|
|
2342
2393
|
};
|
|
2343
|
-
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
2344
2394
|
};
|
|
2345
2395
|
/**
|
|
2346
2396
|
* Decodes an `after` cursor and validates it against the ordering the current request pages
|
|
@@ -2430,7 +2480,8 @@ const selectsCursorField = (info, table) => {
|
|
|
2430
2480
|
* dates and bigints into their transport forms.
|
|
2431
2481
|
*/
|
|
2432
2482
|
const attachRowCursors = (rows, entries) => {
|
|
2433
|
-
|
|
2483
|
+
const encode = cursorEncoder(entries);
|
|
2484
|
+
for (const row of rows) row[ROW_CURSOR_PROP] = encode(row);
|
|
2434
2485
|
};
|
|
2435
2486
|
/** Resolver for the `cursor` meta field: reads the value a list resolver attached, if any. */
|
|
2436
2487
|
const rowCursorResolver = (source) => source?.[ROW_CURSOR_PROP] ?? null;
|
|
@@ -2474,6 +2525,31 @@ const withPrimaryKeyColumns = (columns, table, pkNames) => {
|
|
|
2474
2525
|
return columns;
|
|
2475
2526
|
};
|
|
2476
2527
|
/**
|
|
2528
|
+
* Wraps a dialect's `getTableConfig` in a per-table cache.
|
|
2529
|
+
*
|
|
2530
|
+
* The dialect implementations are not lookups: each call re-runs the table's
|
|
2531
|
+
* `extraConfigBuilder` and rebuilds every index, check, unique constraint, primary key and
|
|
2532
|
+
* foreign key it declares. Nothing about a table changes after it is defined, so the result
|
|
2533
|
+
* is the same every time — but a build asks for it many times per table (primary-key names,
|
|
2534
|
+
* unique column sets, conflict targets, one per relation that keys off the target's PK), and
|
|
2535
|
+
* an overriding resolver asks again at request time.
|
|
2536
|
+
*
|
|
2537
|
+
* The cache is a `WeakMap` on the table object, so it costs nothing once the table is gone.
|
|
2538
|
+
* Bind it once per dialect module, at module scope, so the cache outlives a single
|
|
2539
|
+
* `buildSchema` call.
|
|
2540
|
+
*/
|
|
2541
|
+
const memoizeTableConfig = (getTableConfig) => {
|
|
2542
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
2543
|
+
return (table) => {
|
|
2544
|
+
let config = cache.get(table);
|
|
2545
|
+
if (config === void 0) {
|
|
2546
|
+
config = getTableConfig(table);
|
|
2547
|
+
cache.set(table, config);
|
|
2548
|
+
}
|
|
2549
|
+
return config;
|
|
2550
|
+
};
|
|
2551
|
+
};
|
|
2552
|
+
/**
|
|
2477
2553
|
* Resolves a table's primary-key property names using a dialect's `getTableConfig` to
|
|
2478
2554
|
* surface table-level composite keys (whose member columns aren't flagged `.primary`).
|
|
2479
2555
|
* Each dialect builder binds this with its own getTableConfig and reuses the binding for
|
|
@@ -3030,48 +3106,55 @@ const relationJoinColumns = (tree, table, selectionCtx) => {
|
|
|
3030
3106
|
}
|
|
3031
3107
|
return needed;
|
|
3032
3108
|
};
|
|
3033
|
-
|
|
3109
|
+
/**
|
|
3110
|
+
* The column the extractors fall back to when a selection names none of its own — a query that
|
|
3111
|
+
* asked only for `__typename`, or only for relation fields. The relational query builder still
|
|
3112
|
+
* needs at least one column, and some SQLite column types crash it when they are the only one
|
|
3113
|
+
* selected, so a column of any other type is preferred and the first column is the last resort.
|
|
3114
|
+
*/
|
|
3115
|
+
const fallbackColumnName = (tableColumns) => {
|
|
3116
|
+
const columnKeys = Object.entries(tableColumns);
|
|
3117
|
+
return columnKeys.find(([, column]) => !rqbCrashTypes.includes(column.columnType))?.[0] ?? columnKeys[0][0];
|
|
3118
|
+
};
|
|
3119
|
+
/**
|
|
3120
|
+
* Property names of the columns a selection reads: the ones it names itself, plus the join
|
|
3121
|
+
* columns its relation fields correlate on, plus the fallback when that leaves nothing.
|
|
3122
|
+
*/
|
|
3123
|
+
const selectedColumnNames = (tree, table, selectionCtx) => {
|
|
3034
3124
|
const tableColumns = getColumns(table);
|
|
3035
|
-
const
|
|
3036
|
-
const
|
|
3037
|
-
for (const [_fieldName, fieldData] of treeEntries) {
|
|
3125
|
+
const names = [];
|
|
3126
|
+
for (const fieldData of Object.values(tree)) {
|
|
3038
3127
|
if (!tableColumns[fieldData.name]) continue;
|
|
3039
|
-
|
|
3040
|
-
}
|
|
3041
|
-
for (const columnName of relationJoinColumns(tree, table, selectionCtx)) selectedColumns.push([columnName, true]);
|
|
3042
|
-
if (!selectedColumns.length) {
|
|
3043
|
-
const columnKeys = Object.entries(tableColumns);
|
|
3044
|
-
const columnName = columnKeys.find((e) => rqbCrashTypes.find((haram) => e[1].columnType !== haram))?.[0] ?? columnKeys[0][0];
|
|
3045
|
-
selectedColumns.push([columnName, true]);
|
|
3128
|
+
names.push(fieldData.name);
|
|
3046
3129
|
}
|
|
3130
|
+
names.push(...relationJoinColumns(tree, table, selectionCtx));
|
|
3131
|
+
if (!names.length) names.push(fallbackColumnName(tableColumns));
|
|
3132
|
+
return names;
|
|
3133
|
+
};
|
|
3134
|
+
/** The requested columns as the relational query builder's `columns` map. */
|
|
3135
|
+
const extractSelectedColumnsFromTree = (tree, table, selectionCtx) => {
|
|
3136
|
+
const selectedColumns = selectedColumnNames(tree, table, selectionCtx).map((columnName) => [columnName, true]);
|
|
3047
3137
|
return Object.fromEntries(selectedColumns);
|
|
3048
3138
|
};
|
|
3049
3139
|
/**
|
|
3140
|
+
* The same columns as a SQL select list.
|
|
3141
|
+
*
|
|
3050
3142
|
* Can't automatically determine column type on type level
|
|
3051
3143
|
* Since drizzle table types extend eachother
|
|
3052
3144
|
*/
|
|
3053
3145
|
const extractSelectedColumnsFromTreeSQLFormat = (tree, table, selectionCtx) => {
|
|
3054
3146
|
const tableColumns = getColumns(table);
|
|
3055
|
-
const
|
|
3056
|
-
const selectedColumns = [];
|
|
3057
|
-
for (const [_fieldName, fieldData] of treeEntries) {
|
|
3058
|
-
if (!tableColumns[fieldData.name]) continue;
|
|
3059
|
-
selectedColumns.push([fieldData.name, tableColumns[fieldData.name]]);
|
|
3060
|
-
}
|
|
3061
|
-
for (const columnName of relationJoinColumns(tree, table, selectionCtx)) selectedColumns.push([columnName, tableColumns[columnName]]);
|
|
3062
|
-
if (!selectedColumns.length) {
|
|
3063
|
-
const columnKeys = Object.entries(tableColumns);
|
|
3064
|
-
const columnName = columnKeys.find((e) => rqbCrashTypes.find((haram) => e[1].columnType !== haram))?.[0] ?? columnKeys[0][0];
|
|
3065
|
-
selectedColumns.push([columnName, tableColumns[columnName]]);
|
|
3066
|
-
}
|
|
3147
|
+
const selectedColumns = selectedColumnNames(tree, table, selectionCtx).map((columnName) => [columnName, tableColumns[columnName]]);
|
|
3067
3148
|
return Object.fromEntries(selectedColumns);
|
|
3068
3149
|
};
|
|
3069
3150
|
//#endregion
|
|
3070
3151
|
//#region src/util/builders/common/relation-params.ts
|
|
3071
|
-
const
|
|
3152
|
+
const extractRelationsParams = (relationMap, tables, tableName, info, typeName, options = {}) => {
|
|
3153
|
+
const { typeNameMapper, filterCtx, limits, scope, defaultOrderBy, resolveName } = options;
|
|
3154
|
+
if (!info) return;
|
|
3072
3155
|
const relationsForTable = relationMap[tableName];
|
|
3073
3156
|
if (!relationsForTable) return;
|
|
3074
|
-
const baseField = Object.entries(
|
|
3157
|
+
const baseField = Object.entries(info.fieldsByTypeName).find(([key, _value]) => key === typeName)?.[1];
|
|
3075
3158
|
if (!baseField) return;
|
|
3076
3159
|
const args = {};
|
|
3077
3160
|
for (const [relName, relEntry] of Object.entries(relationsForTable)) {
|
|
@@ -3108,15 +3191,11 @@ const extractRelationsParamsInner = (relationMap, tables, tableName, typeName, o
|
|
|
3108
3191
|
thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName), relWhere) : hasPagination && pkNames.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0;
|
|
3109
3192
|
thisRecord.offset = offset;
|
|
3110
3193
|
thisRecord.limit = limit;
|
|
3111
|
-
thisRecord.with =
|
|
3194
|
+
thisRecord.with = extractRelationsParams(relationMap, tables, targetTableName, relationField, relTypeName, options);
|
|
3112
3195
|
args[relName] = thisRecord;
|
|
3113
3196
|
}
|
|
3114
3197
|
return args;
|
|
3115
3198
|
};
|
|
3116
|
-
const extractRelationsParams = (relationMap, tables, tableName, info, typeName, typeNameMapper, filterCtx, limits, scope, defaultOrderBy, resolveName) => {
|
|
3117
|
-
if (!info) return;
|
|
3118
|
-
return extractRelationsParamsInner(relationMap, tables, tableName, typeName, info, typeNameMapper, true, filterCtx, limits, scope, defaultOrderBy, resolveName);
|
|
3119
|
-
};
|
|
3120
3199
|
/**
|
|
3121
3200
|
* Returns a copy of `relationMap` containing only the relations that should be eagerly
|
|
3122
3201
|
* pre-fetched (per the `shouldEagerLoad` predicate). Pass the result wherever a query or
|
|
@@ -3317,7 +3396,13 @@ const hardDeleteArg = {
|
|
|
3317
3396
|
const prepareMutationRelationColumns = (params) => {
|
|
3318
3397
|
const { relationMap, tables, tableName, typeNameMapper, table, pkNames, parsedInfo, resolveName } = params;
|
|
3319
3398
|
const typeName = resolveObjectTypeName(tableName, typeNameMapper, resolveName);
|
|
3320
|
-
const withParams = relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName,
|
|
3399
|
+
const withParams = relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
|
|
3400
|
+
typeNameMapper,
|
|
3401
|
+
limits: params.limits,
|
|
3402
|
+
scope: params.scope,
|
|
3403
|
+
defaultOrderBy: params.defaultOrderBy,
|
|
3404
|
+
resolveName
|
|
3405
|
+
}) : void 0;
|
|
3321
3406
|
const hasRelations = !!(withParams && Object.keys(withParams).length);
|
|
3322
3407
|
const baseColumns = extractSelectedColumnsFromTreeSQLFormat(parsedInfo.fieldsByTypeName[typeName], table, {
|
|
3323
3408
|
tableName,
|
|
@@ -3896,7 +3981,14 @@ const runRelationalSelect = async (opts) => {
|
|
|
3896
3981
|
offset: distinctKeys ? void 0 : offset,
|
|
3897
3982
|
orderBy: distinctKeys ? (aliasedTable) => [...orderBy ? extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName), where) : [], ...primaryKeyOrderExprs(aliasedTable, pkNames)] : cursorEntries ? (aliasedTable) => cursorOrderExprs(aliasedTable, cursorEntries) : orderBy ? (aliasedTable) => extractOrderBy(aliasedTable, orderBy, relationFilterCtx(filterCtx, tableName), where) : needsDefaultOrder && pkNames?.length ? (aliasedTable) => primaryKeyOrderExprs(aliasedTable, pkNames) : void 0,
|
|
3898
3983
|
where: distinctKeys ? { RAW: (aliasedTable) => primaryKeyRestriction(aliasedTable, pkNames, distinctKeys) } : where || cursorValues || scope?.has(tableName, deleted) ? { RAW: (aliasedTable) => withScope(scope, tableName, aliasedTable, and(where ? extractFilters(aliasedTable, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0, cursorValues ? buildCursorCondition(aliasedTable, cursorEntries, cursorValues, opts.nullOrdering ?? "nulls-smallest") : void 0), deleted) } : void 0,
|
|
3899
|
-
with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName,
|
|
3984
|
+
with: relationMap[tableName] ? extractRelationsParams(relationMap, tables, tableName, parsedInfo, typeName, {
|
|
3985
|
+
typeNameMapper,
|
|
3986
|
+
filterCtx,
|
|
3987
|
+
limits: opts.limits,
|
|
3988
|
+
scope,
|
|
3989
|
+
defaultOrderBy: opts.defaultOrderBy,
|
|
3990
|
+
resolveName: opts.resolveName
|
|
3991
|
+
}) : void 0
|
|
3900
3992
|
};
|
|
3901
3993
|
if (single) {
|
|
3902
3994
|
const result = await queryBase.findFirst(params);
|
|
@@ -4817,12 +4909,13 @@ const collectSelections = (selections, info, tree, parentType, deep, activeFragm
|
|
|
4817
4909
|
fields = {};
|
|
4818
4910
|
tree[parentTypeName] = fields;
|
|
4819
4911
|
}
|
|
4912
|
+
const parentFields = fieldsOf(parentType);
|
|
4820
4913
|
for (const selection of selections) {
|
|
4821
4914
|
if (isExcluded(info, selection)) continue;
|
|
4822
4915
|
if (selection.kind === Kind.FIELD) {
|
|
4823
4916
|
const name = selection.name.value;
|
|
4824
4917
|
if (isMetaField(name)) continue;
|
|
4825
|
-
const fieldDef =
|
|
4918
|
+
const fieldDef = parentFields?.[name];
|
|
4826
4919
|
if (!fieldDef) continue;
|
|
4827
4920
|
const fieldType = namedTypeOf(fieldDef.type);
|
|
4828
4921
|
if (!fieldType) continue;
|
|
@@ -4865,7 +4958,10 @@ const collectSelections = (selections, info, tree, parentType, deep, activeFragm
|
|
|
4865
4958
|
}
|
|
4866
4959
|
return tree;
|
|
4867
4960
|
};
|
|
4868
|
-
|
|
4961
|
+
/** The first key of an object, without materializing the whole key array to discard it. */
|
|
4962
|
+
const firstKey = (object) => {
|
|
4963
|
+
for (const key in object) return key;
|
|
4964
|
+
};
|
|
4869
4965
|
/**
|
|
4870
4966
|
* Reads the selection under a resolver's own field out of its `info`, as a tree of
|
|
4871
4967
|
* {@link ResolveTree} nodes.
|
|
@@ -4916,7 +5012,30 @@ const SEP = "__";
|
|
|
4916
5012
|
* arrays (including array-typed number columns), JSON, buffers, and object-shaped columns
|
|
4917
5013
|
* (e.g. geometry) are excluded entirely.
|
|
4918
5014
|
*/
|
|
5015
|
+
/**
|
|
5016
|
+
* Cache for {@link classifyAggregateColumns}. Keyed on the table object, then on the name it
|
|
5017
|
+
* was classified under — the name only reaches `drizzleColumnToGraphQLType` for enum naming,
|
|
5018
|
+
* but a table registered under two names would name its enums differently, so it is part of
|
|
5019
|
+
* the key rather than assumed irrelevant.
|
|
5020
|
+
*
|
|
5021
|
+
* The entries hold this build's GraphQL types and this build's column exclusions, so the
|
|
5022
|
+
* cache lives exactly as long as a build: {@link resetAggregateColumnCache} runs at the start
|
|
5023
|
+
* of every one, alongside the other per-build registries. Kept across builds it would hand a
|
|
5024
|
+
* second build the first build's enum instances, which is a schema with two types of one name.
|
|
5025
|
+
*/
|
|
5026
|
+
let aggregateColumnSetsCache = /* @__PURE__ */ new WeakMap();
|
|
5027
|
+
/** Drops the classifications cached for the previous build. Called once per `generateSchemaData`. */
|
|
5028
|
+
const resetAggregateColumnCache = () => {
|
|
5029
|
+
aggregateColumnSetsCache = /* @__PURE__ */ new WeakMap();
|
|
5030
|
+
};
|
|
4919
5031
|
const classifyAggregateColumns = (table, tableName) => {
|
|
5032
|
+
let byName = aggregateColumnSetsCache.get(table);
|
|
5033
|
+
if (!byName) {
|
|
5034
|
+
byName = /* @__PURE__ */ new Map();
|
|
5035
|
+
aggregateColumnSetsCache.set(table, byName);
|
|
5036
|
+
}
|
|
5037
|
+
const cached = byName.get(tableName);
|
|
5038
|
+
if (cached) return cached;
|
|
4920
5039
|
const numeric = {};
|
|
4921
5040
|
const orderable = {};
|
|
4922
5041
|
const all = {};
|
|
@@ -4934,11 +5053,13 @@ const classifyAggregateColumns = (table, tableName) => {
|
|
|
4934
5053
|
};
|
|
4935
5054
|
if (dataType === "number") numeric[columnName] = column;
|
|
4936
5055
|
}
|
|
4937
|
-
|
|
5056
|
+
const sets = {
|
|
4938
5057
|
numeric,
|
|
4939
5058
|
orderable,
|
|
4940
5059
|
all
|
|
4941
5060
|
};
|
|
5061
|
+
byName.set(tableName, sets);
|
|
5062
|
+
return sets;
|
|
4942
5063
|
};
|
|
4943
5064
|
/**
|
|
4944
5065
|
* Builds the `${typeName}Aggregate` output type for a table:
|
|
@@ -6062,6 +6183,7 @@ const createSchemaBuilder = (adapter) => {
|
|
|
6062
6183
|
registerScalarOverrides(tables, options);
|
|
6063
6184
|
registerEnumConfig(options);
|
|
6064
6185
|
registerColumnExclusions(tables, options.exclude);
|
|
6186
|
+
resetAggregateColumnCache();
|
|
6065
6187
|
const namedRelations = buildNamedRelations(relations ?? {}, tableEntries);
|
|
6066
6188
|
for (const excluded of excludedTables) delete namedRelations[excluded];
|
|
6067
6189
|
attachTargetPrimaryKeys(namedRelations, tables, (table) => primaryKeyPropNames(table));
|
|
@@ -6451,11 +6573,13 @@ const generateDelete = (db, tableName, table, filterArgs, fieldName, single, req
|
|
|
6451
6573
|
}
|
|
6452
6574
|
});
|
|
6453
6575
|
};
|
|
6576
|
+
/** The dialect's table config, cached per table — see {@link memoizeTableConfig}. */
|
|
6577
|
+
const getTableConfig$5 = memoizeTableConfig(getTableConfig);
|
|
6454
6578
|
/** Primary-key property names for a MySQL table, including table-level composite keys. */
|
|
6455
|
-
const mysqlPrimaryKeyPropNames = (table) => getPrimaryKeyPropNamesFromConfig(table, getTableConfig);
|
|
6579
|
+
const mysqlPrimaryKeyPropNames = (table) => getPrimaryKeyPropNamesFromConfig(table, getTableConfig$5);
|
|
6456
6580
|
const { prepareBuild, addReadFields, finalizeBuild } = createSchemaBuilder({
|
|
6457
6581
|
tableClass: MySqlTable,
|
|
6458
|
-
getTableConfig,
|
|
6582
|
+
getTableConfig: getTableConfig$5,
|
|
6459
6583
|
primaryKeyPropNames: mysqlPrimaryKeyPropNames,
|
|
6460
6584
|
nullOrdering: "nulls-smallest",
|
|
6461
6585
|
returnsRows: false,
|
|
@@ -7336,12 +7460,14 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
|
|
|
7336
7460
|
};
|
|
7337
7461
|
//#endregion
|
|
7338
7462
|
//#region src/util/builders/pg.ts
|
|
7463
|
+
/** The dialect's table config, cached per table — see {@link memoizeTableConfig}. */
|
|
7464
|
+
const getTableConfig$4 = memoizeTableConfig(getTableConfig$1);
|
|
7339
7465
|
/** Primary-key property names for a PG table, including table-level composite keys. */
|
|
7340
|
-
const pgPrimaryKeyPropNames = (table) => getPrimaryKeyPropNamesFromConfig(table, getTableConfig$
|
|
7466
|
+
const pgPrimaryKeyPropNames = (table) => getPrimaryKeyPropNamesFromConfig(table, getTableConfig$4);
|
|
7341
7467
|
const generateUpdateMany$1 = createUpdateManyGenerator(pgPrimaryKeyPropNames);
|
|
7342
7468
|
const pgSchemaData = createSchemaDataGenerator({
|
|
7343
7469
|
tableClass: PgTable,
|
|
7344
|
-
getTableConfig: getTableConfig$
|
|
7470
|
+
getTableConfig: getTableConfig$4,
|
|
7345
7471
|
primaryKeyPropNames: pgPrimaryKeyPropNames,
|
|
7346
7472
|
nullOrdering: "nulls-largest",
|
|
7347
7473
|
returnsRows: true,
|
|
@@ -7350,8 +7476,10 @@ const pgSchemaData = createSchemaDataGenerator({
|
|
|
7350
7476
|
const generateSchemaData$1 = (db, schema, relations, options) => pgSchemaData(db, schema, relations, options);
|
|
7351
7477
|
//#endregion
|
|
7352
7478
|
//#region src/util/builders/sqlite.ts
|
|
7479
|
+
/** The dialect's table config, cached per table — see {@link memoizeTableConfig}. */
|
|
7480
|
+
const getTableConfig$3 = memoizeTableConfig(getTableConfig$2);
|
|
7353
7481
|
/** Primary-key property names for a SQLite table, including table-level composite keys. */
|
|
7354
|
-
const sqlitePrimaryKeyPropNames = (table) => getPrimaryKeyPropNamesFromConfig(table, getTableConfig$
|
|
7482
|
+
const sqlitePrimaryKeyPropNames = (table) => getPrimaryKeyPropNamesFromConfig(table, getTableConfig$3);
|
|
7355
7483
|
/**
|
|
7356
7484
|
* SQLite's batch runner. A synchronous driver (better-sqlite3) commits the moment its
|
|
7357
7485
|
* transaction callback returns, so that callback must not be async — an awaited statement
|
|
@@ -7396,7 +7524,7 @@ const runSqliteUpdateManyBatch = ({ executor, entries, table, returning, anyNest
|
|
|
7396
7524
|
const generateUpdateMany = createUpdateManyGenerator(sqlitePrimaryKeyPropNames, runSqliteUpdateManyBatch);
|
|
7397
7525
|
const sqliteSchemaData = createSchemaDataGenerator({
|
|
7398
7526
|
tableClass: SQLiteTable,
|
|
7399
|
-
getTableConfig: getTableConfig$
|
|
7527
|
+
getTableConfig: getTableConfig$3,
|
|
7400
7528
|
primaryKeyPropNames: sqlitePrimaryKeyPropNames,
|
|
7401
7529
|
nullOrdering: "nulls-smallest",
|
|
7402
7530
|
returnsRows: true,
|
|
@@ -7410,11 +7538,14 @@ const generateSchemaData = (db, schema, relations, options) => sqliteSchemaData(
|
|
|
7410
7538
|
//#endregion
|
|
7411
7539
|
//#region src/util/selection.ts
|
|
7412
7540
|
const contextCache = /* @__PURE__ */ new WeakMap();
|
|
7541
|
+
const getMySqlTableConfig = memoizeTableConfig(getTableConfig);
|
|
7542
|
+
const getPgTableConfig = memoizeTableConfig(getTableConfig$1);
|
|
7543
|
+
const getSQLiteTableConfig = memoizeTableConfig(getTableConfig$2);
|
|
7413
7544
|
/** The dialect's `getTableConfig`, which is the only place a composite primary key shows up. */
|
|
7414
7545
|
const tableConfigFor = (db) => {
|
|
7415
|
-
if (is(db, MySqlAsyncDatabase)) return
|
|
7416
|
-
if (is(db, PgAsyncDatabase)) return
|
|
7417
|
-
if (is(db, SQLiteAsyncDatabase)) return
|
|
7546
|
+
if (is(db, MySqlAsyncDatabase)) return getMySqlTableConfig;
|
|
7547
|
+
if (is(db, PgAsyncDatabase)) return getPgTableConfig;
|
|
7548
|
+
if (is(db, SQLiteAsyncDatabase)) return getSQLiteTableConfig;
|
|
7418
7549
|
throw new Error("Drizzle-GraphQL Error: unsupported database instance — expected a PostgreSQL, MySQL or SQLite one.");
|
|
7419
7550
|
};
|
|
7420
7551
|
const contextFor = (db) => {
|
|
@@ -7495,7 +7626,11 @@ const selectionToWith = (info, options) => {
|
|
|
7495
7626
|
if (!tables[table]) throw new Error(`Drizzle-GraphQL Error: '${table}' is not a table in the Drizzle schema.`);
|
|
7496
7627
|
if (!relationMap[table]) return;
|
|
7497
7628
|
const parsed = parseInfo(info);
|
|
7498
|
-
const params = extractRelationsParams(relationMap, tables, table, parsed, resolveObjectTypeName(table, typeNameMapper, resolveName),
|
|
7629
|
+
const params = extractRelationsParams(relationMap, tables, table, parsed, resolveObjectTypeName(table, typeNameMapper, resolveName), {
|
|
7630
|
+
typeNameMapper,
|
|
7631
|
+
filterCtx,
|
|
7632
|
+
resolveName
|
|
7633
|
+
});
|
|
7499
7634
|
return params && Object.keys(params).length ? params : void 0;
|
|
7500
7635
|
};
|
|
7501
7636
|
/**
|