@constructive-io/graphql-codegen 2.23.3 → 2.24.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.
- package/README.md +147 -2
- package/cli/codegen/babel-ast.d.ts +46 -0
- package/cli/codegen/babel-ast.js +145 -0
- package/cli/codegen/barrel.d.ts +7 -2
- package/cli/codegen/barrel.js +159 -97
- package/cli/codegen/client.js +61 -0
- package/cli/codegen/custom-mutations.d.ts +2 -12
- package/cli/codegen/custom-mutations.js +116 -124
- package/cli/codegen/custom-queries.d.ts +2 -10
- package/cli/codegen/custom-queries.js +246 -335
- package/cli/codegen/index.d.ts +3 -0
- package/cli/codegen/index.js +72 -3
- package/cli/codegen/invalidation.d.ts +20 -0
- package/cli/codegen/invalidation.js +327 -0
- package/cli/codegen/mutation-keys.d.ts +24 -0
- package/cli/codegen/mutation-keys.js +247 -0
- package/cli/codegen/mutations.d.ts +3 -19
- package/cli/codegen/mutations.js +372 -383
- package/cli/codegen/orm/barrel.d.ts +1 -1
- package/cli/codegen/orm/barrel.js +42 -10
- package/cli/codegen/orm/client-generator.d.ts +1 -19
- package/cli/codegen/orm/client-generator.js +108 -77
- package/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
- package/cli/codegen/orm/custom-ops-generator.js +192 -235
- package/cli/codegen/orm/input-types-generator.d.ts +13 -1
- package/cli/codegen/orm/input-types-generator.js +403 -147
- package/cli/codegen/orm/model-generator.d.ts +1 -19
- package/cli/codegen/orm/model-generator.js +229 -234
- package/cli/codegen/queries.d.ts +3 -11
- package/cli/codegen/queries.js +582 -389
- package/cli/codegen/query-keys.d.ts +15 -0
- package/cli/codegen/query-keys.js +477 -0
- package/cli/codegen/scalars.js +1 -0
- package/cli/codegen/schema-types-generator.d.ts +15 -10
- package/cli/codegen/schema-types-generator.js +87 -175
- package/cli/codegen/type-resolver.d.ts +1 -30
- package/cli/codegen/type-resolver.js +0 -53
- package/cli/codegen/types.d.ts +1 -1
- package/cli/codegen/types.js +76 -21
- package/esm/cli/codegen/babel-ast.d.ts +46 -0
- package/esm/cli/codegen/babel-ast.js +97 -0
- package/esm/cli/codegen/barrel.d.ts +7 -2
- package/esm/cli/codegen/barrel.js +126 -97
- package/esm/cli/codegen/client.js +61 -0
- package/esm/cli/codegen/custom-mutations.d.ts +2 -12
- package/esm/cli/codegen/custom-mutations.js +83 -124
- package/esm/cli/codegen/custom-queries.d.ts +2 -10
- package/esm/cli/codegen/custom-queries.js +214 -336
- package/esm/cli/codegen/index.d.ts +3 -0
- package/esm/cli/codegen/index.js +68 -2
- package/esm/cli/codegen/invalidation.d.ts +20 -0
- package/esm/cli/codegen/invalidation.js +291 -0
- package/esm/cli/codegen/mutation-keys.d.ts +24 -0
- package/esm/cli/codegen/mutation-keys.js +211 -0
- package/esm/cli/codegen/mutations.d.ts +3 -19
- package/esm/cli/codegen/mutations.js +340 -384
- package/esm/cli/codegen/orm/barrel.d.ts +1 -1
- package/esm/cli/codegen/orm/barrel.js +10 -11
- package/esm/cli/codegen/orm/client-generator.d.ts +1 -19
- package/esm/cli/codegen/orm/client-generator.js +76 -78
- package/esm/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
- package/esm/cli/codegen/orm/custom-ops-generator.js +160 -236
- package/esm/cli/codegen/orm/input-types-generator.d.ts +13 -1
- package/esm/cli/codegen/orm/input-types-generator.js +371 -148
- package/esm/cli/codegen/orm/model-generator.d.ts +1 -19
- package/esm/cli/codegen/orm/model-generator.js +197 -235
- package/esm/cli/codegen/queries.d.ts +3 -11
- package/esm/cli/codegen/queries.js +550 -390
- package/esm/cli/codegen/query-keys.d.ts +15 -0
- package/esm/cli/codegen/query-keys.js +441 -0
- package/esm/cli/codegen/scalars.js +1 -0
- package/esm/cli/codegen/schema-types-generator.d.ts +15 -10
- package/esm/cli/codegen/schema-types-generator.js +54 -175
- package/esm/cli/codegen/type-resolver.d.ts +1 -30
- package/esm/cli/codegen/type-resolver.js +0 -49
- package/esm/cli/codegen/types.d.ts +1 -1
- package/esm/cli/codegen/types.js +44 -22
- package/esm/types/config.d.ts +75 -0
- package/esm/types/config.js +18 -0
- package/package.json +6 -4
- package/types/config.d.ts +75 -0
- package/types/config.js +19 -1
- package/cli/codegen/ts-ast.d.ts +0 -124
- package/cli/codegen/ts-ast.js +0 -280
- package/esm/cli/codegen/ts-ast.d.ts +0 -124
- package/esm/cli/codegen/ts-ast.js +0 -260
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CleanTable, CleanOperation } from '../../types/schema';
|
|
2
|
+
import type { ResolvedQueryKeyConfig } from '../../types/config';
|
|
3
|
+
export interface QueryKeyGeneratorOptions {
|
|
4
|
+
tables: CleanTable[];
|
|
5
|
+
customQueries: CleanOperation[];
|
|
6
|
+
config: ResolvedQueryKeyConfig;
|
|
7
|
+
}
|
|
8
|
+
export interface GeneratedQueryKeysFile {
|
|
9
|
+
fileName: string;
|
|
10
|
+
content: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generate the complete query-keys.ts file
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateQueryKeysFile(options: QueryKeyGeneratorOptions): GeneratedQueryKeysFile;
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query key factory generator
|
|
3
|
+
*
|
|
4
|
+
* Generates centralized query keys following the lukemorales query-key-factory pattern.
|
|
5
|
+
* Supports hierarchical scoped keys for parent-child entity relationships.
|
|
6
|
+
*
|
|
7
|
+
* Uses Babel AST for code generation - no string concatenation.
|
|
8
|
+
*
|
|
9
|
+
* @see https://tanstack.com/query/docs/framework/react/community/lukemorales-query-key-factory
|
|
10
|
+
*/
|
|
11
|
+
import * as t from '@babel/types';
|
|
12
|
+
import { getTableNames, getGeneratedFileHeader, ucFirst, lcFirst } from './utils';
|
|
13
|
+
import { generateCode, addJSDocComment, asConst, constArray, typedParam, keyofTypeof, } from './babel-ast';
|
|
14
|
+
/**
|
|
15
|
+
* Get all ancestor entities for a given entity based on relationships
|
|
16
|
+
*/
|
|
17
|
+
function getAncestors(entityName, relationships) {
|
|
18
|
+
const relationship = relationships[entityName.toLowerCase()];
|
|
19
|
+
if (!relationship)
|
|
20
|
+
return [];
|
|
21
|
+
if (relationship.ancestors && relationship.ancestors.length > 0) {
|
|
22
|
+
return relationship.ancestors;
|
|
23
|
+
}
|
|
24
|
+
const ancestors = [];
|
|
25
|
+
let current = relationship.parent;
|
|
26
|
+
while (current) {
|
|
27
|
+
ancestors.push(current);
|
|
28
|
+
const parentRel = relationships[current.toLowerCase()];
|
|
29
|
+
current = parentRel?.parent ?? null;
|
|
30
|
+
}
|
|
31
|
+
return ancestors;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Generate scope type declaration for an entity
|
|
35
|
+
*/
|
|
36
|
+
function generateScopeTypeDeclaration(entityName, relationships) {
|
|
37
|
+
const relationship = relationships[entityName.toLowerCase()];
|
|
38
|
+
if (!relationship)
|
|
39
|
+
return null;
|
|
40
|
+
const ancestors = getAncestors(entityName, relationships);
|
|
41
|
+
const allParents = [relationship.parent, ...ancestors];
|
|
42
|
+
const typeName = `${ucFirst(entityName)}Scope`;
|
|
43
|
+
const members = [];
|
|
44
|
+
for (const parent of allParents) {
|
|
45
|
+
const rel = relationships[entityName.toLowerCase()];
|
|
46
|
+
let fkField = `${lcFirst(parent)}Id`;
|
|
47
|
+
if (rel && rel.parent === parent) {
|
|
48
|
+
fkField = rel.foreignKey;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const directRel = Object.entries(relationships).find(([, r]) => r.parent === parent);
|
|
52
|
+
if (directRel) {
|
|
53
|
+
fkField = directRel[1].foreignKey;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const signature = t.tsPropertySignature(t.identifier(fkField), t.tsTypeAnnotation(t.tsStringKeyword()));
|
|
57
|
+
signature.optional = true;
|
|
58
|
+
members.push(signature);
|
|
59
|
+
}
|
|
60
|
+
return t.exportNamedDeclaration(t.tsTypeAliasDeclaration(t.identifier(typeName), null, t.tsTypeLiteral(members)));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build the 'all' property: all: ['entityKey'] as const
|
|
64
|
+
*/
|
|
65
|
+
function buildAllProperty(entityKey, singularName) {
|
|
66
|
+
const prop = t.objectProperty(t.identifier('all'), constArray([t.stringLiteral(entityKey)]));
|
|
67
|
+
addJSDocComment(prop, [`All ${singularName} queries`]);
|
|
68
|
+
return prop;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Build a byParent property for scoped keys
|
|
72
|
+
*/
|
|
73
|
+
function buildByParentProperty(entityKey, typeName, parent, fkField) {
|
|
74
|
+
const parentUpper = ucFirst(parent);
|
|
75
|
+
const parentLower = lcFirst(parent);
|
|
76
|
+
const arrowFn = t.arrowFunctionExpression([typedParam(fkField, t.tsStringKeyword())], constArray([
|
|
77
|
+
t.stringLiteral(entityKey),
|
|
78
|
+
t.objectExpression([
|
|
79
|
+
t.objectProperty(t.identifier(fkField), t.identifier(fkField), false, true)
|
|
80
|
+
])
|
|
81
|
+
]));
|
|
82
|
+
const prop = t.objectProperty(t.identifier(`by${parentUpper}`), arrowFn);
|
|
83
|
+
addJSDocComment(prop, [`${typeName} queries scoped to a specific ${parentLower}`]);
|
|
84
|
+
return prop;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Build the scoped helper function property
|
|
88
|
+
*/
|
|
89
|
+
function buildScopedProperty(keysName, typeName, relationship, ancestors) {
|
|
90
|
+
const scopeTypeName = `${typeName}Scope`;
|
|
91
|
+
const scopeParam = typedParam('scope', t.tsTypeReference(t.identifier(scopeTypeName)), true);
|
|
92
|
+
const statements = [];
|
|
93
|
+
if (relationship.parent) {
|
|
94
|
+
statements.push(t.ifStatement(t.optionalMemberExpression(t.identifier('scope'), t.identifier(relationship.foreignKey), false, true), t.blockStatement([
|
|
95
|
+
t.returnStatement(t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier(`by${ucFirst(relationship.parent)}`)), [t.memberExpression(t.identifier('scope'), t.identifier(relationship.foreignKey))]))
|
|
96
|
+
])));
|
|
97
|
+
}
|
|
98
|
+
for (const ancestor of ancestors) {
|
|
99
|
+
const ancestorLower = lcFirst(ancestor);
|
|
100
|
+
const fkField = `${ancestorLower}Id`;
|
|
101
|
+
statements.push(t.ifStatement(t.optionalMemberExpression(t.identifier('scope'), t.identifier(fkField), false, true), t.blockStatement([
|
|
102
|
+
t.returnStatement(t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier(`by${ucFirst(ancestor)}`)), [t.memberExpression(t.identifier('scope'), t.identifier(fkField))]))
|
|
103
|
+
])));
|
|
104
|
+
}
|
|
105
|
+
statements.push(t.returnStatement(t.memberExpression(t.identifier(keysName), t.identifier('all'))));
|
|
106
|
+
const arrowFn = t.arrowFunctionExpression([scopeParam], t.blockStatement(statements));
|
|
107
|
+
const prop = t.objectProperty(t.identifier('scoped'), arrowFn);
|
|
108
|
+
addJSDocComment(prop, ['Get scope-aware base key']);
|
|
109
|
+
return prop;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Build lists property (scoped version)
|
|
113
|
+
*/
|
|
114
|
+
function buildScopedListsProperty(keysName, scopeTypeName) {
|
|
115
|
+
const scopeParam = typedParam('scope', t.tsTypeReference(t.identifier(scopeTypeName)), true);
|
|
116
|
+
const arrowFn = t.arrowFunctionExpression([scopeParam], constArray([
|
|
117
|
+
t.spreadElement(t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('scoped')), [t.identifier('scope')])),
|
|
118
|
+
t.stringLiteral('list')
|
|
119
|
+
]));
|
|
120
|
+
const prop = t.objectProperty(t.identifier('lists'), arrowFn);
|
|
121
|
+
addJSDocComment(prop, ['List query keys (optionally scoped)']);
|
|
122
|
+
return prop;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Build list property (scoped version)
|
|
126
|
+
*/
|
|
127
|
+
function buildScopedListProperty(keysName, scopeTypeName) {
|
|
128
|
+
const variablesParam = typedParam('variables', t.tsTypeReference(t.identifier('object')), true);
|
|
129
|
+
const scopeParam = typedParam('scope', t.tsTypeReference(t.identifier(scopeTypeName)), true);
|
|
130
|
+
const arrowFn = t.arrowFunctionExpression([variablesParam, scopeParam], constArray([
|
|
131
|
+
t.spreadElement(t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [t.identifier('scope')])),
|
|
132
|
+
t.identifier('variables')
|
|
133
|
+
]));
|
|
134
|
+
const prop = t.objectProperty(t.identifier('list'), arrowFn);
|
|
135
|
+
addJSDocComment(prop, ['List query key with variables']);
|
|
136
|
+
return prop;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Build details property (scoped version)
|
|
140
|
+
*/
|
|
141
|
+
function buildScopedDetailsProperty(keysName, scopeTypeName) {
|
|
142
|
+
const scopeParam = typedParam('scope', t.tsTypeReference(t.identifier(scopeTypeName)), true);
|
|
143
|
+
const arrowFn = t.arrowFunctionExpression([scopeParam], constArray([
|
|
144
|
+
t.spreadElement(t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('scoped')), [t.identifier('scope')])),
|
|
145
|
+
t.stringLiteral('detail')
|
|
146
|
+
]));
|
|
147
|
+
const prop = t.objectProperty(t.identifier('details'), arrowFn);
|
|
148
|
+
addJSDocComment(prop, ['Detail query keys (optionally scoped)']);
|
|
149
|
+
return prop;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Build detail property (scoped version)
|
|
153
|
+
*/
|
|
154
|
+
function buildScopedDetailProperty(keysName, scopeTypeName) {
|
|
155
|
+
const idParam = typedParam('id', t.tsUnionType([t.tsStringKeyword(), t.tsNumberKeyword()]));
|
|
156
|
+
const scopeParam = typedParam('scope', t.tsTypeReference(t.identifier(scopeTypeName)), true);
|
|
157
|
+
const arrowFn = t.arrowFunctionExpression([idParam, scopeParam], constArray([
|
|
158
|
+
t.spreadElement(t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('details')), [t.identifier('scope')])),
|
|
159
|
+
t.identifier('id')
|
|
160
|
+
]));
|
|
161
|
+
const prop = t.objectProperty(t.identifier('detail'), arrowFn);
|
|
162
|
+
addJSDocComment(prop, ['Detail query key for specific item']);
|
|
163
|
+
return prop;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Build simple (non-scoped) lists property
|
|
167
|
+
*/
|
|
168
|
+
function buildSimpleListsProperty(keysName) {
|
|
169
|
+
const arrowFn = t.arrowFunctionExpression([], constArray([
|
|
170
|
+
t.spreadElement(t.memberExpression(t.identifier(keysName), t.identifier('all'))),
|
|
171
|
+
t.stringLiteral('list')
|
|
172
|
+
]));
|
|
173
|
+
const prop = t.objectProperty(t.identifier('lists'), arrowFn);
|
|
174
|
+
addJSDocComment(prop, ['List query keys']);
|
|
175
|
+
return prop;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Build simple (non-scoped) list property
|
|
179
|
+
*/
|
|
180
|
+
function buildSimpleListProperty(keysName) {
|
|
181
|
+
const variablesParam = typedParam('variables', t.tsTypeReference(t.identifier('object')), true);
|
|
182
|
+
const arrowFn = t.arrowFunctionExpression([variablesParam], constArray([
|
|
183
|
+
t.spreadElement(t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('lists')), [])),
|
|
184
|
+
t.identifier('variables')
|
|
185
|
+
]));
|
|
186
|
+
const prop = t.objectProperty(t.identifier('list'), arrowFn);
|
|
187
|
+
addJSDocComment(prop, ['List query key with variables']);
|
|
188
|
+
return prop;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Build simple (non-scoped) details property
|
|
192
|
+
*/
|
|
193
|
+
function buildSimpleDetailsProperty(keysName) {
|
|
194
|
+
const arrowFn = t.arrowFunctionExpression([], constArray([
|
|
195
|
+
t.spreadElement(t.memberExpression(t.identifier(keysName), t.identifier('all'))),
|
|
196
|
+
t.stringLiteral('detail')
|
|
197
|
+
]));
|
|
198
|
+
const prop = t.objectProperty(t.identifier('details'), arrowFn);
|
|
199
|
+
addJSDocComment(prop, ['Detail query keys']);
|
|
200
|
+
return prop;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Build simple (non-scoped) detail property
|
|
204
|
+
*/
|
|
205
|
+
function buildSimpleDetailProperty(keysName) {
|
|
206
|
+
const idParam = typedParam('id', t.tsUnionType([t.tsStringKeyword(), t.tsNumberKeyword()]));
|
|
207
|
+
const arrowFn = t.arrowFunctionExpression([idParam], constArray([
|
|
208
|
+
t.spreadElement(t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('details')), [])),
|
|
209
|
+
t.identifier('id')
|
|
210
|
+
]));
|
|
211
|
+
const prop = t.objectProperty(t.identifier('detail'), arrowFn);
|
|
212
|
+
addJSDocComment(prop, ['Detail query key for specific item']);
|
|
213
|
+
return prop;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Generate query keys declaration for a single table entity
|
|
217
|
+
*/
|
|
218
|
+
function generateEntityKeysDeclaration(table, relationships, generateScopedKeys) {
|
|
219
|
+
const { typeName, singularName } = getTableNames(table);
|
|
220
|
+
const entityKey = typeName.toLowerCase();
|
|
221
|
+
const keysName = `${lcFirst(typeName)}Keys`;
|
|
222
|
+
const relationship = relationships[entityKey];
|
|
223
|
+
const hasRelationship = !!relationship && generateScopedKeys;
|
|
224
|
+
const properties = [];
|
|
225
|
+
properties.push(buildAllProperty(entityKey, singularName));
|
|
226
|
+
if (hasRelationship) {
|
|
227
|
+
const ancestors = getAncestors(typeName, relationships);
|
|
228
|
+
const allParents = [relationship.parent, ...ancestors];
|
|
229
|
+
for (const parent of allParents) {
|
|
230
|
+
let fkField = `${lcFirst(parent)}Id`;
|
|
231
|
+
if (relationship.parent === parent) {
|
|
232
|
+
fkField = relationship.foreignKey;
|
|
233
|
+
}
|
|
234
|
+
properties.push(buildByParentProperty(entityKey, typeName, parent, fkField));
|
|
235
|
+
}
|
|
236
|
+
properties.push(buildScopedProperty(keysName, typeName, relationship, ancestors));
|
|
237
|
+
const scopeTypeName = `${typeName}Scope`;
|
|
238
|
+
properties.push(buildScopedListsProperty(keysName, scopeTypeName));
|
|
239
|
+
properties.push(buildScopedListProperty(keysName, scopeTypeName));
|
|
240
|
+
properties.push(buildScopedDetailsProperty(keysName, scopeTypeName));
|
|
241
|
+
properties.push(buildScopedDetailProperty(keysName, scopeTypeName));
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
properties.push(buildSimpleListsProperty(keysName));
|
|
245
|
+
properties.push(buildSimpleListProperty(keysName));
|
|
246
|
+
properties.push(buildSimpleDetailsProperty(keysName));
|
|
247
|
+
properties.push(buildSimpleDetailProperty(keysName));
|
|
248
|
+
}
|
|
249
|
+
return t.exportNamedDeclaration(t.variableDeclaration('const', [
|
|
250
|
+
t.variableDeclarator(t.identifier(keysName), asConst(t.objectExpression(properties)))
|
|
251
|
+
]));
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Generate query keys declaration for custom operations
|
|
255
|
+
*/
|
|
256
|
+
function generateCustomQueryKeysDeclaration(operations) {
|
|
257
|
+
if (operations.length === 0)
|
|
258
|
+
return null;
|
|
259
|
+
const properties = [];
|
|
260
|
+
for (const op of operations) {
|
|
261
|
+
const hasArgs = op.args.length > 0;
|
|
262
|
+
const hasRequiredArgs = op.args.some((arg) => arg.type.kind === 'NON_NULL');
|
|
263
|
+
let prop;
|
|
264
|
+
if (hasArgs) {
|
|
265
|
+
const variablesParam = typedParam('variables', t.tsTypeReference(t.identifier('object')), !hasRequiredArgs);
|
|
266
|
+
const arrowFn = t.arrowFunctionExpression([variablesParam], constArray([t.stringLiteral(op.name), t.identifier('variables')]));
|
|
267
|
+
prop = t.objectProperty(t.identifier(op.name), arrowFn);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
const arrowFn = t.arrowFunctionExpression([], constArray([t.stringLiteral(op.name)]));
|
|
271
|
+
prop = t.objectProperty(t.identifier(op.name), arrowFn);
|
|
272
|
+
}
|
|
273
|
+
addJSDocComment(prop, [`Query key for ${op.name}`]);
|
|
274
|
+
properties.push(prop);
|
|
275
|
+
}
|
|
276
|
+
return t.exportNamedDeclaration(t.variableDeclaration('const', [
|
|
277
|
+
t.variableDeclarator(t.identifier('customQueryKeys'), asConst(t.objectExpression(properties)))
|
|
278
|
+
]));
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Generate the unified query keys store declaration
|
|
282
|
+
*/
|
|
283
|
+
function generateUnifiedStoreDeclaration(tables, hasCustomQueries) {
|
|
284
|
+
const properties = [];
|
|
285
|
+
for (const table of tables) {
|
|
286
|
+
const { typeName } = getTableNames(table);
|
|
287
|
+
const keysName = `${lcFirst(typeName)}Keys`;
|
|
288
|
+
properties.push(t.objectProperty(t.identifier(lcFirst(typeName)), t.identifier(keysName)));
|
|
289
|
+
}
|
|
290
|
+
if (hasCustomQueries) {
|
|
291
|
+
properties.push(t.objectProperty(t.identifier('custom'), t.identifier('customQueryKeys')));
|
|
292
|
+
}
|
|
293
|
+
const decl = t.exportNamedDeclaration(t.variableDeclaration('const', [
|
|
294
|
+
t.variableDeclarator(t.identifier('queryKeys'), asConst(t.objectExpression(properties)))
|
|
295
|
+
]));
|
|
296
|
+
addJSDocComment(decl, [
|
|
297
|
+
'Unified query key store',
|
|
298
|
+
'',
|
|
299
|
+
'Use this for type-safe query key access across your application.',
|
|
300
|
+
'',
|
|
301
|
+
'@example',
|
|
302
|
+
'```ts',
|
|
303
|
+
'// Invalidate all user queries',
|
|
304
|
+
'queryClient.invalidateQueries({ queryKey: queryKeys.user.all });',
|
|
305
|
+
'',
|
|
306
|
+
'// Invalidate user list queries',
|
|
307
|
+
'queryClient.invalidateQueries({ queryKey: queryKeys.user.lists() });',
|
|
308
|
+
'',
|
|
309
|
+
'// Invalidate specific user',
|
|
310
|
+
'queryClient.invalidateQueries({ queryKey: queryKeys.user.detail(userId) });',
|
|
311
|
+
'```',
|
|
312
|
+
]);
|
|
313
|
+
return decl;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Generate the complete query-keys.ts file
|
|
317
|
+
*/
|
|
318
|
+
export function generateQueryKeysFile(options) {
|
|
319
|
+
const { tables, customQueries, config } = options;
|
|
320
|
+
const { relationships, generateScopedKeys } = config;
|
|
321
|
+
const statements = [];
|
|
322
|
+
// Generate scope types for entities with relationships
|
|
323
|
+
if (generateScopedKeys && Object.keys(relationships).length > 0) {
|
|
324
|
+
const generatedScopes = new Set();
|
|
325
|
+
for (const table of tables) {
|
|
326
|
+
const { typeName } = getTableNames(table);
|
|
327
|
+
const scopeTypeName = `${typeName}Scope`;
|
|
328
|
+
if (!generatedScopes.has(scopeTypeName)) {
|
|
329
|
+
const scopeType = generateScopeTypeDeclaration(typeName, relationships);
|
|
330
|
+
if (scopeType) {
|
|
331
|
+
statements.push(scopeType);
|
|
332
|
+
generatedScopes.add(scopeTypeName);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Generate entity keys
|
|
338
|
+
for (const table of tables) {
|
|
339
|
+
statements.push(generateEntityKeysDeclaration(table, relationships, generateScopedKeys));
|
|
340
|
+
}
|
|
341
|
+
// Generate custom query keys
|
|
342
|
+
const queryOperations = customQueries.filter((op) => op.kind === 'query');
|
|
343
|
+
const customKeysDecl = generateCustomQueryKeysDeclaration(queryOperations);
|
|
344
|
+
if (customKeysDecl) {
|
|
345
|
+
statements.push(customKeysDecl);
|
|
346
|
+
}
|
|
347
|
+
// Generate unified store
|
|
348
|
+
statements.push(generateUnifiedStoreDeclaration(tables, queryOperations.length > 0));
|
|
349
|
+
// Generate QueryKeyScope type
|
|
350
|
+
const scopeTypeDecl = t.exportNamedDeclaration(t.tsTypeAliasDeclaration(t.identifier('QueryKeyScope'), null, keyofTypeof('queryKeys')));
|
|
351
|
+
addJSDocComment(scopeTypeDecl, ['Type representing all available query key scopes']);
|
|
352
|
+
statements.push(scopeTypeDecl);
|
|
353
|
+
// Generate code from AST
|
|
354
|
+
const code = generateCode(statements);
|
|
355
|
+
// Build final content with header and section comments
|
|
356
|
+
const header = getGeneratedFileHeader('Centralized query key factory');
|
|
357
|
+
const description = `// ============================================================================
|
|
358
|
+
// This file provides a centralized, type-safe query key factory following
|
|
359
|
+
// the lukemorales query-key-factory pattern for React Query.
|
|
360
|
+
//
|
|
361
|
+
// Benefits:
|
|
362
|
+
// - Single source of truth for all query keys
|
|
363
|
+
// - Type-safe key access with autocomplete
|
|
364
|
+
// - Hierarchical invalidation (invalidate all 'user.*' queries)
|
|
365
|
+
// - Scoped keys for parent-child relationships
|
|
366
|
+
// ============================================================================`;
|
|
367
|
+
let content = `${header}
|
|
368
|
+
|
|
369
|
+
${description}
|
|
370
|
+
|
|
371
|
+
`;
|
|
372
|
+
// Add scope types section if present
|
|
373
|
+
if (generateScopedKeys && Object.keys(relationships).length > 0) {
|
|
374
|
+
const hasScopes = tables.some(table => {
|
|
375
|
+
const { typeName } = getTableNames(table);
|
|
376
|
+
return !!relationships[typeName.toLowerCase()];
|
|
377
|
+
});
|
|
378
|
+
if (hasScopes) {
|
|
379
|
+
content += `// ============================================================================
|
|
380
|
+
// Scope Types
|
|
381
|
+
// ============================================================================
|
|
382
|
+
|
|
383
|
+
`;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
// Insert section comments into the generated code
|
|
387
|
+
const codeLines = code.split('\n');
|
|
388
|
+
let inScopeTypes = generateScopedKeys && Object.keys(relationships).length > 0;
|
|
389
|
+
let addedEntitySection = false;
|
|
390
|
+
let addedCustomSection = false;
|
|
391
|
+
let addedUnifiedSection = false;
|
|
392
|
+
for (let i = 0; i < codeLines.length; i++) {
|
|
393
|
+
const line = codeLines[i];
|
|
394
|
+
// Detect transition from scope types to entity keys
|
|
395
|
+
if (inScopeTypes && line.startsWith('export const') && line.includes('Keys =')) {
|
|
396
|
+
content += `// ============================================================================
|
|
397
|
+
// Entity Query Keys
|
|
398
|
+
// ============================================================================
|
|
399
|
+
|
|
400
|
+
`;
|
|
401
|
+
inScopeTypes = false;
|
|
402
|
+
addedEntitySection = true;
|
|
403
|
+
}
|
|
404
|
+
// Detect custom query keys section
|
|
405
|
+
if (!addedCustomSection && line.startsWith('export const customQueryKeys')) {
|
|
406
|
+
content += `
|
|
407
|
+
// ============================================================================
|
|
408
|
+
// Custom Query Keys
|
|
409
|
+
// ============================================================================
|
|
410
|
+
|
|
411
|
+
`;
|
|
412
|
+
addedCustomSection = true;
|
|
413
|
+
}
|
|
414
|
+
// Detect unified store section
|
|
415
|
+
if (!addedUnifiedSection && line.includes('* Unified query key store')) {
|
|
416
|
+
content += `
|
|
417
|
+
// ============================================================================
|
|
418
|
+
// Unified Query Key Store
|
|
419
|
+
// ============================================================================
|
|
420
|
+
|
|
421
|
+
`;
|
|
422
|
+
addedUnifiedSection = true;
|
|
423
|
+
}
|
|
424
|
+
content += line + '\n';
|
|
425
|
+
}
|
|
426
|
+
// If no scope types, add entity section at the beginning
|
|
427
|
+
if (!addedEntitySection && !inScopeTypes) {
|
|
428
|
+
const firstExportIndex = content.indexOf('\nexport const');
|
|
429
|
+
if (firstExportIndex !== -1) {
|
|
430
|
+
content = content.slice(0, firstExportIndex) + `
|
|
431
|
+
// ============================================================================
|
|
432
|
+
// Entity Query Keys
|
|
433
|
+
// ============================================================================
|
|
434
|
+
` + content.slice(firstExportIndex);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return {
|
|
438
|
+
fileName: 'query-keys.ts',
|
|
439
|
+
content,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
@@ -1,26 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema types generator for React Query hooks (non-ORM mode)
|
|
3
|
+
*
|
|
4
|
+
* Generates TypeScript interfaces for:
|
|
5
|
+
* 1. INPUT_OBJECT types (e.g., BootstrapUserInput, LoginInput)
|
|
6
|
+
* 2. Payload OBJECT types (e.g., BootstrapUserPayload, LoginPayload)
|
|
7
|
+
* 3. ENUM types (e.g., FieldCategory, TableCategory)
|
|
8
|
+
*
|
|
9
|
+
* These types are referenced by custom mutation/query hooks but not generated
|
|
10
|
+
* elsewhere in non-ORM mode.
|
|
11
|
+
*
|
|
12
|
+
* Uses Babel AST for robust code generation.
|
|
13
|
+
*/
|
|
1
14
|
import type { TypeRegistry } from '../../types/schema';
|
|
15
|
+
import * as t from '@babel/types';
|
|
2
16
|
export interface GeneratedSchemaTypesFile {
|
|
3
17
|
fileName: string;
|
|
4
18
|
content: string;
|
|
5
|
-
/** List of enum type names that were generated */
|
|
6
19
|
generatedEnums: string[];
|
|
7
|
-
/** List of table entity types that are referenced */
|
|
8
20
|
referencedTableTypes: string[];
|
|
9
21
|
}
|
|
10
22
|
export interface GenerateSchemaTypesOptions {
|
|
11
|
-
/** The TypeRegistry containing all GraphQL types */
|
|
12
23
|
typeRegistry: TypeRegistry;
|
|
13
|
-
/** Type names that already exist in types.ts (table entity types) */
|
|
14
24
|
tableTypeNames: Set<string>;
|
|
15
25
|
}
|
|
16
26
|
export interface PayloadTypesResult {
|
|
27
|
+
statements: t.Statement[];
|
|
17
28
|
generatedTypes: Set<string>;
|
|
18
29
|
referencedTableTypes: Set<string>;
|
|
19
30
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Generate comprehensive schema-types.ts file using ts-morph AST
|
|
22
|
-
*
|
|
23
|
-
* This generates all Input/Payload/Enum types from the TypeRegistry
|
|
24
|
-
* that are needed by custom mutation/query hooks.
|
|
25
|
-
*/
|
|
26
31
|
export declare function generateSchemaTypesFile(options: GenerateSchemaTypesOptions): GeneratedSchemaTypesFile;
|