uql-codemod 0.38.0 → 0.39.0
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/README.md +3 -2
- package/dist/transform.js +48 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Only `--flag=value` is read, and anything unrecognised is an error. It exits `0`
|
|
|
28
28
|
- `entity: () => X` on a bare relation decorator, and `Relation<T>` to `T`, its import going with the last use.
|
|
29
29
|
- `virtual` to `computed` (on `@Field` and in `defineEntity`'s `fields`), a check's `expression` and a filter's `condition` to `where` (`@Entity`/`defineEntity`, `@Filter`, `defineFilter`), `raw('sql')` to the tagged template, and a `raw` alias argument onto `.as()`.
|
|
30
30
|
- Members named by string to callbacks: `mappedBy: 'author'` to `(post) => post.author`, and `@Index`, `include`, `references` and `@Entity`/`defineEntity`'s `indexes` and `hooks` likewise: `@Index((post) => [post.title])`.
|
|
31
|
-
- `references: (post) => post.authorId` on a to-one (`@ManyToOne`, `@OneToOne`, or `defineEntity`/`defineRelation`)
|
|
31
|
+
- `references: (post) => post.authorId` on a to-one (`@ManyToOne`, `@OneToOne`, or `defineEntity`/`defineRelation`) that names no foreign key, declaring `@Field({ references: () => User }) authorId` first on a decorated class that has no such column, typed as the key it points at.
|
|
32
32
|
- A partial-index `where` string to `raw`, importing it, on entities and in the migration builder.
|
|
33
33
|
- `col('cost')` to the ref it names, ``(product) => raw`${product.cost}` `` in a `computed` and `refs(Item).cost` in a statement, and `$lock: { wait: 'skip' }` to `{ $wait: 'skip' }`, or to `true` for `{}` and a `'block'` wait.
|
|
34
34
|
- An aggregate's `$agg` to `$select`, and each field it or `$text`'s `$fields` names to a key: `{ $sum: { amount: true } }`.
|
|
@@ -44,7 +44,8 @@ Reported with what to do, never guessed:
|
|
|
44
44
|
- `@Log()`, `@Serialized()`, `@Transactional()` and `@InjectQuerier()`: a `@Transactional()` method becomes a `pool.transaction()` around its body, and only you know which pool.
|
|
45
45
|
- An export removed with no one-to-one replacement (`setQuerierPool`, `getQuerier`, `augmentWhere`, `AbstractPgQuerier`, ...).
|
|
46
46
|
- A value it cannot read: options passed as a variable or a spread, a list that is not a literal, a key under a computed name.
|
|
47
|
-
- A to-one onto a composite key
|
|
47
|
+
- A to-one onto a composite key: declare a column per key and pair each with it in `references`.
|
|
48
|
+
- A to-one in `defineEntity`/`defineRelation` whose entity has no foreign key column: declare it with `defineField`.
|
|
48
49
|
- A partial-index `where` string that interpolates, since `raw` would bind what the template spliced in.
|
|
49
50
|
- A `col()` whose entity it cannot tell: a raw built apart from its query, one under a relation filter, or a column no field maps.
|
|
50
51
|
- SQL in a definition that names a column by hand, as a note: a rename reaches a callback's refs, ``(user) => raw`lower(${user.email})` ``, and never the text.
|
package/dist/transform.js
CHANGED
|
@@ -228,21 +228,63 @@ function addRelationEntity(decorator, node, ctx) {
|
|
|
228
228
|
});
|
|
229
229
|
}
|
|
230
230
|
/**
|
|
231
|
-
* Names the foreign key a to-one found by name alone, `references: (post) => post.authorId` beside `author
|
|
232
|
-
*
|
|
231
|
+
* Names the foreign key a to-one found by name alone, `references: (post) => post.authorId` beside `author`.
|
|
232
|
+
* A key the entity neither declares nor inherits is declared first, on a decorated property, typed as the
|
|
233
|
+
* target's key; elsewhere it is reported.
|
|
233
234
|
*/
|
|
234
|
-
function addForeignKeyReference(relation, key, owner, ctx) {
|
|
235
|
+
function addForeignKeyReference(relation, key, owner, ctx, property) {
|
|
235
236
|
const options = { kind: 'literal', node: relation };
|
|
236
237
|
const cardinality = propertyValue(relation, 'cardinality');
|
|
237
238
|
const toOne = !cardinality || (ts.isStringLiteralLike(cardinality) && TO_ONE_CARDINALITIES.has(cardinality.text));
|
|
238
239
|
const joined = ['mappedBy', 'through', 'references'].some((option) => findProperty(options, option));
|
|
239
240
|
const last = relation.properties.at(-1);
|
|
240
|
-
|
|
241
|
-
if (!key || !last || !toOne || joined || !memberNames(owner.entity, ctx.checker).has(column)) {
|
|
241
|
+
if (!key || !last || !toOne || joined) {
|
|
242
242
|
return;
|
|
243
243
|
}
|
|
244
|
+
const column = `${key}Id`;
|
|
245
|
+
if (!memberNames(owner.entity, ctx.checker).has(column)) {
|
|
246
|
+
if (!property) {
|
|
247
|
+
ctx.unresolved.push(`${ctx.describe(relation)}: declare the foreign key column '${column}' and name it in 'references'`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const declared = foreignKeyColumn(property, column, relation, ctx);
|
|
251
|
+
if (!declared) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
ctx.edits.push(declared);
|
|
255
|
+
}
|
|
244
256
|
ctx.edits.push(appended(last, `, references: (${owner.param}) => ${memberAccess(owner.param, column)}`));
|
|
245
257
|
}
|
|
258
|
+
/** `@Field({ references: () => Target }) <column>?: <key type>;` ahead of the relation, or why it cannot be written. */
|
|
259
|
+
function foreignKeyColumn(property, column, relation, ctx) {
|
|
260
|
+
const { checker } = ctx;
|
|
261
|
+
const targetType = checker.getNonNullableType(checker.getTypeAtLocation(property));
|
|
262
|
+
const target = entityGetterTarget(relation) ?? relationTargetFor(targetType, checker);
|
|
263
|
+
const keys = targetType.getProperties().filter((member) => member.declarations?.some(isIdProperty));
|
|
264
|
+
const [soleKey] = keys;
|
|
265
|
+
if (!target || !soleKey || keys.length > 1) {
|
|
266
|
+
const advice = keys.length > 1
|
|
267
|
+
? `'${target}' has a composite key: declare a column per key and pair each in 'references'`
|
|
268
|
+
: `declare the foreign key column '${column}' and name it in 'references'`;
|
|
269
|
+
ctx.unresolved.push(`${ctx.describe(property)}: ${advice}`);
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
const source = property.getSourceFile();
|
|
273
|
+
const indent = ' '.repeat(source.getLineAndCharacterOfPosition(property.getStart()).character);
|
|
274
|
+
ctx.imports.set('Field', 'the foreign key column');
|
|
275
|
+
return inserted(property, `@Field({ references: () => ${target} }) ${column}?: ${keyTypeSource(soleKey, property, ctx)};\n${indent}`);
|
|
276
|
+
}
|
|
277
|
+
/** The key's type as written where that is in scope, the same file, or as the checker spells it out. */
|
|
278
|
+
function keyTypeSource(key, at, ctx) {
|
|
279
|
+
const written = key.declarations?.find(isIdProperty)?.type;
|
|
280
|
+
if (written && written.getSourceFile() === at.getSourceFile()) {
|
|
281
|
+
return written.getText();
|
|
282
|
+
}
|
|
283
|
+
return ctx.checker.typeToString(ctx.checker.getNonNullableType(ctx.checker.getTypeOfSymbolAtLocation(key, at)), at);
|
|
284
|
+
}
|
|
285
|
+
function isIdProperty(node) {
|
|
286
|
+
return ts.isPropertyDeclaration(node) && decoratorsOf(node).some((decorator) => decoratorName(decorator) === 'Id');
|
|
287
|
+
}
|
|
246
288
|
/**
|
|
247
289
|
* Writes the edits `rewrite` reads off `value`, which still names members as strings, or reports it
|
|
248
290
|
* where it could not be read: what it holds is only known at runtime. A callback or an object literal
|
|
@@ -626,7 +668,7 @@ function rewriteProperty(node, ctx) {
|
|
|
626
668
|
rewriteRelationOptions(options.node, owner.param, ctx, paramFor(target));
|
|
627
669
|
}
|
|
628
670
|
if (TO_ONE_DECORATORS.has(name) && options.kind === 'literal') {
|
|
629
|
-
addForeignKeyReference(options.node, propertyKey(node.name), owner, ctx);
|
|
671
|
+
addForeignKeyReference(options.node, propertyKey(node.name), owner, ctx, node);
|
|
630
672
|
}
|
|
631
673
|
}
|
|
632
674
|
if (decorators.length) {
|