@simplysm/orm-common 13.0.68 → 13.0.70

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 (204) hide show
  1. package/README.md +54 -1447
  2. package/dist/create-db-context.d.ts +10 -10
  3. package/dist/create-db-context.js +9 -9
  4. package/dist/create-db-context.js.map +1 -1
  5. package/dist/ddl/column-ddl.d.ts +4 -4
  6. package/dist/ddl/initialize.d.ts +17 -17
  7. package/dist/ddl/initialize.js +2 -2
  8. package/dist/ddl/initialize.js.map +1 -1
  9. package/dist/ddl/relation-ddl.d.ts +6 -6
  10. package/dist/ddl/schema-ddl.d.ts +4 -4
  11. package/dist/ddl/table-ddl.d.ts +24 -24
  12. package/dist/ddl/table-ddl.js +4 -4
  13. package/dist/ddl/table-ddl.js.map +1 -1
  14. package/dist/errors/db-transaction-error.d.ts +15 -15
  15. package/dist/errors/db-transaction-error.d.ts.map +1 -1
  16. package/dist/exec/executable.d.ts +23 -23
  17. package/dist/exec/executable.js +3 -3
  18. package/dist/exec/executable.js.map +1 -1
  19. package/dist/exec/queryable.d.ts +160 -160
  20. package/dist/exec/queryable.js +119 -119
  21. package/dist/exec/queryable.js.map +1 -1
  22. package/dist/exec/search-parser.d.ts +37 -37
  23. package/dist/exec/search-parser.d.ts.map +1 -1
  24. package/dist/expr/expr-unit.d.ts +4 -4
  25. package/dist/expr/expr.d.ts +257 -257
  26. package/dist/expr/expr.js +265 -265
  27. package/dist/expr/expr.js.map +1 -1
  28. package/dist/query-builder/base/expr-renderer-base.d.ts +9 -9
  29. package/dist/query-builder/base/expr-renderer-base.js +2 -2
  30. package/dist/query-builder/base/expr-renderer-base.js.map +1 -1
  31. package/dist/query-builder/base/query-builder-base.d.ts +26 -26
  32. package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
  33. package/dist/query-builder/base/query-builder-base.js +22 -22
  34. package/dist/query-builder/base/query-builder-base.js.map +1 -1
  35. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +4 -4
  36. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
  37. package/dist/query-builder/mssql/mssql-expr-renderer.js +18 -18
  38. package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -1
  39. package/dist/query-builder/mssql/mssql-query-builder.d.ts +2 -2
  40. package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
  41. package/dist/query-builder/mssql/mssql-query-builder.js +11 -11
  42. package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -1
  43. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +4 -4
  44. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
  45. package/dist/query-builder/mysql/mysql-expr-renderer.js +17 -17
  46. package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -1
  47. package/dist/query-builder/mysql/mysql-query-builder.d.ts +8 -8
  48. package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
  49. package/dist/query-builder/mysql/mysql-query-builder.js +5 -5
  50. package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -1
  51. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +4 -4
  52. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
  53. package/dist/query-builder/postgresql/postgresql-expr-renderer.js +17 -17
  54. package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -1
  55. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +5 -5
  56. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
  57. package/dist/query-builder/postgresql/postgresql-query-builder.js +8 -8
  58. package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
  59. package/dist/query-builder/query-builder.d.ts +1 -1
  60. package/dist/schema/factory/column-builder.d.ts +79 -79
  61. package/dist/schema/factory/column-builder.js +42 -42
  62. package/dist/schema/factory/index-builder.d.ts +39 -39
  63. package/dist/schema/factory/index-builder.js +26 -26
  64. package/dist/schema/factory/relation-builder.d.ts +99 -99
  65. package/dist/schema/factory/relation-builder.d.ts.map +1 -1
  66. package/dist/schema/factory/relation-builder.js +38 -38
  67. package/dist/schema/procedure-builder.d.ts +49 -49
  68. package/dist/schema/procedure-builder.d.ts.map +1 -1
  69. package/dist/schema/procedure-builder.js +33 -33
  70. package/dist/schema/table-builder.d.ts +59 -59
  71. package/dist/schema/table-builder.d.ts.map +1 -1
  72. package/dist/schema/table-builder.js +43 -43
  73. package/dist/schema/view-builder.d.ts +49 -49
  74. package/dist/schema/view-builder.d.ts.map +1 -1
  75. package/dist/schema/view-builder.js +32 -32
  76. package/dist/types/column.d.ts +22 -22
  77. package/dist/types/column.js +1 -1
  78. package/dist/types/column.js.map +1 -1
  79. package/dist/types/db.d.ts +40 -40
  80. package/dist/types/expr.d.ts +59 -59
  81. package/dist/types/expr.d.ts.map +1 -1
  82. package/dist/types/query-def.d.ts +44 -44
  83. package/dist/types/query-def.d.ts.map +1 -1
  84. package/dist/utils/result-parser.d.ts +11 -11
  85. package/dist/utils/result-parser.js +3 -3
  86. package/dist/utils/result-parser.js.map +1 -1
  87. package/package.json +5 -5
  88. package/src/create-db-context.ts +20 -20
  89. package/src/ddl/column-ddl.ts +4 -4
  90. package/src/ddl/initialize.ts +259 -259
  91. package/src/ddl/relation-ddl.ts +89 -89
  92. package/src/ddl/schema-ddl.ts +4 -4
  93. package/src/ddl/table-ddl.ts +189 -189
  94. package/src/errors/db-transaction-error.ts +13 -13
  95. package/src/exec/executable.ts +25 -25
  96. package/src/exec/queryable.ts +2033 -2033
  97. package/src/exec/search-parser.ts +57 -57
  98. package/src/expr/expr-unit.ts +4 -4
  99. package/src/expr/expr.ts +2140 -2140
  100. package/src/query-builder/base/expr-renderer-base.ts +237 -237
  101. package/src/query-builder/base/query-builder-base.ts +213 -213
  102. package/src/query-builder/mssql/mssql-expr-renderer.ts +607 -607
  103. package/src/query-builder/mssql/mssql-query-builder.ts +650 -650
  104. package/src/query-builder/mysql/mysql-expr-renderer.ts +613 -613
  105. package/src/query-builder/mysql/mysql-query-builder.ts +759 -759
  106. package/src/query-builder/postgresql/postgresql-expr-renderer.ts +611 -611
  107. package/src/query-builder/postgresql/postgresql-query-builder.ts +686 -686
  108. package/src/query-builder/query-builder.ts +19 -19
  109. package/src/schema/factory/column-builder.ts +423 -423
  110. package/src/schema/factory/index-builder.ts +164 -164
  111. package/src/schema/factory/relation-builder.ts +453 -453
  112. package/src/schema/procedure-builder.ts +232 -232
  113. package/src/schema/table-builder.ts +319 -319
  114. package/src/schema/view-builder.ts +221 -221
  115. package/src/types/column.ts +188 -188
  116. package/src/types/db.ts +208 -208
  117. package/src/types/expr.ts +697 -697
  118. package/src/types/query-def.ts +513 -513
  119. package/src/utils/result-parser.ts +458 -458
  120. package/tests/db-context/create-db-context.spec.ts +224 -0
  121. package/tests/db-context/define-db-context.spec.ts +68 -0
  122. package/tests/ddl/basic.expected.ts +341 -0
  123. package/tests/ddl/basic.spec.ts +714 -0
  124. package/tests/ddl/column-builder.expected.ts +310 -0
  125. package/tests/ddl/column-builder.spec.ts +637 -0
  126. package/tests/ddl/index-builder.expected.ts +38 -0
  127. package/tests/ddl/index-builder.spec.ts +202 -0
  128. package/tests/ddl/procedure-builder.expected.ts +52 -0
  129. package/tests/ddl/procedure-builder.spec.ts +234 -0
  130. package/tests/ddl/relation-builder.expected.ts +36 -0
  131. package/tests/ddl/relation-builder.spec.ts +372 -0
  132. package/tests/ddl/table-builder.expected.ts +113 -0
  133. package/tests/ddl/table-builder.spec.ts +433 -0
  134. package/tests/ddl/view-builder.expected.ts +38 -0
  135. package/tests/ddl/view-builder.spec.ts +176 -0
  136. package/tests/dml/delete.expected.ts +96 -0
  137. package/tests/dml/delete.spec.ts +160 -0
  138. package/tests/dml/insert.expected.ts +192 -0
  139. package/tests/dml/insert.spec.ts +288 -0
  140. package/tests/dml/update.expected.ts +176 -0
  141. package/tests/dml/update.spec.ts +318 -0
  142. package/tests/dml/upsert.expected.ts +215 -0
  143. package/tests/dml/upsert.spec.ts +242 -0
  144. package/tests/errors/queryable-errors.spec.ts +177 -0
  145. package/tests/escape.spec.ts +100 -0
  146. package/tests/examples/pivot.expected.ts +211 -0
  147. package/tests/examples/pivot.spec.ts +533 -0
  148. package/tests/examples/sampling.expected.ts +69 -0
  149. package/tests/examples/sampling.spec.ts +104 -0
  150. package/tests/examples/unpivot.expected.ts +120 -0
  151. package/tests/examples/unpivot.spec.ts +226 -0
  152. package/tests/exec/search-parser.spec.ts +283 -0
  153. package/tests/executable/basic.expected.ts +18 -0
  154. package/tests/executable/basic.spec.ts +54 -0
  155. package/tests/expr/comparison.expected.ts +282 -0
  156. package/tests/expr/comparison.spec.ts +400 -0
  157. package/tests/expr/conditional.expected.ts +134 -0
  158. package/tests/expr/conditional.spec.ts +276 -0
  159. package/tests/expr/date.expected.ts +332 -0
  160. package/tests/expr/date.spec.ts +526 -0
  161. package/tests/expr/math.expected.ts +62 -0
  162. package/tests/expr/math.spec.ts +106 -0
  163. package/tests/expr/string.expected.ts +218 -0
  164. package/tests/expr/string.spec.ts +356 -0
  165. package/tests/expr/utility.expected.ts +147 -0
  166. package/tests/expr/utility.spec.ts +182 -0
  167. package/tests/select/basic.expected.ts +322 -0
  168. package/tests/select/basic.spec.ts +502 -0
  169. package/tests/select/filter.expected.ts +357 -0
  170. package/tests/select/filter.spec.ts +1068 -0
  171. package/tests/select/group.expected.ts +169 -0
  172. package/tests/select/group.spec.ts +244 -0
  173. package/tests/select/join.expected.ts +582 -0
  174. package/tests/select/join.spec.ts +805 -0
  175. package/tests/select/order.expected.ts +150 -0
  176. package/tests/select/order.spec.ts +189 -0
  177. package/tests/select/recursive-cte.expected.ts +244 -0
  178. package/tests/select/recursive-cte.spec.ts +514 -0
  179. package/tests/select/result-meta.spec.ts +270 -0
  180. package/tests/select/subquery.expected.ts +363 -0
  181. package/tests/select/subquery.spec.ts +537 -0
  182. package/tests/select/view.expected.ts +155 -0
  183. package/tests/select/view.spec.ts +235 -0
  184. package/tests/select/window.expected.ts +345 -0
  185. package/tests/select/window.spec.ts +618 -0
  186. package/tests/setup/MockExecutor.ts +18 -0
  187. package/tests/setup/TestDbContext.ts +59 -0
  188. package/tests/setup/models/Company.ts +13 -0
  189. package/tests/setup/models/Employee.ts +10 -0
  190. package/tests/setup/models/MonthlySales.ts +11 -0
  191. package/tests/setup/models/Post.ts +16 -0
  192. package/tests/setup/models/Sales.ts +10 -0
  193. package/tests/setup/models/User.ts +19 -0
  194. package/tests/setup/procedure/GetAllUsers.ts +9 -0
  195. package/tests/setup/procedure/GetUserById.ts +12 -0
  196. package/tests/setup/test-utils.ts +72 -0
  197. package/tests/setup/views/ActiveUsers.ts +8 -0
  198. package/tests/setup/views/UserSummary.ts +11 -0
  199. package/tests/types/nullable-queryable-record.spec.ts +145 -0
  200. package/tests/utils/result-parser-perf.spec.ts +210 -0
  201. package/tests/utils/result-parser.spec.ts +701 -0
  202. package/docs/expressions.md +0 -172
  203. package/docs/queries.md +0 -444
  204. package/docs/schema.md +0 -245
package/README.md CHANGED
@@ -1,1472 +1,79 @@
1
1
  # @simplysm/orm-common
2
2
 
3
- The common module of Simplysm ORM, providing core ORM functionality including type-safe query builders, schema definitions, and SQL expressions.
4
- It generates JSON AST instead of SQL strings, which are then converted to SQL for each DBMS (MySQL, MSSQL, PostgreSQL).
3
+ Simplysm Package - ORM Module (common)
5
4
 
6
5
  ## Installation
7
6
 
8
- ```bash
9
- npm install @simplysm/orm-common
10
- # or
11
7
  pnpm add @simplysm/orm-common
12
- ```
13
8
 
14
- ## Supported Databases
9
+ **Peer Dependencies:** `@simplysm/core-common`
15
10
 
16
- | Database | Dialect | Minimum Version |
17
- |-------------|---------|----------|
18
- | MySQL | `mysql` | 8.0.14+ |
19
- | SQL Server | `mssql` | 2012+ |
20
- | PostgreSQL | `postgresql` | 9.0+ |
21
-
22
- ## Main Modules
11
+ ## Source Index
23
12
 
24
13
  ### Core
25
14
 
26
- #### `defineDbContext(config)`
27
-
28
- Creates a `DbContextDef` schema blueprint without any runtime state.
29
-
30
- ```typescript
31
- import { defineDbContext } from "@simplysm/orm-common";
32
-
33
- const MyDbDef = defineDbContext({
34
- tables: { user: User, post: Post },
35
- views: { activeUsers: ActiveUsers },
36
- procedures: { getUserById: GetUserById },
37
- migrations: [
38
- {
39
- name: "20260101_add_status",
40
- up: async (db) => {
41
- const c = createColumnFactory();
42
- await db.addColumn(
43
- { database: "mydb", name: "User" },
44
- "status",
45
- c.varchar(20).nullable(),
46
- );
47
- },
48
- },
49
- ],
50
- });
51
- ```
52
-
53
- | Parameter | Type | Description |
54
- |-----------|------|-------------|
55
- | `config.tables` | `Record<string, TableBuilder>` | Table builders |
56
- | `config.views` | `Record<string, ViewBuilder>` | View builders (optional) |
57
- | `config.procedures` | `Record<string, ProcedureBuilder>` | Procedure builders (optional) |
58
- | `config.migrations` | `Migration[]` | Migration list (optional) |
59
-
60
- Returns `DbContextDef<TTables, TViews, TProcedures>`.
61
-
62
- Note: `defineDbContext` automatically adds an internal `_Migration` system table to `tables` to track applied migrations.
63
-
64
- #### `createDbContext(def, executor, opt)`
65
-
66
- Creates a fully functional `DbContextInstance` from a `DbContextDef`.
67
-
68
- ```typescript
69
- import { createDbContext } from "@simplysm/orm-common";
70
-
71
- const db = createDbContext(MyDbDef, executor, { database: "mydb" });
72
-
73
- await db.connect(async () => {
74
- const users = await db.user().result();
75
- });
76
- ```
77
-
78
- | Parameter | Type | Description |
79
- |-----------|------|-------------|
80
- | `def` | `DbContextDef` | Schema definition from `defineDbContext()` |
81
- | `executor` | `DbContextExecutor` | Query executor (from `orm-node` or service client) |
82
- | `opt.database` | `string` | Default database name |
83
- | `opt.schema` | `string?` | Default schema name (MSSQL: `dbo`, PostgreSQL: `public`) |
84
-
85
- Returns `DbContextInstance<TDef>`.
86
-
87
- #### Type Definitions
88
-
89
- | Type | Description |
90
- |------|-------------|
91
- | `DbContextDef<TTables, TViews, TProcedures>` | DbContext definition (schema blueprint) |
92
- | `DbContextInstance<TDef>` | Full DbContext instance with queryable accessors and DDL methods |
93
- | `DbContextBase` | Core interface used internally (status, executeDefs, etc.) |
94
- | `DbContextConnectionMethods` | Interface for connection/transaction methods (`connect`, `connectWithoutTransaction`, `trans`) |
95
- | `DbContextDdlMethods` | Interface for all DDL methods and QueryDef generator methods |
96
- | `DbContextStatus` | `"ready" \| "connect" \| "transact"` — current connection status |
97
-
98
- #### `DbTransactionError`
99
-
100
- Database transaction error class. Wraps DBMS-native errors with a standardized `DbErrorCode`.
101
-
102
- ```typescript
103
- import { DbTransactionError, DbErrorCode } from "@simplysm/orm-common";
104
-
105
- try {
106
- await executor.rollbackTransaction();
107
- } catch (err) {
108
- if (err instanceof DbTransactionError) {
109
- if (err.code === DbErrorCode.NO_ACTIVE_TRANSACTION) {
110
- return; // Already rolled back, ignore
111
- }
112
- }
113
- throw err;
114
- }
115
- ```
116
-
117
- | `DbErrorCode` value | Description |
118
- |---------------------|-------------|
119
- | `NO_ACTIVE_TRANSACTION` | No active transaction (rollback when none exists) |
120
- | `TRANSACTION_ALREADY_STARTED` | Transaction already started |
121
- | `DEADLOCK` | Deadlock detected |
122
- | `LOCK_TIMEOUT` | Lock wait timeout |
123
-
124
- `DbTransactionError` properties:
125
-
126
- | Property | Type | Description |
127
- |----------|------|-------------|
128
- | `code` | `DbErrorCode` | Standardized error code |
129
- | `message` | `string` | Error message |
130
- | `originalError` | `unknown?` | Original DBMS error (for debugging) |
131
-
132
- ---
133
-
134
- ### DbContext Instance API
135
-
136
- The `DbContextInstance` returned by `createDbContext` exposes the following methods:
137
-
138
- #### Connection Management
139
-
140
- | Method | Description |
141
- |--------|-------------|
142
- | `connect(fn, isolationLevel?)` | Open connection → begin transaction → run callback → commit → close. Rolls back on error. |
143
- | `connectWithoutTransaction(fn)` | Open connection → run callback → close. No transaction. Use for DDL or read-only operations. |
144
- | `trans(fn, isolationLevel?)` | Start a transaction within an already-connected context. Use inside `connectWithoutTransaction` when partial transactions are needed. |
145
-
146
- ```typescript
147
- // Default: with transaction
148
- await db.connect(async () => {
149
- await db.user().insert([{ name: "Alice" }]);
150
- await db.post().insert([{ title: "Hello", userId: 1 }]);
151
- });
152
-
153
- // Without transaction (e.g., for DDL)
154
- await db.connectWithoutTransaction(async () => {
155
- await db.createTable(User);
156
- });
157
-
158
- // Nested transaction inside connectWithoutTransaction
159
- await db.connectWithoutTransaction(async () => {
160
- const report = await db.report().result();
161
- await db.trans(async () => {
162
- await db.log().insert([{ action: "read" }]);
163
- });
164
- });
165
- ```
166
-
167
- #### Queryable / Executable Accessors
168
-
169
- Each table/view/procedure defined in `defineDbContext` becomes an accessor method on the instance:
170
-
171
- ```typescript
172
- // Tables and views → returns () => Queryable
173
- db.user() // Queryable<UserData, typeof User>
174
- db.post() // Queryable<PostData, typeof Post>
175
-
176
- // Procedures → returns () => Executable
177
- db.getUserById() // Executable<Params, Returns>
178
- ```
179
-
180
- #### `initialize(options?)`
181
-
182
- Applies all pending migrations in order.
183
-
184
- ```typescript
185
- await db.connectWithoutTransaction(async () => {
186
- await db.initialize();
187
- });
188
- ```
189
-
190
- | Option | Type | Description |
191
- |--------|------|-------------|
192
- | `options.dbs` | `string[]?` | Restrict migration to specific databases |
193
- | `options.force` | `boolean?` | Re-apply all migrations even if already applied |
194
-
195
- #### DDL Methods
196
-
197
- All DDL methods are available on the `DbContextInstance`. DDL cannot be called inside a `connect()` (transact) context — use `connectWithoutTransaction` instead.
198
-
199
- | Method | Description |
200
- |--------|-------------|
201
- | `createTable(table)` | Create a table based on `TableBuilder` |
202
- | `dropTable(table)` | Drop a table |
203
- | `renameTable(table, newName)` | Rename a table |
204
- | `createView(view)` | Create a view |
205
- | `dropView(view)` | Drop a view |
206
- | `createProc(procedure)` | Create a stored procedure |
207
- | `dropProc(procedure)` | Drop a stored procedure |
208
- | `addColumn(table, columnName, column)` | Add a column |
209
- | `dropColumn(table, column)` | Drop a column |
210
- | `modifyColumn(table, columnName, column)` | Modify a column definition |
211
- | `renameColumn(table, column, newName)` | Rename a column |
212
- | `addPk(table, columns)` | Add a primary key |
213
- | `dropPk(table)` | Drop the primary key |
214
- | `addFk(table, relationName, relationDef)` | Add a foreign key |
215
- | `dropFk(table, relationName)` | Drop a foreign key |
216
- | `addIdx(table, indexBuilder)` | Add an index |
217
- | `dropIdx(table, columns)` | Drop an index |
218
- | `clearSchema(params)` | Drop all objects in a schema |
219
- | `schemaExists(database, schema?)` | Check if a schema/database exists |
220
- | `truncate(table)` | Truncate a table |
221
- | `switchFk(table, "on" \| "off")` | Toggle FK constraint checking |
222
-
223
- DDL `QueryDef` generator equivalents (return `QueryDef` without executing):
224
-
225
- | Method | Description |
226
- |--------|-------------|
227
- | `getCreateTableQueryDef(table)` | |
228
- | `getCreateViewQueryDef(view)` | |
229
- | `getCreateProcQueryDef(procedure)` | |
230
- | `getCreateObjectQueryDef(builder)` | Unified for table, view, or procedure |
231
- | `getDropTableQueryDef(table)` | |
232
- | `getRenameTableQueryDef(table, newName)` | |
233
- | `getDropViewQueryDef(view)` | |
234
- | `getDropProcQueryDef(procedure)` | |
235
- | `getAddColumnQueryDef(table, columnName, column)` | |
236
- | `getDropColumnQueryDef(table, column)` | |
237
- | `getModifyColumnQueryDef(table, columnName, column)` | |
238
- | `getRenameColumnQueryDef(table, column, newName)` | |
239
- | `getAddPkQueryDef(table, columns)` | |
240
- | `getDropPkQueryDef(table)` | |
241
- | `getAddFkQueryDef(table, relationName, relationDef)` | |
242
- | `getAddIdxQueryDef(table, indexBuilder)` | |
243
- | `getDropFkQueryDef(table, relationName)` | |
244
- | `getDropIdxQueryDef(table, columns)` | |
245
- | `getClearSchemaQueryDef(params)` | |
246
- | `getSchemaExistsQueryDef(database, schema?)` | |
247
- | `getTruncateQueryDef(table)` | |
248
- | `getSwitchFkQueryDef(table, switch_)` | |
249
-
250
- ---
15
+ | Source | Exports | Description | Test |
16
+ |--------|---------|-------------|------|
17
+ | `src/define-db-context.ts` | `defineDbContext` | Define a database context with tables, views, and migrations | `tests/db-context/define-db-context.spec.ts` |
18
+ | `src/create-db-context.ts` | `createDbContext` | Create a database context instance from a definition | `tests/db-context/create-db-context.spec.ts` |
19
+ | `src/types/db-context-def.ts` | `DbContextBase`, `DbContextStatus`, `DbContextDef`, `DbContextInstance`, `DbContextConnectionMethods`, `DbContextDdlMethods` | Database context type definitions and connection interfaces | `-` |
20
+ | `src/errors/db-transaction-error.ts` | `DbErrorCode`, `DbTransactionError` | Transaction error codes and error class for DB operations | `-` |
251
21
 
252
22
  ### Queryable / Executable
253
23
 
254
- #### `Queryable<TData, TFrom>`
255
-
256
- The main query builder class. All methods are immutable and return a new `Queryable`.
257
-
258
- ```typescript
259
- import { Queryable } from "@simplysm/orm-common";
260
- ```
261
-
262
- **Query building methods (return `Queryable`):**
263
-
264
- | Method | SQL | Notes |
265
- |--------|-----|-------|
266
- | `.select(fn)` | SELECT | Map columns to new shape |
267
- | `.distinct()` | DISTINCT | Remove duplicate rows |
268
- | `.lock()` | FOR UPDATE | Row-level exclusive lock (must be inside `connect`) |
269
- | `.top(count)` | TOP / LIMIT | First N rows |
270
- | `.limit(skip, take)` | OFFSET / FETCH | Pagination — requires prior `orderBy()` |
271
- | `.orderBy(fn, dir?)` | ORDER BY | Add sort; multiple calls chain in order |
272
- | `.where(predicate)` | WHERE | Add condition; multiple calls AND together |
273
- | `.search(fn, searchText)` | WHERE LIKE | Multi-column text search with special syntax |
274
- | `.groupBy(fn)` | GROUP BY | Group rows |
275
- | `.having(predicate)` | HAVING | Filter groups |
276
- | `.join(as, fwd)` | LEFT JOIN | 1:N relationship → result as array property |
277
- | `.joinSingle(as, fwd)` | LEFT JOIN | N:1 / 1:1 → result as single object property |
278
- | `.include(fn)` | LEFT JOIN | Auto-join using `TableBuilder` relation definitions |
279
- | `.wrap()` | Subquery | Wrap current query as subquery |
280
- | `.recursive(fwd)` | WITH RECURSIVE | Recursive CTE for hierarchical data |
281
-
282
- **Static method:**
283
-
284
- | Method | Description |
285
- |--------|-------------|
286
- | `Queryable.union(...queries)` | UNION of 2+ queryables (deduplication) |
287
-
288
- **Execution methods:**
289
-
290
- | Method | Returns | Description |
291
- |--------|---------|-------------|
292
- | `.result()` | `Promise<TData[]>` | Execute SELECT, return all rows |
293
- | `.single()` | `Promise<TData \| undefined>` | Return one row; throws if more than one |
294
- | `.first()` | `Promise<TData \| undefined>` | Return first row only |
295
- | `.count(fwd?)` | `Promise<number>` | Count rows; cannot use after `distinct()`/`groupBy()` without `wrap()` |
296
- | `.exists()` | `Promise<boolean>` | Check if any rows match |
297
- | `.insert(records, outputColumns?)` | `Promise<void \| Pick[]>` | INSERT; returns output if columns specified; auto-chunks at 1000 |
298
- | `.insertIfNotExists(record, outputColumns?)` | `Promise<void \| Pick>` | INSERT if WHERE condition not matched |
299
- | `.insertInto(targetTable, outputColumns?)` | `Promise<void \| Pick[]>` | INSERT INTO ... SELECT |
300
- | `.update(recordFwd, outputColumns?)` | `Promise<void \| Pick[]>` | UPDATE |
301
- | `.delete(outputColumns?)` | `Promise<void \| Pick[]>` | DELETE |
302
- | `.upsert(updateFwd, insertFwd?, outputColumns?)` | `Promise<void \| Pick[]>` | UPDATE or INSERT |
303
- | `.switchFk("on" \| "off")` | `Promise<void>` | Toggle FK for this table |
304
-
305
- **QueryDef generator methods (for custom execution):**
306
-
307
- | Method | Description |
308
- |--------|-------------|
309
- | `.getSelectQueryDef()` | Build SELECT `QueryDef` |
310
- | `.getInsertQueryDef(records, outputColumns?)` | Build INSERT `QueryDef` |
311
- | `.getInsertIfNotExistsQueryDef(record, outputColumns?)` | Build INSERT IF NOT EXISTS `QueryDef` |
312
- | `.getInsertIntoQueryDef(targetTable, outputColumns?)` | Build INSERT INTO SELECT `QueryDef` |
313
- | `.getUpdateQueryDef(recordFwd, outputColumns?)` | Build UPDATE `QueryDef` |
314
- | `.getDeleteQueryDef(outputColumns?)` | Build DELETE `QueryDef` |
315
- | `.getUpsertQueryDef(updateFwd, insertFwd, outputColumns?)` | Build UPSERT `QueryDef` |
316
- | `.getResultMeta(outputColumns?)` | Build `ResultMeta` for result parsing |
317
-
318
- **Examples:**
319
-
320
- ```typescript
321
- // SELECT with WHERE and ORDER BY
322
- const users = await db.user()
323
- .where((u) => [expr.eq(u.isActive, true)])
324
- .orderBy((u) => u.name)
325
- .result();
326
-
327
- // JOIN (1:N)
328
- const usersWithPosts = await db.user()
329
- .join("posts", (qr, u) =>
330
- qr.from(Post).where((p) => [expr.eq(p.userId, u.id)])
331
- )
332
- .result();
333
- // Result: { id, name, posts?: Post[] }
24
+ | Source | Exports | Description | Test |
25
+ |--------|---------|-------------|------|
26
+ | `src/exec/queryable.ts` | `Queryable`, `getMatchedPrimaryKeys`, `QueryableRecord`, `QueryableWriteRecord`, `NullableQueryableRecord`, `UnwrapQueryableRecord`, `PathProxy`, `queryable` | Core queryable builder for SELECT, JOIN, WHERE, and aggregation | `tests/select/`, `tests/dml/`, `tests/errors/` |
27
+ | `src/exec/executable.ts` | `Executable`, `executable` | Executable builder for INSERT, UPDATE, DELETE, and DDL operations | `tests/executable/basic.spec.ts` |
28
+ | `src/exec/search-parser.ts` | `ParsedSearchQuery`, `parseSearchQuery` | Parse search query strings into structured filter objects | `tests/exec/search-parser.spec.ts` |
334
29
 
335
- // INCLUDE (using relation definitions)
336
- const posts = await db.post()
337
- .include((p) => p.author)
338
- .result();
30
+ ### Expression
339
31
 
340
- // Pagination
341
- const page = await db.user()
342
- .orderBy((u) => u.id)
343
- .limit(0, 20)
344
- .result();
345
-
346
- // INSERT and get returned ID
347
- const [inserted] = await db.user().insert(
348
- [{ name: "Alice" }],
349
- ["id"],
350
- );
351
-
352
- // UPSERT
353
- await db.user()
354
- .where((u) => [expr.eq(u.email, "alice@example.com")])
355
- .upsert(
356
- () => ({ loginCount: expr.val("number", 1) }),
357
- (update) => ({ ...update, email: expr.val("string", "alice@example.com") }),
358
- );
359
-
360
- // Recursive CTE (hierarchical query)
361
- const tree = await db.employee()
362
- .where((e) => [expr.null(e.managerId)])
363
- .recursive((cte) =>
364
- cte.from(Employee)
365
- .where((e) => [expr.eq(e.managerId, e.self[0].id)])
366
- )
367
- .result();
368
- ```
369
-
370
- #### `Executable<TParams, TReturns>`
371
-
372
- Wrapper class for stored procedure execution.
373
-
374
- ```typescript
375
- import { Executable } from "@simplysm/orm-common";
376
- ```
377
-
378
- | Method | Returns | Description |
379
- |--------|---------|-------------|
380
- | `.execute(params)` | `Promise<InferColumnExprs<TReturns>[][]>` | Execute the stored procedure |
381
- | `.getExecProcQueryDef(params?)` | `QueryDef` | Build EXEC PROC `QueryDef` |
382
-
383
- ```typescript
384
- const result = await db.getUserById().execute({ userId: 1 });
385
- ```
386
-
387
- ---
32
+ | Source | Exports | Description | Test |
33
+ |--------|---------|-------------|------|
34
+ | `src/expr/expr.ts` | `SwitchExprBuilder`, `expr`, `toExpr` | Expression builder with switch/case and conversion helpers | `tests/expr/` |
35
+ | `src/expr/expr-unit.ts` | `ExprUnit`, `WhereExprUnit`, `ExprInput` | Expression unit types for building type-safe query expressions | `-` |
388
36
 
389
37
  ### Schema Builders
390
38
 
391
- #### `Table(name)` / `TableBuilder`
392
-
393
- Factory function and builder class for defining database tables.
394
-
395
- ```typescript
396
- import { Table } from "@simplysm/orm-common";
397
-
398
- const User = Table("User")
399
- .database("mydb")
400
- .columns((c) => ({
401
- id: c.bigint().autoIncrement(),
402
- name: c.varchar(100),
403
- email: c.varchar(200).nullable(),
404
- status: c.varchar(20).default("active"),
405
- createdAt: c.datetime(),
406
- }))
407
- .primaryKey("id")
408
- .indexes((i) => [
409
- i.index("email").unique(),
410
- i.index("name", "createdAt").orderBy("ASC", "DESC"),
411
- ])
412
- .relations((r) => ({
413
- posts: r.foreignKeyTarget(() => Post, "author"),
414
- profile: r.foreignKeyTarget(() => Profile, "user").single(),
415
- }));
416
- ```
417
-
418
- `TableBuilder` fluent methods:
419
-
420
- | Method | Description |
421
- |--------|-------------|
422
- | `.database(db)` | Set database name |
423
- | `.schema(schema)` | Set schema name (MSSQL/PostgreSQL) |
424
- | `.description(desc)` | Set table description (DDL comment) |
425
- | `.columns(fn)` | Define columns via column factory |
426
- | `.primaryKey(...columns)` | Set primary key (supports composite PK) |
427
- | `.indexes(fn)` | Define indexes via index factory |
428
- | `.relations(fn)` | Define relationships via relation factory |
429
-
430
- `TableBuilder` type inference properties:
431
-
432
- | Property | Description |
433
- |----------|-------------|
434
- | `$infer` | Full row type (columns + relations) |
435
- | `$inferColumns` | Columns-only type |
436
- | `$inferInsert` | INSERT type (required fields required, autoIncrement/nullable/default optional) |
437
- | `$inferUpdate` | UPDATE type (all fields optional) |
438
- | `$columns` | Raw `ColumnBuilderRecord` |
439
- | `$relations` | Raw `RelationBuilderRecord` |
440
-
441
- #### `View(name)` / `ViewBuilder`
442
-
443
- Factory function and builder class for defining database views.
444
-
445
- ```typescript
446
- import { View } from "@simplysm/orm-common";
447
-
448
- const ActiveUsers = View("ActiveUsers")
449
- .database("mydb")
450
- .query((db: MyDb) =>
451
- db.user()
452
- .where((u) => [expr.eq(u.status, "active")])
453
- .select((u) => ({ id: u.id, name: u.name }))
454
- );
455
- ```
456
-
457
- `ViewBuilder` fluent methods:
458
-
459
- | Method | Description |
460
- |--------|-------------|
461
- | `.database(db)` | Set database name |
462
- | `.schema(schema)` | Set schema name |
463
- | `.description(desc)` | Set view description |
464
- | `.query(viewFn)` | Define the view SELECT query; takes a `DbContextBase` and returns `Queryable` |
465
- | `.relations(fn)` | Define relationships (only `relationKey`/`relationKeyTarget`, not FK) |
466
-
467
- `ViewBuilder` type inference properties:
468
-
469
- | Property | Description |
470
- |----------|-------------|
471
- | `$infer` | View row type |
472
- | `$relations` | Raw `RelationBuilderRecord` |
473
-
474
- #### `Procedure(name)` / `ProcedureBuilder`
475
-
476
- Factory function and builder class for defining stored procedures.
477
-
478
- ```typescript
479
- import { Procedure } from "@simplysm/orm-common";
480
-
481
- const GetUserById = Procedure("GetUserById")
482
- .database("mydb")
483
- .params((c) => ({
484
- userId: c.bigint(),
485
- }))
486
- .returns((c) => ({
487
- id: c.bigint(),
488
- name: c.varchar(100),
489
- email: c.varchar(200).nullable(),
490
- }))
491
- .body("SELECT id, name, email FROM User WHERE id = userId");
492
- ```
493
-
494
- `ProcedureBuilder` fluent methods:
495
-
496
- | Method | Description |
497
- |--------|-------------|
498
- | `.database(db)` | Set database name |
499
- | `.schema(schema)` | Set schema name |
500
- | `.description(desc)` | Set procedure description |
501
- | `.params(fn)` | Define input parameters |
502
- | `.returns(fn)` | Define result columns |
503
- | `.body(sql)` | Set procedure body SQL (DBMS-specific syntax) |
504
-
505
- `ProcedureBuilder` type inference properties:
506
-
507
- | Property | Description |
508
- |----------|-------------|
509
- | `$params` | Raw parameter `ColumnBuilderRecord` |
510
- | `$returns` | Raw returns `ColumnBuilderRecord` |
511
-
512
- ### Column Types (`createColumnFactory`)
513
-
514
- Used inside `.columns()`, `.params()`, and `.returns()`, and also directly in migrations.
515
-
516
- ```typescript
517
- import { createColumnFactory } from "@simplysm/orm-common";
518
-
519
- const c = createColumnFactory();
520
- // Used in migrations:
521
- await db.addColumn({ database: "mydb", name: "User" }, "status", c.varchar(20).nullable());
522
- ```
523
-
524
- Available column type methods:
525
-
526
- | Method | SQL Type | TypeScript Type |
527
- |--------|----------|-----------------|
528
- | `c.int()` | INT | `number` |
529
- | `c.bigint()` | BIGINT | `number` |
530
- | `c.float()` | FLOAT | `number` |
531
- | `c.double()` | DOUBLE | `number` |
532
- | `c.decimal(precision, scale?)` | DECIMAL | `number` |
533
- | `c.varchar(length)` | VARCHAR | `string` |
534
- | `c.char(length)` | CHAR | `string` |
535
- | `c.text()` | TEXT | `string` |
536
- | `c.binary()` | LONGBLOB/VARBINARY(MAX)/BYTEA | `Bytes` (Uint8Array) |
537
- | `c.boolean()` | TINYINT(1)/BIT/BOOLEAN | `boolean` |
538
- | `c.datetime()` | DATETIME | `DateTime` |
539
- | `c.date()` | DATE | `DateOnly` |
540
- | `c.time()` | TIME | `Time` |
541
- | `c.uuid()` | BINARY(16)/UNIQUEIDENTIFIER/UUID | `Uuid` |
542
-
543
- `ColumnBuilder` modifier methods:
544
-
545
- | Method | Description |
546
- |--------|-------------|
547
- | `.autoIncrement()` | Auto-increment; excluded from INSERT required fields |
548
- | `.nullable()` | Allow NULL; adds `undefined` to the TypeScript type |
549
- | `.default(value)` | Default value; makes the field optional in INSERT |
550
- | `.description(desc)` | Column description (DDL comment) |
551
-
552
- ### Relation Builders
553
-
554
- Used inside `.relations()`:
555
-
556
- #### `r.foreignKey(columns, targetFn)` → `ForeignKeyBuilder`
557
-
558
- N:1 relationship with DB FK constraint.
559
-
560
- ```typescript
561
- const Post = Table("Post")
562
- .columns((c) => ({ id: c.bigint().autoIncrement(), authorId: c.bigint() }))
563
- .primaryKey("id")
564
- .relations((r) => ({
565
- author: r.foreignKey(["authorId"], () => User),
566
- }));
567
- ```
568
-
569
- #### `r.foreignKeyTarget(targetTableFn, relationName)` → `ForeignKeyTargetBuilder`
570
-
571
- 1:N reverse reference. Results in an array by default; call `.single()` for 1:1.
572
-
573
- ```typescript
574
- const User = Table("User")
575
- .columns((c) => ({ id: c.bigint().autoIncrement() }))
576
- .primaryKey("id")
577
- .relations((r) => ({
578
- posts: r.foreignKeyTarget(() => Post, "author"), // 1:N → Post[]
579
- profile: r.foreignKeyTarget(() => Profile, "user").single(), // 1:1 → Profile
580
- }));
581
- ```
582
-
583
- #### `r.relationKey(columns, targetFn)` → `RelationKeyBuilder`
584
-
585
- N:1 logical relationship without a DB FK constraint. Available for both tables and views.
586
-
587
- #### `r.relationKeyTarget(targetTableFn, relationName)` → `RelationKeyTargetBuilder`
588
-
589
- 1:N logical reverse reference without a DB FK constraint. Call `.single()` for 1:1.
590
-
591
- #### `createRelationFactory(ownerFn)`
592
-
593
- Creates a relation builder factory for the given owner table or view. Used internally by `TableBuilder.relations()` and `ViewBuilder.relations()`. Available for building custom schema utilities.
594
-
595
- Tables receive both FK methods (`foreignKey`, `foreignKeyTarget`) and logical relation methods (`relationKey`, `relationKeyTarget`). Views receive only logical relation methods.
596
-
597
- ```typescript
598
- import { createRelationFactory } from "@simplysm/orm-common";
599
-
600
- const r = createRelationFactory(() => Post);
601
- // r.foreignKey(...) — available (Post is a TableBuilder)
602
- // r.foreignKeyTarget(...) — available
603
- // r.relationKey(...) — available
604
- // r.relationKeyTarget(...) — available
605
- ```
606
-
607
- #### `IndexBuilder` / `createIndexFactory`
608
-
609
- Index builder, used inside `.indexes()`.
610
-
611
- ```typescript
612
- Table("User")
613
- .indexes((i) => [
614
- i.index("email").unique(),
615
- i.index("name").name("IX_User_Name"),
616
- i.index("createdAt").orderBy("DESC"),
617
- i.index("status", "createdAt").orderBy("ASC", "DESC"),
618
- ]);
619
- ```
620
-
621
- `IndexBuilder` methods:
622
-
623
- | Method | Description |
624
- |--------|-------------|
625
- | `.unique()` | Create a unique index |
626
- | `.name(name)` | Set index name |
627
- | `.orderBy(...dirs)` | Set sort direction per column (`"ASC"` or `"DESC"`) |
628
- | `.description(desc)` | Index description |
629
-
630
- ---
631
-
632
- ### SQL Expressions (`expr`)
633
-
634
- The `expr` object provides all SQL expression builders. All methods return `ExprUnit` (SELECT expressions) or `WhereExprUnit` (WHERE conditions).
635
-
636
- ```typescript
637
- import { expr } from "@simplysm/orm-common";
638
- ```
639
-
640
- #### Value / Column Creation
641
-
642
- | Method | Description |
643
- |--------|-------------|
644
- | `expr.val(dataType, value)` | Wrap a literal value as `ExprUnit` |
645
- | `expr.col(dataType, ...path)` | Create a column reference (internal use) |
646
- | `expr.raw(dataType)\`SQL ${interpolation}\`` | Raw SQL with interpolated `ExprUnit` values |
647
-
648
- ```typescript
649
- expr.val("string", "active")
650
- expr.val("number", 100)
651
- expr.val("DateOnly", DateOnly.today())
652
- expr.raw("number")`ARRAY_LENGTH(${u.tags}, 1)`
653
- ```
654
-
655
- #### Comparison Expressions (WHERE)
656
-
657
- | Method | SQL | Description |
658
- |--------|-----|-------------|
659
- | `expr.eq(source, target)` | `<=>` / IS NULL | NULL-safe equality |
660
- | `expr.gt(source, target)` | `>` | Greater than |
661
- | `expr.lt(source, target)` | `<` | Less than |
662
- | `expr.gte(source, target)` | `>=` | Greater than or equal |
663
- | `expr.lte(source, target)` | `<=` | Less than or equal |
664
- | `expr.between(source, from?, to?)` | `BETWEEN` | Range; `undefined` means unbounded |
665
- | `expr.null(source)` | `IS NULL` | Null check |
666
- | `expr.like(source, pattern)` | `LIKE` | Pattern matching (`%`, `_`) |
667
- | `expr.regexp(source, pattern)` | `REGEXP` | Regular expression matching |
668
- | `expr.in(source, values)` | `IN (...)` | Value list match |
669
- | `expr.inQuery(source, query)` | `IN (SELECT ...)` | Subquery match (single-column SELECT) |
670
- | `expr.exists(query)` | `EXISTS (...)` | Subquery existence check |
671
-
672
- ```typescript
673
- db.user()
674
- .where((u) => [
675
- expr.eq(u.status, "active"),
676
- expr.gte(u.age, 18),
677
- expr.between(u.score, 0, 100),
678
- expr.null(u.deletedAt),
679
- expr.in(u.role, ["admin", "manager"]),
680
- expr.like(u.name, "John%"),
681
- ])
682
- ```
683
-
684
- #### Logical Expressions (WHERE)
685
-
686
- | Method | SQL | Description |
687
- |--------|-----|-------------|
688
- | `expr.not(arg)` | `NOT (...)` | Negate a condition |
689
- | `expr.and(conditions)` | `(... AND ...)` | All conditions must be true |
690
- | `expr.or(conditions)` | `(... OR ...)` | At least one condition must be true |
691
-
692
- #### String Expressions
693
-
694
- | Method | SQL | Description |
695
- |--------|-----|-------------|
696
- | `expr.concat(...args)` | `CONCAT(...)` | Concatenate strings; NULL treated as empty string |
697
- | `expr.left(source, length)` | `LEFT(...)` | Extract N characters from left |
698
- | `expr.right(source, length)` | `RIGHT(...)` | Extract N characters from right |
699
- | `expr.trim(source)` | `TRIM(...)` | Remove surrounding whitespace |
700
- | `expr.padStart(source, length, fillString)` | `LPAD(...)` | Left-pad to target length |
701
- | `expr.replace(source, from, to)` | `REPLACE(...)` | Replace all occurrences |
702
- | `expr.upper(source)` | `UPPER(...)` | Uppercase |
703
- | `expr.lower(source)` | `LOWER(...)` | Lowercase |
704
- | `expr.length(source)` | `CHAR_LENGTH(...)` | Character length |
705
- | `expr.byteLength(source)` | `OCTET_LENGTH(...)` | Byte length (UTF-8: Korean = 3 bytes) |
706
- | `expr.substring(source, start, length?)` | `SUBSTRING(...)` | Extract substring (1-based index) |
707
- | `expr.indexOf(source, search)` | `LOCATE(...)` | Find position (1-based; 0 if not found) |
708
-
709
- #### Numeric Expressions
710
-
711
- | Method | SQL | Description |
712
- |--------|-----|-------------|
713
- | `expr.abs(source)` | `ABS(...)` | Absolute value |
714
- | `expr.round(source, digits)` | `ROUND(...)` | Round to N decimal places |
715
- | `expr.ceil(source)` | `CEILING(...)` | Round up |
716
- | `expr.floor(source)` | `FLOOR(...)` | Round down |
717
-
718
- #### Date Expressions
719
-
720
- | Method | SQL | Description |
721
- |--------|-----|-------------|
722
- | `expr.year(source)` | `YEAR(...)` | Extract year (4-digit) |
723
- | `expr.month(source)` | `MONTH(...)` | Extract month (1–12) |
724
- | `expr.day(source)` | `DAY(...)` | Extract day (1–31) |
725
- | `expr.hour(source)` | `HOUR(...)` | Extract hour (0–23) |
726
- | `expr.minute(source)` | `MINUTE(...)` | Extract minute (0–59) |
727
- | `expr.second(source)` | `SECOND(...)` | Extract second (0–59) |
728
- | `expr.isoWeek(source)` | `WEEK(..., 3)` | ISO 8601 week number (1–53, Mon start) |
729
- | `expr.isoWeekStartDate(source)` | — | Monday of the week containing the date |
730
- | `expr.isoYearMonth(source)` | — | First day of the month (YYYY-MM-01) |
731
- | `expr.dateDiff(separator, from, to)` | `DATEDIFF(...)` | Date difference in specified unit |
732
- | `expr.dateAdd(separator, source, value)` | `DATEADD(...)` | Add an interval to a date |
733
- | `expr.formatDate(source, format)` | `DATE_FORMAT(...)` | Format date as string (DBMS-specific format strings) |
734
-
735
- `DateSeparator` values: `"year"`, `"month"`, `"day"`, `"hour"`, `"minute"`, `"second"`
736
-
737
- #### Conditional Expressions
738
-
739
- | Method | SQL | Description |
740
- |--------|-----|-------------|
741
- | `expr.ifNull(...args)` | `COALESCE(...)` | Return first non-null value |
742
- | `expr.nullIf(source, value)` | `NULLIF(...)` | Return NULL if source equals value |
743
- | `expr.is(condition)` | — | Convert WHERE expression to boolean `ExprUnit` |
744
- | `expr.if(condition, then, else_)` | `IF(...)` / `CASE` | Ternary conditional |
745
- | `expr.switch<T>().case(...).default(...)` | `CASE WHEN ... END` | Multi-branch conditional |
746
-
747
- ```typescript
748
- // COALESCE
749
- expr.ifNull(u.nickname, u.name, "Guest")
750
-
751
- // Ternary
752
- expr.if(expr.gte(u.age, 18), "adult", "minor")
753
-
754
- // CASE WHEN
755
- expr.switch<string>()
756
- .case(expr.gte(u.score, 90), "A")
757
- .case(expr.gte(u.score, 80), "B")
758
- .default("F")
759
-
760
- // Boolean from condition
761
- db.user().select((u) => ({
762
- isActive: expr.is(expr.eq(u.status, "active")),
763
- }))
764
- ```
765
-
766
- #### Aggregate Expressions
767
-
768
- | Method | SQL | Description |
769
- |--------|-----|-------------|
770
- | `expr.count(arg?, distinct?)` | `COUNT(...)` | Row count; omit arg for `COUNT(*)` |
771
- | `expr.sum(arg)` | `SUM(...)` | Sum; NULL if all values are NULL |
772
- | `expr.avg(arg)` | `AVG(...)` | Average; NULL if all values are NULL |
773
- | `expr.max(arg)` | `MAX(...)` | Maximum; NULL if all values are NULL |
774
- | `expr.min(arg)` | `MIN(...)` | Minimum; NULL if all values are NULL |
775
- | `expr.greatest(...args)` | `GREATEST(...)` | Largest among multiple values |
776
- | `expr.least(...args)` | `LEAST(...)` | Smallest among multiple values |
777
-
778
- #### Window Functions
779
-
780
- All window functions accept a `WinSpecInput`:
781
-
782
- ```typescript
783
- interface WinSpecInput {
784
- partitionBy?: ExprInput<ColumnPrimitive>[];
785
- orderBy?: [ExprInput<ColumnPrimitive>, ("ASC" | "DESC")?][];
786
- }
787
- ```
788
-
789
- | Method | SQL | Description |
790
- |--------|-----|-------------|
791
- | `expr.rowNumber(spec)` | `ROW_NUMBER() OVER (...)` | Sequential row number within partition |
792
- | `expr.rank(spec)` | `RANK() OVER (...)` | Rank; ties get same rank, next skipped |
793
- | `expr.denseRank(spec)` | `DENSE_RANK() OVER (...)` | Dense rank; ties get same rank, no skipping |
794
- | `expr.ntile(n, spec)` | `NTILE(n) OVER (...)` | Split partition into N buckets |
795
- | `expr.lag(column, spec, options?)` | `LAG(...) OVER (...)` | Previous row value |
796
- | `expr.lead(column, spec, options?)` | `LEAD(...) OVER (...)` | Next row value |
797
- | `expr.firstValue(column, spec)` | `FIRST_VALUE(...) OVER (...)` | First value in window |
798
- | `expr.lastValue(column, spec)` | `LAST_VALUE(...) OVER (...)` | Last value in window |
799
- | `expr.sumOver(column, spec)` | `SUM(...) OVER (...)` | Cumulative/windowed sum |
800
- | `expr.avgOver(column, spec)` | `AVG(...) OVER (...)` | Windowed average |
801
- | `expr.countOver(spec, column?)` | `COUNT(*) OVER (...)` | Windowed row count |
802
- | `expr.minOver(column, spec)` | `MIN(...) OVER (...)` | Windowed minimum |
803
- | `expr.maxOver(column, spec)` | `MAX(...) OVER (...)` | Windowed maximum |
804
-
805
- ```typescript
806
- db.order().select((o) => ({
807
- ...o,
808
- rowNum: expr.rowNumber({
809
- partitionBy: [o.userId],
810
- orderBy: [[o.createdAt, "DESC"]],
811
- }),
812
- runningTotal: expr.sumOver(o.amount, {
813
- partitionBy: [o.userId],
814
- orderBy: [[o.createdAt, "ASC"]],
815
- }),
816
- }))
817
- ```
818
-
819
- #### Other Expressions
820
-
821
- | Method | SQL | Description |
822
- |--------|-----|-------------|
823
- | `expr.cast(source, targetType)` | `CAST(... AS ...)` | Type conversion |
824
- | `expr.subquery(dataType, queryable)` | `(SELECT ...)` | Scalar subquery in SELECT |
825
- | `expr.random()` | `RAND()` / `RANDOM()` | Random number 0–1 |
826
- | `expr.rowNum()` | — | Row number without window spec |
827
- | `expr.toExpr(value)` | — | Convert `ExprInput` to raw `Expr` AST |
828
-
829
- ```typescript
830
- // CAST
831
- expr.cast(o.id, { type: "varchar", length: 20 })
832
-
833
- // Scalar subquery
834
- expr.subquery(
835
- "number",
836
- db.post().where((p) => [expr.eq(p.userId, u.id)]).select(() => ({ cnt: expr.count() }))
837
- )
838
-
839
- // Random sort
840
- db.user().orderBy(() => expr.random()).limit(0, 10)
841
- ```
842
-
843
- ---
39
+ | Source | Exports | Description | Test |
40
+ |--------|---------|-------------|------|
41
+ | `src/schema/table-builder.ts` | `TableBuilder`, `Table` | Table schema builder with columns, indexes, and relations | `tests/ddl/table-builder.spec.ts` |
42
+ | `src/schema/view-builder.ts` | `ViewBuilder`, `View` | View schema builder for defining SQL views | `tests/ddl/view-builder.spec.ts` |
43
+ | `src/schema/procedure-builder.ts` | `ProcedureBuilder`, `Procedure` | Procedure schema builder for stored procedures | `tests/ddl/procedure-builder.spec.ts` |
44
+ | `src/schema/factory/column-builder.ts` | `ColumnBuilder`, `createColumnFactory`, `ColumnBuilderRecord`, `InferColumns`, `InferColumnExprs`, `RequiredInsertKeys`, `OptionalInsertKeys`, `InferInsertColumns`, `InferUpdateColumns`, `DataToColumnBuilderRecord` | Column definition builder with data types and constraints | `tests/ddl/column-builder.spec.ts` |
45
+ | `src/schema/factory/index-builder.ts` | `IndexBuilder`, `createIndexFactory` | Index definition builder for table indexes | `tests/ddl/index-builder.spec.ts` |
46
+ | `src/schema/factory/relation-builder.ts` | `ForeignKeyBuilder`, `ForeignKeyTargetBuilder`, `RelationKeyBuilder`, `RelationKeyTargetBuilder`, `createRelationFactory`, `RelationBuilderRecord`, `ExtractRelationTarget`, `ExtractRelationTargetResult`, `InferDeepRelations` | Foreign key and relation builder for table relationships | `tests/ddl/relation-builder.spec.ts` |
844
47
 
845
48
  ### Models
846
49
 
847
- #### `_Migration` (internal)
848
-
849
- The internal system table used by `initialize()` to track applied migrations. It is automatically added to every `DbContextDef` by `defineDbContext` as `_migration`.
850
-
851
- ```typescript
852
- // Accessible via the db instance (read-only, internal use)
853
- const applied = await db._migration().result();
854
- // Returns: { code: string }[]
855
- ```
856
-
857
- ---
858
-
859
- ### Query Builder (SQL Generation)
860
-
861
- #### `createQueryBuilder(dialect)`
862
-
863
- Creates a dialect-specific `QueryBuilderBase` for converting `QueryDef` JSON AST to SQL strings.
864
-
865
- ```typescript
866
- import { createQueryBuilder } from "@simplysm/orm-common";
867
-
868
- const builder = createQueryBuilder("mysql"); // "mysql" | "mssql" | "postgresql"
869
- const { sql, resultSetIndex, resultSetStride } = builder.build(queryDef);
870
- ```
871
-
872
- The `build(def)` method accepts any `QueryDef` and returns `QueryBuildResult`:
873
-
874
- | Property | Type | Description |
875
- |----------|------|-------------|
876
- | `sql` | `string` | Generated SQL string |
877
- | `resultSetIndex` | `number?` | Which result set to extract (0-based) |
878
- | `resultSetStride` | `number?` | For multi-INSERT: extract every N-th result set |
879
-
880
- Available builders (exposed for extension):
881
-
882
- | Class | Description |
883
- |-------|-------------|
884
- | `QueryBuilderBase` | Abstract base class |
885
- | `ExprRendererBase` | Abstract expression renderer base |
886
- | `MysqlQueryBuilder` | MySQL implementation |
887
- | `MysqlExprRenderer` | MySQL expression renderer |
888
- | `MssqlQueryBuilder` | MSSQL implementation |
889
- | `MssqlExprRenderer` | MSSQL expression renderer |
890
- | `PostgresqlQueryBuilder` | PostgreSQL implementation |
891
- | `PostgresqlExprRenderer` | PostgreSQL expression renderer |
892
-
893
- ---
894
-
895
- ### Low-Level Utilities
896
-
897
- #### `queryable(db, tableOrView, as?)`
898
-
899
- Factory that returns a `() => Queryable` accessor. Used internally by `createDbContext`. Available for building custom db context wrappers.
900
-
901
- ```typescript
902
- import { queryable } from "@simplysm/orm-common";
903
-
904
- const getUserQueryable = queryable(db, User);
905
- const users = await getUserQueryable().where((u) => [expr.eq(u.isActive, true)]).result();
906
- ```
907
-
908
- #### `executable(db, procedureBuilder)`
909
-
910
- Factory that returns a `() => Executable` accessor. Used internally by `createDbContext`.
911
-
912
- ```typescript
913
- import { executable } from "@simplysm/orm-common";
914
-
915
- const getUser = executable(db, GetUserById);
916
- const result = await getUser().execute({ userId: 1 });
917
- ```
918
-
919
- #### `parseQueryResult(rawResults, meta)`
920
-
921
- Parses raw DB query results into typed TypeScript objects. Handles type coercion (e.g., `"1"` → `number`) and JOIN result grouping/nesting. Returns `undefined` for empty or all-null results.
922
-
923
- ```typescript
924
- import { parseQueryResult } from "@simplysm/orm-common";
925
-
926
- const meta = {
927
- columns: { id: "number", name: "string", createdAt: "DateTime" },
928
- joins: {},
929
- };
930
- const result = await parseQueryResult(rawResults, meta);
931
- // Returns TRecord[] | undefined
932
- ```
933
-
934
- #### `parseSearchQuery(searchText)`
935
-
936
- Parses a search query string into SQL LIKE patterns for use with `.search()`.
937
-
938
- ```typescript
939
- import { parseSearchQuery } from "@simplysm/orm-common";
940
-
941
- const parsed = parseSearchQuery('apple "exact phrase" +required -excluded app*');
942
- // {
943
- // or: ["%apple%", "app%"],
944
- // must: ["%exact phrase%", "%required%"],
945
- // not: ["%excluded%"]
946
- // }
947
- ```
948
-
949
- Search syntax:
950
-
951
- | Syntax | Meaning |
952
- |--------|---------|
953
- | `term` | OR: contains term |
954
- | `+term` | MUST: must contain term (AND) |
955
- | `-term` | NOT: must not contain term |
956
- | `"exact phrase"` | MUST: exact phrase match |
957
- | `term*` | Starts with (wildcard) |
958
- | `*term` | Ends with |
959
- | `\*`, `\+`, `\-`, `\%`, `\"`, `\\` | Escape special characters |
960
-
961
- Returns `ParsedSearchQuery`:
962
-
963
- | Property | Type | Description |
964
- |----------|------|-------------|
965
- | `or` | `string[]` | OR conditions (LIKE patterns) |
966
- | `must` | `string[]` | Required AND conditions (LIKE patterns) |
967
- | `not` | `string[]` | NOT conditions (LIKE patterns) |
968
-
969
- #### `getMatchedPrimaryKeys(fkCols, targetTable)`
970
-
971
- Matches FK column array against the target table's primary key and returns the PK column name array. Used internally by `include()` and custom JOIN logic.
972
-
973
- ```typescript
974
- import { getMatchedPrimaryKeys } from "@simplysm/orm-common";
975
-
976
- const pkCols = getMatchedPrimaryKeys(["userId"], User);
977
- // Returns: ["id"]
978
- ```
979
-
980
- ---
981
-
982
- ## Expression Types
983
-
984
- ### `ExprUnit<TPrimitive>`
985
-
986
- Type-safe expression wrapper. All `expr.*` SELECT/value methods return `ExprUnit`.
987
-
988
- ```typescript
989
- import { ExprUnit } from "@simplysm/orm-common";
990
- ```
991
-
992
- | Member | Type | Description |
993
- |--------|------|-------------|
994
- | `dataType` | `ColumnPrimitiveStr` | Runtime type name |
995
- | `expr` | `Expr` | Raw JSON AST |
996
- | `n` | `ExprUnit<NonNullable<TPrimitive>>` | Non-nullable accessor (strips `undefined`) |
997
-
998
- ### `WhereExprUnit`
999
-
1000
- Wrapper for WHERE condition expressions. All comparison and logical `expr.*` methods return `WhereExprUnit`.
1001
-
1002
- ```typescript
1003
- import { WhereExprUnit } from "@simplysm/orm-common";
1004
- ```
1005
-
1006
- ### `ExprInput<TPrimitive>`
1007
-
1008
- Union type that accepts either an `ExprUnit<TPrimitive>` or a plain literal value. Most `expr.*` parameters accept `ExprInput` so you can pass raw values without wrapping in `expr.val()`.
1009
-
1010
- ```typescript
1011
- type ExprInput<TPrimitive> = ExprUnit<TPrimitive> | TPrimitive;
1012
- ```
1013
-
1014
- ### `SwitchExprBuilder<TPrimitive>`
1015
-
1016
- Builder returned by `expr.switch()`:
1017
-
1018
- | Method | Description |
1019
- |--------|-------------|
1020
- | `.case(condition, then)` | Add WHEN ... THEN branch |
1021
- | `.default(value)` | Add ELSE and finalize to `ExprUnit` |
1022
-
1023
- ### `QueryableRecord<TData>`
1024
-
1025
- Maps a data record type to its expression counterpart. Each primitive field becomes `ExprUnit`, array relations become `QueryableRecord[]`, and nested objects become `QueryableRecord`.
1026
-
1027
- ### `NullableQueryableRecord<TData>`
1028
-
1029
- Like `QueryableRecord` but all primitive fields have `| undefined` added (representing LEFT JOIN NULL propagation).
1030
-
1031
- ### `QueryableWriteRecord<TData>`
1032
-
1033
- Write-side record type for `update()`/`upsert()` callbacks. Each field accepts `ExprInput<T>` (either `ExprUnit<T>` or a plain value).
1034
-
1035
- ### `UnwrapQueryableRecord<R>`
1036
-
1037
- Reverse mapping from a `QueryableRecord` shape back to a plain `DataRecord`. Used to infer the result type of `select()`.
1038
-
1039
- ### `PathProxy<TObject>`
1040
-
1041
- Type-safe path proxy used by `.include()`. Only non-primitive (relation) fields are accessible.
1042
-
1043
- ```typescript
1044
- // TypeScript will error if you try to access a primitive column
1045
- db.post().include((p) => p.author) // OK
1046
- db.post().include((p) => p.author.company) // OK (chained)
1047
- // db.post().include((p) => p.title) // Error: title is ColumnPrimitive
1048
- ```
1049
-
1050
- ### `toExpr(value)`
1051
-
1052
- Converts an `ExprInput` to a raw `Expr` JSON AST. Exposed for custom `QueryBuilder` extensions.
1053
-
1054
- ```typescript
1055
- import { toExpr } from "@simplysm/orm-common";
1056
- ```
1057
-
1058
- ---
1059
-
1060
- ## Type Reference
1061
-
1062
- ### Column Types
1063
-
1064
- | Type | Description |
1065
- |------|-------------|
1066
- | `ColumnPrimitive` | Union of all column-storable TypeScript types (`string \| number \| boolean \| DateTime \| DateOnly \| Time \| Uuid \| Bytes \| undefined`) |
1067
- | `ColumnPrimitiveStr` | String key of `ColumnPrimitiveMap` — `"string" \| "number" \| "boolean" \| "DateTime" \| "DateOnly" \| "Time" \| "Uuid" \| "Bytes"` |
1068
- | `ColumnPrimitiveMap` | Mapping from `ColumnPrimitiveStr` → TypeScript type |
1069
- | `DataType` | SQL data type discriminated union (`{ type: "int" }`, `{ type: "varchar"; length: number }`, etc.) |
1070
- | `ColumnMeta` | Column metadata stored inside `ColumnBuilder` |
1071
- | `InferColumnPrimitiveFromDataType<T>` | TypeScript type from a `DataType` |
1072
- | `dataTypeStrToColumnPrimitiveStr` | Constant object mapping SQL type name → `ColumnPrimitiveStr` |
1073
- | `inferColumnPrimitiveStr(value)` | Runtime function: infer `ColumnPrimitiveStr` from a value |
1074
- | `DateSeparator` | `"year" \| "month" \| "day" \| "hour" \| "minute" \| "second"` — date interval unit for `dateDiff`/`dateAdd` |
1075
-
1076
- #### SQL Type to TypeScript Type Mapping
1077
-
1078
- | SQL Type | TypeScript Type | Notes |
1079
- |----------|----------------|-------|
1080
- | `int`, `bigint`, `float`, `double`, `decimal` | `number` | `bigint` means SQL BIGINT (8 bytes), not JS `BigInt` |
1081
- | `varchar`, `char`, `text` | `string` | |
1082
- | `boolean` | `boolean` | MySQL: TINYINT(1), MSSQL: BIT |
1083
- | `datetime` | `DateTime` | From `@simplysm/core-common` |
1084
- | `date` | `DateOnly` | From `@simplysm/core-common` |
1085
- | `time` | `Time` | From `@simplysm/core-common` |
1086
- | `uuid` | `Uuid` | From `@simplysm/core-common` |
1087
- | `binary` | `Bytes` | `Uint8Array` |
1088
-
1089
- ### Database Types
1090
-
1091
- | Type/Const | Description |
1092
- |------------|-------------|
1093
- | `Dialect` | `"mysql" \| "mssql" \| "postgresql"` |
1094
- | `dialects` | `Dialect[]` constant — all supported dialects |
1095
- | `IsolationLevel` | `"READ_UNCOMMITTED" \| "READ_COMMITTED" \| "REPEATABLE_READ" \| "SERIALIZABLE"` |
1096
- | `DataRecord` | Recursive query result type: `{ [key: string]: ColumnPrimitive \| DataRecord \| DataRecord[] }` |
1097
- | `DbContextExecutor` | Interface that actual DB connectors implement |
1098
- | `ResultMeta` | Metadata for converting raw query results (`{ columns, joins }`) |
1099
- | `Migration` | `{ name: string; up: (db) => Promise<void> }` |
1100
- | `QueryBuildResult` | `{ sql: string; resultSetIndex?: number; resultSetStride?: number }` |
1101
-
1102
- ### Schema Builder Types
1103
-
1104
- | Type | Description |
1105
- |------|-------------|
1106
- | `ColumnBuilderRecord` | `Record<string, ColumnBuilder<...>>` |
1107
- | `RelationBuilderRecord` | Union record of all relation builder types |
1108
- | `InferColumns<T>` | Infer column value types from a `ColumnBuilderRecord` |
1109
- | `InferInsertColumns<T>` | INSERT type (required + optional fields) |
1110
- | `InferUpdateColumns<T>` | UPDATE type (all optional) |
1111
- | `InferColumnExprs<T>` | Expression input type from a `ColumnBuilderRecord` |
1112
- | `InferDeepRelations<T>` | Infer relation types (all optional) |
1113
- | `QueryableWriteRecord<TData>` | Write-side record type for update/upsert callbacks (accepts `ExprInput<T>`) |
1114
- | `PathProxy<TObject>` | Type-safe path proxy for `.include()` navigation |
1115
- | `DataToColumnBuilderRecord<TData>` | Convert `DataRecord` to `ColumnBuilderRecord` (for `insertInto` type checking) |
1116
- | `RequiredInsertKeys<T>` | Keys that are required in INSERT (no autoIncrement, nullable, or default) |
1117
- | `OptionalInsertKeys<T>` | Keys that are optional in INSERT |
1118
- | `ExtractRelationTarget<T>` | Extract the TypeScript type of an FK/RelationKey target |
1119
- | `ExtractRelationTargetResult<T>` | Extract the TypeScript type of an FKTarget/RelationKeyTarget (array or single) |
1120
-
1121
- ### QueryDef Types
1122
-
1123
- `QueryDef` is the JSON AST representation of any SQL statement. Every `Queryable` execution method generates a `QueryDef` that is then converted to SQL by `QueryBuilderBase`.
1124
-
1125
- #### `QueryDef`
1126
-
1127
- The master union type for all possible query definitions:
1128
-
1129
- ```typescript
1130
- import { QueryDef } from "@simplysm/orm-common";
1131
-
1132
- type QueryDef =
1133
- | SelectQueryDef | InsertQueryDef | InsertIfNotExistsQueryDef | InsertIntoQueryDef
1134
- | UpdateQueryDef | DeleteQueryDef | UpsertQueryDef
1135
- | ClearSchemaQueryDef | CreateTableQueryDef | DropTableQueryDef | RenameTableQueryDef
1136
- | TruncateQueryDef | AddColumnQueryDef | DropColumnQueryDef | ModifyColumnQueryDef
1137
- | RenameColumnQueryDef | DropPkQueryDef | AddPkQueryDef | AddFkQueryDef | DropFkQueryDef
1138
- | AddIdxQueryDef | DropIdxQueryDef | CreateViewQueryDef | DropViewQueryDef
1139
- | CreateProcQueryDef | DropProcQueryDef | ExecProcQueryDef | SwitchFkQueryDef
1140
- | SchemaExistsQueryDef;
1141
- ```
1142
-
1143
- #### `QueryDefObjectName`
1144
-
1145
- Represents a DBMS object identifier (table, view, procedure). DBMS-specific namespace rules:
1146
-
1147
- - MySQL: `database.name` (schema is ignored)
1148
- - MSSQL: `database.schema.name` (schema defaults to `dbo`)
1149
- - PostgreSQL: `schema.name` (database is connection-level)
1150
-
1151
- ```typescript
1152
- import { QueryDefObjectName } from "@simplysm/orm-common";
1153
-
1154
- interface QueryDefObjectName {
1155
- database?: string;
1156
- schema?: string;
1157
- name: string;
1158
- }
1159
- ```
1160
-
1161
- #### `CudOutputDef`
1162
-
1163
- Defines the OUTPUT clause for INSERT/UPDATE/DELETE operations that return values.
1164
-
1165
- ```typescript
1166
- import { CudOutputDef } from "@simplysm/orm-common";
1167
-
1168
- interface CudOutputDef {
1169
- columns: string[]; // Column names to return
1170
- pkColNames: string[]; // Primary key column names
1171
- aiColName?: string; // Auto-increment column name (if any)
1172
- }
1173
- ```
1174
-
1175
- #### DML QueryDef interfaces
1176
-
1177
- | Interface | `type` | Description |
1178
- |-----------|--------|-------------|
1179
- | `SelectQueryDef` | `"select"` | SELECT with optional FROM, WHERE, JOIN, ORDER BY, GROUP BY, HAVING, LIMIT, DISTINCT, recursive CTE |
1180
- | `SelectQueryDefJoin` | `"select"` | Extends `SelectQueryDef` with `isSingle?: boolean` for 1:1 JOIN distinction |
1181
- | `InsertQueryDef` | `"insert"` | INSERT with records array; optional `overrideIdentity` and `output` |
1182
- | `InsertIfNotExistsQueryDef` | `"insertIfNotExists"` | INSERT only if `existsSelectQuery` returns no rows |
1183
- | `InsertIntoQueryDef` | `"insertInto"` | INSERT INTO ... SELECT from `recordsSelectQuery` |
1184
- | `UpdateQueryDef` | `"update"` | UPDATE with record map, optional WHERE/JOIN/LIMIT |
1185
- | `DeleteQueryDef` | `"delete"` | DELETE with optional WHERE/JOIN/LIMIT |
1186
- | `UpsertQueryDef` | `"upsert"` | UPDATE or INSERT (MERGE pattern): `existsSelectQuery` determines branch |
1187
-
1188
- #### DDL QueryDef interfaces
1189
-
1190
- | Interface | `type` | Description |
1191
- |-----------|--------|-------------|
1192
- | `ClearSchemaQueryDef` | `"clearSchema"` | Drop all objects in a database/schema |
1193
- | `CreateTableQueryDef` | `"createTable"` | CREATE TABLE with column definitions and optional primary key |
1194
- | `DropTableQueryDef` | `"dropTable"` | DROP TABLE |
1195
- | `RenameTableQueryDef` | `"renameTable"` | RENAME TABLE to `newName` |
1196
- | `TruncateQueryDef` | `"truncate"` | TRUNCATE TABLE |
1197
- | `AddColumnQueryDef` | `"addColumn"` | ADD COLUMN with data type and optional constraints |
1198
- | `DropColumnQueryDef` | `"dropColumn"` | DROP COLUMN |
1199
- | `ModifyColumnQueryDef` | `"modifyColumn"` | MODIFY/ALTER COLUMN definition |
1200
- | `RenameColumnQueryDef` | `"renameColumn"` | RENAME COLUMN to `newName` |
1201
- | `DropPkQueryDef` | `"dropPk"` | DROP PRIMARY KEY |
1202
- | `AddPkQueryDef` | `"addPk"` | ADD PRIMARY KEY on given `columns` |
1203
- | `AddFkQueryDef` | `"addFk"` | ADD FOREIGN KEY with name, FK columns, target table and PK columns |
1204
- | `DropFkQueryDef` | `"dropFk"` | DROP FOREIGN KEY by name |
1205
- | `AddIdxQueryDef` | `"addIdx"` | CREATE INDEX with name, columns, order direction, and optional unique |
1206
- | `DropIdxQueryDef` | `"dropIdx"` | DROP INDEX by name |
1207
- | `CreateViewQueryDef` | `"createView"` | CREATE VIEW with a `SelectQueryDef` body |
1208
- | `DropViewQueryDef` | `"dropView"` | DROP VIEW |
1209
- | `CreateProcQueryDef` | `"createProc"` | CREATE PROCEDURE with params, returns columns, and body SQL string |
1210
- | `DropProcQueryDef` | `"dropProc"` | DROP PROCEDURE |
1211
- | `ExecProcQueryDef` | `"execProc"` | EXECUTE PROCEDURE with optional params record |
1212
-
1213
- #### Utility / Meta QueryDef interfaces
1214
-
1215
- | Interface | `type` | Description |
1216
- |-----------|--------|-------------|
1217
- | `SwitchFkQueryDef` | `"switchFk"` | Enable/disable FK constraint checking for a table (`"on"` or `"off"`) |
1218
- | `SchemaExistsQueryDef` | `"schemaExists"` | Check if a database/schema exists |
1219
-
1220
- #### `DDL_TYPES` / `DdlType`
1221
-
1222
- Constant array of all DDL operation type strings. Used internally to block DDL inside transactions and to validate whether a given `QueryDef` is a DDL operation. `SwitchFkQueryDef` is excluded because FK toggle is permitted inside transactions.
1223
-
1224
- ```typescript
1225
- import { DDL_TYPES, DdlType } from "@simplysm/orm-common";
1226
-
1227
- // DDL_TYPES: readonly string[] constant
1228
- // ["clearSchema", "createTable", "dropTable", ..., "dropProc"]
1229
-
1230
- // DdlType: union of all DDL type strings
1231
- type DdlType = "clearSchema" | "createTable" | "dropTable" | "renameTable" | "truncate"
1232
- | "addColumn" | "dropColumn" | "modifyColumn" | "renameColumn"
1233
- | "dropPk" | "addPk" | "addFk" | "dropFk" | "addIdx" | "dropIdx"
1234
- | "createView" | "dropView" | "createProc" | "dropProc";
1235
-
1236
- // Check if a QueryDef is DDL:
1237
- if (DDL_TYPES.includes(queryDef.type as DdlType)) {
1238
- // DDL — cannot run inside a transaction
1239
- }
1240
- ```
1241
-
1242
- ### Expr AST Types
1243
-
1244
- Low-level JSON AST interfaces that represent individual SQL expressions. These are the types stored in `QueryDef` fields such as `select`, `where`, `orderBy`, etc. Direct use is only necessary when extending `QueryBuilderBase` or `ExprRendererBase`.
1245
-
1246
- #### `Expr` and `WhereExpr` (union types)
1247
-
1248
- ```typescript
1249
- import { Expr, WhereExpr } from "@simplysm/orm-common";
1250
- ```
1251
-
1252
- | Union type | Description |
1253
- |------------|-------------|
1254
- | `Expr` | All value-producing expressions: column refs, literals, string/numeric/date/conditional/aggregate/window/subquery |
1255
- | `WhereExpr` | All boolean-producing expressions: comparison operators + logical operators (AND/OR/NOT) |
1256
-
1257
- #### Value expressions
1258
-
1259
- | Interface | `type` | Description |
1260
- |-----------|--------|-------------|
1261
- | `ExprColumn` | `"column"` | Column reference: `{ path: string[] }` — table alias + column name |
1262
- | `ExprValue` | `"value"` | Literal value: `{ value: ColumnPrimitive }` |
1263
- | `ExprRaw` | `"raw"` | Raw SQL template: `{ sql: string; params: Expr[] }` — params referenced as `{0}`, `{1}` |
1264
-
1265
- #### Comparison expressions (WHERE)
1266
-
1267
- | Interface | `type` | SQL |
1268
- |-----------|--------|-----|
1269
- | `ExprEq` | `"eq"` | NULL-safe `=` / `IS NULL` |
1270
- | `ExprGt` | `"gt"` | `>` |
1271
- | `ExprLt` | `"lt"` | `<` |
1272
- | `ExprGte` | `"gte"` | `>=` |
1273
- | `ExprLte` | `"lte"` | `<=` |
1274
- | `ExprBetween` | `"between"` | `BETWEEN from AND to` |
1275
- | `ExprIsNull` | `"null"` | `IS NULL` |
1276
- | `ExprLike` | `"like"` | `LIKE` |
1277
- | `ExprRegexp` | `"regexp"` | `REGEXP` |
1278
- | `ExprIn` | `"in"` | `IN (values)` |
1279
- | `ExprInQuery` | `"inQuery"` | `IN (subquery)` |
1280
- | `ExprExists` | `"exists"` | `EXISTS (subquery)` |
1281
-
1282
- #### Logical expressions (WHERE)
1283
-
1284
- | Interface | `type` | SQL |
1285
- |-----------|--------|-----|
1286
- | `ExprNot` | `"not"` | `NOT (...)` |
1287
- | `ExprAnd` | `"and"` | `(... AND ...)` |
1288
- | `ExprOr` | `"or"` | `(... OR ...)` |
1289
-
1290
- #### String expressions
1291
-
1292
- | Interface | `type` | SQL |
1293
- |-----------|--------|-----|
1294
- | `ExprConcat` | `"concat"` | `CONCAT(args...)` |
1295
- | `ExprLeft` | `"left"` | `LEFT(source, length)` |
1296
- | `ExprRight` | `"right"` | `RIGHT(source, length)` |
1297
- | `ExprTrim` | `"trim"` | `TRIM(arg)` |
1298
- | `ExprPadStart` | `"padStart"` | `LPAD(source, length, fillString)` |
1299
- | `ExprReplace` | `"replace"` | `REPLACE(source, from, to)` |
1300
- | `ExprUpper` | `"upper"` | `UPPER(arg)` |
1301
- | `ExprLower` | `"lower"` | `LOWER(arg)` |
1302
- | `ExprLength` | `"length"` | `CHAR_LENGTH(arg)` |
1303
- | `ExprByteLength` | `"byteLength"` | `OCTET_LENGTH(arg)` |
1304
- | `ExprSubstring` | `"substring"` | `SUBSTRING(source, start, length?)` |
1305
- | `ExprIndexOf` | `"indexOf"` | `LOCATE(source, search)` |
1306
-
1307
- #### Numeric expressions
1308
-
1309
- | Interface | `type` | SQL |
1310
- |-----------|--------|-----|
1311
- | `ExprAbs` | `"abs"` | `ABS(arg)` |
1312
- | `ExprRound` | `"round"` | `ROUND(arg, digits)` |
1313
- | `ExprCeil` | `"ceil"` | `CEILING(arg)` |
1314
- | `ExprFloor` | `"floor"` | `FLOOR(arg)` |
1315
-
1316
- #### Date expressions
1317
-
1318
- | Interface | `type` | SQL |
1319
- |-----------|--------|-----|
1320
- | `ExprYear` | `"year"` | `YEAR(arg)` |
1321
- | `ExprMonth` | `"month"` | `MONTH(arg)` |
1322
- | `ExprDay` | `"day"` | `DAY(arg)` |
1323
- | `ExprHour` | `"hour"` | `HOUR(arg)` |
1324
- | `ExprMinute` | `"minute"` | `MINUTE(arg)` |
1325
- | `ExprSecond` | `"second"` | `SECOND(arg)` |
1326
- | `ExprIsoWeek` | `"isoWeek"` | `WEEK(arg, 3)` |
1327
- | `ExprIsoWeekStartDate` | `"isoWeekStartDate"` | Monday of the ISO week |
1328
- | `ExprIsoYearMonth` | `"isoYearMonth"` | First day of month (YYYY-MM-01) |
1329
- | `ExprDateDiff` | `"dateDiff"` | `DATEDIFF(separator, from, to)` |
1330
- | `ExprDateAdd` | `"dateAdd"` | `DATEADD(separator, source, value)` |
1331
- | `ExprFormatDate` | `"formatDate"` | `DATE_FORMAT(source, format)` |
1332
-
1333
- #### Conditional expressions
1334
-
1335
- | Interface | `type` | SQL |
1336
- |-----------|--------|-----|
1337
- | `ExprIfNull` | `"ifNull"` | `COALESCE(args...)` |
1338
- | `ExprNullIf` | `"nullIf"` | `NULLIF(source, value)` |
1339
- | `ExprIs` | `"is"` | Convert boolean condition to 0/1 value |
1340
- | `ExprSwitch` | `"switch"` | `CASE WHEN ... THEN ... ELSE ... END` |
1341
- | `ExprIf` | `"if"` | `IF(condition, then, else)` |
1342
-
1343
- #### Aggregate expressions
1344
-
1345
- | Interface | `type` | SQL |
1346
- |-----------|--------|-----|
1347
- | `ExprCount` | `"count"` | `COUNT(*)` or `COUNT(arg)` or `COUNT(DISTINCT arg)` |
1348
- | `ExprSum` | `"sum"` | `SUM(arg)` |
1349
- | `ExprAvg` | `"avg"` | `AVG(arg)` |
1350
- | `ExprMax` | `"max"` | `MAX(arg)` |
1351
- | `ExprMin` | `"min"` | `MIN(arg)` |
1352
-
1353
- #### Other expressions
1354
-
1355
- | Interface | `type` | SQL |
1356
- |-----------|--------|-----|
1357
- | `ExprGreatest` | `"greatest"` | `GREATEST(args...)` |
1358
- | `ExprLeast` | `"least"` | `LEAST(args...)` |
1359
- | `ExprRowNum` | `"rowNum"` | Row number (simple, no window spec) |
1360
- | `ExprRandom` | `"random"` | `RAND()` / `RANDOM()` |
1361
- | `ExprCast` | `"cast"` | `CAST(source AS targetType)` |
1362
- | `ExprSubquery` | `"subquery"` | Scalar subquery `(SELECT ...)` |
1363
-
1364
- #### Window function types
1365
-
1366
- `ExprWindow` is the AST node for any window function expression. It combines a `WinFn` (the function) with a `WinSpec` (the OVER clause).
1367
-
1368
- ```typescript
1369
- import { ExprWindow, WinFn, WinSpec } from "@simplysm/orm-common";
1370
-
1371
- interface ExprWindow {
1372
- type: "window";
1373
- fn: WinFn; // The window function
1374
- spec: WinSpec; // OVER (PARTITION BY ... ORDER BY ...)
1375
- }
1376
-
1377
- interface WinSpec {
1378
- partitionBy?: Expr[];
1379
- orderBy?: [Expr, ("ASC" | "DESC")?][];
1380
- }
1381
-
1382
- type WinFn =
1383
- | WinFnRowNumber | WinFnRank | WinFnDenseRank | WinFnNtile
1384
- | WinFnLag | WinFnLead | WinFnFirstValue | WinFnLastValue
1385
- | WinFnSum | WinFnAvg | WinFnCount | WinFnMin | WinFnMax;
1386
- ```
1387
-
1388
- Individual `WinFn` interfaces:
1389
-
1390
- | Interface | `type` | SQL | Notes |
1391
- |-----------|--------|-----|-------|
1392
- | `WinFnRowNumber` | `"rowNumber"` | `ROW_NUMBER()` | |
1393
- | `WinFnRank` | `"rank"` | `RANK()` | |
1394
- | `WinFnDenseRank` | `"denseRank"` | `DENSE_RANK()` | |
1395
- | `WinFnNtile` | `"ntile"` | `NTILE(n)` | Has `n: number` property |
1396
- | `WinFnLag` | `"lag"` | `LAG(column, offset?, default?)` | |
1397
- | `WinFnLead` | `"lead"` | `LEAD(column, offset?, default?)` | |
1398
- | `WinFnFirstValue` | `"firstValue"` | `FIRST_VALUE(column)` | |
1399
- | `WinFnLastValue` | `"lastValue"` | `LAST_VALUE(column)` | |
1400
- | `WinFnSum` | `"sum"` | `SUM(column) OVER (...)` | |
1401
- | `WinFnAvg` | `"avg"` | `AVG(column) OVER (...)` | |
1402
- | `WinFnCount` | `"count"` | `COUNT(*) OVER (...)` | `column` is optional |
1403
- | `WinFnMin` | `"min"` | `MIN(column) OVER (...)` | |
1404
- | `WinFnMax` | `"max"` | `MAX(column) OVER (...)` | |
1405
-
1406
- ---
1407
-
1408
- ## Quick Start
1409
-
1410
- ```typescript
1411
- import { Table, defineDbContext, createDbContext, expr, DateTime } from "@simplysm/orm-common";
1412
-
1413
- // Define table schema
1414
- const User = Table("User")
1415
- .database("mydb")
1416
- .columns((c) => ({
1417
- id: c.bigint().autoIncrement(),
1418
- name: c.varchar(100),
1419
- email: c.varchar(200).nullable(),
1420
- createdAt: c.datetime(),
1421
- }))
1422
- .primaryKey("id");
1423
-
1424
- // Define DbContext
1425
- const MyDbDef = defineDbContext({
1426
- tables: { user: User },
1427
- });
1428
-
1429
- // Create DbContext instance with executor (from orm-node package)
1430
- const db = createDbContext(MyDbDef, executor, { database: "mydb" });
1431
-
1432
- // Execute queries
1433
- await db.connect(async () => {
1434
- // INSERT
1435
- await db.user().insert([
1436
- { name: "John", createdAt: DateTime.now() }
1437
- ]);
1438
-
1439
- // SELECT
1440
- const users = await db.user()
1441
- .where((u) => [expr.eq(u.email, "john@example.com")])
1442
- .result();
1443
-
1444
- // UPDATE
1445
- await db.user()
1446
- .where((u) => [expr.eq(u.id, 1)])
1447
- .update(() => ({ name: "Jane" }));
1448
- });
1449
- ```
1450
-
1451
- ## Security Notes
1452
-
1453
- orm-common uses **enhanced string escaping** instead of parameter binding due to its dynamic query nature.
1454
- Always perform input validation at the application level.
1455
-
1456
- ```typescript
1457
- // Bad: Direct user input
1458
- const userInput = req.query.name; // e.g. malicious SQL payload
1459
- await db.user().where((u) => [expr.eq(u.name, userInput)]).result();
1460
-
1461
- // Good: Validate before use
1462
- const userName = validateUserName(req.query.name);
1463
- await db.user().where((u) => [expr.eq(u.name, userName)]).result();
1464
-
1465
- // Better: Type coercion
1466
- const userId = Number(req.query.id);
1467
- if (Number.isNaN(userId)) throw new Error("Invalid ID");
1468
- await db.user().where((u) => [expr.eq(u.id, userId)]).result();
1469
- ```
50
+ | Source | Exports | Description | Test |
51
+ |--------|---------|-------------|------|
52
+ | `src/models/system-migration.ts` | `_Migration` | Built-in migration tracking table model | `-` |
53
+
54
+ ### Query Builder
55
+
56
+ | Source | Exports | Description | Test |
57
+ |--------|---------|-------------|------|
58
+ | `src/query-builder/query-builder.ts` | `createQueryBuilder` | Factory to create dialect-specific query builders | `-` |
59
+ | `src/query-builder/base/query-builder-base.ts` | `QueryBuilderBase` | Abstract base class for SQL query generation | `-` |
60
+ | `src/query-builder/base/expr-renderer-base.ts` | `ExprRendererBase` | Abstract base class for SQL expression rendering | `-` |
61
+ | `src/query-builder/mysql/mysql-query-builder.ts` | `MysqlQueryBuilder` | MySQL-specific SQL query builder | `-` |
62
+ | `src/query-builder/mysql/mysql-expr-renderer.ts` | `MysqlExprRenderer` | MySQL-specific expression renderer | `-` |
63
+ | `src/query-builder/mssql/mssql-query-builder.ts` | `MssqlQueryBuilder` | MSSQL-specific SQL query builder | `-` |
64
+ | `src/query-builder/mssql/mssql-expr-renderer.ts` | `MssqlExprRenderer` | MSSQL-specific expression renderer | `-` |
65
+ | `src/query-builder/postgresql/postgresql-query-builder.ts` | `PostgresqlQueryBuilder` | PostgreSQL-specific SQL query builder | `-` |
66
+ | `src/query-builder/postgresql/postgresql-expr-renderer.ts` | `PostgresqlExprRenderer` | PostgreSQL-specific expression renderer | `-` |
67
+
68
+ ### Types
69
+
70
+ | Source | Exports | Description | Test |
71
+ |--------|---------|-------------|------|
72
+ | `src/types/db.ts` | `Dialect`, `dialects`, `QueryBuildResult`, `IsolationLevel`, `DataRecord`, `DbContextExecutor`, `ResultMeta`, `Migration` | Core database types (Dialect, IsolationLevel, QueryBuildResult, Migration) | `-` |
73
+ | `src/utils/result-parser.ts` | `parseQueryResult` | Parse raw DB query results into typed JavaScript objects | `tests/utils/result-parser.spec.ts` |
74
+ | `src/types/column.ts` | `DataType`, `ColumnPrimitiveMap`, `ColumnPrimitiveStr`, `ColumnPrimitive`, `dataTypeStrToColumnPrimitiveStr`, `InferColumnPrimitiveFromDataType`, `inferColumnPrimitiveStr`, `ColumnMeta` | Column data type definitions and primitive type mapping | `-` |
75
+ | `src/types/expr.ts` | `DateSeparator`, `ExprColumn`, `ExprValue`, `ExprRaw`, `ExprEq`, `ExprGt`, `ExprLt`, `ExprGte`, `ExprLte`, `ExprBetween`, `ExprIsNull`, `ExprLike`, `ExprRegexp`, `ExprIn`, `ExprInQuery`, `ExprExists`, `ExprNot`, `ExprAnd`, `ExprOr`, `ExprConcat`, `ExprLeft`, `ExprRight`, `ExprTrim`, `ExprPadStart`, `ExprReplace`, `ExprUpper`, `ExprLower`, `ExprLength`, `ExprByteLength`, `ExprSubstring`, `ExprIndexOf`, `ExprAbs`, `ExprRound`, `ExprCeil`, `ExprFloor`, `ExprYear`, `ExprMonth`, `ExprDay`, `ExprHour`, `ExprMinute`, `ExprSecond`, `ExprIsoWeek`, `ExprIsoWeekStartDate`, `ExprIsoYearMonth`, `ExprDateDiff`, `ExprDateAdd`, `ExprFormatDate`, `ExprIfNull`, `ExprNullIf`, `ExprIs`, `ExprSwitch`, `ExprIf`, `ExprCount`, `ExprSum`, `ExprAvg`, `ExprMax`, `ExprMin`, `ExprGreatest`, `ExprLeast`, `ExprRowNum`, `ExprRandom`, `ExprCast`, `WinFnRowNumber`, `WinFnRank`, `WinFnDenseRank`, `WinFnNtile`, `WinFnLag`, `WinFnLead`, `WinFnFirstValue`, `WinFnLastValue`, `WinFnSum`, `WinFnAvg`, `WinFnCount`, `WinFnMin`, `WinFnMax`, `WinFn`, `WinSpec`, `ExprWindow`, `ExprSubquery`, `WhereExpr`, `Expr` | All expression AST node types for the query builder | `-` |
76
+ | `src/types/query-def.ts` | `QueryDefObjectName`, `CudOutputDef`, `SelectQueryDef`, `SelectQueryDefJoin`, `InsertQueryDef`, `InsertIfNotExistsQueryDef`, `InsertIntoQueryDef`, `UpdateQueryDef`, `DeleteQueryDef`, `UpsertQueryDef`, `SwitchFkQueryDef`, `ClearSchemaQueryDef`, `CreateTableQueryDef`, `DropTableQueryDef`, `RenameTableQueryDef`, `TruncateQueryDef`, `AddColumnQueryDef`, `DropColumnQueryDef`, `ModifyColumnQueryDef`, `RenameColumnQueryDef`, `DropPkQueryDef`, `AddPkQueryDef`, `AddFkQueryDef`, `DropFkQueryDef`, `AddIdxQueryDef`, `DropIdxQueryDef`, `CreateViewQueryDef`, `DropViewQueryDef`, `CreateProcQueryDef`, `DropProcQueryDef`, `ExecProcQueryDef`, `SchemaExistsQueryDef`, `DDL_TYPES`, `DdlType`, `QueryDef` | Query definition types for all SQL statement kinds (SELECT, INSERT, DDL, etc.) | `-` |
1470
77
 
1471
78
  ## License
1472
79