@ethlete/query 6.0.0-next.52 ā 6.0.0-next.54
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/CHANGELOG.md +53 -0
- package/fesm2022/ethlete-query-ngrx-toolkit.mjs +96 -24
- package/fesm2022/ethlete-query-ngrx-toolkit.mjs.map +1 -1
- package/fesm2022/ethlete-query-testing.mjs +67 -25
- package/fesm2022/ethlete-query-testing.mjs.map +1 -1
- package/fesm2022/ethlete-query.mjs +7872 -7354
- package/fesm2022/ethlete-query.mjs.map +1 -1
- package/generators/migrate-from-ngrx-toolkit/consumers.js +70 -2
- package/generators/migrate-from-ngrx-toolkit/migration.js +2 -1
- package/generators/migrate-to-query-v3/cleanup-migration.js +142 -53
- package/generators/migrate-to-query-v3/migration-scope.js +2 -0
- package/generators/migrate-to-query-v3/migration.js +3 -3
- package/generators/migrate-to-query-v3/module-graph.js +39 -29
- package/generators/migrate-to-query-v3/query-client-migration.js +138 -124
- package/generators/migrate-to-query-v3/rename-symbols.js +38 -11
- package/generators/migrate-to-query-v3/report.js +40 -7
- package/generators/migrate-to-query-v3/shared.js +19 -1
- package/generators/prep-for-query-v3/migration.js +317 -56
- package/package.json +1 -1
- package/types/ethlete-query-testing.d.ts +28 -11
- package/types/ethlete-query.d.ts +681 -570
|
@@ -5,6 +5,8 @@ export default async function migrate(tree, schema) {
|
|
|
5
5
|
console.log('\nš Starting Query v3 symbol conflict migration...');
|
|
6
6
|
renameConflictingSymbols(tree);
|
|
7
7
|
replaceExperimentalQueryNamespace(tree);
|
|
8
|
+
reportWithArgsNullReturns(tree);
|
|
9
|
+
replaceClearQueryArgs(tree);
|
|
8
10
|
reportPrebuiltPackagesImportingRenamedSymbols(tree);
|
|
9
11
|
if (!schema.skipFormat) {
|
|
10
12
|
await formatFiles(tree);
|
|
@@ -75,10 +77,19 @@ function renameConflictingSymbols(tree) {
|
|
|
75
77
|
}
|
|
76
78
|
function renameSymbolsInFile(content, filePath, symbolUsageCounts) {
|
|
77
79
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
80
|
+
const checker = createBindingChecker(sourceFile);
|
|
78
81
|
const replacements = [];
|
|
79
|
-
// Track which symbols are imported from @ethlete/query
|
|
80
|
-
const importedSymbols = new Set();
|
|
81
82
|
const namespaceAliases = collectQueryNamespaceAliases(sourceFile);
|
|
83
|
+
const renamedImports = new Map();
|
|
84
|
+
sourceFile.statements.forEach((statement) => {
|
|
85
|
+
getQueryNamedImports(statement)?.elements.forEach((element) => {
|
|
86
|
+
const newName = TYPE_RENAMES.get(element.name.text) || FUNCTION_RENAMES.get(element.name.text);
|
|
87
|
+
const symbol = checker.getSymbolAtLocation(element.name);
|
|
88
|
+
if (!element.propertyName && newName && symbol) {
|
|
89
|
+
renamedImports.set(symbol, { oldName: element.name.text, newName });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
82
93
|
function visit(node) {
|
|
83
94
|
const namespaceMember = getNamespaceMember(node, namespaceAliases)?.member;
|
|
84
95
|
const renamedMember = namespaceMember && (TYPE_RENAMES.get(namespaceMember.text) || FUNCTION_RENAMES.get(namespaceMember.text));
|
|
@@ -90,62 +101,29 @@ function renameSymbolsInFile(content, filePath, symbolUsageCounts) {
|
|
|
90
101
|
oldName: namespaceMember.text,
|
|
91
102
|
});
|
|
92
103
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
// Rename identifiers that match our rename maps
|
|
109
|
-
if (ts.isIdentifier(node)) {
|
|
110
|
-
const name = node.text;
|
|
111
|
-
const parent = node.parent;
|
|
112
|
-
// Only rename if this symbol was imported from @ethlete/query
|
|
113
|
-
if (!importedSymbols.has(name)) {
|
|
114
|
-
ts.forEachChild(node, visit);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
const newName = TYPE_RENAMES.get(name) || FUNCTION_RENAMES.get(name);
|
|
118
|
-
if (!newName) {
|
|
119
|
-
ts.forEachChild(node, visit);
|
|
120
|
-
return;
|
|
104
|
+
if (ts.isIdentifier(node) && !ts.isImportSpecifier(node.parent)) {
|
|
105
|
+
const symbol = resolveReferenceSymbol(checker, node);
|
|
106
|
+
const rename = symbol && renamedImports.get(symbol);
|
|
107
|
+
if (rename) {
|
|
108
|
+
replacements.push({
|
|
109
|
+
start: node.getStart(sourceFile),
|
|
110
|
+
end: node.getEnd(),
|
|
111
|
+
replacement: ts.isShorthandPropertyAssignment(node.parent)
|
|
112
|
+
? `${rename.oldName}: ${rename.newName}`
|
|
113
|
+
: rename.newName,
|
|
114
|
+
oldName: rename.oldName,
|
|
115
|
+
});
|
|
121
116
|
}
|
|
122
|
-
// Skip if it's the imported name in an import specifier (we'll handle that separately)
|
|
123
|
-
if ((ts.isImportSpecifier(parent) && (parent.propertyName === node || parent.name === node)) ||
|
|
124
|
-
(ts.isPropertyAccessExpression(parent) && parent.name === node) ||
|
|
125
|
-
(ts.isQualifiedName(parent) && parent.right === node)) {
|
|
126
|
-
ts.forEachChild(node, visit);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
// Rename the identifier
|
|
130
|
-
replacements.push({
|
|
131
|
-
start: node.getStart(sourceFile),
|
|
132
|
-
end: node.getEnd(),
|
|
133
|
-
replacement: newName,
|
|
134
|
-
oldName: name,
|
|
135
|
-
});
|
|
136
117
|
}
|
|
137
118
|
ts.forEachChild(node, visit);
|
|
138
119
|
}
|
|
139
120
|
visit(sourceFile);
|
|
140
|
-
// Update imports in @ethlete/query import declarations
|
|
141
121
|
const importReplacements = updateImports(sourceFile);
|
|
142
122
|
replacements.push(...importReplacements);
|
|
143
|
-
// Count usages
|
|
144
123
|
replacements.forEach((r) => {
|
|
145
124
|
const count = symbolUsageCounts.get(r.oldName) || 0;
|
|
146
125
|
symbolUsageCounts.set(r.oldName, count + 1);
|
|
147
126
|
});
|
|
148
|
-
// Apply replacements in reverse order
|
|
149
127
|
let result = content;
|
|
150
128
|
replacements.sort((a, b) => b.start - a.start);
|
|
151
129
|
for (const { start, end, replacement } of replacements) {
|
|
@@ -153,6 +131,26 @@ function renameSymbolsInFile(content, filePath, symbolUsageCounts) {
|
|
|
153
131
|
}
|
|
154
132
|
return result;
|
|
155
133
|
}
|
|
134
|
+
function createBindingChecker(sourceFile) {
|
|
135
|
+
const options = { noLib: true, noResolve: true, types: [] };
|
|
136
|
+
const host = ts.createCompilerHost(options);
|
|
137
|
+
host.getSourceFile = (fileName) => (fileName === sourceFile.fileName ? sourceFile : undefined);
|
|
138
|
+
host.fileExists = (fileName) => fileName === sourceFile.fileName;
|
|
139
|
+
host.readFile = (fileName) => (fileName === sourceFile.fileName ? sourceFile.text : undefined);
|
|
140
|
+
return ts.createProgram([sourceFile.fileName], options, host).getTypeChecker();
|
|
141
|
+
}
|
|
142
|
+
function resolveReferenceSymbol(checker, identifier) {
|
|
143
|
+
const parent = identifier.parent;
|
|
144
|
+
if (ts.isShorthandPropertyAssignment(parent) && parent.name === identifier) {
|
|
145
|
+
return checker.getShorthandAssignmentValueSymbol(parent);
|
|
146
|
+
}
|
|
147
|
+
if (ts.isExportSpecifier(parent)) {
|
|
148
|
+
return (parent.propertyName ?? parent.name) === identifier && !parent.parent.parent.moduleSpecifier
|
|
149
|
+
? checker.getExportSpecifierLocalTargetSymbol(parent)
|
|
150
|
+
: undefined;
|
|
151
|
+
}
|
|
152
|
+
return checker.getSymbolAtLocation(identifier);
|
|
153
|
+
}
|
|
156
154
|
function collectQueryNamespaceAliases(sourceFile) {
|
|
157
155
|
const aliases = new Set();
|
|
158
156
|
sourceFile.statements.forEach((statement) => {
|
|
@@ -287,14 +285,13 @@ function replaceNamespaceInFile(content, filePath) {
|
|
|
287
285
|
if (!hasExperimentalQueryImport || !experimentalQueryAlias) {
|
|
288
286
|
return applyReplacements(content, namespaceReplacements, namespaceReplacements.length);
|
|
289
287
|
}
|
|
288
|
+
const getAliasMember = (node) => getNamespaceMember(node, new Set([experimentalQueryAlias]))?.member;
|
|
290
289
|
// Second pass: collect used symbols
|
|
291
290
|
function findUsages(node) {
|
|
292
291
|
// Find usages: ExperimentalQuery.someSymbol or E.someSymbol (if aliased)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const symbolName = node.name.text;
|
|
297
|
-
usedSymbols.add(symbolName);
|
|
292
|
+
const member = getAliasMember(node);
|
|
293
|
+
if (member) {
|
|
294
|
+
usedSymbols.add(member.text);
|
|
298
295
|
}
|
|
299
296
|
ts.forEachChild(node, findUsages);
|
|
300
297
|
}
|
|
@@ -352,13 +349,12 @@ function replaceNamespaceInFile(content, filePath) {
|
|
|
352
349
|
}
|
|
353
350
|
}
|
|
354
351
|
// Replace usages: ExperimentalQuery.someSymbol ā someSymbol
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
node.expression.text === experimentalQueryAlias) {
|
|
352
|
+
const member = getAliasMember(node);
|
|
353
|
+
if (member) {
|
|
358
354
|
replacements.push({
|
|
359
355
|
start: node.getStart(sourceFile),
|
|
360
356
|
end: node.getEnd(),
|
|
361
|
-
replacement:
|
|
357
|
+
replacement: member.text,
|
|
362
358
|
});
|
|
363
359
|
}
|
|
364
360
|
ts.forEachChild(node, createReplacements);
|
|
@@ -391,6 +387,271 @@ function applyReplacements(content, replacements, replacementCount) {
|
|
|
391
387
|
});
|
|
392
388
|
return { content: result, modified: replacements.length > 0, replacementCount };
|
|
393
389
|
}
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region withArgs null returns
|
|
392
|
+
function reportWithArgsNullReturns(tree) {
|
|
393
|
+
const sites = [];
|
|
394
|
+
const uncheckedSites = [];
|
|
395
|
+
visitQueryFiles(tree, ['withArgs'], (filePath, content) => {
|
|
396
|
+
const { nullLines, uncheckedLines } = findWithArgsNullReturns(createSourceFile(content, filePath));
|
|
397
|
+
nullLines.forEach((line) => sites.push(` - ${filePath}:${line}`));
|
|
398
|
+
uncheckedLines.forEach((line) => uncheckedSites.push(` - ${filePath}:${line}`));
|
|
399
|
+
});
|
|
400
|
+
if (sites.length > 0) {
|
|
401
|
+
console.warn(`\nā ļø These withArgs callbacks return null. Before v3, null kept the previous args; v3 parks the query instead (args, response and executionState become null, and polling pauses). Check each one: a null that should park the query is fine, one that relied on keeping the previous args must now return those args itself:\n${sites.join('\n')}`);
|
|
402
|
+
}
|
|
403
|
+
if (uncheckedSites.length > 0) {
|
|
404
|
+
console.warn(`\nā ļø TODO: These withArgs callbacks are passed by reference from somewhere this file does not declare, so they could not be checked for a null return. Check each one by hand - v3 parks the query on null, where earlier versions kept the previous args:\n${uncheckedSites.join('\n')}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const isCheckableFunction = (node) => !!node &&
|
|
408
|
+
(ts.isArrowFunction(node) ||
|
|
409
|
+
ts.isFunctionExpression(node) ||
|
|
410
|
+
ts.isFunctionDeclaration(node) ||
|
|
411
|
+
ts.isMethodDeclaration(node)) &&
|
|
412
|
+
!!node.body;
|
|
413
|
+
function resolveCallbackReference(checker, source) {
|
|
414
|
+
const name = ts.isPropertyAccessExpression(source) ? source.name : source;
|
|
415
|
+
if (!ts.isIdentifier(name))
|
|
416
|
+
return undefined;
|
|
417
|
+
const declaration = checker.getSymbolAtLocation(name)?.valueDeclaration;
|
|
418
|
+
if (isCheckableFunction(declaration))
|
|
419
|
+
return declaration;
|
|
420
|
+
if (!declaration || !(ts.isVariableDeclaration(declaration) || ts.isPropertyDeclaration(declaration))) {
|
|
421
|
+
return undefined;
|
|
422
|
+
}
|
|
423
|
+
const initializer = declaration.initializer && skipOuterExpressions(declaration.initializer);
|
|
424
|
+
if (isCheckableFunction(initializer))
|
|
425
|
+
return initializer;
|
|
426
|
+
if (initializer &&
|
|
427
|
+
ts.isCallExpression(initializer) &&
|
|
428
|
+
ts.isIdentifier(initializer.expression) &&
|
|
429
|
+
initializer.expression.text === 'computed') {
|
|
430
|
+
const [computation] = initializer.arguments;
|
|
431
|
+
return isCheckableFunction(computation) ? computation : undefined;
|
|
432
|
+
}
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
435
|
+
function skipOuterExpressions(expression) {
|
|
436
|
+
return ts.isParenthesizedExpression(expression) ||
|
|
437
|
+
ts.isAsExpression(expression) ||
|
|
438
|
+
ts.isSatisfiesExpression(expression)
|
|
439
|
+
? skipOuterExpressions(expression.expression)
|
|
440
|
+
: expression;
|
|
441
|
+
}
|
|
442
|
+
function findWithArgsNullReturns(sourceFile) {
|
|
443
|
+
const checker = createBindingChecker(sourceFile);
|
|
444
|
+
const withArgsImports = new Set();
|
|
445
|
+
const namespaceAliases = collectQueryNamespaceAliases(sourceFile);
|
|
446
|
+
const nullLines = [];
|
|
447
|
+
const uncheckedLines = [];
|
|
448
|
+
const lineOf = (node) => sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
449
|
+
sourceFile.statements.forEach((statement) => {
|
|
450
|
+
getQueryNamedImports(statement)?.elements.forEach((element) => {
|
|
451
|
+
const symbol = checker.getSymbolAtLocation(element.name);
|
|
452
|
+
if ((element.propertyName ?? element.name).text === 'withArgs' && symbol)
|
|
453
|
+
withArgsImports.add(symbol);
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
const isWithArgsCallee = (callee) => {
|
|
457
|
+
if (ts.isIdentifier(callee)) {
|
|
458
|
+
const symbol = checker.getSymbolAtLocation(callee);
|
|
459
|
+
return !!symbol && withArgsImports.has(symbol);
|
|
460
|
+
}
|
|
461
|
+
return getNamespaceMember(callee, namespaceAliases)?.member.text === 'withArgs';
|
|
462
|
+
};
|
|
463
|
+
function visit(node) {
|
|
464
|
+
if (ts.isCallExpression(node) && isWithArgsCallee(node.expression)) {
|
|
465
|
+
const [source] = node.arguments;
|
|
466
|
+
const callback = source && (isCheckableFunction(source) ? source : resolveCallbackReference(checker, source));
|
|
467
|
+
if (callback) {
|
|
468
|
+
collectNullReturns(callback).forEach((nullNode) => nullLines.push(lineOf(nullNode)));
|
|
469
|
+
}
|
|
470
|
+
else if (source) {
|
|
471
|
+
uncheckedLines.push(lineOf(source));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
ts.forEachChild(node, visit);
|
|
475
|
+
}
|
|
476
|
+
visit(sourceFile);
|
|
477
|
+
return { nullLines: [...new Set(nullLines)].sort((a, b) => a - b), uncheckedLines };
|
|
478
|
+
}
|
|
479
|
+
function collectNullReturns(fn) {
|
|
480
|
+
if (!ts.isBlock(fn.body)) {
|
|
481
|
+
return findNullResults(fn.body);
|
|
482
|
+
}
|
|
483
|
+
const found = [];
|
|
484
|
+
function visit(node) {
|
|
485
|
+
if (ts.isFunctionLike(node))
|
|
486
|
+
return;
|
|
487
|
+
if (ts.isReturnStatement(node) && node.expression) {
|
|
488
|
+
found.push(...findNullResults(node.expression));
|
|
489
|
+
}
|
|
490
|
+
ts.forEachChild(node, visit);
|
|
491
|
+
}
|
|
492
|
+
ts.forEachChild(fn.body, visit);
|
|
493
|
+
return found;
|
|
494
|
+
}
|
|
495
|
+
function findNullResults(expression) {
|
|
496
|
+
if (expression.kind === ts.SyntaxKind.NullKeyword)
|
|
497
|
+
return [expression];
|
|
498
|
+
if (ts.isParenthesizedExpression(expression) ||
|
|
499
|
+
ts.isAsExpression(expression) ||
|
|
500
|
+
ts.isSatisfiesExpression(expression) ||
|
|
501
|
+
ts.isTypeAssertionExpression(expression)) {
|
|
502
|
+
return findNullResults(expression.expression);
|
|
503
|
+
}
|
|
504
|
+
if (ts.isConditionalExpression(expression)) {
|
|
505
|
+
return [...findNullResults(expression.whenTrue), ...findNullResults(expression.whenFalse)];
|
|
506
|
+
}
|
|
507
|
+
if (ts.isBinaryExpression(expression) &&
|
|
508
|
+
[ts.SyntaxKind.QuestionQuestionToken, ts.SyntaxKind.BarBarToken, ts.SyntaxKind.AmpersandAmpersandToken].includes(expression.operatorToken.kind)) {
|
|
509
|
+
return findNullResults(expression.right);
|
|
510
|
+
}
|
|
511
|
+
return [];
|
|
512
|
+
}
|
|
513
|
+
//#endregion
|
|
514
|
+
//#region CLEAR_QUERY_ARGS replacement
|
|
515
|
+
const CLEAR_QUERY_ARGS_NAMES = new Set(['CLEAR_QUERY_ARGS', 'ClearQueryArgs']);
|
|
516
|
+
function replaceClearQueryArgs(tree) {
|
|
517
|
+
const updatedFiles = [];
|
|
518
|
+
const reExports = [];
|
|
519
|
+
visitQueryFiles(tree, [...CLEAR_QUERY_ARGS_NAMES], (filePath, content) => {
|
|
520
|
+
const result = replaceClearQueryArgsInFile(content, filePath);
|
|
521
|
+
if (result.content !== content) {
|
|
522
|
+
tree.write(filePath, result.content);
|
|
523
|
+
updatedFiles.push(filePath);
|
|
524
|
+
}
|
|
525
|
+
result.reExportLines.forEach((line) => reExports.push(` - ${filePath}:${line}`));
|
|
526
|
+
});
|
|
527
|
+
if (updatedFiles.length > 0) {
|
|
528
|
+
console.log(`\nš
æļø Replaced CLEAR_QUERY_ARGS with null (the v3 park value) in ${updatedFiles.length} files: ${updatedFiles.join(', ')}`);
|
|
529
|
+
}
|
|
530
|
+
if (reExports.length > 0) {
|
|
531
|
+
console.warn(`\nā ļø These files re-exported CLEAR_QUERY_ARGS or ClearQueryArgs. They now export a local \`null\` under the same name, so their importers keep compiling. Replace each import of it with \`null\`, then delete the export:\n${reExports.join('\n')}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function replaceClearQueryArgsInFile(content, filePath) {
|
|
535
|
+
const sourceFile = createSourceFile(content, filePath);
|
|
536
|
+
const checker = createBindingChecker(sourceFile);
|
|
537
|
+
const namespaceAliases = collectQueryNamespaceAliases(sourceFile);
|
|
538
|
+
const clearImports = new Map();
|
|
539
|
+
const replacements = [];
|
|
540
|
+
const reExportLines = [];
|
|
541
|
+
const statementEnd = (statement) => content[statement.getEnd()] === '\n' ? statement.getEnd() + 1 : statement.getEnd();
|
|
542
|
+
sourceFile.statements.forEach((statement) => {
|
|
543
|
+
const namedBindings = getQueryNamedImports(statement);
|
|
544
|
+
if (!namedBindings)
|
|
545
|
+
return;
|
|
546
|
+
const kept = namedBindings.elements.filter((element) => {
|
|
547
|
+
const importedName = (element.propertyName ?? element.name).text;
|
|
548
|
+
if (!CLEAR_QUERY_ARGS_NAMES.has(importedName))
|
|
549
|
+
return true;
|
|
550
|
+
const symbol = checker.getSymbolAtLocation(element.name);
|
|
551
|
+
if (symbol)
|
|
552
|
+
clearImports.set(symbol, importedName);
|
|
553
|
+
return false;
|
|
554
|
+
});
|
|
555
|
+
if (kept.length === namedBindings.elements.length)
|
|
556
|
+
return;
|
|
557
|
+
const importClause = statement.importClause;
|
|
558
|
+
if (kept.length === 0 && !importClause?.name) {
|
|
559
|
+
replacements.push({ start: statement.getStart(sourceFile), end: statementEnd(statement), replacement: '' });
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
replacements.push({
|
|
563
|
+
start: namedBindings.getStart(sourceFile),
|
|
564
|
+
end: namedBindings.getEnd(),
|
|
565
|
+
replacement: `{ ${kept.map((element) => element.getText(sourceFile)).join(', ')} }`,
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
sourceFile.statements.forEach((statement) => {
|
|
569
|
+
if (!ts.isExportDeclaration(statement) || !statement.exportClause || !ts.isNamedExports(statement.exportClause)) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const fromQuery = isQueryModuleSpecifier(statement.moduleSpecifier);
|
|
573
|
+
if (statement.moduleSpecifier && !fromQuery)
|
|
574
|
+
return;
|
|
575
|
+
const clearedName = (element) => {
|
|
576
|
+
if (fromQuery) {
|
|
577
|
+
const exportedName = (element.propertyName ?? element.name).text;
|
|
578
|
+
return CLEAR_QUERY_ARGS_NAMES.has(exportedName) ? exportedName : undefined;
|
|
579
|
+
}
|
|
580
|
+
const symbol = checker.getExportSpecifierLocalTargetSymbol(element);
|
|
581
|
+
return symbol && clearImports.get(symbol);
|
|
582
|
+
};
|
|
583
|
+
const elements = statement.exportClause.elements;
|
|
584
|
+
const nullExports = elements.flatMap((element) => {
|
|
585
|
+
const name = clearedName(element);
|
|
586
|
+
if (!name)
|
|
587
|
+
return [];
|
|
588
|
+
return [`export ${name === 'ClearQueryArgs' ? 'type' : 'const'} ${element.name.text} = null;`];
|
|
589
|
+
});
|
|
590
|
+
if (nullExports.length === 0)
|
|
591
|
+
return;
|
|
592
|
+
reExportLines.push(sourceFile.getLineAndCharacterOfPosition(statement.getStart(sourceFile)).line + 1);
|
|
593
|
+
const kept = elements.filter((element) => !clearedName(element));
|
|
594
|
+
if (kept.length === 0) {
|
|
595
|
+
replacements.push({
|
|
596
|
+
start: statement.getStart(sourceFile),
|
|
597
|
+
end: statement.getEnd(),
|
|
598
|
+
replacement: nullExports.join('\n'),
|
|
599
|
+
});
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
replacements.push({
|
|
603
|
+
start: statement.exportClause.getStart(sourceFile),
|
|
604
|
+
end: statement.exportClause.getEnd(),
|
|
605
|
+
replacement: `{ ${kept.map((element) => element.getText(sourceFile)).join(', ')} }`,
|
|
606
|
+
});
|
|
607
|
+
replacements.push({
|
|
608
|
+
start: statement.getEnd(),
|
|
609
|
+
end: statement.getEnd(),
|
|
610
|
+
replacement: `\n${nullExports.join('\n')}`,
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
function visit(node) {
|
|
614
|
+
const namespaceMember = getNamespaceMember(node, namespaceAliases)?.member;
|
|
615
|
+
const referencesImport = () => {
|
|
616
|
+
const symbol = ts.isIdentifier(node) ? resolveReferenceSymbol(checker, node) : undefined;
|
|
617
|
+
return !!symbol && clearImports.has(symbol);
|
|
618
|
+
};
|
|
619
|
+
if ((namespaceMember && CLEAR_QUERY_ARGS_NAMES.has(namespaceMember.text)) || referencesImport()) {
|
|
620
|
+
const target = ts.isTypeQueryNode(node.parent) ? node.parent : node;
|
|
621
|
+
const replacement = ts.isShorthandPropertyAssignment(node.parent) ? `${node.getText(sourceFile)}: null` : 'null';
|
|
622
|
+
replacements.push({ start: target.getStart(sourceFile), end: target.getEnd(), replacement });
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
ts.forEachChild(node, visit);
|
|
626
|
+
}
|
|
627
|
+
sourceFile.statements.forEach((statement) => {
|
|
628
|
+
if (!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement))
|
|
629
|
+
visit(statement);
|
|
630
|
+
});
|
|
631
|
+
return { content: applyReplacements(content, replacements, replacements.length).content, reExportLines };
|
|
632
|
+
}
|
|
633
|
+
function isQueryModuleSpecifier(node) {
|
|
634
|
+
return !!node && ts.isStringLiteral(node) && node.text === '@ethlete/query';
|
|
635
|
+
}
|
|
636
|
+
function visitQueryFiles(tree, markers, visitor) {
|
|
637
|
+
visitNotIgnoredFiles(tree, '', (filePath) => {
|
|
638
|
+
if (!filePath.endsWith('.ts') || filePath.endsWith('.spec.ts'))
|
|
639
|
+
return;
|
|
640
|
+
const content = tree.read(filePath, 'utf-8');
|
|
641
|
+
if (!content?.includes('@ethlete/query') || !markers.some((marker) => content.includes(marker)))
|
|
642
|
+
return;
|
|
643
|
+
visitor(filePath, content);
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
function getQueryNamedImports(statement) {
|
|
647
|
+
if (!ts.isImportDeclaration(statement) ||
|
|
648
|
+
!ts.isStringLiteral(statement.moduleSpecifier) ||
|
|
649
|
+
statement.moduleSpecifier.text !== '@ethlete/query') {
|
|
650
|
+
return undefined;
|
|
651
|
+
}
|
|
652
|
+
const namedBindings = statement.importClause?.namedBindings;
|
|
653
|
+
return namedBindings && ts.isNamedImports(namedBindings) ? namedBindings : undefined;
|
|
654
|
+
}
|
|
394
655
|
const isRenamedSymbol = (name) => TYPE_RENAMES.has(name) || FUNCTION_RENAMES.has(name);
|
|
395
656
|
function reportPrebuiltPackagesImportingRenamedSymbols(tree) {
|
|
396
657
|
if (!tree.exists('package.json'))
|
package/package.json
CHANGED
|
@@ -124,7 +124,7 @@ type AuthTestSetup<TLoginArgs extends {
|
|
|
124
124
|
], TFeatures, TBearerData>;
|
|
125
125
|
/** Helper to login a user and flush the HTTP request */
|
|
126
126
|
login: (credentials: TLoginArgs['body'], response: TLoginArgs['response']) => void;
|
|
127
|
-
/** Helper to trigger a refresh and flush the HTTP request */
|
|
127
|
+
/** Helper to trigger a refresh and flush the HTTP request. Sends `buildRefreshArgs(token)` when configured, else `{ body: { token } }` */
|
|
128
128
|
refresh: (token: string, response: TRefreshArgs['response']) => void;
|
|
129
129
|
/** Helper to make a secure request that requires authentication */
|
|
130
130
|
makeSecureRequest: (route: string) => void;
|
|
@@ -184,9 +184,9 @@ type FakeBroadcastChannelHandle = {
|
|
|
184
184
|
* Replaces `globalThis.BroadcastChannel` with an in-memory bus.
|
|
185
185
|
*
|
|
186
186
|
* Messages are structured-cloned at post time (so a non-cloneable payload throws exactly like the
|
|
187
|
-
* real API does), delivered
|
|
188
|
-
* channel that posted them - all three matching browser behavior.
|
|
189
|
-
*
|
|
187
|
+
* real API does), delivered as a task (never synchronously, and after every pending microtask), and
|
|
188
|
+
* never delivered back to the channel that posted them - all three matching browser behavior. Delivery
|
|
189
|
+
* does not depend on fake timers; `await flushMultiTabSync()` in a spec flushes it.
|
|
190
190
|
*/
|
|
191
191
|
declare const installFakeBroadcastChannel: () => FakeBroadcastChannelHandle;
|
|
192
192
|
type FakeWebLocksHandle = {
|
|
@@ -211,9 +211,9 @@ type FakeWebLocksHandle = {
|
|
|
211
211
|
*/
|
|
212
212
|
declare const installFakeWebLocks: () => FakeWebLocksHandle;
|
|
213
213
|
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* tab's release needs one await per hop.
|
|
214
|
+
* Waits for the fakes' pending work - message delivery (a task) and lock grants (a microtask) - even
|
|
215
|
+
* under fake timers. Awaiting it once is enough for a single hop; a grant that has to wait for another
|
|
216
|
+
* tab's release, or a reply to a delivered message, needs one await per hop.
|
|
217
217
|
*/
|
|
218
218
|
declare const flushMultiTabSync: () => Promise<void>;
|
|
219
219
|
|
|
@@ -247,7 +247,7 @@ type FakeQueryPersistenceStoreHandle = {
|
|
|
247
247
|
/**
|
|
248
248
|
* Holds every subsequent `read` pending until {@link flushReads} is called, for testing the window
|
|
249
249
|
* between a cold mount and its hydration (a slow disk, or a mount that happened before startup
|
|
250
|
-
* finished).
|
|
250
|
+
* finished). Each held read returns what the store held when it started, as `loadIndex` does.
|
|
251
251
|
*/
|
|
252
252
|
deferReads: () => void;
|
|
253
253
|
/** Settles the reads held by {@link deferReads} and stops deferring. */
|
|
@@ -295,10 +295,16 @@ type WebSocketTestDouble = {
|
|
|
295
295
|
event: string;
|
|
296
296
|
data: unknown;
|
|
297
297
|
}[];
|
|
298
|
-
/**
|
|
298
|
+
/**
|
|
299
|
+
* Whether the client asked the socket to connect, and whether it has since disconnected it. `active` is
|
|
300
|
+
* socket.io's own flag - `false` after a server disconnect or a rejected handshake, until the next
|
|
301
|
+
* `connect()`; `connectCalls` counts every `connect()`.
|
|
302
|
+
*/
|
|
299
303
|
state: () => {
|
|
300
304
|
connectRequested: boolean;
|
|
301
305
|
disconnected: boolean;
|
|
306
|
+
active: boolean;
|
|
307
|
+
connectCalls: number;
|
|
302
308
|
};
|
|
303
309
|
/**
|
|
304
310
|
* Complete the handshake: flush every buffered emit, then turn the client's `isConnected` true. Pass
|
|
@@ -307,8 +313,19 @@ type WebSocketTestDouble = {
|
|
|
307
313
|
serverConnect: (options?: {
|
|
308
314
|
recovered?: boolean;
|
|
309
315
|
}) => void;
|
|
310
|
-
/**
|
|
311
|
-
|
|
316
|
+
/**
|
|
317
|
+
* Drop the connection, so `isConnected` turns false. The default reason, `'transport close'`, is one
|
|
318
|
+
* socket.io recovers from itself; `'io server disconnect'` is the server kicking the socket, after which
|
|
319
|
+
* socket.io only reconnects when `connect()` is called again.
|
|
320
|
+
*/
|
|
321
|
+
serverDisconnect: (options?: {
|
|
322
|
+
reason?: 'transport close' | 'io server disconnect';
|
|
323
|
+
}) => void;
|
|
324
|
+
/**
|
|
325
|
+
* Reject the handshake the way a server middleware does: the `auth` payload is recorded in
|
|
326
|
+
* {@link handshakes}, `connect_error` fires and the socket stops reconnecting on its own.
|
|
327
|
+
*/
|
|
328
|
+
serverRejectHandshake: (message?: string) => void;
|
|
312
329
|
/**
|
|
313
330
|
* Let the ping expire: the socket still reports itself connected, but buffers every emit like socket.io
|
|
314
331
|
* does until it notices and closes. Follow it with {@link serverDisconnect}.
|