@constructive-io/graphql-codegen 2.20.1 → 2.22.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 (96) hide show
  1. package/README.md +15 -3
  2. package/cli/codegen/barrel.d.ts +4 -1
  3. package/cli/codegen/barrel.js +18 -12
  4. package/cli/codegen/client.js +33 -0
  5. package/cli/codegen/custom-mutations.d.ts +11 -1
  6. package/cli/codegen/custom-mutations.js +49 -15
  7. package/cli/codegen/custom-queries.d.ts +8 -0
  8. package/cli/codegen/custom-queries.js +82 -47
  9. package/cli/codegen/gql-ast.js +9 -5
  10. package/cli/codegen/index.js +39 -8
  11. package/cli/codegen/mutations.d.ts +14 -4
  12. package/cli/codegen/mutations.js +114 -28
  13. package/cli/codegen/orm/barrel.js +4 -2
  14. package/cli/codegen/orm/index.js +17 -0
  15. package/cli/codegen/orm/input-types-generator.js +83 -29
  16. package/cli/codegen/orm/model-generator.js +6 -4
  17. package/cli/codegen/queries.d.ts +7 -3
  18. package/cli/codegen/queries.js +185 -158
  19. package/cli/codegen/scalars.d.ts +6 -4
  20. package/cli/codegen/scalars.js +17 -9
  21. package/cli/codegen/schema-types-generator.d.ts +26 -0
  22. package/cli/codegen/schema-types-generator.js +365 -0
  23. package/cli/codegen/ts-ast.d.ts +3 -1
  24. package/cli/codegen/ts-ast.js +2 -2
  25. package/cli/codegen/type-resolver.d.ts +52 -6
  26. package/cli/codegen/type-resolver.js +97 -19
  27. package/cli/codegen/types.d.ts +7 -4
  28. package/cli/codegen/types.js +94 -41
  29. package/cli/codegen/utils.d.ts +20 -2
  30. package/cli/codegen/utils.js +32 -7
  31. package/cli/commands/generate-orm.js +5 -5
  32. package/cli/commands/generate.d.ts +4 -1
  33. package/cli/commands/generate.js +27 -8
  34. package/cli/introspect/transform-schema.d.ts +33 -21
  35. package/cli/introspect/transform-schema.js +31 -21
  36. package/esm/cli/codegen/barrel.d.ts +4 -1
  37. package/esm/cli/codegen/barrel.js +18 -12
  38. package/esm/cli/codegen/client.js +33 -0
  39. package/esm/cli/codegen/custom-mutations.d.ts +11 -1
  40. package/esm/cli/codegen/custom-mutations.js +50 -16
  41. package/esm/cli/codegen/custom-queries.d.ts +8 -0
  42. package/esm/cli/codegen/custom-queries.js +83 -48
  43. package/esm/cli/codegen/gql-ast.js +10 -6
  44. package/esm/cli/codegen/index.js +39 -8
  45. package/esm/cli/codegen/mutations.d.ts +14 -4
  46. package/esm/cli/codegen/mutations.js +115 -29
  47. package/esm/cli/codegen/orm/barrel.js +4 -2
  48. package/esm/cli/codegen/orm/index.js +17 -0
  49. package/esm/cli/codegen/orm/input-types-generator.js +83 -29
  50. package/esm/cli/codegen/orm/model-generator.js +7 -5
  51. package/esm/cli/codegen/queries.d.ts +7 -3
  52. package/esm/cli/codegen/queries.js +186 -159
  53. package/esm/cli/codegen/scalars.d.ts +6 -4
  54. package/esm/cli/codegen/scalars.js +16 -8
  55. package/esm/cli/codegen/schema-types-generator.d.ts +26 -0
  56. package/esm/cli/codegen/schema-types-generator.js +362 -0
  57. package/esm/cli/codegen/ts-ast.d.ts +3 -1
  58. package/esm/cli/codegen/ts-ast.js +2 -2
  59. package/esm/cli/codegen/type-resolver.d.ts +52 -6
  60. package/esm/cli/codegen/type-resolver.js +97 -20
  61. package/esm/cli/codegen/types.d.ts +7 -4
  62. package/esm/cli/codegen/types.js +95 -41
  63. package/esm/cli/codegen/utils.d.ts +20 -2
  64. package/esm/cli/codegen/utils.js +31 -7
  65. package/esm/cli/commands/generate-orm.js +5 -5
  66. package/esm/cli/commands/generate.d.ts +4 -1
  67. package/esm/cli/commands/generate.js +27 -8
  68. package/esm/cli/introspect/transform-schema.d.ts +33 -21
  69. package/esm/cli/introspect/transform-schema.js +31 -21
  70. package/esm/types/config.d.ts +16 -1
  71. package/esm/types/config.js +6 -0
  72. package/esm/types/schema.d.ts +2 -0
  73. package/package.json +8 -6
  74. package/types/config.d.ts +16 -1
  75. package/types/config.js +6 -0
  76. package/types/schema.d.ts +2 -0
  77. package/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  78. package/__tests__/codegen/input-types-generator.test.js +0 -635
  79. package/cli/codegen/filters.d.ts +0 -27
  80. package/cli/codegen/filters.js +0 -357
  81. package/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  82. package/cli/codegen/orm/input-types-generator.test.js +0 -75
  83. package/cli/codegen/orm/select-types.test.d.ts +0 -11
  84. package/cli/codegen/orm/select-types.test.js +0 -22
  85. package/cli/introspect/transform-schema.test.d.ts +0 -1
  86. package/cli/introspect/transform-schema.test.js +0 -67
  87. package/esm/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  88. package/esm/__tests__/codegen/input-types-generator.test.js +0 -633
  89. package/esm/cli/codegen/filters.d.ts +0 -27
  90. package/esm/cli/codegen/filters.js +0 -351
  91. package/esm/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  92. package/esm/cli/codegen/orm/input-types-generator.test.js +0 -73
  93. package/esm/cli/codegen/orm/select-types.test.d.ts +0 -11
  94. package/esm/cli/codegen/orm/select-types.test.js +0 -21
  95. package/esm/cli/introspect/transform-schema.test.d.ts +0 -1
  96. package/esm/cli/introspect/transform-schema.test.js +0 -65
@@ -1,633 +0,0 @@
1
- /**
2
- * Comprehensive tests for input-types-generator.ts
3
- *
4
- * Uses snapshot testing to validate generated TypeScript output.
5
- * These snapshots capture the current string-based output and will be
6
- * used to validate the AST-based migration produces equivalent results.
7
- */
8
- // Jest globals - no import needed
9
- import { generateInputTypesFile, collectInputTypeNames, collectPayloadTypeNames } from '../../cli/codegen/orm/input-types-generator';
10
- // ============================================================================
11
- // Test Fixtures - Field Types
12
- // ============================================================================
13
- const fieldTypes = {
14
- uuid: { gqlType: 'UUID', isArray: false },
15
- string: { gqlType: 'String', isArray: false },
16
- int: { gqlType: 'Int', isArray: false },
17
- float: { gqlType: 'Float', isArray: false },
18
- boolean: { gqlType: 'Boolean', isArray: false },
19
- datetime: { gqlType: 'Datetime', isArray: false },
20
- date: { gqlType: 'Date', isArray: false },
21
- json: { gqlType: 'JSON', isArray: false },
22
- bigint: { gqlType: 'BigInt', isArray: false },
23
- stringArray: { gqlType: 'String', isArray: true },
24
- intArray: { gqlType: 'Int', isArray: true },
25
- };
26
- // ============================================================================
27
- // Test Fixtures - Helper Functions
28
- // ============================================================================
29
- const emptyRelations = {
30
- belongsTo: [],
31
- hasOne: [],
32
- hasMany: [],
33
- manyToMany: [],
34
- };
35
- function createTable(partial) {
36
- return {
37
- name: partial.name,
38
- fields: partial.fields ?? [],
39
- relations: partial.relations ?? emptyRelations,
40
- query: partial.query,
41
- inflection: partial.inflection,
42
- constraints: partial.constraints,
43
- };
44
- }
45
- function createTypeRegistry(types) {
46
- return new Map(Object.entries(types));
47
- }
48
- function createTypeRef(kind, name, ofType) {
49
- return { kind, name, ofType };
50
- }
51
- function createNonNull(inner) {
52
- return { kind: 'NON_NULL', name: null, ofType: inner };
53
- }
54
- function createList(inner) {
55
- return { kind: 'LIST', name: null, ofType: inner };
56
- }
57
- // ============================================================================
58
- // Test Fixtures - Sample Tables
59
- // ============================================================================
60
- /**
61
- * Simple User table with basic scalar fields
62
- */
63
- const userTable = createTable({
64
- name: 'User',
65
- fields: [
66
- { name: 'id', type: fieldTypes.uuid },
67
- { name: 'email', type: fieldTypes.string },
68
- { name: 'name', type: fieldTypes.string },
69
- { name: 'age', type: fieldTypes.int },
70
- { name: 'isActive', type: fieldTypes.boolean },
71
- { name: 'createdAt', type: fieldTypes.datetime },
72
- { name: 'metadata', type: fieldTypes.json },
73
- ],
74
- query: {
75
- all: 'users',
76
- one: 'user',
77
- create: 'createUser',
78
- update: 'updateUser',
79
- delete: 'deleteUser',
80
- },
81
- });
82
- /**
83
- * Post table with belongsTo relation to User
84
- */
85
- const postTable = createTable({
86
- name: 'Post',
87
- fields: [
88
- { name: 'id', type: fieldTypes.uuid },
89
- { name: 'title', type: fieldTypes.string },
90
- { name: 'content', type: fieldTypes.string },
91
- { name: 'authorId', type: fieldTypes.uuid },
92
- { name: 'publishedAt', type: fieldTypes.datetime },
93
- { name: 'tags', type: fieldTypes.stringArray },
94
- ],
95
- relations: {
96
- belongsTo: [
97
- {
98
- fieldName: 'author',
99
- isUnique: false,
100
- referencesTable: 'User',
101
- type: null,
102
- keys: [{ name: 'authorId', type: fieldTypes.uuid }],
103
- },
104
- ],
105
- hasOne: [],
106
- hasMany: [
107
- {
108
- fieldName: 'comments',
109
- isUnique: false,
110
- referencedByTable: 'Comment',
111
- type: null,
112
- keys: [],
113
- },
114
- ],
115
- manyToMany: [],
116
- },
117
- query: {
118
- all: 'posts',
119
- one: 'post',
120
- create: 'createPost',
121
- update: 'updatePost',
122
- delete: 'deletePost',
123
- },
124
- });
125
- /**
126
- * Comment table with relations
127
- */
128
- const commentTable = createTable({
129
- name: 'Comment',
130
- fields: [
131
- { name: 'id', type: fieldTypes.uuid },
132
- { name: 'body', type: fieldTypes.string },
133
- { name: 'postId', type: fieldTypes.uuid },
134
- { name: 'authorId', type: fieldTypes.uuid },
135
- { name: 'createdAt', type: fieldTypes.datetime },
136
- ],
137
- relations: {
138
- belongsTo: [
139
- {
140
- fieldName: 'post',
141
- isUnique: false,
142
- referencesTable: 'Post',
143
- type: null,
144
- keys: [],
145
- },
146
- {
147
- fieldName: 'author',
148
- isUnique: false,
149
- referencesTable: 'User',
150
- type: null,
151
- keys: [],
152
- },
153
- ],
154
- hasOne: [],
155
- hasMany: [],
156
- manyToMany: [],
157
- },
158
- query: {
159
- all: 'comments',
160
- one: 'comment',
161
- create: 'createComment',
162
- update: 'updateComment',
163
- delete: 'deleteComment',
164
- },
165
- });
166
- /**
167
- * User table with hasMany to posts (update to include relation)
168
- */
169
- const userTableWithRelations = createTable({
170
- ...userTable,
171
- relations: {
172
- belongsTo: [],
173
- hasOne: [],
174
- hasMany: [
175
- {
176
- fieldName: 'posts',
177
- isUnique: false,
178
- referencedByTable: 'Post',
179
- type: null,
180
- keys: [],
181
- },
182
- {
183
- fieldName: 'comments',
184
- isUnique: false,
185
- referencedByTable: 'Comment',
186
- type: null,
187
- keys: [],
188
- },
189
- ],
190
- manyToMany: [],
191
- },
192
- });
193
- /**
194
- * Category table with manyToMany relation
195
- */
196
- const categoryTable = createTable({
197
- name: 'Category',
198
- fields: [
199
- { name: 'id', type: fieldTypes.uuid },
200
- { name: 'name', type: fieldTypes.string },
201
- { name: 'slug', type: fieldTypes.string },
202
- ],
203
- relations: {
204
- belongsTo: [],
205
- hasOne: [],
206
- hasMany: [],
207
- manyToMany: [
208
- {
209
- fieldName: 'posts',
210
- rightTable: 'Post',
211
- junctionTable: 'PostCategory',
212
- type: null,
213
- },
214
- ],
215
- },
216
- query: {
217
- all: 'categories',
218
- one: 'category',
219
- create: 'createCategory',
220
- update: 'updateCategory',
221
- delete: 'deleteCategory',
222
- },
223
- });
224
- /**
225
- * Profile table with hasOne relation
226
- */
227
- const profileTable = createTable({
228
- name: 'Profile',
229
- fields: [
230
- { name: 'id', type: fieldTypes.uuid },
231
- { name: 'bio', type: fieldTypes.string },
232
- { name: 'userId', type: fieldTypes.uuid },
233
- { name: 'avatarUrl', type: fieldTypes.string },
234
- ],
235
- relations: {
236
- belongsTo: [
237
- {
238
- fieldName: 'user',
239
- isUnique: true,
240
- referencesTable: 'User',
241
- type: null,
242
- keys: [],
243
- },
244
- ],
245
- hasOne: [],
246
- hasMany: [],
247
- manyToMany: [],
248
- },
249
- query: {
250
- all: 'profiles',
251
- one: 'profile',
252
- create: 'createProfile',
253
- update: 'updateProfile',
254
- delete: 'deleteProfile',
255
- },
256
- });
257
- // User with hasOne to profile
258
- const userTableWithProfile = createTable({
259
- ...userTable,
260
- relations: {
261
- belongsTo: [],
262
- hasOne: [
263
- {
264
- fieldName: 'profile',
265
- isUnique: true,
266
- referencedByTable: 'Profile',
267
- type: null,
268
- keys: [],
269
- },
270
- ],
271
- hasMany: [
272
- {
273
- fieldName: 'posts',
274
- isUnique: false,
275
- referencedByTable: 'Post',
276
- type: null,
277
- keys: [],
278
- },
279
- ],
280
- manyToMany: [],
281
- },
282
- });
283
- // ============================================================================
284
- // Test Fixtures - Sample TypeRegistry (for custom operations)
285
- // ============================================================================
286
- const sampleTypeRegistry = createTypeRegistry({
287
- LoginInput: {
288
- kind: 'INPUT_OBJECT',
289
- name: 'LoginInput',
290
- inputFields: [
291
- { name: 'email', type: createNonNull(createTypeRef('SCALAR', 'String')) },
292
- { name: 'password', type: createNonNull(createTypeRef('SCALAR', 'String')) },
293
- { name: 'rememberMe', type: createTypeRef('SCALAR', 'Boolean') },
294
- ],
295
- },
296
- RegisterInput: {
297
- kind: 'INPUT_OBJECT',
298
- name: 'RegisterInput',
299
- inputFields: [
300
- { name: 'email', type: createNonNull(createTypeRef('SCALAR', 'String')) },
301
- { name: 'password', type: createNonNull(createTypeRef('SCALAR', 'String')) },
302
- { name: 'name', type: createTypeRef('SCALAR', 'String') },
303
- ],
304
- },
305
- UserRole: {
306
- kind: 'ENUM',
307
- name: 'UserRole',
308
- enumValues: ['ADMIN', 'USER', 'GUEST'],
309
- },
310
- LoginPayload: {
311
- kind: 'OBJECT',
312
- name: 'LoginPayload',
313
- fields: [
314
- { name: 'token', type: createTypeRef('SCALAR', 'String') },
315
- { name: 'user', type: createTypeRef('OBJECT', 'User') },
316
- { name: 'expiresAt', type: createTypeRef('SCALAR', 'Datetime') },
317
- ],
318
- },
319
- });
320
- // ============================================================================
321
- // Tests - Full File Generation
322
- // ============================================================================
323
- describe('generateInputTypesFile', () => {
324
- it('generates complete types file for single table', () => {
325
- const result = generateInputTypesFile(new Map(), new Set(), [userTable]);
326
- expect(result.content).toMatchSnapshot();
327
- });
328
- it('generates complete types file for multiple tables with relations', () => {
329
- const tables = [userTableWithRelations, postTable, commentTable];
330
- const result = generateInputTypesFile(new Map(), new Set(), tables);
331
- expect(result.content).toMatchSnapshot();
332
- });
333
- it('generates types with hasOne relations', () => {
334
- const tables = [userTableWithProfile, profileTable];
335
- const result = generateInputTypesFile(new Map(), new Set(), tables);
336
- expect(result.content).toMatchSnapshot();
337
- });
338
- it('generates types with manyToMany relations', () => {
339
- const tables = [postTable, categoryTable];
340
- const result = generateInputTypesFile(new Map(), new Set(), tables);
341
- expect(result.content).toMatchSnapshot();
342
- });
343
- it('generates custom input types from TypeRegistry', () => {
344
- const usedInputTypes = new Set(['LoginInput', 'RegisterInput', 'UserRole']);
345
- const result = generateInputTypesFile(sampleTypeRegistry, usedInputTypes, [userTable]);
346
- expect(result.content).toMatchSnapshot();
347
- });
348
- it('generates payload types for custom operations', () => {
349
- const usedInputTypes = new Set(['LoginInput']);
350
- const usedPayloadTypes = new Set(['LoginPayload']);
351
- const result = generateInputTypesFile(sampleTypeRegistry, usedInputTypes, [userTable], usedPayloadTypes);
352
- expect(result.content).toMatchSnapshot();
353
- });
354
- it('handles empty tables array', () => {
355
- const result = generateInputTypesFile(new Map(), new Set());
356
- expect(result.content).toMatchSnapshot();
357
- });
358
- });
359
- // ============================================================================
360
- // Tests - Scalar Filter Types
361
- // ============================================================================
362
- describe('scalar filter types', () => {
363
- it('includes all standard scalar filter interfaces', () => {
364
- const result = generateInputTypesFile(new Map(), new Set(), [userTable]);
365
- // String filters
366
- expect(result.content).toContain('export interface StringFilter {');
367
- expect(result.content).toContain('equalTo?: string;');
368
- expect(result.content).toContain('includes?: string;');
369
- expect(result.content).toContain('likeInsensitive?: string;');
370
- // Int filters
371
- expect(result.content).toContain('export interface IntFilter {');
372
- expect(result.content).toContain('lessThan?: number;');
373
- expect(result.content).toContain('greaterThanOrEqualTo?: number;');
374
- // UUID filters
375
- expect(result.content).toContain('export interface UUIDFilter {');
376
- // Datetime filters
377
- expect(result.content).toContain('export interface DatetimeFilter {');
378
- // JSON filters
379
- expect(result.content).toContain('export interface JSONFilter {');
380
- expect(result.content).toContain('containsKey?: string;');
381
- expect(result.content).toContain('containsAllKeys?: string[];');
382
- // Boolean filters
383
- expect(result.content).toContain('export interface BooleanFilter {');
384
- // BigInt filters
385
- expect(result.content).toContain('export interface BigIntFilter {');
386
- // Float filters
387
- expect(result.content).toContain('export interface FloatFilter {');
388
- });
389
- });
390
- // ============================================================================
391
- // Tests - Entity Types
392
- // ============================================================================
393
- describe('entity types', () => {
394
- it('generates entity interface with correct field types', () => {
395
- const result = generateInputTypesFile(new Map(), new Set(), [userTable]);
396
- expect(result.content).toContain('export interface User {');
397
- expect(result.content).toContain('id: string;'); // UUID -> string
398
- expect(result.content).toContain('email?: string | null;');
399
- expect(result.content).toContain('age?: number | null;');
400
- expect(result.content).toContain('isActive?: boolean | null;');
401
- expect(result.content).toContain('metadata?: Record<string, unknown> | null;');
402
- });
403
- it('generates entity relations interface', () => {
404
- const result = generateInputTypesFile(new Map(), new Set(), [userTableWithRelations, postTable]);
405
- expect(result.content).toContain('export interface UserRelations {');
406
- expect(result.content).toContain('posts?: ConnectionResult<Post>;');
407
- });
408
- it('generates WithRelations type alias', () => {
409
- const result = generateInputTypesFile(new Map(), new Set(), [userTableWithRelations]);
410
- expect(result.content).toContain('export type UserWithRelations = User & UserRelations;');
411
- });
412
- });
413
- // ============================================================================
414
- // Tests - Select Types
415
- // ============================================================================
416
- describe('entity select types', () => {
417
- it('generates select type with scalar fields', () => {
418
- const result = generateInputTypesFile(new Map(), new Set(), [userTable]);
419
- expect(result.content).toContain('export type UserSelect = {');
420
- expect(result.content).toContain('id?: boolean;');
421
- expect(result.content).toContain('email?: boolean;');
422
- expect(result.content).toContain('name?: boolean;');
423
- });
424
- it('generates select type with belongsTo relation options', () => {
425
- const result = generateInputTypesFile(new Map(), new Set(), [postTable, userTable]);
426
- expect(result.content).toContain('export type PostSelect = {');
427
- expect(result.content).toContain('author?: boolean | { select?: UserSelect };');
428
- });
429
- it('generates select type with hasMany relation options', () => {
430
- const result = generateInputTypesFile(new Map(), new Set(), [userTableWithRelations, postTable]);
431
- expect(result.content).toContain('posts?: boolean | {');
432
- expect(result.content).toContain('select?: PostSelect;');
433
- expect(result.content).toContain('first?: number;');
434
- expect(result.content).toContain('filter?: PostFilter;');
435
- expect(result.content).toContain('orderBy?: PostsOrderBy[];');
436
- });
437
- it('generates select type with manyToMany relation options', () => {
438
- const result = generateInputTypesFile(new Map(), new Set(), [categoryTable, postTable]);
439
- expect(result.content).toContain('export type CategorySelect = {');
440
- expect(result.content).toContain('posts?: boolean | {');
441
- expect(result.content).toContain('select?: PostSelect;');
442
- });
443
- });
444
- // ============================================================================
445
- // Tests - Table Filter Types
446
- // ============================================================================
447
- describe('table filter types', () => {
448
- it('generates filter type for table', () => {
449
- const result = generateInputTypesFile(new Map(), new Set(), [userTable]);
450
- expect(result.content).toContain('export interface UserFilter {');
451
- expect(result.content).toContain('id?: UUIDFilter;');
452
- expect(result.content).toContain('email?: StringFilter;');
453
- expect(result.content).toContain('age?: IntFilter;');
454
- expect(result.content).toContain('isActive?: BooleanFilter;');
455
- expect(result.content).toContain('createdAt?: DatetimeFilter;');
456
- expect(result.content).toContain('metadata?: JSONFilter;');
457
- // Logical operators
458
- expect(result.content).toContain('and?: UserFilter[];');
459
- expect(result.content).toContain('or?: UserFilter[];');
460
- expect(result.content).toContain('not?: UserFilter;');
461
- });
462
- });
463
- // ============================================================================
464
- // Tests - OrderBy Types
465
- // ============================================================================
466
- describe('orderBy types', () => {
467
- it('generates orderBy type for table', () => {
468
- const result = generateInputTypesFile(new Map(), new Set(), [userTable]);
469
- expect(result.content).toContain('export type UsersOrderBy =');
470
- expect(result.content).toContain("'PRIMARY_KEY_ASC'");
471
- expect(result.content).toContain("'PRIMARY_KEY_DESC'");
472
- expect(result.content).toContain("'NATURAL'");
473
- expect(result.content).toContain("'ID_ASC'");
474
- expect(result.content).toContain("'ID_DESC'");
475
- expect(result.content).toContain("'EMAIL_ASC'");
476
- expect(result.content).toContain("'NAME_DESC'");
477
- });
478
- });
479
- // ============================================================================
480
- // Tests - CRUD Input Types
481
- // ============================================================================
482
- describe('CRUD input types', () => {
483
- it('generates create input type', () => {
484
- const result = generateInputTypesFile(new Map(), new Set(), [userTable]);
485
- expect(result.content).toContain('export interface CreateUserInput {');
486
- expect(result.content).toContain('clientMutationId?: string;');
487
- expect(result.content).toContain('user: {');
488
- expect(result.content).toContain('email?: string;');
489
- expect(result.content).toContain('name?: string;');
490
- // Should not include auto-generated fields
491
- expect(result.content).not.toMatch(/user:\s*\{[^}]*\bid\b/);
492
- });
493
- it('generates update input type with patch', () => {
494
- const result = generateInputTypesFile(new Map(), new Set(), [userTable]);
495
- expect(result.content).toContain('export interface UserPatch {');
496
- expect(result.content).toContain('email?: string | null;');
497
- expect(result.content).toContain('name?: string | null;');
498
- expect(result.content).toContain('export interface UpdateUserInput {');
499
- expect(result.content).toContain('id: string;');
500
- expect(result.content).toContain('patch: UserPatch;');
501
- });
502
- it('generates delete input type', () => {
503
- const result = generateInputTypesFile(new Map(), new Set(), [userTable]);
504
- expect(result.content).toContain('export interface DeleteUserInput {');
505
- expect(result.content).toContain('clientMutationId?: string;');
506
- expect(result.content).toContain('id: string;');
507
- });
508
- });
509
- // ============================================================================
510
- // Tests - Custom Input Types (from TypeRegistry)
511
- // ============================================================================
512
- describe('custom input types', () => {
513
- it('generates input types from TypeRegistry', () => {
514
- const usedInputTypes = new Set(['LoginInput', 'RegisterInput']);
515
- const result = generateInputTypesFile(sampleTypeRegistry, usedInputTypes, []);
516
- expect(result.content).toContain('export interface LoginInput {');
517
- expect(result.content).toContain('email: string;'); // Non-null
518
- expect(result.content).toContain('password: string;'); // Non-null
519
- expect(result.content).toContain('rememberMe?: boolean;'); // Optional
520
- });
521
- it('generates enum types from TypeRegistry', () => {
522
- const usedInputTypes = new Set(['UserRole']);
523
- const result = generateInputTypesFile(sampleTypeRegistry, usedInputTypes, []);
524
- expect(result.content).toContain("export type UserRole = 'ADMIN' | 'USER' | 'GUEST';");
525
- });
526
- });
527
- // ============================================================================
528
- // Tests - Payload/Return Types
529
- // ============================================================================
530
- describe('payload types', () => {
531
- it('generates payload types for custom operations', () => {
532
- const usedInputTypes = new Set();
533
- const usedPayloadTypes = new Set(['LoginPayload']);
534
- const result = generateInputTypesFile(sampleTypeRegistry, usedInputTypes, [], usedPayloadTypes);
535
- expect(result.content).toContain('export interface LoginPayload {');
536
- expect(result.content).toContain('token?: string | null;');
537
- expect(result.content).toContain('expiresAt?: string | null;');
538
- });
539
- it('generates Select types for payload types', () => {
540
- const usedInputTypes = new Set();
541
- const usedPayloadTypes = new Set(['LoginPayload']);
542
- const result = generateInputTypesFile(sampleTypeRegistry, usedInputTypes, [], usedPayloadTypes);
543
- expect(result.content).toContain('export type LoginPayloadSelect = {');
544
- expect(result.content).toContain('token?: boolean;');
545
- expect(result.content).toContain('expiresAt?: boolean;');
546
- });
547
- });
548
- // ============================================================================
549
- // Tests - Helper Functions
550
- // ============================================================================
551
- describe('collectInputTypeNames', () => {
552
- it('collects Input type names from operations', () => {
553
- const operations = [
554
- {
555
- args: [
556
- { name: 'input', type: createNonNull(createTypeRef('INPUT_OBJECT', 'LoginInput')) },
557
- ],
558
- },
559
- {
560
- args: [
561
- { name: 'data', type: createTypeRef('INPUT_OBJECT', 'RegisterInput') },
562
- ],
563
- },
564
- ];
565
- const result = collectInputTypeNames(operations);
566
- expect(result.has('LoginInput')).toBe(true);
567
- expect(result.has('RegisterInput')).toBe(true);
568
- });
569
- it('collects Filter type names', () => {
570
- const operations = [
571
- {
572
- args: [
573
- { name: 'filter', type: createTypeRef('INPUT_OBJECT', 'UserFilter') },
574
- ],
575
- },
576
- ];
577
- const result = collectInputTypeNames(operations);
578
- expect(result.has('UserFilter')).toBe(true);
579
- });
580
- });
581
- describe('collectPayloadTypeNames', () => {
582
- it('collects Payload type names from operations', () => {
583
- const operations = [
584
- { returnType: createTypeRef('OBJECT', 'LoginPayload') },
585
- { returnType: createTypeRef('OBJECT', 'RegisterPayload') },
586
- ];
587
- const result = collectPayloadTypeNames(operations);
588
- expect(result.has('LoginPayload')).toBe(true);
589
- expect(result.has('RegisterPayload')).toBe(true);
590
- });
591
- it('excludes Connection types', () => {
592
- const operations = [
593
- { returnType: createTypeRef('OBJECT', 'UsersConnection') },
594
- ];
595
- const result = collectPayloadTypeNames(operations);
596
- expect(result.has('UsersConnection')).toBe(false);
597
- });
598
- });
599
- // ============================================================================
600
- // Tests - Edge Cases
601
- // ============================================================================
602
- describe('edge cases', () => {
603
- it('handles table with no fields', () => {
604
- const emptyTable = createTable({ name: 'Empty', fields: [] });
605
- const result = generateInputTypesFile(new Map(), new Set(), [emptyTable]);
606
- expect(result.content).toContain('export interface Empty {');
607
- expect(result.content).toContain('export interface EmptyFilter {');
608
- });
609
- it('handles table with only id field', () => {
610
- const minimalTable = createTable({
611
- name: 'Minimal',
612
- fields: [{ name: 'id', type: fieldTypes.uuid }],
613
- });
614
- const result = generateInputTypesFile(new Map(), new Set(), [minimalTable]);
615
- expect(result.content).toContain('export interface Minimal {');
616
- expect(result.content).toContain('id: string;');
617
- });
618
- it('handles circular relations gracefully', () => {
619
- // User has posts, Post has author (User)
620
- const tables = [userTableWithRelations, postTable];
621
- const result = generateInputTypesFile(new Map(), new Set(), tables);
622
- // Should not cause infinite loops or errors
623
- expect(result.content).toContain('export type UserSelect = {');
624
- expect(result.content).toContain('export type PostSelect = {');
625
- });
626
- it('handles unknown input type gracefully', () => {
627
- const usedInputTypes = new Set(['UnknownType']);
628
- const result = generateInputTypesFile(new Map(), usedInputTypes, []);
629
- // Should generate a fallback type
630
- expect(result.content).toContain("// Type 'UnknownType' not found in schema");
631
- expect(result.content).toContain('export type UnknownType = Record<string, unknown>;');
632
- });
633
- });
@@ -1,27 +0,0 @@
1
- /**
2
- * PostGraphile filter type generators
3
- *
4
- * Generates TypeScript interfaces for PostGraphile filter types:
5
- * - Base scalar filters (StringFilter, IntFilter, etc.)
6
- * - Table-specific filters (CarFilter, UserFilter, etc.)
7
- * - OrderBy enum types
8
- */
9
- import type { CleanTable } from '../../types/schema';
10
- /**
11
- * Generate all base PostGraphile filter types
12
- * These are shared across all tables
13
- */
14
- export declare function generateBaseFilterTypes(): string;
15
- /**
16
- * Generate filter interface for a specific table
17
- */
18
- export declare function generateTableFilter(table: CleanTable): string;
19
- /**
20
- * Generate OrderBy type for a specific table
21
- */
22
- export declare function generateTableOrderBy(table: CleanTable): string;
23
- /**
24
- * Generate inline filter and orderBy types for a query hook file
25
- * These are embedded directly in the hook file for self-containment
26
- */
27
- export declare function generateInlineFilterTypes(table: CleanTable): string;