@typeorm/codemod 1.0.1 → 1.0.2

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.
Files changed (70) hide show
  1. package/README.md +1 -0
  2. package/dist/cli/print-usage.js +1 -0
  3. package/dist/cli/print-usage.js.map +1 -1
  4. package/dist/cli/run-transforms.js +167 -79
  5. package/dist/cli/run-transforms.js.map +1 -1
  6. package/dist/dependencies/v1.js +1 -0
  7. package/dist/dependencies/v1.js.map +1 -1
  8. package/dist/index.js +8 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/transforms/ast-helpers.js +641 -17
  11. package/dist/transforms/ast-helpers.js.map +1 -1
  12. package/dist/transforms/stats.js +11 -0
  13. package/dist/transforms/stats.js.map +1 -1
  14. package/dist/transforms/todo.js +22 -5
  15. package/dist/transforms/todo.js.map +1 -1
  16. package/dist/transforms/v1/column-unsigned-numeric.js +21 -18
  17. package/dist/transforms/v1/column-unsigned-numeric.js.map +1 -1
  18. package/dist/transforms/v1/connection-options-reader.js +23 -7
  19. package/dist/transforms/v1/connection-options-reader.js.map +1 -1
  20. package/dist/transforms/v1/connection-to-datasource.js +613 -77
  21. package/dist/transforms/v1/connection-to-datasource.js.map +1 -1
  22. package/dist/transforms/v1/datasource-expo.js +79 -0
  23. package/dist/transforms/v1/datasource-expo.js.map +1 -0
  24. package/dist/transforms/v1/datasource-mongodb.js +77 -50
  25. package/dist/transforms/v1/datasource-mongodb.js.map +1 -1
  26. package/dist/transforms/v1/datasource-mssql.js +88 -44
  27. package/dist/transforms/v1/datasource-mssql.js.map +1 -1
  28. package/dist/transforms/v1/datasource-mysql-connector.js +22 -6
  29. package/dist/transforms/v1/datasource-mysql-connector.js.map +1 -1
  30. package/dist/transforms/v1/datasource-name.js +17 -14
  31. package/dist/transforms/v1/datasource-name.js.map +1 -1
  32. package/dist/transforms/v1/datasource-sap.js +51 -39
  33. package/dist/transforms/v1/datasource-sap.js.map +1 -1
  34. package/dist/transforms/v1/file-logger.js +2 -21
  35. package/dist/transforms/v1/file-logger.js.map +1 -1
  36. package/dist/transforms/v1/find-options-string-relations.js +75 -11
  37. package/dist/transforms/v1/find-options-string-relations.js.map +1 -1
  38. package/dist/transforms/v1/find-options-string-select.js +75 -13
  39. package/dist/transforms/v1/find-options-string-select.js.map +1 -1
  40. package/dist/transforms/v1/global-functions.js +79 -21
  41. package/dist/transforms/v1/global-functions.js.map +1 -1
  42. package/dist/transforms/v1/index.js +6 -1
  43. package/dist/transforms/v1/index.js.map +1 -1
  44. package/dist/transforms/v1/mongodb-stats.js +18 -11
  45. package/dist/transforms/v1/mongodb-stats.js.map +1 -1
  46. package/dist/transforms/v1/mongodb-types.js +59 -2
  47. package/dist/transforms/v1/mongodb-types.js.map +1 -1
  48. package/dist/transforms/v1/query-builder-on-conflict.js +30 -17
  49. package/dist/transforms/v1/query-builder-on-conflict.js.map +1 -1
  50. package/dist/transforms/v1/query-builder-print-sql.js +15 -12
  51. package/dist/transforms/v1/query-builder-print-sql.js.map +1 -1
  52. package/dist/transforms/v1/query-builder-replace-property-names.js +13 -12
  53. package/dist/transforms/v1/query-builder-replace-property-names.js.map +1 -1
  54. package/dist/transforms/v1/query-builder-where-expression.js +29 -12
  55. package/dist/transforms/v1/query-builder-where-expression.js.map +1 -1
  56. package/dist/transforms/v1/query-runner-loaded-tables-views.js +0 -1
  57. package/dist/transforms/v1/query-runner-loaded-tables-views.js.map +1 -1
  58. package/dist/transforms/v1/relation-count.js +3 -0
  59. package/dist/transforms/v1/relation-count.js.map +1 -1
  60. package/dist/transforms/v1/repository-abstract.js +146 -29
  61. package/dist/transforms/v1/repository-abstract.js.map +1 -1
  62. package/dist/transforms/v1/repository-exist.js +18 -1
  63. package/dist/transforms/v1/repository-exist.js.map +1 -1
  64. package/dist/transforms/v1/repository-find-by-ids.js +67 -24
  65. package/dist/transforms/v1/repository-find-by-ids.js.map +1 -1
  66. package/dist/transforms/v1/repository-find-one-by-id.js +3 -0
  67. package/dist/transforms/v1/repository-find-one-by-id.js.map +1 -1
  68. package/dist/transforms/v1/use-container.js +60 -12
  69. package/dist/transforms/v1/use-container.js.map +1 -1
  70. package/package.json +1 -1
@@ -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 (`as`, `!`, parens) around
11
- * an identifier and returns the identifier's name. Used so accessor-chain
12
- * tracking also recognizes `(ds as DataSource).manager`, `ds!.manager`, and
13
- * `(ds).manager` — patterns jscodeshift would otherwise miss because the
14
- * surface node is a `TSAsExpression` / `TSNonNullExpression` / paren.
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
- MysqlConnectionOptions: "MysqlDataSourceOptions",
42
- MariaDbConnectionOptions: "MariaDbDataSourceOptions",
43
- PostgresConnectionOptions: "PostgresDataSourceOptions",
44
- CockroachConnectionOptions: "CockroachDataSourceOptions",
45
- SqlServerConnectionOptions: "SqlServerDataSourceOptions",
46
- OracleConnectionOptions: "OracleDataSourceOptions",
47
- SqliteConnectionOptions: "BetterSqlite3DataSourceOptions",
48
- BetterSqlite3ConnectionOptions: "BetterSqlite3DataSourceOptions",
49
- SapConnectionOptions: "SapDataSourceOptions",
50
- MongoConnectionOptions: "MongoDataSourceOptions",
51
- CordovaConnectionOptions: "CordovaDataSourceOptions",
52
- NativescriptConnectionOptions: "NativescriptDataSourceOptions",
53
- ReactNativeConnectionOptions: "ReactNativeDataSourceOptions",
54
- ExpoConnectionOptions: "ExpoDataSourceOptions",
55
- AuroraMysqlConnectionOptions: "AuroraMysqlDataSourceOptions",
56
- AuroraPostgresConnectionOptions: "AuroraPostgresDataSourceOptions",
57
- SpannerConnectionOptions: "SpannerDataSourceOptions",
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. Consults `deepPathRewrites` first for
98
- // cross-directory moves, then falls back to swapping the last path
99
- // segment when it's an exact rename key.
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
- if (hasAlias) {
135
- // Alias stays; user chose it deliberately. The alias already
136
- // refers to the renamed import so usages don't need rewriting.
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;
@@ -174,6 +351,136 @@ const connectionToDataSource = (file, api) => {
174
351
  hasChanges = true;
175
352
  }
176
353
  });
354
+ // Driver-specific options types aren't top-level exports from "typeorm",
355
+ // and deep-path imports like `"typeorm/driver/sap/SapDataSourceOptions"`
356
+ // are brittle across refactors. Rewrite every reference to
357
+ // `Extract<DataSourceOptions, { type: "<driver>" }>` so users only need
358
+ // the union exported from the index. Handles both v0 (`SapConnectionOptions`)
359
+ // and v1 (`SapDataSourceOptions`) names, and aliased imports.
360
+ const driverOptionLocalBindings = new Map();
361
+ root.find(j.ImportDeclaration).forEach((importPath) => {
362
+ const importSource = importPath.node.source.value;
363
+ if (typeof importSource !== "string" ||
364
+ (importSource !== "typeorm" &&
365
+ !importSource.startsWith(typeormPathPrefix))) {
366
+ return;
367
+ }
368
+ const specifiers = importPath.node.specifiers ?? [];
369
+ const kept = specifiers.filter((spec) => {
370
+ if (spec.type !== "ImportSpecifier" ||
371
+ spec.imported.type !== "Identifier") {
372
+ return true;
373
+ }
374
+ const literals = driverOptionTypeLiterals[spec.imported.name];
375
+ if (!literals)
376
+ return true;
377
+ const localName = spec.local?.type === "Identifier"
378
+ ? spec.local.name
379
+ : spec.imported.name;
380
+ driverOptionLocalBindings.set(localName, literals);
381
+ return false;
382
+ });
383
+ if (kept.length !== specifiers.length) {
384
+ importPath.node.specifiers = kept;
385
+ hasChanges = true;
386
+ if (kept.length === 0)
387
+ j(importPath).remove();
388
+ }
389
+ });
390
+ if (driverOptionLocalBindings.size > 0) {
391
+ // Walk every type reference in the file and replace tracked local
392
+ // bindings with an inline Extract<DataSourceOptions, { type: ... }>
393
+ // expression. TS narrowing via the `type` discriminator still picks
394
+ // the correct driver-specific fields.
395
+ root.find(j.TSTypeReference).forEach((refPath) => {
396
+ const typeName = refPath.node.typeName;
397
+ if (typeName.type !== "Identifier")
398
+ return;
399
+ const literals = driverOptionLocalBindings.get(typeName.name);
400
+ if (!literals)
401
+ return;
402
+ j(refPath).replaceWith(buildExtractDataSourceOptions(j, literals));
403
+ hasChanges = true;
404
+ });
405
+ // Emit / augment `import type { DataSourceOptions } from "typeorm"`
406
+ // so the Extract expressions resolve.
407
+ ensureDataSourceOptionsTypeImport(root, j);
408
+ }
409
+ // Re-export source paths (`export { X } from "typeorm/driver/..."` and
410
+ // `export * from "typeorm/driver/..."`) follow the same deep-path rewrite
411
+ // rules as import sources so barrel files that re-export renamed modules
412
+ // end up pointing at the v1 path.
413
+ root.find(j.ExportNamedDeclaration).forEach((exportPath) => {
414
+ const source = exportPath.node.source?.value;
415
+ if (typeof source !== "string")
416
+ return;
417
+ const rewritten = rewriteTypeormPath(source);
418
+ if (rewritten !== source) {
419
+ exportPath.node.source.value = rewritten;
420
+ hasChanges = true;
421
+ }
422
+ });
423
+ root.find(j.ExportAllDeclaration).forEach((exportPath) => {
424
+ const source = exportPath.node.source.value;
425
+ if (typeof source !== "string")
426
+ return;
427
+ const rewritten = rewriteTypeormPath(source);
428
+ if (rewritten !== source) {
429
+ exportPath.node.source.value = rewritten;
430
+ hasChanges = true;
431
+ }
432
+ });
433
+ // Cross-import dedup: the rewrites above can produce two ImportDeclarations
434
+ // that now target the same module with the same specifier (e.g. an
435
+ // existing `import type { BetterSqlite3DataSourceOptions }` plus a
436
+ // newly-renamed `import type { SqliteConnectionOptions }` from the old
437
+ // `sqlite/` path). Drop any later declaration whose (source, specifier
438
+ // set) tuple is already covered by an earlier one.
439
+ const seenImportKeys = new Map();
440
+ root.find(j.ImportDeclaration).forEach((importPath) => {
441
+ const source = importPath.node.source.value;
442
+ if (typeof source !== "string")
443
+ return;
444
+ const isTypeOnly = importPath.node.importKind === "type";
445
+ const specifiers = importPath.node.specifiers ?? [];
446
+ const specifierKey = specifiers
447
+ .map((spec) => {
448
+ const specType = spec.type;
449
+ if (specType === "ImportSpecifier") {
450
+ const s = spec;
451
+ const imported = s.imported.type === "Identifier"
452
+ ? (s.imported.name ?? "")
453
+ : "";
454
+ const local = s.local?.type === "Identifier"
455
+ ? (s.local.name ?? "")
456
+ : imported;
457
+ // Per-specifier importKind distinguishes
458
+ // `import { type X }` from `import { X }` — the two
459
+ // bindings have different runtime semantics and must
460
+ // not collapse into the same dedup key.
461
+ const kind = s.importKind === "type" ? "type:" : "";
462
+ return `named:${kind}${imported}->${local}`;
463
+ }
464
+ if (specType === "ImportDefaultSpecifier") {
465
+ const s = spec;
466
+ return `default:${s.local?.type === "Identifier" ? (s.local.name ?? "") : ""}`;
467
+ }
468
+ if (specType === "ImportNamespaceSpecifier") {
469
+ const s = spec;
470
+ return `namespace:${s.local?.type === "Identifier" ? (s.local.name ?? "") : ""}`;
471
+ }
472
+ return specType;
473
+ })
474
+ .sort()
475
+ .join("|");
476
+ const key = `${isTypeOnly ? "type:" : ""}${source}::${specifierKey}`;
477
+ if (seenImportKeys.has(key)) {
478
+ j(importPath).remove();
479
+ hasChanges = true;
480
+ return;
481
+ }
482
+ seenImportKeys.set(key, importPath.node);
483
+ });
177
484
  // Rewrite a single `{ X [: Y] }` property in a destructured require of
178
485
  // typeorm. Records the local binding in `localRenames` so the shared
179
486
  // NewExpression/TSTypeReference rewrite loop below picks it up.
@@ -223,6 +530,11 @@ const connectionToDataSource = (file, api) => {
223
530
  hasChanges = true;
224
531
  }
225
532
  });
533
+ // Rename re-exports from "typeorm" (e.g. barrel files that do
534
+ // `export { Connection } from "typeorm"`)
535
+ if ((0, ast_helpers_1.renameReExportSpecifiers)(root, j, "typeorm", typeRenames)) {
536
+ hasChanges = true;
537
+ }
226
538
  for (const [oldName, newName] of localRenames) {
227
539
  // TSTypeReference (e.g. const x: Connection = ...)
228
540
  root.find(j.TSTypeReference, {
@@ -262,10 +574,15 @@ const connectionToDataSource = (file, api) => {
262
574
  // Expand to the set of LOCAL bindings — covers aliased ESM imports
263
575
  // (`import { Connection as LegacyConn }`) and aliased CJS destructures
264
576
  // (`const { Connection: LegacyConn } = require("typeorm")`) so code
265
- // using the alias still gets `.connect()` → `.initialize()`.
577
+ // using the alias still gets `.connect()` → `.initialize()`. When no
578
+ // alias is in play the set simply contains `Connection` / `DataSource`
579
+ // from the plain imports.
266
580
  const connectionTypeNames = (0, ast_helpers_1.expandLocalNamesForImports)(root, j, "typeorm", new Set(["Connection", "DataSource"]));
267
- connectionTypeNames.add("Connection");
268
- connectionTypeNames.add("DataSource");
581
+ // Namespace-import locals for "typeorm" — used to gate qualified type
582
+ // references (`typeorm.Repository<T>`) inside `getTypeReferenceRootName`
583
+ // so array-element lookups don't mistake `Foo.Repository[]` from another
584
+ // library for a TypeORM receiver.
585
+ const typeormNamespaceNames = (0, ast_helpers_1.getNamespaceLocalNames)(root, j, "typeorm");
269
586
  const connectionVarNames = new Set();
270
587
  root.find(j.VariableDeclarator).forEach((path) => {
271
588
  const init = path.node.init;
@@ -293,32 +610,62 @@ const connectionToDataSource = (file, api) => {
293
610
  });
294
611
  (0, ast_helpers_1.forEachIdentifierParam)(root, j, collectDataSourceTyped);
295
612
  // Rename method calls: .connect() → .initialize(), .close() → .destroy()
296
- // Only on variables known to be Connection/DataSource instances
297
- for (const [oldMethod, newMethod] of Object.entries(methodRenames)) {
298
- root.find(j.CallExpression, {
299
- callee: {
300
- type: "MemberExpression",
301
- property: { name: oldMethod },
302
- },
303
- }).forEach((path) => {
304
- if (path.node.callee.type === "MemberExpression" &&
305
- path.node.callee.property.type === "Identifier") {
306
- const objName = unwrapIdentifierName(path.node.callee.object);
307
- if (objName && connectionVarNames.has(objName)) {
613
+ // Only on receivers known to be Connection/DataSource instances —
614
+ // covered after typed-var AND `this.<member>` collection below.
615
+ const renameDataSourceMethods = () => {
616
+ for (const [oldMethod, newMethod] of Object.entries(methodRenames)) {
617
+ root.find(j.CallExpression, {
618
+ callee: {
619
+ type: "MemberExpression",
620
+ property: { name: oldMethod },
621
+ },
622
+ }).forEach((path) => {
623
+ if (path.node.callee.type === "MemberExpression" &&
624
+ path.node.callee.property.type === "Identifier" &&
625
+ receiverIsIn(path.node.callee.object, connectionVarNames, thisConnectionMembers)) {
308
626
  path.node.callee.property.name = newMethod;
309
627
  hasChanges = true;
310
628
  }
311
- }
312
- });
313
- }
629
+ });
630
+ }
631
+ };
314
632
  // Gate metadata type matches on the actual local bindings imported from
315
633
  // typeorm — users sometimes have their own classes named `EntityMetadata`
316
634
  // or `ColumnMetadata` and their `.connection` property access must not
317
635
  // be rewritten.
318
636
  const connectionPropLocalNames = (0, ast_helpers_1.expandLocalNamesForImports)(root, j, "typeorm", typesWithConnectionProp);
319
637
  const indirectDataSourceLocalNames = (0, ast_helpers_1.expandLocalNamesForImports)(root, j, "typeorm", typesWithIndirectDataSource);
638
+ // Specifically the local names bound to typeorm's `EntityMetadata` —
639
+ // used to gate the constructor-option rewrite below. Alias-aware so
640
+ // `import { EntityMetadata as EM }` is still recognized.
641
+ const entityMetadataLocalNames = (0, ast_helpers_1.expandLocalNamesForImports)(root, j, "typeorm", new Set(["EntityMetadata"]));
320
642
  const connectionPropVarNames = new Set();
321
643
  const indirectDataSourceVarNames = new Set();
644
+ // Parallel sets for `this.X` member accesses — class properties and
645
+ // getters whose annotation is a tracked TypeORM type. Keyed by the
646
+ // member name (not prefixed) since the access shape (`this.X`) already
647
+ // signals the lookup side.
648
+ const thisConnectionMembers = new Set();
649
+ const thisConnectionPropMembers = new Set();
650
+ const thisIndirectMembers = new Set();
651
+ const classifyType = (typeName) => {
652
+ if (connectionTypeNames.has(typeName))
653
+ return connectionVarNames;
654
+ if (connectionPropLocalNames.has(typeName))
655
+ return connectionPropVarNames;
656
+ if (indirectDataSourceLocalNames.has(typeName))
657
+ return indirectDataSourceVarNames;
658
+ return null;
659
+ };
660
+ const classifyThisMemberType = (typeName) => {
661
+ if (connectionTypeNames.has(typeName))
662
+ return thisConnectionMembers;
663
+ if (connectionPropLocalNames.has(typeName))
664
+ return thisConnectionPropMembers;
665
+ if (indirectDataSourceLocalNames.has(typeName))
666
+ return thisIndirectMembers;
667
+ return null;
668
+ };
322
669
  const collectTypedIdentifier = (id) => {
323
670
  if (!id.name || !id.typeAnnotation)
324
671
  return;
@@ -330,18 +677,106 @@ const connectionToDataSource = (file, api) => {
330
677
  const ref = ann.typeAnnotation;
331
678
  if (ref.typeName.type !== "Identifier")
332
679
  return;
333
- if (connectionPropLocalNames.has(ref.typeName.name)) {
334
- connectionPropVarNames.add(id.name);
335
- }
336
- else if (indirectDataSourceLocalNames.has(ref.typeName.name)) {
337
- indirectDataSourceVarNames.add(id.name);
338
- }
680
+ const bucket = classifyType(ref.typeName.name);
681
+ if (bucket)
682
+ bucket.add(id.name);
339
683
  };
340
684
  root.find(j.VariableDeclarator).forEach((path) => {
341
685
  if ((0, ast_helpers_1.isIdentifier)(path.node.id))
342
686
  collectTypedIdentifier(path.node.id);
343
687
  });
344
688
  (0, ast_helpers_1.forEachIdentifierParam)(root, j, collectTypedIdentifier);
689
+ // Class properties with a TypeORM type annotation — e.g.
690
+ // private readonly tenantConnection: DataSource
691
+ // — register `this.tenantConnection` for rename-side lookup.
692
+ root.find(j.ClassProperty).forEach((path) => {
693
+ const keyNode = path.node.key;
694
+ if (keyNode.type !== "Identifier")
695
+ return;
696
+ const ann = path.node.typeAnnotation;
697
+ if (ann?.type !== "TSTypeAnnotation")
698
+ return;
699
+ const inner = ann.typeAnnotation;
700
+ if (inner.type !== "TSTypeReference")
701
+ return;
702
+ const typeName = inner.typeName;
703
+ if (typeName.type !== "Identifier")
704
+ return;
705
+ const bucket = classifyThisMemberType(typeName.name);
706
+ if (bucket)
707
+ bucket.add(keyNode.name);
708
+ });
709
+ // Constructor parameter properties — `constructor(private readonly x: DataSource)`
710
+ // is a TSParameterProperty whose inner `parameter` is the typed Identifier.
711
+ // These become class fields accessed via `this.x`.
712
+ root.find(j.TSParameterProperty).forEach((path) => {
713
+ const inner = path.node.parameter;
714
+ if (!inner)
715
+ return;
716
+ const id = resolveTSParameterPropertyIdentifier(inner);
717
+ if (!id)
718
+ return;
719
+ const ann = id.typeAnnotation;
720
+ if (ann?.type !== "TSTypeAnnotation")
721
+ return;
722
+ if (ann.typeAnnotation.type !== "TSTypeReference")
723
+ return;
724
+ const ref = ann.typeAnnotation;
725
+ if (ref.typeName.type !== "Identifier")
726
+ return;
727
+ const bucket = classifyThisMemberType(ref.typeName.name);
728
+ if (bucket)
729
+ bucket.add(id.name);
730
+ });
731
+ // Class getters with a return type annotation — e.g.
732
+ // private get entityManager(): EntityManager { ... }
733
+ // — same `this.X` lookup path.
734
+ root.find(j.ClassMethod).forEach((path) => {
735
+ if (path.node.kind !== "get")
736
+ return;
737
+ const keyNode = path.node.key;
738
+ if (keyNode.type !== "Identifier")
739
+ return;
740
+ const rt = path.node.returnType;
741
+ if (rt?.type !== "TSTypeAnnotation")
742
+ return;
743
+ const inner = rt.typeAnnotation;
744
+ if (inner.type !== "TSTypeReference")
745
+ return;
746
+ const typeName = inner.typeName;
747
+ if (typeName.type !== "Identifier")
748
+ return;
749
+ const bucket = classifyThisMemberType(typeName.name);
750
+ if (bucket)
751
+ bucket.add(keyNode.name);
752
+ });
753
+ // Resolves an object-access receiver to the set of known-type names it
754
+ // resolves against. Returns null for receivers we can't classify.
755
+ // x → { key: "x", isThisMember: false }
756
+ // this.x → { key: "x", isThisMember: true }
757
+ // (x as T) → same as x
758
+ // (this.x as T) → same as this.x
759
+ const resolveReceiver = (node) => {
760
+ const direct = unwrapIdentifierName(node);
761
+ if (direct)
762
+ return { key: direct, isThisMember: false };
763
+ const unwrapped = (0, ast_helpers_1.unwrapTsExpression)(node);
764
+ if ((unwrapped.type === "MemberExpression" ||
765
+ unwrapped.type === "OptionalMemberExpression") &&
766
+ unwrapped.object.type ===
767
+ "ThisExpression" &&
768
+ unwrapped.property.type === "Identifier") {
769
+ const prop = unwrapped.property;
770
+ return { key: prop.name, isThisMember: true };
771
+ }
772
+ return null;
773
+ };
774
+ const receiverIsIn = (node, bare, thisMember) => {
775
+ const r = resolveReceiver(node);
776
+ if (!r)
777
+ return false;
778
+ return r.isThisMember ? thisMember.has(r.key) : bare.has(r.key);
779
+ };
345
780
  // Track variables assigned from DataSource accessors, so code like
346
781
  // const manager = dataSource.manager
347
782
  // manager.connection.getMetadata(...)
@@ -391,16 +826,16 @@ const connectionToDataSource = (file, api) => {
391
826
  connectionPropVarNames.add(path.node.id.name);
392
827
  }
393
828
  });
829
+ renameDataSourceMethods();
394
830
  // Rename .isConnected → .isInitialized on Connection/DataSource instances
395
831
  root.find(j.MemberExpression, {
396
832
  property: { name: "isConnected" },
397
833
  }).forEach((path) => {
398
- if (path.node.property.type === "Identifier") {
399
- const objName = unwrapIdentifierName(path.node.object);
400
- if (objName && connectionVarNames.has(objName)) {
401
- path.node.property.name = "isInitialized";
402
- hasChanges = true;
403
- }
834
+ if (path.node.property.type !== "Identifier")
835
+ return;
836
+ if (receiverIsIn(path.node.object, connectionVarNames, thisConnectionMembers)) {
837
+ path.node.property.name = "isInitialized";
838
+ hasChanges = true;
404
839
  }
405
840
  });
406
841
  // Rename .connection → .dataSource on known TypeORM instances.
@@ -411,21 +846,122 @@ const connectionToDataSource = (file, api) => {
411
846
  }).forEach((path) => {
412
847
  if (path.node.property.type !== "Identifier")
413
848
  return;
414
- const objName = unwrapIdentifierName(path.node.object);
415
- if (!objName)
416
- return;
417
- if (connectionPropVarNames.has(objName)) {
849
+ if (receiverIsIn(path.node.object, connectionPropVarNames, thisConnectionPropMembers)) {
418
850
  path.node.property.name = "dataSource";
419
851
  hasChanges = true;
420
852
  return;
421
853
  }
422
- if (indirectDataSourceVarNames.has(objName)) {
854
+ if (receiverIsIn(path.node.object, indirectDataSourceVarNames, thisIndirectMembers)) {
423
855
  // `col.connection` → `col.entityMetadata.dataSource`
424
856
  path.node.object = j.memberExpression(path.node.object, j.identifier("entityMetadata"));
425
857
  path.node.property.name = "dataSource";
426
858
  hasChanges = true;
427
859
  }
428
860
  });
861
+ // Destructuring pattern: `const { connection } = event` / `for (const
862
+ // { connection } of events)` where `event`/`events[i]` is a tracked
863
+ // connectionProp receiver (subscriber event, EntityManager, QueryRunner,
864
+ // Repository, etc.). Rename the `connection` key to `dataSource` and
865
+ // preserve the local binding name.
866
+ const renameConnectionInObjectPattern = (id) => {
867
+ if (id.type !== "ObjectPattern")
868
+ return;
869
+ for (const prop of id.properties) {
870
+ if (prop.type !== "Property" && prop.type !== "ObjectProperty")
871
+ continue;
872
+ const typed = prop;
873
+ if (typed.key.type !== "Identifier")
874
+ continue;
875
+ if (typed.key.name !== "connection")
876
+ continue;
877
+ typed.key.name = "dataSource";
878
+ // Shorthand `{ connection }` expands to `{ dataSource: connection }`
879
+ // to keep the local variable name unchanged — the consumer code
880
+ // still references `connection`.
881
+ if (typed.shorthand)
882
+ typed.shorthand = false;
883
+ hasChanges = true;
884
+ }
885
+ };
886
+ root.find(j.VariableDeclarator).forEach((declPath) => {
887
+ const init = declPath.node.init;
888
+ if (!init)
889
+ return;
890
+ if (!receiverIsIn(init, connectionPropVarNames, thisConnectionPropMembers)) {
891
+ return;
892
+ }
893
+ renameConnectionInObjectPattern(declPath.node.id);
894
+ });
895
+ // `for (const { connection } of events)` — `ForOfStatement.left` is a
896
+ // `VariableDeclaration` whose declarators have no `init`, so the
897
+ // plain VariableDeclarator loop above skips them. Classify the loop
898
+ // variable's inferred type: either the iterable variable itself is a
899
+ // tracked receiver, or its declared annotation is `Tracked[]` /
900
+ // `Array<Tracked>`.
901
+ const elementTypeOf = (rhsNode) => {
902
+ if (rhsNode.type !== "Identifier")
903
+ return null;
904
+ const name = rhsNode.name;
905
+ let resolved = null;
906
+ root.find(j.VariableDeclarator).forEach((p) => {
907
+ if (resolved)
908
+ return;
909
+ if (p.node.id.type !== "Identifier")
910
+ return;
911
+ if (p.node.id.name !== name)
912
+ return;
913
+ const ann = p.node.id.typeAnnotation;
914
+ if (ann?.type !== "TSTypeAnnotation")
915
+ return;
916
+ const inner = ann.typeAnnotation;
917
+ if (inner.type === "TSArrayType") {
918
+ resolved =
919
+ (0, ast_helpers_1.getTypeReferenceRootName)(inner.elementType, typeormNamespaceNames) ?? null;
920
+ return;
921
+ }
922
+ if (inner.type === "TSTypeReference" &&
923
+ inner.typeName?.name ===
924
+ "Array") {
925
+ const params = inner.typeParameters?.params;
926
+ if (params && params.length > 0) {
927
+ resolved =
928
+ (0, ast_helpers_1.getTypeReferenceRootName)(params[0], typeormNamespaceNames) ?? null;
929
+ }
930
+ }
931
+ });
932
+ return resolved;
933
+ };
934
+ const visitForOf = (leftNode, rightNode) => {
935
+ if (!leftNode || !rightNode)
936
+ return;
937
+ if (leftNode.type !== "VariableDeclaration")
938
+ return;
939
+ // Either: the iterable itself is a tracked receiver (rare but valid
940
+ // — e.g. a generator that yields the receiver), OR its type is an
941
+ // array/`Array<T>` of a tracked type.
942
+ const directlyTracked = receiverIsIn(rightNode, connectionPropVarNames, thisConnectionPropMembers);
943
+ const elementType = elementTypeOf(rightNode);
944
+ const elementTracked = elementType !== null && connectionPropLocalNames.has(elementType);
945
+ if (!directlyTracked && !elementTracked)
946
+ return;
947
+ for (const d of leftNode
948
+ .declarations) {
949
+ if (d.type !== "VariableDeclarator")
950
+ continue;
951
+ renameConnectionInObjectPattern(d.id);
952
+ }
953
+ };
954
+ root.find(j.ForOfStatement).forEach((p) => visitForOf(p.node.left, p.node.right));
955
+ root.find(j.ForInStatement).forEach((p) => visitForOf(p.node.left, p.node.right));
956
+ // Rewrite the `connection` option passed to metadata-type constructors:
957
+ // - `new EntityMetadata({ connection: X, ... })` → rename key to
958
+ // `dataSource` (the option was just renamed in v1).
959
+ // - `new ColumnMetadata({ connection, ... })` / `IndexMetadata` →
960
+ // drop the `connection` key entirely. These constructors no longer
961
+ // accept `connection` in v1; the DataSource is reached through
962
+ // `entityMetadata.dataSource` and is set by the entity-metadata
963
+ // builder rather than the caller.
964
+ hasChanges = rewriteMetadataConstructors(root, j, entityMetadataLocalNames, indirectDataSourceLocalNames, hasChanges);
429
965
  return hasChanges ? root.toSource() : undefined;
430
966
  };
431
967
  exports.connectionToDataSource = connectionToDataSource;