@constructive-io/graphql-codegen 3.3.1 → 4.0.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 (67) hide show
  1. package/README.md +0 -4
  2. package/core/ast.js +6 -5
  3. package/core/codegen/custom-mutations.js +22 -22
  4. package/core/codegen/custom-queries.js +24 -17
  5. package/core/codegen/hooks-ast.d.ts +1 -1
  6. package/core/codegen/hooks-ast.js +22 -5
  7. package/core/codegen/index.d.ts +1 -1
  8. package/core/codegen/mutations.js +16 -12
  9. package/core/codegen/orm/custom-ops-generator.js +37 -3
  10. package/core/codegen/orm/input-types-generator.js +161 -89
  11. package/core/codegen/orm/model-generator.js +72 -73
  12. package/core/codegen/orm/select-types.d.ts +27 -17
  13. package/core/codegen/queries.js +37 -25
  14. package/core/codegen/schema-types-generator.js +21 -0
  15. package/core/codegen/templates/hooks-selection.ts +12 -0
  16. package/core/codegen/templates/query-builder.ts +103 -59
  17. package/core/codegen/templates/select-types.ts +59 -33
  18. package/core/codegen/types.js +26 -0
  19. package/core/codegen/utils.d.ts +1 -1
  20. package/core/codegen/utils.js +1 -1
  21. package/core/custom-ast.js +9 -8
  22. package/core/database/index.js +2 -3
  23. package/core/index.d.ts +2 -0
  24. package/core/index.js +2 -0
  25. package/core/introspect/infer-tables.js +144 -58
  26. package/core/introspect/transform-schema.d.ts +1 -1
  27. package/core/introspect/transform-schema.js +3 -1
  28. package/esm/core/ast.js +6 -5
  29. package/esm/core/codegen/custom-mutations.js +23 -23
  30. package/esm/core/codegen/custom-queries.js +25 -18
  31. package/esm/core/codegen/hooks-ast.d.ts +1 -1
  32. package/esm/core/codegen/hooks-ast.js +22 -5
  33. package/esm/core/codegen/index.d.ts +1 -1
  34. package/esm/core/codegen/mutations.js +16 -12
  35. package/esm/core/codegen/orm/custom-ops-generator.js +38 -4
  36. package/esm/core/codegen/orm/input-types-generator.js +163 -91
  37. package/esm/core/codegen/orm/model-generator.js +73 -74
  38. package/esm/core/codegen/orm/select-types.d.ts +27 -17
  39. package/esm/core/codegen/queries.js +37 -25
  40. package/esm/core/codegen/schema-types-generator.js +21 -0
  41. package/esm/core/codegen/types.js +26 -0
  42. package/esm/core/codegen/utils.d.ts +1 -1
  43. package/esm/core/codegen/utils.js +1 -1
  44. package/esm/core/custom-ast.js +9 -8
  45. package/esm/core/database/index.js +2 -3
  46. package/esm/core/index.d.ts +2 -0
  47. package/esm/core/index.js +2 -0
  48. package/esm/core/introspect/infer-tables.js +144 -58
  49. package/esm/core/introspect/transform-schema.d.ts +1 -1
  50. package/esm/core/introspect/transform-schema.js +3 -1
  51. package/esm/generators/field-selector.js +1 -0
  52. package/esm/generators/index.d.ts +3 -0
  53. package/esm/generators/index.js +3 -0
  54. package/esm/generators/mutations.js +4 -4
  55. package/esm/generators/select.js +4 -4
  56. package/esm/index.d.ts +1 -1
  57. package/esm/index.js +1 -1
  58. package/esm/types/schema.d.ts +5 -3
  59. package/generators/field-selector.js +1 -0
  60. package/generators/index.d.ts +3 -0
  61. package/generators/index.js +3 -0
  62. package/generators/mutations.js +3 -3
  63. package/generators/select.js +3 -3
  64. package/index.d.ts +1 -1
  65. package/index.js +1 -1
  66. package/package.json +11 -11
  67. package/types/schema.d.ts +5 -3
@@ -6,14 +6,14 @@
6
6
  */
7
7
  import * as t from '@babel/types';
8
8
  import { generateCode } from '../babel-ast';
9
- import { getFilterTypeName, getGeneratedFileHeader, getOrderByTypeName, getPrimaryKeyInfo, getTableNames, hasValidPrimaryKey, lcFirst, } from '../utils';
9
+ import { getFilterTypeName, getGeneratedFileHeader, getOrderByTypeName, getPrimaryKeyInfo, getSingleRowQueryName, getTableNames, hasValidPrimaryKey, lcFirst, } from '../utils';
10
10
  function createImportDeclaration(moduleSpecifier, namedImports, typeOnly = false) {
11
11
  const specifiers = namedImports.map((name) => t.importSpecifier(t.identifier(name), t.identifier(name)));
12
12
  const decl = t.importDeclaration(specifiers, t.stringLiteral(moduleSpecifier));
13
13
  decl.importKind = typeOnly ? 'type' : 'value';
14
14
  return decl;
15
15
  }
16
- function buildMethodBody(builderFn, args, operation, typeName, fieldName) {
16
+ function buildMethodBody(builderFn, args, operation, typeName, fieldName, extraProps = []) {
17
17
  const destructureDecl = t.variableDeclaration('const', [
18
18
  t.variableDeclarator(t.objectPattern([
19
19
  t.objectProperty(t.identifier('document'), t.identifier('document'), false, true),
@@ -28,6 +28,7 @@ function buildMethodBody(builderFn, args, operation, typeName, fieldName) {
28
28
  t.objectProperty(t.identifier('fieldName'), t.stringLiteral(fieldName)),
29
29
  t.objectProperty(t.identifier('document'), t.identifier('document'), false, true),
30
30
  t.objectProperty(t.identifier('variables'), t.identifier('variables'), false, true),
31
+ ...extraProps,
31
32
  ]),
32
33
  ]));
33
34
  return [destructureDecl, returnStmt];
@@ -38,10 +39,6 @@ function createClassMethod(name, typeParameters, params, returnType, body) {
38
39
  method.returnType = returnType;
39
40
  return method;
40
41
  }
41
- function createDeclareMethod(name, typeParameters, params, returnType) {
42
- const method = t.tsDeclareMethod(null, t.identifier(name), typeParameters, params, returnType);
43
- return method;
44
- }
45
42
  function createTypeParam(constraintTypeName, defaultType) {
46
43
  const param = t.tsTypeParameter(t.tsTypeReference(t.identifier(constraintTypeName)), defaultType ?? null, 'S');
47
44
  return t.tsTypeParameterDeclaration([param]);
@@ -85,6 +82,8 @@ export function generateModelFile(table, _useSharedTypes) {
85
82
  const pkFields = getPrimaryKeyInfo(table);
86
83
  const pkField = pkFields[0];
87
84
  const pluralQueryName = table.query?.all ?? pluralName;
85
+ const singleQueryName = table.query?.one;
86
+ const singleResultFieldName = getSingleRowQueryName(table);
88
87
  const createMutationName = table.query?.create ?? `create${typeName}`;
89
88
  const updateMutationName = table.query?.update;
90
89
  const deleteMutationName = table.query?.delete;
@@ -129,7 +128,6 @@ export function generateModelFile(table, _useSharedTypes) {
129
128
  classBody.push(t.classMethod('constructor', t.identifier('constructor'), [paramProp], t.blockStatement([])));
130
129
  // Reusable type reference factories
131
130
  const sRef = () => t.tsTypeReference(t.identifier('S'));
132
- const selectRef = () => t.tsTypeReference(t.identifier(selectTypeName));
133
131
  const pkTsType = () => tsTypeFromPrimitive(pkField.tsType);
134
132
  // ── findMany ───────────────────────────────────────────────────────────
135
133
  {
@@ -148,17 +146,12 @@ export function generateModelFile(table, _useSharedTypes) {
148
146
  ])))),
149
147
  ]),
150
148
  ])));
151
- // Overload 1: with select (autocompletion)
152
- const o1Param = t.identifier('args');
153
- o1Param.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
149
+ const implParam = t.identifier('args');
150
+ implParam.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
154
151
  argsType(sRef()),
155
152
  t.tsTypeLiteral([requiredSelectProp()]),
156
153
  strictSelectGuard(selectTypeName),
157
154
  ]));
158
- classBody.push(createDeclareMethod('findMany', createTypeParam(selectTypeName), [o1Param], retType(sRef())));
159
- // Implementation
160
- const implParam = t.identifier('args');
161
- implParam.typeAnnotation = t.tsTypeAnnotation(argsType(selectRef()));
162
155
  const selectExpr = t.memberExpression(t.identifier('args'), t.identifier('select'));
163
156
  const bodyArgs = [
164
157
  t.stringLiteral(typeName),
@@ -180,7 +173,7 @@ export function generateModelFile(table, _useSharedTypes) {
180
173
  t.stringLiteral(orderByTypeName),
181
174
  t.identifier('connectionFieldsMap'),
182
175
  ];
183
- classBody.push(createClassMethod('findMany', null, [implParam], null, buildMethodBody('buildFindManyDocument', bodyArgs, 'query', typeName, pluralQueryName)));
176
+ classBody.push(createClassMethod('findMany', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildFindManyDocument', bodyArgs, 'query', typeName, pluralQueryName)));
184
177
  }
185
178
  // ── findFirst ──────────────────────────────────────────────────────────
186
179
  {
@@ -198,17 +191,12 @@ export function generateModelFile(table, _useSharedTypes) {
198
191
  ]))),
199
192
  ]),
200
193
  ])));
201
- // Overload 1: with select (autocompletion)
202
- const o1Param = t.identifier('args');
203
- o1Param.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
194
+ const implParam = t.identifier('args');
195
+ implParam.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
204
196
  argsType(sRef()),
205
197
  t.tsTypeLiteral([requiredSelectProp()]),
206
198
  strictSelectGuard(selectTypeName),
207
199
  ]));
208
- classBody.push(createDeclareMethod('findFirst', createTypeParam(selectTypeName), [o1Param], retType(sRef())));
209
- // Implementation
210
- const implParam = t.identifier('args');
211
- implParam.typeAnnotation = t.tsTypeAnnotation(argsType(selectRef()));
212
200
  const selectExpr = t.memberExpression(t.identifier('args'), t.identifier('select'));
213
201
  const bodyArgs = [
214
202
  t.stringLiteral(typeName),
@@ -220,15 +208,13 @@ export function generateModelFile(table, _useSharedTypes) {
220
208
  t.stringLiteral(whereTypeName),
221
209
  t.identifier('connectionFieldsMap'),
222
210
  ];
223
- classBody.push(createClassMethod('findFirst', null, [implParam], null, buildMethodBody('buildFindFirstDocument', bodyArgs, 'query', typeName, pluralQueryName)));
211
+ classBody.push(createClassMethod('findFirst', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildFindFirstDocument', bodyArgs, 'query', typeName, pluralQueryName)));
224
212
  }
225
213
  // ── findOne ────────────────────────────────────────────────────────────
226
- const singleQueryName = table.query?.one;
227
- if (singleQueryName && hasValidPrimaryKey(table)) {
228
- const pkGqlType = pkField.gqlType.replace(/!/g, '') + '!';
214
+ if (hasValidPrimaryKey(table)) {
229
215
  const retType = (sel) => t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
230
216
  t.tsTypeLiteral([
231
- t.tsPropertySignature(t.identifier(singleQueryName), t.tsTypeAnnotation(t.tsUnionType([
217
+ t.tsPropertySignature(t.identifier(singleResultFieldName), t.tsTypeAnnotation(t.tsUnionType([
232
218
  t.tsTypeReference(t.identifier('InferSelectResult'), t.tsTypeParameterInstantiation([
233
219
  t.tsTypeReference(t.identifier(relationTypeName)),
234
220
  sel,
@@ -242,33 +228,59 @@ export function generateModelFile(table, _useSharedTypes) {
242
228
  prop.optional = false;
243
229
  return prop;
244
230
  };
245
- // Overload 1: with select (autocompletion)
246
- const o1Param = t.identifier('args');
247
- o1Param.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
231
+ const implParam = t.identifier('args');
232
+ implParam.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
248
233
  t.tsTypeLiteral([pkProp(), requiredSelectProp()]),
249
234
  strictSelectGuard(selectTypeName),
250
235
  ]));
251
- classBody.push(createDeclareMethod('findOne', createTypeParam(selectTypeName), [o1Param], retType(sRef())));
252
- // Implementation
253
- const implParam = t.identifier('args');
254
- implParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeLiteral([
255
- pkProp(),
256
- (() => {
257
- const prop = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(selectTypeName))));
258
- return prop;
259
- })(),
260
- ]));
261
236
  const selectExpr = t.memberExpression(t.identifier('args'), t.identifier('select'));
262
- const bodyArgs = [
263
- t.stringLiteral(typeName),
264
- t.stringLiteral(singleQueryName),
265
- t.memberExpression(t.identifier('args'), t.identifier(pkField.name)),
266
- selectExpr,
267
- t.stringLiteral(pkField.name),
268
- t.stringLiteral(pkGqlType),
269
- t.identifier('connectionFieldsMap'),
270
- ];
271
- classBody.push(createClassMethod('findOne', null, [implParam], null, buildMethodBody('buildFindOneDocument', bodyArgs, 'query', typeName, singleQueryName)));
237
+ if (singleQueryName) {
238
+ const pkGqlType = pkField.gqlType.replace(/!/g, '') + '!';
239
+ const bodyArgs = [
240
+ t.stringLiteral(typeName),
241
+ t.stringLiteral(singleQueryName),
242
+ t.memberExpression(t.identifier('args'), t.identifier(pkField.name)),
243
+ selectExpr,
244
+ t.stringLiteral(pkField.name),
245
+ t.stringLiteral(pkGqlType),
246
+ t.identifier('connectionFieldsMap'),
247
+ ];
248
+ classBody.push(createClassMethod('findOne', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildFindOneDocument', bodyArgs, 'query', typeName, singleResultFieldName)));
249
+ }
250
+ else {
251
+ const idExpr = t.memberExpression(t.identifier('args'), t.identifier(pkField.name));
252
+ const findOneArgs = t.objectExpression([
253
+ t.objectProperty(t.identifier('where'), t.objectExpression([
254
+ t.objectProperty(t.identifier(pkField.name), t.objectExpression([
255
+ t.objectProperty(t.identifier('equalTo'), idExpr),
256
+ ])),
257
+ ])),
258
+ t.objectProperty(t.identifier('first'), t.numericLiteral(1)),
259
+ ]);
260
+ const bodyArgs = [
261
+ t.stringLiteral(typeName),
262
+ t.stringLiteral(pluralQueryName),
263
+ selectExpr,
264
+ findOneArgs,
265
+ t.stringLiteral(whereTypeName),
266
+ t.stringLiteral(orderByTypeName),
267
+ t.identifier('connectionFieldsMap'),
268
+ ];
269
+ const firstNodeExpr = t.optionalMemberExpression(t.optionalMemberExpression(t.memberExpression(t.identifier('data'), t.identifier(pluralQueryName)), t.identifier('nodes'), false, true), t.numericLiteral(0), true, true);
270
+ const transformDataParam = t.identifier('data');
271
+ const transformedNodesProp = t.tsPropertySignature(t.identifier('nodes'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier('InferSelectResult'), t.tsTypeParameterInstantiation([
272
+ t.tsTypeReference(t.identifier(relationTypeName)),
273
+ sRef(),
274
+ ])))));
275
+ transformedNodesProp.optional = true;
276
+ const transformedCollectionProp = t.tsPropertySignature(t.identifier(pluralQueryName), t.tsTypeAnnotation(t.tsTypeLiteral([transformedNodesProp])));
277
+ transformedCollectionProp.optional = true;
278
+ transformDataParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeLiteral([transformedCollectionProp]));
279
+ const transformFn = t.arrowFunctionExpression([transformDataParam], t.objectExpression([
280
+ t.objectProperty(t.stringLiteral(singleResultFieldName), t.logicalExpression('??', firstNodeExpr, t.nullLiteral())),
281
+ ]));
282
+ classBody.push(createClassMethod('findOne', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildFindManyDocument', bodyArgs, 'query', typeName, singleResultFieldName, [t.objectProperty(t.identifier('transform'), transformFn)])));
283
+ }
272
284
  }
273
285
  // ── create ─────────────────────────────────────────────────────────────
274
286
  {
@@ -284,17 +296,12 @@ export function generateModelFile(table, _useSharedTypes) {
284
296
  ]))),
285
297
  ]),
286
298
  ])));
287
- // Overload 1: with select (autocompletion)
288
- const o1Param = t.identifier('args');
289
- o1Param.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
299
+ const implParam = t.identifier('args');
300
+ implParam.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
290
301
  argsType(sRef()),
291
302
  t.tsTypeLiteral([requiredSelectProp()]),
292
303
  strictSelectGuard(selectTypeName),
293
304
  ]));
294
- classBody.push(createDeclareMethod('create', createTypeParam(selectTypeName), [o1Param], retType(sRef())));
295
- // Implementation
296
- const implParam = t.identifier('args');
297
- implParam.typeAnnotation = t.tsTypeAnnotation(argsType(selectRef()));
298
305
  const selectExpr = t.memberExpression(t.identifier('args'), t.identifier('select'));
299
306
  const bodyArgs = [
300
307
  t.stringLiteral(typeName),
@@ -305,7 +312,7 @@ export function generateModelFile(table, _useSharedTypes) {
305
312
  t.stringLiteral(createInputTypeName),
306
313
  t.identifier('connectionFieldsMap'),
307
314
  ];
308
- classBody.push(createClassMethod('create', null, [implParam], null, buildMethodBody('buildCreateDocument', bodyArgs, 'mutation', typeName, createMutationName)));
315
+ classBody.push(createClassMethod('create', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildCreateDocument', bodyArgs, 'mutation', typeName, createMutationName)));
309
316
  }
310
317
  // ── update ─────────────────────────────────────────────────────────────
311
318
  if (updateMutationName) {
@@ -331,18 +338,14 @@ export function generateModelFile(table, _useSharedTypes) {
331
338
  ]))),
332
339
  ]),
333
340
  ])));
334
- // Overload 1: with select (autocompletion)
335
- const o1Param = t.identifier('args');
336
- o1Param.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
341
+ const implParam = t.identifier('args');
342
+ implParam.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
337
343
  argsType(sRef()),
338
344
  t.tsTypeLiteral([requiredSelectProp()]),
339
345
  strictSelectGuard(selectTypeName),
340
346
  ]));
341
- classBody.push(createDeclareMethod('update', createTypeParam(selectTypeName), [o1Param], retType(sRef())));
342
- // Implementation
343
- const implParam = t.identifier('args');
344
- implParam.typeAnnotation = t.tsTypeAnnotation(argsType(selectRef()));
345
347
  const selectExpr = t.memberExpression(t.identifier('args'), t.identifier('select'));
348
+ const patchFieldName = table.query?.patchFieldName ?? lcFirst(typeName) + 'Patch';
346
349
  const bodyArgs = [
347
350
  t.stringLiteral(typeName),
348
351
  t.stringLiteral(updateMutationName),
@@ -352,9 +355,10 @@ export function generateModelFile(table, _useSharedTypes) {
352
355
  t.memberExpression(t.identifier('args'), t.identifier('data')),
353
356
  t.stringLiteral(updateInputTypeName),
354
357
  t.stringLiteral(pkField.name),
358
+ t.stringLiteral(patchFieldName),
355
359
  t.identifier('connectionFieldsMap'),
356
360
  ];
357
- classBody.push(createClassMethod('update', null, [implParam], null, buildMethodBody('buildUpdateByPkDocument', bodyArgs, 'mutation', typeName, updateMutationName)));
361
+ classBody.push(createClassMethod('update', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildUpdateByPkDocument', bodyArgs, 'mutation', typeName, updateMutationName)));
358
362
  }
359
363
  // ── delete ─────────────────────────────────────────────────────────────
360
364
  if (deleteMutationName) {
@@ -376,17 +380,12 @@ export function generateModelFile(table, _useSharedTypes) {
376
380
  ]))),
377
381
  ]),
378
382
  ])));
379
- // Overload 1: with select (autocompletion)
380
- const o1Param = t.identifier('args');
381
- o1Param.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
383
+ const implParam = t.identifier('args');
384
+ implParam.typeAnnotation = t.tsTypeAnnotation(t.tsIntersectionType([
382
385
  argsType(sRef()),
383
386
  t.tsTypeLiteral([requiredSelectProp()]),
384
387
  strictSelectGuard(selectTypeName),
385
388
  ]));
386
- classBody.push(createDeclareMethod('delete', createTypeParam(selectTypeName), [o1Param], retType(sRef())));
387
- // Implementation
388
- const implParam = t.identifier('args');
389
- implParam.typeAnnotation = t.tsTypeAnnotation(argsType(selectRef()));
390
389
  const selectExpr = t.memberExpression(t.identifier('args'), t.identifier('select'));
391
390
  const bodyArgs = [
392
391
  t.stringLiteral(typeName),
@@ -398,7 +397,7 @@ export function generateModelFile(table, _useSharedTypes) {
398
397
  selectExpr,
399
398
  t.identifier('connectionFieldsMap'),
400
399
  ];
401
- classBody.push(createClassMethod('delete', null, [implParam], null, buildMethodBody('buildDeleteByPkDocument', bodyArgs, 'mutation', typeName, deleteMutationName)));
400
+ classBody.push(createClassMethod('delete', createTypeParam(selectTypeName), [implParam], retType(sRef()), buildMethodBody('buildDeleteByPkDocument', bodyArgs, 'mutation', typeName, deleteMutationName)));
402
401
  }
403
402
  const classDecl = t.classDeclaration(t.identifier(modelName), null, t.classBody(classBody));
404
403
  statements.push(t.exportNamedDeclaration(classDecl));
@@ -44,25 +44,27 @@ export interface NestedSelectConfig {
44
44
  filter?: Record<string, unknown>;
45
45
  orderBy?: string[];
46
46
  }
47
+ type DepthLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
48
+ type DecrementDepth = {
49
+ 0: 0;
50
+ 1: 0;
51
+ 2: 1;
52
+ 3: 2;
53
+ 4: 3;
54
+ 5: 4;
55
+ 6: 5;
56
+ 7: 6;
57
+ 8: 7;
58
+ 9: 8;
59
+ 10: 9;
60
+ };
47
61
  /**
48
62
  * Recursively validates select objects, rejecting unknown keys.
49
63
  *
50
- * NOTE: This type is intentionally NOT used in generated parameter positions
51
- * (conditional types block IDE autocompletion). Parameters use `S` directly
52
- * with `S extends XxxSelect` constraints, which provides full
53
- * autocompletion via TypeScript's contextual typing.
54
- *
55
- * @example
56
- * // This will cause a type error because 'invalid' doesn't exist:
57
- * type Result = DeepExact<{ id: true, invalid: true }, { id?: boolean }>;
58
- * // Result = never (causes assignment error)
59
- *
60
- * @example
61
- * // This works because all fields are valid:
62
- * type Result = DeepExact<{ id: true }, { id?: boolean; name?: boolean }>;
63
- * // Result = { id: true }
64
+ * NOTE: Depth is intentionally capped to avoid circular-instantiation issues
65
+ * in very large cyclic schemas.
64
66
  */
65
- export type DeepExact<T, Shape> = T extends Shape ? Exclude<keyof T, keyof Shape> extends never ? {
67
+ export type DeepExact<T, Shape, Depth extends DepthLevel = 10> = Depth extends 0 ? T extends Shape ? T : never : T extends Shape ? Exclude<keyof T, keyof Shape> extends never ? {
66
68
  [K in keyof T]: K extends keyof Shape ? T[K] extends {
67
69
  select: infer NS;
68
70
  } ? Extract<Shape[K], {
@@ -70,10 +72,10 @@ export type DeepExact<T, Shape> = T extends Shape ? Exclude<keyof T, keyof Shape
70
72
  }> extends {
71
73
  select?: infer ShapeNS;
72
74
  } ? DeepExact<Omit<T[K], 'select'> & {
73
- select: DeepExact<NS, NonNullable<ShapeNS>>;
75
+ select: DeepExact<NS, NonNullable<ShapeNS>, DecrementDepth[Depth]>;
74
76
  }, Extract<Shape[K], {
75
77
  select?: unknown;
76
- }>> : never : T[K] : never;
78
+ }>, DecrementDepth[Depth]> : never : T[K] : never;
77
79
  } : never : never;
78
80
  /**
79
81
  * Enforces exact select shape while keeping contextual typing on `S extends XxxSelect`.
@@ -81,6 +83,13 @@ export type DeepExact<T, Shape> = T extends Shape ? Exclude<keyof T, keyof Shape
81
83
  * `{ select: S } & StrictSelect<S, XxxSelect>`.
82
84
  */
83
85
  export type StrictSelect<S, Shape> = S extends DeepExact<S, Shape> ? {} : never;
86
+ /**
87
+ * Hook-optimized strict select variant.
88
+ *
89
+ * Uses a shallower recursion depth to keep editor autocomplete responsive
90
+ * in large schemas while still validating common nested-select mistakes.
91
+ */
92
+ export type HookStrictSelect<S, Shape> = S extends DeepExact<S, Shape, 5> ? {} : never;
84
93
  /**
85
94
  * Infers the result type from a select configuration
86
95
  *
@@ -205,3 +214,4 @@ export type MutationResult<TEntity, TSelect, TPayloadKey extends string> = Query
205
214
  [EntityKey: string]: ResolveSelectResult<TEntity, TSelect>;
206
215
  };
207
216
  }>;
217
+ export {};
@@ -38,7 +38,12 @@ export function generateListQueryHook(table, options = {}) {
38
38
  }
39
39
  }
40
40
  statements.push(createImportDeclaration('../../orm/input-types', [selectTypeName, relationTypeName, filterTypeName, orderByTypeName], true));
41
- statements.push(createImportDeclaration('../../orm/select-types', ['FindManyArgs', 'InferSelectResult', 'ConnectionResult', 'StrictSelect'], true));
41
+ statements.push(createImportDeclaration('../../orm/select-types', [
42
+ 'FindManyArgs',
43
+ 'InferSelectResult',
44
+ 'ConnectionResult',
45
+ 'HookStrictSelect',
46
+ ], true));
42
47
  // Re-exports
43
48
  statements.push(createTypeReExport([selectTypeName, relationTypeName, filterTypeName, orderByTypeName], '../../orm/input-types'));
44
49
  // Query key
@@ -155,8 +160,10 @@ export function generateListQueryHook(table, options = {}) {
155
160
  const fetchFnName = `fetch${ucFirst(pluralName)}Query`;
156
161
  {
157
162
  // Overload 1: with fields
158
- const f1ParamType = t.tsTypeLiteral([
159
- t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsListSelectionType(sRef(), selectTypeName, filterTypeName, orderByTypeName))),
163
+ const f1ParamType = t.tsIntersectionType([
164
+ t.tsTypeLiteral([
165
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsListSelectionType(sRef(), selectTypeName, filterTypeName, orderByTypeName))),
166
+ ]),
160
167
  ]);
161
168
  const f1Decl = exportAsyncDeclareFunction(fetchFnName, createSTypeParam(selectTypeName), [createFunctionParam('params', f1ParamType)], typeRef('Promise', [listResultTypeAST(sRef())]));
162
169
  addJSDocComment(f1Decl, [
@@ -186,15 +193,17 @@ export function generateListQueryHook(table, options = {}) {
186
193
  if (reactQueryEnabled) {
187
194
  const prefetchFnName = `prefetch${ucFirst(pluralName)}Query`;
188
195
  // Overload 1: with fields
189
- const p1Params = [
190
- t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsListSelectionType(sRef(), selectTypeName, filterTypeName, orderByTypeName))),
191
- ];
196
+ const p1BaseParamType = t.tsIntersectionType([
197
+ t.tsTypeLiteral([
198
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsListSelectionType(sRef(), selectTypeName, filterTypeName, orderByTypeName))),
199
+ ]),
200
+ ]);
192
201
  const p1ParamType = hasRelationships && useCentralizedKeys
193
202
  ? t.tsIntersectionType([
194
- t.tsTypeLiteral(p1Params),
203
+ p1BaseParamType,
195
204
  scopeTypeLiteral(scopeTypeName),
196
205
  ])
197
- : t.tsTypeLiteral(p1Params);
206
+ : p1BaseParamType;
198
207
  const p1Decl = exportAsyncDeclareFunction(prefetchFnName, createSTypeParam(selectTypeName), [
199
208
  createFunctionParam('queryClient', typeRef('QueryClient')),
200
209
  createFunctionParam('params', p1ParamType),
@@ -277,7 +286,7 @@ export function generateSingleQueryHook(table, options = {}) {
277
286
  }
278
287
  }
279
288
  statements.push(createImportDeclaration('../../orm/input-types', [selectTypeName, relationTypeName], true));
280
- statements.push(createImportDeclaration('../../orm/select-types', ['InferSelectResult', 'StrictSelect'], true));
289
+ statements.push(createImportDeclaration('../../orm/select-types', ['InferSelectResult', 'HookStrictSelect'], true));
281
290
  // Re-exports
282
291
  statements.push(createTypeReExport([selectTypeName, relationTypeName], '../../orm/input-types'));
283
292
  // Query key
@@ -345,12 +354,11 @@ export function generateSingleQueryHook(table, options = {}) {
345
354
  docLines.push('```');
346
355
  }
347
356
  // Overload 1: with fields
348
- const o1Props = [
349
- t.tsPropertySignature(t.identifier(pkFieldName), t.tsTypeAnnotation(pkTsType)),
350
- t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsSelectionType(sRef(), selectTypeName))),
351
- ];
352
357
  const o1ParamType = t.tsIntersectionType([
353
- t.tsTypeLiteral(o1Props),
358
+ t.tsTypeLiteral([
359
+ t.tsPropertySignature(t.identifier(pkFieldName), t.tsTypeAnnotation(pkTsType)),
360
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsSelectionType(sRef(), selectTypeName))),
361
+ ]),
354
362
  buildSingleOptionsType(singleResultTypeAST(sRef()), typeRef('TData')),
355
363
  ]);
356
364
  const o1 = exportDeclareFunction(hookName, createSAndTDataTypeParams(selectTypeName, singleResultTypeAST(sRef())), [createFunctionParam('params', o1ParamType)], typeRef('UseQueryResult', [typeRef('TData')]));
@@ -393,11 +401,13 @@ export function generateSingleQueryHook(table, options = {}) {
393
401
  const fetchFnName = `fetch${ucFirst(singularName)}Query`;
394
402
  {
395
403
  // Overload 1: with fields
396
- const f1Props = [
397
- t.tsPropertySignature(t.identifier(pkFieldName), t.tsTypeAnnotation(pkTsType)),
398
- t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsSelectionType(sRef(), selectTypeName))),
399
- ];
400
- const f1Decl = exportAsyncDeclareFunction(fetchFnName, createSTypeParam(selectTypeName), [createFunctionParam('params', t.tsTypeLiteral(f1Props))], typeRef('Promise', [singleResultTypeAST(sRef())]));
404
+ const f1ParamType = t.tsIntersectionType([
405
+ t.tsTypeLiteral([
406
+ t.tsPropertySignature(t.identifier(pkFieldName), t.tsTypeAnnotation(pkTsType)),
407
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsSelectionType(sRef(), selectTypeName))),
408
+ ]),
409
+ ]);
410
+ const f1Decl = exportAsyncDeclareFunction(fetchFnName, createSTypeParam(selectTypeName), [createFunctionParam('params', f1ParamType)], typeRef('Promise', [singleResultTypeAST(sRef())]));
401
411
  addJSDocComment(f1Decl, [
402
412
  `Fetch a single ${typeName} without React hooks`,
403
413
  '',
@@ -424,16 +434,18 @@ export function generateSingleQueryHook(table, options = {}) {
424
434
  if (reactQueryEnabled) {
425
435
  const prefetchFnName = `prefetch${ucFirst(singularName)}Query`;
426
436
  // Overload 1: with fields
427
- const p1Props = [
428
- t.tsPropertySignature(t.identifier(pkFieldName), t.tsTypeAnnotation(pkTsType)),
429
- t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsSelectionType(sRef(), selectTypeName))),
430
- ];
437
+ const p1BaseParamType = t.tsIntersectionType([
438
+ t.tsTypeLiteral([
439
+ t.tsPropertySignature(t.identifier(pkFieldName), t.tsTypeAnnotation(pkTsType)),
440
+ t.tsPropertySignature(t.identifier('selection'), t.tsTypeAnnotation(withFieldsSelectionType(sRef(), selectTypeName))),
441
+ ]),
442
+ ]);
431
443
  const p1ParamType = hasRelationships && useCentralizedKeys
432
444
  ? t.tsIntersectionType([
433
- t.tsTypeLiteral(p1Props),
445
+ p1BaseParamType,
434
446
  scopeTypeLiteral(scopeTypeName),
435
447
  ])
436
- : t.tsTypeLiteral(p1Props);
448
+ : p1BaseParamType;
437
449
  const p1Decl = exportAsyncDeclareFunction(prefetchFnName, createSTypeParam(selectTypeName), [
438
450
  createFunctionParam('queryClient', typeRef('QueryClient')),
439
451
  createFunctionParam('params', p1ParamType),
@@ -60,6 +60,25 @@ function shouldSkipType(typeName, tableTypeNames) {
60
60
  }
61
61
  return false;
62
62
  }
63
+ function collectCustomScalarTypes(typeRegistry) {
64
+ const customScalarTypes = new Set();
65
+ for (const [typeName, typeInfo] of typeRegistry) {
66
+ if (typeInfo.kind !== 'SCALAR')
67
+ continue;
68
+ if (SCALAR_NAMES.has(typeName))
69
+ continue;
70
+ customScalarTypes.add(typeName);
71
+ }
72
+ return Array.from(customScalarTypes).sort();
73
+ }
74
+ function generateCustomScalarTypes(customScalarTypes) {
75
+ const statements = [];
76
+ for (const scalarType of customScalarTypes) {
77
+ const alias = t.tsTypeAliasDeclaration(t.identifier(scalarType), null, t.tsUnknownKeyword());
78
+ statements.push(t.exportNamedDeclaration(alias));
79
+ }
80
+ return statements;
81
+ }
63
82
  function generateEnumTypes(typeRegistry, tableTypeNames) {
64
83
  const statements = [];
65
84
  const generatedTypes = new Set();
@@ -224,6 +243,7 @@ export function generateSchemaTypesFile(options) {
224
243
  const { typeRegistry, tableTypeNames } = options;
225
244
  const allStatements = [];
226
245
  let generatedTypes = new Set();
246
+ const customScalarTypes = collectCustomScalarTypes(typeRegistry);
227
247
  const enumResult = generateEnumTypes(typeRegistry, tableTypeNames);
228
248
  generatedTypes = new Set([...generatedTypes, ...enumResult.generatedTypes]);
229
249
  const unionResult = generateUnionTypes(typeRegistry, tableTypeNames, generatedTypes);
@@ -239,6 +259,7 @@ export function generateSchemaTypesFile(options) {
239
259
  typesImport.importKind = 'type';
240
260
  allStatements.push(typesImport);
241
261
  }
262
+ allStatements.push(...generateCustomScalarTypes(customScalarTypes));
242
263
  allStatements.push(...enumResult.statements);
243
264
  allStatements.push(...unionResult.statements);
244
265
  allStatements.push(...inputResult.statements);
@@ -2,6 +2,7 @@
2
2
  * Types generator - generates types.ts with entity interfaces using Babel AST
3
3
  */
4
4
  import * as t from '@babel/types';
5
+ import { SCALAR_NAMES } from './scalars';
5
6
  import { generateCode } from './babel-ast';
6
7
  import { fieldTypeToTs, getGeneratedFileHeader, getScalarFields, } from './utils';
7
8
  /** All filter type configurations - scalar and list filters */
@@ -151,12 +152,34 @@ function createInterfaceDeclaration(name, properties) {
151
152
  const interfaceDecl = t.tsInterfaceDeclaration(t.identifier(name), null, null, t.tsInterfaceBody(props));
152
153
  return t.exportNamedDeclaration(interfaceDecl);
153
154
  }
155
+ function createTypeAlias(name, typeNode) {
156
+ const typeAlias = t.tsTypeAliasDeclaration(t.identifier(name), null, typeNode);
157
+ return t.exportNamedDeclaration(typeAlias);
158
+ }
159
+ function collectCustomScalarTypes(tables, excludedTypeNames) {
160
+ const customScalarTypes = new Set();
161
+ const tableTypeNames = new Set(tables.map((table) => table.name));
162
+ for (const table of tables) {
163
+ for (const field of getScalarFields(table)) {
164
+ const cleanType = field.type.gqlType.replace(/!/g, '');
165
+ if (SCALAR_NAMES.has(cleanType))
166
+ continue;
167
+ if (excludedTypeNames.has(cleanType))
168
+ continue;
169
+ if (tableTypeNames.has(cleanType))
170
+ continue;
171
+ customScalarTypes.add(cleanType);
172
+ }
173
+ }
174
+ return Array.from(customScalarTypes).sort();
175
+ }
154
176
  /**
155
177
  * Generate types.ts content with all entity interfaces and base filter types
156
178
  */
157
179
  export function generateTypesFile(tables, options = {}) {
158
180
  const { enumsFromSchemaTypes = [] } = options;
159
181
  const enumSet = new Set(enumsFromSchemaTypes);
182
+ const customScalarTypes = collectCustomScalarTypes(tables, enumSet);
160
183
  const statements = [];
161
184
  // Collect which enums are actually used by entity fields
162
185
  const usedEnums = new Set();
@@ -179,6 +202,9 @@ export function generateTypesFile(tables, options = {}) {
179
202
  importDecl.importKind = 'type';
180
203
  statements.push(importDecl);
181
204
  }
205
+ for (const scalarType of customScalarTypes) {
206
+ statements.push(createTypeAlias(scalarType, t.tsUnknownKeyword()));
207
+ }
182
208
  // Generate entity interfaces
183
209
  for (const table of tables) {
184
210
  const scalarFields = getScalarFields(table);
@@ -72,7 +72,7 @@ export declare function getUpdateMutationFileName(table: CleanTable): string;
72
72
  export declare function getDeleteMutationFileName(table: CleanTable): string;
73
73
  /**
74
74
  * Get the GraphQL query name for fetching all rows
75
- * Uses inflection from _meta, falls back to convention
75
+ * Uses inflection from introspection, falls back to convention
76
76
  */
77
77
  export declare function getAllRowsQueryName(table: CleanTable): string;
78
78
  /**
@@ -128,7 +128,7 @@ export function getDeleteMutationFileName(table) {
128
128
  // ============================================================================
129
129
  /**
130
130
  * Get the GraphQL query name for fetching all rows
131
- * Uses inflection from _meta, falls back to convention
131
+ * Uses inflection from introspection, falls back to convention
132
132
  */
133
133
  export function getAllRowsQueryName(table) {
134
134
  return (table.query?.all ||
@@ -1,4 +1,5 @@
1
1
  import * as t from 'gql-ast';
2
+ import { Kind } from 'graphql';
2
3
  /**
3
4
  * Get custom AST for MetaField type - handles PostgreSQL types that need subfield selections
4
5
  */
@@ -76,28 +77,28 @@ export function geometryPointAst(name) {
76
77
  export function geometryCollectionAst(name) {
77
78
  // Manually create inline fragment since gql-ast doesn't support it
78
79
  const inlineFragment = {
79
- kind: 'InlineFragment',
80
+ kind: Kind.INLINE_FRAGMENT,
80
81
  typeCondition: {
81
- kind: 'NamedType',
82
+ kind: Kind.NAMED_TYPE,
82
83
  name: {
83
- kind: 'Name',
84
+ kind: Kind.NAME,
84
85
  value: 'GeometryPoint',
85
86
  },
86
87
  },
87
88
  selectionSet: {
88
- kind: 'SelectionSet',
89
+ kind: Kind.SELECTION_SET,
89
90
  selections: [
90
91
  {
91
- kind: 'Field',
92
+ kind: Kind.FIELD,
92
93
  name: {
93
- kind: 'Name',
94
+ kind: Kind.NAME,
94
95
  value: 'x',
95
96
  },
96
97
  },
97
98
  {
98
- kind: 'Field',
99
+ kind: Kind.FIELD,
99
100
  name: {
100
- kind: 'Name',
101
+ kind: Kind.NAME,
101
102
  value: 'y',
102
103
  },
103
104
  },