@simplysm/orm-common 13.0.82 → 13.0.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -0
- package/dist/ddl/initialize.d.ts +2 -2
- package/dist/ddl/initialize.js +1 -1
- package/dist/ddl/initialize.js.map +1 -1
- package/dist/ddl/table-ddl.d.ts +1 -1
- package/dist/exec/queryable.d.ts +115 -115
- package/dist/exec/queryable.js +68 -68
- package/dist/exec/queryable.js.map +1 -1
- package/dist/expr/expr.d.ts +248 -248
- package/dist/expr/expr.js +250 -250
- package/dist/query-builder/base/expr-renderer-base.d.ts +7 -7
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +3 -3
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js +5 -5
- package/dist/query-builder/mssql/mssql-query-builder.d.ts +2 -2
- package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.js +7 -7
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +2 -2
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.js +4 -4
- package/dist/query-builder/mysql/mysql-query-builder.d.ts +10 -10
- package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.js +4 -4
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +2 -2
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js +4 -4
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +8 -8
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.js +7 -7
- package/dist/query-builder/query-builder.d.ts +1 -1
- package/dist/schema/factory/column-builder.d.ts +46 -46
- package/dist/schema/factory/column-builder.js +25 -25
- package/dist/schema/factory/index-builder.d.ts +22 -22
- package/dist/schema/factory/index-builder.js +14 -14
- package/dist/schema/factory/relation-builder.d.ts +93 -93
- package/dist/schema/factory/relation-builder.d.ts.map +1 -1
- package/dist/schema/factory/relation-builder.js +37 -37
- package/dist/schema/procedure-builder.d.ts +38 -38
- package/dist/schema/procedure-builder.d.ts.map +1 -1
- package/dist/schema/procedure-builder.js +26 -26
- package/dist/schema/table-builder.d.ts +38 -38
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +29 -29
- package/dist/schema/view-builder.d.ts +26 -26
- package/dist/schema/view-builder.d.ts.map +1 -1
- package/dist/schema/view-builder.js +18 -18
- package/dist/types/db.d.ts +40 -40
- package/dist/types/expr.d.ts +75 -75
- package/dist/types/expr.d.ts.map +1 -1
- package/dist/types/query-def.d.ts +32 -32
- package/dist/types/query-def.d.ts.map +1 -1
- package/docs/db-context.md +238 -0
- package/docs/expressions.md +413 -0
- package/docs/query-builder.md +198 -0
- package/docs/queryable.md +420 -0
- package/docs/schema-builders.md +216 -0
- package/docs/types-and-utilities.md +353 -0
- package/package.json +4 -3
- package/src/ddl/initialize.ts +16 -16
- package/src/ddl/table-ddl.ts +1 -1
- package/src/exec/queryable.ts +163 -163
- package/src/expr/expr.ts +257 -257
- package/src/query-builder/base/expr-renderer-base.ts +8 -8
- package/src/query-builder/mssql/mssql-expr-renderer.ts +20 -20
- package/src/query-builder/mssql/mssql-query-builder.ts +28 -28
- package/src/query-builder/mysql/mysql-expr-renderer.ts +22 -22
- package/src/query-builder/mysql/mysql-query-builder.ts +65 -65
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +15 -15
- package/src/query-builder/postgresql/postgresql-query-builder.ts +43 -43
- package/src/query-builder/query-builder.ts +1 -1
- package/src/schema/factory/column-builder.ts +48 -48
- package/src/schema/factory/index-builder.ts +22 -22
- package/src/schema/factory/relation-builder.ts +95 -95
- package/src/schema/procedure-builder.ts +38 -38
- package/src/schema/table-builder.ts +38 -38
- package/src/schema/view-builder.ts +28 -28
- package/src/types/db.ts +41 -41
- package/src/types/expr.ts +79 -79
- package/src/types/query-def.ts +37 -37
- package/tests/ddl/basic.expected.ts +8 -8
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Schema Builders
|
|
2
|
+
|
|
3
|
+
Define database tables, views, and stored procedures with a fluent, type-safe API.
|
|
4
|
+
|
|
5
|
+
## API Reference
|
|
6
|
+
|
|
7
|
+
### `Table(name)`
|
|
8
|
+
|
|
9
|
+
Factory function that creates a `TableBuilder` for defining table schemas.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
function Table(name: string): TableBuilder<{}, {}>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
#### TableBuilder Methods
|
|
16
|
+
|
|
17
|
+
| Method | Signature | Description |
|
|
18
|
+
|--------|-----------|-------------|
|
|
19
|
+
| `description(desc)` | `.description(desc: string)` | Set table description (DDL comment) |
|
|
20
|
+
| `database(db)` | `.database(db: string)` | Set database name |
|
|
21
|
+
| `schema(schema)` | `.schema(schema: string)` | Set schema name (MSSQL: `dbo`, PostgreSQL: `public`) |
|
|
22
|
+
| `columns(fn)` | `.columns((c) => ({...}))` | Define columns using column factory |
|
|
23
|
+
| `primaryKey(...cols)` | `.primaryKey("col1", "col2")` | Set primary key (single or composite) |
|
|
24
|
+
| `indexes(fn)` | `.indexes((i) => [...])` | Define indexes |
|
|
25
|
+
| `relations(fn)` | `.relations((r) => ({...}))` | Define relationships (FK, reverse FK, logical relations) |
|
|
26
|
+
|
|
27
|
+
#### Type Inference Properties
|
|
28
|
+
|
|
29
|
+
| Property | Description |
|
|
30
|
+
|----------|-------------|
|
|
31
|
+
| `$inferSelect` | Full type (columns + deep relations) |
|
|
32
|
+
| `$inferColumns` | Columns only |
|
|
33
|
+
| `$inferInsert` | Insert type (autoIncrement/nullable/default fields are optional) |
|
|
34
|
+
| `$inferUpdate` | Update type (all fields optional) |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
### `View(name)`
|
|
39
|
+
|
|
40
|
+
Factory function that creates a `ViewBuilder` for defining database views.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
function View(name: string): ViewBuilder<any, {}, {}>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
#### ViewBuilder Methods
|
|
47
|
+
|
|
48
|
+
| Method | Signature | Description |
|
|
49
|
+
|--------|-----------|-------------|
|
|
50
|
+
| `description(desc)` | `.description(desc: string)` | Set view description |
|
|
51
|
+
| `database(db)` | `.database(db: string)` | Set database name |
|
|
52
|
+
| `schema(schema)` | `.schema(schema: string)` | Set schema name |
|
|
53
|
+
| `query(viewFn)` | `.query((db) => db.table().select(...))` | Define view SELECT query |
|
|
54
|
+
| `relations(fn)` | `.relations((r) => ({...}))` | Define relationships (logical only, no FK) |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
### `Procedure(name)`
|
|
59
|
+
|
|
60
|
+
Factory function that creates a `ProcedureBuilder` for defining stored procedures.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
function Procedure(name: string): ProcedureBuilder<never, never>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### ProcedureBuilder Methods
|
|
67
|
+
|
|
68
|
+
| Method | Signature | Description |
|
|
69
|
+
|--------|-----------|-------------|
|
|
70
|
+
| `description(desc)` | `.description(desc: string)` | Set procedure description |
|
|
71
|
+
| `database(db)` | `.database(db: string)` | Set database name |
|
|
72
|
+
| `schema(schema)` | `.schema(schema: string)` | Set schema name |
|
|
73
|
+
| `params(fn)` | `.params((c) => ({...}))` | Define input parameters |
|
|
74
|
+
| `returns(fn)` | `.returns((c) => ({...}))` | Define return columns |
|
|
75
|
+
| `body(sql)` | `.body("SELECT ...")` | Set procedure body SQL |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
### Column Factory
|
|
80
|
+
|
|
81
|
+
The column factory (`c`) is provided inside `.columns()` and `.params()`/`.returns()` callbacks.
|
|
82
|
+
|
|
83
|
+
#### Column Types
|
|
84
|
+
|
|
85
|
+
| Method | SQL Type | TypeScript Type | Notes |
|
|
86
|
+
|--------|----------|-----------------|-------|
|
|
87
|
+
| `c.int()` | INT | `number` | 4 bytes |
|
|
88
|
+
| `c.bigint()` | BIGINT | `number` | 8 bytes |
|
|
89
|
+
| `c.float()` | FLOAT | `number` | Single precision |
|
|
90
|
+
| `c.double()` | DOUBLE | `number` | Double precision |
|
|
91
|
+
| `c.decimal(p, s)` | DECIMAL(p,s) | `number` | Fixed-point |
|
|
92
|
+
| `c.varchar(len)` | VARCHAR(len) | `string` | Variable-length string |
|
|
93
|
+
| `c.char(len)` | CHAR(len) | `string` | Fixed-length string |
|
|
94
|
+
| `c.text()` | TEXT | `string` | Large text |
|
|
95
|
+
| `c.binary()` | BLOB/VARBINARY/BYTEA | `Bytes` | Binary data |
|
|
96
|
+
| `c.boolean()` | TINYINT(1)/BIT/BOOLEAN | `boolean` | |
|
|
97
|
+
| `c.datetime()` | DATETIME | `DateTime` | Date + time |
|
|
98
|
+
| `c.date()` | DATE | `DateOnly` | Date only |
|
|
99
|
+
| `c.time()` | TIME | `Time` | Time only |
|
|
100
|
+
| `c.uuid()` | BINARY(16)/UNIQUEIDENTIFIER/UUID | `Uuid` | |
|
|
101
|
+
|
|
102
|
+
#### Column Modifiers
|
|
103
|
+
|
|
104
|
+
| Method | Description |
|
|
105
|
+
|--------|-------------|
|
|
106
|
+
| `.autoIncrement()` | Auto-increment (optional in INSERT inference) |
|
|
107
|
+
| `.nullable()` | Allow NULL (adds `undefined` to type) |
|
|
108
|
+
| `.default(value)` | Set default value (optional in INSERT inference) |
|
|
109
|
+
| `.description(desc)` | Set column comment |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### Index Factory
|
|
114
|
+
|
|
115
|
+
The index factory (`i`) is provided inside `.indexes()` callbacks.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
i.index("col1", "col2") // Create index on columns
|
|
119
|
+
.unique() // Make unique
|
|
120
|
+
.orderBy("ASC", "DESC") // Set sort order per column
|
|
121
|
+
.name("IX_Custom_Name") // Custom index name
|
|
122
|
+
.description("desc") // Description
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
### Relation Factory
|
|
128
|
+
|
|
129
|
+
The relation factory (`r`) is provided inside `.relations()` callbacks.
|
|
130
|
+
|
|
131
|
+
| Method | Type | Description |
|
|
132
|
+
|--------|------|-------------|
|
|
133
|
+
| `r.foreignKey(cols, targetFn)` | N:1 | FK constraint created in DB |
|
|
134
|
+
| `r.foreignKeyTarget(targetFn, relName)` | 1:N | Reverse FK reference (array by default) |
|
|
135
|
+
| `r.relationKey(cols, targetFn)` | N:1 | Logical relation (no DB FK) |
|
|
136
|
+
| `r.relationKeyTarget(targetFn, relName)` | 1:N | Logical reverse reference |
|
|
137
|
+
|
|
138
|
+
Both `foreignKeyTarget` and `relationKeyTarget` support `.single()` to indicate a 1:1 relationship (returns single object instead of array).
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Usage Examples
|
|
143
|
+
|
|
144
|
+
### Complete Table Definition
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const User = Table("User")
|
|
148
|
+
.database("mydb")
|
|
149
|
+
.description("Application users")
|
|
150
|
+
.columns((c) => ({
|
|
151
|
+
id: c.bigint().autoIncrement(),
|
|
152
|
+
name: c.varchar(100),
|
|
153
|
+
email: c.varchar(200).nullable(),
|
|
154
|
+
status: c.varchar(20).default("active"),
|
|
155
|
+
createdAt: c.datetime().default("CURRENT_TIMESTAMP"),
|
|
156
|
+
}))
|
|
157
|
+
.primaryKey("id")
|
|
158
|
+
.indexes((i) => [
|
|
159
|
+
i.index("email").unique(),
|
|
160
|
+
i.index("status", "createdAt").orderBy("ASC", "DESC"),
|
|
161
|
+
]);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Table with Relations
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const Post = Table("Post")
|
|
168
|
+
.columns((c) => ({
|
|
169
|
+
id: c.bigint().autoIncrement(),
|
|
170
|
+
authorId: c.bigint(),
|
|
171
|
+
title: c.varchar(200),
|
|
172
|
+
}))
|
|
173
|
+
.primaryKey("id")
|
|
174
|
+
.relations((r) => ({
|
|
175
|
+
author: r.foreignKey(["authorId"], () => User),
|
|
176
|
+
}));
|
|
177
|
+
|
|
178
|
+
const User = Table("User")
|
|
179
|
+
.columns((c) => ({
|
|
180
|
+
id: c.bigint().autoIncrement(),
|
|
181
|
+
name: c.varchar(100),
|
|
182
|
+
}))
|
|
183
|
+
.primaryKey("id")
|
|
184
|
+
.relations((r) => ({
|
|
185
|
+
posts: r.foreignKeyTarget(() => Post, "author"),
|
|
186
|
+
profile: r.foreignKeyTarget(() => Profile, "user").single(),
|
|
187
|
+
}));
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### View Definition
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
const ActiveUsers = View("ActiveUsers")
|
|
194
|
+
.database("mydb")
|
|
195
|
+
.query((db: MyDb) =>
|
|
196
|
+
db.user()
|
|
197
|
+
.where((u) => [expr.eq(u.status, "active")])
|
|
198
|
+
.select((u) => ({ id: u.id, name: u.name, email: u.email }))
|
|
199
|
+
);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Procedure Definition
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const GetUserById = Procedure("GetUserById")
|
|
206
|
+
.database("mydb")
|
|
207
|
+
.params((c) => ({
|
|
208
|
+
userId: c.bigint(),
|
|
209
|
+
}))
|
|
210
|
+
.returns((c) => ({
|
|
211
|
+
id: c.bigint(),
|
|
212
|
+
name: c.varchar(100),
|
|
213
|
+
email: c.varchar(200),
|
|
214
|
+
}))
|
|
215
|
+
.body("SELECT id, name, email FROM User WHERE id = userId");
|
|
216
|
+
```
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# Types and Utilities
|
|
2
|
+
|
|
3
|
+
Core type definitions, error handling, search parsing, and result parsing utilities.
|
|
4
|
+
|
|
5
|
+
## API Reference
|
|
6
|
+
|
|
7
|
+
### Column Types
|
|
8
|
+
|
|
9
|
+
#### `DataType`
|
|
10
|
+
|
|
11
|
+
SQL data type definition used in column metadata.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
type DataType =
|
|
15
|
+
| { type: "int" }
|
|
16
|
+
| { type: "bigint" }
|
|
17
|
+
| { type: "float" }
|
|
18
|
+
| { type: "double" }
|
|
19
|
+
| { type: "decimal"; precision: number; scale?: number }
|
|
20
|
+
| { type: "varchar"; length: number }
|
|
21
|
+
| { type: "char"; length: number }
|
|
22
|
+
| { type: "text" }
|
|
23
|
+
| { type: "binary" }
|
|
24
|
+
| { type: "boolean" }
|
|
25
|
+
| { type: "datetime" }
|
|
26
|
+
| { type: "date" }
|
|
27
|
+
| { type: "time" }
|
|
28
|
+
| { type: "uuid" };
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
#### `ColumnPrimitive`
|
|
32
|
+
|
|
33
|
+
All primitive TypeScript types that can be stored in columns. `undefined` represents NULL.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
type ColumnPrimitive = string | number | boolean | DateTime | DateOnly | Time | Uuid | Bytes | undefined;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### `ColumnPrimitiveStr`
|
|
40
|
+
|
|
41
|
+
String keys for column primitive type mapping.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
type ColumnPrimitiveStr = "string" | "number" | "boolean" | "DateTime" | "DateOnly" | "Time" | "Uuid" | "Bytes";
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### `ColumnMeta`
|
|
48
|
+
|
|
49
|
+
Column metadata generated by `ColumnBuilder`.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
interface ColumnMeta {
|
|
53
|
+
type: ColumnPrimitiveStr;
|
|
54
|
+
dataType: DataType;
|
|
55
|
+
autoIncrement?: boolean;
|
|
56
|
+
nullable?: boolean;
|
|
57
|
+
default?: ColumnPrimitive;
|
|
58
|
+
description?: string;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### `inferColumnPrimitiveStr(value)`
|
|
63
|
+
|
|
64
|
+
Infer `ColumnPrimitiveStr` from a runtime value.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
function inferColumnPrimitiveStr(value: ColumnPrimitive): ColumnPrimitiveStr
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
inferColumnPrimitiveStr("hello") // "string"
|
|
72
|
+
inferColumnPrimitiveStr(123) // "number"
|
|
73
|
+
inferColumnPrimitiveStr(new DateTime()) // "DateTime"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### `dataTypeStrToColumnPrimitiveStr`
|
|
77
|
+
|
|
78
|
+
Mapping from SQL type strings to TypeScript type names.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
dataTypeStrToColumnPrimitiveStr["int"] // "number"
|
|
82
|
+
dataTypeStrToColumnPrimitiveStr["varchar"] // "string"
|
|
83
|
+
dataTypeStrToColumnPrimitiveStr["datetime"] // "DateTime"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### Database Types
|
|
89
|
+
|
|
90
|
+
#### `Dialect`
|
|
91
|
+
|
|
92
|
+
Supported database dialects.
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
type Dialect = "mysql" | "mssql" | "postgresql";
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### `dialects`
|
|
99
|
+
|
|
100
|
+
Array of all supported dialects (useful for testing).
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const dialects: Dialect[] = ["mysql", "mssql", "postgresql"];
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### `IsolationLevel`
|
|
107
|
+
|
|
108
|
+
Transaction isolation levels.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
type IsolationLevel =
|
|
112
|
+
| "READ_UNCOMMITTED"
|
|
113
|
+
| "READ_COMMITTED"
|
|
114
|
+
| "REPEATABLE_READ"
|
|
115
|
+
| "SERIALIZABLE";
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### `DataRecord`
|
|
119
|
+
|
|
120
|
+
Recursive type for query result records (supports nested relations).
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
type DataRecord = {
|
|
124
|
+
[key: string]: ColumnPrimitive | DataRecord | DataRecord[];
|
|
125
|
+
};
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### `ResultMeta`
|
|
129
|
+
|
|
130
|
+
Metadata for transforming raw query results into typed TypeScript objects.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
interface ResultMeta {
|
|
134
|
+
columns: Record<string, ColumnPrimitiveStr>;
|
|
135
|
+
joins: Record<string, { isSingle: boolean }>;
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### `QueryBuildResult`
|
|
140
|
+
|
|
141
|
+
Result of building a QueryDef into SQL.
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
interface QueryBuildResult {
|
|
145
|
+
sql: string;
|
|
146
|
+
resultSetIndex?: number;
|
|
147
|
+
resultSetStride?: number;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### `Migration`
|
|
152
|
+
|
|
153
|
+
Database migration definition.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
interface Migration {
|
|
157
|
+
name: string;
|
|
158
|
+
up: (db: DbContextBase & DbContextDdlMethods) => Promise<void>;
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### Error Handling
|
|
165
|
+
|
|
166
|
+
#### `DbTransactionError`
|
|
167
|
+
|
|
168
|
+
Standardized database transaction error with DBMS-independent error codes.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
class DbTransactionError extends Error {
|
|
172
|
+
readonly name = "DbTransactionError";
|
|
173
|
+
readonly code: DbErrorCode;
|
|
174
|
+
readonly originalError?: unknown;
|
|
175
|
+
|
|
176
|
+
constructor(code: DbErrorCode, message: string, originalError?: unknown);
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### `DbErrorCode`
|
|
181
|
+
|
|
182
|
+
Transaction-related error codes.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
enum DbErrorCode {
|
|
186
|
+
NO_ACTIVE_TRANSACTION = "NO_ACTIVE_TRANSACTION",
|
|
187
|
+
TRANSACTION_ALREADY_STARTED = "TRANSACTION_ALREADY_STARTED",
|
|
188
|
+
DEADLOCK = "DEADLOCK",
|
|
189
|
+
LOCK_TIMEOUT = "LOCK_TIMEOUT",
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
try {
|
|
195
|
+
await executor.rollbackTransaction();
|
|
196
|
+
} catch (err) {
|
|
197
|
+
if (err instanceof DbTransactionError) {
|
|
198
|
+
if (err.code === DbErrorCode.NO_ACTIVE_TRANSACTION) {
|
|
199
|
+
return; // Already rolled back, safe to ignore
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
### Search Parser
|
|
209
|
+
|
|
210
|
+
#### `parseSearchQuery(searchText)`
|
|
211
|
+
|
|
212
|
+
Parse a user search string into structured SQL LIKE patterns.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
function parseSearchQuery(searchText: string): ParsedSearchQuery
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
interface ParsedSearchQuery {
|
|
220
|
+
or: string[]; // General terms (OR condition)
|
|
221
|
+
must: string[]; // Required terms (AND condition, + prefix or quotes)
|
|
222
|
+
not: string[]; // Excluded terms (NOT condition, - prefix)
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Search Syntax:**
|
|
227
|
+
|
|
228
|
+
| Syntax | Meaning | Example |
|
|
229
|
+
|--------|---------|---------|
|
|
230
|
+
| `term1 term2` | OR (match any) | `apple banana` |
|
|
231
|
+
| `+term` | Required (AND) | `+apple +banana` |
|
|
232
|
+
| `-term` | Excluded (NOT) | `apple -banana` |
|
|
233
|
+
| `"exact phrase"` | Exact match (required) | `"delicious fruit"` |
|
|
234
|
+
| `*` | Wildcard | `app*` -> `app%` |
|
|
235
|
+
|
|
236
|
+
**Escape sequences:** `\\` (literal `\`), `\*` (literal `*`), `\%` (literal `%`), `\"` (literal `"`), `\+` (literal `+`), `\-` (literal `-`)
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
parseSearchQuery('apple "delicious fruit" -banana +strawberry')
|
|
240
|
+
// {
|
|
241
|
+
// or: ["%apple%"],
|
|
242
|
+
// must: ["%delicious fruit%", "%strawberry%"],
|
|
243
|
+
// not: ["%banana%"]
|
|
244
|
+
// }
|
|
245
|
+
|
|
246
|
+
parseSearchQuery('app* test')
|
|
247
|
+
// {
|
|
248
|
+
// or: ["app%", "%test%"],
|
|
249
|
+
// must: [],
|
|
250
|
+
// not: []
|
|
251
|
+
// }
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
### Result Parser
|
|
257
|
+
|
|
258
|
+
#### `parseQueryResult(rawResults, meta)`
|
|
259
|
+
|
|
260
|
+
Transform raw database query results into typed TypeScript objects. Handles type conversion, nested JOIN result grouping, and deduplication.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
async function parseQueryResult<TRecord>(
|
|
264
|
+
rawResults: Record<string, unknown>[],
|
|
265
|
+
meta: ResultMeta,
|
|
266
|
+
): Promise<TRecord[] | undefined>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Features:**
|
|
270
|
+
- Type conversion (string to number, string to DateTime, etc.)
|
|
271
|
+
- Flat-to-nested object transformation (`"posts.id"` -> `{ posts: { id } }`)
|
|
272
|
+
- JOIN result grouping with deduplication
|
|
273
|
+
- Single vs array relationship handling (`isSingle`)
|
|
274
|
+
- Event loop yielding for large datasets (every 100 records)
|
|
275
|
+
- Returns `undefined` for empty results
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// Simple type parsing
|
|
279
|
+
const raw = [{ id: "1", createdAt: "2026-01-07T10:00:00.000Z" }];
|
|
280
|
+
const meta = { columns: { id: "number", createdAt: "DateTime" }, joins: {} };
|
|
281
|
+
const result = await parseQueryResult(raw, meta);
|
|
282
|
+
// [{ id: 1, createdAt: DateTime(...) }]
|
|
283
|
+
|
|
284
|
+
// JOIN result nesting
|
|
285
|
+
const raw = [
|
|
286
|
+
{ id: 1, name: "Alice", "posts.id": 10, "posts.title": "Post1" },
|
|
287
|
+
{ id: 1, name: "Alice", "posts.id": 11, "posts.title": "Post2" },
|
|
288
|
+
];
|
|
289
|
+
const meta = {
|
|
290
|
+
columns: { id: "number", name: "string", "posts.id": "number", "posts.title": "string" },
|
|
291
|
+
joins: { posts: { isSingle: false } },
|
|
292
|
+
};
|
|
293
|
+
const result = await parseQueryResult(raw, meta);
|
|
294
|
+
// [{ id: 1, name: "Alice", posts: [{ id: 10, title: "Post1" }, { id: 11, title: "Post2" }] }]
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
### QueryDef Types
|
|
300
|
+
|
|
301
|
+
#### `QueryDefObjectName`
|
|
302
|
+
|
|
303
|
+
Database object name with optional namespace.
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
interface QueryDefObjectName {
|
|
307
|
+
database?: string;
|
|
308
|
+
schema?: string;
|
|
309
|
+
name: string;
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### `QueryDef`
|
|
314
|
+
|
|
315
|
+
Union type of all query definitions (DML + DDL + utility).
|
|
316
|
+
|
|
317
|
+
Key DML types:
|
|
318
|
+
|
|
319
|
+
| Type | Interface | Description |
|
|
320
|
+
|------|-----------|-------------|
|
|
321
|
+
| `"select"` | `SelectQueryDef` | SELECT with FROM, WHERE, JOIN, ORDER BY, GROUP BY, HAVING, LIMIT, WITH |
|
|
322
|
+
| `"insert"` | `InsertQueryDef` | INSERT with records, output, overrideIdentity |
|
|
323
|
+
| `"insertIfNotExists"` | `InsertIfNotExistsQueryDef` | Conditional INSERT |
|
|
324
|
+
| `"insertInto"` | `InsertIntoQueryDef` | INSERT INTO ... SELECT |
|
|
325
|
+
| `"update"` | `UpdateQueryDef` | UPDATE with JOIN support |
|
|
326
|
+
| `"delete"` | `DeleteQueryDef` | DELETE with JOIN support |
|
|
327
|
+
| `"upsert"` | `UpsertQueryDef` | INSERT or UPDATE (MERGE) |
|
|
328
|
+
|
|
329
|
+
#### `DDL_TYPES`
|
|
330
|
+
|
|
331
|
+
Constant array of all DDL query type strings. Used to prevent DDL execution inside transactions.
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
const DDL_TYPES: readonly string[]
|
|
335
|
+
// ["clearSchema", "createTable", "dropTable", "renameTable", "truncate",
|
|
336
|
+
// "addColumn", "dropColumn", "modifyColumn", "renameColumn",
|
|
337
|
+
// "dropPrimaryKey", "addPrimaryKey", "addForeignKey", "dropForeignKey",
|
|
338
|
+
// "addIndex", "dropIndex", "createView", "dropView", "createProc", "dropProc"]
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
### Type Inference Utilities
|
|
344
|
+
|
|
345
|
+
| Type | Description |
|
|
346
|
+
|------|-------------|
|
|
347
|
+
| `InferColumns<T>` | Infer value types from ColumnBuilderRecord |
|
|
348
|
+
| `InferInsertColumns<T>` | Infer INSERT type (required + optional fields) |
|
|
349
|
+
| `InferUpdateColumns<T>` | Infer UPDATE type (all fields optional) |
|
|
350
|
+
| `InferColumnExprs<T>` | Infer expression input types |
|
|
351
|
+
| `InferDeepRelations<T>` | Infer nested relation types (all optional) |
|
|
352
|
+
| `ExtractRelationTarget<T>` | Extract N:1 relation target type |
|
|
353
|
+
| `ExtractRelationTargetResult<T>` | Extract 1:N relation target type (array or single) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/orm-common",
|
|
3
|
-
"version": "13.0.
|
|
3
|
+
"version": "13.0.84",
|
|
4
4
|
"description": "Simplysm Package - ORM Module (common)",
|
|
5
5
|
"author": "simplysm",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
17
17
|
"src",
|
|
18
|
-
"tests"
|
|
18
|
+
"tests",
|
|
19
|
+
"docs"
|
|
19
20
|
],
|
|
20
21
|
"sideEffects": false,
|
|
21
22
|
"dependencies": {
|
|
22
|
-
"@simplysm/core-common": "13.0.
|
|
23
|
+
"@simplysm/core-common": "13.0.84"
|
|
23
24
|
}
|
|
24
25
|
}
|
package/src/ddl/initialize.ts
CHANGED
|
@@ -46,7 +46,7 @@ export async function initialize(
|
|
|
46
46
|
|
|
47
47
|
const force = options?.force ?? false;
|
|
48
48
|
|
|
49
|
-
// 1. DB
|
|
49
|
+
// 1. Check DB existence
|
|
50
50
|
for (const dbName of dbNames) {
|
|
51
51
|
const schemaExistsDef = getSchemaExistsQueryDef(dbName, db.schema);
|
|
52
52
|
const result = await db.executeDefs([schemaExistsDef]);
|
|
@@ -57,7 +57,7 @@ export async function initialize(
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
if (force) {
|
|
60
|
-
// 2. force:
|
|
60
|
+
// 2. force: initialize all dbs
|
|
61
61
|
for (const dbName of dbNames) {
|
|
62
62
|
const clearDef = getClearSchemaQueryDef({ database: dbName, schema: db.schema });
|
|
63
63
|
await db.executeDefs([clearDef]);
|
|
@@ -69,7 +69,7 @@ export async function initialize(
|
|
|
69
69
|
await db._migration().insert(def.meta.migrations.map((m) => ({ code: m.name })));
|
|
70
70
|
}
|
|
71
71
|
} else {
|
|
72
|
-
// 3. Migration
|
|
72
|
+
// 3. Migration-based initialize
|
|
73
73
|
let appliedMigrations: { code: string }[] | undefined;
|
|
74
74
|
try {
|
|
75
75
|
appliedMigrations = await db._migration().execute();
|
|
@@ -102,13 +102,13 @@ export async function initialize(
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
|
-
*
|
|
105
|
+
* Generate all objects (table/view/procedure/FK/index)
|
|
106
106
|
*/
|
|
107
107
|
async function createAllObjects(
|
|
108
108
|
db: DbContextBase,
|
|
109
109
|
def: DbContextDef<any, any, any>,
|
|
110
110
|
): Promise<void> {
|
|
111
|
-
// 1. Table/View/Procedure
|
|
111
|
+
// 1. Generate Table/View/Procedure
|
|
112
112
|
const builders = getBuilders(def);
|
|
113
113
|
const createDefs: QueryDef[] = [];
|
|
114
114
|
for (const builder of builders) {
|
|
@@ -118,7 +118,7 @@ async function createAllObjects(
|
|
|
118
118
|
await db.executeDefs(createDefs);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
// 2. FK
|
|
121
|
+
// 2. Generate FK (TableBuilder only)
|
|
122
122
|
const tables = builders.filter((b) => b instanceof TableBuilder);
|
|
123
123
|
const addFkDefs: QueryDef[] = [];
|
|
124
124
|
for (const table of tables) {
|
|
@@ -136,7 +136,7 @@ async function createAllObjects(
|
|
|
136
136
|
await db.executeDefs(addFkDefs);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
// 3. Index
|
|
139
|
+
// 3. Generate Index (TableBuilder only)
|
|
140
140
|
const createIndexDefs: QueryDef[] = [];
|
|
141
141
|
for (const table of tables) {
|
|
142
142
|
const indexes = table.meta.indexes;
|
|
@@ -153,7 +153,7 @@ async function createAllObjects(
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
/**
|
|
156
|
-
*
|
|
156
|
+
* Collect all builders from DbContext (Table/View/Procedure)
|
|
157
157
|
*/
|
|
158
158
|
function getBuilders(
|
|
159
159
|
def: DbContextDef<any, any, any>,
|
|
@@ -186,8 +186,8 @@ function getBuilders(
|
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
|
-
* ForeignKeyTarget/RelationKeyTarget
|
|
190
|
-
* - targetTableFn()
|
|
189
|
+
* Validate ForeignKeyTarget/RelationKeyTarget relations
|
|
190
|
+
* - Checks if the table returned by targetTableFn() has a FK/RelationKey matching the relationName
|
|
191
191
|
*/
|
|
192
192
|
export function validateRelations(def: DbContextDef<any, any, any>): void {
|
|
193
193
|
const builders = getBuilders(def);
|
|
@@ -211,8 +211,8 @@ export function validateRelations(def: DbContextDef<any, any, any>): void {
|
|
|
211
211
|
|
|
212
212
|
if (!(fkRel instanceof ForeignKeyBuilder) && !(fkRel instanceof RelationKeyBuilder)) {
|
|
213
213
|
throw new Error(
|
|
214
|
-
`Invalid relation target: ${table.meta.name}.${relName}
|
|
215
|
-
`
|
|
214
|
+
`Invalid relation target: '${fkRelName}' referenced by ${table.meta.name}.${relName} ` +
|
|
215
|
+
`is not a valid ForeignKey/RelationKey in ${targetTable.meta.name}.`,
|
|
216
216
|
);
|
|
217
217
|
}
|
|
218
218
|
}
|
|
@@ -220,9 +220,9 @@ export function validateRelations(def: DbContextDef<any, any, any>): void {
|
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
/**
|
|
223
|
-
*
|
|
223
|
+
* Check if the error indicates a table does not exist
|
|
224
224
|
*
|
|
225
|
-
* DBMS
|
|
225
|
+
* DBMS-specific error code/message patterns:
|
|
226
226
|
* - MySQL: errno 1146 (ER_NO_SUCH_TABLE), "Table 'xxx' doesn't exist"
|
|
227
227
|
* - MSSQL: number 208, "Invalid object name 'xxx'"
|
|
228
228
|
* - PostgreSQL: code "42P01", "relation \"xxx\" does not exist"
|
|
@@ -230,13 +230,13 @@ export function validateRelations(def: DbContextDef<any, any, any>): void {
|
|
|
230
230
|
function isTableNotExistsError(err: unknown): boolean {
|
|
231
231
|
if (err == null) return false;
|
|
232
232
|
|
|
233
|
-
// error code
|
|
233
|
+
// Check error code first (reliable even in multilingual environments)
|
|
234
234
|
const errObj = err as Record<string, unknown>;
|
|
235
235
|
if (errObj["errno"] === 1146) return true; // MySQL ER_NO_SUCH_TABLE
|
|
236
236
|
if (errObj["number"] === 208) return true; // MSSQL
|
|
237
237
|
if (errObj["code"] === "42P01") return true; // PostgreSQL
|
|
238
238
|
|
|
239
|
-
//
|
|
239
|
+
// Fallback: message matching (may be unreliable in multilingual environments)
|
|
240
240
|
const message = err instanceof Error ? err.message : String(err);
|
|
241
241
|
const lowerMessage = message.toLowerCase();
|
|
242
242
|
|