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