@typeorm/codemod 1.0.1 → 1.0.3-nightly.20261009
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/LICENSE +21 -0
- package/README.md +2 -1
- package/dist/cli/print-usage.js +1 -0
- package/dist/cli/print-usage.js.map +1 -1
- package/dist/cli/run-transforms.js +167 -79
- package/dist/cli/run-transforms.js.map +1 -1
- package/dist/dependencies/v1.js +1 -0
- package/dist/dependencies/v1.js.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/transforms/ast-helpers.js +820 -26
- package/dist/transforms/ast-helpers.js.map +1 -1
- package/dist/transforms/stats.js +11 -0
- package/dist/transforms/stats.js.map +1 -1
- package/dist/transforms/todo.js +22 -5
- package/dist/transforms/todo.js.map +1 -1
- package/dist/transforms/v1/column-readonly.js +28 -8
- package/dist/transforms/v1/column-readonly.js.map +1 -1
- package/dist/transforms/v1/column-unsigned-numeric.js +21 -18
- package/dist/transforms/v1/column-unsigned-numeric.js.map +1 -1
- package/dist/transforms/v1/column-width-zerofill.js +9 -3
- package/dist/transforms/v1/column-width-zerofill.js.map +1 -1
- package/dist/transforms/v1/connection-options-reader.js +23 -7
- package/dist/transforms/v1/connection-options-reader.js.map +1 -1
- package/dist/transforms/v1/connection-to-datasource.js +871 -88
- package/dist/transforms/v1/connection-to-datasource.js.map +1 -1
- package/dist/transforms/v1/datasource-expo.js +79 -0
- package/dist/transforms/v1/datasource-expo.js.map +1 -0
- package/dist/transforms/v1/datasource-mongodb.js +77 -50
- package/dist/transforms/v1/datasource-mongodb.js.map +1 -1
- package/dist/transforms/v1/datasource-mssql.js +88 -44
- package/dist/transforms/v1/datasource-mssql.js.map +1 -1
- package/dist/transforms/v1/datasource-mysql-connector.js +22 -6
- package/dist/transforms/v1/datasource-mysql-connector.js.map +1 -1
- package/dist/transforms/v1/datasource-name.js +17 -14
- package/dist/transforms/v1/datasource-name.js.map +1 -1
- package/dist/transforms/v1/datasource-sap.js +51 -39
- package/dist/transforms/v1/datasource-sap.js.map +1 -1
- package/dist/transforms/v1/datasource-sqlite-options.js +4 -1
- package/dist/transforms/v1/datasource-sqlite-options.js.map +1 -1
- package/dist/transforms/v1/file-logger.js +2 -21
- package/dist/transforms/v1/file-logger.js.map +1 -1
- package/dist/transforms/v1/find-options-string-relations.js +75 -11
- package/dist/transforms/v1/find-options-string-relations.js.map +1 -1
- package/dist/transforms/v1/find-options-string-select.js +75 -13
- package/dist/transforms/v1/find-options-string-select.js.map +1 -1
- package/dist/transforms/v1/global-functions.js +79 -21
- package/dist/transforms/v1/global-functions.js.map +1 -1
- package/dist/transforms/v1/index.js +6 -1
- package/dist/transforms/v1/index.js.map +1 -1
- package/dist/transforms/v1/mongodb-stats.js +18 -11
- package/dist/transforms/v1/mongodb-stats.js.map +1 -1
- package/dist/transforms/v1/mongodb-types.js +62 -2
- package/dist/transforms/v1/mongodb-types.js.map +1 -1
- package/dist/transforms/v1/query-builder-on-conflict.js +30 -17
- package/dist/transforms/v1/query-builder-on-conflict.js.map +1 -1
- package/dist/transforms/v1/query-builder-print-sql.js +15 -12
- package/dist/transforms/v1/query-builder-print-sql.js.map +1 -1
- package/dist/transforms/v1/query-builder-replace-property-names.js +13 -12
- package/dist/transforms/v1/query-builder-replace-property-names.js.map +1 -1
- package/dist/transforms/v1/query-builder-where-expression.js +29 -12
- package/dist/transforms/v1/query-builder-where-expression.js.map +1 -1
- package/dist/transforms/v1/query-runner-loaded-tables-views.js +0 -1
- package/dist/transforms/v1/query-runner-loaded-tables-views.js.map +1 -1
- package/dist/transforms/v1/relation-count.js +3 -0
- package/dist/transforms/v1/relation-count.js.map +1 -1
- package/dist/transforms/v1/repository-abstract.js +146 -29
- package/dist/transforms/v1/repository-abstract.js.map +1 -1
- package/dist/transforms/v1/repository-exist.js +18 -1
- package/dist/transforms/v1/repository-exist.js.map +1 -1
- package/dist/transforms/v1/repository-find-by-ids.js +67 -24
- package/dist/transforms/v1/repository-find-by-ids.js.map +1 -1
- package/dist/transforms/v1/repository-find-one-by-id.js +3 -0
- package/dist/transforms/v1/repository-find-one-by-id.js.map +1 -1
- package/dist/transforms/v1/use-container.js +60 -12
- package/dist/transforms/v1/use-container.js.map +1 -1
- package/package.json +16 -17
|
@@ -7,11 +7,11 @@ exports.fn = exports.connectionToDataSource = exports.description = exports.name
|
|
|
7
7
|
const node_path_1 = __importDefault(require("node:path"));
|
|
8
8
|
const ast_helpers_1 = require("../ast-helpers");
|
|
9
9
|
/**
|
|
10
|
-
* Unwraps common TypeScript expression wrappers
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* Unwraps common TypeScript expression wrappers around an identifier and
|
|
11
|
+
* returns the identifier's name. Handles `ds as DataSource`, `ds!`,
|
|
12
|
+
* `ds satisfies DataSource`, and the angle-bracket cast `<DataSource>ds`;
|
|
13
|
+
* identifiers reached through these wrappers are tracked alongside bare
|
|
14
|
+
* identifiers so accessor-chain transforms don't miss them.
|
|
15
15
|
*/
|
|
16
16
|
const unwrapIdentifierName = (node) => {
|
|
17
17
|
let current = node;
|
|
@@ -28,33 +28,208 @@ const unwrapIdentifierName = (node) => {
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
|
+
// Builds `Extract<DataSourceOptions, { type: "<literal>" }>` as a
|
|
32
|
+
// TSTypeReference node. Parses a synthetic type alias and pulls out its
|
|
33
|
+
// right-hand side so the returned node carries source-location info —
|
|
34
|
+
// recast otherwise splits builder-made nodes across lines because they
|
|
35
|
+
// have no `loc`, and Prettier preserves those line breaks verbatim.
|
|
36
|
+
// Drivers that share a literal with a sibling (mysql / mariadb both on
|
|
37
|
+
// MysqlDataSourceOptions) emit a union so `Extract` matches the
|
|
38
|
+
// declared union-typed `type` field.
|
|
39
|
+
const buildExtractDataSourceOptions = (j, literals) => {
|
|
40
|
+
const unionLiteral = literals.map((lit) => `"${lit}"`).join(" | ");
|
|
41
|
+
const source = `type __TypeormCodemodExtract = Extract<DataSourceOptions, { type: ${unionLiteral} }>`;
|
|
42
|
+
const aliasPath = j(source).find(j.TSTypeAliasDeclaration).paths()[0];
|
|
43
|
+
return aliasPath.node
|
|
44
|
+
.typeAnnotation;
|
|
45
|
+
};
|
|
46
|
+
// Ensures `import type { DataSourceOptions } from "typeorm"` is present.
|
|
47
|
+
// Augments an existing `import type` from `"typeorm"` when one exists, or
|
|
48
|
+
// emits a new line after the last import otherwise. Idempotent — a second
|
|
49
|
+
// run finds `DataSourceOptions` already imported and returns.
|
|
50
|
+
const ensureDataSourceOptionsTypeImport = (root, j) => {
|
|
51
|
+
const typeormImports = root.find(j.ImportDeclaration, {
|
|
52
|
+
source: { value: "typeorm" },
|
|
53
|
+
});
|
|
54
|
+
let hasIt = false;
|
|
55
|
+
typeormImports.forEach((p) => {
|
|
56
|
+
p.node.specifiers?.forEach((spec) => {
|
|
57
|
+
if (spec.type === "ImportSpecifier" &&
|
|
58
|
+
spec.imported.type === "Identifier" &&
|
|
59
|
+
spec.imported.name === "DataSourceOptions") {
|
|
60
|
+
hasIt = true;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
if (hasIt)
|
|
65
|
+
return;
|
|
66
|
+
// Prefer augmenting an existing `import type { ... } from "typeorm"`
|
|
67
|
+
// — but only when that declaration is a pure named-specifier form.
|
|
68
|
+
// Pushing an `ImportSpecifier` into a namespace or default-only import
|
|
69
|
+
// (`import type * as ns` / `import type D`) produces invalid TS.
|
|
70
|
+
let augmented = false;
|
|
71
|
+
typeormImports.forEach((p) => {
|
|
72
|
+
if (augmented)
|
|
73
|
+
return;
|
|
74
|
+
if (p.node.importKind !== "type")
|
|
75
|
+
return;
|
|
76
|
+
const specifiers = p.node.specifiers ?? [];
|
|
77
|
+
const onlyNamed = specifiers.every((s) => s.type === "ImportSpecifier");
|
|
78
|
+
if (!onlyNamed)
|
|
79
|
+
return;
|
|
80
|
+
p.node.specifiers?.push(j.importSpecifier(j.identifier("DataSourceOptions")));
|
|
81
|
+
augmented = true;
|
|
82
|
+
});
|
|
83
|
+
if (augmented)
|
|
84
|
+
return;
|
|
85
|
+
const newImport = j.importDeclaration([j.importSpecifier(j.identifier("DataSourceOptions"))], j.literal("typeorm"));
|
|
86
|
+
newImport.importKind = "type";
|
|
87
|
+
const allImports = root.find(j.ImportDeclaration);
|
|
88
|
+
if (allImports.length > 0) {
|
|
89
|
+
allImports.at(-1).insertAfter(newImport);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
root.find(j.Program).forEach((p) => {
|
|
93
|
+
p.node.body.unshift(newImport);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
// Resolves the inner Identifier of a TSParameterProperty's `.parameter`,
|
|
98
|
+
// handling both the plain `Identifier` and default-valued `AssignmentPattern`
|
|
99
|
+
// forms. Returns null when neither shape applies.
|
|
100
|
+
const resolveTSParameterPropertyIdentifier = (inner) => {
|
|
101
|
+
if (inner.type === "Identifier")
|
|
102
|
+
return inner;
|
|
103
|
+
if (inner.type === "AssignmentPattern" &&
|
|
104
|
+
inner.left.type === "Identifier") {
|
|
105
|
+
return inner.left;
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
};
|
|
109
|
+
// Rename `connection` to `dataSource` on every ObjectProperty in `arg`.
|
|
110
|
+
// Returns true when at least one property was renamed. The shorthand
|
|
111
|
+
// `{ connection }` becomes `{ dataSource: connection }` — the variable
|
|
112
|
+
// reference stays intact and the object drops the shorthand flag.
|
|
113
|
+
const renameConnectionKeyToDataSource = (arg) => {
|
|
114
|
+
let changed = false;
|
|
115
|
+
for (const prop of arg.properties) {
|
|
116
|
+
if (prop.type !== "Property" && prop.type !== "ObjectProperty")
|
|
117
|
+
continue;
|
|
118
|
+
const p = prop;
|
|
119
|
+
if (p.key.type !== "Identifier" || p.key.name !== "connection")
|
|
120
|
+
continue;
|
|
121
|
+
p.key.name = "dataSource";
|
|
122
|
+
if (p.shorthand)
|
|
123
|
+
p.shorthand = false;
|
|
124
|
+
changed = true;
|
|
125
|
+
}
|
|
126
|
+
return changed;
|
|
127
|
+
};
|
|
128
|
+
// Remove every `connection` ObjectProperty from `arg`. Returns true when
|
|
129
|
+
// at least one property was dropped.
|
|
130
|
+
const dropConnectionKey = (arg) => {
|
|
131
|
+
const before = arg.properties.length;
|
|
132
|
+
arg.properties = arg.properties.filter((prop) => {
|
|
133
|
+
if (prop.type !== "Property" && prop.type !== "ObjectProperty") {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
const p = prop;
|
|
137
|
+
return !(p.key.type === "Identifier" && p.key.name === "connection");
|
|
138
|
+
});
|
|
139
|
+
return arg.properties.length !== before;
|
|
140
|
+
};
|
|
141
|
+
// Walks every `new X(...)` whose callee is one of the tracked metadata
|
|
142
|
+
// constructors and rewrites the `connection` option key according to the
|
|
143
|
+
// constructor's v1 semantics (rename vs drop).
|
|
144
|
+
const rewriteMetadataConstructors = (root, j, entityMetadataLocalNames, indirectDataSourceLocalNames, hasChanges) => {
|
|
145
|
+
root.find(j.NewExpression).forEach((path) => {
|
|
146
|
+
const callee = path.node.callee;
|
|
147
|
+
if (callee.type !== "Identifier")
|
|
148
|
+
return;
|
|
149
|
+
const name = callee.name;
|
|
150
|
+
const isEntityMetadata = entityMetadataLocalNames.has(name);
|
|
151
|
+
const isIndirect = indirectDataSourceLocalNames.has(name);
|
|
152
|
+
if (!isEntityMetadata && !isIndirect)
|
|
153
|
+
return;
|
|
154
|
+
const [arg] = path.node.arguments;
|
|
155
|
+
if (arg?.type !== "ObjectExpression")
|
|
156
|
+
return;
|
|
157
|
+
const changed = isEntityMetadata
|
|
158
|
+
? renameConnectionKeyToDataSource(arg)
|
|
159
|
+
: dropConnectionKey(arg);
|
|
160
|
+
if (changed)
|
|
161
|
+
hasChanges = true;
|
|
162
|
+
});
|
|
163
|
+
return hasChanges;
|
|
164
|
+
};
|
|
31
165
|
exports.name = node_path_1.default.basename(__filename, node_path_1.default.extname(__filename));
|
|
32
166
|
exports.description = "migrate from `Connection` to `DataSource`";
|
|
33
167
|
const connectionToDataSource = (file, api) => {
|
|
34
168
|
const j = api.jscodeshift;
|
|
35
169
|
const root = j(file.source);
|
|
170
|
+
// File-level scope gate: without a typeorm import anywhere in the file,
|
|
171
|
+
// occurrences of `Connection` / `DataSource` must belong to an
|
|
172
|
+
// unrelated library (e.g. mongoose `new Connection().connect()`) and
|
|
173
|
+
// this transform must not touch them.
|
|
174
|
+
if (!(0, ast_helpers_1.fileImportsFrom)(root, j, "typeorm"))
|
|
175
|
+
return undefined;
|
|
36
176
|
let hasChanges = false;
|
|
177
|
+
// Generic type renames. Driver-specific *ConnectionOptions types are
|
|
178
|
+
// handled separately via the Extract rewrite below — they're not
|
|
179
|
+
// top-level exports from `"typeorm"`, so the codemod can't just rename
|
|
180
|
+
// the identifier and leave a broken import behind.
|
|
37
181
|
const typeRenames = {
|
|
38
182
|
Connection: "DataSource",
|
|
39
183
|
ConnectionOptions: "DataSourceOptions",
|
|
40
184
|
BaseConnectionOptions: "BaseDataSourceOptions",
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
185
|
+
};
|
|
186
|
+
// Maps every driver-specific options type name (both the v0 form ending
|
|
187
|
+
// in `ConnectionOptions` and the v1 form ending in `DataSourceOptions`)
|
|
188
|
+
// to the `type` literal(s) the driver carries. Used to rewrite type
|
|
189
|
+
// references to `Extract<DataSourceOptions, { type: "..." }>` so users
|
|
190
|
+
// don't need deep-path imports like `typeorm/driver/sap/...` for a
|
|
191
|
+
// single field annotation.
|
|
192
|
+
const driverOptionTypeLiterals = {
|
|
193
|
+
AuroraMysqlConnectionOptions: ["aurora-mysql"],
|
|
194
|
+
AuroraMysqlDataSourceOptions: ["aurora-mysql"],
|
|
195
|
+
AuroraPostgresConnectionOptions: ["aurora-postgres"],
|
|
196
|
+
AuroraPostgresDataSourceOptions: ["aurora-postgres"],
|
|
197
|
+
BetterSqlite3ConnectionOptions: ["better-sqlite3"],
|
|
198
|
+
BetterSqlite3DataSourceOptions: ["better-sqlite3"],
|
|
199
|
+
CapacitorConnectionOptions: ["capacitor"],
|
|
200
|
+
CapacitorDataSourceOptions: ["capacitor"],
|
|
201
|
+
CockroachConnectionOptions: ["cockroachdb"],
|
|
202
|
+
CockroachDataSourceOptions: ["cockroachdb"],
|
|
203
|
+
CordovaConnectionOptions: ["cordova"],
|
|
204
|
+
CordovaDataSourceOptions: ["cordova"],
|
|
205
|
+
ExpoConnectionOptions: ["expo"],
|
|
206
|
+
ExpoDataSourceOptions: ["expo"],
|
|
207
|
+
MongoConnectionOptions: ["mongodb"],
|
|
208
|
+
MongoDataSourceOptions: ["mongodb"],
|
|
209
|
+
// MySQL options cover both `type: "mysql"` and `type: "mariadb"`.
|
|
210
|
+
// Using a single literal would make `Extract` return `never`, so we
|
|
211
|
+
// emit a union `{ type: "mysql" | "mariadb" }` that matches.
|
|
212
|
+
MysqlConnectionOptions: ["mysql", "mariadb"],
|
|
213
|
+
MysqlDataSourceOptions: ["mysql", "mariadb"],
|
|
214
|
+
NativescriptConnectionOptions: ["nativescript"],
|
|
215
|
+
NativescriptDataSourceOptions: ["nativescript"],
|
|
216
|
+
OracleConnectionOptions: ["oracle"],
|
|
217
|
+
OracleDataSourceOptions: ["oracle"],
|
|
218
|
+
PostgresConnectionOptions: ["postgres"],
|
|
219
|
+
PostgresDataSourceOptions: ["postgres"],
|
|
220
|
+
ReactNativeConnectionOptions: ["react-native"],
|
|
221
|
+
ReactNativeDataSourceOptions: ["react-native"],
|
|
222
|
+
SapConnectionOptions: ["sap"],
|
|
223
|
+
SapDataSourceOptions: ["sap"],
|
|
224
|
+
// The v0 `sqlite` driver was removed; its options type maps to
|
|
225
|
+
// `better-sqlite3`'s `type` literal in v1.
|
|
226
|
+
SqliteConnectionOptions: ["better-sqlite3"],
|
|
227
|
+
SpannerConnectionOptions: ["spanner"],
|
|
228
|
+
SpannerDataSourceOptions: ["spanner"],
|
|
229
|
+
SqljsConnectionOptions: ["sqljs"],
|
|
230
|
+
SqljsDataSourceOptions: ["sqljs"],
|
|
231
|
+
SqlServerConnectionOptions: ["mssql"],
|
|
232
|
+
SqlServerDataSourceOptions: ["mssql"],
|
|
58
233
|
};
|
|
59
234
|
const methodRenames = {
|
|
60
235
|
connect: "initialize",
|
|
@@ -75,6 +250,20 @@ const connectionToDataSource = (file, api) => {
|
|
|
75
250
|
"SoftDeleteQueryBuilder",
|
|
76
251
|
"RelationQueryBuilder",
|
|
77
252
|
"EntityMetadata",
|
|
253
|
+
// Subscriber event types — all extend BaseEvent which deprecated
|
|
254
|
+
// `connection` in favor of `dataSource`. Covers the usual destructure
|
|
255
|
+
// pattern in subscriber handlers: `const { connection } = event`.
|
|
256
|
+
"BaseEvent",
|
|
257
|
+
"InsertEvent",
|
|
258
|
+
"UpdateEvent",
|
|
259
|
+
"RemoveEvent",
|
|
260
|
+
"SoftRemoveEvent",
|
|
261
|
+
"RecoverEvent",
|
|
262
|
+
"LoadEvent",
|
|
263
|
+
"QueryEvent",
|
|
264
|
+
"TransactionStartEvent",
|
|
265
|
+
"TransactionCommitEvent",
|
|
266
|
+
"TransactionRollbackEvent",
|
|
78
267
|
]);
|
|
79
268
|
// Metadata types whose v0.3 `.connection` getter was removed entirely in
|
|
80
269
|
// v1 (renamed in #12249). Access now goes through `.entityMetadata.dataSource`
|
|
@@ -84,25 +273,16 @@ const connectionToDataSource = (file, api) => {
|
|
|
84
273
|
"ColumnMetadata",
|
|
85
274
|
"IndexMetadata",
|
|
86
275
|
]);
|
|
87
|
-
// Full-path overrides for deep imports where the v1 module also moved
|
|
88
|
-
// to a different directory. The generic last-segment swap below cannot
|
|
89
|
-
// handle these because swapping only the filename leaves the old
|
|
90
|
-
// directory intact (e.g. `typeorm/driver/sqlite/` was removed in v1).
|
|
91
|
-
const deepPathRewrites = {
|
|
92
|
-
"typeorm/driver/sqlite/SqliteConnectionOptions": "typeorm/driver/better-sqlite3/BetterSqlite3DataSourceOptions",
|
|
93
|
-
};
|
|
94
276
|
const localRenames = new Map();
|
|
95
277
|
const typeormPathPrefix = "typeorm/";
|
|
96
278
|
// Returns the rewritten module path for a `typeorm[/...]` import, or the
|
|
97
|
-
// original when no rewrite applies.
|
|
98
|
-
//
|
|
99
|
-
//
|
|
279
|
+
// original when no rewrite applies. Swaps the last path segment when
|
|
280
|
+
// that segment is an exact rename key (e.g. `.../Connection` →
|
|
281
|
+
// `.../DataSource`). Driver-specific options-type imports are handled
|
|
282
|
+
// by the Extract rewrite and never reach this helper.
|
|
100
283
|
const rewriteTypeormPath = (source) => {
|
|
101
284
|
if (!source.startsWith(typeormPathPrefix))
|
|
102
285
|
return source;
|
|
103
|
-
const fullPathRewrite = deepPathRewrites[source];
|
|
104
|
-
if (fullPathRewrite)
|
|
105
|
-
return fullPathRewrite;
|
|
106
286
|
const lastSlash = source.lastIndexOf("/");
|
|
107
287
|
// No slash or trailing slash → no segment to rewrite
|
|
108
288
|
if (lastSlash === -1 || lastSlash === source.length - 1)
|
|
@@ -131,13 +311,10 @@ const connectionToDataSource = (file, api) => {
|
|
|
131
311
|
const hasAlias = spec.local?.type === "Identifier" &&
|
|
132
312
|
spec.local.name !== oldImported;
|
|
133
313
|
spec.imported.name = newImported;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
else {
|
|
139
|
-
// No alias — propagate the rename to the local binding and
|
|
140
|
-
// record it for the NewExpression / TSTypeReference loop.
|
|
314
|
+
// With an alias (`Connection as Foo`), the local binding already
|
|
315
|
+
// points to the renamed import — usages need no rewriting. Without
|
|
316
|
+
// one, propagate the rename to the local binding.
|
|
317
|
+
if (!hasAlias) {
|
|
141
318
|
const localName = spec.local?.type === "Identifier"
|
|
142
319
|
? spec.local.name
|
|
143
320
|
: oldImported;
|
|
@@ -151,7 +328,10 @@ const connectionToDataSource = (file, api) => {
|
|
|
151
328
|
// Dedupe specifiers that now share the same imported name after the
|
|
152
329
|
// rename — e.g. `import { Connection, DataSource }` would otherwise
|
|
153
330
|
// emit `import { DataSource, DataSource }`. Keep the first occurrence
|
|
154
|
-
// of each imported
|
|
331
|
+
// of each (importKind, imported, local) tuple. Per-specifier
|
|
332
|
+
// `importKind` ("type" vs unset for value) participates so
|
|
333
|
+
// `import { DataSource, type DataSource }` keeps both — they are
|
|
334
|
+
// distinct specifiers TS 4.5+ recognizes.
|
|
155
335
|
if (path.node.specifiers) {
|
|
156
336
|
const seen = new Set();
|
|
157
337
|
path.node.specifiers = path.node.specifiers.filter((spec) => {
|
|
@@ -159,7 +339,11 @@ const connectionToDataSource = (file, api) => {
|
|
|
159
339
|
spec.imported.type !== "Identifier") {
|
|
160
340
|
return true;
|
|
161
341
|
}
|
|
162
|
-
const
|
|
342
|
+
const specKind = spec.importKind === "type"
|
|
343
|
+
? "type:"
|
|
344
|
+
: "";
|
|
345
|
+
const localName = spec.local?.type === "Identifier" ? spec.local.name : "";
|
|
346
|
+
const key = `${specKind}${spec.imported.name}::${localName}`;
|
|
163
347
|
if (seen.has(key)) {
|
|
164
348
|
hasChanges = true;
|
|
165
349
|
return false;
|
|
@@ -174,6 +358,136 @@ const connectionToDataSource = (file, api) => {
|
|
|
174
358
|
hasChanges = true;
|
|
175
359
|
}
|
|
176
360
|
});
|
|
361
|
+
// Driver-specific options types aren't top-level exports from "typeorm",
|
|
362
|
+
// and deep-path imports like `"typeorm/driver/sap/SapDataSourceOptions"`
|
|
363
|
+
// are brittle across refactors. Rewrite every reference to
|
|
364
|
+
// `Extract<DataSourceOptions, { type: "<driver>" }>` so users only need
|
|
365
|
+
// the union exported from the index. Handles both v0 (`SapConnectionOptions`)
|
|
366
|
+
// and v1 (`SapDataSourceOptions`) names, and aliased imports.
|
|
367
|
+
const driverOptionLocalBindings = new Map();
|
|
368
|
+
root.find(j.ImportDeclaration).forEach((importPath) => {
|
|
369
|
+
const importSource = importPath.node.source.value;
|
|
370
|
+
if (typeof importSource !== "string" ||
|
|
371
|
+
(importSource !== "typeorm" &&
|
|
372
|
+
!importSource.startsWith(typeormPathPrefix))) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const specifiers = importPath.node.specifiers ?? [];
|
|
376
|
+
const kept = specifiers.filter((spec) => {
|
|
377
|
+
if (spec.type !== "ImportSpecifier" ||
|
|
378
|
+
spec.imported.type !== "Identifier") {
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
const literals = driverOptionTypeLiterals[spec.imported.name];
|
|
382
|
+
if (!literals)
|
|
383
|
+
return true;
|
|
384
|
+
const localName = spec.local?.type === "Identifier"
|
|
385
|
+
? spec.local.name
|
|
386
|
+
: spec.imported.name;
|
|
387
|
+
driverOptionLocalBindings.set(localName, literals);
|
|
388
|
+
return false;
|
|
389
|
+
});
|
|
390
|
+
if (kept.length !== specifiers.length) {
|
|
391
|
+
importPath.node.specifiers = kept;
|
|
392
|
+
hasChanges = true;
|
|
393
|
+
if (kept.length === 0)
|
|
394
|
+
j(importPath).remove();
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
if (driverOptionLocalBindings.size > 0) {
|
|
398
|
+
// Walk every type reference in the file and replace tracked local
|
|
399
|
+
// bindings with an inline Extract<DataSourceOptions, { type: ... }>
|
|
400
|
+
// expression. TS narrowing via the `type` discriminator still picks
|
|
401
|
+
// the correct driver-specific fields.
|
|
402
|
+
root.find(j.TSTypeReference).forEach((refPath) => {
|
|
403
|
+
const typeName = refPath.node.typeName;
|
|
404
|
+
if (typeName.type !== "Identifier")
|
|
405
|
+
return;
|
|
406
|
+
const literals = driverOptionLocalBindings.get(typeName.name);
|
|
407
|
+
if (!literals)
|
|
408
|
+
return;
|
|
409
|
+
j(refPath).replaceWith(buildExtractDataSourceOptions(j, literals));
|
|
410
|
+
hasChanges = true;
|
|
411
|
+
});
|
|
412
|
+
// Emit / augment `import type { DataSourceOptions } from "typeorm"`
|
|
413
|
+
// so the Extract expressions resolve.
|
|
414
|
+
ensureDataSourceOptionsTypeImport(root, j);
|
|
415
|
+
}
|
|
416
|
+
// Re-export source paths (`export { X } from "typeorm/driver/..."` and
|
|
417
|
+
// `export * from "typeorm/driver/..."`) follow the same deep-path rewrite
|
|
418
|
+
// rules as import sources so barrel files that re-export renamed modules
|
|
419
|
+
// end up pointing at the v1 path.
|
|
420
|
+
root.find(j.ExportNamedDeclaration).forEach((exportPath) => {
|
|
421
|
+
const source = exportPath.node.source?.value;
|
|
422
|
+
if (typeof source !== "string")
|
|
423
|
+
return;
|
|
424
|
+
const rewritten = rewriteTypeormPath(source);
|
|
425
|
+
if (rewritten !== source) {
|
|
426
|
+
exportPath.node.source.value = rewritten;
|
|
427
|
+
hasChanges = true;
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
root.find(j.ExportAllDeclaration).forEach((exportPath) => {
|
|
431
|
+
const source = exportPath.node.source.value;
|
|
432
|
+
if (typeof source !== "string")
|
|
433
|
+
return;
|
|
434
|
+
const rewritten = rewriteTypeormPath(source);
|
|
435
|
+
if (rewritten !== source) {
|
|
436
|
+
exportPath.node.source.value = rewritten;
|
|
437
|
+
hasChanges = true;
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
// Cross-import dedup: the rewrites above can produce two ImportDeclarations
|
|
441
|
+
// that now target the same module with the same specifier (e.g. an
|
|
442
|
+
// existing `import type { BetterSqlite3DataSourceOptions }` plus a
|
|
443
|
+
// newly-renamed `import type { SqliteConnectionOptions }` from the old
|
|
444
|
+
// `sqlite/` path). Drop any later declaration whose (source, specifier
|
|
445
|
+
// set) tuple is already covered by an earlier one.
|
|
446
|
+
const seenImportKeys = new Map();
|
|
447
|
+
root.find(j.ImportDeclaration).forEach((importPath) => {
|
|
448
|
+
const source = importPath.node.source.value;
|
|
449
|
+
if (typeof source !== "string")
|
|
450
|
+
return;
|
|
451
|
+
const isTypeOnly = importPath.node.importKind === "type";
|
|
452
|
+
const specifiers = importPath.node.specifiers ?? [];
|
|
453
|
+
const specifierKey = specifiers
|
|
454
|
+
.map((spec) => {
|
|
455
|
+
const specType = spec.type;
|
|
456
|
+
if (specType === "ImportSpecifier") {
|
|
457
|
+
const s = spec;
|
|
458
|
+
const imported = s.imported.type === "Identifier"
|
|
459
|
+
? (s.imported.name ?? "")
|
|
460
|
+
: "";
|
|
461
|
+
const local = s.local?.type === "Identifier"
|
|
462
|
+
? (s.local.name ?? "")
|
|
463
|
+
: imported;
|
|
464
|
+
// Per-specifier importKind distinguishes
|
|
465
|
+
// `import { type X }` from `import { X }` — the two
|
|
466
|
+
// bindings have different runtime semantics and must
|
|
467
|
+
// not collapse into the same dedup key.
|
|
468
|
+
const kind = s.importKind === "type" ? "type:" : "";
|
|
469
|
+
return `named:${kind}${imported}->${local}`;
|
|
470
|
+
}
|
|
471
|
+
if (specType === "ImportDefaultSpecifier") {
|
|
472
|
+
const s = spec;
|
|
473
|
+
return `default:${s.local?.type === "Identifier" ? (s.local.name ?? "") : ""}`;
|
|
474
|
+
}
|
|
475
|
+
if (specType === "ImportNamespaceSpecifier") {
|
|
476
|
+
const s = spec;
|
|
477
|
+
return `namespace:${s.local?.type === "Identifier" ? (s.local.name ?? "") : ""}`;
|
|
478
|
+
}
|
|
479
|
+
return specType;
|
|
480
|
+
})
|
|
481
|
+
.sort()
|
|
482
|
+
.join("|");
|
|
483
|
+
const key = `${isTypeOnly ? "type:" : ""}${source}::${specifierKey}`;
|
|
484
|
+
if (seenImportKeys.has(key)) {
|
|
485
|
+
j(importPath).remove();
|
|
486
|
+
hasChanges = true;
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
seenImportKeys.set(key, importPath.node);
|
|
490
|
+
});
|
|
177
491
|
// Rewrite a single `{ X [: Y] }` property in a destructured require of
|
|
178
492
|
// typeorm. Records the local binding in `localRenames` so the shared
|
|
179
493
|
// NewExpression/TSTypeReference rewrite loop below picks it up.
|
|
@@ -223,6 +537,11 @@ const connectionToDataSource = (file, api) => {
|
|
|
223
537
|
hasChanges = true;
|
|
224
538
|
}
|
|
225
539
|
});
|
|
540
|
+
// Rename re-exports from "typeorm" (e.g. barrel files that do
|
|
541
|
+
// `export { Connection } from "typeorm"`)
|
|
542
|
+
if ((0, ast_helpers_1.renameReExportSpecifiers)(root, j, "typeorm", typeRenames)) {
|
|
543
|
+
hasChanges = true;
|
|
544
|
+
}
|
|
226
545
|
for (const [oldName, newName] of localRenames) {
|
|
227
546
|
// TSTypeReference (e.g. const x: Connection = ...)
|
|
228
547
|
root.find(j.TSTypeReference, {
|
|
@@ -262,10 +581,15 @@ const connectionToDataSource = (file, api) => {
|
|
|
262
581
|
// Expand to the set of LOCAL bindings — covers aliased ESM imports
|
|
263
582
|
// (`import { Connection as LegacyConn }`) and aliased CJS destructures
|
|
264
583
|
// (`const { Connection: LegacyConn } = require("typeorm")`) so code
|
|
265
|
-
// using the alias still gets `.connect()` → `.initialize()`.
|
|
584
|
+
// using the alias still gets `.connect()` → `.initialize()`. When no
|
|
585
|
+
// alias is in play the set simply contains `Connection` / `DataSource`
|
|
586
|
+
// from the plain imports.
|
|
266
587
|
const connectionTypeNames = (0, ast_helpers_1.expandLocalNamesForImports)(root, j, "typeorm", new Set(["Connection", "DataSource"]));
|
|
267
|
-
|
|
268
|
-
|
|
588
|
+
// Namespace-import locals for "typeorm" — used to gate qualified type
|
|
589
|
+
// references (`typeorm.Repository<T>`) inside `getTypeReferenceRootName`
|
|
590
|
+
// so array-element lookups don't mistake `Foo.Repository[]` from another
|
|
591
|
+
// library for a TypeORM receiver.
|
|
592
|
+
const typeormNamespaceNames = (0, ast_helpers_1.getNamespaceLocalNames)(root, j, "typeorm");
|
|
269
593
|
const connectionVarNames = new Set();
|
|
270
594
|
root.find(j.VariableDeclarator).forEach((path) => {
|
|
271
595
|
const init = path.node.init;
|
|
@@ -293,32 +617,62 @@ const connectionToDataSource = (file, api) => {
|
|
|
293
617
|
});
|
|
294
618
|
(0, ast_helpers_1.forEachIdentifierParam)(root, j, collectDataSourceTyped);
|
|
295
619
|
// Rename method calls: .connect() → .initialize(), .close() → .destroy()
|
|
296
|
-
// Only on
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
620
|
+
// Only on receivers known to be Connection/DataSource instances —
|
|
621
|
+
// covered after typed-var AND `this.<member>` collection below.
|
|
622
|
+
const renameDataSourceMethods = () => {
|
|
623
|
+
for (const [oldMethod, newMethod] of Object.entries(methodRenames)) {
|
|
624
|
+
root.find(j.CallExpression, {
|
|
625
|
+
callee: {
|
|
626
|
+
type: "MemberExpression",
|
|
627
|
+
property: { name: oldMethod },
|
|
628
|
+
},
|
|
629
|
+
}).forEach((path) => {
|
|
630
|
+
if (path.node.callee.type === "MemberExpression" &&
|
|
631
|
+
path.node.callee.property.type === "Identifier" &&
|
|
632
|
+
receiverIsIn(path.node.callee.object, connectionVarNames, thisConnectionMembers)) {
|
|
308
633
|
path.node.callee.property.name = newMethod;
|
|
309
634
|
hasChanges = true;
|
|
310
635
|
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
};
|
|
314
639
|
// Gate metadata type matches on the actual local bindings imported from
|
|
315
640
|
// typeorm — users sometimes have their own classes named `EntityMetadata`
|
|
316
641
|
// or `ColumnMetadata` and their `.connection` property access must not
|
|
317
642
|
// be rewritten.
|
|
318
643
|
const connectionPropLocalNames = (0, ast_helpers_1.expandLocalNamesForImports)(root, j, "typeorm", typesWithConnectionProp);
|
|
319
644
|
const indirectDataSourceLocalNames = (0, ast_helpers_1.expandLocalNamesForImports)(root, j, "typeorm", typesWithIndirectDataSource);
|
|
645
|
+
// Specifically the local names bound to typeorm's `EntityMetadata` —
|
|
646
|
+
// used to gate the constructor-option rewrite below. Alias-aware so
|
|
647
|
+
// `import { EntityMetadata as EM }` is still recognized.
|
|
648
|
+
const entityMetadataLocalNames = (0, ast_helpers_1.expandLocalNamesForImports)(root, j, "typeorm", new Set(["EntityMetadata"]));
|
|
320
649
|
const connectionPropVarNames = new Set();
|
|
321
650
|
const indirectDataSourceVarNames = new Set();
|
|
651
|
+
// Parallel sets for `this.X` member accesses — class properties and
|
|
652
|
+
// getters whose annotation is a tracked TypeORM type. Keyed by the
|
|
653
|
+
// member name (not prefixed) since the access shape (`this.X`) already
|
|
654
|
+
// signals the lookup side.
|
|
655
|
+
const thisConnectionMembers = new Set();
|
|
656
|
+
const thisConnectionPropMembers = new Set();
|
|
657
|
+
const thisIndirectMembers = new Set();
|
|
658
|
+
const classifyType = (typeName) => {
|
|
659
|
+
if (connectionTypeNames.has(typeName))
|
|
660
|
+
return connectionVarNames;
|
|
661
|
+
if (connectionPropLocalNames.has(typeName))
|
|
662
|
+
return connectionPropVarNames;
|
|
663
|
+
if (indirectDataSourceLocalNames.has(typeName))
|
|
664
|
+
return indirectDataSourceVarNames;
|
|
665
|
+
return null;
|
|
666
|
+
};
|
|
667
|
+
const classifyThisMemberType = (typeName) => {
|
|
668
|
+
if (connectionTypeNames.has(typeName))
|
|
669
|
+
return thisConnectionMembers;
|
|
670
|
+
if (connectionPropLocalNames.has(typeName))
|
|
671
|
+
return thisConnectionPropMembers;
|
|
672
|
+
if (indirectDataSourceLocalNames.has(typeName))
|
|
673
|
+
return thisIndirectMembers;
|
|
674
|
+
return null;
|
|
675
|
+
};
|
|
322
676
|
const collectTypedIdentifier = (id) => {
|
|
323
677
|
if (!id.name || !id.typeAnnotation)
|
|
324
678
|
return;
|
|
@@ -330,18 +684,106 @@ const connectionToDataSource = (file, api) => {
|
|
|
330
684
|
const ref = ann.typeAnnotation;
|
|
331
685
|
if (ref.typeName.type !== "Identifier")
|
|
332
686
|
return;
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
else if (indirectDataSourceLocalNames.has(ref.typeName.name)) {
|
|
337
|
-
indirectDataSourceVarNames.add(id.name);
|
|
338
|
-
}
|
|
687
|
+
const bucket = classifyType(ref.typeName.name);
|
|
688
|
+
if (bucket)
|
|
689
|
+
bucket.add(id.name);
|
|
339
690
|
};
|
|
340
691
|
root.find(j.VariableDeclarator).forEach((path) => {
|
|
341
692
|
if ((0, ast_helpers_1.isIdentifier)(path.node.id))
|
|
342
693
|
collectTypedIdentifier(path.node.id);
|
|
343
694
|
});
|
|
344
695
|
(0, ast_helpers_1.forEachIdentifierParam)(root, j, collectTypedIdentifier);
|
|
696
|
+
// Class properties with a TypeORM type annotation — e.g.
|
|
697
|
+
// private readonly tenantConnection: DataSource
|
|
698
|
+
// — register `this.tenantConnection` for rename-side lookup.
|
|
699
|
+
root.find(j.ClassProperty).forEach((path) => {
|
|
700
|
+
const keyNode = path.node.key;
|
|
701
|
+
if (keyNode.type !== "Identifier")
|
|
702
|
+
return;
|
|
703
|
+
const ann = path.node.typeAnnotation;
|
|
704
|
+
if (ann?.type !== "TSTypeAnnotation")
|
|
705
|
+
return;
|
|
706
|
+
const inner = ann.typeAnnotation;
|
|
707
|
+
if (inner.type !== "TSTypeReference")
|
|
708
|
+
return;
|
|
709
|
+
const typeName = inner.typeName;
|
|
710
|
+
if (typeName.type !== "Identifier")
|
|
711
|
+
return;
|
|
712
|
+
const bucket = classifyThisMemberType(typeName.name);
|
|
713
|
+
if (bucket)
|
|
714
|
+
bucket.add(keyNode.name);
|
|
715
|
+
});
|
|
716
|
+
// Constructor parameter properties — `constructor(private readonly x: DataSource)`
|
|
717
|
+
// is a TSParameterProperty whose inner `parameter` is the typed Identifier.
|
|
718
|
+
// These become class fields accessed via `this.x`.
|
|
719
|
+
root.find(j.TSParameterProperty).forEach((path) => {
|
|
720
|
+
const inner = path.node.parameter;
|
|
721
|
+
if (!inner)
|
|
722
|
+
return;
|
|
723
|
+
const id = resolveTSParameterPropertyIdentifier(inner);
|
|
724
|
+
if (!id)
|
|
725
|
+
return;
|
|
726
|
+
const ann = id.typeAnnotation;
|
|
727
|
+
if (ann?.type !== "TSTypeAnnotation")
|
|
728
|
+
return;
|
|
729
|
+
if (ann.typeAnnotation.type !== "TSTypeReference")
|
|
730
|
+
return;
|
|
731
|
+
const ref = ann.typeAnnotation;
|
|
732
|
+
if (ref.typeName.type !== "Identifier")
|
|
733
|
+
return;
|
|
734
|
+
const bucket = classifyThisMemberType(ref.typeName.name);
|
|
735
|
+
if (bucket)
|
|
736
|
+
bucket.add(id.name);
|
|
737
|
+
});
|
|
738
|
+
// Class getters with a return type annotation — e.g.
|
|
739
|
+
// private get entityManager(): EntityManager { ... }
|
|
740
|
+
// — same `this.X` lookup path.
|
|
741
|
+
root.find(j.ClassMethod).forEach((path) => {
|
|
742
|
+
if (path.node.kind !== "get")
|
|
743
|
+
return;
|
|
744
|
+
const keyNode = path.node.key;
|
|
745
|
+
if (keyNode.type !== "Identifier")
|
|
746
|
+
return;
|
|
747
|
+
const rt = path.node.returnType;
|
|
748
|
+
if (rt?.type !== "TSTypeAnnotation")
|
|
749
|
+
return;
|
|
750
|
+
const inner = rt.typeAnnotation;
|
|
751
|
+
if (inner.type !== "TSTypeReference")
|
|
752
|
+
return;
|
|
753
|
+
const typeName = inner.typeName;
|
|
754
|
+
if (typeName.type !== "Identifier")
|
|
755
|
+
return;
|
|
756
|
+
const bucket = classifyThisMemberType(typeName.name);
|
|
757
|
+
if (bucket)
|
|
758
|
+
bucket.add(keyNode.name);
|
|
759
|
+
});
|
|
760
|
+
// Resolves an object-access receiver to the set of known-type names it
|
|
761
|
+
// resolves against. Returns null for receivers we can't classify.
|
|
762
|
+
// x → { key: "x", isThisMember: false }
|
|
763
|
+
// this.x → { key: "x", isThisMember: true }
|
|
764
|
+
// (x as T) → same as x
|
|
765
|
+
// (this.x as T) → same as this.x
|
|
766
|
+
const resolveReceiver = (node) => {
|
|
767
|
+
const direct = unwrapIdentifierName(node);
|
|
768
|
+
if (direct)
|
|
769
|
+
return { key: direct, isThisMember: false };
|
|
770
|
+
const unwrapped = (0, ast_helpers_1.unwrapTsExpression)(node);
|
|
771
|
+
if ((unwrapped.type === "MemberExpression" ||
|
|
772
|
+
unwrapped.type === "OptionalMemberExpression") &&
|
|
773
|
+
unwrapped.computed !== true &&
|
|
774
|
+
unwrapped.object?.type === "ThisExpression" &&
|
|
775
|
+
unwrapped.property?.type === "Identifier") {
|
|
776
|
+
const prop = unwrapped.property;
|
|
777
|
+
return { key: prop.name, isThisMember: true };
|
|
778
|
+
}
|
|
779
|
+
return null;
|
|
780
|
+
};
|
|
781
|
+
const receiverIsIn = (node, bare, thisMember) => {
|
|
782
|
+
const r = resolveReceiver(node);
|
|
783
|
+
if (!r)
|
|
784
|
+
return false;
|
|
785
|
+
return r.isThisMember ? thisMember.has(r.key) : bare.has(r.key);
|
|
786
|
+
};
|
|
345
787
|
// Track variables assigned from DataSource accessors, so code like
|
|
346
788
|
// const manager = dataSource.manager
|
|
347
789
|
// manager.connection.getMetadata(...)
|
|
@@ -358,24 +800,38 @@ const connectionToDataSource = (file, api) => {
|
|
|
358
800
|
createQueryRunner: "QueryRunner",
|
|
359
801
|
createQueryBuilder: "SelectQueryBuilder",
|
|
360
802
|
};
|
|
803
|
+
// Recognizes DataSource receivers: a bare local, or `this.<member>` tracked
|
|
804
|
+
// by the TSParameterProperty / ClassProperty walks above.
|
|
805
|
+
const isDataSourceReceiver = (node) => {
|
|
806
|
+
const direct = unwrapIdentifierName(node);
|
|
807
|
+
if (direct && connectionVarNames.has(direct))
|
|
808
|
+
return true;
|
|
809
|
+
const unwrapped = (0, ast_helpers_1.unwrapTsExpression)(node);
|
|
810
|
+
if ((unwrapped.type === "MemberExpression" ||
|
|
811
|
+
unwrapped.type === "OptionalMemberExpression") &&
|
|
812
|
+
unwrapped.computed !== true &&
|
|
813
|
+
unwrapped.object?.type === "ThisExpression" &&
|
|
814
|
+
unwrapped.property?.type === "Identifier") {
|
|
815
|
+
const propName = unwrapped.property.name;
|
|
816
|
+
if (thisConnectionMembers.has(propName))
|
|
817
|
+
return true;
|
|
818
|
+
}
|
|
819
|
+
return false;
|
|
820
|
+
};
|
|
361
821
|
// Returns the TypeORM type that a DataSource accessor chain resolves to
|
|
362
|
-
// (`ds.manager` → "EntityManager",
|
|
363
|
-
// or null when the initializer isn't a recognized accessor-chain pattern.
|
|
822
|
+
// (`ds.manager` → "EntityManager", etc.), or null if unrecognized.
|
|
364
823
|
const resolveAccessorChainType = (init) => {
|
|
365
824
|
if (init.type === "MemberExpression") {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
init.property.type !== "Identifier") {
|
|
825
|
+
if (init.property.type !== "Identifier")
|
|
826
|
+
return null;
|
|
827
|
+
if (!isDataSourceReceiver(init.object))
|
|
370
828
|
return null;
|
|
371
|
-
}
|
|
372
829
|
return dataSourceMemberAccessors[init.property.name] ?? null;
|
|
373
830
|
}
|
|
374
831
|
if (init.type === "CallExpression" &&
|
|
375
832
|
init.callee.type === "MemberExpression" &&
|
|
376
833
|
init.callee.property.type === "Identifier") {
|
|
377
|
-
|
|
378
|
-
if (!baseName || !connectionVarNames.has(baseName))
|
|
834
|
+
if (!isDataSourceReceiver(init.callee.object))
|
|
379
835
|
return null;
|
|
380
836
|
return dataSourceCallAccessors[init.callee.property.name] ?? null;
|
|
381
837
|
}
|
|
@@ -391,16 +847,90 @@ const connectionToDataSource = (file, api) => {
|
|
|
391
847
|
connectionPropVarNames.add(path.node.id.name);
|
|
392
848
|
}
|
|
393
849
|
});
|
|
850
|
+
// Class fields with no explicit type annotation but an accessor-chain
|
|
851
|
+
// initializer (`private manager = this.dataSource.manager`). Extends the
|
|
852
|
+
// earlier ClassProperty walk, which only tracks fields with an explicit
|
|
853
|
+
// TypeORM type annotation.
|
|
854
|
+
root.find(j.ClassProperty).forEach((path) => {
|
|
855
|
+
const keyNode = path.node.key;
|
|
856
|
+
if (keyNode.type !== "Identifier")
|
|
857
|
+
return;
|
|
858
|
+
const existing = path.node
|
|
859
|
+
.typeAnnotation;
|
|
860
|
+
if (existing?.type === "TSTypeAnnotation")
|
|
861
|
+
return;
|
|
862
|
+
const value = path.node.value;
|
|
863
|
+
if (!value)
|
|
864
|
+
return;
|
|
865
|
+
const typeName = resolveAccessorChainType(value);
|
|
866
|
+
if (typeName && typesWithConnectionProp.has(typeName)) {
|
|
867
|
+
thisConnectionPropMembers.add(keyNode.name);
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
// Destructured property → TypeORM type, for `const { queryRunner } = event`
|
|
871
|
+
// style bindings where the event is already tracked as a receiver.
|
|
872
|
+
const destructuredPropertyTypes = {
|
|
873
|
+
manager: "EntityManager",
|
|
874
|
+
mongoManager: "EntityManager",
|
|
875
|
+
queryRunner: "QueryRunner",
|
|
876
|
+
metadata: "EntityMetadata",
|
|
877
|
+
};
|
|
878
|
+
// Local binding name for an ObjectPattern property value. Handles both the
|
|
879
|
+
// plain `{ queryRunner }` / `{ queryRunner: qr }` forms and defaulted
|
|
880
|
+
// `{ queryRunner = defaultQr }`.
|
|
881
|
+
const getPatternBindingName = (value) => {
|
|
882
|
+
if (value.type === "Identifier") {
|
|
883
|
+
return value.name;
|
|
884
|
+
}
|
|
885
|
+
if (value.type === "AssignmentPattern" &&
|
|
886
|
+
value.left.type === "Identifier") {
|
|
887
|
+
return value.left.name;
|
|
888
|
+
}
|
|
889
|
+
return null;
|
|
890
|
+
};
|
|
891
|
+
const classifyDestructuredProperty = (prop) => {
|
|
892
|
+
if (prop.type !== "Property" && prop.type !== "ObjectProperty")
|
|
893
|
+
return;
|
|
894
|
+
const typed = prop;
|
|
895
|
+
if (typed.key.type !== "Identifier" || !typed.key.name)
|
|
896
|
+
return;
|
|
897
|
+
const typeName = destructuredPropertyTypes[typed.key.name];
|
|
898
|
+
if (!typeName)
|
|
899
|
+
return;
|
|
900
|
+
const local = getPatternBindingName(typed.value);
|
|
901
|
+
if (!local)
|
|
902
|
+
return;
|
|
903
|
+
if (typesWithConnectionProp.has(typeName)) {
|
|
904
|
+
connectionPropVarNames.add(local);
|
|
905
|
+
}
|
|
906
|
+
if (typesWithIndirectDataSource.has(typeName)) {
|
|
907
|
+
indirectDataSourceVarNames.add(local);
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
root.find(j.VariableDeclarator).forEach((declPath) => {
|
|
911
|
+
const init = declPath.node.init;
|
|
912
|
+
if (!init)
|
|
913
|
+
return;
|
|
914
|
+
if (!receiverIsIn(init, connectionPropVarNames, thisConnectionPropMembers)) {
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
const id = declPath.node.id;
|
|
918
|
+
if (id.type !== "ObjectPattern")
|
|
919
|
+
return;
|
|
920
|
+
for (const prop of id.properties) {
|
|
921
|
+
classifyDestructuredProperty(prop);
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
renameDataSourceMethods();
|
|
394
925
|
// Rename .isConnected → .isInitialized on Connection/DataSource instances
|
|
395
926
|
root.find(j.MemberExpression, {
|
|
396
927
|
property: { name: "isConnected" },
|
|
397
928
|
}).forEach((path) => {
|
|
398
|
-
if (path.node.property.type
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
929
|
+
if (path.node.property.type !== "Identifier")
|
|
930
|
+
return;
|
|
931
|
+
if (receiverIsIn(path.node.object, connectionVarNames, thisConnectionMembers)) {
|
|
932
|
+
path.node.property.name = "isInitialized";
|
|
933
|
+
hasChanges = true;
|
|
404
934
|
}
|
|
405
935
|
});
|
|
406
936
|
// Rename .connection → .dataSource on known TypeORM instances.
|
|
@@ -411,21 +941,274 @@ const connectionToDataSource = (file, api) => {
|
|
|
411
941
|
}).forEach((path) => {
|
|
412
942
|
if (path.node.property.type !== "Identifier")
|
|
413
943
|
return;
|
|
414
|
-
|
|
415
|
-
if (!objName)
|
|
416
|
-
return;
|
|
417
|
-
if (connectionPropVarNames.has(objName)) {
|
|
944
|
+
if (receiverIsIn(path.node.object, connectionPropVarNames, thisConnectionPropMembers)) {
|
|
418
945
|
path.node.property.name = "dataSource";
|
|
419
946
|
hasChanges = true;
|
|
420
947
|
return;
|
|
421
948
|
}
|
|
422
|
-
if (indirectDataSourceVarNames
|
|
949
|
+
if (receiverIsIn(path.node.object, indirectDataSourceVarNames, thisIndirectMembers)) {
|
|
423
950
|
// `col.connection` → `col.entityMetadata.dataSource`
|
|
424
951
|
path.node.object = j.memberExpression(path.node.object, j.identifier("entityMetadata"));
|
|
425
952
|
path.node.property.name = "dataSource";
|
|
426
953
|
hasChanges = true;
|
|
427
954
|
}
|
|
428
955
|
});
|
|
956
|
+
const isReferenceIdentifier = (idPath) => {
|
|
957
|
+
const parentNode = idPath.parent?.node;
|
|
958
|
+
if (!parentNode)
|
|
959
|
+
return true;
|
|
960
|
+
const parent = parentNode;
|
|
961
|
+
const isPropertyKey = parent.type === "Property" ||
|
|
962
|
+
parent.type === "ObjectProperty" ||
|
|
963
|
+
parent.type === "TSPropertySignature" ||
|
|
964
|
+
parent.type === "ClassProperty" ||
|
|
965
|
+
parent.type === "ClassMethod" ||
|
|
966
|
+
parent.type === "MethodDefinition";
|
|
967
|
+
if (isPropertyKey && parent.key === idPath.node && !parent.computed) {
|
|
968
|
+
return false;
|
|
969
|
+
}
|
|
970
|
+
const isMemberProperty = parent.type === "MemberExpression" ||
|
|
971
|
+
parent.type === "OptionalMemberExpression";
|
|
972
|
+
if (isMemberProperty &&
|
|
973
|
+
parent.property === idPath.node &&
|
|
974
|
+
!parent.computed) {
|
|
975
|
+
return false;
|
|
976
|
+
}
|
|
977
|
+
if (parent.type === "ImportSpecifier" &&
|
|
978
|
+
parent.imported === idPath.node) {
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
if (parent.type === "ExportSpecifier" &&
|
|
982
|
+
parent.exported === idPath.node) {
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
return true;
|
|
986
|
+
};
|
|
987
|
+
// Cache all `connection` and `dataSource` identifier paths once, then
|
|
988
|
+
// filter by scope subtree per destructure pass instead of re-walking the
|
|
989
|
+
// AST. Both caches use the current `node.name` at lookup time, so a
|
|
990
|
+
// previously-renamed `connection` → `dataSource` node correctly counts
|
|
991
|
+
// as a `dataSource` reference in subsequent passes.
|
|
992
|
+
const allConnectionRefs = [];
|
|
993
|
+
const allDataSourceRefs = [];
|
|
994
|
+
root.find(j.Identifier, { name: "connection" }).forEach((p) => {
|
|
995
|
+
allConnectionRefs.push(p);
|
|
996
|
+
});
|
|
997
|
+
root.find(j.Identifier, { name: "dataSource" }).forEach((p) => {
|
|
998
|
+
allDataSourceRefs.push(p);
|
|
999
|
+
});
|
|
1000
|
+
const isInSubtree = (idPath, root) => {
|
|
1001
|
+
let cursor = idPath.parent;
|
|
1002
|
+
while (cursor) {
|
|
1003
|
+
if (cursor.node === root)
|
|
1004
|
+
return true;
|
|
1005
|
+
cursor = cursor.parent;
|
|
1006
|
+
}
|
|
1007
|
+
return false;
|
|
1008
|
+
};
|
|
1009
|
+
// True if any `dataSource` reference lives inside the given subtree — that
|
|
1010
|
+
// reference would rebind to the about-to-be-renamed local, so the rename
|
|
1011
|
+
// must fall back to the alias form. Checks both caches because a previous
|
|
1012
|
+
// rename pass may have renamed a cached `connection` path to `dataSource`,
|
|
1013
|
+
// creating a sibling-scope conflict that ast-types' scope object — being
|
|
1014
|
+
// built once at parse time — won't reflect.
|
|
1015
|
+
const hasDataSourceUsageIn = (scopeBodyNode) => {
|
|
1016
|
+
const isDataSourceRefInSubtree = (idPath) => idPath.node.name === "dataSource" &&
|
|
1017
|
+
isInSubtree(idPath, scopeBodyNode) &&
|
|
1018
|
+
isReferenceIdentifier(idPath);
|
|
1019
|
+
for (const p of allDataSourceRefs) {
|
|
1020
|
+
if (isDataSourceRefInSubtree(p))
|
|
1021
|
+
return true;
|
|
1022
|
+
}
|
|
1023
|
+
for (const p of allConnectionRefs) {
|
|
1024
|
+
if (isDataSourceRefInSubtree(p))
|
|
1025
|
+
return true;
|
|
1026
|
+
}
|
|
1027
|
+
return false;
|
|
1028
|
+
};
|
|
1029
|
+
// Collect every `connection` reference inside `scopeBodyNode` whose
|
|
1030
|
+
// resolved binding is `scope`. Skips non-reference positions and paths
|
|
1031
|
+
// whose node was renamed by a previous pass.
|
|
1032
|
+
const collectConnectionRefsInScope = (scope, scopeBodyNode) => {
|
|
1033
|
+
const expectedScopeNode = scope.path?.node;
|
|
1034
|
+
const refs = [];
|
|
1035
|
+
for (const idPath of allConnectionRefs) {
|
|
1036
|
+
if (idPath.node.name !== "connection")
|
|
1037
|
+
continue;
|
|
1038
|
+
if (!isInSubtree(idPath, scopeBodyNode))
|
|
1039
|
+
continue;
|
|
1040
|
+
if (!isReferenceIdentifier(idPath))
|
|
1041
|
+
continue;
|
|
1042
|
+
const refScope = idPath.scope;
|
|
1043
|
+
if (!refScope)
|
|
1044
|
+
continue;
|
|
1045
|
+
const lookup = refScope.lookup("connection");
|
|
1046
|
+
if (!lookup)
|
|
1047
|
+
continue;
|
|
1048
|
+
if (lookup !== scope &&
|
|
1049
|
+
(!expectedScopeNode || lookup.path?.node !== expectedScopeNode)) {
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
refs.push(idPath);
|
|
1053
|
+
}
|
|
1054
|
+
return refs;
|
|
1055
|
+
};
|
|
1056
|
+
// Attempts a scope-aware rename of a shorthand `{ connection }` binding to
|
|
1057
|
+
// `{ dataSource }`, plus every reference to the local within the scope.
|
|
1058
|
+
// Returns false (caller falls back to the alias form) when the rename
|
|
1059
|
+
// would disturb an existing `dataSource` usage in the subtree.
|
|
1060
|
+
const tryRenameShorthandConnectionBinding = (prop, declPath, scopeBodyNode) => {
|
|
1061
|
+
const scope = declPath.scope;
|
|
1062
|
+
if (!scope)
|
|
1063
|
+
return false;
|
|
1064
|
+
if (scope.declares("dataSource"))
|
|
1065
|
+
return false;
|
|
1066
|
+
if (hasDataSourceUsageIn(scopeBodyNode))
|
|
1067
|
+
return false;
|
|
1068
|
+
const refsToRename = collectConnectionRefsInScope(scope, scopeBodyNode);
|
|
1069
|
+
prop.key.name = "dataSource";
|
|
1070
|
+
if (prop.value.type === "Identifier") {
|
|
1071
|
+
;
|
|
1072
|
+
prop.value.name = "dataSource";
|
|
1073
|
+
}
|
|
1074
|
+
else if (getPatternBindingName(prop.value) !== null) {
|
|
1075
|
+
;
|
|
1076
|
+
prop.value.left.name = "dataSource";
|
|
1077
|
+
}
|
|
1078
|
+
for (const ref of refsToRename)
|
|
1079
|
+
ref.node.name = "dataSource";
|
|
1080
|
+
return true;
|
|
1081
|
+
};
|
|
1082
|
+
// `const { connection } = event` / `for (const { connection } of events)`
|
|
1083
|
+
// where the RHS is a tracked receiver. Tries a scope-aware rename of the
|
|
1084
|
+
// local binding; falls back to the alias form `{ dataSource: connection }`
|
|
1085
|
+
// when the rename would collide with an existing `dataSource` binding.
|
|
1086
|
+
const renameConnectionInObjectPattern = (id, declPath, scopeBodyNode) => {
|
|
1087
|
+
if (id.type !== "ObjectPattern")
|
|
1088
|
+
return;
|
|
1089
|
+
for (const prop of id.properties) {
|
|
1090
|
+
if (prop.type !== "Property" && prop.type !== "ObjectProperty")
|
|
1091
|
+
continue;
|
|
1092
|
+
const typed = prop;
|
|
1093
|
+
if (typed.key.type !== "Identifier")
|
|
1094
|
+
continue;
|
|
1095
|
+
if (typed.key.name !== "connection")
|
|
1096
|
+
continue;
|
|
1097
|
+
if (typed.shorthand &&
|
|
1098
|
+
tryRenameShorthandConnectionBinding(typed, declPath, scopeBodyNode)) {
|
|
1099
|
+
hasChanges = true;
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
// Fallback: rename only the key, keep the local name.
|
|
1103
|
+
// `{ connection }` expands to `{ dataSource: connection }`.
|
|
1104
|
+
typed.key.name = "dataSource";
|
|
1105
|
+
if (typed.shorthand)
|
|
1106
|
+
typed.shorthand = false;
|
|
1107
|
+
hasChanges = true;
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
root.find(j.VariableDeclarator).forEach((declPath) => {
|
|
1111
|
+
const init = declPath.node.init;
|
|
1112
|
+
if (!init)
|
|
1113
|
+
return;
|
|
1114
|
+
if (!receiverIsIn(init, connectionPropVarNames, thisConnectionPropMembers)) {
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
const scoped = declPath;
|
|
1118
|
+
const scopeNode = scoped.scope?.path?.node ?? declPath.node;
|
|
1119
|
+
renameConnectionInObjectPattern(declPath.node.id, scoped, scopeNode);
|
|
1120
|
+
});
|
|
1121
|
+
// `for (const { connection } of events)` — `ForOfStatement.left` is a
|
|
1122
|
+
// `VariableDeclaration` whose declarators have no `init`, so the
|
|
1123
|
+
// plain VariableDeclarator loop above skips them. Classify the loop
|
|
1124
|
+
// variable's inferred type: either the iterable variable itself is a
|
|
1125
|
+
// tracked receiver, or its declared annotation is `Tracked[]` /
|
|
1126
|
+
// `Array<Tracked>`.
|
|
1127
|
+
const elementTypeOf = (rhsNode) => {
|
|
1128
|
+
if (rhsNode.type !== "Identifier")
|
|
1129
|
+
return null;
|
|
1130
|
+
const name = rhsNode.name;
|
|
1131
|
+
let resolved = null;
|
|
1132
|
+
root.find(j.VariableDeclarator).forEach((p) => {
|
|
1133
|
+
if (resolved)
|
|
1134
|
+
return;
|
|
1135
|
+
if (p.node.id.type !== "Identifier")
|
|
1136
|
+
return;
|
|
1137
|
+
if (p.node.id.name !== name)
|
|
1138
|
+
return;
|
|
1139
|
+
const ann = p.node.id.typeAnnotation;
|
|
1140
|
+
if (ann?.type !== "TSTypeAnnotation")
|
|
1141
|
+
return;
|
|
1142
|
+
const inner = ann.typeAnnotation;
|
|
1143
|
+
if (inner.type === "TSArrayType") {
|
|
1144
|
+
resolved =
|
|
1145
|
+
(0, ast_helpers_1.getTypeReferenceRootName)(inner.elementType, typeormNamespaceNames) ?? null;
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
if (inner.type === "TSTypeReference" &&
|
|
1149
|
+
inner.typeName?.name ===
|
|
1150
|
+
"Array") {
|
|
1151
|
+
const params = inner.typeParameters?.params;
|
|
1152
|
+
if (params && params.length > 0) {
|
|
1153
|
+
resolved =
|
|
1154
|
+
(0, ast_helpers_1.getTypeReferenceRootName)(params[0], typeormNamespaceNames) ?? null;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
return resolved;
|
|
1159
|
+
};
|
|
1160
|
+
// Set of VariableDeclarator AST nodes that belong to a tracked for-of /
|
|
1161
|
+
// for-in loop's `left` (i.e. the per-iteration binding). Populated in the
|
|
1162
|
+
// ForOf/ForIn walks below; consumed by the root-wide VariableDeclarator
|
|
1163
|
+
// walk that follows, so declarator paths carry their proper `.scope` info.
|
|
1164
|
+
const forLoopDeclaratorNodes = new Set();
|
|
1165
|
+
// Maps a tracked declarator node → the loop body to walk for references.
|
|
1166
|
+
const forLoopBodyByDeclarator = new Map();
|
|
1167
|
+
const collectForLoopDeclarators = (forPath) => {
|
|
1168
|
+
const leftNode = forPath.node.left;
|
|
1169
|
+
const rightNode = forPath.node.right;
|
|
1170
|
+
const bodyNode = forPath.node.body;
|
|
1171
|
+
if (!leftNode || !rightNode || !bodyNode)
|
|
1172
|
+
return;
|
|
1173
|
+
if (leftNode.type !== "VariableDeclaration")
|
|
1174
|
+
return;
|
|
1175
|
+
const directlyTracked = receiverIsIn(rightNode, connectionPropVarNames, thisConnectionPropMembers);
|
|
1176
|
+
const elementType = elementTypeOf(rightNode);
|
|
1177
|
+
const elementTracked = elementType !== null && connectionPropLocalNames.has(elementType);
|
|
1178
|
+
if (!directlyTracked && !elementTracked)
|
|
1179
|
+
return;
|
|
1180
|
+
for (const d of leftNode
|
|
1181
|
+
.declarations) {
|
|
1182
|
+
if (d.type !== "VariableDeclarator")
|
|
1183
|
+
continue;
|
|
1184
|
+
forLoopDeclaratorNodes.add(d);
|
|
1185
|
+
forLoopBodyByDeclarator.set(d, bodyNode);
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
root.find(j.ForOfStatement).forEach(collectForLoopDeclarators);
|
|
1189
|
+
root.find(j.ForInStatement).forEach(collectForLoopDeclarators);
|
|
1190
|
+
if (forLoopDeclaratorNodes.size > 0) {
|
|
1191
|
+
// Walking from the root (rather than `j(forPath.node)`) preserves
|
|
1192
|
+
// `.scope` on the declarator paths — ast-types scope info is only
|
|
1193
|
+
// populated by the top-down traversal that starts at Program.
|
|
1194
|
+
root.find(j.VariableDeclarator).forEach((dPath) => {
|
|
1195
|
+
if (!forLoopDeclaratorNodes.has(dPath.node))
|
|
1196
|
+
return;
|
|
1197
|
+
const bodyNode = forLoopBodyByDeclarator.get(dPath.node);
|
|
1198
|
+
if (!bodyNode)
|
|
1199
|
+
return;
|
|
1200
|
+
renameConnectionInObjectPattern(dPath.node.id, dPath, bodyNode);
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
// Rewrite the `connection` option passed to metadata-type constructors:
|
|
1204
|
+
// - `new EntityMetadata({ connection: X, ... })` → rename key to
|
|
1205
|
+
// `dataSource` (the option was just renamed in v1).
|
|
1206
|
+
// - `new ColumnMetadata({ connection, ... })` / `IndexMetadata` →
|
|
1207
|
+
// drop the `connection` key entirely. These constructors no longer
|
|
1208
|
+
// accept `connection` in v1; the DataSource is reached through
|
|
1209
|
+
// `entityMetadata.dataSource` and is set by the entity-metadata
|
|
1210
|
+
// builder rather than the caller.
|
|
1211
|
+
hasChanges = rewriteMetadataConstructors(root, j, entityMetadataLocalNames, indirectDataSourceLocalNames, hasChanges);
|
|
429
1212
|
return hasChanges ? root.toSource() : undefined;
|
|
430
1213
|
};
|
|
431
1214
|
exports.connectionToDataSource = connectionToDataSource;
|