@simplysm/orm-common 13.0.97 → 13.0.98

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 ADDED
@@ -0,0 +1,267 @@
1
+ # @simplysm/orm-common
2
+
3
+ ORM Module (common) -- dialect-independent ORM for MySQL, MSSQL, and PostgreSQL.
4
+
5
+ Provides schema definition, type-safe query building, expression construction, and query rendering without any direct database dependency. Actual database execution is handled by `@simplysm/orm-node`.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @simplysm/orm-common
11
+ ```
12
+
13
+ ## API Reference
14
+
15
+ ### Core
16
+
17
+ Define and create database contexts with connection/transaction management.
18
+
19
+ | Export | Kind | Description |
20
+ |--------|------|-------------|
21
+ | `defineDbContext` | function | Create a DbContext definition (blueprint) from tables, views, procedures, and migrations |
22
+ | `createDbContext` | function | Create a runtime DbContext instance from a definition and executor |
23
+ | `DbContextBase` | interface | Internal interface used by Queryable/Executable (status, database, schema, executeDefs) |
24
+ | `DbContextDef` | interface | DbContext definition holding schema metadata (tables, views, procedures, migrations) |
25
+ | `DbContextInstance` | type | Full runtime instance with queryable accessors, DDL methods, and connection management |
26
+ | `DbContextStatus` | type | `"ready" \| "connect" \| "transact"` |
27
+ | `DbTransactionError` | class | Standardized transaction error with `DbErrorCode` |
28
+ | `DbErrorCode` | enum | `NO_ACTIVE_TRANSACTION`, `TRANSACTION_ALREADY_STARTED`, `DEADLOCK`, `LOCK_TIMEOUT` |
29
+
30
+ [Detailed documentation](docs/core.md)
31
+
32
+ ### Queryable / Executable
33
+
34
+ Build and execute SELECT, INSERT, UPDATE, DELETE, and UPSERT queries with full type safety.
35
+
36
+ | Export | Kind | Description |
37
+ |--------|------|-------------|
38
+ | `Queryable` | class | Chaining query builder (select, where, join, include, orderBy, limit, groupBy, etc.) |
39
+ | `queryable` | function | Factory that creates a Queryable getter for a table or view |
40
+ | `getMatchedPrimaryKeys` | function | Match FK columns to target table PK columns |
41
+ | `Executable` | class | Stored procedure execution wrapper |
42
+ | `executable` | function | Factory that creates an Executable getter for a procedure |
43
+ | `parseSearchQuery` | function | Parse search text into LIKE patterns (OR / +must / -not) |
44
+ | `ParsedSearchQuery` | interface | Result of `parseSearchQuery` with `or`, `must`, `not` arrays |
45
+
46
+ [Detailed documentation](docs/queryable.md)
47
+
48
+ ### Expression
49
+
50
+ Dialect-independent SQL expression builder producing JSON AST.
51
+
52
+ | Export | Kind | Description |
53
+ |--------|------|-------------|
54
+ | `expr` | const | Expression builder with 70+ methods (comparisons, string, math, date, aggregate, window) |
55
+ | `ExprUnit` | class | Type-safe expression wrapper tracking return type via generics |
56
+ | `WhereExprUnit` | class | WHERE clause expression wrapper |
57
+ | `ExprInput` | type | Accepts `ExprUnit<T>` or literal `T` |
58
+ | `SwitchExprBuilder` | interface | CASE WHEN builder returned by `expr.switch()` |
59
+
60
+ [Detailed documentation](docs/expression.md)
61
+
62
+ ### Schema Builders
63
+
64
+ Define tables, views, procedures, columns, indexes, and relations via fluent API.
65
+
66
+ | Export | Kind | Description |
67
+ |--------|------|-------------|
68
+ | `Table` | function | Create a `TableBuilder` for a table |
69
+ | `TableBuilder` | class | Fluent builder: `.database()`, `.columns()`, `.primaryKey()`, `.indexes()`, `.relations()` |
70
+ | `View` | function | Create a `ViewBuilder` for a view |
71
+ | `ViewBuilder` | class | Fluent builder: `.database()`, `.query()`, `.relations()` |
72
+ | `Procedure` | function | Create a `ProcedureBuilder` for a stored procedure |
73
+ | `ProcedureBuilder` | class | Fluent builder: `.database()`, `.params()`, `.returns()`, `.body()` |
74
+ | `ColumnBuilder` | class | Column definition: `.autoIncrement()`, `.nullable()`, `.default()`, `.description()` |
75
+ | `createColumnFactory` | function | Returns column type methods (int, bigint, varchar, text, datetime, etc.) |
76
+ | `IndexBuilder` | class | Index definition: `.name()`, `.unique()`, `.orderBy()`, `.description()` |
77
+ | `createIndexFactory` | function | Returns `{ index(...columns) }` |
78
+ | `ForeignKeyBuilder` | class | N:1 FK relation (creates DB constraint) |
79
+ | `ForeignKeyTargetBuilder` | class | 1:N FK reverse-reference with `.single()` |
80
+ | `RelationKeyBuilder` | class | N:1 logical relation (no DB constraint) |
81
+ | `RelationKeyTargetBuilder` | class | 1:N logical reverse-reference with `.single()` |
82
+ | `createRelationFactory` | function | Returns FK + RelationKey methods (table gets both, view gets RelationKey only) |
83
+ | `_Migration` | const | Built-in system migration table (`_migration` with `code` column) |
84
+
85
+ [Detailed documentation](docs/schema-builders.md)
86
+
87
+ ### Query Builder
88
+
89
+ Render `QueryDef` JSON AST to dialect-specific SQL strings.
90
+
91
+ | Export | Kind | Description |
92
+ |--------|------|-------------|
93
+ | `createQueryBuilder` | function | Create a dialect-specific QueryBuilder (`"mysql"`, `"mssql"`, `"postgresql"`) |
94
+ | `QueryBuilderBase` | abstract class | Base class with dispatch and common render methods |
95
+ | `ExprRendererBase` | abstract class | Base class for expression-to-SQL rendering |
96
+ | `MysqlQueryBuilder` | class | MySQL implementation |
97
+ | `MysqlExprRenderer` | class | MySQL expression renderer |
98
+ | `MssqlQueryBuilder` | class | MSSQL implementation |
99
+ | `MssqlExprRenderer` | class | MSSQL expression renderer |
100
+ | `PostgresqlQueryBuilder` | class | PostgreSQL implementation |
101
+ | `PostgresqlExprRenderer` | class | PostgreSQL expression renderer |
102
+
103
+ [Detailed documentation](docs/query-builder.md)
104
+
105
+ ### Types
106
+
107
+ TypeScript types for dialects, queries, expressions, columns, and results.
108
+
109
+ | Export | Kind | Description |
110
+ |--------|------|-------------|
111
+ | `Dialect` | type | `"mysql" \| "mssql" \| "postgresql"` |
112
+ | `dialects` | const | `["mysql", "mssql", "postgresql"]` |
113
+ | `QueryBuildResult` | interface | `{ sql, resultSetIndex?, resultSetStride? }` |
114
+ | `IsolationLevel` | type | `"READ_UNCOMMITTED" \| "READ_COMMITTED" \| "REPEATABLE_READ" \| "SERIALIZABLE"` |
115
+ | `DataRecord` | type | Recursive record type for query results |
116
+ | `DbContextExecutor` | interface | Executor interface (connect, close, beginTransaction, executeDefs, etc.) |
117
+ | `ResultMeta` | interface | Metadata for result type transformation and JOIN nesting |
118
+ | `Migration` | interface | `{ name, up }` migration definition |
119
+ | `DataType` | type | SQL type union (int, bigint, varchar, decimal, datetime, etc.) |
120
+ | `ColumnPrimitive` | type | All column value types (string, number, boolean, DateTime, DateOnly, Time, Uuid, Bytes, undefined) |
121
+ | `ColumnPrimitiveStr` | type | Type name keys: `"string" \| "number" \| "boolean" \| "DateTime" \| ...` |
122
+ | `ColumnMeta` | interface | Column metadata (type, dataType, autoIncrement, nullable, default, description) |
123
+ | `Expr` | type | Discriminated union of 40+ expression AST node types |
124
+ | `WhereExpr` | type | Subset of Expr for WHERE clauses (comparison + logical) |
125
+ | `QueryDef` | type | Union of all query definition types (DML + DDL + Utils + Meta) |
126
+ | `SelectQueryDef` | interface | SELECT query definition |
127
+ | `InsertQueryDef` | interface | INSERT query definition |
128
+ | `UpdateQueryDef` | interface | UPDATE query definition |
129
+ | `DeleteQueryDef` | interface | DELETE query definition |
130
+ | `UpsertQueryDef` | interface | UPSERT query definition |
131
+ | `QueryDefObjectName` | interface | `{ database?, schema?, name }` |
132
+
133
+ [Detailed documentation](docs/types.md)
134
+
135
+ ### Utilities
136
+
137
+ Result parsing helpers.
138
+
139
+ | Export | Kind | Description |
140
+ |--------|------|-------------|
141
+ | `parseQueryResult` | function | Transform flat DB results to typed nested objects via `ResultMeta` |
142
+
143
+ [Detailed documentation](docs/utilities.md)
144
+
145
+ ## Usage Examples
146
+
147
+ ### 1. Define Schema and DbContext
148
+
149
+ ```typescript
150
+ import { Table, defineDbContext, createDbContext, expr } from "@simplysm/orm-common";
151
+
152
+ const User = Table("User")
153
+ .database("mydb")
154
+ .columns((c) => ({
155
+ id: c.bigint().autoIncrement(),
156
+ name: c.varchar(100),
157
+ email: c.varchar(200).nullable(),
158
+ status: c.varchar(20).default("active"),
159
+ createdAt: c.datetime(),
160
+ }))
161
+ .primaryKey("id")
162
+ .indexes((i) => [i.index("email").unique()]);
163
+
164
+ const Post = Table("Post")
165
+ .database("mydb")
166
+ .columns((c) => ({
167
+ id: c.bigint().autoIncrement(),
168
+ authorId: c.bigint(),
169
+ title: c.varchar(200),
170
+ content: c.text(),
171
+ }))
172
+ .primaryKey("id")
173
+ .relations((r) => ({
174
+ author: r.foreignKey(["authorId"], () => User),
175
+ }));
176
+
177
+ const MyDb = defineDbContext({
178
+ tables: { user: User, post: Post },
179
+ });
180
+
181
+ // createDbContext requires an executor (from @simplysm/orm-node or service-client)
182
+ const db = createDbContext(MyDb, executor, { database: "mydb" });
183
+ ```
184
+
185
+ ### 2. Query with Filters, Joins, and Aggregation
186
+
187
+ ```typescript
188
+ await db.connect(async () => {
189
+ // Basic query with WHERE
190
+ const activeUsers = await db.user()
191
+ .where((u) => [expr.eq(u.status, "active")])
192
+ .orderBy((u) => u.name)
193
+ .execute();
194
+
195
+ // JOIN with include
196
+ const postsWithAuthor = await db.post()
197
+ .include((p) => p.author)
198
+ .execute();
199
+
200
+ // Aggregation
201
+ const stats = await db.post()
202
+ .select((p) => ({
203
+ authorId: p.authorId,
204
+ postCount: expr.count(p.id),
205
+ }))
206
+ .groupBy((p) => [p.authorId])
207
+ .execute();
208
+ });
209
+ ```
210
+
211
+ ### 3. Insert, Update, Delete
212
+
213
+ ```typescript
214
+ await db.connect(async () => {
215
+ // Insert with output
216
+ const [inserted] = await db.user().insert(
217
+ [{ name: "Alice", createdAt: DateTime.now() }],
218
+ ["id"],
219
+ );
220
+
221
+ // Update
222
+ await db.user()
223
+ .where((u) => [expr.eq(u.id, inserted.id)])
224
+ .update((u) => ({
225
+ status: expr.val("string", "verified"),
226
+ }));
227
+
228
+ // Delete
229
+ await db.user()
230
+ .where((u) => [expr.eq(u.status, "deleted")])
231
+ .delete();
232
+
233
+ // Upsert
234
+ await db.user()
235
+ .where((u) => [expr.eq(u.email, "alice@test.com")])
236
+ .upsert(() => ({
237
+ name: expr.val("string", "Alice"),
238
+ email: expr.val("string", "alice@test.com"),
239
+ createdAt: expr.val("DateTime", DateTime.now()),
240
+ }));
241
+ });
242
+ ```
243
+
244
+ ### 4. Text Search
245
+
246
+ ```typescript
247
+ await db.connect(async () => {
248
+ // Search with OR, +must, -exclude syntax
249
+ const results = await db.user()
250
+ .search((u) => [u.name, u.email], "alice +active -deleted")
251
+ .execute();
252
+ });
253
+ ```
254
+
255
+ ### 5. Recursive CTE
256
+
257
+ ```typescript
258
+ await db.connect(async () => {
259
+ const hierarchy = await db.employee()
260
+ .where((e) => [expr.null(e.managerId)])
261
+ .recursive((cte) =>
262
+ cte.from(Employee)
263
+ .where((e) => [expr.eq(e.managerId, e.self![0].id)])
264
+ )
265
+ .execute();
266
+ });
267
+ ```
package/docs/core.md ADDED
@@ -0,0 +1,225 @@
1
+ # Core
2
+
3
+ Database context definition, creation, and lifecycle management.
4
+
5
+ Source: `src/define-db-context.ts`, `src/create-db-context.ts`, `src/types/db-context-def.ts`, `src/errors/db-transaction-error.ts`
6
+
7
+ ## defineDbContext
8
+
9
+ Create a `DbContextDef` blueprint from tables, views, procedures, and migrations. Automatically includes the `_migration` system table.
10
+
11
+ ```typescript
12
+ function defineDbContext<
13
+ TTables extends Record<string, TableBuilder<any, any>>,
14
+ TViews extends Record<string, ViewBuilder<any, any, any>>,
15
+ TProcedures extends Record<string, ProcedureBuilder<any, any>>,
16
+ >(config: {
17
+ tables?: TTables;
18
+ views?: TViews;
19
+ procedures?: TProcedures;
20
+ migrations?: Migration[];
21
+ }): DbContextDef<TTables & { _migration: typeof _Migration }, TViews, TProcedures>;
22
+ ```
23
+
24
+ **Example:**
25
+
26
+ ```typescript
27
+ const MyDb = defineDbContext({
28
+ tables: { user: User, post: Post },
29
+ views: { userSummary: UserSummary },
30
+ procedures: { getUserById: GetUserById },
31
+ migrations: [
32
+ { name: "20260101_001_init", up: async (db) => { await db.createTable(User); } },
33
+ ],
34
+ });
35
+ ```
36
+
37
+ ## createDbContext
38
+
39
+ Create a runtime `DbContextInstance` from a definition and executor. This is the main entry point for database operations.
40
+
41
+ ```typescript
42
+ /**
43
+ * @param def - Definition object created by defineDbContext()
44
+ * @param executor - Query executor (NodeDbContextExecutor, ServiceDbContextExecutor, etc.)
45
+ * @param opt - Database options
46
+ * @param opt.database - Database name
47
+ * @param opt.schema - Schema name (MSSQL: dbo, PostgreSQL: public)
48
+ * @returns A complete DbContext instance
49
+ */
50
+ function createDbContext<TDef extends DbContextDef<any, any, any>>(
51
+ def: TDef,
52
+ executor: DbContextExecutor,
53
+ opt: { database: string; schema?: string },
54
+ ): DbContextInstance<TDef>;
55
+ ```
56
+
57
+ The returned instance provides:
58
+
59
+ - **Queryable accessors** -- one per table/view (e.g., `db.user()` returns a `Queryable`)
60
+ - **Executable accessors** -- one per procedure (e.g., `db.getUserById()` returns an `Executable`)
61
+ - **Connection management** -- `connect()`, `connectWithoutTransaction()`, `transaction()`
62
+ - **DDL methods** -- `createTable()`, `dropTable()`, `addColumn()`, `addIndex()`, etc.
63
+ - **Initialization** -- `initialize()` to run migrations
64
+
65
+ **Example:**
66
+
67
+ ```typescript
68
+ const db = createDbContext(MyDb, executor, { database: "mydb" });
69
+
70
+ await db.connect(async () => {
71
+ const users = await db.user().execute();
72
+ });
73
+ ```
74
+
75
+ ## DbContextBase
76
+
77
+ Internal interface satisfied by the DbContext instance. Used by `Queryable`, `Executable`, and `ViewBuilder`.
78
+
79
+ ```typescript
80
+ interface DbContextBase {
81
+ status: DbContextStatus;
82
+ readonly database: string | undefined;
83
+ readonly schema: string | undefined;
84
+ getNextAlias(): string;
85
+ resetAliasCounter(): void;
86
+ executeDefs<T = DataRecord>(
87
+ defs: QueryDef[],
88
+ resultMetas?: (ResultMeta | undefined)[],
89
+ ): Promise<T[][]>;
90
+ getQueryDefObjectName(
91
+ tableOrView: TableBuilder<any, any> | ViewBuilder<any, any, any>,
92
+ ): QueryDefObjectName;
93
+ switchFk(table: QueryDefObjectName, enabled: boolean): Promise<void>;
94
+ }
95
+ ```
96
+
97
+ ## DbContextDef
98
+
99
+ Definition (blueprint) created by `defineDbContext()`. Contains schema metadata but no runtime state.
100
+
101
+ ```typescript
102
+ interface DbContextDef<
103
+ TTables extends Record<string, TableBuilder<any, any>>,
104
+ TViews extends Record<string, ViewBuilder<any, any, any>>,
105
+ TProcedures extends Record<string, ProcedureBuilder<any, any>>,
106
+ > {
107
+ readonly meta: {
108
+ readonly tables: TTables;
109
+ readonly views: TViews;
110
+ readonly procedures: TProcedures;
111
+ readonly migrations: Migration[];
112
+ };
113
+ }
114
+ ```
115
+
116
+ ## DbContextInstance
117
+
118
+ Full runtime type created by `createDbContext`. Combines `DbContextBase`, connection methods, DDL methods, and auto-mapped queryable/executable accessors.
119
+
120
+ ```typescript
121
+ type DbContextInstance<TDef extends DbContextDef<any, any, any>> = DbContextBase &
122
+ DbContextConnectionMethods &
123
+ DbContextDdlMethods & {
124
+ [K in keyof TDef["meta"]["tables"]]: () => Queryable<...>;
125
+ } & {
126
+ [K in keyof TDef["meta"]["views"]]: () => Queryable<...>;
127
+ } & {
128
+ [K in keyof TDef["meta"]["procedures"]]: () => Executable<...>;
129
+ } & {
130
+ _migration: () => Queryable<{ code: string }, any>;
131
+ initialize(options?: { dbs?: string[]; force?: boolean }): Promise<void>;
132
+ };
133
+ ```
134
+
135
+ ### Connection Methods
136
+
137
+ ```typescript
138
+ interface DbContextConnectionMethods {
139
+ /** Execute within a transaction (auto commit/rollback) */
140
+ connect<TResult>(fn: () => Promise<TResult>, isolationLevel?: IsolationLevel): Promise<TResult>;
141
+
142
+ /** Connect without transaction (for DDL or read-only operations) */
143
+ connectWithoutTransaction<TResult>(callback: () => Promise<TResult>): Promise<TResult>;
144
+
145
+ /** Start a transaction within an already-connected state */
146
+ transaction<TResult>(fn: () => Promise<TResult>, isolationLevel?: IsolationLevel): Promise<TResult>;
147
+ }
148
+ ```
149
+
150
+ ### DDL Methods
151
+
152
+ The instance exposes DDL execution methods and their corresponding QueryDef generators:
153
+
154
+ | Method | Description |
155
+ |--------|-------------|
156
+ | `createTable(table)` | CREATE TABLE |
157
+ | `dropTable(table)` | DROP TABLE |
158
+ | `renameTable(table, newName)` | RENAME TABLE |
159
+ | `createView(view)` | CREATE VIEW |
160
+ | `dropView(view)` | DROP VIEW |
161
+ | `createProc(procedure)` | CREATE PROCEDURE |
162
+ | `dropProc(procedure)` | DROP PROCEDURE |
163
+ | `addColumn(table, name, column)` | ADD COLUMN |
164
+ | `dropColumn(table, column)` | DROP COLUMN |
165
+ | `modifyColumn(table, name, column)` | MODIFY COLUMN |
166
+ | `renameColumn(table, column, newName)` | RENAME COLUMN |
167
+ | `addPrimaryKey(table, columns)` | ADD PRIMARY KEY |
168
+ | `dropPrimaryKey(table)` | DROP PRIMARY KEY |
169
+ | `addForeignKey(table, name, def)` | ADD FOREIGN KEY |
170
+ | `dropForeignKey(table, name)` | DROP FOREIGN KEY |
171
+ | `addIndex(table, indexBuilder)` | CREATE INDEX |
172
+ | `dropIndex(table, columns)` | DROP INDEX |
173
+ | `clearSchema(params)` | Clear all objects in schema |
174
+ | `schemaExists(database, schema?)` | Check schema existence |
175
+ | `truncate(table)` | TRUNCATE TABLE |
176
+ | `switchFk(table, enabled)` | Enable/disable FK constraints |
177
+
178
+ ## DbContextStatus
179
+
180
+ ```typescript
181
+ type DbContextStatus = "ready" | "connect" | "transact";
182
+ ```
183
+
184
+ Lifecycle: `ready` -> `connect` -> `transact` -> `connect` -> `ready`
185
+
186
+ ## DbTransactionError
187
+
188
+ Standardized transaction error wrapping DBMS-specific native errors.
189
+
190
+ ```typescript
191
+ class DbTransactionError extends Error {
192
+ readonly name = "DbTransactionError";
193
+ constructor(
194
+ public readonly code: DbErrorCode,
195
+ message: string,
196
+ public readonly originalError?: unknown,
197
+ );
198
+ }
199
+ ```
200
+
201
+ ## DbErrorCode
202
+
203
+ ```typescript
204
+ enum DbErrorCode {
205
+ NO_ACTIVE_TRANSACTION = "NO_ACTIVE_TRANSACTION",
206
+ TRANSACTION_ALREADY_STARTED = "TRANSACTION_ALREADY_STARTED",
207
+ DEADLOCK = "DEADLOCK",
208
+ LOCK_TIMEOUT = "LOCK_TIMEOUT",
209
+ }
210
+ ```
211
+
212
+ **Example:**
213
+
214
+ ```typescript
215
+ try {
216
+ await executor.rollbackTransaction();
217
+ } catch (err) {
218
+ if (err instanceof DbTransactionError) {
219
+ if (err.code === DbErrorCode.NO_ACTIVE_TRANSACTION) {
220
+ return; // Already rolled back
221
+ }
222
+ }
223
+ throw err;
224
+ }
225
+ ```