uql-codemod 0.28.0 → 0.30.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 CHANGED
@@ -27,6 +27,7 @@ Only the `--flag=value` form is read, and anything unrecognised is an error: a m
27
27
  - `@Field()` and `@Field({ ... })` with no `type`, and `@Id()`: inserts the `type` the property's declared TypeScript type implies. Skipped when `references` is present, because schema generation resolves that column from the referenced primary key.
28
28
  - Bare relation decorators: inserts `entity: () => X`, unwrapping arrays and the old `Relation<T>` alias.
29
29
  - `Relation<T>` becomes `T`, and its import goes with the last usage - `uql-orm` no longer exports it. A usage somewhere the codemod does not reach, such as a type alias, keeps the import and is reported.
30
+ - `@Field({ virtual })` becomes `@Field({ computed })`, the name it was renamed to - written as a plain key, a quoted one, or a shorthand, which becomes `computed: virtual` so it keeps referring to the same local. A decorator given both names is reported instead, because which one wins is your call.
30
31
  - `import 'reflect-metadata'` goes. Removing the package from your `package.json` is left to you.
31
32
  - `tsconfig.json`: removes `experimentalDecorators` and `emitDecoratorMetadata`, keeping the rest of the file - comments and formatting included - exactly as written.
32
33
 
package/dist/transform.js CHANGED
@@ -45,9 +45,45 @@ function decoratorOptions(node) {
45
45
  ? { kind: 'opaque', reason: 'its options object spreads another' }
46
46
  : { kind: 'literal', node: first };
47
47
  }
48
- function hasProperty(options, name) {
49
- return (options.kind === 'literal' &&
50
- options.node.properties.some((prop) => prop.name && ts.isIdentifier(prop.name) && prop.name.text === name));
48
+ /**
49
+ * The property `name` in the decorator's options, whether its key is plain, quoted or a shorthand.
50
+ *
51
+ * All three forms, because both callers care about the option being *there*: one to leave an option
52
+ * alone that is already stated, the other to rename it. Matching only the plain key inserted a second
53
+ * `type` beside a quoted one, and left a shorthand `virtual` behind while reporting nothing.
54
+ */
55
+ function findProperty(options, name) {
56
+ if (options.kind !== 'literal') {
57
+ return undefined;
58
+ }
59
+ return options.node.properties.find((prop) => (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) && propertyKey(prop.name) === name);
60
+ }
61
+ /** A key's name where it is spelled out, which is every form but a computed one (`{ [k]: v }`). */
62
+ function propertyKey(name) {
63
+ return ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : undefined;
64
+ }
65
+ /**
66
+ * Renames the `virtual` option to `computed`, the one it was renamed to.
67
+ *
68
+ * Only the key is replaced, so the expression, its formatting and any comment inside the options
69
+ * object survive untouched. A shorthand has no value to keep, so it becomes `computed: virtual` -
70
+ * renaming the key alone would rebind it to a local that does not exist.
71
+ */
72
+ function renameVirtualOption(decorator, ctx, node) {
73
+ const options = decoratorOptions(decorator);
74
+ const virtual = findProperty(options, 'virtual');
75
+ if (!virtual) {
76
+ if (options.kind === 'opaque' && /\bvirtual\b/.test(decorator.getText())) {
77
+ ctx.unresolved.push(`${ctx.describe(node)}: ${options.reason}`);
78
+ }
79
+ return;
80
+ }
81
+ if (findProperty(options, 'computed')) {
82
+ ctx.unresolved.push(`${ctx.describe(node)}: gives both 'virtual' and 'computed'; keep 'computed'.`);
83
+ return;
84
+ }
85
+ const text = ts.isShorthandPropertyAssignment(virtual) ? 'computed: virtual' : 'computed';
86
+ ctx.edits.push({ start: virtual.name.getStart(), end: virtual.name.getEnd(), text });
51
87
  }
52
88
  /** Inserts `option` into the decorator's options object, creating one when the call has no arguments. */
53
89
  function insertOption(options, option) {
@@ -70,7 +106,7 @@ function insertOption(options, option) {
70
106
  */
71
107
  function addInferredOption(decorator, node, ctx, spec) {
72
108
  const options = decoratorOptions(decorator);
73
- if (spec.satisfiedBy.some((name) => hasProperty(options, name))) {
109
+ if (spec.satisfiedBy.some((name) => findProperty(options, name))) {
74
110
  return undefined;
75
111
  }
76
112
  if (options.kind === 'opaque') {
@@ -213,6 +249,7 @@ function rewriteProperty(node, ctx) {
213
249
  }
214
250
  if (FIELD_DECORATORS.has(name)) {
215
251
  addFieldType(decorator, node, ctx);
252
+ renameVirtualOption(decorator, ctx, node);
216
253
  }
217
254
  if (RELATION_DECORATORS.has(name)) {
218
255
  addRelationEntity(decorator, node, ctx);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "uql-codemod",
3
- "description": "Migrates uql-orm entities from legacy decorators to the TC39 standard spec",
3
+ "description": "Upgrade uql-orm across versions that introduce breaking changes",
4
4
  "license": "MIT",
5
- "version": "0.28.0",
5
+ "version": "0.30.0",
6
6
  "type": "module",
7
7
  "engines": {
8
8
  "node": ">=24"
@@ -21,6 +21,7 @@
21
21
  "README.md"
22
22
  ],
23
23
  "scripts": {
24
+ "prepack": "bun run build",
24
25
  "build": "rimraf dist .tsconfig.tsbuildinfo && tsc -b tsconfig.build.json",
25
26
  "clean": "rimraf dist .tsconfig.tsbuildinfo"
26
27
  },