@ensnode/ponder-subgraph 0.1.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.
@@ -0,0 +1,867 @@
1
+ // src/middleware.ts
2
+ import { maxAliasesPlugin } from "@escape.tech/graphql-armor-max-aliases";
3
+ import { maxDepthPlugin } from "@escape.tech/graphql-armor-max-depth";
4
+ import { maxTokensPlugin } from "@escape.tech/graphql-armor-max-tokens";
5
+ import { createYoga } from "graphql-yoga";
6
+ import { createMiddleware } from "hono/factory";
7
+
8
+ // src/graphql.ts
9
+ import DataLoader from "dataloader";
10
+ import {
11
+ Many,
12
+ One,
13
+ and,
14
+ arrayContained,
15
+ arrayContains,
16
+ asc,
17
+ createTableRelationsHelpers,
18
+ desc,
19
+ eq,
20
+ extractTablesRelationalConfig,
21
+ getTableColumns,
22
+ getTableUniqueName,
23
+ gt,
24
+ gte,
25
+ inArray,
26
+ is,
27
+ isNotNull,
28
+ isNull,
29
+ like,
30
+ lt,
31
+ lte,
32
+ ne,
33
+ not,
34
+ notInArray,
35
+ notLike,
36
+ or,
37
+ relations,
38
+ sql
39
+ } from "drizzle-orm";
40
+ import {
41
+ PgDialect,
42
+ PgEnumColumn,
43
+ PgInteger,
44
+ PgSerial,
45
+ isPgEnum,
46
+ pgTable
47
+ } from "drizzle-orm/pg-core";
48
+ import {
49
+ GraphQLBoolean,
50
+ GraphQLEnumType,
51
+ GraphQLFloat,
52
+ GraphQLInputObjectType,
53
+ GraphQLInt,
54
+ GraphQLInterfaceType,
55
+ GraphQLList,
56
+ GraphQLNonNull,
57
+ GraphQLObjectType,
58
+ GraphQLScalarType,
59
+ GraphQLSchema,
60
+ GraphQLString
61
+ } from "graphql";
62
+ import { GraphQLJSON } from "graphql-scalars";
63
+
64
+ // src/helpers.ts
65
+ var intersectionOf = (arrays) => arrays.reduce((a, b) => a.filter((c) => b.includes(c)));
66
+ var capitalize = (str) => {
67
+ if (!str) return str;
68
+ return `${str.charAt(0).toUpperCase()}${str.slice(1)}`;
69
+ };
70
+
71
+ // src/serialize.ts
72
+ function serialize(value) {
73
+ return JSON.stringify(
74
+ value,
75
+ (_, v) => typeof v === "bigint" ? { __type: "bigint", value: v.toString() } : v
76
+ );
77
+ }
78
+ function deserialize(value) {
79
+ return JSON.parse(
80
+ value,
81
+ (_, value_) => (value_ == null ? void 0 : value_.__type) === "bigint" ? BigInt(value_.value) : value_
82
+ );
83
+ }
84
+
85
+ // src/graphql.ts
86
+ var onchain = Symbol.for("ponder:onchain");
87
+ var DEFAULT_LIMIT = 100;
88
+ var MAX_LIMIT = 1e3;
89
+ var OrderDirectionEnum = new GraphQLEnumType({
90
+ name: "OrderDirection",
91
+ values: {
92
+ asc: { value: "asc" },
93
+ desc: { value: "desc" }
94
+ }
95
+ });
96
+ function buildGraphQLSchema(_schema, polymorphicConfig = { types: {}, fields: {} }) {
97
+ const schema = { ..._schema };
98
+ const _tablesConfig = extractTablesRelationalConfig(schema, createTableRelationsHelpers);
99
+ const polymorphicTableConfigs = Object.fromEntries(
100
+ Object.entries(polymorphicConfig.types).map(([interfaceTypeName, implementingTables]) => [
101
+ interfaceTypeName,
102
+ implementingTables.map((table) => getTableUniqueName(table)).map((tableName) => _tablesConfig.tables[_tablesConfig.tableNamesMap[tableName]])
103
+ ])
104
+ );
105
+ Object.assign(
106
+ schema,
107
+ ...Object.keys(polymorphicConfig.types).map(
108
+ (interfaceTypeName) => getIntersectionTableSchema(interfaceTypeName, polymorphicTableConfigs[interfaceTypeName])
109
+ )
110
+ );
111
+ const polymorphicFields = Object.entries(polymorphicConfig.fields).map(([fieldPath, interfaceTypeName]) => [
112
+ fieldPath.split("."),
113
+ interfaceTypeName
114
+ ]);
115
+ const tablesConfig = extractTablesRelationalConfig(schema, createTableRelationsHelpers);
116
+ const tables = Object.values(tablesConfig.tables);
117
+ const enums = Object.entries(schema).filter(
118
+ (el) => isPgEnum(el[1])
119
+ );
120
+ const enumTypes = {};
121
+ for (const [enumTsName, enumObject] of enums) {
122
+ enumTypes[enumObject.enumName] = new GraphQLEnumType({
123
+ name: enumTsName,
124
+ values: enumObject.enumValues.reduce(
125
+ (acc, cur) => ({ ...acc, [cur]: {} }),
126
+ {}
127
+ )
128
+ });
129
+ }
130
+ const entityOrderByEnums = {};
131
+ for (const table of tables) {
132
+ const values = Object.keys(table.columns).reduce(
133
+ (acc, columnName) => ({
134
+ ...acc,
135
+ [columnName]: { value: columnName }
136
+ }),
137
+ {}
138
+ );
139
+ entityOrderByEnums[table.tsName] = new GraphQLEnumType({
140
+ name: `${getSubgraphEntityName(table.tsName)}_orderBy`,
141
+ values
142
+ });
143
+ }
144
+ const entityFilterTypes = {};
145
+ for (const table of tables) {
146
+ const filterType = new GraphQLInputObjectType({
147
+ name: `${getSubgraphEntityName(table.tsName)}_filter`,
148
+ fields: () => {
149
+ const filterFields = {
150
+ // Logical operators
151
+ // NOTE: lower case and/or
152
+ and: { type: new GraphQLList(filterType) },
153
+ or: { type: new GraphQLList(filterType) }
154
+ };
155
+ for (const [columnName, column] of Object.entries(table.columns)) {
156
+ const type = columnToGraphQLCore(column, enumTypes);
157
+ if (type instanceof GraphQLList) {
158
+ const baseType = innerType(type);
159
+ conditionSuffixes.universal.forEach((suffix) => {
160
+ filterFields[`${columnName}${suffix}`] = {
161
+ type: new GraphQLList(baseType)
162
+ };
163
+ });
164
+ conditionSuffixes.plural.forEach((suffix) => {
165
+ filterFields[`${columnName}${suffix}`] = { type: baseType };
166
+ });
167
+ }
168
+ if (type instanceof GraphQLScalarType || type instanceof GraphQLEnumType) {
169
+ if (type.name === "JSON") continue;
170
+ conditionSuffixes.universal.forEach((suffix) => {
171
+ filterFields[`${columnName}${suffix}`] = {
172
+ type
173
+ };
174
+ });
175
+ conditionSuffixes.singular.forEach((suffix) => {
176
+ filterFields[`${columnName}${suffix}`] = {
177
+ type: new GraphQLList(type)
178
+ };
179
+ });
180
+ if (["String", "ID"].includes(type.name)) {
181
+ conditionSuffixes.string.forEach((suffix) => {
182
+ filterFields[`${columnName}${suffix}`] = {
183
+ type
184
+ };
185
+ });
186
+ conditionSuffixes.numeric.forEach((suffix) => {
187
+ filterFields[`${columnName}${suffix}`] = {
188
+ type
189
+ };
190
+ });
191
+ }
192
+ if (["Int", "Float", "BigInt"].includes(type.name)) {
193
+ conditionSuffixes.numeric.forEach((suffix) => {
194
+ filterFields[`${columnName}${suffix}`] = {
195
+ type
196
+ };
197
+ });
198
+ }
199
+ }
200
+ }
201
+ for (const [relationName, relation] of Object.entries(table.relations)) {
202
+ if (is(relation, One)) {
203
+ conditionSuffixes.universal.forEach((suffix) => {
204
+ filterFields[`${relation.fieldName}${suffix}`] = {
205
+ type: GraphQLString
206
+ };
207
+ });
208
+ }
209
+ }
210
+ return filterFields;
211
+ }
212
+ });
213
+ entityFilterTypes[table.tsName] = filterType;
214
+ }
215
+ const entityTypes = {};
216
+ const interfaceTypes = {};
217
+ const entityPageTypes = {};
218
+ for (const interfaceTypeName of Object.keys(polymorphicTableConfigs)) {
219
+ const table = tablesConfig.tables[interfaceTypeName];
220
+ interfaceTypes[interfaceTypeName] = new GraphQLInterfaceType({
221
+ name: interfaceTypeName,
222
+ fields: () => {
223
+ const fieldConfigMap = {};
224
+ for (const [columnName, column] of Object.entries(table.columns)) {
225
+ const type = columnToGraphQLCore(column, enumTypes);
226
+ fieldConfigMap[columnName] = {
227
+ type: column.notNull ? new GraphQLNonNull(type) : type
228
+ };
229
+ }
230
+ return fieldConfigMap;
231
+ }
232
+ });
233
+ }
234
+ for (const table of tables) {
235
+ if (isInterfaceType(polymorphicConfig, table.tsName)) continue;
236
+ const entityTypeName = getSubgraphEntityName(table.tsName);
237
+ entityTypes[table.tsName] = new GraphQLObjectType({
238
+ name: entityTypeName,
239
+ interfaces: Object.entries(polymorphicTableConfigs).filter(
240
+ ([, implementingTables]) => implementingTables.map((table2) => table2.tsName).includes(table.tsName)
241
+ ).map(([interfaceTypeName]) => interfaceTypes[interfaceTypeName]),
242
+ fields: () => {
243
+ var _a, _b, _c, _d;
244
+ const fieldConfigMap = {};
245
+ for (const [columnName, column] of Object.entries(table.columns)) {
246
+ const type = columnToGraphQLCore(column, enumTypes);
247
+ fieldConfigMap[columnName] = {
248
+ type: column.notNull ? new GraphQLNonNull(type) : type
249
+ };
250
+ }
251
+ const relations2 = Object.entries(table.relations);
252
+ for (const [relationName, relation] of relations2) {
253
+ const referencedTable = tables.find(
254
+ (table2) => table2.dbName === relation.referencedTableName
255
+ );
256
+ if (!referencedTable)
257
+ throw new Error(
258
+ `Internal error: Referenced table "${relation.referencedTableName}" not found`
259
+ );
260
+ const referencedEntityType = entityTypes[referencedTable.tsName];
261
+ const referencedEntityPageType = entityPageTypes[referencedTable.tsName];
262
+ const referencedEntityFilterType = entityFilterTypes[referencedTable.tsName];
263
+ if (referencedEntityType === void 0 || referencedEntityPageType === void 0 || referencedEntityFilterType === void 0)
264
+ throw new Error(
265
+ `Internal error: Referenced entity types not found for table "${referencedTable.tsName}" `
266
+ );
267
+ if (is(relation, One)) {
268
+ const fields = ((_a = relation.config) == null ? void 0 : _a.fields) ?? [];
269
+ const references = ((_b = relation.config) == null ? void 0 : _b.references) ?? [];
270
+ if (fields.length !== references.length) {
271
+ throw new Error(
272
+ "Internal error: Fields and references arrays must be the same length"
273
+ );
274
+ }
275
+ fieldConfigMap[relationName] = {
276
+ // Note: There is a `relation.isNullable` field here but it appears
277
+ // to be internal / incorrect. Until we have support for foriegn
278
+ // key constraints, all `one` relations must be nullable.
279
+ type: referencedEntityType,
280
+ resolve: (parent, _args, context) => {
281
+ const loader = context.getDataLoader({ table: referencedTable });
282
+ const rowFragment = {};
283
+ for (let i = 0; i < references.length; i++) {
284
+ const referenceColumn = references[i];
285
+ const fieldColumn = fields[i];
286
+ const fieldColumnTsName = getColumnTsName(fieldColumn);
287
+ const referenceColumnTsName = getColumnTsName(referenceColumn);
288
+ rowFragment[referenceColumnTsName] = parent[fieldColumnTsName];
289
+ }
290
+ const encodedId = encodeRowFragment(rowFragment);
291
+ return loader.load(encodedId);
292
+ }
293
+ };
294
+ } else if (is(relation, Many)) {
295
+ const oneRelation = Object.values(referencedTable.relations).find(
296
+ (relation2) => relation2.relationName === relationName || is(relation2, One) && relation2.referencedTableName === table.dbName
297
+ );
298
+ if (!oneRelation)
299
+ throw new Error(
300
+ `Internal error: Relation "${relationName}" not found in table "${referencedTable.tsName}"`
301
+ );
302
+ const fields = ((_c = oneRelation.config) == null ? void 0 : _c.fields) ?? [];
303
+ const references = ((_d = oneRelation.config) == null ? void 0 : _d.references) ?? [];
304
+ const referencedEntityOrderByType = entityOrderByEnums[referencedTable.tsName];
305
+ if (!referencedEntityOrderByType)
306
+ throw new Error(`Entity_orderBy Enum not found for ${referencedTable.tsName}`);
307
+ fieldConfigMap[relationName] = {
308
+ type: referencedEntityPageType,
309
+ args: {
310
+ where: { type: referencedEntityFilterType },
311
+ orderBy: { type: referencedEntityOrderByType },
312
+ orderDirection: { type: OrderDirectionEnum },
313
+ first: { type: GraphQLInt },
314
+ skip: { type: GraphQLInt }
315
+ },
316
+ resolve: (parent, args, context, info) => {
317
+ const relationalConditions = [];
318
+ for (let i = 0; i < references.length; i++) {
319
+ const column = fields[i];
320
+ const value = parent[references[i].name];
321
+ relationalConditions.push(eq(column, value));
322
+ }
323
+ return executePluralQuery(
324
+ referencedTable,
325
+ schema[referencedTable.tsName],
326
+ context.drizzle,
327
+ args,
328
+ relationalConditions
329
+ );
330
+ }
331
+ };
332
+ } else {
333
+ throw new Error(
334
+ `Internal error: Relation "${relationName}" is unsupported, expected One or Many`
335
+ );
336
+ }
337
+ }
338
+ polymorphicFields.filter(([[parent]]) => parent === entityTypeName).forEach(([[, fieldName], interfaceTypeName]) => {
339
+ fieldConfigMap[fieldName] = definePolymorphicPluralField({
340
+ schema,
341
+ interfaceType: interfaceTypes[interfaceTypeName],
342
+ filterType: entityFilterTypes[interfaceTypeName],
343
+ orderByType: entityOrderByEnums[interfaceTypeName],
344
+ intersectionTableConfig: tablesConfig.tables[interfaceTypeName],
345
+ implementingTableConfigs: polymorphicTableConfigs[interfaceTypeName]
346
+ });
347
+ });
348
+ return fieldConfigMap;
349
+ }
350
+ });
351
+ entityPageTypes[table.tsName] = new GraphQLNonNull(
352
+ new GraphQLList(new GraphQLNonNull(entityTypes[table.tsName]))
353
+ );
354
+ }
355
+ const queryFields = {};
356
+ for (const table of tables) {
357
+ if (isInterfaceType(polymorphicConfig, table.tsName)) continue;
358
+ const entityType = entityTypes[table.tsName];
359
+ const entityPageType = entityPageTypes[table.tsName];
360
+ const entityFilterType = entityFilterTypes[table.tsName];
361
+ const singularFieldName = table.tsName.charAt(0).toLowerCase() + table.tsName.slice(1);
362
+ const pluralFieldName = `${singularFieldName}s`;
363
+ queryFields[singularFieldName] = {
364
+ type: entityType,
365
+ // Find the primary key columns and GraphQL core types and include them
366
+ // as arguments to the singular query type.
367
+ args: Object.fromEntries(
368
+ table.primaryKey.map((column) => [
369
+ getColumnTsName(column),
370
+ {
371
+ type: new GraphQLNonNull(columnToGraphQLCore(column, enumTypes))
372
+ }
373
+ ])
374
+ ),
375
+ resolve: async (_parent, args, context) => {
376
+ const loader = context.getDataLoader({ table });
377
+ const encodedId = encodeRowFragment(args);
378
+ return loader.load(encodedId);
379
+ }
380
+ };
381
+ const entityOrderByType = entityOrderByEnums[table.tsName];
382
+ if (!entityOrderByType) throw new Error(`Entity_orderBy Enum not found for ${table.tsName}`);
383
+ queryFields[pluralFieldName] = {
384
+ type: entityPageType,
385
+ args: {
386
+ where: { type: entityFilterType },
387
+ orderBy: { type: entityOrderByType },
388
+ orderDirection: { type: OrderDirectionEnum },
389
+ first: { type: GraphQLInt },
390
+ skip: { type: GraphQLInt }
391
+ },
392
+ resolve: async (_parent, args, context, info) => {
393
+ return executePluralQuery(table, schema[table.tsName], context.drizzle, args);
394
+ }
395
+ };
396
+ }
397
+ polymorphicFields.filter(([[parent]]) => parent === "Query").forEach(([[, fieldName], interfaceTypeName]) => {
398
+ queryFields[fieldName] = definePolymorphicPluralField({
399
+ schema,
400
+ interfaceType: interfaceTypes[interfaceTypeName],
401
+ filterType: entityFilterTypes[interfaceTypeName],
402
+ orderByType: entityOrderByEnums[interfaceTypeName],
403
+ intersectionTableConfig: tablesConfig.tables[interfaceTypeName],
404
+ implementingTableConfigs: polymorphicTableConfigs[interfaceTypeName]
405
+ });
406
+ });
407
+ return new GraphQLSchema({
408
+ // Include these here so they are listed first in the printed schema.
409
+ types: [GraphQLJSON, GraphQLBigInt, GraphQLPageInfo],
410
+ query: new GraphQLObjectType({
411
+ name: "Query",
412
+ fields: queryFields
413
+ })
414
+ });
415
+ }
416
+ var GraphQLPageInfo = new GraphQLObjectType({
417
+ name: "PageInfo",
418
+ fields: {
419
+ hasNextPage: { type: new GraphQLNonNull(GraphQLBoolean) },
420
+ hasPreviousPage: { type: new GraphQLNonNull(GraphQLBoolean) },
421
+ startCursor: { type: GraphQLString },
422
+ endCursor: { type: GraphQLString }
423
+ }
424
+ });
425
+ var GraphQLBigInt = new GraphQLScalarType({
426
+ name: "BigInt",
427
+ serialize: (value) => String(value),
428
+ parseValue: (value) => BigInt(value),
429
+ parseLiteral: (value) => {
430
+ if (value.kind === "StringValue") {
431
+ return BigInt(value.value);
432
+ } else {
433
+ throw new Error(
434
+ `Invalid value kind provided for field of type BigInt: ${value.kind}. Expected: StringValue`
435
+ );
436
+ }
437
+ }
438
+ });
439
+ var columnToGraphQLCore = (column, enumTypes) => {
440
+ if (column.columnType === "PgEvmBigint") {
441
+ return GraphQLBigInt;
442
+ }
443
+ if (column instanceof PgEnumColumn) {
444
+ if (column.enum === void 0) {
445
+ throw new Error(
446
+ `Internal error: Expected enum column "${getColumnTsName(column)}" to have an "enum" property`
447
+ );
448
+ }
449
+ const enumType = enumTypes[column.enum.enumName];
450
+ if (enumType === void 0) {
451
+ throw new Error(
452
+ `Internal error: Expected to find a GraphQL enum named "${column.enum.enumName}"`
453
+ );
454
+ }
455
+ return enumType;
456
+ }
457
+ switch (column.dataType) {
458
+ case "boolean":
459
+ return GraphQLBoolean;
460
+ case "json":
461
+ return GraphQLJSON;
462
+ case "date":
463
+ return GraphQLString;
464
+ case "string":
465
+ return GraphQLString;
466
+ case "bigint":
467
+ return GraphQLString;
468
+ case "number":
469
+ return is(column, PgInteger) || is(column, PgSerial) ? GraphQLInt : GraphQLFloat;
470
+ case "buffer":
471
+ return new GraphQLList(new GraphQLNonNull(GraphQLInt));
472
+ case "array": {
473
+ if (column.columnType === "PgVector") {
474
+ return new GraphQLList(new GraphQLNonNull(GraphQLFloat));
475
+ }
476
+ if (column.columnType === "PgGeometry") {
477
+ return new GraphQLList(new GraphQLNonNull(GraphQLFloat));
478
+ }
479
+ const innerType2 = columnToGraphQLCore(column.baseColumn, enumTypes);
480
+ return new GraphQLList(new GraphQLNonNull(innerType2));
481
+ }
482
+ default:
483
+ throw new Error(`Type ${column.dataType} is not implemented`);
484
+ }
485
+ };
486
+ var innerType = (type) => {
487
+ if (type instanceof GraphQLScalarType || type instanceof GraphQLEnumType) return type;
488
+ if (type instanceof GraphQLList || type instanceof GraphQLNonNull) return innerType(type.ofType);
489
+ throw new Error(`Type ${type.toString()} is not implemented`);
490
+ };
491
+ async function executePluralQuery(table, from, drizzle, args, extraConditions = []) {
492
+ const limit = args.first ?? DEFAULT_LIMIT;
493
+ if (limit > MAX_LIMIT) {
494
+ throw new Error(`Invalid limit. Got ${limit}, expected <=${MAX_LIMIT}.`);
495
+ }
496
+ const skip = args.skip ?? 0;
497
+ const orderBySchema = buildOrderBySchema(table, args);
498
+ const orderBy = orderBySchema.map(([columnName, direction]) => {
499
+ const column = table.columns[columnName];
500
+ if (column === void 0) {
501
+ throw new Error(`Unknown column "${columnName}" used in orderBy argument`);
502
+ }
503
+ return direction === "asc" ? asc(column) : desc(column);
504
+ });
505
+ const whereConditions = buildWhereConditions(args.where, table);
506
+ const query = drizzle.select().from(from).where(and(...whereConditions, ...extraConditions)).orderBy(...orderBy).limit(limit).offset(skip);
507
+ const startTime = performance.now();
508
+ const rows = await query;
509
+ const queryDurationSeconds = (performance.now() - startTime) / 1e3;
510
+ const isSlowQuery = queryDurationSeconds > 2;
511
+ if (isSlowQuery) {
512
+ console.warn(`Slow Query Detected (${queryDurationSeconds.toFixed(4)}s)`);
513
+ console.warn(new PgDialect().sqlToQuery(query.getSQL()).sql);
514
+ console.log("\n");
515
+ }
516
+ return rows;
517
+ }
518
+ var conditionSuffixes = {
519
+ universal: ["", "_not"],
520
+ singular: ["_in", "_not_in"],
521
+ plural: ["_has", "_not_has"],
522
+ numeric: ["_gt", "_lt", "_gte", "_lte"],
523
+ string: [
524
+ "_contains",
525
+ "_not_contains",
526
+ "_starts_with",
527
+ "_ends_with",
528
+ "_not_starts_with",
529
+ "_not_ends_with"
530
+ ]
531
+ };
532
+ var conditionSuffixesByLengthDesc = Object.values(conditionSuffixes).flat().sort((a, b) => b.length - a.length);
533
+ function buildWhereConditions(where, table) {
534
+ const conditions = [];
535
+ if (where === void 0) return conditions;
536
+ for (const [whereKey, rawValue] of Object.entries(where)) {
537
+ if (whereKey === "and" || whereKey === "or") {
538
+ if (!Array.isArray(rawValue)) {
539
+ throw new Error(
540
+ `Invalid query: Expected an array for the ${whereKey} operator. Got: ${rawValue}`
541
+ );
542
+ }
543
+ const nestedConditions = rawValue.flatMap(
544
+ (subWhere) => buildWhereConditions(subWhere, table)
545
+ );
546
+ if (nestedConditions.length > 0) {
547
+ conditions.push(whereKey === "and" ? and(...nestedConditions) : or(...nestedConditions));
548
+ }
549
+ continue;
550
+ }
551
+ const conditionSuffix = conditionSuffixesByLengthDesc.find((s) => whereKey.endsWith(s));
552
+ if (conditionSuffix === void 0) {
553
+ throw new Error(`Invariant violation: Condition suffix not found for where key ${whereKey}`);
554
+ }
555
+ const columnName = whereKey.slice(0, whereKey.length - conditionSuffix.length);
556
+ const column = columnName in table.relations ? (
557
+ // if the referenced name is a relation, the relevant column is this table's `${relationName}Id`
558
+ table.columns[`${columnName}Id`]
559
+ ) : (
560
+ // otherwise validate that the column name is present in the table
561
+ table.columns[columnName]
562
+ );
563
+ if (column === void 0) {
564
+ throw new Error(`Invalid query: Where clause contains unknown column ${columnName}`);
565
+ }
566
+ switch (conditionSuffix) {
567
+ case "":
568
+ if (column.columnType === "PgArray") {
569
+ conditions.push(and(arrayContains(column, rawValue), arrayContained(column, rawValue)));
570
+ } else {
571
+ if (rawValue === null) {
572
+ conditions.push(isNull(column));
573
+ } else {
574
+ conditions.push(eq(column, rawValue));
575
+ }
576
+ }
577
+ break;
578
+ case "_not":
579
+ if (column.columnType === "PgArray") {
580
+ conditions.push(
581
+ not(and(arrayContains(column, rawValue), arrayContained(column, rawValue)))
582
+ );
583
+ } else {
584
+ if (rawValue === null) {
585
+ conditions.push(isNotNull(column));
586
+ } else {
587
+ conditions.push(ne(column, rawValue));
588
+ }
589
+ }
590
+ break;
591
+ case "_in":
592
+ conditions.push(inArray(column, rawValue));
593
+ break;
594
+ case "_not_in":
595
+ conditions.push(notInArray(column, rawValue));
596
+ break;
597
+ case "_has":
598
+ conditions.push(arrayContains(column, [rawValue]));
599
+ break;
600
+ case "_not_has":
601
+ conditions.push(not(arrayContains(column, [rawValue])));
602
+ break;
603
+ case "_gt":
604
+ conditions.push(gt(column, rawValue));
605
+ break;
606
+ case "_lt":
607
+ conditions.push(lt(column, rawValue));
608
+ break;
609
+ case "_gte":
610
+ conditions.push(gte(column, rawValue));
611
+ break;
612
+ case "_lte":
613
+ conditions.push(lte(column, rawValue));
614
+ break;
615
+ case "_contains":
616
+ conditions.push(like(column, `%${rawValue}%`));
617
+ break;
618
+ case "_not_contains":
619
+ conditions.push(notLike(column, `%${rawValue}%`));
620
+ break;
621
+ case "_starts_with":
622
+ conditions.push(like(column, `${rawValue}%`));
623
+ break;
624
+ case "_ends_with":
625
+ conditions.push(like(column, `%${rawValue}`));
626
+ break;
627
+ case "_not_starts_with":
628
+ conditions.push(notLike(column, `${rawValue}%`));
629
+ break;
630
+ case "_not_ends_with":
631
+ conditions.push(notLike(column, `%${rawValue}`));
632
+ break;
633
+ default:
634
+ throw new Error(`Invalid Condition Suffix ${conditionSuffix}`);
635
+ }
636
+ }
637
+ return conditions;
638
+ }
639
+ function buildOrderBySchema(table, args) {
640
+ const userDirection = args.orderDirection ?? "asc";
641
+ const userColumns = args.orderBy !== void 0 ? [[args.orderBy, userDirection]] : [];
642
+ const pkColumns = table.primaryKey.map((column) => [getColumnTsName(column), userDirection]);
643
+ const missingPkColumns = pkColumns.filter(
644
+ (pkColumn) => !userColumns.some((userColumn) => userColumn[0] === pkColumn[0])
645
+ );
646
+ return [...userColumns, ...missingPkColumns];
647
+ }
648
+ function buildDataLoaderCache({ drizzle }) {
649
+ const dataLoaderMap = /* @__PURE__ */ new Map();
650
+ return ({ table }) => {
651
+ const baseQuery = drizzle.query[table.tsName];
652
+ if (baseQuery === void 0)
653
+ throw new Error(`Internal error: Unknown table "${table.tsName}" in data loader cache`);
654
+ let dataLoader = dataLoaderMap.get(table);
655
+ if (dataLoader === void 0) {
656
+ dataLoader = new DataLoader(
657
+ async (encodedIds) => {
658
+ const decodedRowFragments = encodedIds.map(decodeRowFragment);
659
+ const idConditions = decodedRowFragments.map(
660
+ (decodedRowFragment) => and(...buildWhereConditions(decodedRowFragment, table))
661
+ );
662
+ const rows = await baseQuery.findMany({
663
+ where: or(...idConditions),
664
+ limit: encodedIds.length
665
+ });
666
+ return decodedRowFragments.map(
667
+ (fragment) => rows.find(
668
+ (row) => Object.entries(fragment).every(([col, val]) => row[col] === val)
669
+ )
670
+ );
671
+ },
672
+ { maxBatchSize: 1e3 }
673
+ );
674
+ dataLoaderMap.set(table, dataLoader);
675
+ }
676
+ return dataLoader;
677
+ };
678
+ }
679
+ function getColumnTsName(column) {
680
+ const tableColumns = getTableColumns(column.table);
681
+ return Object.entries(tableColumns).find(([_, c]) => c.name === column.name)[0];
682
+ }
683
+ function encodeRowFragment(rowFragment) {
684
+ return Buffer.from(serialize(rowFragment)).toString("base64");
685
+ }
686
+ function decodeRowFragment(encodedRowFragment) {
687
+ return deserialize(Buffer.from(encodedRowFragment, "base64").toString());
688
+ }
689
+ function getSubgraphEntityName(tsName) {
690
+ return capitalize(tsName);
691
+ }
692
+ function isInterfaceType(polymorphicConfig, columnName) {
693
+ return columnName in polymorphicConfig.types;
694
+ }
695
+ function getIntersectionTableSchema(interfaceTypeName, tableConfigs) {
696
+ if (tableConfigs.length === 0) throw new Error("Must have some tables to intersect");
697
+ const baseColumns = tableConfigs[0].columns;
698
+ const baseRelations = tableConfigs[0].relations;
699
+ const commonColumnNames = intersectionOf(
700
+ tableConfigs.map((table) => Object.keys(table.columns))
701
+ //
702
+ );
703
+ const commonRelationsNames = intersectionOf(
704
+ tableConfigs.map((table) => Object.keys(table.relations))
705
+ );
706
+ const intersectionTable = pgTable("intersection_table", (t) => {
707
+ function getColumnBuilder(column) {
708
+ const sqlType = column.getSQLType();
709
+ if (sqlType === "numeric(78)") {
710
+ return t.numeric({ precision: 78 });
711
+ }
712
+ const built = t[sqlType.split("(")[0]]();
713
+ return column.primary ? built.primaryKey() : built;
714
+ }
715
+ const columnMap = {};
716
+ for (const columnName of commonColumnNames) {
717
+ const baseColumn = baseColumns[columnName];
718
+ columnMap[columnName] = getColumnBuilder(baseColumn).notNull(baseColumn.notNull);
719
+ }
720
+ return columnMap;
721
+ });
722
+ const intersectionTableRelations = relations(
723
+ intersectionTable,
724
+ ({ one }) => commonRelationsNames.reduce((memo, relationName) => {
725
+ const relation = baseRelations[relationName];
726
+ if (is(relation, One)) {
727
+ memo[relationName] = one(relation.referencedTable, relation.config);
728
+ } else if (is(relation, Many)) {
729
+ }
730
+ return memo;
731
+ }, {})
732
+ );
733
+ return {
734
+ [interfaceTypeName]: intersectionTable,
735
+ [`${interfaceTypeName}Relations`]: intersectionTableRelations
736
+ };
737
+ }
738
+ function getColumnsUnion(tables) {
739
+ return tables.reduce(
740
+ (memo, table) => ({
741
+ ...memo,
742
+ ...table.columns
743
+ }),
744
+ {}
745
+ );
746
+ }
747
+ function buildUnionAllQuery(drizzle, schema, tables, where = {}) {
748
+ const allColumns = getColumnsUnion(tables);
749
+ const allColumnNames = Object.keys(allColumns).sort();
750
+ const subqueries = tables.map((table) => {
751
+ const selectAllColumnsIncludingNulls = allColumnNames.reduce((memo, columnName) => {
752
+ var _a;
753
+ const column = allColumns[columnName];
754
+ return {
755
+ ...memo,
756
+ [columnName]: sql.raw(`${((_a = table.columns[columnName]) == null ? void 0 : _a.name) ?? "NULL"}::${column.getSQLType()}`).as(column.name)
757
+ };
758
+ }, {});
759
+ const relationalConditions = Object.entries(where).map(
760
+ ([foreignKeyName, foreignKeyValue]) => eq(table.columns[foreignKeyName], foreignKeyValue)
761
+ );
762
+ return drizzle.select({
763
+ ...selectAllColumnsIncludingNulls,
764
+ // inject __typename into each subquery
765
+ __typename: sql.raw(`'${getSubgraphEntityName(table.tsName)}'`).as("__typename")
766
+ }).from(schema[table.tsName]).where(and(...relationalConditions)).$dynamic();
767
+ });
768
+ return subqueries.reduce((memo, fragment, i) => i === 0 ? fragment : memo.unionAll(fragment)).as("intersection_table");
769
+ }
770
+ function definePolymorphicPluralField({
771
+ schema,
772
+ interfaceType,
773
+ filterType,
774
+ orderByType,
775
+ intersectionTableConfig,
776
+ implementingTableConfigs
777
+ }) {
778
+ return {
779
+ type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(interfaceType))),
780
+ args: {
781
+ where: { type: filterType },
782
+ orderBy: { type: orderByType },
783
+ orderDirection: { type: OrderDirectionEnum },
784
+ first: { type: GraphQLInt },
785
+ skip: { type: GraphQLInt }
786
+ },
787
+ resolve: async (parent, args, { drizzle }, info) => {
788
+ const foreignKeyName = getForeignKeyFieldName(intersectionTableConfig, info.parentType.name);
789
+ const relationalFilter = foreignKeyName ? { [foreignKeyName]: parent.id } : {};
790
+ const subquery = buildUnionAllQuery(
791
+ drizzle,
792
+ schema,
793
+ implementingTableConfigs,
794
+ relationalFilter
795
+ );
796
+ return executePluralQuery(intersectionTableConfig, subquery, drizzle, args);
797
+ }
798
+ };
799
+ }
800
+ function getForeignKeyFieldName(table, parentTypeName) {
801
+ var _a, _b, _c;
802
+ const relationName = Object.keys(table.relations).find(
803
+ (relationName2) => getSubgraphEntityName(relationName2) === parentTypeName
804
+ );
805
+ if (!relationName) return;
806
+ const relation = table.relations[relationName];
807
+ if (!is(relation, One)) return;
808
+ const fkEntry = Object.entries(((_c = (_b = (_a = relation.config) == null ? void 0 : _a.fields) == null ? void 0 : _b[0]) == null ? void 0 : _c.table) ?? {}).find(
809
+ ([_, column]) => {
810
+ var _a2, _b2, _c2;
811
+ return column.name === ((_c2 = (_b2 = (_a2 = relation.config) == null ? void 0 : _a2.fields) == null ? void 0 : _b2[0]) == null ? void 0 : _c2.name);
812
+ }
813
+ );
814
+ return fkEntry == null ? void 0 : fkEntry[0];
815
+ }
816
+
817
+ // src/middleware.ts
818
+ var graphql = ({
819
+ db,
820
+ schema,
821
+ polymorphicConfig
822
+ }, {
823
+ maxOperationTokens = 1e3,
824
+ maxOperationDepth = 100,
825
+ maxOperationAliases = 30
826
+ } = {
827
+ // Default limits are from Apollo:
828
+ // https://www.apollographql.com/blog/prevent-graph-misuse-with-operation-size-and-complexity-limit
829
+ maxOperationTokens: 1e3,
830
+ maxOperationDepth: 100,
831
+ maxOperationAliases: 30
832
+ }) => {
833
+ const graphqlSchema = buildGraphQLSchema(schema, polymorphicConfig);
834
+ const yoga = createYoga({
835
+ graphqlEndpoint: "*",
836
+ // Disable built-in route validation, use Hono routing instead
837
+ schema: graphqlSchema,
838
+ context: () => {
839
+ const getDataLoader = buildDataLoaderCache({ drizzle: db });
840
+ return { drizzle: db, getDataLoader };
841
+ },
842
+ maskedErrors: process.env.NODE_ENV === "production" ? true : {
843
+ maskError(error) {
844
+ console.error(error.originalError);
845
+ return error;
846
+ }
847
+ },
848
+ logging: false,
849
+ graphiql: true,
850
+ parserAndValidationCache: false,
851
+ plugins: [
852
+ maxTokensPlugin({ n: maxOperationTokens }),
853
+ maxDepthPlugin({ n: maxOperationDepth, ignoreIntrospection: false }),
854
+ maxAliasesPlugin({ n: maxOperationAliases, allowList: [] })
855
+ ]
856
+ });
857
+ return createMiddleware(async (c) => {
858
+ const response = await yoga.handle(c.req.raw);
859
+ response.status = 200;
860
+ response.statusText = "OK";
861
+ return response;
862
+ });
863
+ };
864
+ export {
865
+ graphql
866
+ };
867
+ //# sourceMappingURL=middleware.js.map