@ethlete/query 6.0.0-next.2 → 6.0.0-next.20

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.
@@ -0,0 +1,153 @@
1
+ import * as ts from 'typescript';
2
+ import { createSourceFile } from './shared.js';
3
+ const MAX_BARREL_DEPTH = 8;
4
+ const normalizePath = (path) => {
5
+ const segments = [];
6
+ for (const segment of path.split('/')) {
7
+ if (segment === '' || segment === '.')
8
+ continue;
9
+ if (segment === '..') {
10
+ segments.pop();
11
+ continue;
12
+ }
13
+ segments.push(segment);
14
+ }
15
+ return segments.join('/');
16
+ };
17
+ const dirName = (path) => path.slice(0, Math.max(0, path.lastIndexOf('/')));
18
+ const readTsConfigPaths = (tree) => {
19
+ const paths = new Map();
20
+ for (const configPath of ['tsconfig.base.json', 'tsconfig.json']) {
21
+ const raw = tree.read(configPath, 'utf-8');
22
+ if (!raw)
23
+ continue;
24
+ try {
25
+ // tsconfig files are JSONC in practice; the TS parser is the only thing that reliably reads
26
+ // them, comments and trailing commas included.
27
+ const parsed = ts.parseConfigFileTextToJson(configPath, raw).config;
28
+ Object.entries(parsed?.compilerOptions?.paths ?? {}).forEach(([key, targets]) => {
29
+ if (!paths.has(key))
30
+ paths.set(key, targets);
31
+ });
32
+ }
33
+ catch {
34
+ // A tsconfig we cannot parse just means fewer resolvable aliases, not a failed migration.
35
+ }
36
+ }
37
+ return paths;
38
+ };
39
+ /** The files a specifier could point at, most specific first. */
40
+ const candidateFiles = (base) => [
41
+ base.endsWith('.ts') ? base : `${base}.ts`,
42
+ `${base}/index.ts`,
43
+ `${base}.d.ts`,
44
+ ];
45
+ export const createModuleGraph = (tree) => {
46
+ const tsConfigPaths = readTsConfigPaths(tree);
47
+ const declaringFileCache = new Map();
48
+ const resolveEntryFile = (fromFile, specifier) => {
49
+ if (specifier.startsWith('.')) {
50
+ const base = normalizePath(`${dirName(fromFile)}/${specifier}`);
51
+ return candidateFiles(base).find((candidate) => tree.exists(candidate)) ?? null;
52
+ }
53
+ for (const [pattern, targets] of tsConfigPaths.entries()) {
54
+ const wildcardIndex = pattern.indexOf('*');
55
+ let suffix;
56
+ if (wildcardIndex === -1) {
57
+ if (pattern !== specifier)
58
+ continue;
59
+ suffix = '';
60
+ }
61
+ else {
62
+ const prefix = pattern.slice(0, wildcardIndex);
63
+ const patternSuffix = pattern.slice(wildcardIndex + 1);
64
+ if (!specifier.startsWith(prefix) || !specifier.endsWith(patternSuffix))
65
+ continue;
66
+ suffix = specifier.slice(prefix.length, specifier.length - patternSuffix.length);
67
+ }
68
+ for (const target of targets) {
69
+ const resolvedTarget = normalizePath(target.replace('*', suffix));
70
+ const candidate = candidateFiles(resolvedTarget).find((option) => tree.exists(option));
71
+ if (candidate)
72
+ return candidate;
73
+ }
74
+ }
75
+ return null;
76
+ };
77
+ const declaresSymbol = (sourceFile, symbolName) => {
78
+ let declares = false;
79
+ const isExported = (node) => ts.canHaveModifiers(node) &&
80
+ ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
81
+ ts.forEachChild(sourceFile, (node) => {
82
+ if (declares)
83
+ return;
84
+ if (ts.isVariableStatement(node) && isExported(node)) {
85
+ declares = node.declarationList.declarations.some((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === symbolName);
86
+ return;
87
+ }
88
+ if ((ts.isFunctionDeclaration(node) ||
89
+ ts.isClassDeclaration(node) ||
90
+ ts.isInterfaceDeclaration(node) ||
91
+ ts.isTypeAliasDeclaration(node) ||
92
+ ts.isEnumDeclaration(node)) &&
93
+ isExported(node) &&
94
+ node.name?.text === symbolName) {
95
+ declares = true;
96
+ }
97
+ });
98
+ return declares;
99
+ };
100
+ const findDeclaringFileIn = (entryFile, symbolName, depth, seen) => {
101
+ if (depth > MAX_BARREL_DEPTH || seen.has(`${entryFile}#${symbolName}`))
102
+ return null;
103
+ seen.add(`${entryFile}#${symbolName}`);
104
+ const content = tree.read(entryFile, 'utf-8');
105
+ if (!content)
106
+ return null;
107
+ const sourceFile = createSourceFile(content, entryFile);
108
+ if (declaresSymbol(sourceFile, symbolName))
109
+ return entryFile;
110
+ const starExports = [];
111
+ for (const node of sourceFile.statements) {
112
+ if (!ts.isExportDeclaration(node) || !node.moduleSpecifier || !ts.isStringLiteral(node.moduleSpecifier)) {
113
+ continue;
114
+ }
115
+ const nextEntry = resolveEntryFile(entryFile, node.moduleSpecifier.text);
116
+ if (!nextEntry)
117
+ continue;
118
+ if (!node.exportClause) {
119
+ starExports.push(nextEntry);
120
+ continue;
121
+ }
122
+ if (!ts.isNamedExports(node.exportClause))
123
+ continue;
124
+ for (const element of node.exportClause.elements) {
125
+ if (element.name.text !== symbolName)
126
+ continue;
127
+ const originalName = element.propertyName?.text ?? symbolName;
128
+ const found = findDeclaringFileIn(nextEntry, originalName, depth + 1, seen);
129
+ if (found)
130
+ return found;
131
+ }
132
+ }
133
+ for (const starExport of starExports) {
134
+ const found = findDeclaringFileIn(starExport, symbolName, depth + 1, seen);
135
+ if (found)
136
+ return found;
137
+ }
138
+ return null;
139
+ };
140
+ return {
141
+ findDeclaringFile: (fromFile, specifier, symbolName) => {
142
+ const cacheKey = `${fromFile}|${specifier}|${symbolName}`;
143
+ const cached = declaringFileCache.get(cacheKey);
144
+ if (cached !== undefined)
145
+ return cached;
146
+ const entryFile = resolveEntryFile(fromFile, specifier);
147
+ const declaringFile = entryFile ? findDeclaringFileIn(entryFile, symbolName, 0, new Set()) : null;
148
+ declaringFileCache.set(cacheKey, declaringFile);
149
+ return declaringFile;
150
+ },
151
+ };
152
+ };
153
+ //# sourceMappingURL=module-graph.js.map
@@ -0,0 +1,525 @@
1
+ import * as ts from 'typescript';
2
+ import { capitalizeFirstLetter, createSourceFile, ensureConfigSuffix, ensureImportFromQuery, getVariableStatementEnd, } from './shared.js';
3
+ export const migrateQueryClients = (tree, report, scope) => {
4
+ const queryClientFiles = new Map();
5
+ const variableRenames = new Map();
6
+ scope.visit(tree, (filePath) => {
7
+ if (!filePath.endsWith('.ts')) {
8
+ return;
9
+ }
10
+ const content = tree.read(filePath, 'utf-8');
11
+ if (!content || !hasQueryClientInstantiation(content)) {
12
+ return;
13
+ }
14
+ const renames = new Map();
15
+ const nextContent = migrateQueryClientToConfig({ content, filePath, renamesOut: renames, report });
16
+ renames.forEach((newName, oldName) => {
17
+ variableRenames.set(oldName, newName);
18
+ });
19
+ const configNames = extractClientConfigNames(nextContent);
20
+ if (configNames.length > 0) {
21
+ queryClientFiles.set(filePath, configNames);
22
+ }
23
+ if (nextContent !== content) {
24
+ tree.write(filePath, nextContent);
25
+ }
26
+ });
27
+ if (queryClientFiles.size > 0) {
28
+ console.log('\n✅ Migrated QueryClient instantiations in:');
29
+ queryClientFiles.forEach((configNames, filePath) => {
30
+ console.log(` - ${filePath} (${configNames.join(', ')})`);
31
+ });
32
+ }
33
+ return { queryClientFiles, variableRenames };
34
+ };
35
+ export const generateProviderAliases = (tree, queryClientFiles) => {
36
+ const updatedFiles = [];
37
+ for (const [filePath, configNames] of queryClientFiles.entries()) {
38
+ const content = tree.read(filePath, 'utf-8');
39
+ if (!content) {
40
+ continue;
41
+ }
42
+ const nextContent = addProviderAliasesToFile(content, configNames);
43
+ if (nextContent !== content) {
44
+ tree.write(filePath, nextContent);
45
+ updatedFiles.push(filePath);
46
+ }
47
+ }
48
+ if (updatedFiles.length > 0) {
49
+ console.log('\n✅ Generated query client provider aliases in:');
50
+ updatedFiles.forEach((filePath) => console.log(` - ${filePath}`));
51
+ }
52
+ };
53
+ export const generateQueryCreators = (tree, queryClientFiles) => {
54
+ const generatedFiles = [];
55
+ for (const [filePath, configNames] of queryClientFiles.entries()) {
56
+ const content = tree.read(filePath, 'utf-8');
57
+ if (!content) {
58
+ continue;
59
+ }
60
+ const nextContent = addQueryCreatorsToFile(content, configNames);
61
+ if (nextContent !== content) {
62
+ tree.write(filePath, nextContent);
63
+ generatedFiles.push(filePath);
64
+ }
65
+ }
66
+ if (generatedFiles.length > 0) {
67
+ console.log('\n✅ Generated query creators in:');
68
+ generatedFiles.forEach((filePath) => console.log(` - ${filePath}`));
69
+ }
70
+ };
71
+ export const updateImportsAcrossWorkspace = (tree, renames, scope) => {
72
+ if (renames.size === 0) {
73
+ return;
74
+ }
75
+ scope.visit(tree, (filePath) => {
76
+ if (!filePath.endsWith('.ts')) {
77
+ return;
78
+ }
79
+ const content = tree.read(filePath, 'utf-8');
80
+ if (!content) {
81
+ return;
82
+ }
83
+ const nextContent = updateImportsInFile(content, renames);
84
+ if (nextContent !== content) {
85
+ tree.write(filePath, nextContent);
86
+ }
87
+ });
88
+ };
89
+ const hasQueryClientInstantiation = (content) => {
90
+ const sourceFile = createSourceFile(content);
91
+ let hasQueryClientImport = false;
92
+ let hasNewQueryClient = false;
93
+ const visit = (node) => {
94
+ if (ts.isImportDeclaration(node) &&
95
+ ts.isStringLiteral(node.moduleSpecifier) &&
96
+ node.moduleSpecifier.text === '@ethlete/query' &&
97
+ node.importClause?.namedBindings &&
98
+ ts.isNamedImports(node.importClause.namedBindings)) {
99
+ const hasLegacyClientImport = node.importClause.namedBindings.elements.some((element) => {
100
+ return element.name.text === 'V2QueryClient';
101
+ });
102
+ if (hasLegacyClientImport) {
103
+ hasQueryClientImport = true;
104
+ }
105
+ }
106
+ if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'V2QueryClient') {
107
+ hasNewQueryClient = true;
108
+ }
109
+ ts.forEachChild(node, visit);
110
+ };
111
+ visit(sourceFile);
112
+ return hasQueryClientImport && hasNewQueryClient;
113
+ };
114
+ const migrateQueryClientToConfig = ({ content, filePath, renamesOut, report }) => {
115
+ const sourceFile = createSourceFile(content, filePath);
116
+ const replacements = [];
117
+ const variableRenames = new Map();
118
+ const visit = (node) => {
119
+ if (ts.isNewExpression(node) &&
120
+ ts.isIdentifier(node.expression) &&
121
+ node.expression.text === 'V2QueryClient' &&
122
+ node.arguments &&
123
+ node.arguments.length > 0) {
124
+ const configArgument = node.arguments[0];
125
+ if (ts.isObjectLiteralExpression(configArgument)) {
126
+ const oldVariableName = getVariableNameForQueryClient(node);
127
+ const newVariableName = ensureConfigSuffix(oldVariableName);
128
+ if (oldVariableName !== newVariableName) {
129
+ const collisionRegex = new RegExp(`\\b(?:const|let|var)\\s+${newVariableName}\\b`);
130
+ if (collisionRegex.test(content)) {
131
+ report.addWarning({
132
+ title: `Resolve query client rename collision for ${oldVariableName}`,
133
+ summary: `The migration renamed ${oldVariableName} to ${newVariableName}, but a declaration with the target name already exists.`,
134
+ action: `Verify the declarations in ${filePath} and rename one side manually before shipping the migrated client.`,
135
+ locations: [{ filePath }],
136
+ source: 'query-client-migration',
137
+ dedupeKey: `rename-collision:${filePath}:${oldVariableName}:${newVariableName}`,
138
+ });
139
+ }
140
+ variableRenames.set(oldVariableName, newVariableName);
141
+ if (renamesOut) {
142
+ renamesOut.set(oldVariableName, newVariableName);
143
+ }
144
+ }
145
+ reportDroppedClientOptions({ configArgument, sourceFile, filePath, report });
146
+ replacements.push({
147
+ start: node.getStart(sourceFile),
148
+ end: node.getEnd(),
149
+ replacement: migrateConfigObject(configArgument, node, sourceFile),
150
+ });
151
+ }
152
+ }
153
+ ts.forEachChild(node, visit);
154
+ };
155
+ visit(sourceFile);
156
+ let result = content;
157
+ replacements.sort((left, right) => right.start - left.start);
158
+ replacements.forEach(({ start, end, replacement }) => {
159
+ result = result.slice(0, start) + replacement + result.slice(end);
160
+ });
161
+ result = removeQueryClientImport(result);
162
+ return renameVariables(result, variableRenames);
163
+ };
164
+ const removeQueryClientImport = (content) => {
165
+ const sourceFile = createSourceFile(content);
166
+ let queryImportNode;
167
+ ts.forEachChild(sourceFile, (node) => {
168
+ if (ts.isImportDeclaration(node) &&
169
+ ts.isStringLiteral(node.moduleSpecifier) &&
170
+ node.moduleSpecifier.text === '@ethlete/query') {
171
+ queryImportNode = node;
172
+ }
173
+ });
174
+ const requiredImports = [
175
+ 'createDeleteQuery',
176
+ 'createGetQuery',
177
+ 'createPatchQuery',
178
+ 'createPostQuery',
179
+ 'createPutQuery',
180
+ 'createQueryClient',
181
+ ];
182
+ if (!queryImportNode?.importClause?.namedBindings || !ts.isNamedImports(queryImportNode.importClause.namedBindings)) {
183
+ return ensureImportFromQuery(content, requiredImports);
184
+ }
185
+ const existingElements = queryImportNode.importClause.namedBindings.elements.filter((element) => {
186
+ return element.name.text !== 'V2QueryClient';
187
+ });
188
+ const nextImports = new Set(requiredImports);
189
+ existingElements.forEach((element) => nextImports.add(element.name.text));
190
+ const nextImportStatement = `import { ${Array.from(nextImports).sort().join(', ')} } from '@ethlete/query';`;
191
+ return (content.slice(0, queryImportNode.getStart(sourceFile)) +
192
+ nextImportStatement +
193
+ content.slice(queryImportNode.getEnd()));
194
+ };
195
+ const renameVariables = (content, renames) => {
196
+ if (renames.size === 0) {
197
+ return content;
198
+ }
199
+ const sourceFile = createSourceFile(content);
200
+ const replacements = [];
201
+ const visit = (node) => {
202
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
203
+ const nextName = renames.get(node.name.text);
204
+ if (nextName) {
205
+ replacements.push({
206
+ start: node.name.getStart(sourceFile),
207
+ end: node.name.getEnd(),
208
+ replacement: nextName,
209
+ });
210
+ }
211
+ }
212
+ else if (ts.isIdentifier(node)) {
213
+ const nextName = renames.get(node.text);
214
+ if (nextName) {
215
+ const parent = node.parent;
216
+ if (!ts.isPropertyAssignment(parent) || parent.name !== node) {
217
+ replacements.push({
218
+ start: node.getStart(sourceFile),
219
+ end: node.getEnd(),
220
+ replacement: nextName,
221
+ });
222
+ }
223
+ }
224
+ }
225
+ ts.forEachChild(node, visit);
226
+ };
227
+ visit(sourceFile);
228
+ const seen = new Set();
229
+ const uniqueReplacements = replacements.filter((replacement) => {
230
+ const key = `${replacement.start}:${replacement.end}`;
231
+ if (seen.has(key)) {
232
+ return false;
233
+ }
234
+ seen.add(key);
235
+ return true;
236
+ });
237
+ let result = content;
238
+ uniqueReplacements.sort((left, right) => right.start - left.start);
239
+ uniqueReplacements.forEach(({ start, end, replacement }) => {
240
+ result = result.slice(0, start) + replacement + result.slice(end);
241
+ });
242
+ return result;
243
+ };
244
+ /**
245
+ * What each v2 client option turns into, or why it has nowhere to go.
246
+ *
247
+ * Silently dropping these is how two apps here lost their default headers without a single warning.
248
+ * A dropped option that shows up in the report is a five-minute fix; one that doesn't is a bug
249
+ * report from production.
250
+ */
251
+ const DROPPED_CLIENT_OPTIONS = {
252
+ 'request.autoRefreshQueriesOnWindowFocus': 'No direct equivalent. Re-run the affected queries yourself — `withAutoRefresh({ onSignalChanges: [windowFocusSignal] })` per query, or `injectMyClient().refreshQueriesInUse()` from a focus listener.',
253
+ 'request.enableSmartPolling': 'No direct equivalent. `withPolling({ interval })` polls unconditionally; pause it yourself when the tab is hidden if that matters.',
254
+ 'logging.preparedQuerySubscriptions': 'Removed. Use the v3 devtools (`provideQueryDevtools()`) or `withLogging({ logFn })` on the queries you want to trace.',
255
+ 'logging.queryStateChanges': 'Removed. Use the v3 devtools (`provideQueryDevtools()`) or `withLogging({ logFn })` on the queries you want to trace.',
256
+ 'request.autoRefreshOn': 'No direct equivalent. Re-run the affected queries yourself with `withAutoRefresh` or `refreshQueriesInUse()`.',
257
+ };
258
+ const reportDroppedClientOptions = ({ configArgument, sourceFile, filePath, report, }) => {
259
+ const migrated = new Set(['baseRoute', 'queryParams', 'cacheAdapter', 'retryFn', 'request', 'logging']);
260
+ const raise = (optionPath, node) => {
261
+ report.addWarning({
262
+ title: `Reconfigure dropped query client option "${optionPath}"`,
263
+ summary: `The v2 client option \`${optionPath}\` has no place in \`createQueryClient\` and was dropped by the migration.`,
264
+ action: DROPPED_CLIENT_OPTIONS[optionPath] ?? 'Check whether the behaviour is still needed and reimplement it.',
265
+ locations: [{ filePath, line: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1 }],
266
+ source: 'query-client-migration',
267
+ dedupeKey: `dropped-client-option:${filePath}:${optionPath}`,
268
+ });
269
+ };
270
+ configArgument.properties.forEach((property) => {
271
+ if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) {
272
+ return;
273
+ }
274
+ const name = property.name.text;
275
+ if ((name === 'request' || name === 'logging') && ts.isObjectLiteralExpression(property.initializer)) {
276
+ property.initializer.properties.forEach((nested) => {
277
+ if (!ts.isPropertyAssignment(nested) || !ts.isIdentifier(nested.name)) {
278
+ return;
279
+ }
280
+ if (name === 'request' && ['queryParams', 'cacheAdapter', 'retryFn'].includes(nested.name.text)) {
281
+ return;
282
+ }
283
+ raise(`${name}.${nested.name.text}`, nested);
284
+ });
285
+ return;
286
+ }
287
+ if (!migrated.has(name)) {
288
+ raise(name, property);
289
+ }
290
+ });
291
+ };
292
+ const migrateConfigObject = (configArgument, node, sourceFile) => {
293
+ const variableName = getVariableNameForQueryClient(node);
294
+ const nextConfig = [];
295
+ const baseRouteProperty = configArgument.properties.find((property) => {
296
+ return ts.isPropertyAssignment(property) && ts.isIdentifier(property.name) && property.name.text === 'baseRoute';
297
+ });
298
+ if (baseRouteProperty && ts.isPropertyAssignment(baseRouteProperty)) {
299
+ const baseRouteValue = baseRouteProperty.initializer
300
+ .getText(sourceFile)
301
+ .replace('as `https://${string}`', '')
302
+ .trim();
303
+ nextConfig.push(`baseUrl: ${baseRouteValue}`);
304
+ }
305
+ nextConfig.push(`name: '${variableName}'`);
306
+ const requestProperty = configArgument.properties.find((property) => {
307
+ return ts.isPropertyAssignment(property) && ts.isIdentifier(property.name) && property.name.text === 'request';
308
+ });
309
+ if (requestProperty &&
310
+ ts.isPropertyAssignment(requestProperty) &&
311
+ ts.isObjectLiteralExpression(requestProperty.initializer)) {
312
+ requestProperty.initializer.properties.forEach((property) => {
313
+ if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) {
314
+ return;
315
+ }
316
+ if (property.name.text === 'queryParams') {
317
+ nextConfig.push(`queryString: ${property.initializer.getText(sourceFile)}`);
318
+ }
319
+ if (property.name.text === 'cacheAdapter') {
320
+ nextConfig.push(`cacheAdapter: ${property.initializer.getText(sourceFile)}`);
321
+ }
322
+ if (property.name.text === 'retryFn') {
323
+ nextConfig.push(`retryFn: ${property.initializer.getText(sourceFile)}`);
324
+ }
325
+ });
326
+ }
327
+ return `createQueryClient({\n ${nextConfig.join(',\n ')}\n})`;
328
+ };
329
+ const getVariableNameForQueryClient = (node) => {
330
+ let parent = node.parent;
331
+ while (parent) {
332
+ if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
333
+ return parent.name.text;
334
+ }
335
+ parent = parent.parent;
336
+ }
337
+ return 'client';
338
+ };
339
+ const extractClientConfigNames = (content) => {
340
+ const sourceFile = createSourceFile(content);
341
+ const configNames = [];
342
+ const visit = (node) => {
343
+ if (ts.isVariableDeclaration(node) &&
344
+ ts.isIdentifier(node.name) &&
345
+ node.initializer &&
346
+ ts.isCallExpression(node.initializer) &&
347
+ ts.isIdentifier(node.initializer.expression) &&
348
+ node.initializer.expression.text === 'createQueryClient') {
349
+ const configArgument = node.initializer.arguments[0];
350
+ if (!configArgument || !ts.isObjectLiteralExpression(configArgument)) {
351
+ ts.forEachChild(node, visit);
352
+ return;
353
+ }
354
+ const nameProperty = configArgument.properties.find((property) => {
355
+ return ts.isPropertyAssignment(property) && ts.isIdentifier(property.name) && property.name.text === 'name';
356
+ });
357
+ if (nameProperty && ts.isPropertyAssignment(nameProperty) && ts.isStringLiteral(nameProperty.initializer)) {
358
+ configNames.push(nameProperty.initializer.text);
359
+ }
360
+ }
361
+ ts.forEachChild(node, visit);
362
+ };
363
+ visit(sourceFile);
364
+ return configNames;
365
+ };
366
+ const addProviderAliasesToFile = (content, configNames) => {
367
+ const sourceFile = createSourceFile(content);
368
+ const insertions = [];
369
+ let result = content;
370
+ configNames.forEach((configName) => {
371
+ const variableName = ensureConfigSuffix(configName);
372
+ const position = getVariableStatementEnd(sourceFile, variableName, 'createQueryClient');
373
+ if (!position) {
374
+ return;
375
+ }
376
+ const { injectName, provideName } = getProviderAliasNames(variableName);
377
+ if (result.includes(`export const [${provideName}, ${injectName}] = ${variableName};`)) {
378
+ return;
379
+ }
380
+ insertions.push({
381
+ position,
382
+ text: `\n\nexport const [${provideName}, ${injectName}] = ${variableName};`,
383
+ });
384
+ });
385
+ insertions.sort((left, right) => right.position - left.position);
386
+ insertions.forEach(({ position, text }) => {
387
+ result = result.slice(0, position) + text + result.slice(position);
388
+ });
389
+ return result;
390
+ };
391
+ const getProviderAliasNames = (configVariableName) => {
392
+ const baseName = configVariableName.replace(/Config$/, '');
393
+ const capitalizedBaseName = capitalizeFirstLetter(baseName);
394
+ return {
395
+ provideName: `provide${capitalizedBaseName}`,
396
+ injectName: `inject${capitalizedBaseName}`,
397
+ };
398
+ };
399
+ const addQueryCreatorsToFile = (content, configNames) => {
400
+ const sourceFile = createSourceFile(content);
401
+ const insertions = [];
402
+ let result = content;
403
+ configNames.forEach((configName) => {
404
+ const variableName = ensureConfigSuffix(configName);
405
+ const position = getVariableStatementEnd(sourceFile, variableName, 'createQueryClient');
406
+ if (!position) {
407
+ return;
408
+ }
409
+ const creatorBlock = generateCreatorsForConfig(configName);
410
+ if (result.includes(creatorBlock.split('\n')[0])) {
411
+ return;
412
+ }
413
+ insertions.push({
414
+ position,
415
+ text: `\n\n${creatorBlock}`,
416
+ });
417
+ });
418
+ insertions.sort((left, right) => right.position - left.position);
419
+ insertions.forEach(({ position, text }) => {
420
+ result = result.slice(0, position) + text + result.slice(position);
421
+ });
422
+ return result;
423
+ };
424
+ const generateCreatorsForConfig = (configName) => {
425
+ const creatorBaseName = configName.replace('Client', '');
426
+ const configVariableName = ensureConfigSuffix(configName);
427
+ return [
428
+ `export const ${creatorBaseName}Get = createGetQuery(${configVariableName});`,
429
+ `export const ${creatorBaseName}Post = createPostQuery(${configVariableName});`,
430
+ `export const ${creatorBaseName}Put = createPutQuery(${configVariableName});`,
431
+ `export const ${creatorBaseName}Patch = createPatchQuery(${configVariableName});`,
432
+ `export const ${creatorBaseName}Delete = createDeleteQuery(${configVariableName});`,
433
+ ].join('\n');
434
+ };
435
+ const updateImportsInFile = (content, renames) => {
436
+ const sourceFile = createSourceFile(content);
437
+ const replacements = [];
438
+ const visit = (node) => {
439
+ if (ts.isImportDeclaration(node) &&
440
+ node.importClause?.namedBindings &&
441
+ ts.isNamedImports(node.importClause.namedBindings)) {
442
+ const nextElements = [];
443
+ let hasChanges = false;
444
+ node.importClause.namedBindings.elements.forEach((element) => {
445
+ const nextName = renames.get(element.name.text);
446
+ if (!nextName) {
447
+ nextElements.push(element.getText(sourceFile));
448
+ return;
449
+ }
450
+ hasChanges = true;
451
+ nextElements.push(element.propertyName ? `${nextName} as ${element.name.text}` : nextName);
452
+ });
453
+ if (hasChanges) {
454
+ replacements.push({
455
+ start: node.getStart(sourceFile),
456
+ end: node.getEnd(),
457
+ replacement: `import { ${nextElements.join(', ')} } from '${node.moduleSpecifier.text}';`,
458
+ });
459
+ }
460
+ }
461
+ if (ts.isIdentifier(node)) {
462
+ const nextName = renames.get(node.text);
463
+ if (!nextName) {
464
+ ts.forEachChild(node, visit);
465
+ return;
466
+ }
467
+ const parent = node.parent;
468
+ if (ts.isImportSpecifier(parent)) {
469
+ ts.forEachChild(node, visit);
470
+ return;
471
+ }
472
+ if (ts.isVariableDeclaration(parent) && parent.name === node) {
473
+ ts.forEachChild(node, visit);
474
+ return;
475
+ }
476
+ if (ts.isFunctionDeclaration(parent) && parent.name === node) {
477
+ ts.forEachChild(node, visit);
478
+ return;
479
+ }
480
+ if (ts.isMethodDeclaration(parent) && parent.name === node) {
481
+ ts.forEachChild(node, visit);
482
+ return;
483
+ }
484
+ if (ts.isParameter(parent) && parent.name === node) {
485
+ ts.forEachChild(node, visit);
486
+ return;
487
+ }
488
+ if (ts.isPropertyDeclaration(parent) && parent.name === node) {
489
+ ts.forEachChild(node, visit);
490
+ return;
491
+ }
492
+ if (ts.isPropertyAccessExpression(parent) && parent.name === node) {
493
+ ts.forEachChild(node, visit);
494
+ return;
495
+ }
496
+ if (ts.isPropertyAssignment(parent) && parent.name === node) {
497
+ ts.forEachChild(node, visit);
498
+ return;
499
+ }
500
+ replacements.push({
501
+ start: node.getStart(sourceFile),
502
+ end: node.getEnd(),
503
+ replacement: nextName,
504
+ });
505
+ }
506
+ ts.forEachChild(node, visit);
507
+ };
508
+ visit(sourceFile);
509
+ let result = content;
510
+ const seen = new Set();
511
+ const uniqueReplacements = replacements.filter((replacement) => {
512
+ const key = `${replacement.start}:${replacement.end}`;
513
+ if (seen.has(key)) {
514
+ return false;
515
+ }
516
+ seen.add(key);
517
+ return true;
518
+ });
519
+ uniqueReplacements.sort((left, right) => right.start - left.start);
520
+ uniqueReplacements.forEach(({ start, end, replacement }) => {
521
+ result = result.slice(0, start) + replacement + result.slice(end);
522
+ });
523
+ return result;
524
+ };
525
+ //# sourceMappingURL=query-client-migration.js.map