@simplysm/orm-common 13.0.85 → 13.0.87

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.
@@ -1,238 +0,0 @@
1
- # DbContext
2
-
3
- Central entry point for database operations. Combines schema definitions with an executor for connection management, transaction handling, DDL operations, and schema initialization.
4
-
5
- ## API Reference
6
-
7
- ### `defineDbContext(config)`
8
-
9
- Creates a `DbContextDef` (blueprint) containing schema metadata but no runtime state.
10
-
11
- ```typescript
12
- function defineDbContext<TTables, TViews, TProcedures>(config: {
13
- tables?: TTables;
14
- views?: TViews;
15
- procedures?: TProcedures;
16
- migrations?: Migration[];
17
- }): DbContextDef<TTables & { _migration: typeof _Migration }, TViews, TProcedures>
18
- ```
19
-
20
- A `_migration` system table is automatically added to track applied migrations.
21
-
22
- ---
23
-
24
- ### `createDbContext(def, executor, opt)`
25
-
26
- Creates a complete `DbContextInstance` from a definition and executor.
27
-
28
- ```typescript
29
- function createDbContext<TDef extends DbContextDef<any, any, any>>(
30
- def: TDef,
31
- executor: DbContextExecutor,
32
- opt: { database: string; schema?: string },
33
- ): DbContextInstance<TDef>
34
- ```
35
-
36
- **Parameters:**
37
- - `def` -- Definition created by `defineDbContext()`
38
- - `executor` -- Query executor implementation (e.g., `NodeDbContextExecutor`, `ServiceDbContextExecutor`)
39
- - `opt.database` -- Database name
40
- - `opt.schema` -- Schema name (MSSQL: `dbo`, PostgreSQL: `public`)
41
-
42
- The returned instance automatically maps:
43
- - Each table key to a `() => Queryable` accessor
44
- - Each view key to a `() => Queryable` accessor
45
- - Each procedure key to a `() => Executable` accessor
46
-
47
- ---
48
-
49
- ### Connection Management
50
-
51
- #### `db.connect(fn, isolationLevel?)`
52
-
53
- Execute callback within a managed connection and transaction. Automatically commits on success or rolls back on error.
54
-
55
- ```typescript
56
- connect<TResult>(
57
- fn: () => Promise<TResult>,
58
- isolationLevel?: IsolationLevel
59
- ): Promise<TResult>
60
- ```
61
-
62
- #### `db.connectWithoutTransaction(fn)`
63
-
64
- Connect without a transaction. Used for DDL operations or read-only operations.
65
-
66
- ```typescript
67
- connectWithoutTransaction<TResult>(
68
- fn: () => Promise<TResult>
69
- ): Promise<TResult>
70
- ```
71
-
72
- #### `db.transaction(fn, isolationLevel?)`
73
-
74
- Start a nested transaction within an already connected state (`connectWithoutTransaction`).
75
-
76
- ```typescript
77
- transaction<TResult>(
78
- fn: () => Promise<TResult>,
79
- isolationLevel?: IsolationLevel
80
- ): Promise<TResult>
81
- ```
82
-
83
- **IsolationLevel values:** `"READ_UNCOMMITTED"` | `"READ_COMMITTED"` | `"REPEATABLE_READ"` | `"SERIALIZABLE"`
84
-
85
- **DbContextStatus lifecycle:** `"ready"` -> `"connect"` -> `"transact"` -> `"connect"` -> `"ready"`
86
-
87
- ---
88
-
89
- ### DDL Methods
90
-
91
- All DDL methods are available on the `DbContextInstance`. They cannot be executed inside a transaction.
92
-
93
- #### Table Operations
94
-
95
- | Method | Description |
96
- |--------|-------------|
97
- | `createTable(table)` | CREATE TABLE from a TableBuilder |
98
- | `dropTable(table)` | DROP TABLE |
99
- | `renameTable(table, newName)` | RENAME TABLE |
100
- | `createView(view)` | CREATE VIEW from a ViewBuilder |
101
- | `dropView(view)` | DROP VIEW |
102
- | `createProc(procedure)` | CREATE PROCEDURE from a ProcedureBuilder |
103
- | `dropProc(procedure)` | DROP PROCEDURE |
104
-
105
- #### Column Operations
106
-
107
- | Method | Description |
108
- |--------|-------------|
109
- | `addColumn(table, name, column)` | ADD COLUMN |
110
- | `dropColumn(table, column)` | DROP COLUMN |
111
- | `modifyColumn(table, name, column)` | MODIFY COLUMN (type/properties) |
112
- | `renameColumn(table, column, newName)` | RENAME COLUMN |
113
-
114
- #### Constraint Operations
115
-
116
- | Method | Description |
117
- |--------|-------------|
118
- | `addPrimaryKey(table, columns)` | ADD PRIMARY KEY |
119
- | `dropPrimaryKey(table)` | DROP PRIMARY KEY |
120
- | `addForeignKey(table, name, fkBuilder)` | ADD FOREIGN KEY |
121
- | `dropForeignKey(table, name)` | DROP FOREIGN KEY |
122
- | `addIndex(table, indexBuilder)` | CREATE INDEX |
123
- | `dropIndex(table, columns)` | DROP INDEX |
124
-
125
- #### Schema Operations
126
-
127
- | Method | Description |
128
- |--------|-------------|
129
- | `clearSchema(params)` | Drop all objects in a schema |
130
- | `schemaExists(database, schema?)` | Check if schema/database exists |
131
- | `truncate(table)` | TRUNCATE TABLE |
132
- | `switchFk(table, enabled)` | Enable/disable FK constraints |
133
-
134
- Each DDL method also has a corresponding `get*QueryDef()` method that returns the raw `QueryDef` without executing it.
135
-
136
- ---
137
-
138
- ### `db.initialize(options?)`
139
-
140
- Run all pending migrations and create missing tables/views/procedures.
141
-
142
- ```typescript
143
- initialize(options?: {
144
- dbs?: string[]; // Limit to specific databases
145
- force?: boolean; // Force re-create (drop and recreate)
146
- }): Promise<void>
147
- ```
148
-
149
- ---
150
-
151
- ### DbContextExecutor Interface
152
-
153
- Implement this interface to connect the ORM to a specific database driver.
154
-
155
- ```typescript
156
- interface DbContextExecutor {
157
- connect(): Promise<void>;
158
- close(): Promise<void>;
159
- beginTransaction(isolationLevel?: IsolationLevel): Promise<void>;
160
- commitTransaction(): Promise<void>;
161
- rollbackTransaction(): Promise<void>;
162
- executeDefs<T = DataRecord>(
163
- defs: QueryDef[],
164
- resultMetas?: (ResultMeta | undefined)[],
165
- ): Promise<T[][]>;
166
- }
167
- ```
168
-
169
- ---
170
-
171
- ### Migration
172
-
173
- Define schema migrations for version control.
174
-
175
- ```typescript
176
- interface Migration {
177
- name: string;
178
- up: (db: DbContextBase & DbContextDdlMethods) => Promise<void>;
179
- }
180
- ```
181
-
182
- ---
183
-
184
- ## Usage Examples
185
-
186
- ### Basic Setup and Query
187
-
188
- ```typescript
189
- const MyDb = defineDbContext({
190
- tables: { user: User, post: Post },
191
- views: { activeUsers: ActiveUsers },
192
- procedures: { getUserById: GetUserById },
193
- migrations: [
194
- {
195
- name: "20260101_001_add_status_column",
196
- up: async (db) => {
197
- await db.addColumn(
198
- { database: "mydb", name: "User" },
199
- "status",
200
- createColumnFactory().varchar(20).default("active"),
201
- );
202
- },
203
- },
204
- ],
205
- });
206
-
207
- const db = createDbContext(MyDb, executor, { database: "mydb" });
208
-
209
- // Transactional query
210
- await db.connect(async () => {
211
- const users = await db.user().execute();
212
- const activeUsers = await db.activeUsers().execute();
213
- });
214
-
215
- // DDL operations (no transaction)
216
- await db.connectWithoutTransaction(async () => {
217
- await db.initialize();
218
- });
219
-
220
- // Execute procedure
221
- await db.connect(async () => {
222
- const result = await db.getUserById().execute({ userId: 1n });
223
- });
224
- ```
225
-
226
- ### Nested Transaction
227
-
228
- ```typescript
229
- await db.connectWithoutTransaction(async () => {
230
- // DDL first (no transaction needed)
231
- await db.createTable(NewTable);
232
-
233
- // Then transactional DML
234
- await db.transaction(async () => {
235
- await db.user().insert([{ name: "Alice" }]);
236
- });
237
- });
238
- ```
@@ -1,413 +0,0 @@
1
- # Expression Builder
2
-
3
- The `expr` object provides a dialect-independent SQL expression AST builder. Expressions are compiled to SQL by the QueryBuilder for each target DBMS (MySQL, MSSQL, PostgreSQL).
4
-
5
- ## API Reference
6
-
7
- ### Value Creation
8
-
9
- #### `expr.val(dataType, value)`
10
-
11
- Wrap a literal value as an `ExprUnit`.
12
-
13
- ```typescript
14
- expr.val("string", "active")
15
- expr.val("number", 100)
16
- expr.val("DateOnly", DateOnly.today())
17
- ```
18
-
19
- #### `expr.col(dataType, ...path)`
20
-
21
- Create a column reference (typically used internally; proxy objects in Queryable callbacks handle this automatically).
22
-
23
- ```typescript
24
- expr.col("string", "T1", "name")
25
- ```
26
-
27
- #### `expr.raw(dataType)\`template\``
28
-
29
- Raw SQL expression (escape hatch). Interpolated values are automatically parameterized.
30
-
31
- ```typescript
32
- db.user().select((u) => ({
33
- data: expr.raw("string")`JSON_EXTRACT(${u.metadata}, '$.email')`,
34
- }))
35
- ```
36
-
37
- ---
38
-
39
- ### Comparison Operators (WHERE)
40
-
41
- All comparison operators return `WhereExprUnit` for use in `.where()` callbacks.
42
-
43
- | Method | SQL | Description |
44
- |--------|-----|-------------|
45
- | `expr.eq(source, target)` | `=` (NULL-safe) | Equality comparison |
46
- | `expr.gt(source, target)` | `>` | Greater than |
47
- | `expr.lt(source, target)` | `<` | Less than |
48
- | `expr.gte(source, target)` | `>=` | Greater than or equal |
49
- | `expr.lte(source, target)` | `<=` | Less than or equal |
50
- | `expr.between(source, from?, to?)` | `BETWEEN` | Range comparison (undefined = unbounded) |
51
-
52
- ```typescript
53
- db.user().where((u) => [
54
- expr.eq(u.status, "active"),
55
- expr.gte(u.age, 18),
56
- expr.between(u.score, 60, 100),
57
- ])
58
- ```
59
-
60
- ---
61
-
62
- ### NULL Check
63
-
64
- #### `expr.null(source)`
65
-
66
- Check if value is NULL (`IS NULL`).
67
-
68
- ```typescript
69
- db.user().where((u) => [expr.null(u.deletedAt)])
70
- ```
71
-
72
- ---
73
-
74
- ### String Search (WHERE)
75
-
76
- #### `expr.like(source, pattern)`
77
-
78
- LIKE pattern matching. `%` matches any characters, `_` matches single character.
79
-
80
- ```typescript
81
- db.user().where((u) => [expr.like(u.name, "John%")])
82
- ```
83
-
84
- #### `expr.regexp(source, pattern)`
85
-
86
- Regular expression matching. Syntax varies by DBMS.
87
-
88
- ```typescript
89
- db.user().where((u) => [expr.regexp(u.email, "^[a-z]+@")])
90
- ```
91
-
92
- ---
93
-
94
- ### IN / EXISTS (WHERE)
95
-
96
- #### `expr.in(source, values)`
97
-
98
- Check if value is in a list.
99
-
100
- ```typescript
101
- db.user().where((u) => [expr.in(u.status, ["active", "pending"])])
102
- ```
103
-
104
- #### `expr.inQuery(source, query)`
105
-
106
- Check if value is in a subquery result (subquery must SELECT a single column).
107
-
108
- ```typescript
109
- db.user().where((u) => [
110
- expr.inQuery(
111
- u.id,
112
- db.order().where((o) => [expr.gt(o.amount, 1000)]).select((o) => ({ userId: o.userId })),
113
- ),
114
- ])
115
- ```
116
-
117
- #### `expr.exists(query)`
118
-
119
- Check if a subquery returns any rows.
120
-
121
- ```typescript
122
- db.user().where((u) => [
123
- expr.exists(db.order().where((o) => [expr.eq(o.userId, u.id)])),
124
- ])
125
- ```
126
-
127
- ---
128
-
129
- ### Logical Operators (WHERE)
130
-
131
- #### `expr.not(condition)`
132
-
133
- Negate a condition.
134
-
135
- ```typescript
136
- expr.not(expr.eq(u.status, "deleted"))
137
- ```
138
-
139
- #### `expr.and(conditions)`
140
-
141
- Combine conditions with AND (note: `.where()` arrays are implicitly AND-ed).
142
-
143
- ```typescript
144
- expr.and([expr.eq(u.status, "active"), expr.gte(u.age, 18)])
145
- ```
146
-
147
- #### `expr.or(conditions)`
148
-
149
- Combine conditions with OR.
150
-
151
- ```typescript
152
- expr.or([expr.eq(u.status, "active"), expr.eq(u.status, "pending")])
153
- ```
154
-
155
- ---
156
-
157
- ### String Functions (SELECT)
158
-
159
- | Method | SQL | Description |
160
- |--------|-----|-------------|
161
- | `expr.concat(...args)` | `CONCAT` | Concatenate strings (NULL treated as empty) |
162
- | `expr.left(source, length)` | `LEFT` | Extract left N characters |
163
- | `expr.right(source, length)` | `RIGHT` | Extract right N characters |
164
- | `expr.trim(source)` | `TRIM` | Remove leading/trailing whitespace |
165
- | `expr.padStart(source, length, fill)` | `LPAD` | Left-pad string |
166
- | `expr.replace(source, from, to)` | `REPLACE` | Replace occurrences |
167
- | `expr.upper(source)` | `UPPER` | Convert to uppercase |
168
- | `expr.lower(source)` | `LOWER` | Convert to lowercase |
169
- | `expr.length(source)` | `CHAR_LENGTH` | Character count |
170
- | `expr.byteLength(source)` | `OCTET_LENGTH` | Byte count |
171
- | `expr.substring(source, start, length?)` | `SUBSTRING` | Extract substring (1-based index) |
172
- | `expr.indexOf(source, search)` | `LOCATE/CHARINDEX` | Find position (1-based, 0 if not found) |
173
-
174
- ```typescript
175
- db.user().select((u) => ({
176
- fullName: expr.concat(u.firstName, " ", u.lastName),
177
- initial: expr.left(u.name, 1),
178
- phone: expr.replace(u.phone, "-", ""),
179
- }))
180
- ```
181
-
182
- ---
183
-
184
- ### Numeric Functions (SELECT)
185
-
186
- | Method | SQL | Description |
187
- |--------|-----|-------------|
188
- | `expr.abs(source)` | `ABS` | Absolute value |
189
- | `expr.round(source, digits)` | `ROUND` | Round to N decimal places |
190
- | `expr.ceil(source)` | `CEILING` | Round up |
191
- | `expr.floor(source)` | `FLOOR` | Round down |
192
-
193
- ```typescript
194
- db.product().select((p) => ({
195
- price: expr.round(p.price, 2),
196
- absDiscount: expr.abs(p.discount),
197
- }))
198
- ```
199
-
200
- ---
201
-
202
- ### Date Functions (SELECT)
203
-
204
- #### Extraction
205
-
206
- | Method | SQL | Returns |
207
- |--------|-----|---------|
208
- | `expr.year(source)` | `YEAR` | Year (4-digit) |
209
- | `expr.month(source)` | `MONTH` | Month (1-12) |
210
- | `expr.day(source)` | `DAY` | Day (1-31) |
211
- | `expr.hour(source)` | `HOUR` | Hour (0-23) |
212
- | `expr.minute(source)` | `MINUTE` | Minute (0-59) |
213
- | `expr.second(source)` | `SECOND` | Second (0-59) |
214
- | `expr.isoWeek(source)` | `WEEK` | ISO week number (1-53) |
215
- | `expr.isoWeekStartDate(source)` | -- | Monday of the week |
216
- | `expr.isoYearMonth(source)` | -- | First day of the month |
217
-
218
- #### Arithmetic
219
-
220
- | Method | SQL | Description |
221
- |--------|-----|-------------|
222
- | `expr.dateDiff(unit, from, to)` | `DATEDIFF` | Difference between dates |
223
- | `expr.dateAdd(unit, source, value)` | `DATEADD` | Add interval to date |
224
- | `expr.formatDate(source, format)` | `DATE_FORMAT` | Format date as string |
225
-
226
- **DateUnit values:** `"year"` | `"month"` | `"day"` | `"hour"` | `"minute"` | `"second"`
227
-
228
- ```typescript
229
- db.user().select((u) => ({
230
- age: expr.dateDiff("year", u.birthDate, expr.val("DateOnly", DateOnly.today())),
231
- expiresAt: expr.dateAdd("month", u.startDate, 12),
232
- birthYear: expr.year(u.birthDate),
233
- }))
234
- ```
235
-
236
- ---
237
-
238
- ### Conditional Functions (SELECT)
239
-
240
- #### `expr.coalesce(...args)`
241
-
242
- Return first non-null value (`COALESCE`).
243
-
244
- ```typescript
245
- expr.coalesce(u.nickname, u.name, "Guest")
246
- ```
247
-
248
- #### `expr.nullIf(source, value)`
249
-
250
- Return NULL if source equals value (`NULLIF`).
251
-
252
- ```typescript
253
- expr.nullIf(u.bio, "") // empty string -> NULL
254
- ```
255
-
256
- #### `expr.is(condition)`
257
-
258
- Convert WHERE expression to boolean value for SELECT.
259
-
260
- ```typescript
261
- db.user().select((u) => ({
262
- isActive: expr.is(expr.eq(u.status, "active")),
263
- }))
264
- ```
265
-
266
- #### `expr.switch<T>()`
267
-
268
- CASE WHEN expression builder (chaining API).
269
-
270
- ```typescript
271
- db.user().select((u) => ({
272
- grade: expr.switch<string>()
273
- .case(expr.gte(u.score, 90), "A")
274
- .case(expr.gte(u.score, 80), "B")
275
- .default("C"),
276
- }))
277
- ```
278
-
279
- #### `expr.if(condition, then, else_)`
280
-
281
- Simple IF/IIF expression.
282
-
283
- ```typescript
284
- expr.if(expr.gte(u.age, 18), "adult", "minor")
285
- ```
286
-
287
- ---
288
-
289
- ### Aggregate Functions (SELECT)
290
-
291
- All aggregate functions ignore NULL values. Return NULL only when all values are NULL or no rows.
292
-
293
- | Method | SQL | Description |
294
- |--------|-----|-------------|
295
- | `expr.count(arg?, distinct?)` | `COUNT` | Row count (no arg = count all) |
296
- | `expr.sum(arg)` | `SUM` | Sum of numeric column |
297
- | `expr.avg(arg)` | `AVG` | Average of numeric column |
298
- | `expr.max(arg)` | `MAX` | Maximum value |
299
- | `expr.min(arg)` | `MIN` | Minimum value |
300
-
301
- ```typescript
302
- db.order().select((o) => ({
303
- userId: o.userId,
304
- totalAmount: expr.sum(o.amount),
305
- orderCount: expr.count(),
306
- uniqueProducts: expr.count(o.productId, true),
307
- lastOrder: expr.max(o.createdAt),
308
- })).groupBy((o) => [o.userId])
309
- ```
310
-
311
- ---
312
-
313
- ### Other Functions
314
-
315
- | Method | SQL | Description |
316
- |--------|-----|-------------|
317
- | `expr.greatest(...args)` | `GREATEST` | Maximum of multiple values |
318
- | `expr.least(...args)` | `LEAST` | Minimum of multiple values |
319
- | `expr.rowNum()` | `ROW_NUMBER` | Simple row number |
320
- | `expr.random()` | `RAND/RANDOM` | Random number |
321
- | `expr.cast(source, targetType)` | `CAST` | Type conversion |
322
-
323
- ```typescript
324
- expr.cast(u.id, { type: "varchar", length: 10 })
325
- expr.greatest(u.score1, u.score2, u.score3)
326
- ```
327
-
328
- ---
329
-
330
- ### Window Functions
331
-
332
- #### `expr.window(fn, spec?)`
333
-
334
- Apply a window function with OVER clause.
335
-
336
- **Window function types:**
337
-
338
- | Function | Description |
339
- |----------|-------------|
340
- | `{ type: "rowNumber" }` | ROW_NUMBER() |
341
- | `{ type: "rank" }` | RANK() |
342
- | `{ type: "denseRank" }` | DENSE_RANK() |
343
- | `{ type: "ntile", n }` | NTILE(n) |
344
- | `{ type: "lag", column, offset?, default? }` | LAG() |
345
- | `{ type: "lead", column, offset?, default? }` | LEAD() |
346
- | `{ type: "firstValue", column }` | FIRST_VALUE() |
347
- | `{ type: "lastValue", column }` | LAST_VALUE() |
348
- | `{ type: "sum", column }` | Window SUM |
349
- | `{ type: "avg", column }` | Window AVG |
350
- | `{ type: "count", column? }` | Window COUNT |
351
- | `{ type: "min", column }` | Window MIN |
352
- | `{ type: "max", column }` | Window MAX |
353
-
354
- **WinSpec:**
355
-
356
- ```typescript
357
- {
358
- partitionBy?: ExprInput[],
359
- orderBy?: [ExprInput, ("ASC" | "DESC")?][],
360
- }
361
- ```
362
-
363
- ```typescript
364
- db.order().select((o) => ({
365
- id: o.id,
366
- rowNum: expr.window(
367
- { type: "rowNumber" },
368
- { partitionBy: [o.userId], orderBy: [[o.createdAt, "DESC"]] },
369
- ),
370
- runningTotal: expr.window(
371
- { type: "sum", column: o.amount },
372
- { partitionBy: [o.userId], orderBy: [[o.createdAt]] },
373
- ),
374
- }))
375
- ```
376
-
377
- ---
378
-
379
- ### Subquery
380
-
381
- #### `expr.subquery(query, fn)`
382
-
383
- Scalar subquery for use in SELECT expressions.
384
-
385
- ```typescript
386
- db.user().select((u) => ({
387
- name: u.name,
388
- postCount: expr.subquery(
389
- db.post().where((p) => [expr.eq(p.authorId, u.id)]),
390
- (q) => expr.count(q.id),
391
- ),
392
- }))
393
- ```
394
-
395
- ---
396
-
397
- ### Helper Types
398
-
399
- #### `ExprUnit<TPrimitive>`
400
-
401
- Type-safe expression wrapper that tracks the return type via TypeScript generics. Access `.n` to assert non-nullable.
402
-
403
- #### `WhereExprUnit`
404
-
405
- Expression wrapper for WHERE clause conditions.
406
-
407
- #### `ExprInput<TPrimitive>`
408
-
409
- Union type accepting either `ExprUnit<T>` or a literal value of type `T`.
410
-
411
- ```typescript
412
- type ExprInput<T> = ExprUnit<T> | T;
413
- ```