uql-codemod 0.39.0 → 0.39.1
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 +1 -1
- package/dist/transform.js +57 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ Reported with what to do, never guessed:
|
|
|
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
47
|
- A to-one onto a composite key: declare a column per key and pair each with it in `references`.
|
|
48
|
-
- A to-one
|
|
48
|
+
- A to-one whose `<relation>Id` is not a declared column: an undecorated property, a `defineEntity` whose `fields` leave it out, or a `defineRelation` whose entity has no such member.
|
|
49
49
|
- A partial-index `where` string that interpolates, since `raw` would bind what the template spliced in.
|
|
50
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.
|
|
51
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
|
@@ -157,8 +157,12 @@ function noteHandNamedColumns(sql, owner, ctx) {
|
|
|
157
157
|
}
|
|
158
158
|
/** The names of the members of the entity `node` declares or names. */
|
|
159
159
|
function memberNames(node, checker) {
|
|
160
|
+
return propertyNames(instanceTypeOf(node, checker));
|
|
161
|
+
}
|
|
162
|
+
/** The instance type of the entity `node` declares or names. */
|
|
163
|
+
function instanceTypeOf(node, checker) {
|
|
160
164
|
const type = checker.getTypeAtLocation(node);
|
|
161
|
-
return
|
|
165
|
+
return type.getConstructSignatures()[0]?.getReturnType() ?? type;
|
|
162
166
|
}
|
|
163
167
|
function propertyNames(type) {
|
|
164
168
|
return new Set(type.getProperties().map(({ name }) => name));
|
|
@@ -228,11 +232,11 @@ function addRelationEntity(decorator, node, ctx) {
|
|
|
228
232
|
});
|
|
229
233
|
}
|
|
230
234
|
/**
|
|
231
|
-
* Names the foreign key a to-one
|
|
232
|
-
*
|
|
233
|
-
*
|
|
235
|
+
* Names the foreign key a to-one used to find by name, `references: (post) => post.authorId` beside `author`.
|
|
236
|
+
* `site` says where the entity declares its columns: the decorated property, ahead of which a missing column
|
|
237
|
+
* is declared, or `defineEntity`'s `fields`. A column it cannot see declared is reported instead.
|
|
234
238
|
*/
|
|
235
|
-
function addForeignKeyReference(relation, key, owner, ctx,
|
|
239
|
+
function addForeignKeyReference(relation, key, owner, ctx, site) {
|
|
236
240
|
const options = { kind: 'literal', node: relation };
|
|
237
241
|
const cardinality = propertyValue(relation, 'cardinality');
|
|
238
242
|
const toOne = !cardinality || (ts.isStringLiteralLike(cardinality) && TO_ONE_CARDINALITIES.has(cardinality.text));
|
|
@@ -242,49 +246,58 @@ function addForeignKeyReference(relation, key, owner, ctx, property) {
|
|
|
242
246
|
return;
|
|
243
247
|
}
|
|
244
248
|
const column = `${key}Id`;
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
ctx.edits.push(declared);
|
|
249
|
+
const declaration = columnDeclaration(column, relation, owner, ctx.checker, site);
|
|
250
|
+
if (typeof declaration === 'string') {
|
|
251
|
+
ctx.unresolved.push(`${ctx.describe(site && ts.isPropertyDeclaration(site) ? site : relation)}: ${declaration}`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (declaration) {
|
|
255
|
+
ctx.edits.push(declaration);
|
|
256
|
+
ctx.imports.set('Field', 'the foreign key column');
|
|
255
257
|
}
|
|
256
258
|
ctx.edits.push(appended(last, `, references: (${owner.param}) => ${memberAccess(owner.param, column)}`));
|
|
257
259
|
}
|
|
260
|
+
const undeclaredColumn = (column) => `declare the foreign key column '${column}' and name it in 'references'`;
|
|
261
|
+
/** Nothing where `column` is declared, the declaration to write where a decorated class lacks it, or why neither. */
|
|
262
|
+
function columnDeclaration(column, relation, owner, checker, site) {
|
|
263
|
+
const member = instanceTypeOf(owner.entity, checker).getProperty(column);
|
|
264
|
+
if (site && ts.isPropertyDeclaration(site)) {
|
|
265
|
+
if (!member) {
|
|
266
|
+
return foreignKeyColumn(site, column, relation, checker);
|
|
267
|
+
}
|
|
268
|
+
return member.declarations?.some(isFieldProperty) ? undefined : undeclaredColumn(column);
|
|
269
|
+
}
|
|
270
|
+
const declared = site ? objectProperties(site).some(({ name }) => propertyKey(name) === column) : member;
|
|
271
|
+
return declared ? undefined : undeclaredColumn(column);
|
|
272
|
+
}
|
|
258
273
|
/** `@Field({ references: () => Target }) <column>?: <key type>;` ahead of the relation, or why it cannot be written. */
|
|
259
|
-
function foreignKeyColumn(property, column, relation,
|
|
260
|
-
const { checker } = ctx;
|
|
274
|
+
function foreignKeyColumn(property, column, relation, checker) {
|
|
261
275
|
const targetType = checker.getNonNullableType(checker.getTypeAtLocation(property));
|
|
262
276
|
const target = entityGetterTarget(relation) ?? relationTargetFor(targetType, checker);
|
|
263
277
|
const keys = targetType.getProperties().filter((member) => member.declarations?.some(isIdProperty));
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return undefined;
|
|
278
|
+
if (keys.length > 1) {
|
|
279
|
+
return `'${target}' has a composite key: declare a column per key and pair each in 'references'`;
|
|
280
|
+
}
|
|
281
|
+
const [key] = keys;
|
|
282
|
+
if (!target || !key) {
|
|
283
|
+
return undeclaredColumn(column);
|
|
271
284
|
}
|
|
272
|
-
const
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
return inserted(property, `@Field({ references: () => ${target} }) ${column}?: ${keyTypeSource(soleKey, property, ctx)};\n${indent}`);
|
|
285
|
+
const indent = ' '.repeat(property.getSourceFile().getLineAndCharacterOfPosition(property.getStart()).character);
|
|
286
|
+
const type = keyTypeSource(key, property, checker);
|
|
287
|
+
return inserted(property, `@Field({ references: () => ${target} }) ${column}?: ${type};\n${indent}`);
|
|
276
288
|
}
|
|
277
289
|
/** 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,
|
|
290
|
+
function keyTypeSource(key, at, checker) {
|
|
279
291
|
const written = key.declarations?.find(isIdProperty)?.type;
|
|
280
292
|
if (written && written.getSourceFile() === at.getSourceFile()) {
|
|
281
293
|
return written.getText();
|
|
282
294
|
}
|
|
283
|
-
return
|
|
284
|
-
}
|
|
285
|
-
function isIdProperty(node) {
|
|
286
|
-
return ts.isPropertyDeclaration(node) && decoratorsOf(node).some((decorator) => decoratorName(decorator) === 'Id');
|
|
295
|
+
return checker.typeToString(checker.getNonNullableType(checker.getTypeOfSymbolAtLocation(key, at)), at);
|
|
287
296
|
}
|
|
297
|
+
/** Whether a declaration is a property carrying one of `names` as a decorator. */
|
|
298
|
+
const decoratedWith = (names) => (node) => ts.isPropertyDeclaration(node) && decoratorsOf(node).some((decorator) => names.has(decoratorName(decorator) ?? ''));
|
|
299
|
+
const isIdProperty = decoratedWith(new Set(['Id']));
|
|
300
|
+
const isFieldProperty = decoratedWith(FIELD_DECORATORS);
|
|
288
301
|
/**
|
|
289
302
|
* Writes the edits `rewrite` reads off `value`, which still names members as strings, or reports it
|
|
290
303
|
* where it could not be read: what it holds is only known at runtime. A callback or an object literal
|
|
@@ -352,6 +365,7 @@ function rewriteDefineCall(call, ctx) {
|
|
|
352
365
|
}
|
|
353
366
|
}
|
|
354
367
|
function rewriteEntityOptions(options, owner, ctx) {
|
|
368
|
+
const fields = propertyValue(options, 'fields');
|
|
355
369
|
for (const index of arrayElements(propertyValue(options, 'indexes'))) {
|
|
356
370
|
if (ts.isObjectLiteralExpression(index)) {
|
|
357
371
|
rewriteIndexOptions(index, owner, ctx);
|
|
@@ -360,7 +374,7 @@ function rewriteEntityOptions(options, owner, ctx) {
|
|
|
360
374
|
for (const check of arrayElements(propertyValue(options, 'checks'))) {
|
|
361
375
|
renameSqlOption(optionsOf(check), 'expression', 'where', check, owner, ctx);
|
|
362
376
|
}
|
|
363
|
-
for (const field of objectProperties(
|
|
377
|
+
for (const field of objectProperties(fields)) {
|
|
364
378
|
renameSqlOption(optionsOf(field.initializer), 'virtual', 'computed', field, owner, ctx);
|
|
365
379
|
}
|
|
366
380
|
for (const filter of objectProperties(propertyValue(options, 'filters'))) {
|
|
@@ -369,10 +383,11 @@ function rewriteEntityOptions(options, owner, ctx) {
|
|
|
369
383
|
for (const hook of objectProperties(propertyValue(options, 'hooks'))) {
|
|
370
384
|
rewriteKeyList(hook.initializer, owner.param, hook, 'a hook list', ctx);
|
|
371
385
|
}
|
|
386
|
+
const declared = fields && ts.isObjectLiteralExpression(fields) ? fields : undefined;
|
|
372
387
|
for (const relation of objectProperties(propertyValue(options, 'relations'))) {
|
|
373
388
|
if (ts.isObjectLiteralExpression(relation.initializer)) {
|
|
374
389
|
rewriteRelationOptions(relation.initializer, owner.param, ctx);
|
|
375
|
-
addForeignKeyReference(relation.initializer, propertyKey(relation.name), owner, ctx);
|
|
390
|
+
addForeignKeyReference(relation.initializer, propertyKey(relation.name), owner, ctx, declared);
|
|
376
391
|
}
|
|
377
392
|
}
|
|
378
393
|
}
|
|
@@ -605,7 +620,7 @@ function isIdKeyBrand(member) {
|
|
|
605
620
|
* One written makes `idKey` a value the file has to import, which is recorded for {@link addImports}.
|
|
606
621
|
*/
|
|
607
622
|
function brandIdKey(node, ctx) {
|
|
608
|
-
const ids = node.members.filter(
|
|
623
|
+
const ids = node.members.filter(isIdProperty);
|
|
609
624
|
if (!ids.length || node.members.some(isIdKeyBrand)) {
|
|
610
625
|
return;
|
|
611
626
|
}
|
|
@@ -666,9 +681,9 @@ function rewriteProperty(node, ctx) {
|
|
|
666
681
|
if (RELATION_DECORATORS.has(name) && options.kind === 'literal') {
|
|
667
682
|
const target = relationTargetFor(ctx.checker.getTypeAtLocation(node), ctx.checker);
|
|
668
683
|
rewriteRelationOptions(options.node, owner.param, ctx, paramFor(target));
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
684
|
+
if (TO_ONE_DECORATORS.has(name)) {
|
|
685
|
+
addForeignKeyReference(options.node, propertyKey(node.name), owner, ctx, node);
|
|
686
|
+
}
|
|
672
687
|
}
|
|
673
688
|
}
|
|
674
689
|
if (decorators.length) {
|
|
@@ -969,9 +984,9 @@ export function transformFile(source, checker) {
|
|
|
969
984
|
}
|
|
970
985
|
if (ts.isCallExpression(node)) {
|
|
971
986
|
rewriteDefineCall(node, ctx);
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
987
|
+
if (importsUql) {
|
|
988
|
+
rewriteBuilderCall(node, ctx);
|
|
989
|
+
}
|
|
975
990
|
}
|
|
976
991
|
if (ts.isPropertyAssignment(node)) {
|
|
977
992
|
rewriteStatementKeys(node, ctx);
|