@joktec/mysql 0.2.13 → 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 (171) hide show
  1. package/README.md +419 -79
  2. package/dist/__tests__/mysql.column.decorator.spec.d.ts +2 -0
  3. package/dist/__tests__/mysql.column.decorator.spec.d.ts.map +1 -0
  4. package/dist/__tests__/mysql.column.decorator.spec.js +533 -0
  5. package/dist/__tests__/mysql.column.decorator.spec.js.map +1 -0
  6. package/dist/__tests__/mysql.dialect.spec.d.ts +2 -0
  7. package/dist/__tests__/mysql.dialect.spec.d.ts.map +1 -0
  8. package/dist/__tests__/mysql.dialect.spec.js +29 -0
  9. package/dist/__tests__/mysql.dialect.spec.js.map +1 -0
  10. package/dist/__tests__/mysql.exception.spec.d.ts +2 -0
  11. package/dist/__tests__/mysql.exception.spec.d.ts.map +1 -0
  12. package/dist/__tests__/mysql.exception.spec.js +42 -0
  13. package/dist/__tests__/mysql.exception.spec.js.map +1 -0
  14. package/dist/__tests__/mysql.helper.spec.d.ts +2 -0
  15. package/dist/__tests__/mysql.helper.spec.d.ts.map +1 -0
  16. package/dist/__tests__/mysql.helper.spec.js +80 -0
  17. package/dist/__tests__/mysql.helper.spec.js.map +1 -0
  18. package/dist/__tests__/mysql.module.integration.spec.d.ts +2 -0
  19. package/dist/__tests__/mysql.module.integration.spec.d.ts.map +1 -0
  20. package/dist/__tests__/mysql.module.integration.spec.js +67 -0
  21. package/dist/__tests__/mysql.module.integration.spec.js.map +1 -0
  22. package/dist/__tests__/mysql.repo.cursor.spec.d.ts +2 -0
  23. package/dist/__tests__/mysql.repo.cursor.spec.d.ts.map +1 -0
  24. package/dist/__tests__/mysql.repo.cursor.spec.js +146 -0
  25. package/dist/__tests__/mysql.repo.cursor.spec.js.map +1 -0
  26. package/dist/__tests__/mysql.service.spec.d.ts +2 -0
  27. package/dist/__tests__/mysql.service.spec.d.ts.map +1 -0
  28. package/dist/__tests__/mysql.service.spec.js +70 -0
  29. package/dist/__tests__/mysql.service.spec.js.map +1 -0
  30. package/dist/__tests__/mysql.utils.spec.d.ts +2 -0
  31. package/dist/__tests__/mysql.utils.spec.d.ts.map +1 -0
  32. package/dist/__tests__/mysql.utils.spec.js +21 -0
  33. package/dist/__tests__/mysql.utils.spec.js.map +1 -0
  34. package/dist/decorators/column.decorator.d.ts +10 -0
  35. package/dist/decorators/column.decorator.d.ts.map +1 -0
  36. package/dist/decorators/column.decorator.js +181 -0
  37. package/dist/decorators/column.decorator.js.map +1 -0
  38. package/dist/decorators/columns/array.column.d.ts +3 -0
  39. package/dist/decorators/columns/array.column.d.ts.map +1 -0
  40. package/dist/decorators/columns/array.column.js +13 -0
  41. package/dist/decorators/columns/array.column.js.map +1 -0
  42. package/dist/decorators/columns/bool.column.d.ts +2 -0
  43. package/dist/decorators/columns/bool.column.d.ts.map +1 -0
  44. package/dist/decorators/columns/bool.column.js +8 -0
  45. package/dist/decorators/columns/bool.column.js.map +1 -0
  46. package/dist/decorators/columns/column.factory.d.ts +5 -0
  47. package/dist/decorators/columns/column.factory.d.ts.map +1 -0
  48. package/dist/decorators/columns/column.factory.js +42 -0
  49. package/dist/decorators/columns/column.factory.js.map +1 -0
  50. package/dist/decorators/columns/column.type.d.ts +136 -0
  51. package/dist/decorators/columns/column.type.d.ts.map +1 -0
  52. package/dist/decorators/columns/column.type.js +3 -0
  53. package/dist/decorators/columns/column.type.js.map +1 -0
  54. package/dist/decorators/columns/column.util.d.ts +22 -0
  55. package/dist/decorators/columns/column.util.d.ts.map +1 -0
  56. package/dist/decorators/columns/column.util.js +163 -0
  57. package/dist/decorators/columns/column.util.js.map +1 -0
  58. package/dist/decorators/columns/date.column.d.ts +2 -0
  59. package/dist/decorators/columns/date.column.d.ts.map +1 -0
  60. package/dist/decorators/columns/date.column.js +8 -0
  61. package/dist/decorators/columns/date.column.js.map +1 -0
  62. package/dist/decorators/columns/enum.column.d.ts +3 -0
  63. package/dist/decorators/columns/enum.column.d.ts.map +1 -0
  64. package/dist/decorators/columns/enum.column.js +8 -0
  65. package/dist/decorators/columns/enum.column.js.map +1 -0
  66. package/dist/decorators/columns/index.d.ts +17 -0
  67. package/dist/decorators/columns/index.d.ts.map +1 -0
  68. package/dist/decorators/columns/index.js +33 -0
  69. package/dist/decorators/columns/index.js.map +1 -0
  70. package/dist/decorators/columns/nested.column.d.ts +4 -0
  71. package/dist/decorators/columns/nested.column.d.ts.map +1 -0
  72. package/dist/decorators/columns/nested.column.js +12 -0
  73. package/dist/decorators/columns/nested.column.js.map +1 -0
  74. package/dist/decorators/columns/number.column.d.ts +3 -0
  75. package/dist/decorators/columns/number.column.d.ts.map +1 -0
  76. package/dist/decorators/columns/number.column.js +16 -0
  77. package/dist/decorators/columns/number.column.js.map +1 -0
  78. package/dist/decorators/columns/object.column.d.ts +4 -0
  79. package/dist/decorators/columns/object.column.d.ts.map +1 -0
  80. package/dist/decorators/columns/object.column.js +13 -0
  81. package/dist/decorators/columns/object.column.js.map +1 -0
  82. package/dist/decorators/columns/primary.column.d.ts +3 -0
  83. package/dist/decorators/columns/primary.column.d.ts.map +1 -0
  84. package/dist/decorators/columns/primary.column.js +48 -0
  85. package/dist/decorators/columns/primary.column.js.map +1 -0
  86. package/dist/decorators/columns/string.column.d.ts +3 -0
  87. package/dist/decorators/columns/string.column.d.ts.map +1 -0
  88. package/dist/decorators/columns/string.column.js +28 -0
  89. package/dist/decorators/columns/string.column.js.map +1 -0
  90. package/dist/decorators/columns/swagger.column.d.ts +4 -0
  91. package/dist/decorators/columns/swagger.column.d.ts.map +1 -0
  92. package/dist/decorators/columns/swagger.column.js +49 -0
  93. package/dist/decorators/columns/swagger.column.js.map +1 -0
  94. package/dist/decorators/columns/timestamp.column.d.ts +6 -0
  95. package/dist/decorators/columns/timestamp.column.d.ts.map +1 -0
  96. package/dist/decorators/columns/timestamp.column.js +55 -0
  97. package/dist/decorators/columns/timestamp.column.js.map +1 -0
  98. package/dist/decorators/columns/transform.column.d.ts +4 -0
  99. package/dist/decorators/columns/transform.column.d.ts.map +1 -0
  100. package/dist/decorators/columns/transform.column.js +18 -0
  101. package/dist/decorators/columns/transform.column.js.map +1 -0
  102. package/dist/decorators/columns/virtual.column.d.ts +4 -0
  103. package/dist/decorators/columns/virtual.column.d.ts.map +1 -0
  104. package/dist/decorators/columns/virtual.column.js +23 -0
  105. package/dist/decorators/columns/virtual.column.js.map +1 -0
  106. package/dist/decorators/index.d.ts +3 -0
  107. package/dist/decorators/index.d.ts.map +1 -1
  108. package/dist/decorators/index.js +2 -0
  109. package/dist/decorators/index.js.map +1 -1
  110. package/dist/decorators/table.decorator.d.ts +31 -2
  111. package/dist/decorators/table.decorator.d.ts.map +1 -1
  112. package/dist/decorators/table.decorator.js +55 -18
  113. package/dist/decorators/table.decorator.js.map +1 -1
  114. package/dist/decorators/timestamp.decorator.d.ts +4 -0
  115. package/dist/decorators/timestamp.decorator.d.ts.map +1 -0
  116. package/dist/decorators/timestamp.decorator.js +19 -0
  117. package/dist/decorators/timestamp.decorator.js.map +1 -0
  118. package/dist/helpers/index.d.ts +0 -1
  119. package/dist/helpers/index.d.ts.map +1 -1
  120. package/dist/helpers/index.js +0 -1
  121. package/dist/helpers/index.js.map +1 -1
  122. package/dist/helpers/mysql.helper.d.ts +21 -6
  123. package/dist/helpers/mysql.helper.d.ts.map +1 -1
  124. package/dist/helpers/mysql.helper.js +152 -82
  125. package/dist/helpers/mysql.helper.js.map +1 -1
  126. package/dist/helpers/mysql.utils.d.ts.map +1 -1
  127. package/dist/helpers/mysql.utils.js +28 -11
  128. package/dist/helpers/mysql.utils.js.map +1 -1
  129. package/dist/index.d.ts +1 -0
  130. package/dist/index.d.ts.map +1 -1
  131. package/dist/index.js +6 -0
  132. package/dist/index.js.map +1 -1
  133. package/dist/models/mysql.model.d.ts.map +1 -1
  134. package/dist/models/mysql.model.js +4 -11
  135. package/dist/models/mysql.model.js.map +1 -1
  136. package/dist/models/mysql.option.d.ts +11 -2
  137. package/dist/models/mysql.option.d.ts.map +1 -1
  138. package/dist/mysql.config.d.ts +1 -17
  139. package/dist/mysql.config.d.ts.map +1 -1
  140. package/dist/mysql.config.js +1 -17
  141. package/dist/mysql.config.js.map +1 -1
  142. package/dist/mysql.exception.d.ts.map +1 -1
  143. package/dist/mysql.exception.js +48 -4
  144. package/dist/mysql.exception.js.map +1 -1
  145. package/dist/mysql.module.d.ts.map +1 -1
  146. package/dist/mysql.module.js.map +1 -1
  147. package/dist/mysql.repo.d.ts +9 -2
  148. package/dist/mysql.repo.d.ts.map +1 -1
  149. package/dist/mysql.repo.js +164 -48
  150. package/dist/mysql.repo.js.map +1 -1
  151. package/dist/mysql.service.d.ts.map +1 -1
  152. package/dist/mysql.service.js +8 -2
  153. package/dist/mysql.service.js.map +1 -1
  154. package/dist/services/index.d.ts +1 -0
  155. package/dist/services/index.d.ts.map +1 -1
  156. package/dist/services/index.js +1 -0
  157. package/dist/services/index.js.map +1 -1
  158. package/dist/services/mysql.dialect.d.ts +14 -0
  159. package/dist/services/mysql.dialect.d.ts.map +1 -0
  160. package/dist/services/mysql.dialect.js +50 -0
  161. package/dist/services/mysql.dialect.js.map +1 -0
  162. package/dist/services/mysql.strategy.d.ts +1 -1
  163. package/dist/services/mysql.strategy.d.ts.map +1 -1
  164. package/dist/services/mysql.strategy.js +1 -1
  165. package/dist/services/mysql.strategy.js.map +1 -1
  166. package/dist/tsconfig.tsbuildinfo +1 -1
  167. package/package.json +14 -10
  168. package/dist/helpers/mysql.finder.d.ts +0 -15
  169. package/dist/helpers/mysql.finder.d.ts.map +0 -1
  170. package/dist/helpers/mysql.finder.js +0 -128
  171. package/dist/helpers/mysql.finder.js.map +0 -1
package/README.md CHANGED
@@ -1,118 +1,458 @@
1
- <div align="center">
2
- <h1>@joktec/mysql</h1>
3
- <p>A NestJS library that provides a wrapper around the `sequelize` and `sequelize-typescript` libraries for MySQL databases.</p>
4
- </div>
1
+ # @joktec/mysql
5
2
 
6
- ## Table of Contents
7
- 1. [Introduction](#introduction)
8
- 2. [Installation](#installation)
9
- 3. [Getting Started](#getting-started)
10
- 4. [Reference](#reference)
11
- 5. [Contributing](#contributing)
3
+ Relational TypeORM database package for JokTec applications.
12
4
 
13
- ## Introduction
14
- This library provides an easy-to-use interface for working with MySQL databases in NestJS applications. It is built on top of the popular `sequelize` and `sequelize-typescript` libraries, and provides a set of convenient abstractions for working with these libraries.
5
+ `@joktec/mysql` wraps TypeORM with JokTec config, lifecycle, entity registration, base repositories, naming strategy support, and shared CRUD pagination contracts from `@joktec/core`. The package name remains `mysql`, but the first-class relational targets are MySQL, MariaDB, and Postgres.
15
6
 
16
- ## Installation
17
- To install this library, use either `npm` or `yarn`:
7
+ ## Install
18
8
 
19
9
  ```bash
20
- npm install @joktec/mysql
21
- # or
22
10
  yarn add @joktec/mysql
23
11
  ```
24
12
 
25
- ## Getting Started
26
- ### Configuration
27
- To use this library, you must first provide the necessary configuration. This can be done by creating a config.yml file in your application's root directory, with the following content:
28
- ```yaml
29
- mysql:
30
- host: localhost
31
- port: 3306
32
- user: my_user
33
- password: my_pass
34
- database: my_database
35
- ```
36
- Replace the values with your actual database connection information.
13
+ ## Public Surface
14
+
15
+ - module and service:
16
+ - `MysqlModule`
17
+ - `MysqlService`
18
+ - `MysqlRepo`
19
+ - config and client:
20
+ - `MysqlConfig`
21
+ - `MysqlClient`
22
+ - `Dialect`
23
+ - `MysqlLogLevel`
24
+ - relational dialect capabilities
25
+ - model contracts:
26
+ - `MysqlModel`
27
+ - `IMysqlRequest`
28
+ - `IMysqlResponse`
29
+ - decorators and helpers:
30
+ - `@Tables`
31
+ - `@Column`
32
+ - `@PrimaryColumn`
33
+ - `@PrimaryGeneratedColumn`
34
+ - `@TimestampColumn`
35
+ - `MysqlHelper`
36
+ - `MysqlNamingStrategy`
37
+ - selected TypeORM exports.
38
+
39
+ ## Module Registration
40
+
41
+ Register application entities through `MysqlModule.forRoot(...)`:
37
42
 
38
- ### Module
39
- Once you have provided the configuration, you can create a MysqlModule in your NestJS application:
40
- ```typescript
41
- import { CoreModule, Module } from '@joktec/core';
43
+ ```ts
44
+ import { Module } from '@joktec/core';
42
45
  import { MysqlModule } from '@joktec/mysql';
46
+ import { ProfileBadge } from './entities/profile-badge.entity';
43
47
 
44
48
  @Module({
45
- imports: [CoreModule, MysqlModule],
49
+ imports: [
50
+ MysqlModule.forRoot({
51
+ conId: 'default',
52
+ models: [ProfileBadge],
53
+ }),
54
+ ],
46
55
  })
47
- export class AppModule {}
56
+ export class RepositoryModule {}
48
57
  ```
49
58
 
50
- ### Service
51
- You can then use the MysqlService to interact with the database:
52
- ```typescript
59
+ Use `conId` when the application config contains multiple SQL connections. Repositories resolve the TypeORM repository through the same `conId`, including transaction-scoped managers when passed through repository options.
60
+
61
+ ## Repository Usage
62
+
63
+ Extend `MysqlRepo` for each application entity:
64
+
65
+ ```ts
53
66
  import { Injectable } from '@joktec/core';
54
- import { MysqlService } from '@joktec/mysql';
67
+ import { MysqlRepo, MysqlService } from '@joktec/mysql';
68
+ import { ProfileBadge } from '../entities/profile-badge.entity';
55
69
 
56
70
  @Injectable()
57
- export class UserService {
58
- constructor(private readonly mysqlService: MysqlService) {}
71
+ export class ProfileBadgeRepo extends MysqlRepo<ProfileBadge, string> {
72
+ constructor(mysqlService: MysqlService) {
73
+ super(mysqlService, ProfileBadge);
74
+ }
75
+ }
76
+ ```
77
+
78
+ Services can then use the shared `BaseService` contract:
59
79
 
60
- async getUsers() {
61
- const users = await this.mysqlService.getModel(User).findAll();
62
- return users;
80
+ ```ts
81
+ import { BaseService, Injectable } from '@joktec/core';
82
+ import { IMysqlRequest } from '@joktec/mysql';
83
+ import { ProfileBadge } from '../entities/profile-badge.entity';
84
+ import { ProfileBadgeRepo } from '../repositories/profile-badge.repo';
85
+
86
+ @Injectable()
87
+ export class ProfileBadgeService extends BaseService<ProfileBadge, string, IMysqlRequest<ProfileBadge>> {
88
+ constructor(protected profileBadgeRepo: ProfileBadgeRepo) {
89
+ super(profileBadgeRepo);
63
90
  }
64
91
  }
65
92
  ```
66
93
 
67
- ### Repository
68
- #### Define a Model
69
- You can define a model using `@joktec/mysql` (based on interface of `sequelize-typescript`):
70
- ```typescript
71
- import { Table, Column, Model } from '@joktec/mysql';
94
+ ## Config Shape
72
95
 
73
- @Table
74
- export class User extends Model<User> {
75
- @Column
76
- name: string;
96
+ The application config reads the `mysql` section and maps it to `MysqlConfig`.
77
97
 
78
- @Column
79
- email: string;
98
+ Common fields:
99
+
100
+ ```yaml
101
+ mysql:
102
+ conId: default
103
+ dialect: mysql
104
+ host: localhost
105
+ port: 3306
106
+ username: example_user
107
+ password: example_password
108
+ database: example_db
109
+ charset: utf8mb4
110
+ timezone: Z
111
+ connectTimeout: 20000
112
+ sync: false
113
+ benchmark:
114
+ enable: false
115
+ all: false
116
+ level: [error, warn]
117
+ ```
118
+
119
+ Supported `dialect` values are `mysql`, `mariadb`, and `postgres`. Other TypeORM drivers are not treated as stable package targets until they have package-level contract tests.
120
+
121
+ `slaves` can be configured for TypeORM replication. Slave entries inherit missing host, port, username, password, and database values from the master config.
122
+
123
+ `sync` is disabled by default. For multi-process deployments, enable `sync` only in one controlled owner process, such as a local example app, migration owner, or development-only bootstrap.
124
+
125
+ ## Query Contract
126
+
127
+ `IMysqlRequest<T>` extends `IBaseRequest<T>` and adds SQL-specific flags:
128
+
129
+ ```ts
130
+ {
131
+ select?: string | Array<keyof T>;
132
+ keyword?: string;
133
+ condition?: ICondition<T>;
134
+ page?: number;
135
+ offset?: number;
136
+ cursor?: string;
137
+ cursorKey?: keyof T | Array<keyof T> | string;
138
+ limit?: number;
139
+ sort?: ISort<T>;
140
+ populate?: IPopulate<T>;
141
+ withDeleted?: boolean;
80
142
  }
81
143
  ```
82
144
 
83
- #### Define a Repository
84
- You can create a repository for your model by extending the MysqlRepository class and providing the model type as a generic argument:
85
- ```typescript
86
- import { Injectable } from '@nestjs/common';
87
- import { MysqlRepository } from '@joktec/mysql';
88
- import { User } from './user.model';
145
+ Supported repository operations include `paginate`, `find`, `count`, `findOne`, `create`, `update`, `delete`, `restore`, `upsert`, and `bulkUpsert`.
89
146
 
90
- @Injectable()
91
- export class UserRepository extends MysqlRepository<User> {}
147
+ Repository reads use QueryBuilder so condition, projection, sorting, relation population, and cursor pagination share the same field validation path. Request field names are validated against TypeORM metadata before they are interpolated into SQL identifiers.
148
+
149
+ ## Pagination
150
+
151
+ `MysqlRepo.paginate` supports page, offset, and cursor pagination through the shared `@joktec/core` response contracts.
152
+
153
+ Runtime priority:
154
+
155
+ 1. cursor when `cursor` or `cursorKey` exists
156
+ 2. offset when `offset` exists
157
+ 3. page as the default fallback
158
+
159
+ Cursor pagination behavior:
160
+
161
+ - default cursor keys: `createdAt` plus entity primary key columns
162
+ - custom `cursorKey`: supported
163
+ - primary keys are appended as tie-breakers
164
+ - cursor keys are validated against TypeORM column metadata
165
+ - cursor conditions are built as lexicographic SQL `OR` clauses
166
+ - fetches `limit + 1` rows to compute `hasNextPage`
167
+ - returns `nextCursor` as an opaque token
168
+
169
+ Example first cursor request:
170
+
171
+ ```ts
172
+ const firstPage = await profileBadgeRepo.paginate({
173
+ cursorKey: 'createdAt',
174
+ limit: 20,
175
+ sort: { createdAt: 'desc' },
176
+ });
92
177
  ```
93
- #### Using Repository in Service
94
- You can then use the repository in your service:
95
- ```typescript
96
- import { Injectable } from '@nestjs/common';
97
- import { UserRepository } from './user.repository';
98
178
 
99
- @Injectable()
100
- export class UserService {
101
- constructor(private readonly userRepository: UserRepository) {}
179
+ Example next cursor request:
180
+
181
+ ```ts
182
+ const nextPage = await profileBadgeRepo.paginate({
183
+ cursor: firstPage.nextCursor,
184
+ limit: 20,
185
+ });
186
+ ```
187
+
188
+ For stable cursor pagination, prefer cursor keys backed by indexed columns. The default assumes entities have `createdAt` and primary key columns available through the shared model pattern.
189
+
190
+ ## Entity Notes
191
+
192
+ Keep entity definitions in the consuming app or package. Keep app-specific query behavior inside app repositories or services, not inside `@joktec/mysql`.
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.
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
+
204
+ ```ts
205
+ import { Column, MysqlModel, PrimaryColumn, Tables } from '@joktec/mysql';
206
+
207
+ @Tables<ProfileBadge>({ name: 'profile_badges', index: ['code', 'active'] })
208
+ export class ProfileBadge extends MysqlModel {
209
+ @PrimaryColumn('uuidv7')
210
+ id?: string;
211
+
212
+ @Column({ length: 64, nullable: false, unique: true })
213
+ code!: string;
214
+
215
+ @Column({ length: 128, nullable: false })
216
+ title!: string;
217
+
218
+ @Column({ nullable: false, default: true })
219
+ active!: boolean;
220
+ }
221
+ ```
222
+
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`.
224
+
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.
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.
102
232
 
103
- async getUsers() {
104
- const users = await this.userRepository.findAll();
105
- return users;
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';
106
373
  }
107
374
  }
108
375
  ```
109
376
 
110
- ## Reference
111
- [sequelize](https://www.npmjs.com/package/sequelize)
377
+ `@joktec/mysql` intentionally does not support Mongo ObjectId columns or TypeORM MongoDB connections. Use `@joktec/mongo` for Mongo schemas and ObjectId references.
112
378
 
113
- [sequelize-typescript](https://www.npmjs.com/package/sequelize-typescript)
379
+ Guidelines:
114
380
 
115
- ## Contributing
116
- Contributions to `@joktec/mysql` are welcome. If you would like to contribute, please fork the repository, make your changes, and submit a pull request.
381
+ - Prefer numeric auto-increment primary keys for write-heavy MySQL tables.
382
+ - Use UUID primary keys only when the application needs globally unique/public identifiers.
383
+ - Prefer `uuidv7` over random UUIDs when the id is also used as a cursor or clustered/indexed ordering signal.
384
+ - For UUID-heavy cursor pagination, prefer a composite cursor such as `createdAt + id`, or a monotonic indexed cursor column.
385
+ - Add indexes that match common filters and cursor sort order. A cursor using `createdAt + id` should have a matching composite index where possible.
386
+ - `@Tables` provides common TypeORM entity/index wiring, but database-specific search/index behavior should still be verified per dialect.
117
387
 
118
- Please make sure to update tests as appropriate.
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
+
437
+ ## Error Contract
438
+
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.
440
+
441
+ ## Repository Layout
442
+
443
+ - `src/mysql.module.ts`: Nest module and entity registration.
444
+ - `src/mysql.service.ts`: connection lifecycle service.
445
+ - `src/mysql.repo.ts`: base repository and pagination implementation.
446
+ - `src/mysql.config.ts`: config validation and defaults.
447
+ - `src/helpers`: query parsing and TypeORM option helpers.
448
+ - `src/services`: naming strategy and benchmark helpers.
449
+ - `src/models`: model, request, response, and options contracts.
450
+ - `src/index.ts`: public package export boundary.
451
+
452
+ ## Development
453
+
454
+ ```bash
455
+ yarn lint --scope @joktec/mysql
456
+ yarn build --scope @joktec/mysql
457
+ yarn test --scope @joktec/mysql
458
+ ```
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=mysql.column.decorator.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mysql.column.decorator.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/mysql.column.decorator.spec.ts"],"names":[],"mappings":""}