@drizzle-graphql-suite/schema 0.8.2 → 0.9.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 +5 -489
- package/index.d.ts +1 -32
- package/index.js +1 -2054
- package/package.json +5 -19
- package/adapters/pg.d.ts +0 -10
- package/adapters/types.d.ts +0 -11
- package/case-ops.d.ts +0 -2
- package/codegen.d.ts +0 -12
- package/data-mappers.d.ts +0 -12
- package/graphql/scalars.d.ts +0 -2
- package/graphql/type-builder.d.ts +0 -7
- package/permissions.d.ts +0 -25
- package/row-security.d.ts +0 -21
- package/schema-builder.d.ts +0 -72
- package/types.d.ts +0 -253
package/index.js
CHANGED
|
@@ -1,2054 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { createTableRelationsHelpers as createTableRelationsHelpers2, extractTablesRelationalConfig } from "drizzle-orm";
|
|
3
|
-
|
|
4
|
-
// src/schema-builder.ts
|
|
5
|
-
import {
|
|
6
|
-
and,
|
|
7
|
-
asc,
|
|
8
|
-
count,
|
|
9
|
-
createTableRelationsHelpers,
|
|
10
|
-
desc,
|
|
11
|
-
eq,
|
|
12
|
-
exists,
|
|
13
|
-
getTableColumns as getTableColumns2,
|
|
14
|
-
getTableName,
|
|
15
|
-
gt,
|
|
16
|
-
gte,
|
|
17
|
-
ilike,
|
|
18
|
-
inArray,
|
|
19
|
-
is as is3,
|
|
20
|
-
isNotNull,
|
|
21
|
-
isNull,
|
|
22
|
-
like,
|
|
23
|
-
lt,
|
|
24
|
-
lte,
|
|
25
|
-
ne,
|
|
26
|
-
normalizeRelation,
|
|
27
|
-
not,
|
|
28
|
-
notIlike,
|
|
29
|
-
notInArray,
|
|
30
|
-
notLike,
|
|
31
|
-
One,
|
|
32
|
-
or,
|
|
33
|
-
Relations,
|
|
34
|
-
sql
|
|
35
|
-
} from "drizzle-orm";
|
|
36
|
-
import { PgTable as PgTable2 } from "drizzle-orm/pg-core";
|
|
37
|
-
import {
|
|
38
|
-
GraphQLBoolean as GraphQLBoolean2,
|
|
39
|
-
GraphQLEnumType as GraphQLEnumType2,
|
|
40
|
-
GraphQLError as GraphQLError2,
|
|
41
|
-
GraphQLInputObjectType as GraphQLInputObjectType2,
|
|
42
|
-
GraphQLInt as GraphQLInt2,
|
|
43
|
-
GraphQLList as GraphQLList2,
|
|
44
|
-
GraphQLNonNull as GraphQLNonNull2,
|
|
45
|
-
GraphQLObjectType as GraphQLObjectType2,
|
|
46
|
-
GraphQLSchema,
|
|
47
|
-
GraphQLString as GraphQLString2,
|
|
48
|
-
printSchema
|
|
49
|
-
} from "graphql";
|
|
50
|
-
import { parseResolveInfo } from "graphql-parse-resolve-info";
|
|
51
|
-
|
|
52
|
-
// src/adapters/pg.ts
|
|
53
|
-
import { is } from "drizzle-orm";
|
|
54
|
-
import { PgTable } from "drizzle-orm/pg-core";
|
|
55
|
-
|
|
56
|
-
class PgAdapter {
|
|
57
|
-
supportsReturning = true;
|
|
58
|
-
isTable(value) {
|
|
59
|
-
return is(value, PgTable);
|
|
60
|
-
}
|
|
61
|
-
async executeInsert(db, table, values, returningColumns) {
|
|
62
|
-
let query = db.insert(table).values(values);
|
|
63
|
-
if (returningColumns) {
|
|
64
|
-
query = query.returning(returningColumns);
|
|
65
|
-
}
|
|
66
|
-
query = query.onConflictDoNothing();
|
|
67
|
-
return await query;
|
|
68
|
-
}
|
|
69
|
-
async executeUpdate(db, table, set, where, returningColumns) {
|
|
70
|
-
let query = db.update(table).set(set);
|
|
71
|
-
if (where) {
|
|
72
|
-
query = query.where(where);
|
|
73
|
-
}
|
|
74
|
-
if (returningColumns) {
|
|
75
|
-
query = query.returning(returningColumns);
|
|
76
|
-
}
|
|
77
|
-
return await query;
|
|
78
|
-
}
|
|
79
|
-
async executeDelete(db, table, where, returningColumns) {
|
|
80
|
-
let query = db.delete(table);
|
|
81
|
-
if (where) {
|
|
82
|
-
query = query.where(where);
|
|
83
|
-
}
|
|
84
|
-
if (returningColumns) {
|
|
85
|
-
query = query.returning(returningColumns);
|
|
86
|
-
}
|
|
87
|
-
return await query;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// src/case-ops.ts
|
|
92
|
-
var uncapitalize = (input) => input.length ? `${input[0]?.toLocaleLowerCase()}${input.length > 1 ? input.slice(1, input.length) : ""}` : input;
|
|
93
|
-
var capitalize = (input) => input.length ? `${input[0]?.toLocaleUpperCase()}${input.length > 1 ? input.slice(1, input.length) : ""}` : input;
|
|
94
|
-
|
|
95
|
-
// src/data-mappers.ts
|
|
96
|
-
import { getTableColumns } from "drizzle-orm";
|
|
97
|
-
import { GraphQLError } from "graphql";
|
|
98
|
-
var remapToGraphQLCore = (key, value, tableName, column, relationMap) => {
|
|
99
|
-
if (value instanceof Date)
|
|
100
|
-
return value.toISOString();
|
|
101
|
-
if (value instanceof Buffer)
|
|
102
|
-
return Array.from(value);
|
|
103
|
-
if (typeof value === "bigint")
|
|
104
|
-
return value.toString();
|
|
105
|
-
if (Array.isArray(value)) {
|
|
106
|
-
const rel = relationMap?.[tableName]?.[key];
|
|
107
|
-
if (rel) {
|
|
108
|
-
return remapToGraphQLArrayOutput(value, rel.targetTableName, rel.relation.referencedTable, relationMap);
|
|
109
|
-
}
|
|
110
|
-
if (column.columnType === "PgGeometry" || column.columnType === "PgVector")
|
|
111
|
-
return value;
|
|
112
|
-
return value.map((arrVal) => remapToGraphQLCore(key, arrVal, tableName, column, relationMap));
|
|
113
|
-
}
|
|
114
|
-
if (typeof value === "object") {
|
|
115
|
-
const rel = relationMap?.[tableName]?.[key];
|
|
116
|
-
if (rel) {
|
|
117
|
-
return remapToGraphQLSingleOutput(value, rel.targetTableName, rel.relation.referencedTable, relationMap);
|
|
118
|
-
}
|
|
119
|
-
if (column.columnType === "PgGeometryObject")
|
|
120
|
-
return value;
|
|
121
|
-
if (column.dataType === "json")
|
|
122
|
-
return value;
|
|
123
|
-
return JSON.stringify(value);
|
|
124
|
-
}
|
|
125
|
-
return value;
|
|
126
|
-
};
|
|
127
|
-
var remapToGraphQLSingleOutput = (queryOutput, tableName, table, relationMap) => {
|
|
128
|
-
for (const [key, value] of Object.entries(queryOutput)) {
|
|
129
|
-
if (value === undefined || value === null) {
|
|
130
|
-
delete queryOutput[key];
|
|
131
|
-
} else {
|
|
132
|
-
queryOutput[key] = remapToGraphQLCore(key, value, tableName, table[key], relationMap);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return queryOutput;
|
|
136
|
-
};
|
|
137
|
-
var remapToGraphQLArrayOutput = (queryOutput, tableName, table, relationMap) => {
|
|
138
|
-
for (const entry of queryOutput) {
|
|
139
|
-
remapToGraphQLSingleOutput(entry, tableName, table, relationMap);
|
|
140
|
-
}
|
|
141
|
-
return queryOutput;
|
|
142
|
-
};
|
|
143
|
-
var remapFromGraphQLCore = (value, column, columnName) => {
|
|
144
|
-
switch (column.dataType) {
|
|
145
|
-
case "date": {
|
|
146
|
-
const formatted = new Date(value);
|
|
147
|
-
if (Number.isNaN(formatted.getTime()))
|
|
148
|
-
throw new GraphQLError(`Field '${columnName}' is not a valid date!`);
|
|
149
|
-
return formatted;
|
|
150
|
-
}
|
|
151
|
-
case "buffer": {
|
|
152
|
-
if (!Array.isArray(value)) {
|
|
153
|
-
throw new GraphQLError(`Field '${columnName}' is not an array!`);
|
|
154
|
-
}
|
|
155
|
-
return Buffer.from(value);
|
|
156
|
-
}
|
|
157
|
-
case "json": {
|
|
158
|
-
if (column.columnType === "PgGeometryObject")
|
|
159
|
-
return value;
|
|
160
|
-
return value;
|
|
161
|
-
}
|
|
162
|
-
case "array": {
|
|
163
|
-
if (!Array.isArray(value)) {
|
|
164
|
-
throw new GraphQLError(`Field '${columnName}' is not an array!`);
|
|
165
|
-
}
|
|
166
|
-
if (column.columnType === "PgGeometry" && value.length !== 2) {
|
|
167
|
-
throw new GraphQLError(`Invalid float tuple in field '${columnName}': expected array with length of 2, received ${value.length}`);
|
|
168
|
-
}
|
|
169
|
-
return value;
|
|
170
|
-
}
|
|
171
|
-
case "bigint": {
|
|
172
|
-
try {
|
|
173
|
-
return BigInt(value);
|
|
174
|
-
} catch {
|
|
175
|
-
throw new GraphQLError(`Field '${columnName}' is not a BigInt!`);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
default: {
|
|
179
|
-
return value;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
var remapFromGraphQLSingleInput = (queryInput, table) => {
|
|
184
|
-
for (const [key, value] of Object.entries(queryInput)) {
|
|
185
|
-
if (value === undefined) {
|
|
186
|
-
delete queryInput[key];
|
|
187
|
-
} else {
|
|
188
|
-
const column = getTableColumns(table)[key];
|
|
189
|
-
if (!column)
|
|
190
|
-
throw new GraphQLError(`Unknown column: ${key}`);
|
|
191
|
-
if (value === null && column.notNull) {
|
|
192
|
-
delete queryInput[key];
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
queryInput[key] = remapFromGraphQLCore(value, column, key);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return queryInput;
|
|
199
|
-
};
|
|
200
|
-
var remapFromGraphQLArrayInput = (queryInput, table) => {
|
|
201
|
-
for (const entry of queryInput)
|
|
202
|
-
remapFromGraphQLSingleInput(entry, table);
|
|
203
|
-
return queryInput;
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
// src/graphql/type-builder.ts
|
|
207
|
-
import { is as is2 } from "drizzle-orm";
|
|
208
|
-
import {
|
|
209
|
-
PgBigInt53,
|
|
210
|
-
PgBigSerial53,
|
|
211
|
-
PgInteger,
|
|
212
|
-
PgSerial,
|
|
213
|
-
PgSmallInt,
|
|
214
|
-
PgSmallSerial
|
|
215
|
-
} from "drizzle-orm/pg-core";
|
|
216
|
-
import {
|
|
217
|
-
GraphQLBoolean,
|
|
218
|
-
GraphQLEnumType,
|
|
219
|
-
GraphQLFloat,
|
|
220
|
-
GraphQLInputObjectType,
|
|
221
|
-
GraphQLInt,
|
|
222
|
-
GraphQLList,
|
|
223
|
-
GraphQLNonNull,
|
|
224
|
-
GraphQLObjectType,
|
|
225
|
-
GraphQLString
|
|
226
|
-
} from "graphql";
|
|
227
|
-
|
|
228
|
-
// src/graphql/scalars.ts
|
|
229
|
-
import { GraphQLScalarType, Kind } from "graphql";
|
|
230
|
-
var GraphQLJSON = new GraphQLScalarType({
|
|
231
|
-
name: "JSON",
|
|
232
|
-
description: "The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).",
|
|
233
|
-
serialize(value) {
|
|
234
|
-
return value;
|
|
235
|
-
},
|
|
236
|
-
parseValue(value) {
|
|
237
|
-
return value;
|
|
238
|
-
},
|
|
239
|
-
parseLiteral(ast) {
|
|
240
|
-
switch (ast.kind) {
|
|
241
|
-
case Kind.STRING:
|
|
242
|
-
case Kind.BOOLEAN:
|
|
243
|
-
return ast.value;
|
|
244
|
-
case Kind.INT:
|
|
245
|
-
case Kind.FLOAT:
|
|
246
|
-
return parseFloat(ast.value);
|
|
247
|
-
case Kind.OBJECT: {
|
|
248
|
-
const value = Object.create(null);
|
|
249
|
-
ast.fields.forEach((field) => {
|
|
250
|
-
value[field.name.value] = GraphQLJSON.parseLiteral(field.value);
|
|
251
|
-
});
|
|
252
|
-
return value;
|
|
253
|
-
}
|
|
254
|
-
case Kind.LIST:
|
|
255
|
-
return ast.values.map((n) => GraphQLJSON.parseLiteral(n));
|
|
256
|
-
case Kind.NULL:
|
|
257
|
-
return null;
|
|
258
|
-
default:
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// src/graphql/type-builder.ts
|
|
265
|
-
var allowedNameChars = /^[a-zA-Z0-9_]+$/;
|
|
266
|
-
var enumMap = new WeakMap;
|
|
267
|
-
var generateEnumCached = (column, columnName, tableName) => {
|
|
268
|
-
const cached = enumMap.get(column);
|
|
269
|
-
if (cached)
|
|
270
|
-
return cached;
|
|
271
|
-
const gqlEnum = new GraphQLEnumType({
|
|
272
|
-
name: `${capitalize(tableName)}${capitalize(columnName)}Enum`,
|
|
273
|
-
values: Object.fromEntries((column.enumValues ?? []).map((e, index) => {
|
|
274
|
-
const enumName = e.replace(/-/g, "_");
|
|
275
|
-
const finalName = allowedNameChars.test(enumName) ? enumName : `Option${index}`;
|
|
276
|
-
return [
|
|
277
|
-
finalName,
|
|
278
|
-
{
|
|
279
|
-
value: e,
|
|
280
|
-
description: `Value: ${e}`
|
|
281
|
-
}
|
|
282
|
-
];
|
|
283
|
-
}))
|
|
284
|
-
});
|
|
285
|
-
enumMap.set(column, gqlEnum);
|
|
286
|
-
return gqlEnum;
|
|
287
|
-
};
|
|
288
|
-
var geoXyType = new GraphQLObjectType({
|
|
289
|
-
name: "PgGeometryObject",
|
|
290
|
-
fields: {
|
|
291
|
-
x: { type: GraphQLFloat },
|
|
292
|
-
y: { type: GraphQLFloat }
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
var geoXyInputType = new GraphQLInputObjectType({
|
|
296
|
-
name: "PgGeometryObjectInput",
|
|
297
|
-
fields: {
|
|
298
|
-
x: { type: GraphQLFloat },
|
|
299
|
-
y: { type: GraphQLFloat }
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
var columnToGraphQLCore = (column, columnName, tableName, isInput) => {
|
|
303
|
-
switch (column.dataType) {
|
|
304
|
-
case "boolean":
|
|
305
|
-
return { type: GraphQLBoolean, description: "Boolean" };
|
|
306
|
-
case "json":
|
|
307
|
-
return column.columnType === "PgGeometryObject" ? {
|
|
308
|
-
type: isInput ? geoXyInputType : geoXyType,
|
|
309
|
-
description: "Geometry points XY"
|
|
310
|
-
} : { type: GraphQLJSON, description: "JSON" };
|
|
311
|
-
case "date":
|
|
312
|
-
return { type: GraphQLString, description: "Date" };
|
|
313
|
-
case "string":
|
|
314
|
-
if (column.enumValues?.length) {
|
|
315
|
-
return { type: generateEnumCached(column, columnName, tableName) };
|
|
316
|
-
}
|
|
317
|
-
return { type: GraphQLString, description: "String" };
|
|
318
|
-
case "bigint":
|
|
319
|
-
return { type: GraphQLString, description: "BigInt" };
|
|
320
|
-
case "number":
|
|
321
|
-
return is2(column, PgInteger) || is2(column, PgSmallInt) || is2(column, PgBigInt53) || is2(column, PgSerial) || is2(column, PgSmallSerial) || is2(column, PgBigSerial53) ? { type: GraphQLInt, description: "Integer" } : { type: GraphQLFloat, description: "Float" };
|
|
322
|
-
case "buffer":
|
|
323
|
-
return {
|
|
324
|
-
type: new GraphQLList(new GraphQLNonNull(GraphQLInt)),
|
|
325
|
-
description: "Buffer"
|
|
326
|
-
};
|
|
327
|
-
case "array": {
|
|
328
|
-
if (column.columnType === "PgVector") {
|
|
329
|
-
return {
|
|
330
|
-
type: new GraphQLList(new GraphQLNonNull(GraphQLFloat)),
|
|
331
|
-
description: "Array<Float>"
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
if (column.columnType === "PgGeometry") {
|
|
335
|
-
return {
|
|
336
|
-
type: new GraphQLList(new GraphQLNonNull(GraphQLFloat)),
|
|
337
|
-
description: "Tuple<[Float, Float]>"
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
const innerType = columnToGraphQLCore(column.baseColumn, columnName, tableName, isInput);
|
|
341
|
-
return {
|
|
342
|
-
type: new GraphQLList(new GraphQLNonNull(innerType.type)),
|
|
343
|
-
description: `Array<${innerType.description}>`
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
default:
|
|
347
|
-
throw new Error(`Drizzle-GraphQL Error: Type ${column.dataType} is not implemented!`);
|
|
348
|
-
}
|
|
349
|
-
};
|
|
350
|
-
var drizzleColumnToGraphQLType = (column, columnName, tableName, forceNullable = false, defaultIsNullable = false, isInput = false) => {
|
|
351
|
-
const typeDesc = columnToGraphQLCore(column, columnName, tableName, isInput);
|
|
352
|
-
const noDesc = ["string", "boolean", "number"];
|
|
353
|
-
if (noDesc.find((e) => e === column.dataType))
|
|
354
|
-
delete typeDesc.description;
|
|
355
|
-
if (forceNullable)
|
|
356
|
-
return typeDesc;
|
|
357
|
-
if (column.notNull && !(defaultIsNullable && (column.hasDefault || column.defaultFn))) {
|
|
358
|
-
return {
|
|
359
|
-
type: new GraphQLNonNull(typeDesc.type),
|
|
360
|
-
description: typeDesc.description
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
return typeDesc;
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
// src/permissions.ts
|
|
367
|
-
var readOnly = () => ({
|
|
368
|
-
query: true,
|
|
369
|
-
insert: false,
|
|
370
|
-
update: false,
|
|
371
|
-
delete: false
|
|
372
|
-
});
|
|
373
|
-
var permissive = (id, tables) => ({
|
|
374
|
-
id,
|
|
375
|
-
mode: "permissive",
|
|
376
|
-
tables
|
|
377
|
-
});
|
|
378
|
-
var restricted = (id, tables) => ({
|
|
379
|
-
id,
|
|
380
|
-
mode: "restricted",
|
|
381
|
-
tables
|
|
382
|
-
});
|
|
383
|
-
function mergePermissionsIntoConfig(baseConfig, permissions, allTableNames) {
|
|
384
|
-
const excluded = [...baseConfig.tables?.exclude ?? []];
|
|
385
|
-
const tableConfig = {
|
|
386
|
-
...baseConfig.tables?.config ?? {}
|
|
387
|
-
};
|
|
388
|
-
const mutationFilter = {};
|
|
389
|
-
for (const tableName of allTableNames) {
|
|
390
|
-
if (excluded.includes(tableName))
|
|
391
|
-
continue;
|
|
392
|
-
const access = resolveTableAccess(permissions, tableName);
|
|
393
|
-
if (access === false) {
|
|
394
|
-
excluded.push(tableName);
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
397
|
-
if (access === true) {
|
|
398
|
-
continue;
|
|
399
|
-
}
|
|
400
|
-
const defaultAllow = permissions.mode === "permissive";
|
|
401
|
-
const queryAllowed = access.query ?? defaultAllow;
|
|
402
|
-
const insertAllowed = access.insert ?? defaultAllow;
|
|
403
|
-
const updateAllowed = access.update ?? defaultAllow;
|
|
404
|
-
const deleteAllowed = access.delete ?? defaultAllow;
|
|
405
|
-
if (!queryAllowed && !insertAllowed && !updateAllowed && !deleteAllowed) {
|
|
406
|
-
excluded.push(tableName);
|
|
407
|
-
continue;
|
|
408
|
-
}
|
|
409
|
-
tableConfig[tableName] = {
|
|
410
|
-
...tableConfig[tableName],
|
|
411
|
-
queries: queryAllowed
|
|
412
|
-
};
|
|
413
|
-
const anyMutation = insertAllowed || updateAllowed || deleteAllowed;
|
|
414
|
-
if (!anyMutation) {
|
|
415
|
-
tableConfig[tableName] = { ...tableConfig[tableName], mutations: false };
|
|
416
|
-
} else {
|
|
417
|
-
tableConfig[tableName] = { ...tableConfig[tableName], mutations: true };
|
|
418
|
-
if (!insertAllowed || !updateAllowed || !deleteAllowed) {
|
|
419
|
-
mutationFilter[tableName] = {
|
|
420
|
-
insert: insertAllowed,
|
|
421
|
-
update: updateAllowed,
|
|
422
|
-
delete: deleteAllowed
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
const config = {
|
|
428
|
-
...baseConfig,
|
|
429
|
-
tables: {
|
|
430
|
-
exclude: excluded.length ? excluded : undefined,
|
|
431
|
-
config: Object.keys(tableConfig).length ? tableConfig : undefined
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
|
-
return { config, mutationFilter };
|
|
435
|
-
}
|
|
436
|
-
function resolveTableAccess(permissions, tableName) {
|
|
437
|
-
const override = permissions.tables?.[tableName];
|
|
438
|
-
if (permissions.mode === "permissive") {
|
|
439
|
-
if (override === undefined)
|
|
440
|
-
return true;
|
|
441
|
-
return override;
|
|
442
|
-
}
|
|
443
|
-
if (override === undefined)
|
|
444
|
-
return false;
|
|
445
|
-
return override;
|
|
446
|
-
}
|
|
447
|
-
function postFilterMutations(mutations, mutationFilter) {
|
|
448
|
-
for (const [tableName, flags] of Object.entries(mutationFilter)) {
|
|
449
|
-
const cap = capitalize(tableName);
|
|
450
|
-
if (!flags.insert) {
|
|
451
|
-
delete mutations[`insertInto${cap}`];
|
|
452
|
-
delete mutations[`insertInto${cap}Single`];
|
|
453
|
-
}
|
|
454
|
-
if (!flags.update) {
|
|
455
|
-
delete mutations[`update${cap}`];
|
|
456
|
-
}
|
|
457
|
-
if (!flags.delete) {
|
|
458
|
-
delete mutations[`deleteFrom${cap}`];
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// src/schema-builder.ts
|
|
464
|
-
class SchemaBuilder {
|
|
465
|
-
db;
|
|
466
|
-
tables;
|
|
467
|
-
relationMap;
|
|
468
|
-
relationalSchema;
|
|
469
|
-
tableNamesMap;
|
|
470
|
-
config;
|
|
471
|
-
hooks;
|
|
472
|
-
adapter;
|
|
473
|
-
suffixes;
|
|
474
|
-
limitRelationDepth;
|
|
475
|
-
limitSelfRelationDepth;
|
|
476
|
-
allTableNames;
|
|
477
|
-
excludedTables;
|
|
478
|
-
tableOperations;
|
|
479
|
-
pruneRelations;
|
|
480
|
-
filterTypes = new Map;
|
|
481
|
-
orderByTypes = new Map;
|
|
482
|
-
filterValueTypes = new Map;
|
|
483
|
-
selectFieldTypes = new Map;
|
|
484
|
-
innerOrder;
|
|
485
|
-
constructor(db, config) {
|
|
486
|
-
this.db = db;
|
|
487
|
-
this.config = config ?? {};
|
|
488
|
-
this.hooks = config?.hooks ?? {};
|
|
489
|
-
this.adapter = new PgAdapter;
|
|
490
|
-
this.suffixes = {
|
|
491
|
-
list: config?.suffixes?.list ?? "",
|
|
492
|
-
single: config?.suffixes?.single ?? "Single"
|
|
493
|
-
};
|
|
494
|
-
this.limitRelationDepth = config?.limitRelationDepth ?? 3;
|
|
495
|
-
this.limitSelfRelationDepth = config?.limitSelfRelationDepth ?? 1;
|
|
496
|
-
this.pruneRelations = new Map(Object.entries(config?.pruneRelations ?? {}));
|
|
497
|
-
const schema = db._.fullSchema;
|
|
498
|
-
if (!schema) {
|
|
499
|
-
throw new Error("Drizzle-GraphQL Error: Schema not found in drizzle instance. Make sure you're using drizzle-orm v0.30.9 or above and schema is passed to drizzle constructor!");
|
|
500
|
-
}
|
|
501
|
-
const dbConfig = db._;
|
|
502
|
-
this.relationalSchema = dbConfig.schema;
|
|
503
|
-
this.tableNamesMap = dbConfig.tableNamesMap;
|
|
504
|
-
if (typeof this.limitRelationDepth === "number") {
|
|
505
|
-
if (this.limitRelationDepth < 0 || this.limitRelationDepth !== ~~this.limitRelationDepth) {
|
|
506
|
-
throw new Error("Drizzle-GraphQL Error: config.limitRelationDepth is supposed to be nonnegative integer or undefined!");
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
if (this.limitSelfRelationDepth < 1 || this.limitSelfRelationDepth !== ~~this.limitSelfRelationDepth) {
|
|
510
|
-
throw new Error("Drizzle-GraphQL Error: config.limitSelfRelationDepth must be a positive integer!");
|
|
511
|
-
}
|
|
512
|
-
if (this.suffixes.list === this.suffixes.single) {
|
|
513
|
-
throw new Error("Drizzle-GraphQL Error: List and single query suffixes cannot be the same. This would create conflicting GraphQL field names.");
|
|
514
|
-
}
|
|
515
|
-
this.innerOrder = new GraphQLInputObjectType2({
|
|
516
|
-
name: "InnerOrder",
|
|
517
|
-
fields: {
|
|
518
|
-
direction: {
|
|
519
|
-
type: new GraphQLNonNull2(new GraphQLEnumType2({
|
|
520
|
-
name: "OrderDirection",
|
|
521
|
-
description: "Order by direction",
|
|
522
|
-
values: {
|
|
523
|
-
asc: { value: "asc", description: "Ascending order" },
|
|
524
|
-
desc: { value: "desc", description: "Descending order" }
|
|
525
|
-
}
|
|
526
|
-
}))
|
|
527
|
-
},
|
|
528
|
-
priority: {
|
|
529
|
-
type: new GraphQLNonNull2(GraphQLInt2),
|
|
530
|
-
description: "Priority of current field"
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
});
|
|
534
|
-
this.tables = this.extractTables(schema);
|
|
535
|
-
this.allTableNames = Object.keys(this.tables);
|
|
536
|
-
this.excludedTables = new Set(config?.tables?.exclude ?? []);
|
|
537
|
-
this.tableOperations = config?.tables?.config ?? {};
|
|
538
|
-
for (const tableName of this.excludedTables) {
|
|
539
|
-
delete this.tables[tableName];
|
|
540
|
-
}
|
|
541
|
-
this.relationMap = this.buildRelationMap(schema);
|
|
542
|
-
}
|
|
543
|
-
toFieldMap(tableFields, relationFields) {
|
|
544
|
-
return { ...tableFields, ...relationFields };
|
|
545
|
-
}
|
|
546
|
-
buildEntities() {
|
|
547
|
-
const queries = {};
|
|
548
|
-
const mutations = {};
|
|
549
|
-
const inputs = {};
|
|
550
|
-
const outputs = {};
|
|
551
|
-
for (const tableName of Object.keys(this.tables)) {
|
|
552
|
-
const tableOps = this.tableOperations[tableName];
|
|
553
|
-
const generateQueries = tableOps?.queries !== false;
|
|
554
|
-
const generateMutations = tableOps?.mutations !== false && this.config.mutations !== false;
|
|
555
|
-
if (!generateQueries && !generateMutations)
|
|
556
|
-
continue;
|
|
557
|
-
const tableTypes = this.generateTableTypes(tableName);
|
|
558
|
-
const { insertInput, updateInput, tableFilters, tableOrder } = tableTypes.inputs;
|
|
559
|
-
const { selectSingleOutput, selectArrOutput, singleTableItemOutput, arrTableItemOutput } = tableTypes.outputs;
|
|
560
|
-
if (generateQueries) {
|
|
561
|
-
const selectArr = this.createQueryResolver(tableName, tableOrder, tableFilters);
|
|
562
|
-
queries[selectArr.name] = {
|
|
563
|
-
type: selectArrOutput,
|
|
564
|
-
args: selectArr.args,
|
|
565
|
-
resolve: selectArr.resolver
|
|
566
|
-
};
|
|
567
|
-
const selectSingle = this.createSingleQueryResolver(tableName, tableOrder, tableFilters);
|
|
568
|
-
queries[selectSingle.name] = {
|
|
569
|
-
type: selectSingleOutput,
|
|
570
|
-
args: selectSingle.args,
|
|
571
|
-
resolve: selectSingle.resolver
|
|
572
|
-
};
|
|
573
|
-
const selectCount = this.createCountResolver(tableName, tableFilters);
|
|
574
|
-
queries[selectCount.name] = {
|
|
575
|
-
type: GraphQLInt2,
|
|
576
|
-
args: selectCount.args,
|
|
577
|
-
resolve: selectCount.resolver
|
|
578
|
-
};
|
|
579
|
-
inputs[tableFilters.name] = tableFilters;
|
|
580
|
-
inputs[tableOrder.name] = tableOrder;
|
|
581
|
-
outputs[selectSingleOutput.name] = selectSingleOutput;
|
|
582
|
-
}
|
|
583
|
-
if (generateMutations) {
|
|
584
|
-
const insertArr = this.createInsertResolver(tableName, insertInput);
|
|
585
|
-
mutations[insertArr.name] = {
|
|
586
|
-
type: arrTableItemOutput,
|
|
587
|
-
args: insertArr.args,
|
|
588
|
-
resolve: insertArr.resolver
|
|
589
|
-
};
|
|
590
|
-
const insertSingle = this.createInsertSingleResolver(tableName, insertInput);
|
|
591
|
-
mutations[insertSingle.name] = {
|
|
592
|
-
type: singleTableItemOutput,
|
|
593
|
-
args: insertSingle.args,
|
|
594
|
-
resolve: insertSingle.resolver
|
|
595
|
-
};
|
|
596
|
-
const update = this.createUpdateResolver(tableName, updateInput, tableFilters);
|
|
597
|
-
mutations[update.name] = {
|
|
598
|
-
type: arrTableItemOutput,
|
|
599
|
-
args: update.args,
|
|
600
|
-
resolve: update.resolver
|
|
601
|
-
};
|
|
602
|
-
const del = this.createDeleteResolver(tableName, tableFilters);
|
|
603
|
-
mutations[del.name] = {
|
|
604
|
-
type: arrTableItemOutput,
|
|
605
|
-
args: del.args,
|
|
606
|
-
resolve: del.resolver
|
|
607
|
-
};
|
|
608
|
-
inputs[insertInput.name] = insertInput;
|
|
609
|
-
inputs[updateInput.name] = updateInput;
|
|
610
|
-
inputs[tableFilters.name] = tableFilters;
|
|
611
|
-
inputs[tableOrder.name] = tableOrder;
|
|
612
|
-
outputs[singleTableItemOutput.name] = singleTableItemOutput;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
return { queries, mutations, inputs, types: outputs };
|
|
616
|
-
}
|
|
617
|
-
build() {
|
|
618
|
-
const entities = this.buildEntities();
|
|
619
|
-
const { queries, mutations, inputs, types: outputs } = entities;
|
|
620
|
-
const graphQLSchemaConfig = {
|
|
621
|
-
types: [...Object.values(inputs), ...Object.values(outputs)],
|
|
622
|
-
query: new GraphQLObjectType2({
|
|
623
|
-
name: "Query",
|
|
624
|
-
fields: queries
|
|
625
|
-
})
|
|
626
|
-
};
|
|
627
|
-
if (this.config.mutations !== false) {
|
|
628
|
-
graphQLSchemaConfig.mutation = new GraphQLObjectType2({
|
|
629
|
-
name: "Mutation",
|
|
630
|
-
fields: mutations
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
const schema = new GraphQLSchema(graphQLSchemaConfig);
|
|
634
|
-
this.logDebugInfo(schema);
|
|
635
|
-
const cache = new Map;
|
|
636
|
-
const db = this.db;
|
|
637
|
-
const baseConfig = this.config;
|
|
638
|
-
const allTableNames = this.allTableNames;
|
|
639
|
-
const withPermissions = (permissions) => {
|
|
640
|
-
const cached = cache.get(permissions.id);
|
|
641
|
-
if (cached)
|
|
642
|
-
return cached;
|
|
643
|
-
const { config: mergedConfig, mutationFilter } = mergePermissionsIntoConfig(baseConfig, permissions, allTableNames);
|
|
644
|
-
const excludedSet = new Set(mergedConfig.tables?.exclude ?? []);
|
|
645
|
-
const hasAnyTable = allTableNames.some((t) => !excludedSet.has(t));
|
|
646
|
-
if (!hasAnyTable) {
|
|
647
|
-
const emptySchema = new GraphQLSchema({
|
|
648
|
-
query: new GraphQLObjectType2({
|
|
649
|
-
name: "Query",
|
|
650
|
-
fields: {
|
|
651
|
-
_empty: { type: GraphQLBoolean2 }
|
|
652
|
-
}
|
|
653
|
-
})
|
|
654
|
-
});
|
|
655
|
-
cache.set(permissions.id, emptySchema);
|
|
656
|
-
return emptySchema;
|
|
657
|
-
}
|
|
658
|
-
const permBuilder = new SchemaBuilder(db, mergedConfig);
|
|
659
|
-
const permEntities = permBuilder.buildEntities();
|
|
660
|
-
if (Object.keys(mutationFilter).length) {
|
|
661
|
-
postFilterMutations(permEntities.mutations, mutationFilter);
|
|
662
|
-
}
|
|
663
|
-
const permSchemaConfig = {
|
|
664
|
-
types: [...Object.values(permEntities.inputs), ...Object.values(permEntities.types)],
|
|
665
|
-
query: new GraphQLObjectType2({
|
|
666
|
-
name: "Query",
|
|
667
|
-
fields: Object.keys(permEntities.queries).length ? permEntities.queries : { _empty: { type: GraphQLBoolean2 } }
|
|
668
|
-
})
|
|
669
|
-
};
|
|
670
|
-
if (mergedConfig.mutations !== false && Object.keys(permEntities.mutations).length) {
|
|
671
|
-
permSchemaConfig.mutation = new GraphQLObjectType2({
|
|
672
|
-
name: "Mutation",
|
|
673
|
-
fields: permEntities.mutations
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
const permSchema = new GraphQLSchema(permSchemaConfig);
|
|
677
|
-
cache.set(permissions.id, permSchema);
|
|
678
|
-
return permSchema;
|
|
679
|
-
};
|
|
680
|
-
const clearPermissionCache = (id) => {
|
|
681
|
-
if (id)
|
|
682
|
-
cache.delete(id);
|
|
683
|
-
else
|
|
684
|
-
cache.clear();
|
|
685
|
-
};
|
|
686
|
-
return { schema, entities, withPermissions, clearPermissionCache };
|
|
687
|
-
}
|
|
688
|
-
logDebugInfo(schema) {
|
|
689
|
-
const debug = this.config.debug;
|
|
690
|
-
if (!debug)
|
|
691
|
-
return;
|
|
692
|
-
const showSize = debug === true || typeof debug === "object" && debug.schemaSize !== false;
|
|
693
|
-
const showTree = typeof debug === "object" && debug.relationTree === true;
|
|
694
|
-
if (showSize) {
|
|
695
|
-
const sdl = printSchema(schema);
|
|
696
|
-
const typeCount = Object.keys(schema.getTypeMap()).filter((t) => !t.startsWith("__")).length;
|
|
697
|
-
console.info(`[drizzle-graphql] Schema: ${sdl.length} bytes, ${typeCount} types`);
|
|
698
|
-
}
|
|
699
|
-
if (showTree) {
|
|
700
|
-
for (const [tableName, relations] of Object.entries(this.relationMap)) {
|
|
701
|
-
const relNames = Object.keys(relations);
|
|
702
|
-
if (relNames.length) {
|
|
703
|
-
console.info(`[drizzle-graphql] ${tableName}: ${relNames.join(", ")}`);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
extractTables(schema) {
|
|
709
|
-
const entries = Object.entries(schema).filter(([_, value]) => is3(value, PgTable2));
|
|
710
|
-
if (!entries.length) {
|
|
711
|
-
throw new Error("Drizzle-GraphQL Error: No tables detected in Drizzle-ORM's database instance. Did you forget to pass schema to drizzle constructor?");
|
|
712
|
-
}
|
|
713
|
-
return Object.fromEntries(entries);
|
|
714
|
-
}
|
|
715
|
-
getTable(tableName) {
|
|
716
|
-
const table = this.tables[tableName];
|
|
717
|
-
if (!table) {
|
|
718
|
-
throw new Error(`Drizzle-GraphQL Error: Table '${tableName}' not found.`);
|
|
719
|
-
}
|
|
720
|
-
return table;
|
|
721
|
-
}
|
|
722
|
-
buildRelationMap(schema) {
|
|
723
|
-
const schemaEntries = Object.entries(schema);
|
|
724
|
-
const tableEntries = Object.entries(this.tables);
|
|
725
|
-
const rawRelations = schemaEntries.filter(([_, value]) => is3(value, Relations)).map(([_, value]) => {
|
|
726
|
-
const entry = tableEntries.find(([__, tableValue]) => tableValue === value.table);
|
|
727
|
-
if (!entry)
|
|
728
|
-
return null;
|
|
729
|
-
return [entry[0], value];
|
|
730
|
-
}).filter((entry) => entry !== null).map(([tableName, relValue]) => [
|
|
731
|
-
tableName,
|
|
732
|
-
relValue.config(createTableRelationsHelpers(this.getTable(tableName)))
|
|
733
|
-
]);
|
|
734
|
-
return Object.fromEntries(rawRelations.map(([relName, config]) => {
|
|
735
|
-
const namedConfig = Object.fromEntries(Object.entries(config).map(([innerRelName, innerRelValue]) => {
|
|
736
|
-
const targetEntry = tableEntries.find(([_, tableValue]) => tableValue === innerRelValue.referencedTable);
|
|
737
|
-
if (!targetEntry)
|
|
738
|
-
return null;
|
|
739
|
-
return [
|
|
740
|
-
innerRelName,
|
|
741
|
-
{ relation: innerRelValue, targetTableName: targetEntry[0] }
|
|
742
|
-
];
|
|
743
|
-
}).filter((entry) => entry !== null));
|
|
744
|
-
return [relName, namedConfig];
|
|
745
|
-
}));
|
|
746
|
-
}
|
|
747
|
-
getFilterType(tableName) {
|
|
748
|
-
const cached = this.filterTypes.get(tableName);
|
|
749
|
-
if (cached)
|
|
750
|
-
return cached;
|
|
751
|
-
const filters = new GraphQLInputObjectType2({
|
|
752
|
-
name: `${capitalize(tableName)}Filters`,
|
|
753
|
-
fields: () => {
|
|
754
|
-
const filterColumns = this.getFilterValues(tableName);
|
|
755
|
-
const relationFilterFields = this.buildRelationFilterFields(tableName);
|
|
756
|
-
return {
|
|
757
|
-
...filterColumns,
|
|
758
|
-
...relationFilterFields,
|
|
759
|
-
OR: {
|
|
760
|
-
type: new GraphQLList2(new GraphQLNonNull2(new GraphQLInputObjectType2({
|
|
761
|
-
name: `${capitalize(tableName)}FiltersOr`,
|
|
762
|
-
fields: () => ({
|
|
763
|
-
...filterColumns,
|
|
764
|
-
...relationFilterFields
|
|
765
|
-
})
|
|
766
|
-
})))
|
|
767
|
-
}
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
});
|
|
771
|
-
this.filterTypes.set(tableName, filters);
|
|
772
|
-
return filters;
|
|
773
|
-
}
|
|
774
|
-
getOrderByType(tableName) {
|
|
775
|
-
const cached = this.orderByTypes.get(tableName);
|
|
776
|
-
if (cached)
|
|
777
|
-
return cached;
|
|
778
|
-
const table = this.getTable(tableName);
|
|
779
|
-
const columns = getTableColumns2(table);
|
|
780
|
-
const orderColumns = Object.fromEntries(Object.entries(columns).map(([columnName]) => [columnName, { type: this.innerOrder }]));
|
|
781
|
-
const order = new GraphQLInputObjectType2({
|
|
782
|
-
name: `${capitalize(tableName)}OrderBy`,
|
|
783
|
-
fields: orderColumns
|
|
784
|
-
});
|
|
785
|
-
this.orderByTypes.set(tableName, order);
|
|
786
|
-
return order;
|
|
787
|
-
}
|
|
788
|
-
getFilterValues(tableName) {
|
|
789
|
-
const cached = this.filterValueTypes.get(tableName);
|
|
790
|
-
if (cached)
|
|
791
|
-
return cached;
|
|
792
|
-
const table = this.getTable(tableName);
|
|
793
|
-
const columns = getTableColumns2(table);
|
|
794
|
-
const result = Object.fromEntries(Object.entries(columns).map(([columnName, col]) => [
|
|
795
|
-
columnName,
|
|
796
|
-
{ type: this.generateColumnFilterValues(col, tableName, columnName) }
|
|
797
|
-
]));
|
|
798
|
-
this.filterValueTypes.set(tableName, result);
|
|
799
|
-
return result;
|
|
800
|
-
}
|
|
801
|
-
getSelectFields(tableName) {
|
|
802
|
-
const cached = this.selectFieldTypes.get(tableName);
|
|
803
|
-
if (cached)
|
|
804
|
-
return cached;
|
|
805
|
-
const table = this.getTable(tableName);
|
|
806
|
-
const columns = getTableColumns2(table);
|
|
807
|
-
const result = Object.fromEntries(Object.entries(columns).map(([columnName, col]) => [
|
|
808
|
-
columnName,
|
|
809
|
-
drizzleColumnToGraphQLType(col, columnName, tableName)
|
|
810
|
-
]));
|
|
811
|
-
this.selectFieldTypes.set(tableName, result);
|
|
812
|
-
return result;
|
|
813
|
-
}
|
|
814
|
-
generateColumnFilterValues(column, tableName, columnName) {
|
|
815
|
-
const columnGraphQLType = drizzleColumnToGraphQLType(column, columnName, tableName, true, false, true);
|
|
816
|
-
const columnArr = new GraphQLList2(new GraphQLNonNull2(columnGraphQLType.type));
|
|
817
|
-
const baseFields = {
|
|
818
|
-
eq: { type: columnGraphQLType.type, description: columnGraphQLType.description },
|
|
819
|
-
ne: { type: columnGraphQLType.type, description: columnGraphQLType.description },
|
|
820
|
-
lt: { type: columnGraphQLType.type, description: columnGraphQLType.description },
|
|
821
|
-
lte: { type: columnGraphQLType.type, description: columnGraphQLType.description },
|
|
822
|
-
gt: { type: columnGraphQLType.type, description: columnGraphQLType.description },
|
|
823
|
-
gte: { type: columnGraphQLType.type, description: columnGraphQLType.description },
|
|
824
|
-
like: { type: GraphQLString2 },
|
|
825
|
-
notLike: { type: GraphQLString2 },
|
|
826
|
-
ilike: { type: GraphQLString2 },
|
|
827
|
-
notIlike: { type: GraphQLString2 },
|
|
828
|
-
inArray: { type: columnArr, description: `Array<${columnGraphQLType.description}>` },
|
|
829
|
-
notInArray: { type: columnArr, description: `Array<${columnGraphQLType.description}>` },
|
|
830
|
-
isNull: { type: GraphQLBoolean2 },
|
|
831
|
-
isNotNull: { type: GraphQLBoolean2 }
|
|
832
|
-
};
|
|
833
|
-
return new GraphQLInputObjectType2({
|
|
834
|
-
name: `${capitalize(tableName)}${capitalize(columnName)}Filters`,
|
|
835
|
-
fields: {
|
|
836
|
-
...baseFields,
|
|
837
|
-
OR: {
|
|
838
|
-
type: new GraphQLList2(new GraphQLNonNull2(new GraphQLInputObjectType2({
|
|
839
|
-
name: `${capitalize(tableName)}${capitalize(columnName)}FiltersOr`,
|
|
840
|
-
fields: baseFields
|
|
841
|
-
})))
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
});
|
|
845
|
-
}
|
|
846
|
-
generateSelectFields(tableName, typeName, withOrder, currentDepth = 0, usedTables = new Set, currentSelfDepth = 0, forceLeaf, allowedRelations) {
|
|
847
|
-
const relations = this.relationMap[tableName];
|
|
848
|
-
const relationEntries = relations ? Object.entries(relations) : [];
|
|
849
|
-
const order = withOrder ? this.getOrderByType(tableName) : undefined;
|
|
850
|
-
const filters = this.getFilterType(tableName);
|
|
851
|
-
const tableFields = this.getSelectFields(tableName);
|
|
852
|
-
if (forceLeaf || typeof this.limitRelationDepth !== "number" && usedTables.has(tableName) || typeof this.limitRelationDepth === "number" && currentDepth >= this.limitRelationDepth || !relationEntries.length) {
|
|
853
|
-
return { order, filters, tableFields, relationFields: {} };
|
|
854
|
-
}
|
|
855
|
-
const rawRelationFields = [];
|
|
856
|
-
const updatedUsedTables = new Set(usedTables).add(tableName);
|
|
857
|
-
const newDepth = currentDepth + 1;
|
|
858
|
-
for (const [relationName, { targetTableName, relation }] of relationEntries) {
|
|
859
|
-
if (allowedRelations && !allowedRelations.includes(relationName))
|
|
860
|
-
continue;
|
|
861
|
-
const pruneRule = this.pruneRelations.get(`${tableName}.${relationName}`);
|
|
862
|
-
if (pruneRule === false)
|
|
863
|
-
continue;
|
|
864
|
-
const relTypeName = `${typeName}${capitalize(relationName)}Relation`;
|
|
865
|
-
const isOne = is3(relation, One);
|
|
866
|
-
const isSelfRelation = targetTableName === tableName;
|
|
867
|
-
if (isSelfRelation && currentSelfDepth + 1 >= this.limitSelfRelationDepth) {
|
|
868
|
-
continue;
|
|
869
|
-
}
|
|
870
|
-
const isCrossCycle = !isSelfRelation && usedTables.has(targetTableName);
|
|
871
|
-
const effectiveDepth = isCrossCycle && typeof this.limitRelationDepth === "number" ? Math.max(newDepth, this.limitRelationDepth - 1) : newDepth;
|
|
872
|
-
const isLeaf = pruneRule === "leaf";
|
|
873
|
-
const onlyRels = pruneRule && typeof pruneRule === "object" ? pruneRule.only : undefined;
|
|
874
|
-
const relData = this.generateSelectFields(targetTableName, relTypeName, !isOne, effectiveDepth, updatedUsedTables, isSelfRelation ? currentSelfDepth + 1 : 0, isLeaf, onlyRels);
|
|
875
|
-
const relType = new GraphQLObjectType2({
|
|
876
|
-
name: relTypeName,
|
|
877
|
-
fields: this.toFieldMap(relData.tableFields, relData.relationFields)
|
|
878
|
-
});
|
|
879
|
-
if (isOne) {
|
|
880
|
-
rawRelationFields.push([
|
|
881
|
-
relationName,
|
|
882
|
-
{
|
|
883
|
-
type: relType,
|
|
884
|
-
args: {
|
|
885
|
-
where: { type: relData.filters }
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
]);
|
|
889
|
-
continue;
|
|
890
|
-
}
|
|
891
|
-
rawRelationFields.push([
|
|
892
|
-
relationName,
|
|
893
|
-
{
|
|
894
|
-
type: new GraphQLNonNull2(new GraphQLList2(new GraphQLNonNull2(relType))),
|
|
895
|
-
args: {
|
|
896
|
-
where: { type: relData.filters },
|
|
897
|
-
orderBy: { type: relData.order },
|
|
898
|
-
offset: { type: GraphQLInt2 },
|
|
899
|
-
limit: { type: GraphQLInt2 }
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
]);
|
|
903
|
-
}
|
|
904
|
-
const relationFields = Object.fromEntries(rawRelationFields);
|
|
905
|
-
return { order, filters, tableFields, relationFields };
|
|
906
|
-
}
|
|
907
|
-
generateTableTypes(tableName) {
|
|
908
|
-
const stylizedName = capitalize(tableName);
|
|
909
|
-
const { tableFields, relationFields, filters, order } = this.generateSelectFields(tableName, stylizedName, true);
|
|
910
|
-
const table = this.getTable(tableName);
|
|
911
|
-
const columns = getTableColumns2(table);
|
|
912
|
-
const columnEntries = Object.entries(columns);
|
|
913
|
-
const insertFields = Object.fromEntries(columnEntries.map(([columnName, col]) => [
|
|
914
|
-
columnName,
|
|
915
|
-
drizzleColumnToGraphQLType(col, columnName, tableName, false, true, true)
|
|
916
|
-
]));
|
|
917
|
-
const updateFields = Object.fromEntries(columnEntries.map(([columnName, col]) => [
|
|
918
|
-
columnName,
|
|
919
|
-
drizzleColumnToGraphQLType(col, columnName, tableName, true, false, true)
|
|
920
|
-
]));
|
|
921
|
-
const insertInput = new GraphQLInputObjectType2({
|
|
922
|
-
name: `${stylizedName}InsertInput`,
|
|
923
|
-
fields: insertFields
|
|
924
|
-
});
|
|
925
|
-
const updateInput = new GraphQLInputObjectType2({
|
|
926
|
-
name: `${stylizedName}UpdateInput`,
|
|
927
|
-
fields: updateFields
|
|
928
|
-
});
|
|
929
|
-
const selectSingleOutput = new GraphQLObjectType2({
|
|
930
|
-
name: `${stylizedName}SelectItem`,
|
|
931
|
-
fields: this.toFieldMap(tableFields, relationFields)
|
|
932
|
-
});
|
|
933
|
-
const selectArrOutput = new GraphQLNonNull2(new GraphQLList2(new GraphQLNonNull2(selectSingleOutput)));
|
|
934
|
-
const singleTableItemOutput = new GraphQLObjectType2({
|
|
935
|
-
name: `${stylizedName}Item`,
|
|
936
|
-
fields: this.toFieldMap(tableFields)
|
|
937
|
-
});
|
|
938
|
-
const arrTableItemOutput = new GraphQLNonNull2(new GraphQLList2(new GraphQLNonNull2(singleTableItemOutput)));
|
|
939
|
-
return {
|
|
940
|
-
inputs: {
|
|
941
|
-
insertInput,
|
|
942
|
-
updateInput,
|
|
943
|
-
tableOrder: order,
|
|
944
|
-
tableFilters: filters
|
|
945
|
-
},
|
|
946
|
-
outputs: {
|
|
947
|
-
selectSingleOutput,
|
|
948
|
-
selectArrOutput,
|
|
949
|
-
singleTableItemOutput,
|
|
950
|
-
arrTableItemOutput
|
|
951
|
-
}
|
|
952
|
-
};
|
|
953
|
-
}
|
|
954
|
-
buildRelationFilterFields(tableName) {
|
|
955
|
-
const relations = this.relationMap[tableName];
|
|
956
|
-
if (!relations)
|
|
957
|
-
return {};
|
|
958
|
-
const fields = {};
|
|
959
|
-
for (const [relationName, { targetTableName, relation }] of Object.entries(relations)) {
|
|
960
|
-
if (is3(relation, One)) {
|
|
961
|
-
fields[relationName] = {
|
|
962
|
-
type: this.getFilterType(targetTableName)
|
|
963
|
-
};
|
|
964
|
-
} else {
|
|
965
|
-
fields[relationName] = {
|
|
966
|
-
type: new GraphQLInputObjectType2({
|
|
967
|
-
name: `${capitalize(tableName)}${capitalize(relationName)}RelFilter`,
|
|
968
|
-
fields: () => ({
|
|
969
|
-
some: { type: this.getFilterType(targetTableName) },
|
|
970
|
-
every: { type: this.getFilterType(targetTableName) },
|
|
971
|
-
none: { type: this.getFilterType(targetTableName) }
|
|
972
|
-
})
|
|
973
|
-
})
|
|
974
|
-
};
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
return fields;
|
|
978
|
-
}
|
|
979
|
-
createQueryResolver(tableName, orderArgs, filterArgs) {
|
|
980
|
-
const queryName = `${uncapitalize(tableName)}${this.suffixes.list}`;
|
|
981
|
-
const typeName = `${capitalize(tableName)}SelectItem`;
|
|
982
|
-
const table = this.getTable(tableName);
|
|
983
|
-
const queryBase = this.db.query[tableName];
|
|
984
|
-
if (!queryBase) {
|
|
985
|
-
throw new Error(`Drizzle-GraphQL Error: Table ${tableName} not found in drizzle instance. Did you forget to pass schema to drizzle constructor?`);
|
|
986
|
-
}
|
|
987
|
-
const args = {
|
|
988
|
-
offset: { type: GraphQLInt2 },
|
|
989
|
-
limit: { type: GraphQLInt2 },
|
|
990
|
-
orderBy: { type: orderArgs },
|
|
991
|
-
where: { type: filterArgs }
|
|
992
|
-
};
|
|
993
|
-
const resolver = async (_source, resolverArgs, context, info) => {
|
|
994
|
-
return this.executeWithHooks(tableName, "query", resolverArgs, context, info, async (finalArgs) => {
|
|
995
|
-
try {
|
|
996
|
-
const { offset, limit, orderBy, where } = finalArgs;
|
|
997
|
-
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
998
|
-
const query = queryBase.findMany({
|
|
999
|
-
columns: this.extractColumns(this.getFieldsByTypeName(parsedInfo, typeName), table),
|
|
1000
|
-
offset,
|
|
1001
|
-
limit,
|
|
1002
|
-
orderBy: orderBy ? this.extractOrderBy(table, orderBy) : undefined,
|
|
1003
|
-
where: where ? this.extractAllFilters(table, tableName, where) : undefined,
|
|
1004
|
-
with: this.relationMap[tableName] ? this.extractRelationsParams(tableName, parsedInfo, typeName) : undefined
|
|
1005
|
-
});
|
|
1006
|
-
const result = await query;
|
|
1007
|
-
return remapToGraphQLArrayOutput(result, tableName, table, this.relationMap);
|
|
1008
|
-
} catch (e) {
|
|
1009
|
-
if (typeof e === "object" && e !== null && "message" in e && typeof e.message === "string") {
|
|
1010
|
-
throw new GraphQLError2(e.message);
|
|
1011
|
-
}
|
|
1012
|
-
throw e;
|
|
1013
|
-
}
|
|
1014
|
-
});
|
|
1015
|
-
};
|
|
1016
|
-
return { name: queryName, resolver, args };
|
|
1017
|
-
}
|
|
1018
|
-
createSingleQueryResolver(tableName, orderArgs, filterArgs) {
|
|
1019
|
-
const queryName = `${uncapitalize(tableName)}${this.suffixes.single}`;
|
|
1020
|
-
const typeName = `${capitalize(tableName)}SelectItem`;
|
|
1021
|
-
const table = this.getTable(tableName);
|
|
1022
|
-
const queryBase = this.db.query[tableName];
|
|
1023
|
-
if (!queryBase) {
|
|
1024
|
-
throw new Error(`Drizzle-GraphQL Error: Table ${tableName} not found in drizzle instance. Did you forget to pass schema to drizzle constructor?`);
|
|
1025
|
-
}
|
|
1026
|
-
const args = {
|
|
1027
|
-
offset: { type: GraphQLInt2 },
|
|
1028
|
-
orderBy: { type: orderArgs },
|
|
1029
|
-
where: { type: filterArgs }
|
|
1030
|
-
};
|
|
1031
|
-
const resolver = async (_source, resolverArgs, context, info) => {
|
|
1032
|
-
return this.executeWithHooks(tableName, "querySingle", resolverArgs, context, info, async (finalArgs) => {
|
|
1033
|
-
try {
|
|
1034
|
-
const { offset, orderBy, where } = finalArgs;
|
|
1035
|
-
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
1036
|
-
const query = queryBase.findFirst({
|
|
1037
|
-
columns: this.extractColumns(this.getFieldsByTypeName(parsedInfo, typeName), table),
|
|
1038
|
-
offset,
|
|
1039
|
-
orderBy: orderBy ? this.extractOrderBy(table, orderBy) : undefined,
|
|
1040
|
-
where: where ? this.extractAllFilters(table, tableName, where) : undefined,
|
|
1041
|
-
with: this.relationMap[tableName] ? this.extractRelationsParams(tableName, parsedInfo, typeName) : undefined
|
|
1042
|
-
});
|
|
1043
|
-
const result = await query;
|
|
1044
|
-
if (!result)
|
|
1045
|
-
return;
|
|
1046
|
-
return remapToGraphQLSingleOutput(result, tableName, table, this.relationMap);
|
|
1047
|
-
} catch (e) {
|
|
1048
|
-
if (typeof e === "object" && e !== null && "message" in e && typeof e.message === "string") {
|
|
1049
|
-
throw new GraphQLError2(e.message);
|
|
1050
|
-
}
|
|
1051
|
-
throw e;
|
|
1052
|
-
}
|
|
1053
|
-
});
|
|
1054
|
-
};
|
|
1055
|
-
return { name: queryName, resolver, args };
|
|
1056
|
-
}
|
|
1057
|
-
createCountResolver(tableName, filterArgs) {
|
|
1058
|
-
const queryName = `${uncapitalize(tableName)}Count`;
|
|
1059
|
-
const table = this.getTable(tableName);
|
|
1060
|
-
const args = {
|
|
1061
|
-
where: { type: filterArgs }
|
|
1062
|
-
};
|
|
1063
|
-
const resolver = async (_source, resolverArgs, context, info) => {
|
|
1064
|
-
return this.executeWithHooks(tableName, "count", resolverArgs, context, info, async (finalArgs) => {
|
|
1065
|
-
try {
|
|
1066
|
-
const { where } = finalArgs;
|
|
1067
|
-
const whereClause = where ? this.extractAllFilters(table, tableName, where) : undefined;
|
|
1068
|
-
return await this.executeCountQuery(table, whereClause);
|
|
1069
|
-
} catch (e) {
|
|
1070
|
-
if (typeof e === "object" && e !== null && "message" in e && typeof e.message === "string") {
|
|
1071
|
-
throw new GraphQLError2(e.message);
|
|
1072
|
-
}
|
|
1073
|
-
throw e;
|
|
1074
|
-
}
|
|
1075
|
-
});
|
|
1076
|
-
};
|
|
1077
|
-
return { name: queryName, resolver, args };
|
|
1078
|
-
}
|
|
1079
|
-
createInsertResolver(tableName, baseType) {
|
|
1080
|
-
const queryName = `insertInto${capitalize(tableName)}`;
|
|
1081
|
-
const typeName = `${capitalize(tableName)}Item`;
|
|
1082
|
-
const table = this.getTable(tableName);
|
|
1083
|
-
const args = {
|
|
1084
|
-
values: { type: new GraphQLNonNull2(new GraphQLList2(new GraphQLNonNull2(baseType))) }
|
|
1085
|
-
};
|
|
1086
|
-
const resolver = async (_source, resolverArgs, context, info) => {
|
|
1087
|
-
return this.executeWithHooks(tableName, "insert", resolverArgs, context, info, async (finalArgs) => {
|
|
1088
|
-
try {
|
|
1089
|
-
const input = remapFromGraphQLArrayInput(finalArgs.values, table);
|
|
1090
|
-
if (!input.length)
|
|
1091
|
-
throw new GraphQLError2("No values were provided!");
|
|
1092
|
-
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
1093
|
-
const columns = this.extractColumnsSQLFormat(this.getFieldsByTypeName(parsedInfo, typeName), table);
|
|
1094
|
-
const result = await this.adapter.executeInsert(this.db, table, input, columns);
|
|
1095
|
-
return remapToGraphQLArrayOutput(result, tableName, table);
|
|
1096
|
-
} catch (e) {
|
|
1097
|
-
if (typeof e === "object" && e !== null && "message" in e && typeof e.message === "string") {
|
|
1098
|
-
throw new GraphQLError2(e.message);
|
|
1099
|
-
}
|
|
1100
|
-
throw e;
|
|
1101
|
-
}
|
|
1102
|
-
});
|
|
1103
|
-
};
|
|
1104
|
-
return { name: queryName, resolver, args };
|
|
1105
|
-
}
|
|
1106
|
-
createInsertSingleResolver(tableName, baseType) {
|
|
1107
|
-
const queryName = `insertInto${capitalize(tableName)}Single`;
|
|
1108
|
-
const typeName = `${capitalize(tableName)}Item`;
|
|
1109
|
-
const table = this.getTable(tableName);
|
|
1110
|
-
const args = {
|
|
1111
|
-
values: { type: new GraphQLNonNull2(baseType) }
|
|
1112
|
-
};
|
|
1113
|
-
const resolver = async (_source, resolverArgs, context, info) => {
|
|
1114
|
-
return this.executeWithHooks(tableName, "insertSingle", resolverArgs, context, info, async (finalArgs) => {
|
|
1115
|
-
try {
|
|
1116
|
-
const input = remapFromGraphQLSingleInput(finalArgs.values, table);
|
|
1117
|
-
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
1118
|
-
const columns = this.extractColumnsSQLFormat(this.getFieldsByTypeName(parsedInfo, typeName), table);
|
|
1119
|
-
const result = await this.adapter.executeInsert(this.db, table, [input], columns);
|
|
1120
|
-
if (!result[0])
|
|
1121
|
-
return;
|
|
1122
|
-
return remapToGraphQLSingleOutput(result[0], tableName, table);
|
|
1123
|
-
} catch (e) {
|
|
1124
|
-
if (typeof e === "object" && e !== null && "message" in e && typeof e.message === "string") {
|
|
1125
|
-
throw new GraphQLError2(e.message);
|
|
1126
|
-
}
|
|
1127
|
-
throw e;
|
|
1128
|
-
}
|
|
1129
|
-
});
|
|
1130
|
-
};
|
|
1131
|
-
return { name: queryName, resolver, args };
|
|
1132
|
-
}
|
|
1133
|
-
createUpdateResolver(tableName, setArgs, filterArgs) {
|
|
1134
|
-
const queryName = `update${capitalize(tableName)}`;
|
|
1135
|
-
const typeName = `${capitalize(tableName)}Item`;
|
|
1136
|
-
const table = this.getTable(tableName);
|
|
1137
|
-
const args = {
|
|
1138
|
-
set: { type: new GraphQLNonNull2(setArgs) },
|
|
1139
|
-
where: { type: filterArgs }
|
|
1140
|
-
};
|
|
1141
|
-
const resolver = async (_source, resolverArgs, context, info) => {
|
|
1142
|
-
return this.executeWithHooks(tableName, "update", resolverArgs, context, info, async (finalArgs) => {
|
|
1143
|
-
try {
|
|
1144
|
-
const { where, set } = finalArgs;
|
|
1145
|
-
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
1146
|
-
const columns = this.extractColumnsSQLFormat(this.getFieldsByTypeName(parsedInfo, typeName), table);
|
|
1147
|
-
const input = remapFromGraphQLSingleInput(set, table);
|
|
1148
|
-
if (!Object.keys(input).length)
|
|
1149
|
-
throw new GraphQLError2("Unable to update with no values specified!");
|
|
1150
|
-
const whereClause = where ? this.extractAllFilters(table, tableName, where) : undefined;
|
|
1151
|
-
const result = await this.adapter.executeUpdate(this.db, table, input, whereClause, columns);
|
|
1152
|
-
return remapToGraphQLArrayOutput(result, tableName, table);
|
|
1153
|
-
} catch (e) {
|
|
1154
|
-
if (typeof e === "object" && e !== null && "message" in e && typeof e.message === "string") {
|
|
1155
|
-
throw new GraphQLError2(e.message);
|
|
1156
|
-
}
|
|
1157
|
-
throw e;
|
|
1158
|
-
}
|
|
1159
|
-
});
|
|
1160
|
-
};
|
|
1161
|
-
return { name: queryName, resolver, args };
|
|
1162
|
-
}
|
|
1163
|
-
createDeleteResolver(tableName, filterArgs) {
|
|
1164
|
-
const queryName = `deleteFrom${capitalize(tableName)}`;
|
|
1165
|
-
const typeName = `${capitalize(tableName)}Item`;
|
|
1166
|
-
const table = this.getTable(tableName);
|
|
1167
|
-
const args = {
|
|
1168
|
-
where: { type: filterArgs }
|
|
1169
|
-
};
|
|
1170
|
-
const resolver = async (_source, resolverArgs, context, info) => {
|
|
1171
|
-
return this.executeWithHooks(tableName, "delete", resolverArgs, context, info, async (finalArgs) => {
|
|
1172
|
-
try {
|
|
1173
|
-
const { where } = finalArgs;
|
|
1174
|
-
const parsedInfo = parseResolveInfo(info, { deep: true });
|
|
1175
|
-
const columns = this.extractColumnsSQLFormat(this.getFieldsByTypeName(parsedInfo, typeName), table);
|
|
1176
|
-
const whereClause = where ? this.extractAllFilters(table, tableName, where) : undefined;
|
|
1177
|
-
const result = await this.adapter.executeDelete(this.db, table, whereClause, columns);
|
|
1178
|
-
return remapToGraphQLArrayOutput(result, tableName, table);
|
|
1179
|
-
} catch (e) {
|
|
1180
|
-
if (typeof e === "object" && e !== null && "message" in e && typeof e.message === "string") {
|
|
1181
|
-
throw new GraphQLError2(e.message);
|
|
1182
|
-
}
|
|
1183
|
-
throw e;
|
|
1184
|
-
}
|
|
1185
|
-
});
|
|
1186
|
-
};
|
|
1187
|
-
return { name: queryName, resolver, args };
|
|
1188
|
-
}
|
|
1189
|
-
extractColumnFilters(column, columnName, operators) {
|
|
1190
|
-
if (!operators.OR?.length)
|
|
1191
|
-
delete operators.OR;
|
|
1192
|
-
const entries = Object.entries(operators);
|
|
1193
|
-
if (operators.OR) {
|
|
1194
|
-
const orVariants = [];
|
|
1195
|
-
for (const variant of operators.OR) {
|
|
1196
|
-
const extracted = this.extractColumnFilters(column, columnName, variant);
|
|
1197
|
-
if (extracted)
|
|
1198
|
-
orVariants.push(extracted);
|
|
1199
|
-
}
|
|
1200
|
-
const orClause = orVariants.length > 1 ? or(...orVariants) : orVariants.length === 1 ? orVariants[0] : undefined;
|
|
1201
|
-
if (entries.length <= 1)
|
|
1202
|
-
return orClause;
|
|
1203
|
-
const { OR: _, ...rest } = operators;
|
|
1204
|
-
const fieldClause = this.extractColumnFilters(column, columnName, rest);
|
|
1205
|
-
if (!fieldClause)
|
|
1206
|
-
return orClause;
|
|
1207
|
-
if (!orClause)
|
|
1208
|
-
return fieldClause;
|
|
1209
|
-
return and(fieldClause, orClause);
|
|
1210
|
-
}
|
|
1211
|
-
const comparisonOps = { eq, ne, gt, gte, lt, lte };
|
|
1212
|
-
const stringOps = { like, notLike, ilike, notIlike };
|
|
1213
|
-
const arrayOps = {
|
|
1214
|
-
inArray,
|
|
1215
|
-
notInArray
|
|
1216
|
-
};
|
|
1217
|
-
const nullOps = { isNull, isNotNull };
|
|
1218
|
-
const variants = [];
|
|
1219
|
-
for (const [operatorName, operatorValue] of entries) {
|
|
1220
|
-
if (operatorValue === null || operatorValue === false)
|
|
1221
|
-
continue;
|
|
1222
|
-
if (operatorName in comparisonOps) {
|
|
1223
|
-
const op = comparisonOps[operatorName];
|
|
1224
|
-
if (op) {
|
|
1225
|
-
const singleValue = remapFromGraphQLCore(operatorValue, column, columnName);
|
|
1226
|
-
variants.push(op(column, singleValue));
|
|
1227
|
-
}
|
|
1228
|
-
} else if (operatorName in stringOps) {
|
|
1229
|
-
const op = stringOps[operatorName];
|
|
1230
|
-
if (op)
|
|
1231
|
-
variants.push(op(column, operatorValue));
|
|
1232
|
-
} else if (operatorName in arrayOps) {
|
|
1233
|
-
const op = arrayOps[operatorName];
|
|
1234
|
-
if (op) {
|
|
1235
|
-
if (!operatorValue.length) {
|
|
1236
|
-
throw new GraphQLError2(`WHERE ${columnName}: Unable to use operator ${operatorName} with an empty array!`);
|
|
1237
|
-
}
|
|
1238
|
-
const arrayValue = operatorValue.map((val) => remapFromGraphQLCore(val, column, columnName));
|
|
1239
|
-
variants.push(op(column, arrayValue));
|
|
1240
|
-
}
|
|
1241
|
-
} else if (operatorName in nullOps) {
|
|
1242
|
-
const op = nullOps[operatorName];
|
|
1243
|
-
if (op)
|
|
1244
|
-
variants.push(op(column));
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
return variants.length ? variants.length > 1 ? and(...variants) : variants[0] : undefined;
|
|
1248
|
-
}
|
|
1249
|
-
extractTableColumnFilters(table, tableName, filters) {
|
|
1250
|
-
if (!filters.OR?.length)
|
|
1251
|
-
delete filters.OR;
|
|
1252
|
-
const tableColumns = getTableColumns2(table);
|
|
1253
|
-
const columnEntries = [];
|
|
1254
|
-
const relationEntries = [];
|
|
1255
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
1256
|
-
if (key === "OR")
|
|
1257
|
-
continue;
|
|
1258
|
-
if (value === null)
|
|
1259
|
-
continue;
|
|
1260
|
-
if (tableColumns[key]) {
|
|
1261
|
-
columnEntries.push([key, value]);
|
|
1262
|
-
} else {
|
|
1263
|
-
relationEntries.push([key, value]);
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
if (filters.OR) {
|
|
1267
|
-
const orVariants = [];
|
|
1268
|
-
for (const variant of filters.OR) {
|
|
1269
|
-
const extracted = this.extractAllFilters(table, tableName, variant);
|
|
1270
|
-
if (extracted)
|
|
1271
|
-
orVariants.push(extracted);
|
|
1272
|
-
}
|
|
1273
|
-
const orClause = orVariants.length > 1 ? or(...orVariants) : orVariants.length === 1 ? orVariants[0] : undefined;
|
|
1274
|
-
if (columnEntries.length === 0 && relationEntries.length === 0)
|
|
1275
|
-
return orClause;
|
|
1276
|
-
const { OR: _, ...rest } = filters;
|
|
1277
|
-
const fieldClause = this.extractAllFilters(table, tableName, rest);
|
|
1278
|
-
if (!fieldClause)
|
|
1279
|
-
return orClause;
|
|
1280
|
-
if (!orClause)
|
|
1281
|
-
return fieldClause;
|
|
1282
|
-
return and(fieldClause, orClause);
|
|
1283
|
-
}
|
|
1284
|
-
const variants = [];
|
|
1285
|
-
for (const [columnName, operators] of columnEntries) {
|
|
1286
|
-
const column = tableColumns[columnName];
|
|
1287
|
-
if (!column)
|
|
1288
|
-
continue;
|
|
1289
|
-
const result = this.extractColumnFilters(column, columnName, operators);
|
|
1290
|
-
if (result)
|
|
1291
|
-
variants.push(result);
|
|
1292
|
-
}
|
|
1293
|
-
for (const [relationName, filterValue] of relationEntries) {
|
|
1294
|
-
const result = this.extractRelationFilters(table, tableName, relationName, filterValue);
|
|
1295
|
-
if (result)
|
|
1296
|
-
variants.push(result);
|
|
1297
|
-
}
|
|
1298
|
-
return variants.length ? variants.length > 1 ? and(...variants) : variants[0] : undefined;
|
|
1299
|
-
}
|
|
1300
|
-
extractAllFilters(table, tableName, filters) {
|
|
1301
|
-
return this.extractTableColumnFilters(table, tableName, filters);
|
|
1302
|
-
}
|
|
1303
|
-
extractRelationFilters(table, tableName, relationName, filterValue) {
|
|
1304
|
-
const rel = this.relationMap[tableName]?.[relationName];
|
|
1305
|
-
if (!rel)
|
|
1306
|
-
return;
|
|
1307
|
-
const { targetTableName, relation } = rel;
|
|
1308
|
-
const targetTable = this.getTable(targetTableName);
|
|
1309
|
-
const isOne = is3(relation, One);
|
|
1310
|
-
if (isOne) {
|
|
1311
|
-
return this.buildExistsSubquery(table, targetTable, relation, targetTableName, filterValue, "some");
|
|
1312
|
-
} else {
|
|
1313
|
-
const variants = [];
|
|
1314
|
-
if (filterValue.some) {
|
|
1315
|
-
const result = this.buildExistsSubquery(table, targetTable, relation, targetTableName, filterValue.some, "some");
|
|
1316
|
-
if (result)
|
|
1317
|
-
variants.push(result);
|
|
1318
|
-
}
|
|
1319
|
-
if (filterValue.every) {
|
|
1320
|
-
const result = this.buildExistsSubquery(table, targetTable, relation, targetTableName, filterValue.every, "every");
|
|
1321
|
-
if (result)
|
|
1322
|
-
variants.push(result);
|
|
1323
|
-
}
|
|
1324
|
-
if (filterValue.none) {
|
|
1325
|
-
const result = this.buildExistsSubquery(table, targetTable, relation, targetTableName, filterValue.none, "none");
|
|
1326
|
-
if (result)
|
|
1327
|
-
variants.push(result);
|
|
1328
|
-
}
|
|
1329
|
-
return variants.length ? variants.length > 1 ? and(...variants) : variants[0] : undefined;
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
buildExistsSubquery(parentTable, targetTable, relation, targetTableName, filterValue, quantifier) {
|
|
1333
|
-
const joinCondition = this.buildJoinCondition(parentTable, targetTable, relation);
|
|
1334
|
-
if (!joinCondition)
|
|
1335
|
-
return;
|
|
1336
|
-
const innerFilter = this.extractAllFilters(targetTable, targetTableName, filterValue);
|
|
1337
|
-
const whereClause = innerFilter ? and(joinCondition, innerFilter) : joinCondition;
|
|
1338
|
-
if (!whereClause)
|
|
1339
|
-
return;
|
|
1340
|
-
const subquery = this.db.select({ _: sql`1` }).from(targetTable).where(whereClause);
|
|
1341
|
-
switch (quantifier) {
|
|
1342
|
-
case "some":
|
|
1343
|
-
return exists(subquery);
|
|
1344
|
-
case "none":
|
|
1345
|
-
return not(exists(subquery));
|
|
1346
|
-
case "every": {
|
|
1347
|
-
if (!innerFilter)
|
|
1348
|
-
return;
|
|
1349
|
-
const negatedFilter = not(innerFilter);
|
|
1350
|
-
const everyWhereClause = and(joinCondition, negatedFilter);
|
|
1351
|
-
if (!everyWhereClause)
|
|
1352
|
-
return;
|
|
1353
|
-
const everySubquery = this.db.select({ _: sql`1` }).from(targetTable).where(everyWhereClause);
|
|
1354
|
-
return not(exists(everySubquery));
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
buildJoinCondition(parentTable, _targetTable, relation) {
|
|
1359
|
-
try {
|
|
1360
|
-
const { fields, references } = normalizeRelation(this.relationalSchema, this.tableNamesMap, relation);
|
|
1361
|
-
if (!fields?.length || !references?.length)
|
|
1362
|
-
return;
|
|
1363
|
-
const parentName = getTableName(parentTable);
|
|
1364
|
-
const conditions = [];
|
|
1365
|
-
for (let i = 0;i < fields.length; i++) {
|
|
1366
|
-
const field = fields[i];
|
|
1367
|
-
const ref = references[i];
|
|
1368
|
-
if (!field || !ref)
|
|
1369
|
-
continue;
|
|
1370
|
-
if (getTableName(field.table) === parentName) {
|
|
1371
|
-
conditions.push(sql`${sql.identifier(parentName)}.${sql.identifier(field.name)} = ${ref}`);
|
|
1372
|
-
} else if (getTableName(ref.table) === parentName) {
|
|
1373
|
-
conditions.push(sql`${field} = ${sql.identifier(parentName)}.${sql.identifier(ref.name)}`);
|
|
1374
|
-
} else {
|
|
1375
|
-
conditions.push(eq(field, ref));
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
return conditions.length > 1 ? and(...conditions) : conditions[0];
|
|
1379
|
-
} catch {
|
|
1380
|
-
return;
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
extractOrderBy(table, orderArgs) {
|
|
1384
|
-
const res = [];
|
|
1385
|
-
for (const [column, config] of Object.entries(orderArgs).sort((a, b) => {
|
|
1386
|
-
const ap = a[1]?.priority ?? 0;
|
|
1387
|
-
const bp = b[1]?.priority ?? 0;
|
|
1388
|
-
return bp - ap;
|
|
1389
|
-
})) {
|
|
1390
|
-
if (!config)
|
|
1391
|
-
continue;
|
|
1392
|
-
const { direction } = config;
|
|
1393
|
-
const col = getTableColumns2(table)[column];
|
|
1394
|
-
if (!col)
|
|
1395
|
-
continue;
|
|
1396
|
-
res.push(direction === "asc" ? asc(col) : desc(col));
|
|
1397
|
-
}
|
|
1398
|
-
return res;
|
|
1399
|
-
}
|
|
1400
|
-
extractColumns(tree, table) {
|
|
1401
|
-
const tableColumns = getTableColumns2(table);
|
|
1402
|
-
const selectedColumns = [];
|
|
1403
|
-
for (const [_, fieldData] of Object.entries(tree)) {
|
|
1404
|
-
if (!tableColumns[fieldData.name])
|
|
1405
|
-
continue;
|
|
1406
|
-
selectedColumns.push([fieldData.name, true]);
|
|
1407
|
-
}
|
|
1408
|
-
if (!selectedColumns.length) {
|
|
1409
|
-
const columnKeys = Object.entries(tableColumns);
|
|
1410
|
-
const columnName = columnKeys.find((e) => !rqbCrashTypes.includes(e[1].columnType))?.[0] ?? columnKeys[0]?.[0] ?? "";
|
|
1411
|
-
selectedColumns.push([columnName, true]);
|
|
1412
|
-
}
|
|
1413
|
-
return Object.fromEntries(selectedColumns);
|
|
1414
|
-
}
|
|
1415
|
-
extractColumnsSQLFormat(tree, table) {
|
|
1416
|
-
const tableColumns = getTableColumns2(table);
|
|
1417
|
-
const selectedColumns = [];
|
|
1418
|
-
for (const [_, fieldData] of Object.entries(tree)) {
|
|
1419
|
-
const col = tableColumns[fieldData.name];
|
|
1420
|
-
if (!col)
|
|
1421
|
-
continue;
|
|
1422
|
-
selectedColumns.push([fieldData.name, col]);
|
|
1423
|
-
}
|
|
1424
|
-
if (!selectedColumns.length) {
|
|
1425
|
-
const columnKeys = Object.entries(tableColumns);
|
|
1426
|
-
const columnName = columnKeys.find((e) => !rqbCrashTypes.includes(e[1].columnType))?.[0] ?? columnKeys[0]?.[0] ?? "";
|
|
1427
|
-
const fallbackCol = tableColumns[columnName];
|
|
1428
|
-
if (fallbackCol)
|
|
1429
|
-
selectedColumns.push([columnName, fallbackCol]);
|
|
1430
|
-
}
|
|
1431
|
-
return Object.fromEntries(selectedColumns);
|
|
1432
|
-
}
|
|
1433
|
-
getFieldsByTypeName(info, typeName) {
|
|
1434
|
-
return info.fieldsByTypeName[typeName] ?? {};
|
|
1435
|
-
}
|
|
1436
|
-
extractRelationsParams(tableName, info, typeName) {
|
|
1437
|
-
return this.extractRelationsParamsInner(tableName, typeName, info, true);
|
|
1438
|
-
}
|
|
1439
|
-
extractRelationsParamsInner(tableName, typeName, originField, isInitial = false) {
|
|
1440
|
-
const relations = this.relationMap[tableName];
|
|
1441
|
-
if (!relations)
|
|
1442
|
-
return;
|
|
1443
|
-
const baseField = Object.entries(originField.fieldsByTypeName).find(([key]) => key === typeName)?.[1];
|
|
1444
|
-
if (!baseField)
|
|
1445
|
-
return;
|
|
1446
|
-
const args = {};
|
|
1447
|
-
for (const [relName, { targetTableName }] of Object.entries(relations)) {
|
|
1448
|
-
const relTypeName = `${isInitial ? capitalize(tableName) : typeName}${capitalize(relName)}Relation`;
|
|
1449
|
-
const relFieldSelection = Object.values(baseField).find((field) => field.name === relName)?.fieldsByTypeName[relTypeName];
|
|
1450
|
-
if (!relFieldSelection)
|
|
1451
|
-
continue;
|
|
1452
|
-
const targetTable = this.getTable(targetTableName);
|
|
1453
|
-
const columns = this.extractColumns(relFieldSelection, targetTable);
|
|
1454
|
-
const thisRecord = { columns };
|
|
1455
|
-
const relationField = Object.values(baseField).find((e) => e.name === relName);
|
|
1456
|
-
const relationArgs = relationField?.args;
|
|
1457
|
-
thisRecord.orderBy = relationArgs?.orderBy ? this.extractOrderBy(targetTable, relationArgs.orderBy) : undefined;
|
|
1458
|
-
thisRecord.where = relationArgs?.where ? this.extractAllFilters(targetTable, relName, relationArgs.where) : undefined;
|
|
1459
|
-
thisRecord.offset = relationArgs?.offset ?? undefined;
|
|
1460
|
-
thisRecord.limit = relationArgs?.limit ?? undefined;
|
|
1461
|
-
thisRecord.with = relationField ? this.extractRelationsParamsInner(targetTableName, relTypeName, relationField) : undefined;
|
|
1462
|
-
args[relName] = thisRecord;
|
|
1463
|
-
}
|
|
1464
|
-
return args;
|
|
1465
|
-
}
|
|
1466
|
-
async executeWithHooks(tableName, operation, args, context, info, execute) {
|
|
1467
|
-
const tableHooks = this.hooks[tableName];
|
|
1468
|
-
if (!tableHooks)
|
|
1469
|
-
return execute(args);
|
|
1470
|
-
const opHooks = tableHooks[operation];
|
|
1471
|
-
if (!opHooks)
|
|
1472
|
-
return execute(args);
|
|
1473
|
-
if ("resolve" in opHooks && opHooks.resolve) {
|
|
1474
|
-
const resolveFn = opHooks.resolve;
|
|
1475
|
-
const defaultResolve = (overrideArgs) => execute(overrideArgs ?? args);
|
|
1476
|
-
return resolveFn({ args, context, info, defaultResolve });
|
|
1477
|
-
}
|
|
1478
|
-
let finalArgs = args;
|
|
1479
|
-
let beforeData;
|
|
1480
|
-
if ("before" in opHooks && opHooks.before) {
|
|
1481
|
-
const beforeResult = await opHooks.before({
|
|
1482
|
-
args,
|
|
1483
|
-
context,
|
|
1484
|
-
info
|
|
1485
|
-
});
|
|
1486
|
-
if (beforeResult) {
|
|
1487
|
-
if (beforeResult.args)
|
|
1488
|
-
finalArgs = beforeResult.args;
|
|
1489
|
-
if (beforeResult.data)
|
|
1490
|
-
beforeData = beforeResult.data;
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
const result = await execute(finalArgs);
|
|
1494
|
-
if ("after" in opHooks && opHooks.after) {
|
|
1495
|
-
return opHooks.after({ result, beforeData, context, info });
|
|
1496
|
-
}
|
|
1497
|
-
return result;
|
|
1498
|
-
}
|
|
1499
|
-
async executeCountQuery(table, where) {
|
|
1500
|
-
const db = this.db;
|
|
1501
|
-
if (db.$count && typeof db.$count === "function") {
|
|
1502
|
-
try {
|
|
1503
|
-
const result2 = where ? await db.$count(table, where) : await db.$count(table);
|
|
1504
|
-
return Number(result2) || 0;
|
|
1505
|
-
} catch (_) {}
|
|
1506
|
-
}
|
|
1507
|
-
const query = this.db.select({ count: count() }).from(table);
|
|
1508
|
-
const result = await (where ? query.where(where) : query);
|
|
1509
|
-
const value = result[0]?.count || 0;
|
|
1510
|
-
return Number(value);
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
var rqbCrashTypes = ["SQLiteBigInt", "SQLiteBlobJson", "SQLiteBlobBuffer"];
|
|
1514
|
-
// src/codegen.ts
|
|
1515
|
-
import {
|
|
1516
|
-
isEnumType,
|
|
1517
|
-
isInputObjectType,
|
|
1518
|
-
isListType,
|
|
1519
|
-
isNonNullType,
|
|
1520
|
-
isObjectType,
|
|
1521
|
-
isScalarType,
|
|
1522
|
-
printSchema as printSchema2
|
|
1523
|
-
} from "graphql";
|
|
1524
|
-
function getEntitiesFromSchema(schema) {
|
|
1525
|
-
const queryType = schema.getQueryType();
|
|
1526
|
-
const mutationType = schema.getMutationType();
|
|
1527
|
-
const entities = new Map;
|
|
1528
|
-
if (!queryType)
|
|
1529
|
-
return entities;
|
|
1530
|
-
const queryFields = queryType.getFields();
|
|
1531
|
-
const mutationFields = mutationType?.getFields() ?? {};
|
|
1532
|
-
for (const [fieldName] of Object.entries(queryFields)) {
|
|
1533
|
-
if (!fieldName.endsWith("Count"))
|
|
1534
|
-
continue;
|
|
1535
|
-
const tableName = fieldName.slice(0, -5);
|
|
1536
|
-
const listName = queryFields[`${tableName}s`] ? `${tableName}s` : queryFields[tableName] ? tableName : undefined;
|
|
1537
|
-
const singleName = queryFields[tableName] ? tableName : undefined;
|
|
1538
|
-
if (!listName)
|
|
1539
|
-
continue;
|
|
1540
|
-
const listField = queryFields[listName];
|
|
1541
|
-
if (!listField)
|
|
1542
|
-
continue;
|
|
1543
|
-
const selectItemType = unwrapToObjectType(listField.type);
|
|
1544
|
-
if (!selectItemType)
|
|
1545
|
-
continue;
|
|
1546
|
-
const selectFields = selectItemType.getFields();
|
|
1547
|
-
const scalarFields = [];
|
|
1548
|
-
const relations = {};
|
|
1549
|
-
for (const [sfName, sfField] of Object.entries(selectFields)) {
|
|
1550
|
-
const unwrapped = unwrapType(sfField.type);
|
|
1551
|
-
if (isObjectType(unwrapped.type)) {
|
|
1552
|
-
const isList = unwrapped.isList;
|
|
1553
|
-
relations[sfName] = {
|
|
1554
|
-
entity: sfName,
|
|
1555
|
-
type: isList ? "many" : "one",
|
|
1556
|
-
_graphqlType: unwrapped.type
|
|
1557
|
-
};
|
|
1558
|
-
} else {
|
|
1559
|
-
scalarFields.push(sfName);
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
const capitalName = capitalize(tableName);
|
|
1563
|
-
const insertName = mutationFields[`insertInto${capitalName}`] ? `insertInto${capitalName}` : undefined;
|
|
1564
|
-
const insertSingleName = mutationFields[`insertInto${capitalName}Single`] ? `insertInto${capitalName}Single` : undefined;
|
|
1565
|
-
const updateName = mutationFields[`update${capitalName}`] ? `update${capitalName}` : undefined;
|
|
1566
|
-
const deleteName = mutationFields[`deleteFrom${capitalName}`] ? `deleteFrom${capitalName}` : undefined;
|
|
1567
|
-
const typeMap = schema.getTypeMap();
|
|
1568
|
-
const hasFilters = `${capitalName}Filters` in typeMap;
|
|
1569
|
-
const hasInsertInput = `${capitalName}InsertInput` in typeMap;
|
|
1570
|
-
const hasUpdateInput = `${capitalName}UpdateInput` in typeMap;
|
|
1571
|
-
const hasOrderBy = `${capitalName}OrderBy` in typeMap;
|
|
1572
|
-
entities.set(tableName, {
|
|
1573
|
-
tableName,
|
|
1574
|
-
typeName: capitalName,
|
|
1575
|
-
fields: scalarFields,
|
|
1576
|
-
relations,
|
|
1577
|
-
queryName: singleName,
|
|
1578
|
-
queryListName: listName,
|
|
1579
|
-
countName: fieldName,
|
|
1580
|
-
insertName,
|
|
1581
|
-
insertSingleName,
|
|
1582
|
-
updateName,
|
|
1583
|
-
deleteName,
|
|
1584
|
-
hasFilters,
|
|
1585
|
-
hasInsertInput,
|
|
1586
|
-
hasUpdateInput,
|
|
1587
|
-
hasOrderBy
|
|
1588
|
-
});
|
|
1589
|
-
}
|
|
1590
|
-
const entityFieldSets = new Map;
|
|
1591
|
-
for (const [name, info] of entities) {
|
|
1592
|
-
entityFieldSets.set(name, new Set(info.fields));
|
|
1593
|
-
}
|
|
1594
|
-
for (const entity of entities.values()) {
|
|
1595
|
-
for (const [, rel] of Object.entries(entity.relations)) {
|
|
1596
|
-
const graphqlType = rel._graphqlType;
|
|
1597
|
-
delete rel._graphqlType;
|
|
1598
|
-
if (!graphqlType)
|
|
1599
|
-
continue;
|
|
1600
|
-
const relFields = graphqlType.getFields();
|
|
1601
|
-
const relScalarNames = new Set;
|
|
1602
|
-
for (const [fname, ffield] of Object.entries(relFields)) {
|
|
1603
|
-
const unwrapped = unwrapType(ffield.type);
|
|
1604
|
-
if (!isObjectType(unwrapped.type)) {
|
|
1605
|
-
relScalarNames.add(fname);
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
|
-
let bestMatch;
|
|
1609
|
-
let bestOverlap = 0;
|
|
1610
|
-
for (const [entityName, entityFields] of entityFieldSets) {
|
|
1611
|
-
let overlap = 0;
|
|
1612
|
-
for (const f of relScalarNames) {
|
|
1613
|
-
if (entityFields.has(f))
|
|
1614
|
-
overlap++;
|
|
1615
|
-
}
|
|
1616
|
-
if (overlap === relScalarNames.size && overlap > bestOverlap) {
|
|
1617
|
-
bestOverlap = overlap;
|
|
1618
|
-
bestMatch = entityName;
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
if (bestMatch) {
|
|
1622
|
-
rel.entity = bestMatch;
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
return entities;
|
|
1627
|
-
}
|
|
1628
|
-
function unwrapToObjectType(type) {
|
|
1629
|
-
if (isNonNullType(type))
|
|
1630
|
-
return unwrapToObjectType(type.ofType);
|
|
1631
|
-
if (isListType(type))
|
|
1632
|
-
return unwrapToObjectType(type.ofType);
|
|
1633
|
-
if (isObjectType(type))
|
|
1634
|
-
return type;
|
|
1635
|
-
return null;
|
|
1636
|
-
}
|
|
1637
|
-
function unwrapType(type) {
|
|
1638
|
-
let isList = false;
|
|
1639
|
-
let isNonNull = false;
|
|
1640
|
-
let current = type;
|
|
1641
|
-
if (isNonNullType(current)) {
|
|
1642
|
-
isNonNull = true;
|
|
1643
|
-
current = current.ofType;
|
|
1644
|
-
}
|
|
1645
|
-
if (isListType(current)) {
|
|
1646
|
-
isList = true;
|
|
1647
|
-
current = current.ofType;
|
|
1648
|
-
if (isNonNullType(current)) {
|
|
1649
|
-
current = current.ofType;
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
return { type: current, isList, isNonNull };
|
|
1653
|
-
}
|
|
1654
|
-
function generateFilterTypeCode(schema, typeName, entities) {
|
|
1655
|
-
const typeMap = schema.getTypeMap();
|
|
1656
|
-
const filterType = typeMap[`${typeName}Filters`];
|
|
1657
|
-
if (!filterType || !isInputObjectType(filterType))
|
|
1658
|
-
return "";
|
|
1659
|
-
const lines = [];
|
|
1660
|
-
const fields = filterType.getFields();
|
|
1661
|
-
lines.push(`export type ${typeName}Filters = {`);
|
|
1662
|
-
for (const [fieldName, field] of Object.entries(fields)) {
|
|
1663
|
-
if (fieldName === "OR") {
|
|
1664
|
-
lines.push(` OR?: ${typeName}Filters[]`);
|
|
1665
|
-
continue;
|
|
1666
|
-
}
|
|
1667
|
-
const entity = entities.get(uncapitalize(typeName));
|
|
1668
|
-
const isRelation = entity?.relations[fieldName];
|
|
1669
|
-
if (isRelation) {
|
|
1670
|
-
const relType = capitalize(isRelation.entity);
|
|
1671
|
-
if (isRelation.type === "one") {
|
|
1672
|
-
lines.push(` ${fieldName}?: ${relType}Filters`);
|
|
1673
|
-
} else {
|
|
1674
|
-
lines.push(` ${fieldName}?: { some?: ${relType}Filters; every?: ${relType}Filters; none?: ${relType}Filters }`);
|
|
1675
|
-
}
|
|
1676
|
-
continue;
|
|
1677
|
-
}
|
|
1678
|
-
const filterInputType = field.type;
|
|
1679
|
-
const unwrapped = isNonNullType(filterInputType) ? filterInputType.ofType : filterInputType;
|
|
1680
|
-
if (isInputObjectType(unwrapped)) {
|
|
1681
|
-
const opFields = unwrapped.getFields();
|
|
1682
|
-
const columnType = inferColumnTsType(opFields);
|
|
1683
|
-
lines.push(` ${fieldName}?: {`);
|
|
1684
|
-
lines.push(` eq?: ${columnType} | null`);
|
|
1685
|
-
lines.push(` ne?: ${columnType} | null`);
|
|
1686
|
-
lines.push(` lt?: ${columnType} | null`);
|
|
1687
|
-
lines.push(` lte?: ${columnType} | null`);
|
|
1688
|
-
lines.push(` gt?: ${columnType} | null`);
|
|
1689
|
-
lines.push(` gte?: ${columnType} | null`);
|
|
1690
|
-
lines.push(` like?: string | null`);
|
|
1691
|
-
lines.push(` notLike?: string | null`);
|
|
1692
|
-
lines.push(` ilike?: string | null`);
|
|
1693
|
-
lines.push(` notIlike?: string | null`);
|
|
1694
|
-
lines.push(` inArray?: ${columnType}[] | null`);
|
|
1695
|
-
lines.push(` notInArray?: ${columnType}[] | null`);
|
|
1696
|
-
lines.push(` isNull?: boolean | null`);
|
|
1697
|
-
lines.push(` isNotNull?: boolean | null`);
|
|
1698
|
-
lines.push(` OR?: Array<Omit<${typeName}Filters['${fieldName}'], 'OR'>> | null`);
|
|
1699
|
-
lines.push(` }`);
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
lines.push("}");
|
|
1703
|
-
return lines.join(`
|
|
1704
|
-
`);
|
|
1705
|
-
}
|
|
1706
|
-
function inferColumnTsType(opFields) {
|
|
1707
|
-
const eqField = opFields.eq;
|
|
1708
|
-
if (!eqField)
|
|
1709
|
-
return "unknown";
|
|
1710
|
-
return graphqlTypeToTs(eqField.type);
|
|
1711
|
-
}
|
|
1712
|
-
function graphqlTypeToTs(type) {
|
|
1713
|
-
if (isNonNullType(type))
|
|
1714
|
-
return graphqlTypeToTs(type.ofType);
|
|
1715
|
-
if (isListType(type))
|
|
1716
|
-
return `${graphqlTypeToTs(type.ofType)}[]`;
|
|
1717
|
-
if (isScalarType(type)) {
|
|
1718
|
-
switch (type.name) {
|
|
1719
|
-
case "String":
|
|
1720
|
-
return "string";
|
|
1721
|
-
case "Int":
|
|
1722
|
-
case "Float":
|
|
1723
|
-
return "number";
|
|
1724
|
-
case "Boolean":
|
|
1725
|
-
return "boolean";
|
|
1726
|
-
case "JSON":
|
|
1727
|
-
return "unknown";
|
|
1728
|
-
default:
|
|
1729
|
-
return "unknown";
|
|
1730
|
-
}
|
|
1731
|
-
}
|
|
1732
|
-
if (isEnumType(type)) {
|
|
1733
|
-
return type.getValues().map((v) => `'${v.value}'`).join(" | ");
|
|
1734
|
-
}
|
|
1735
|
-
return "unknown";
|
|
1736
|
-
}
|
|
1737
|
-
function generateInputTypeCode(schema, inputTypeName, exportName) {
|
|
1738
|
-
const typeMap = schema.getTypeMap();
|
|
1739
|
-
const inputType = typeMap[inputTypeName];
|
|
1740
|
-
if (!inputType || !isInputObjectType(inputType))
|
|
1741
|
-
return "";
|
|
1742
|
-
const lines = [];
|
|
1743
|
-
const fields = inputType.getFields();
|
|
1744
|
-
lines.push(`export type ${exportName} = {`);
|
|
1745
|
-
for (const [fieldName, field] of Object.entries(fields)) {
|
|
1746
|
-
const isRequired = isNonNullType(field.type);
|
|
1747
|
-
const tsType = graphqlTypeToTs(field.type);
|
|
1748
|
-
const opt = isRequired ? "" : "?";
|
|
1749
|
-
const nullUnion = isRequired ? "" : " | null";
|
|
1750
|
-
lines.push(` ${fieldName}${opt}: ${tsType}${nullUnion}`);
|
|
1751
|
-
}
|
|
1752
|
-
lines.push("}");
|
|
1753
|
-
return lines.join(`
|
|
1754
|
-
`);
|
|
1755
|
-
}
|
|
1756
|
-
function generateOrderByTypeCode(schema, typeName) {
|
|
1757
|
-
const typeMap = schema.getTypeMap();
|
|
1758
|
-
const orderByType = typeMap[`${typeName}OrderBy`];
|
|
1759
|
-
if (!orderByType || !isInputObjectType(orderByType))
|
|
1760
|
-
return "";
|
|
1761
|
-
const fields = orderByType.getFields();
|
|
1762
|
-
const fieldNames = Object.keys(fields);
|
|
1763
|
-
const lines = [];
|
|
1764
|
-
lines.push(`export type ${typeName}OrderBy = {`);
|
|
1765
|
-
for (const name of fieldNames) {
|
|
1766
|
-
lines.push(` ${name}?: { direction: 'asc' | 'desc'; priority: number }`);
|
|
1767
|
-
}
|
|
1768
|
-
lines.push("}");
|
|
1769
|
-
return lines.join(`
|
|
1770
|
-
`);
|
|
1771
|
-
}
|
|
1772
|
-
function generateWireFormatType(entity, drizzleImportPath, typeNameOverrides) {
|
|
1773
|
-
const drizzleTypeName = typeNameOverrides[entity.tableName] ?? capitalize(entity.tableName);
|
|
1774
|
-
if (drizzleImportPath) {
|
|
1775
|
-
return `export type ${entity.typeName}Wire = Omit<Drizzle${drizzleTypeName}, DateKeys<Drizzle${drizzleTypeName}>>
|
|
1776
|
-
& { [K in DateKeys<Drizzle${drizzleTypeName}>]: string }`;
|
|
1777
|
-
}
|
|
1778
|
-
return `// Wire format for ${entity.tableName} (no Drizzle import configured)
|
|
1779
|
-
export type ${entity.typeName}Wire = Record<string, unknown>`;
|
|
1780
|
-
}
|
|
1781
|
-
function generateSDL(schema) {
|
|
1782
|
-
return printSchema2(schema);
|
|
1783
|
-
}
|
|
1784
|
-
function generateTypes(schema, options) {
|
|
1785
|
-
const entities = getEntitiesFromSchema(schema);
|
|
1786
|
-
const drizzlePath = options?.drizzle?.importPath;
|
|
1787
|
-
const typeOverrides = options?.drizzle?.typeNames ?? {};
|
|
1788
|
-
const lines = [];
|
|
1789
|
-
lines.push("// ─── Auto-generated by drizzle-graphql-pg codegen ────────");
|
|
1790
|
-
lines.push("// Do not edit manually. Re-run the codegen script to update.");
|
|
1791
|
-
lines.push("");
|
|
1792
|
-
lines.push("// biome-ignore lint/suspicious/noExplicitAny: utility type for date key extraction");
|
|
1793
|
-
lines.push("type DateKeys<T> = { [K in keyof T]: T[K] extends Date | null ? K : T[K] extends Date ? K : never }[keyof T]");
|
|
1794
|
-
lines.push("");
|
|
1795
|
-
if (drizzlePath) {
|
|
1796
|
-
const imports = [];
|
|
1797
|
-
for (const entity of entities.values()) {
|
|
1798
|
-
const drizzleTypeName = typeOverrides[entity.tableName] ?? capitalize(entity.tableName);
|
|
1799
|
-
imports.push(` type ${drizzleTypeName} as Drizzle${drizzleTypeName}`);
|
|
1800
|
-
}
|
|
1801
|
-
lines.push(`import {`);
|
|
1802
|
-
lines.push(imports.join(`,
|
|
1803
|
-
`));
|
|
1804
|
-
lines.push(`} from '${drizzlePath}'`);
|
|
1805
|
-
lines.push("");
|
|
1806
|
-
}
|
|
1807
|
-
lines.push("// ─── Wire Format Types ──────────────────────────────────");
|
|
1808
|
-
lines.push("// Drizzle types with Date fields converted to string (GraphQL serialization)");
|
|
1809
|
-
lines.push("");
|
|
1810
|
-
for (const entity of entities.values()) {
|
|
1811
|
-
lines.push(generateWireFormatType(entity, drizzlePath, typeOverrides));
|
|
1812
|
-
lines.push("");
|
|
1813
|
-
}
|
|
1814
|
-
lines.push("// ─── Filter Types ──────────────────────────────────────");
|
|
1815
|
-
lines.push("");
|
|
1816
|
-
for (const entity of entities.values()) {
|
|
1817
|
-
if (!entity.hasFilters)
|
|
1818
|
-
continue;
|
|
1819
|
-
const code = generateFilterTypeCode(schema, entity.typeName, entities);
|
|
1820
|
-
if (code) {
|
|
1821
|
-
lines.push(code);
|
|
1822
|
-
lines.push("");
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
lines.push("// ─── Input Types ──────────────────────────────────────");
|
|
1826
|
-
lines.push("");
|
|
1827
|
-
for (const entity of entities.values()) {
|
|
1828
|
-
if (entity.hasInsertInput) {
|
|
1829
|
-
const code = generateInputTypeCode(schema, `${entity.typeName}InsertInput`, `${entity.typeName}InsertInput`);
|
|
1830
|
-
if (code) {
|
|
1831
|
-
lines.push(code);
|
|
1832
|
-
lines.push("");
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
if (entity.hasUpdateInput) {
|
|
1836
|
-
const code = generateInputTypeCode(schema, `${entity.typeName}UpdateInput`, `${entity.typeName}UpdateInput`);
|
|
1837
|
-
if (code) {
|
|
1838
|
-
lines.push(code);
|
|
1839
|
-
lines.push("");
|
|
1840
|
-
}
|
|
1841
|
-
}
|
|
1842
|
-
}
|
|
1843
|
-
lines.push("// ─── OrderBy Types ──────────────────────────────────────");
|
|
1844
|
-
lines.push("");
|
|
1845
|
-
for (const entity of entities.values()) {
|
|
1846
|
-
if (!entity.hasOrderBy)
|
|
1847
|
-
continue;
|
|
1848
|
-
const code = generateOrderByTypeCode(schema, entity.typeName);
|
|
1849
|
-
if (code) {
|
|
1850
|
-
lines.push(code);
|
|
1851
|
-
lines.push("");
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
|
-
return lines.join(`
|
|
1855
|
-
`);
|
|
1856
|
-
}
|
|
1857
|
-
function generateEntityDefs(schema, options) {
|
|
1858
|
-
const entities = getEntitiesFromSchema(schema);
|
|
1859
|
-
const typeOverrides = options?.drizzle?.typeNames ?? {};
|
|
1860
|
-
const lines = [];
|
|
1861
|
-
lines.push("// ─── Auto-generated by drizzle-graphql-pg codegen ────────");
|
|
1862
|
-
lines.push("// Do not edit manually. Re-run the codegen script to update.");
|
|
1863
|
-
lines.push("");
|
|
1864
|
-
lines.push("import type {");
|
|
1865
|
-
const typeImports = [];
|
|
1866
|
-
for (const entity of entities.values()) {
|
|
1867
|
-
typeImports.push(` ${entity.typeName}Wire`);
|
|
1868
|
-
if (entity.hasFilters)
|
|
1869
|
-
typeImports.push(` ${entity.typeName}Filters`);
|
|
1870
|
-
if (entity.hasInsertInput)
|
|
1871
|
-
typeImports.push(` ${entity.typeName}InsertInput`);
|
|
1872
|
-
if (entity.hasUpdateInput)
|
|
1873
|
-
typeImports.push(` ${entity.typeName}UpdateInput`);
|
|
1874
|
-
if (entity.hasOrderBy)
|
|
1875
|
-
typeImports.push(` ${entity.typeName}OrderBy`);
|
|
1876
|
-
}
|
|
1877
|
-
lines.push(typeImports.join(`,
|
|
1878
|
-
`));
|
|
1879
|
-
lines.push("} from './types'");
|
|
1880
|
-
lines.push("");
|
|
1881
|
-
lines.push("export const schema = {");
|
|
1882
|
-
for (const entity of entities.values()) {
|
|
1883
|
-
lines.push(` ${entity.tableName}: {`);
|
|
1884
|
-
if (entity.queryName)
|
|
1885
|
-
lines.push(` queryName: '${entity.queryName}',`);
|
|
1886
|
-
if (entity.queryListName)
|
|
1887
|
-
lines.push(` queryListName: '${entity.queryListName}',`);
|
|
1888
|
-
if (entity.countName)
|
|
1889
|
-
lines.push(` countName: '${entity.countName}',`);
|
|
1890
|
-
if (entity.insertName)
|
|
1891
|
-
lines.push(` insertName: '${entity.insertName}',`);
|
|
1892
|
-
if (entity.insertSingleName)
|
|
1893
|
-
lines.push(` insertSingleName: '${entity.insertSingleName}',`);
|
|
1894
|
-
if (entity.updateName)
|
|
1895
|
-
lines.push(` updateName: '${entity.updateName}',`);
|
|
1896
|
-
if (entity.deleteName)
|
|
1897
|
-
lines.push(` deleteName: '${entity.deleteName}',`);
|
|
1898
|
-
lines.push(` fields: [${entity.fields.map((f) => `'${f}'`).join(", ")}],`);
|
|
1899
|
-
lines.push(` relations: {`);
|
|
1900
|
-
for (const [relName, rel] of Object.entries(entity.relations)) {
|
|
1901
|
-
lines.push(` ${relName}: { entity: '${rel.entity}', type: '${rel.type}' },`);
|
|
1902
|
-
}
|
|
1903
|
-
lines.push(` },`);
|
|
1904
|
-
lines.push(` },`);
|
|
1905
|
-
}
|
|
1906
|
-
lines.push("} as const");
|
|
1907
|
-
lines.push("");
|
|
1908
|
-
lines.push("export type EntityDefs = {");
|
|
1909
|
-
for (const entity of entities.values()) {
|
|
1910
|
-
lines.push(` ${entity.tableName}: {`);
|
|
1911
|
-
lines.push(` fields: ${entity.typeName}Wire`);
|
|
1912
|
-
lines.push(` relations: {`);
|
|
1913
|
-
for (const [relName, rel] of Object.entries(entity.relations)) {
|
|
1914
|
-
lines.push(` ${relName}: { entity: '${rel.entity}'; type: '${rel.type}' }`);
|
|
1915
|
-
}
|
|
1916
|
-
lines.push(` }`);
|
|
1917
|
-
if (entity.hasFilters)
|
|
1918
|
-
lines.push(` filters: ${entity.typeName}Filters`);
|
|
1919
|
-
if (entity.hasInsertInput)
|
|
1920
|
-
lines.push(` insertInput: ${entity.typeName}InsertInput`);
|
|
1921
|
-
if (entity.hasUpdateInput)
|
|
1922
|
-
lines.push(` updateInput: ${entity.typeName}UpdateInput`);
|
|
1923
|
-
if (entity.hasOrderBy)
|
|
1924
|
-
lines.push(` orderBy: ${entity.typeName}OrderBy`);
|
|
1925
|
-
lines.push(` }`);
|
|
1926
|
-
}
|
|
1927
|
-
lines.push("}");
|
|
1928
|
-
lines.push("");
|
|
1929
|
-
lines.push("export type TableNameMap = {");
|
|
1930
|
-
for (const entity of entities.values()) {
|
|
1931
|
-
const drizzleTypeName = typeOverrides[entity.tableName] ?? capitalize(entity.tableName);
|
|
1932
|
-
lines.push(` ${drizzleTypeName}: '${entity.tableName}'`);
|
|
1933
|
-
}
|
|
1934
|
-
lines.push("}");
|
|
1935
|
-
lines.push("");
|
|
1936
|
-
return lines.join(`
|
|
1937
|
-
`);
|
|
1938
|
-
}
|
|
1939
|
-
// src/row-security.ts
|
|
1940
|
-
function withRowSecurity(rules) {
|
|
1941
|
-
const hooks = {};
|
|
1942
|
-
for (const [tableName, rule] of Object.entries(rules)) {
|
|
1943
|
-
const before = (ctx) => {
|
|
1944
|
-
const whereClause = rule(ctx.context);
|
|
1945
|
-
const existingWhere = ctx.args?.where;
|
|
1946
|
-
const mergedWhere = existingWhere ? { ...existingWhere, ...whereClause } : whereClause;
|
|
1947
|
-
return { args: { ...ctx.args, where: mergedWhere } };
|
|
1948
|
-
};
|
|
1949
|
-
const ops = ["query", "querySingle", "count", "update", "delete"];
|
|
1950
|
-
const tableHooks = {};
|
|
1951
|
-
for (const op of ops) {
|
|
1952
|
-
tableHooks[op] = { before };
|
|
1953
|
-
}
|
|
1954
|
-
hooks[tableName] = tableHooks;
|
|
1955
|
-
}
|
|
1956
|
-
return hooks;
|
|
1957
|
-
}
|
|
1958
|
-
function mergeHooks(...configs) {
|
|
1959
|
-
const result = {};
|
|
1960
|
-
for (const config of configs) {
|
|
1961
|
-
if (!config)
|
|
1962
|
-
continue;
|
|
1963
|
-
for (const [tableName, tableHooks] of Object.entries(config)) {
|
|
1964
|
-
if (!result[tableName]) {
|
|
1965
|
-
result[tableName] = {};
|
|
1966
|
-
}
|
|
1967
|
-
const existing = result[tableName];
|
|
1968
|
-
for (const [op, hooks] of Object.entries(tableHooks)) {
|
|
1969
|
-
if (!existing[op]) {
|
|
1970
|
-
existing[op] = hooks;
|
|
1971
|
-
continue;
|
|
1972
|
-
}
|
|
1973
|
-
const existingOp = existing[op];
|
|
1974
|
-
if ("resolve" in hooks) {
|
|
1975
|
-
existing[op] = hooks;
|
|
1976
|
-
continue;
|
|
1977
|
-
}
|
|
1978
|
-
if ("resolve" in existingOp) {
|
|
1979
|
-
existing[op] = hooks;
|
|
1980
|
-
continue;
|
|
1981
|
-
}
|
|
1982
|
-
const merged = {};
|
|
1983
|
-
if (hooks.before && existingOp.before) {
|
|
1984
|
-
const first = existingOp.before;
|
|
1985
|
-
const second = hooks.before;
|
|
1986
|
-
merged.before = async (ctx) => {
|
|
1987
|
-
const firstResult = await first(ctx);
|
|
1988
|
-
const nextCtx = firstResult?.args ? { ...ctx, args: firstResult.args } : ctx;
|
|
1989
|
-
const secondResult = await second(nextCtx);
|
|
1990
|
-
return {
|
|
1991
|
-
args: secondResult?.args ?? firstResult?.args ?? undefined,
|
|
1992
|
-
data: secondResult?.data ?? firstResult?.data ?? undefined
|
|
1993
|
-
};
|
|
1994
|
-
};
|
|
1995
|
-
} else {
|
|
1996
|
-
merged.before = hooks.before ?? existingOp.before;
|
|
1997
|
-
}
|
|
1998
|
-
if (hooks.after && existingOp.after) {
|
|
1999
|
-
const first = existingOp.after;
|
|
2000
|
-
const second = hooks.after;
|
|
2001
|
-
merged.after = async (ctx) => {
|
|
2002
|
-
const firstResult = await first(ctx);
|
|
2003
|
-
return second({ ...ctx, result: firstResult });
|
|
2004
|
-
};
|
|
2005
|
-
} else {
|
|
2006
|
-
merged.after = hooks.after ?? existingOp.after;
|
|
2007
|
-
}
|
|
2008
|
-
existing[op] = merged;
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
return result;
|
|
2013
|
-
}
|
|
2014
|
-
|
|
2015
|
-
// src/index.ts
|
|
2016
|
-
var buildSchema = (db, config) => {
|
|
2017
|
-
const builder = new SchemaBuilder(db, config);
|
|
2018
|
-
return builder.build();
|
|
2019
|
-
};
|
|
2020
|
-
var buildEntities = (db, config) => {
|
|
2021
|
-
const builder = new SchemaBuilder(db, config);
|
|
2022
|
-
return builder.buildEntities();
|
|
2023
|
-
};
|
|
2024
|
-
var buildSchemaFromDrizzle = (drizzleSchema, config) => {
|
|
2025
|
-
const { tables, tableNamesMap } = extractTablesRelationalConfig(drizzleSchema, createTableRelationsHelpers2);
|
|
2026
|
-
const schemaKeys = Object.keys(tables);
|
|
2027
|
-
const findStub = {
|
|
2028
|
-
findMany: () => Promise.resolve([]),
|
|
2029
|
-
findFirst: () => Promise.resolve(null)
|
|
2030
|
-
};
|
|
2031
|
-
const query = Object.fromEntries(schemaKeys.map((name) => [name, findStub]));
|
|
2032
|
-
const mockDb = {
|
|
2033
|
-
_: { fullSchema: drizzleSchema, schema: tables, tableNamesMap },
|
|
2034
|
-
query,
|
|
2035
|
-
select: () => ({ from: () => ({ where: () => ({}) }) })
|
|
2036
|
-
};
|
|
2037
|
-
const builder = new SchemaBuilder(mockDb, config);
|
|
2038
|
-
return builder.build();
|
|
2039
|
-
};
|
|
2040
|
-
export {
|
|
2041
|
-
withRowSecurity,
|
|
2042
|
-
restricted,
|
|
2043
|
-
readOnly,
|
|
2044
|
-
permissive,
|
|
2045
|
-
mergeHooks,
|
|
2046
|
-
generateTypes,
|
|
2047
|
-
generateSDL,
|
|
2048
|
-
generateEntityDefs,
|
|
2049
|
-
buildSchemaFromDrizzle,
|
|
2050
|
-
buildSchema,
|
|
2051
|
-
buildEntities,
|
|
2052
|
-
SchemaBuilder,
|
|
2053
|
-
GraphQLJSON
|
|
2054
|
-
};
|
|
1
|
+
export * from '@graphql-suite/schema'
|