@joktec/mysql 0.2.14 → 0.2.15

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 (95) hide show
  1. package/README.md +212 -2
  2. package/dist/__tests__/mysql.column.decorator.spec.js +474 -1
  3. package/dist/__tests__/mysql.column.decorator.spec.js.map +1 -1
  4. package/dist/__tests__/mysql.dialect.spec.js +1 -0
  5. package/dist/__tests__/mysql.dialect.spec.js.map +1 -1
  6. package/dist/decorators/column.decorator.d.ts +4 -5
  7. package/dist/decorators/column.decorator.d.ts.map +1 -1
  8. package/dist/decorators/column.decorator.js +151 -5
  9. package/dist/decorators/column.decorator.js.map +1 -1
  10. package/dist/decorators/columns/array.column.d.ts +2 -1
  11. package/dist/decorators/columns/array.column.d.ts.map +1 -1
  12. package/dist/decorators/columns/array.column.js +7 -2
  13. package/dist/decorators/columns/array.column.js.map +1 -1
  14. package/dist/decorators/columns/column.factory.d.ts +3 -3
  15. package/dist/decorators/columns/column.factory.d.ts.map +1 -1
  16. package/dist/decorators/columns/column.factory.js +4 -2
  17. package/dist/decorators/columns/column.factory.js.map +1 -1
  18. package/dist/decorators/columns/column.type.d.ts +107 -4
  19. package/dist/decorators/columns/column.type.d.ts.map +1 -1
  20. package/dist/decorators/columns/column.util.d.ts +13 -6
  21. package/dist/decorators/columns/column.util.d.ts.map +1 -1
  22. package/dist/decorators/columns/column.util.js +85 -4
  23. package/dist/decorators/columns/column.util.js.map +1 -1
  24. package/dist/decorators/columns/enum.column.d.ts +2 -2
  25. package/dist/decorators/columns/enum.column.d.ts.map +1 -1
  26. package/dist/decorators/columns/enum.column.js.map +1 -1
  27. package/dist/decorators/columns/index.d.ts +3 -0
  28. package/dist/decorators/columns/index.d.ts.map +1 -1
  29. package/dist/decorators/columns/index.js +3 -0
  30. package/dist/decorators/columns/index.js.map +1 -1
  31. package/dist/decorators/columns/nested.column.d.ts +2 -2
  32. package/dist/decorators/columns/nested.column.d.ts.map +1 -1
  33. package/dist/decorators/columns/nested.column.js +2 -2
  34. package/dist/decorators/columns/nested.column.js.map +1 -1
  35. package/dist/decorators/columns/number.column.d.ts +2 -2
  36. package/dist/decorators/columns/number.column.d.ts.map +1 -1
  37. package/dist/decorators/columns/number.column.js +2 -1
  38. package/dist/decorators/columns/number.column.js.map +1 -1
  39. package/dist/decorators/columns/object.column.d.ts +4 -0
  40. package/dist/decorators/columns/object.column.d.ts.map +1 -0
  41. package/dist/decorators/columns/object.column.js +13 -0
  42. package/dist/decorators/columns/object.column.js.map +1 -0
  43. package/dist/decorators/columns/string.column.d.ts +2 -2
  44. package/dist/decorators/columns/string.column.d.ts.map +1 -1
  45. package/dist/decorators/columns/string.column.js +10 -7
  46. package/dist/decorators/columns/string.column.js.map +1 -1
  47. package/dist/decorators/columns/swagger.column.d.ts +2 -2
  48. package/dist/decorators/columns/swagger.column.d.ts.map +1 -1
  49. package/dist/decorators/columns/swagger.column.js +10 -1
  50. package/dist/decorators/columns/swagger.column.js.map +1 -1
  51. package/dist/decorators/columns/timestamp.column.d.ts +6 -0
  52. package/dist/decorators/columns/timestamp.column.d.ts.map +1 -0
  53. package/dist/decorators/columns/timestamp.column.js +55 -0
  54. package/dist/decorators/columns/timestamp.column.js.map +1 -0
  55. package/dist/decorators/columns/transform.column.d.ts +2 -2
  56. package/dist/decorators/columns/transform.column.d.ts.map +1 -1
  57. package/dist/decorators/columns/transform.column.js +5 -2
  58. package/dist/decorators/columns/transform.column.js.map +1 -1
  59. package/dist/decorators/columns/virtual.column.d.ts +4 -0
  60. package/dist/decorators/columns/virtual.column.d.ts.map +1 -0
  61. package/dist/decorators/columns/virtual.column.js +23 -0
  62. package/dist/decorators/columns/virtual.column.js.map +1 -0
  63. package/dist/decorators/index.d.ts +2 -0
  64. package/dist/decorators/index.d.ts.map +1 -1
  65. package/dist/decorators/index.js +1 -0
  66. package/dist/decorators/index.js.map +1 -1
  67. package/dist/decorators/table.decorator.d.ts +31 -2
  68. package/dist/decorators/table.decorator.d.ts.map +1 -1
  69. package/dist/decorators/table.decorator.js +55 -24
  70. package/dist/decorators/table.decorator.js.map +1 -1
  71. package/dist/decorators/timestamp.decorator.d.ts +4 -0
  72. package/dist/decorators/timestamp.decorator.d.ts.map +1 -0
  73. package/dist/decorators/timestamp.decorator.js +19 -0
  74. package/dist/decorators/timestamp.decorator.js.map +1 -0
  75. package/dist/helpers/index.d.ts +0 -1
  76. package/dist/helpers/index.d.ts.map +1 -1
  77. package/dist/helpers/index.js +0 -1
  78. package/dist/helpers/index.js.map +1 -1
  79. package/dist/index.d.ts +1 -1
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +2 -1
  82. package/dist/index.js.map +1 -1
  83. package/dist/models/mysql.model.d.ts.map +1 -1
  84. package/dist/models/mysql.model.js +4 -11
  85. package/dist/models/mysql.model.js.map +1 -1
  86. package/dist/mysql.repo.d.ts +1 -2
  87. package/dist/mysql.repo.d.ts.map +1 -1
  88. package/dist/mysql.repo.js +0 -15
  89. package/dist/mysql.repo.js.map +1 -1
  90. package/dist/tsconfig.tsbuildinfo +1 -1
  91. package/package.json +2 -2
  92. package/dist/helpers/mysql.finder.d.ts +0 -15
  93. package/dist/helpers/mysql.finder.d.ts.map +0 -1
  94. package/dist/helpers/mysql.finder.js +0 -128
  95. package/dist/helpers/mysql.finder.js.map +0 -1
package/README.md CHANGED
@@ -30,8 +30,9 @@ yarn add @joktec/mysql
30
30
  - `@Tables`
31
31
  - `@Column`
32
32
  - `@PrimaryColumn`
33
+ - `@PrimaryGeneratedColumn`
34
+ - `@TimestampColumn`
33
35
  - `MysqlHelper`
34
- - `MysqlFinder`
35
36
  - `MysqlNamingStrategy`
36
37
  - selected TypeORM exports.
37
38
 
@@ -192,6 +193,14 @@ Keep entity definitions in the consuming app or package. Keep app-specific query
192
193
 
193
194
  `@joktec/mysql` exposes schema-first decorators that wrap TypeORM metadata together with Swagger, `class-validator`, and `class-transformer` metadata. Use them when an entity should be reused as the source class for mapped DTOs.
194
195
 
196
+ The wrapper philosophy is pragmatic:
197
+
198
+ - use `@Column` as the main property-level wrapper for common TypeORM column, relation, relation-id, version, view, and virtual metadata
199
+ - keep `@PrimaryColumn` / `@PrimaryGeneratedColumn` and `@TimestampColumn` separate because primary keys and timestamps have strong business semantics
200
+ - infer validation, transform, and Swagger metadata from one source of truth whenever possible
201
+ - keep raw TypeORM available for rare advanced cases instead of wrapping every decorator
202
+ - use `immutable` as the API read-only hint, while `update: false` remains the TypeORM write behavior
203
+
195
204
  ```ts
196
205
  import { Column, MysqlModel, PrimaryColumn, Tables } from '@joktec/mysql';
197
206
 
@@ -211,10 +220,162 @@ export class ProfileBadge extends MysqlModel {
211
220
  }
212
221
  ```
213
222
 
214
- `@Column` accepts normal TypeORM column options and adds optional schema metadata such as `hidden`, `groups`, `example`, `deprecated`, `swagger`, `decorators`, `required`, `isEmail`, `isPhone`, `isHexColor`, `isUrl`, `minlength`, `maxlength`, `min`, and `max`.
223
+ `@Column` accepts normal TypeORM column options and adds optional schema metadata such as `hidden`, `optional`, `expose`, `nested`, `each`, `example`, `deprecated`, `immutable`, `swagger`, `decorators`, `required`, `isEmail`, `isPhone`, `isHexColor`, `isUrl`, `isInt`, `isUUID`, `isObject`, `minlength`, `maxlength`, `min`, `max`, `index`, and `check`.
215
224
 
216
225
  `@PrimaryColumn` supports TypeORM generated strategies (`increment`, `uuid`, `rowid`, `identity`) and JokTec-managed `uuidv7`. `uuidv7` is stored as a 36-character varchar and generated before insert when the entity does not already have an id.
217
226
 
227
+ `@TimestampColumn('create' | 'update' | 'delete')` wraps TypeORM create/update/delete timestamp columns and adds the shared validation, transform, Swagger, and GraphQL metadata used by the column wrapper.
228
+
229
+ ### Read-Only and Immutable Metadata
230
+
231
+ `immutable` controls generated Swagger `readOnly` metadata. It is intentionally named to match the Mongo schema wrapper terminology.
232
+
233
+ TypeORM `update: false` still controls ORM write behavior. When `immutable` is not set, `update: false` is also treated as Swagger `readOnly`.
234
+
235
+ Priority:
236
+
237
+ 1. `swagger.readOnly` is the final override
238
+ 2. `immutable` controls API read-only metadata
239
+ 3. `update: false` falls back to API read-only metadata
240
+
241
+ Examples:
242
+
243
+ ```ts
244
+ class AuditEntity extends MysqlModel {
245
+ @Column({ type: 'varchar', length: 36, update: false })
246
+ createdBy?: string;
247
+
248
+ @Column({ type: 'varchar', length: 36, immutable: true })
249
+ updatedBy?: string;
250
+
251
+ @Column({ type: 'varchar', update: false, immutable: false })
252
+ serviceInsertOnlyButClientWritable?: string;
253
+ }
254
+ ```
255
+
256
+ The wrapper defaults read-only Swagger metadata for naturally system-managed or computed fields:
257
+
258
+ - `@PrimaryColumn(...)` and `@PrimaryGeneratedColumn(...)`
259
+ - `@TimestampColumn('create' | 'update' | 'delete')`
260
+ - `@Column({ kind: 'version' })`
261
+ - `@Column({ kind: 'view' })`
262
+ - `@Column({ kind: 'virtual' })` getter fields
263
+ - `@Column({ kind: 'virtual', mode: 'sql' })`
264
+ - `@Column({ kind: 'relation-id' })`
265
+ - `@Column({ kind: 'tree', tree: 'level' })`
266
+
267
+ Special TypeORM column modes are exposed through `@Column({ kind })` instead of standalone wrapper names:
268
+
269
+ ```ts
270
+ class ProfileStats {
271
+ @Column({ kind: 'version' })
272
+ version?: number;
273
+
274
+ @Column({ kind: 'view', name: 'display_name' })
275
+ displayName?: string;
276
+
277
+ @Column('int', {
278
+ kind: 'virtual',
279
+ mode: 'sql',
280
+ query: alias => `SELECT COUNT(*) FROM follows WHERE follows.profile_id = ${alias}.id`,
281
+ })
282
+ followerCount?: number;
283
+
284
+ @Column({ kind: 'virtual', comment: 'Display label' })
285
+ get label(): string {
286
+ return `${this.displayName}`;
287
+ }
288
+ }
289
+ ```
290
+
291
+ `kind: 'virtual'` defaults to metadata-only TypeScript getters. Use `mode: 'sql'` for TypeORM SQL-calculated virtual columns that require a `query(alias)` option.
292
+
293
+ Relations and relation ids can also be expressed through `@Column`:
294
+
295
+ ```ts
296
+ class ArticleEntity extends MysqlModel {
297
+ @Column('uuid', { nullable: true, index: 'IDX_article_author_id', isUUID: true })
298
+ authorId?: string;
299
+
300
+ @Column({
301
+ kind: 'relation',
302
+ relation: 'many-to-one',
303
+ type: () => UserEntity,
304
+ inverseSide: user => user.articles,
305
+ joinColumn: { name: 'author_id' },
306
+ nullable: true,
307
+ })
308
+ author?: UserEntity;
309
+
310
+ @Column({
311
+ kind: 'relation-id',
312
+ relationId: (article: ArticleEntity) => article.author,
313
+ nullable: true,
314
+ })
315
+ resolvedAuthorId?: string;
316
+ }
317
+ ```
318
+
319
+ `index` and `check` can be declared on normal, version, relation, and relation-id columns when the constraint belongs to one property. Use `@Tables({ checks: [...] })` for composite table checks.
320
+
321
+ `@Tables` supports common class-level TypeORM metadata:
322
+
323
+ ```ts
324
+ @Tables({ name: 'profiles', checks: [{ name: 'CHK_score', expression: 'score >= 0' }] })
325
+ export class ProfileEntity extends MysqlModel {}
326
+
327
+ @Tables({ kind: 'view', name: 'active_profiles', expression: 'SELECT * FROM profiles WHERE deleted_at IS NULL' })
328
+ export class ActiveProfileView {}
329
+
330
+ @Tables({ inheritance: { column: { name: 'kind', type: 'varchar' } } })
331
+ export class BaseContent extends MysqlModel {}
332
+
333
+ @Tables({ kind: 'child', discriminatorValue: 'article' })
334
+ export class ArticleContent extends BaseContent {}
335
+
336
+ @Tables({ tree: 'closure-table' })
337
+ export class CategoryEntity extends MysqlModel {}
338
+ ```
339
+
340
+ JSON-like columns can model raw records or nested classes:
341
+
342
+ ```ts
343
+ class Preference {
344
+ @Column({ required: true })
345
+ theme!: string;
346
+
347
+ @Column({ default: true })
348
+ publicProfile?: boolean;
349
+ }
350
+
351
+ class InsightMetric {
352
+ @Column({ required: true })
353
+ key!: string;
354
+
355
+ @Column('int', { required: true })
356
+ value!: number;
357
+ }
358
+
359
+ @Tables<CreatorInsight>({ name: 'creator_insights' })
360
+ export class CreatorInsight extends MysqlModel {
361
+ @PrimaryColumn('uuidv7')
362
+ id?: string;
363
+
364
+ @Column('jsonb', { nullable: true })
365
+ preference?: Preference;
366
+
367
+ @Column('jsonb', { nullable: true, nested: InsightMetric, each: true })
368
+ metrics?: InsightMetric[];
369
+
370
+ @Column({ kind: 'virtual', comment: 'Computed score label', optional: true })
371
+ get scoreLabel(): string {
372
+ return 'standard';
373
+ }
374
+ }
375
+ ```
376
+
377
+ `@joktec/mysql` intentionally does not support Mongo ObjectId columns or TypeORM MongoDB connections. Use `@joktec/mongo` for Mongo schemas and ObjectId references.
378
+
218
379
  Guidelines:
219
380
 
220
381
  - Prefer numeric auto-increment primary keys for write-heavy MySQL tables.
@@ -224,6 +385,55 @@ Guidelines:
224
385
  - Add indexes that match common filters and cursor sort order. A cursor using `createdAt + id` should have a matching composite index where possible.
225
386
  - `@Tables` provides common TypeORM entity/index wiring, but database-specific search/index behavior should still be verified per dialect.
226
387
 
388
+ ### Migration Notes
389
+
390
+ Recent schema-first decorator changes affect applications that still use raw TypeORM, Swagger, `class-validator`, and `class-transformer` decorators together on each entity property.
391
+
392
+ When migrating an entity:
393
+
394
+ - Migrate one property at a time and replace the whole property decorator stack when the wrapper option can express the same behavior.
395
+ - Replace `@PrimaryGeneratedColumn()` with `@PrimaryColumn('increment')`.
396
+ - Replace `@PrimaryGeneratedColumn('uuid')` with `@PrimaryColumn('uuid')`, or `@PrimaryColumn('uuidv7')` when the app wants framework-generated time-ordered UUIDs.
397
+ - Replace raw TypeORM `@CreateDateColumn`, `@UpdateDateColumn`, and `@DeleteDateColumn` with `@TimestampColumn('create' | 'update' | 'delete')` when the shared metadata wrapper is desired.
398
+ - Replace raw TypeORM `@VersionColumn`, `@VirtualColumn`, and `@ViewColumn` with `@Column({ kind: 'version' })`, `@Column({ kind: 'virtual', mode: 'sql', query })`, or `@Column({ kind: 'view' })`.
399
+ - Replace relation decorator stacks such as `@ManyToOne` + `@JoinColumn` + Swagger metadata with `@Column({ kind: 'relation', ... })` when the wrapper can express the same relationship.
400
+ - Replace TypeORM `@RelationId` with `@Column({ kind: 'relation-id', relationId })` for relation id properties.
401
+ - Replace duplicate validation decorators such as `@IsNotEmpty`, `@IsOptional`, `@IsEmail`, `@IsInt`, `@IsUUID`, `@IsObject`, `@MinLength`, `@MaxLength`, `@Min`, and `@Max` with `@Column` options where possible.
402
+ - Replace simple `@Expose`, grouped expose, hidden fields, and Swagger property metadata with `hidden`, `groups`, `expose`, `example`, `comment`, `deprecated`, and `swagger` options where possible.
403
+ - Replace repeated `swagger: { readOnly: true }` with `immutable: true` when the API contract is read-only. Use `update: false` when TypeORM should skip updates after insert.
404
+ - Keep custom validators or transforms in `decorators: [...]` when there is no wrapper option.
405
+ - Do not migrate Mongo/ObjectId-style columns into this package.
406
+
407
+ Common mappings:
408
+
409
+ | Legacy decorators | Schema-first wrapper |
410
+ | --- | --- |
411
+ | `@PrimaryGeneratedColumn()` | `@PrimaryColumn('increment')` |
412
+ | `@PrimaryGeneratedColumn('uuid')` | `@PrimaryColumn('uuid')` |
413
+ | app-generated ordered UUID id | `@PrimaryColumn('uuidv7')` |
414
+ | TypeORM `@CreateDateColumn(...)` | `@TimestampColumn('create', ...)` |
415
+ | TypeORM `@UpdateDateColumn(...)` | `@TimestampColumn('update', ...)` |
416
+ | TypeORM `@DeleteDateColumn(...)` | `@TimestampColumn('delete', ...)` |
417
+ | TypeORM `@VersionColumn(...)` | `@Column({ kind: 'version', ... })` |
418
+ | TypeORM `@VirtualColumn(...)` | `@Column({ kind: 'virtual', mode: 'sql', query, ... })` |
419
+ | TypeORM `@ViewColumn(...)` | `@Column({ kind: 'view', ... })` |
420
+ | `@Column(...)` | `@Column(...)` from `@joktec/mysql` |
421
+ | `@Index(...)` on one property | `@Column({ index: true | 'IDX_name' | { name, options } })` |
422
+ | `@Check(...)` on one property | `@Column({ check: 'sql expression' | { name, expression } })` |
423
+ | `@ManyToOne(...)` + `@JoinColumn(...)` | `@Column({ kind: 'relation', relation: 'many-to-one', joinColumn, ... })` |
424
+ | `@OneToMany(...)` | `@Column({ kind: 'relation', relation: 'one-to-many', ... })` |
425
+ | `@RelationId(...)` | `@Column({ kind: 'relation-id', relationId })` |
426
+ | `@IsInt()` | `@Column({ isInt: true })` or an integer column type |
427
+ | `@IsUUID()` | `@Column({ isUUID: true })` |
428
+ | `@IsObject()` | `@Column({ isObject: true })` or a JSON column |
429
+ | `@Type(() => Number)` | inferred by numeric design type or pass through `decorators` for custom cases |
430
+ | `@ValidateNested()` + `@Type(() => Preference)` | `@Column('jsonb', { nested: Preference })` |
431
+ | `@ValidateNested({ each: true })` + `@Type(() => Preference)` | `@Column('jsonb', { nested: Preference, each: true })` |
432
+ | `@Expose()` + `@ApiProperty(...)` on a getter | `@Column({ kind: 'virtual', ... })` |
433
+ | Swagger `readOnly: true` | `@Column({ immutable: true })` or `update: false` when ORM updates must also be blocked |
434
+
435
+ Raw TypeORM decorators remain available for advanced cases that are intentionally outside the wrapper surface, including listeners, non-primary `@Generated`, `@ForeignKey`, and Postgres `@Exclusion`.
436
+
227
437
  ## Error Contract
228
438
 
229
439
  `MysqlCatch` normalizes common TypeORM driver errors into stable framework codes, including duplicate key, foreign key violation, not-null violation, unknown column, deadlock, lock timeout, connection failure, and transaction conflict. Raw driver details remain attached for logging/debugging, but application code should branch on the stable framework message.