@ethlete/query 6.0.0-next.6 → 6.0.0-next.61
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 +945 -0
- package/fesm2022/ethlete-query-devtools-contract.mjs +1 -0
- package/fesm2022/ethlete-query-ngrx-toolkit.mjs +411 -0
- package/fesm2022/ethlete-query-ngrx-toolkit.mjs.map +1 -0
- package/fesm2022/ethlete-query-testing.mjs +548 -169
- package/fesm2022/ethlete-query-testing.mjs.map +1 -1
- package/fesm2022/ethlete-query.mjs +14680 -7172
- package/fesm2022/ethlete-query.mjs.map +1 -1
- package/generators/deprecate-legacy-queries/migration.js +85 -0
- package/generators/deprecate-legacy-queries/schema.json +26 -0
- package/generators/generators.json +30 -0
- package/generators/migrate-from-ngrx-toolkit/args-assignability.js +140 -0
- package/generators/migrate-from-ngrx-toolkit/consumers.js +521 -0
- package/generators/migrate-from-ngrx-toolkit/creators.js +301 -0
- package/generators/migrate-from-ngrx-toolkit/facade.js +295 -0
- package/generators/migrate-from-ngrx-toolkit/feature.js +513 -0
- package/generators/migrate-from-ngrx-toolkit/migration.js +140 -0
- package/generators/migrate-from-ngrx-toolkit/report.js +154 -0
- package/generators/migrate-from-ngrx-toolkit/schema.json +42 -0
- package/generators/migrate-from-ngrx-toolkit/store-removal.js +633 -0
- package/generators/migrate-from-ngrx-toolkit/ts-edits.js +128 -0
- package/generators/migrate-query-client-features/migration.js +144 -0
- package/generators/migrate-query-client-features/report.js +136 -0
- package/generators/migrate-query-client-features/schema.json +26 -0
- package/generators/migrate-query-opt-in-features/migration.js +195 -0
- package/generators/migrate-query-opt-in-features/report.js +143 -0
- package/generators/migrate-query-opt-in-features/schema.json +31 -0
- package/generators/migrate-to-query-v3/auth-provider-scaffold.js +160 -0
- package/generators/migrate-to-query-v3/cleanup-migration.js +247 -125
- package/generators/migrate-to-query-v3/http-client-check.js +93 -0
- package/generators/migrate-to-query-v3/legacy-prepare-migration.js +129 -40
- package/generators/migrate-to-query-v3/legacy-query-creator-migration.js +274 -152
- package/generators/migrate-to-query-v3/migration-scope.js +40 -0
- package/generators/migrate-to-query-v3/migration.js +19 -10
- package/generators/migrate-to-query-v3/module-graph.js +165 -0
- package/generators/migrate-to-query-v3/query-client-migration.js +342 -160
- package/generators/migrate-to-query-v3/rename-symbols.js +233 -0
- package/generators/migrate-to-query-v3/report.js +40 -7
- package/generators/migrate-to-query-v3/schema.json +12 -0
- package/generators/migrate-to-query-v3/shared.js +97 -3
- package/generators/move-devtools-contract-imports/migration.js +155 -0
- package/generators/move-devtools-contract-imports/moved-names.js +153 -0
- package/generators/move-devtools-contract-imports/schema.json +26 -0
- package/generators/prep-for-query-v3/migration.js +465 -65
- package/generators/report-legacy-query-apis/migration.js +256 -0
- package/generators/report-legacy-query-apis/schema.json +25 -0
- package/migrations/query-field-default-types.md +43 -0
- package/migrations/search-query-field-string.md +43 -0
- package/migrations.json +87 -0
- package/package.json +34 -14
- package/types/ethlete-query-devtools-contract.d.ts +2 -0
- package/types/ethlete-query-ngrx-toolkit.d.ts +131 -0
- package/types/ethlete-query-testing.d.ts +295 -160
- package/types/ethlete-query.d.ts +9755 -3682
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { formatFiles } from '@nx/devkit';
|
|
2
|
+
import { writeFileSync } from 'fs';
|
|
3
|
+
import * as ts from 'typescript';
|
|
4
|
+
import { createMigrationScope } from '../migrate-to-query-v3/migration-scope.js';
|
|
5
|
+
import { addJsDocTag, createSourceFile, legacyQueryDeprecationTag } from '../migrate-to-query-v3/shared.js';
|
|
6
|
+
const CREATOR_FN = 'createLegacyQueryCreator';
|
|
7
|
+
const isMigratableFile = (filePath) => /\.(ts|mts|cts)$/.test(filePath) && !filePath.endsWith('.d.ts');
|
|
8
|
+
const creatorNameOf = (call, sourceFile) => {
|
|
9
|
+
const [options] = call.arguments;
|
|
10
|
+
if (!options || !ts.isObjectLiteralExpression(options)) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
const property = options.properties.find((candidate) => ts.isPropertyAssignment(candidate) && candidate.name.getText(sourceFile) === 'creator');
|
|
14
|
+
if (!property || !ts.isIdentifier(property.initializer)) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
return property.initializer.text;
|
|
18
|
+
};
|
|
19
|
+
const collectWrappers = (sourceFile) => {
|
|
20
|
+
const wrappers = [];
|
|
21
|
+
const visit = (node) => {
|
|
22
|
+
if (ts.isVariableDeclaration(node) &&
|
|
23
|
+
node.initializer &&
|
|
24
|
+
ts.isCallExpression(node.initializer) &&
|
|
25
|
+
ts.isIdentifier(node.initializer.expression) &&
|
|
26
|
+
node.initializer.expression.text === CREATOR_FN) {
|
|
27
|
+
const statement = node.parent.parent;
|
|
28
|
+
// A wrapper declared inside a function body has no importable call sites to strike through.
|
|
29
|
+
if (ts.isVariableStatement(statement) && ts.isSourceFile(statement.parent)) {
|
|
30
|
+
wrappers.push({ statement, creatorName: creatorNameOf(node.initializer, sourceFile) });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
ts.forEachChild(node, visit);
|
|
34
|
+
};
|
|
35
|
+
visit(sourceFile);
|
|
36
|
+
return wrappers;
|
|
37
|
+
};
|
|
38
|
+
export const deprecateLegacyQueries = (options) => {
|
|
39
|
+
const { tree, scope, scanOnly = false } = options;
|
|
40
|
+
let deprecatedWrappers = 0;
|
|
41
|
+
const files = [];
|
|
42
|
+
scope.visit(tree, (filePath) => {
|
|
43
|
+
if (!isMigratableFile(filePath))
|
|
44
|
+
return;
|
|
45
|
+
const content = tree.read(filePath, 'utf-8');
|
|
46
|
+
if (!content?.includes(CREATOR_FN))
|
|
47
|
+
return;
|
|
48
|
+
const sourceFile = createSourceFile(content, filePath);
|
|
49
|
+
const wrappers = collectWrappers(sourceFile);
|
|
50
|
+
let nextContent = content;
|
|
51
|
+
let tagged = 0;
|
|
52
|
+
// Back to front, so an insertion never shifts the position the next wrapper was parsed at.
|
|
53
|
+
for (const wrapper of wrappers.sort((a, b) => b.statement.getStart(sourceFile) - a.statement.getStart(sourceFile))) {
|
|
54
|
+
const updated = addJsDocTag(nextContent, sourceFile, wrapper.statement, legacyQueryDeprecationTag(wrapper.creatorName));
|
|
55
|
+
if (updated === null)
|
|
56
|
+
continue;
|
|
57
|
+
nextContent = updated;
|
|
58
|
+
tagged += 1;
|
|
59
|
+
}
|
|
60
|
+
if (tagged === 0)
|
|
61
|
+
return;
|
|
62
|
+
if (!scanOnly)
|
|
63
|
+
tree.write(filePath, nextContent);
|
|
64
|
+
deprecatedWrappers += tagged;
|
|
65
|
+
files.push(filePath);
|
|
66
|
+
});
|
|
67
|
+
return { deprecatedWrappers, touchedFiles: files.length, files };
|
|
68
|
+
};
|
|
69
|
+
export default async function migrate(tree, schema) {
|
|
70
|
+
console.log('\n🔄 Deprecating legacy query wrappers...');
|
|
71
|
+
const scope = createMigrationScope(tree, schema);
|
|
72
|
+
console.log(` Scope: ${scope.describe()}`);
|
|
73
|
+
const scanFile = process.env['ETHLETE_SCAN_FILE'];
|
|
74
|
+
const { deprecatedWrappers, touchedFiles, files } = deprecateLegacyQueries({ tree, scope, scanOnly: !!scanFile });
|
|
75
|
+
if (scanFile) {
|
|
76
|
+
writeFileSync(scanFile, JSON.stringify(files.sort()), 'utf8');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
console.log(` Tagged ${deprecatedWrappers} wrapper${deprecatedWrappers === 1 ? '' : 's'} in ${touchedFiles} file${touchedFiles === 1 ? '' : 's'}.`);
|
|
80
|
+
if (!schema.skipFormat) {
|
|
81
|
+
await formatFiles(tree);
|
|
82
|
+
}
|
|
83
|
+
console.log('\n✅ Legacy query wrappers deprecated successfully!');
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "deprecate-legacy-queries",
|
|
4
|
+
"title": "Deprecate legacy query wrappers",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"projects": {
|
|
8
|
+
"type": "array",
|
|
9
|
+
"items": { "type": "string" },
|
|
10
|
+
"description": "Nx projects to migrate. Defaults to every project in the workspace.",
|
|
11
|
+
"alias": "project"
|
|
12
|
+
},
|
|
13
|
+
"include": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": { "type": "string" },
|
|
16
|
+
"description": "Path prefixes to migrate, relative to the workspace root (e.g. 'libs/queries'). Unioned with `projects`. Defaults to the whole workspace.",
|
|
17
|
+
"alias": "includePath"
|
|
18
|
+
},
|
|
19
|
+
"skipFormat": {
|
|
20
|
+
"type": "boolean",
|
|
21
|
+
"description": "Skip formatting files after migration",
|
|
22
|
+
"default": false
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": []
|
|
26
|
+
}
|
|
@@ -5,10 +5,40 @@
|
|
|
5
5
|
"schema": "./migrate-to-query-v3/schema.json",
|
|
6
6
|
"description": "Migrate from legacy query system to new query system using interop"
|
|
7
7
|
},
|
|
8
|
+
"migrate-query-client-features": {
|
|
9
|
+
"factory": "./migrate-query-client-features/migration",
|
|
10
|
+
"schema": "./migrate-query-client-features/schema.json",
|
|
11
|
+
"description": "Migrate createQueryClient multiTabSync/persistence options to opt-in features"
|
|
12
|
+
},
|
|
8
13
|
"prep-for-query-v3": {
|
|
9
14
|
"factory": "./prep-for-query-v3/migration",
|
|
10
15
|
"schema": "./prep-for-query-v3/schema.json",
|
|
11
16
|
"description": "Update code to prepare for migration to query v3"
|
|
17
|
+
},
|
|
18
|
+
"deprecate-legacy-queries": {
|
|
19
|
+
"factory": "./deprecate-legacy-queries/migration",
|
|
20
|
+
"schema": "./deprecate-legacy-queries/schema.json",
|
|
21
|
+
"description": "Mark existing createLegacyQueryCreator wrappers as @deprecated so their call sites surface as still on v2"
|
|
22
|
+
},
|
|
23
|
+
"migrate-query-opt-in-features": {
|
|
24
|
+
"factory": "./migrate-query-opt-in-features/migration",
|
|
25
|
+
"schema": "./migrate-query-opt-in-features/schema.json",
|
|
26
|
+
"description": "Migrate query clients to the opt-in error pipeline and bearer auth providers to the opt-in multi-tab sync feature"
|
|
27
|
+
},
|
|
28
|
+
"migrate-from-ngrx-toolkit": {
|
|
29
|
+
"factory": "./migrate-from-ngrx-toolkit/migration",
|
|
30
|
+
"schema": "./migrate-from-ngrx-toolkit/schema.json",
|
|
31
|
+
"description": "Move @tomtomb/ngrx-toolkit features onto v3 query creators and the @ethlete/query/ngrx-toolkit interop"
|
|
32
|
+
},
|
|
33
|
+
"report-legacy-query-apis": {
|
|
34
|
+
"factory": "./report-legacy-query-apis/migration",
|
|
35
|
+
"schema": "./report-legacy-query-apis/schema.json",
|
|
36
|
+
"description": "Write a task to query-v3-migration-tasks.md for every legacy query collection, infinity query, *etQuery template and entity store"
|
|
37
|
+
},
|
|
38
|
+
"move-devtools-contract-imports": {
|
|
39
|
+
"factory": "./move-devtools-contract-imports/migration",
|
|
40
|
+
"schema": "./move-devtools-contract-imports/schema.json",
|
|
41
|
+
"description": "Import the devtools contract from @ethlete/query/devtools-contract instead of @ethlete/query"
|
|
12
42
|
}
|
|
13
43
|
}
|
|
14
44
|
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { dirname } from 'node:path';
|
|
2
|
+
import * as ts from 'typescript';
|
|
3
|
+
const TOOLKIT_PROBE = '__EtToolkitArgs';
|
|
4
|
+
const CREATOR_PROBE = '__EtCreatorArgs';
|
|
5
|
+
const PROBE_FILE = '/__et_args_probe__.ts';
|
|
6
|
+
const MAX_DEPTH = 12;
|
|
7
|
+
const PROBE_SOURCE = `
|
|
8
|
+
import type { ${TOOLKIT_PROBE} as ToolkitArgs } from '__toolkit__';
|
|
9
|
+
import type { ${CREATOR_PROBE} as CreatorArgs } from '__creator__';
|
|
10
|
+
|
|
11
|
+
type OptionalNullAsUndefined<T> = {
|
|
12
|
+
[K in keyof T]: undefined extends T[K] ? OptionalNullAsUndefined<Exclude<T[K], null>> : OptionalNullAsUndefined<T[K]>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ToolkitSide = OptionalNullAsUndefined<{
|
|
16
|
+
[K in keyof ToolkitArgs as K extends 'params' ? 'queryParams' : K extends 'body' ? K : never]: ToolkitArgs[K];
|
|
17
|
+
}>;
|
|
18
|
+
export type CreatorSide = OptionalNullAsUndefined<{
|
|
19
|
+
[K in keyof CreatorArgs as K extends 'queryParams' | 'body' ? K : never]: CreatorArgs[K];
|
|
20
|
+
}>;
|
|
21
|
+
`;
|
|
22
|
+
const COMPILER_OPTIONS = {
|
|
23
|
+
strict: true,
|
|
24
|
+
noEmit: true,
|
|
25
|
+
skipLibCheck: true,
|
|
26
|
+
target: ts.ScriptTarget.ES2022,
|
|
27
|
+
module: ts.ModuleKind.ESNext,
|
|
28
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
29
|
+
lib: ['lib.es2022.d.ts'],
|
|
30
|
+
types: [],
|
|
31
|
+
};
|
|
32
|
+
const toTreePath = (fileName) => fileName.replace(/^\//, '');
|
|
33
|
+
const containsAny = (checker, type, seen, depth) => {
|
|
34
|
+
if (type.flags & ts.TypeFlags.Any)
|
|
35
|
+
return true;
|
|
36
|
+
if (seen.has(type) || depth > MAX_DEPTH)
|
|
37
|
+
return false;
|
|
38
|
+
seen.add(type);
|
|
39
|
+
if (type.isUnionOrIntersection())
|
|
40
|
+
return type.types.some((part) => containsAny(checker, part, seen, depth + 1));
|
|
41
|
+
if (!(type.flags & ts.TypeFlags.Object))
|
|
42
|
+
return false;
|
|
43
|
+
const typeArguments = type.objectFlags & ts.ObjectFlags.Reference
|
|
44
|
+
? checker.getTypeArguments(type)
|
|
45
|
+
: [];
|
|
46
|
+
return (typeArguments.some((argument) => containsAny(checker, argument, seen, depth + 1)) ||
|
|
47
|
+
checker
|
|
48
|
+
.getPropertiesOfType(type)
|
|
49
|
+
.some((property) => containsAny(checker, checker.getTypeOfSymbol(property), seen, depth + 1)));
|
|
50
|
+
};
|
|
51
|
+
const isRecord = (checker, type) => (type.isIntersection() ? type.types : [type]).every((part) => !!(part.flags & ts.TypeFlags.Object)) &&
|
|
52
|
+
!checker.isArrayType(type) &&
|
|
53
|
+
!checker.isTupleType(type) &&
|
|
54
|
+
checker.getSignaturesOfType(type, ts.SignatureKind.Call).length === 0;
|
|
55
|
+
const hasEveryKey = (checker, source, target, seen, depth) => {
|
|
56
|
+
const from = checker.getNonNullableType(source);
|
|
57
|
+
const to = checker.getNonNullableType(target);
|
|
58
|
+
if (seen.has(from) || depth > MAX_DEPTH || !isRecord(checker, from) || !isRecord(checker, to))
|
|
59
|
+
return true;
|
|
60
|
+
seen.add(from);
|
|
61
|
+
return checker.getPropertiesOfType(from).every((property) => {
|
|
62
|
+
const counterpart = checker.getPropertyOfType(to, property.name);
|
|
63
|
+
return (!!counterpart &&
|
|
64
|
+
hasEveryKey(checker, checker.getTypeOfSymbol(property), checker.getTypeOfSymbol(counterpart), seen, depth + 1));
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
export const toolkitArgsFitCreator = (tree, graph, toolkit, creator) => {
|
|
68
|
+
if (toolkit.file === creator.file)
|
|
69
|
+
return false;
|
|
70
|
+
const appended = new Map([
|
|
71
|
+
[toolkit.file, `\nexport type ${TOOLKIT_PROBE} = ${toolkit.typeText ?? '{}'};\n`],
|
|
72
|
+
[creator.file, `\nexport type ${CREATOR_PROBE} = ${creator.typeText ?? '{}'};\n`],
|
|
73
|
+
]);
|
|
74
|
+
const libDirectory = dirname(ts.getDefaultLibFilePath(COMPILER_OPTIONS));
|
|
75
|
+
const readFile = (fileName) => {
|
|
76
|
+
if (fileName === PROBE_FILE)
|
|
77
|
+
return PROBE_SOURCE;
|
|
78
|
+
if (fileName.startsWith(libDirectory))
|
|
79
|
+
return ts.sys.readFile(fileName);
|
|
80
|
+
const path = toTreePath(fileName);
|
|
81
|
+
const content = tree.read(path, 'utf-8');
|
|
82
|
+
return content === null ? undefined : content + (appended.get(path) ?? '');
|
|
83
|
+
};
|
|
84
|
+
const fileExists = (fileName) => fileName === PROBE_FILE ||
|
|
85
|
+
(fileName.startsWith(libDirectory) ? ts.sys.fileExists(fileName) : tree.isFile(toTreePath(fileName)));
|
|
86
|
+
const resolutionHost = {
|
|
87
|
+
fileExists,
|
|
88
|
+
readFile,
|
|
89
|
+
directoryExists: (name) => tree.exists(toTreePath(name)) && !tree.isFile(toTreePath(name)),
|
|
90
|
+
getCurrentDirectory: () => '/',
|
|
91
|
+
};
|
|
92
|
+
const resolveOne = (name, containingFile) => {
|
|
93
|
+
const workspaceFile = containingFile === PROBE_FILE
|
|
94
|
+
? name === '__toolkit__'
|
|
95
|
+
? toolkit.file
|
|
96
|
+
: name === '__creator__'
|
|
97
|
+
? creator.file
|
|
98
|
+
: null
|
|
99
|
+
: graph.resolveFile(toTreePath(containingFile), name);
|
|
100
|
+
if (workspaceFile)
|
|
101
|
+
return { resolvedFileName: `/${workspaceFile}`, extension: ts.Extension.Ts };
|
|
102
|
+
return ts.resolveModuleName(name, containingFile, COMPILER_OPTIONS, resolutionHost).resolvedModule;
|
|
103
|
+
};
|
|
104
|
+
const host = {
|
|
105
|
+
...resolutionHost,
|
|
106
|
+
getSourceFile: (fileName, languageVersion) => {
|
|
107
|
+
const text = readFile(fileName);
|
|
108
|
+
return text === undefined ? undefined : ts.createSourceFile(fileName, text, languageVersion, true);
|
|
109
|
+
},
|
|
110
|
+
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
|
|
111
|
+
writeFile: () => undefined,
|
|
112
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
113
|
+
useCaseSensitiveFileNames: () => true,
|
|
114
|
+
getNewLine: () => '\n',
|
|
115
|
+
resolveModuleNameLiterals: (literals, containingFile) => literals.map((literal) => ({ resolvedModule: resolveOne(literal.text, containingFile) })),
|
|
116
|
+
};
|
|
117
|
+
const program = ts.createProgram({ rootNames: [PROBE_FILE], options: COMPILER_OPTIONS, host });
|
|
118
|
+
const checker = program.getTypeChecker();
|
|
119
|
+
const probe = program.getSourceFile(PROBE_FILE);
|
|
120
|
+
const exportsOf = probe ? checker.getSymbolAtLocation(probe) : undefined;
|
|
121
|
+
const typeOf = (name) => {
|
|
122
|
+
const symbol = exportsOf ? checker.getExportsOfModule(exportsOf).find((each) => each.name === name) : undefined;
|
|
123
|
+
return symbol ? checker.getDeclaredTypeOfSymbol(symbol) : null;
|
|
124
|
+
};
|
|
125
|
+
const toolkitSide = typeOf('ToolkitSide');
|
|
126
|
+
const creatorSide = typeOf('CreatorSide');
|
|
127
|
+
if (!toolkitSide || !creatorSide)
|
|
128
|
+
return false;
|
|
129
|
+
if (containsAny(checker, toolkitSide, new Set(), 0) || containsAny(checker, creatorSide, new Set(), 0))
|
|
130
|
+
return false;
|
|
131
|
+
const keys = (type) => checker
|
|
132
|
+
.getPropertiesOfType(type)
|
|
133
|
+
.map((property) => property.name)
|
|
134
|
+
.sort()
|
|
135
|
+
.join(',');
|
|
136
|
+
return (keys(toolkitSide) === keys(creatorSide) &&
|
|
137
|
+
checker.isTypeAssignableTo(toolkitSide, creatorSide) &&
|
|
138
|
+
hasEveryKey(checker, toolkitSide, creatorSide, new Set(), 0));
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=args-assignability.js.map
|