@constructive-io/graphql-codegen 2.22.1 → 2.23.1

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.
Files changed (99) hide show
  1. package/cli/codegen/barrel.d.ts +5 -1
  2. package/cli/codegen/barrel.js +13 -11
  3. package/cli/codegen/index.d.ts +3 -3
  4. package/cli/codegen/index.js +15 -9
  5. package/cli/codegen/orm/client-generator.js +3 -2
  6. package/cli/codegen/orm/custom-ops-generator.js +17 -4
  7. package/cli/codegen/orm/input-types-generator.js +129 -18
  8. package/cli/codegen/orm/model-generator.js +2 -1
  9. package/cli/codegen/orm/query-builder.d.ts +1 -1
  10. package/cli/codegen/orm/query-builder.js +2 -2
  11. package/cli/codegen/schema-types-generator.js +5 -5
  12. package/cli/codegen/utils.d.ts +6 -1
  13. package/cli/codegen/utils.js +23 -8
  14. package/cli/commands/generate-orm.d.ts +5 -3
  15. package/cli/commands/generate-orm.js +65 -84
  16. package/cli/commands/generate.d.ts +2 -0
  17. package/cli/commands/generate.js +66 -87
  18. package/cli/commands/shared.d.ts +74 -0
  19. package/cli/commands/shared.js +88 -0
  20. package/cli/index.js +75 -45
  21. package/cli/introspect/index.d.ts +8 -5
  22. package/cli/introspect/index.js +19 -7
  23. package/cli/introspect/infer-tables.d.ts +51 -0
  24. package/cli/introspect/infer-tables.js +550 -0
  25. package/cli/introspect/source/endpoint.d.ts +34 -0
  26. package/cli/introspect/source/endpoint.js +35 -0
  27. package/cli/introspect/source/file.d.ts +20 -0
  28. package/cli/introspect/source/file.js +103 -0
  29. package/cli/introspect/source/index.d.ts +48 -0
  30. package/cli/introspect/source/index.js +72 -0
  31. package/cli/introspect/source/types.d.ts +58 -0
  32. package/cli/introspect/source/types.js +27 -0
  33. package/cli/introspect/transform.d.ts +5 -6
  34. package/cli/introspect/transform.js +0 -173
  35. package/cli/watch/cache.d.ts +3 -4
  36. package/cli/watch/cache.js +6 -10
  37. package/cli/watch/poller.d.ts +1 -2
  38. package/cli/watch/poller.js +27 -45
  39. package/cli/watch/types.d.ts +0 -3
  40. package/core/ast.js +4 -4
  41. package/core/query-builder.js +12 -12
  42. package/esm/cli/codegen/barrel.d.ts +5 -1
  43. package/esm/cli/codegen/barrel.js +13 -11
  44. package/esm/cli/codegen/index.d.ts +3 -3
  45. package/esm/cli/codegen/index.js +18 -12
  46. package/esm/cli/codegen/orm/client-generator.js +3 -2
  47. package/esm/cli/codegen/orm/custom-ops-generator.js +18 -5
  48. package/esm/cli/codegen/orm/input-types-generator.js +130 -19
  49. package/esm/cli/codegen/orm/model-generator.js +3 -2
  50. package/esm/cli/codegen/orm/query-builder.d.ts +1 -1
  51. package/esm/cli/codegen/orm/query-builder.js +2 -2
  52. package/esm/cli/codegen/schema-types-generator.js +6 -6
  53. package/esm/cli/codegen/utils.d.ts +6 -1
  54. package/esm/cli/codegen/utils.js +22 -8
  55. package/esm/cli/commands/generate-orm.d.ts +5 -3
  56. package/esm/cli/commands/generate-orm.js +65 -84
  57. package/esm/cli/commands/generate.d.ts +2 -0
  58. package/esm/cli/commands/generate.js +66 -87
  59. package/esm/cli/commands/shared.d.ts +74 -0
  60. package/esm/cli/commands/shared.js +84 -0
  61. package/esm/cli/index.js +76 -46
  62. package/esm/cli/introspect/index.d.ts +8 -5
  63. package/esm/cli/introspect/index.js +10 -3
  64. package/esm/cli/introspect/infer-tables.d.ts +51 -0
  65. package/esm/cli/introspect/infer-tables.js +547 -0
  66. package/esm/cli/introspect/source/endpoint.d.ts +34 -0
  67. package/esm/cli/introspect/source/endpoint.js +31 -0
  68. package/esm/cli/introspect/source/file.d.ts +20 -0
  69. package/esm/cli/introspect/source/file.js +66 -0
  70. package/esm/cli/introspect/source/index.d.ts +48 -0
  71. package/esm/cli/introspect/source/index.js +54 -0
  72. package/esm/cli/introspect/source/types.d.ts +58 -0
  73. package/esm/cli/introspect/source/types.js +23 -0
  74. package/esm/cli/introspect/transform.d.ts +5 -6
  75. package/esm/cli/introspect/transform.js +0 -172
  76. package/esm/cli/watch/cache.d.ts +3 -4
  77. package/esm/cli/watch/cache.js +7 -11
  78. package/esm/cli/watch/poller.d.ts +1 -2
  79. package/esm/cli/watch/poller.js +28 -46
  80. package/esm/cli/watch/types.d.ts +0 -3
  81. package/esm/core/ast.js +4 -4
  82. package/esm/core/query-builder.js +12 -12
  83. package/esm/generators/mutations.js +3 -3
  84. package/esm/generators/select.js +7 -7
  85. package/esm/types/config.d.ts +21 -5
  86. package/esm/types/config.js +2 -1
  87. package/generators/mutations.js +3 -3
  88. package/generators/select.js +7 -7
  89. package/package.json +5 -3
  90. package/types/config.d.ts +21 -5
  91. package/types/config.js +2 -1
  92. package/cli/introspect/fetch-meta.d.ts +0 -31
  93. package/cli/introspect/fetch-meta.js +0 -108
  94. package/cli/introspect/meta-query.d.ts +0 -111
  95. package/cli/introspect/meta-query.js +0 -191
  96. package/esm/cli/introspect/fetch-meta.d.ts +0 -31
  97. package/esm/cli/introspect/fetch-meta.js +0 -104
  98. package/esm/cli/introspect/meta-query.d.ts +0 -111
  99. package/esm/cli/introspect/meta-query.js +0 -188
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Infer PostGraphile table metadata from standard GraphQL introspection
3
+ *
4
+ * This module replaces the need for the _meta query by recognizing PostGraphile's
5
+ * naming conventions and type patterns from standard GraphQL introspection.
6
+ *
7
+ * Key patterns recognized:
8
+ * - Connection types: {PluralName}Connection → entity name
9
+ * - Filter types: {Name}Filter
10
+ * - Input types: Create{Name}Input, Update{Name}Input, Delete{Name}Input
11
+ * - Payload types: Create{Name}Payload, Update{Name}Payload, Delete{Name}Payload
12
+ * - Query operations: {pluralName} (list), {singularName} (single)
13
+ * - Mutation operations: create{Name}, update{Name}, delete{Name}
14
+ */
15
+ import type { IntrospectionQueryResponse } from '../../types/introspection';
16
+ import type { CleanTable } from '../../types/schema';
17
+ /**
18
+ * PostGraphile naming patterns for type detection
19
+ */
20
+ declare const PATTERNS: {
21
+ connection: RegExp;
22
+ edge: RegExp;
23
+ filter: RegExp;
24
+ condition: RegExp;
25
+ orderBy: RegExp;
26
+ patch: RegExp;
27
+ createInput: RegExp;
28
+ updateInput: RegExp;
29
+ deleteInput: RegExp;
30
+ createPayload: RegExp;
31
+ updatePayload: RegExp;
32
+ deletePayload: RegExp;
33
+ createMutation: RegExp;
34
+ updateMutation: RegExp;
35
+ deleteMutation: RegExp;
36
+ };
37
+ export interface InferTablesOptions {
38
+ /**
39
+ * Custom pattern overrides (for non-standard PostGraphile configurations)
40
+ */
41
+ patterns?: Partial<typeof PATTERNS>;
42
+ }
43
+ /**
44
+ * Infer CleanTable[] from GraphQL introspection by recognizing PostGraphile patterns
45
+ *
46
+ * @param introspection - Standard GraphQL introspection response
47
+ * @param options - Optional configuration
48
+ * @returns Array of CleanTable objects compatible with existing generators
49
+ */
50
+ export declare function inferTablesFromIntrospection(introspection: IntrospectionQueryResponse, options?: InferTablesOptions): CleanTable[];
51
+ export {};
@@ -0,0 +1,547 @@
1
+ import { unwrapType, getBaseTypeName, isList } from '../../types/introspection';
2
+ import { singularize, pluralize, lcFirst, ucFirst } from 'inflekt';
3
+ // ============================================================================
4
+ // Pattern Matching Constants
5
+ // ============================================================================
6
+ /**
7
+ * PostGraphile naming patterns for type detection
8
+ */
9
+ const PATTERNS = {
10
+ // Type suffixes
11
+ connection: /^(.+)Connection$/,
12
+ edge: /^(.+)Edge$/,
13
+ filter: /^(.+)Filter$/,
14
+ condition: /^(.+)Condition$/,
15
+ orderBy: /^(.+)OrderBy$/,
16
+ patch: /^(.+)Patch$/,
17
+ // Input type patterns
18
+ createInput: /^Create(.+)Input$/,
19
+ updateInput: /^Update(.+)Input$/,
20
+ deleteInput: /^Delete(.+)Input$/,
21
+ // Payload type patterns
22
+ createPayload: /^Create(.+)Payload$/,
23
+ updatePayload: /^Update(.+)Payload$/,
24
+ deletePayload: /^Delete(.+)Payload$/,
25
+ // Mutation name patterns (camelCase)
26
+ createMutation: /^create([A-Z][a-zA-Z0-9]*)$/,
27
+ updateMutation: /^update([A-Z][a-zA-Z0-9]*)$/,
28
+ deleteMutation: /^delete([A-Z][a-zA-Z0-9]*)$/,
29
+ };
30
+ /**
31
+ * Built-in GraphQL types to ignore
32
+ */
33
+ const BUILTIN_TYPES = new Set([
34
+ 'Query',
35
+ 'Mutation',
36
+ 'Subscription',
37
+ 'String',
38
+ 'Int',
39
+ 'Float',
40
+ 'Boolean',
41
+ 'ID',
42
+ // PostGraphile built-in types
43
+ 'Node',
44
+ 'PageInfo',
45
+ 'Cursor',
46
+ 'UUID',
47
+ 'Datetime',
48
+ 'Date',
49
+ 'Time',
50
+ 'JSON',
51
+ 'BigInt',
52
+ 'BigFloat',
53
+ ]);
54
+ /**
55
+ * Types that start with __ are internal GraphQL types
56
+ */
57
+ function isInternalType(name) {
58
+ return name.startsWith('__');
59
+ }
60
+ /**
61
+ * Infer CleanTable[] from GraphQL introspection by recognizing PostGraphile patterns
62
+ *
63
+ * @param introspection - Standard GraphQL introspection response
64
+ * @param options - Optional configuration
65
+ * @returns Array of CleanTable objects compatible with existing generators
66
+ */
67
+ export function inferTablesFromIntrospection(introspection, options = {}) {
68
+ const { __schema: schema } = introspection;
69
+ const { types, queryType, mutationType } = schema;
70
+ // Build lookup maps for efficient access
71
+ const typeMap = buildTypeMap(types);
72
+ const queryFields = getTypeFields(typeMap.get(queryType.name));
73
+ const mutationFields = mutationType
74
+ ? getTypeFields(typeMap.get(mutationType.name))
75
+ : [];
76
+ // Step 1: Detect entity types by finding Connection types
77
+ const entityNames = detectEntityTypes(types);
78
+ // Step 2: Build CleanTable for each entity
79
+ const tables = [];
80
+ for (const entityName of entityNames) {
81
+ const entityType = typeMap.get(entityName);
82
+ if (!entityType)
83
+ continue;
84
+ // Infer all metadata for this entity
85
+ const { table, hasRealOperation } = buildCleanTable(entityName, entityType, typeMap, queryFields, mutationFields);
86
+ // Only include tables that have at least one real operation
87
+ if (hasRealOperation) {
88
+ tables.push(table);
89
+ }
90
+ }
91
+ return tables;
92
+ }
93
+ // ============================================================================
94
+ // Entity Detection
95
+ // ============================================================================
96
+ /**
97
+ * Detect entity types by finding Connection types in the schema
98
+ *
99
+ * PostGraphile generates a {PluralName}Connection type for each table.
100
+ * From this, we can derive the entity type name.
101
+ */
102
+ function detectEntityTypes(types) {
103
+ const entityNames = new Set();
104
+ const typeNames = new Set(types.map((t) => t.name));
105
+ for (const type of types) {
106
+ // Skip internal types
107
+ if (isInternalType(type.name))
108
+ continue;
109
+ if (BUILTIN_TYPES.has(type.name))
110
+ continue;
111
+ // Check for Connection pattern
112
+ const connectionMatch = type.name.match(PATTERNS.connection);
113
+ if (connectionMatch) {
114
+ const pluralName = connectionMatch[1]; // e.g., "Users" from "UsersConnection"
115
+ const singularName = singularize(pluralName); // e.g., "User"
116
+ // Verify the entity type actually exists
117
+ if (typeNames.has(singularName)) {
118
+ entityNames.add(singularName);
119
+ }
120
+ }
121
+ }
122
+ return entityNames;
123
+ }
124
+ /**
125
+ * Build a complete CleanTable from an entity type
126
+ */
127
+ function buildCleanTable(entityName, entityType, typeMap, queryFields, mutationFields) {
128
+ // Extract scalar fields from entity type
129
+ const fields = extractEntityFields(entityType, typeMap);
130
+ // Infer relations from entity fields
131
+ const relations = inferRelations(entityType, typeMap);
132
+ // Match query and mutation operations
133
+ const queryOps = matchQueryOperations(entityName, queryFields, typeMap);
134
+ const mutationOps = matchMutationOperations(entityName, mutationFields);
135
+ // Check if we found at least one real operation (not a fallback)
136
+ const hasRealOperation = !!(queryOps.all ||
137
+ queryOps.one ||
138
+ mutationOps.create ||
139
+ mutationOps.update ||
140
+ mutationOps.delete);
141
+ // Infer primary key from mutation inputs
142
+ const constraints = inferConstraints(entityName, typeMap);
143
+ // Build inflection map from discovered types
144
+ const inflection = buildInflection(entityName, typeMap);
145
+ // Combine query operations with fallbacks for UI purposes
146
+ // (but hasRealOperation indicates if we should include this table)
147
+ const query = {
148
+ all: queryOps.all ?? lcFirst(pluralize(entityName)),
149
+ one: queryOps.one ?? lcFirst(entityName),
150
+ create: mutationOps.create ?? `create${entityName}`,
151
+ update: mutationOps.update,
152
+ delete: mutationOps.delete,
153
+ };
154
+ return {
155
+ table: {
156
+ name: entityName,
157
+ fields,
158
+ relations,
159
+ inflection,
160
+ query,
161
+ constraints,
162
+ },
163
+ hasRealOperation,
164
+ };
165
+ }
166
+ // ============================================================================
167
+ // Field Extraction
168
+ // ============================================================================
169
+ /**
170
+ * Extract scalar fields from an entity type
171
+ * Excludes relation fields (those returning other entity types or connections)
172
+ */
173
+ function extractEntityFields(entityType, typeMap) {
174
+ const fields = [];
175
+ if (!entityType.fields)
176
+ return fields;
177
+ for (const field of entityType.fields) {
178
+ const baseTypeName = getBaseTypeName(field.type);
179
+ if (!baseTypeName)
180
+ continue;
181
+ // Skip relation fields (those returning other objects or connections)
182
+ const fieldType = typeMap.get(baseTypeName);
183
+ if (fieldType?.kind === 'OBJECT') {
184
+ // Check if it's a Connection type (hasMany) or entity type (belongsTo)
185
+ if (baseTypeName.endsWith('Connection') ||
186
+ isEntityType(baseTypeName, typeMap)) {
187
+ continue; // Skip relation fields
188
+ }
189
+ }
190
+ // Include scalar, enum, and other non-relation fields
191
+ fields.push({
192
+ name: field.name,
193
+ type: convertToCleanFieldType(field.type),
194
+ });
195
+ }
196
+ return fields;
197
+ }
198
+ /**
199
+ * Check if a type name is an entity type (has a corresponding Connection)
200
+ */
201
+ function isEntityType(typeName, typeMap) {
202
+ const connectionName = `${pluralize(typeName)}Connection`;
203
+ return typeMap.has(connectionName);
204
+ }
205
+ /**
206
+ * Convert IntrospectionTypeRef to CleanFieldType
207
+ */
208
+ function convertToCleanFieldType(typeRef) {
209
+ const baseType = unwrapType(typeRef);
210
+ const isArray = isList(typeRef);
211
+ return {
212
+ gqlType: baseType.name ?? 'Unknown',
213
+ isArray,
214
+ // PostgreSQL-specific fields are not available from introspection
215
+ // They were optional anyway and not used by generators
216
+ };
217
+ }
218
+ // ============================================================================
219
+ // Relation Inference
220
+ // ============================================================================
221
+ /**
222
+ * Infer relations from entity type fields
223
+ */
224
+ function inferRelations(entityType, typeMap) {
225
+ const belongsTo = [];
226
+ const hasMany = [];
227
+ const manyToMany = [];
228
+ if (!entityType.fields) {
229
+ return { belongsTo, hasOne: [], hasMany, manyToMany };
230
+ }
231
+ for (const field of entityType.fields) {
232
+ const baseTypeName = getBaseTypeName(field.type);
233
+ if (!baseTypeName)
234
+ continue;
235
+ // Check for Connection type → hasMany or manyToMany
236
+ if (baseTypeName.endsWith('Connection')) {
237
+ const relation = inferHasManyOrManyToMany(field, baseTypeName, typeMap);
238
+ if (relation.type === 'manyToMany') {
239
+ manyToMany.push(relation.relation);
240
+ }
241
+ else {
242
+ hasMany.push(relation.relation);
243
+ }
244
+ continue;
245
+ }
246
+ // Check for entity type → belongsTo
247
+ if (isEntityType(baseTypeName, typeMap)) {
248
+ belongsTo.push({
249
+ fieldName: field.name,
250
+ isUnique: false, // Can't determine from introspection alone
251
+ referencesTable: baseTypeName,
252
+ type: baseTypeName,
253
+ keys: [], // Would need FK info to populate
254
+ });
255
+ }
256
+ }
257
+ return { belongsTo, hasOne: [], hasMany, manyToMany };
258
+ }
259
+ /**
260
+ * Determine if a Connection field is hasMany or manyToMany
261
+ *
262
+ * ManyToMany pattern: field name contains "By" and "And"
263
+ * e.g., "productsByOrderItemOrderIdAndProductId"
264
+ */
265
+ function inferHasManyOrManyToMany(field, connectionTypeName, typeMap) {
266
+ // Extract the related entity name from Connection type
267
+ const match = connectionTypeName.match(PATTERNS.connection);
268
+ const relatedPluralName = match ? match[1] : connectionTypeName;
269
+ const relatedEntityName = singularize(relatedPluralName);
270
+ // Check for manyToMany pattern in field name
271
+ const isManyToMany = field.name.includes('By') && field.name.includes('And');
272
+ if (isManyToMany) {
273
+ // For ManyToMany, extract the actual entity name from the field name prefix
274
+ // Field name pattern: {relatedEntities}By{JunctionTable}{Keys}
275
+ // e.g., "usersByMembershipActorIdAndEntityId" → "users" → "User"
276
+ // e.g., "productsByOrderItemOrderIdAndProductId" → "products" → "Product"
277
+ const prefixMatch = field.name.match(/^([a-z]+)By/i);
278
+ const actualEntityName = prefixMatch
279
+ ? singularize(ucFirst(prefixMatch[1]))
280
+ : relatedEntityName;
281
+ // Try to extract junction table from field name
282
+ // Pattern: {relatedEntities}By{JunctionTable}{Keys}
283
+ // e.g., "productsByProductCategoryProductIdAndCategoryId" → "ProductCategory"
284
+ // The junction table name ends where the first field key begins (identified by capital letter after lowercase)
285
+ const junctionMatch = field.name.match(/By([A-Z][a-z]+(?:[A-Z][a-z]+)*?)(?:[A-Z][a-z]+Id)/);
286
+ const junctionTable = junctionMatch ? junctionMatch[1] : 'Unknown';
287
+ return {
288
+ type: 'manyToMany',
289
+ relation: {
290
+ fieldName: field.name,
291
+ rightTable: actualEntityName,
292
+ junctionTable,
293
+ type: connectionTypeName,
294
+ },
295
+ };
296
+ }
297
+ return {
298
+ type: 'hasMany',
299
+ relation: {
300
+ fieldName: field.name,
301
+ isUnique: false,
302
+ referencedByTable: relatedEntityName,
303
+ type: connectionTypeName,
304
+ keys: [],
305
+ },
306
+ };
307
+ }
308
+ /**
309
+ * Match query operations for an entity
310
+ *
311
+ * Looks for:
312
+ * - List query: returns {PluralName}Connection (e.g., users → UsersConnection)
313
+ * - Single query: returns {EntityName} with id/nodeId arg (e.g., user → User)
314
+ */
315
+ function matchQueryOperations(entityName, queryFields, typeMap) {
316
+ const pluralName = pluralize(entityName);
317
+ const connectionTypeName = `${pluralName}Connection`;
318
+ let all = null;
319
+ let one = null;
320
+ for (const field of queryFields) {
321
+ const returnTypeName = getBaseTypeName(field.type);
322
+ if (!returnTypeName)
323
+ continue;
324
+ // Match list query by return type (Connection)
325
+ if (returnTypeName === connectionTypeName) {
326
+ // Prefer the simple plural name, but accept any that returns the connection
327
+ if (!all || field.name === lcFirst(pluralName)) {
328
+ all = field.name;
329
+ }
330
+ }
331
+ // Match single query by return type (Entity) and having an id-like arg
332
+ if (returnTypeName === entityName) {
333
+ const hasIdArg = field.args.some((arg) => arg.name === 'id' ||
334
+ arg.name === 'nodeId' ||
335
+ arg.name.toLowerCase().endsWith('id'));
336
+ if (hasIdArg) {
337
+ // Prefer exact match (e.g., "user" for "User")
338
+ if (!one || field.name === lcFirst(entityName)) {
339
+ one = field.name;
340
+ }
341
+ }
342
+ }
343
+ }
344
+ return { all, one };
345
+ }
346
+ /**
347
+ * Match mutation operations for an entity
348
+ *
349
+ * Looks for mutations named:
350
+ * - create{EntityName}
351
+ * - update{EntityName} or update{EntityName}ById
352
+ * - delete{EntityName} or delete{EntityName}ById
353
+ */
354
+ function matchMutationOperations(entityName, mutationFields) {
355
+ let create = null;
356
+ let update = null;
357
+ let del = null;
358
+ const expectedCreate = `create${entityName}`;
359
+ const expectedUpdate = `update${entityName}`;
360
+ const expectedDelete = `delete${entityName}`;
361
+ for (const field of mutationFields) {
362
+ // Exact match for create
363
+ if (field.name === expectedCreate) {
364
+ create = field.name;
365
+ }
366
+ // Match update (could be updateUser or updateUserById)
367
+ if (field.name === expectedUpdate ||
368
+ field.name === `${expectedUpdate}ById`) {
369
+ // Prefer non-ById version
370
+ if (!update || field.name === expectedUpdate) {
371
+ update = field.name;
372
+ }
373
+ }
374
+ // Match delete (could be deleteUser or deleteUserById)
375
+ if (field.name === expectedDelete ||
376
+ field.name === `${expectedDelete}ById`) {
377
+ // Prefer non-ById version
378
+ if (!del || field.name === expectedDelete) {
379
+ del = field.name;
380
+ }
381
+ }
382
+ }
383
+ return { create, update, delete: del };
384
+ }
385
+ // ============================================================================
386
+ // Constraint Inference
387
+ // ============================================================================
388
+ /**
389
+ * Infer constraints from mutation input types
390
+ *
391
+ * Primary key can be inferred from Update/Delete mutation input types,
392
+ * which typically have an 'id' field or similar.
393
+ */
394
+ function inferConstraints(entityName, typeMap) {
395
+ const primaryKey = [];
396
+ // Try to find Update or Delete input type to extract PK
397
+ const updateInputName = `Update${entityName}Input`;
398
+ const deleteInputName = `Delete${entityName}Input`;
399
+ const updateInput = typeMap.get(updateInputName);
400
+ const deleteInput = typeMap.get(deleteInputName);
401
+ // Check update input for id field
402
+ const inputToCheck = updateInput || deleteInput;
403
+ if (inputToCheck?.inputFields) {
404
+ const idField = inputToCheck.inputFields.find((f) => f.name === 'id' || f.name === 'nodeId');
405
+ if (idField) {
406
+ primaryKey.push({
407
+ name: 'primary',
408
+ fields: [
409
+ {
410
+ name: idField.name,
411
+ type: convertToCleanFieldType(idField.type),
412
+ },
413
+ ],
414
+ });
415
+ }
416
+ }
417
+ // If no PK found from inputs, try to find 'id' field in entity type
418
+ if (primaryKey.length === 0) {
419
+ const entityType = typeMap.get(entityName);
420
+ if (entityType?.fields) {
421
+ const idField = entityType.fields.find((f) => f.name === 'id' || f.name === 'nodeId');
422
+ if (idField) {
423
+ primaryKey.push({
424
+ name: 'primary',
425
+ fields: [
426
+ {
427
+ name: idField.name,
428
+ type: convertToCleanFieldType(idField.type),
429
+ },
430
+ ],
431
+ });
432
+ }
433
+ }
434
+ }
435
+ return {
436
+ primaryKey,
437
+ foreignKey: [], // Would need FK info to populate
438
+ unique: [], // Would need constraint info to populate
439
+ };
440
+ }
441
+ // ============================================================================
442
+ // Inflection Building
443
+ // ============================================================================
444
+ /**
445
+ * Build inflection map from discovered types
446
+ */
447
+ function buildInflection(entityName, typeMap) {
448
+ const pluralName = pluralize(entityName);
449
+ const singularFieldName = lcFirst(entityName);
450
+ const pluralFieldName = lcFirst(pluralName);
451
+ // Check which types actually exist in the schema
452
+ const hasFilter = typeMap.has(`${entityName}Filter`);
453
+ const hasPatch = typeMap.has(`${entityName}Patch`);
454
+ const hasUpdatePayload = typeMap.has(`Update${entityName}Payload`);
455
+ // Detect the actual OrderBy type from schema
456
+ // PostGraphile typically generates {PluralName}OrderBy (e.g., AddressesOrderBy)
457
+ // but we check for the actual type in case of custom inflection
458
+ const expectedOrderByType = `${pluralName}OrderBy`;
459
+ const orderByType = findOrderByType(entityName, pluralName, typeMap) || expectedOrderByType;
460
+ return {
461
+ allRows: pluralFieldName,
462
+ allRowsSimple: pluralFieldName,
463
+ conditionType: `${entityName}Condition`,
464
+ connection: `${pluralName}Connection`,
465
+ createField: `create${entityName}`,
466
+ createInputType: `Create${entityName}Input`,
467
+ createPayloadType: `Create${entityName}Payload`,
468
+ deleteByPrimaryKey: `delete${entityName}`,
469
+ deletePayloadType: `Delete${entityName}Payload`,
470
+ edge: `${pluralName}Edge`,
471
+ edgeField: lcFirst(pluralName),
472
+ enumType: `${entityName}Enum`,
473
+ filterType: hasFilter ? `${entityName}Filter` : null,
474
+ inputType: `${entityName}Input`,
475
+ orderByType,
476
+ patchField: singularFieldName,
477
+ patchType: hasPatch ? `${entityName}Patch` : null,
478
+ tableFieldName: singularFieldName,
479
+ tableType: entityName,
480
+ typeName: entityName,
481
+ updateByPrimaryKey: `update${entityName}`,
482
+ updatePayloadType: hasUpdatePayload ? `Update${entityName}Payload` : null,
483
+ };
484
+ }
485
+ /**
486
+ * Find the actual OrderBy enum type for an entity from the schema
487
+ *
488
+ * PostGraphile generates OrderBy enums with various patterns:
489
+ * - {PluralName}OrderBy (e.g., AddressesOrderBy, UsersOrderBy)
490
+ * - Sometimes with custom inflection (e.g., SchemataOrderBy for Schema)
491
+ *
492
+ * We search for the actual type in the schema to handle all cases.
493
+ */
494
+ function findOrderByType(entityName, pluralName, typeMap) {
495
+ // Try the standard pattern first: {PluralName}OrderBy
496
+ const standardName = `${pluralName}OrderBy`;
497
+ if (typeMap.has(standardName)) {
498
+ return standardName;
499
+ }
500
+ // Build a list of candidate OrderBy names to check
501
+ // These are variations of the entity name with common plural suffixes
502
+ const candidates = [
503
+ `${entityName}sOrderBy`, // Simple 's' plural: User -> UsersOrderBy
504
+ `${entityName}esOrderBy`, // 'es' plural: Address -> AddressesOrderBy
505
+ `${entityName}OrderBy`, // No change (already plural or singular OK)
506
+ ];
507
+ // Check each candidate
508
+ for (const candidate of candidates) {
509
+ if (typeMap.has(candidate) && typeMap.get(candidate)?.kind === 'ENUM') {
510
+ return candidate;
511
+ }
512
+ }
513
+ // Fallback: search for an enum that EXACTLY matches the entity plural pattern
514
+ // We look for types that are the pluralized entity name + OrderBy
515
+ // This avoids matching SchemaGrantsOrderBy when looking for Schema's OrderBy
516
+ for (const [typeName, type] of typeMap) {
517
+ if (type.kind !== 'ENUM' || !typeName.endsWith('OrderBy'))
518
+ continue;
519
+ // Extract the base name (without OrderBy suffix)
520
+ const baseName = typeName.slice(0, -7); // Remove 'OrderBy'
521
+ // Check if singularizing the base name gives us the entity name
522
+ // e.g., 'Schemata' -> 'Schema', 'Users' -> 'User', 'Addresses' -> 'Address'
523
+ if (singularize(baseName) === entityName) {
524
+ return typeName;
525
+ }
526
+ }
527
+ return null;
528
+ }
529
+ // ============================================================================
530
+ // Utility Functions
531
+ // ============================================================================
532
+ /**
533
+ * Build a map of type name → IntrospectionType for efficient lookup
534
+ */
535
+ function buildTypeMap(types) {
536
+ const map = new Map();
537
+ for (const type of types) {
538
+ map.set(type.name, type);
539
+ }
540
+ return map;
541
+ }
542
+ /**
543
+ * Get fields from a type, returning empty array if null
544
+ */
545
+ function getTypeFields(type) {
546
+ return type?.fields ?? [];
547
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Endpoint Schema Source
3
+ *
4
+ * Fetches GraphQL schema via introspection from a live endpoint.
5
+ * Wraps the existing fetchSchema() function with the SchemaSource interface.
6
+ */
7
+ import type { SchemaSource, SchemaSourceResult } from './types';
8
+ export interface EndpointSchemaSourceOptions {
9
+ /**
10
+ * GraphQL endpoint URL
11
+ */
12
+ endpoint: string;
13
+ /**
14
+ * Optional authorization header value (e.g., "Bearer token")
15
+ */
16
+ authorization?: string;
17
+ /**
18
+ * Optional additional headers
19
+ */
20
+ headers?: Record<string, string>;
21
+ /**
22
+ * Request timeout in milliseconds (default: 30000)
23
+ */
24
+ timeout?: number;
25
+ }
26
+ /**
27
+ * Schema source that fetches from a live GraphQL endpoint
28
+ */
29
+ export declare class EndpointSchemaSource implements SchemaSource {
30
+ private readonly options;
31
+ constructor(options: EndpointSchemaSourceOptions);
32
+ fetch(): Promise<SchemaSourceResult>;
33
+ describe(): string;
34
+ }
@@ -0,0 +1,31 @@
1
+ import { SchemaSourceError } from './types';
2
+ import { fetchSchema } from '../fetch-schema';
3
+ /**
4
+ * Schema source that fetches from a live GraphQL endpoint
5
+ */
6
+ export class EndpointSchemaSource {
7
+ options;
8
+ constructor(options) {
9
+ this.options = options;
10
+ }
11
+ async fetch() {
12
+ const result = await fetchSchema({
13
+ endpoint: this.options.endpoint,
14
+ authorization: this.options.authorization,
15
+ headers: this.options.headers,
16
+ timeout: this.options.timeout,
17
+ });
18
+ if (!result.success) {
19
+ throw new SchemaSourceError(result.error ?? 'Unknown error fetching schema', this.describe());
20
+ }
21
+ if (!result.data) {
22
+ throw new SchemaSourceError('No introspection data returned', this.describe());
23
+ }
24
+ return {
25
+ introspection: result.data,
26
+ };
27
+ }
28
+ describe() {
29
+ return `endpoint: ${this.options.endpoint}`;
30
+ }
31
+ }
@@ -0,0 +1,20 @@
1
+ import type { SchemaSource, SchemaSourceResult } from './types';
2
+ export interface FileSchemaSourceOptions {
3
+ /**
4
+ * Path to the GraphQL schema file (.graphql)
5
+ * Can be absolute or relative to current working directory
6
+ */
7
+ schemaPath: string;
8
+ }
9
+ /**
10
+ * Schema source that loads from a local GraphQL schema file
11
+ *
12
+ * Supports .graphql files containing SDL (Schema Definition Language).
13
+ * The SDL is parsed using graphql-js and converted to introspection format.
14
+ */
15
+ export declare class FileSchemaSource implements SchemaSource {
16
+ private readonly options;
17
+ constructor(options: FileSchemaSourceOptions);
18
+ fetch(): Promise<SchemaSourceResult>;
19
+ describe(): string;
20
+ }