@simplysm/orm-common 13.0.84 → 13.0.86

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 CHANGED
@@ -1,106 +1,112 @@
1
1
  # @simplysm/orm-common
2
2
 
3
- > Simplysm Package - ORM Module (common)
3
+ 플랫폼 중립적인 ORM 핵심 모듈. 스키마 정의, 타입 안전한 쿼리 빌더, DDL 관리, MySQL/PostgreSQL/MSSQL 방언 지원.
4
4
 
5
- A dialect-independent ORM framework for TypeScript that supports MySQL, MSSQL, and PostgreSQL. Provides type-safe schema definitions, fluent query building, expression AST generation, and automatic DDL/migration management -- all without writing raw SQL.
6
-
7
- ## Installation
5
+ ## 설치
8
6
 
9
7
  ```bash
10
8
  npm install @simplysm/orm-common
11
9
  ```
12
10
 
13
- ## Key Concepts
11
+ **의존성:** `@simplysm/core-common`
12
+
13
+ ## 문서
14
+
15
+ | 카테고리 | 설명 |
16
+ |---------|------|
17
+ | [스키마 정의](docs/schema.md) | Table, View, Procedure, Column, Relation, Index 빌더 |
18
+ | [쿼리 & 표현식](docs/query.md) | Queryable, Executable, expr 표현식 빌더 |
19
+ | [DDL & 초기화](docs/ddl.md) | DDL 메서드, 스키마 초기화, 마이그레이션 |
14
20
 
15
- - **Schema Builders** -- Define tables, views, and procedures with a fluent API (`Table`, `View`, `Procedure`)
16
- - **DbContext** -- Central entry point that combines schema definitions with a database executor for connection/transaction management
17
- - **Queryable** -- Chainable query builder for SELECT, INSERT, UPDATE, DELETE, JOIN, GROUP BY, UNION, and recursive CTE
18
- - **Expression Builder (`expr`)** -- Dialect-independent expression AST generator for WHERE conditions, SELECT projections, aggregations, window functions, and more
19
- - **Query Builder** -- Converts expression ASTs into dialect-specific SQL (MySQL, MSSQL, PostgreSQL)
20
- - **Search Parser** -- Parses user search strings into structured SQL LIKE patterns
21
+ ## 빠른 시작
21
22
 
22
- ## Quick Start
23
+ ### DbContext 정의
23
24
 
24
25
  ```typescript
25
- import {
26
- Table, View, Procedure,
27
- defineDbContext, createDbContext,
28
- expr,
29
- } from "@simplysm/orm-common";
30
-
31
- // 1. Define tables
32
- const User = Table("User")
26
+ import { defineDbContext, createDbContext, Table, expr } from "@simplysm/orm-common";
27
+
28
+ // 테이블 정의
29
+ const User = Table("user")
33
30
  .columns((c) => ({
34
- id: c.bigint().autoIncrement(),
31
+ id: c.int().autoIncrement(),
35
32
  name: c.varchar(100),
36
33
  email: c.varchar(200).nullable(),
37
- status: c.varchar(20).default("active"),
34
+ createdAt: c.datetime(),
38
35
  }))
39
36
  .primaryKey("id")
40
37
  .indexes((i) => [i.index("email").unique()]);
41
38
 
42
- const Post = Table("Post")
39
+ const Order = Table("order")
43
40
  .columns((c) => ({
44
- id: c.bigint().autoIncrement(),
45
- authorId: c.bigint(),
46
- title: c.varchar(200),
47
- content: c.text(),
41
+ id: c.int().autoIncrement(),
42
+ userId: c.int(),
43
+ amount: c.decimal(10, 2),
48
44
  }))
49
45
  .primaryKey("id")
50
46
  .relations((r) => ({
51
- author: r.foreignKey(["authorId"], () => User),
47
+ user: r.foreignKey(["userId"], () => User),
52
48
  }));
53
49
 
54
- // 2. Define DbContext
50
+ // DbContext 정의 (스키마 블루프린트)
55
51
  const MyDb = defineDbContext({
56
- tables: { user: User, post: Post },
52
+ tables: { user: User, order: Order },
57
53
  });
58
54
 
59
- // 3. Create instance with executor
55
+ // DbContext 인스턴스 생성 (런타임)
60
56
  const db = createDbContext(MyDb, executor, { database: "mydb" });
57
+ ```
58
+
59
+ ### 쿼리 실행
61
60
 
62
- // 4. Query
61
+ ```typescript
63
62
  await db.connect(async () => {
64
- // SELECT with WHERE
65
- const activeUsers = await db.user()
66
- .where((u) => [expr.eq(u.status, "active")])
63
+ // SELECT
64
+ const users = await db.user()
65
+ .where((c) => [expr.eq(c.name, "Alice")])
66
+ .orderBy((c) => c.createdAt, "DESC")
67
+ .execute();
68
+
69
+ // JOIN (관계 기반)
70
+ const orders = await db.order()
71
+ .include((c) => c.user)
72
+ .where((c) => [expr.gt(c.amount, 100)])
67
73
  .execute();
68
74
 
69
- // JOIN
70
- const postsWithAuthor = await db.post()
71
- .include((p) => p.author)
75
+ // 집계
76
+ const stats = await db.order()
77
+ .select((c) => ({
78
+ userId: c.userId,
79
+ total: expr.sum(c.amount),
80
+ count: expr.count(),
81
+ }))
82
+ .groupBy((c) => [c.userId])
72
83
  .execute();
73
84
 
74
85
  // INSERT
75
- await db.user().insert([{ name: "Alice" }]);
86
+ await db.user().insert([{ name: "Bob", email: "bob@example.com", createdAt: new DateTime() }]);
87
+
88
+ // INSERT 후 ID 반환
89
+ const [inserted] = await db.user().insert(
90
+ [{ name: "Charlie", createdAt: new DateTime() }],
91
+ ["id"],
92
+ );
76
93
 
77
94
  // UPDATE
78
95
  await db.user()
79
- .where((u) => [expr.eq(u.id, 1)])
80
- .update(() => ({ name: expr.val("string", "Bob") }));
96
+ .where((c) => [expr.eq(c.id, 1)])
97
+ .update((c) => ({ name: expr.val("string", "Alice2") }));
81
98
 
82
99
  // DELETE
83
100
  await db.user()
84
- .where((u) => [expr.eq(u.id, 1)])
101
+ .where((c) => [expr.eq(c.id, 1)])
85
102
  .delete();
86
103
  });
87
104
  ```
88
105
 
89
- ## Documentation
90
-
91
- | Category | Description | File |
92
- |----------|-------------|------|
93
- | Schema Builders | Table, View, Procedure definitions and column/index/relation factories | [docs/schema-builders.md](docs/schema-builders.md) |
94
- | DbContext | defineDbContext, createDbContext, connection/transaction management, DDL methods | [docs/db-context.md](docs/db-context.md) |
95
- | Queryable | Chainable query builder for SELECT, INSERT, UPDATE, DELETE, JOIN, GROUP BY, etc. | [docs/queryable.md](docs/queryable.md) |
96
- | Expression Builder | Dialect-independent expression AST (`expr.*`) for conditions, projections, aggregations | [docs/expressions.md](docs/expressions.md) |
97
- | Query Builder | QueryDef to SQL rendering for MySQL, MSSQL, PostgreSQL | [docs/query-builder.md](docs/query-builder.md) |
98
- | Types and Utilities | Column types, database types, error handling, search parsing, result parsing | [docs/types-and-utilities.md](docs/types-and-utilities.md) |
99
-
100
- ## Supported Dialects
106
+ ### 지원 방언
101
107
 
102
- | Dialect | Version | Notes |
103
- |---------|---------|-------|
104
- | MySQL | 8.0.14+ | `database.name` namespace |
105
- | MSSQL | 2012+ | `database.schema.name` namespace (default schema: `dbo`) |
106
- | PostgreSQL | 9.0+ | `schema.name` namespace (default schema: `public`) |
108
+ | 방언 | | 최소 버전 |
109
+ |------|-----|----------|
110
+ | MySQL | `"mysql"` | 8.0.14+ |
111
+ | MSSQL | `"mssql"` | 2012+ |
112
+ | PostgreSQL | `"postgresql"` | 9.0+ |
package/docs/ddl.md ADDED
@@ -0,0 +1,195 @@
1
+ # DDL & 초기화
2
+
3
+ `createDbContext()`로 생성된 DbContext 인스턴스에서 사용하는 DDL 메서드.
4
+
5
+ **중요:** DDL 연산은 트랜잭션 내에서 실행할 수 없다. `connectWithoutTransaction()` 내에서 사용해야 한다.
6
+
7
+ ## 스키마 초기화
8
+
9
+ ```typescript
10
+ await db.connectWithoutTransaction(async () => {
11
+ // 정의된 모든 테이블/뷰/프로시저 자동 생성
12
+ await db.initialize();
13
+
14
+ // 기존 스키마 삭제 후 재생성
15
+ await db.initialize({ force: true });
16
+
17
+ // 특정 DB만 초기화
18
+ await db.initialize({ dbs: ["mydb"] });
19
+ });
20
+ ```
21
+
22
+ ## 테이블 DDL
23
+
24
+ ```typescript
25
+ // 테이블 생성 (TableBuilder 전달)
26
+ await db.createTable(User);
27
+
28
+ // 테이블 삭제 (QueryDefObjectName 전달)
29
+ await db.dropTable({ name: "user", database: "mydb" });
30
+
31
+ // 테이블 이름 변경
32
+ await db.renameTable({ name: "user" }, "users_v2");
33
+ ```
34
+
35
+ ## 컬럼 DDL
36
+
37
+ ```typescript
38
+ import { createColumnFactory } from "@simplysm/orm-common";
39
+ const c = createColumnFactory();
40
+
41
+ // 컬럼 추가
42
+ await db.addColumn({ name: "user" }, "phone", c.varchar(20).nullable());
43
+
44
+ // 컬럼 수정
45
+ await db.modifyColumn({ name: "user" }, "phone", c.varchar(50).nullable());
46
+
47
+ // 컬럼 이름 변경
48
+ await db.renameColumn({ name: "user" }, "phone", "phoneNumber");
49
+
50
+ // 컬럼 삭제
51
+ await db.dropColumn({ name: "user" }, "phone");
52
+ ```
53
+
54
+ ## 키/인덱스 DDL
55
+
56
+ ```typescript
57
+ // PK
58
+ await db.addPrimaryKey({ name: "user" }, ["id"]);
59
+ await db.dropPrimaryKey({ name: "user" });
60
+
61
+ // FK
62
+ await db.addForeignKey({ name: "order" }, "user", userRelationDef);
63
+ await db.dropForeignKey({ name: "order" }, "user");
64
+
65
+ // 인덱스
66
+ await db.addIndex({ name: "user" }, indexBuilder);
67
+ await db.dropIndex({ name: "user" }, ["email"]);
68
+ ```
69
+
70
+ ## 뷰/프로시저 DDL
71
+
72
+ ```typescript
73
+ await db.createView(UserSummary);
74
+ await db.dropView({ name: "user_summary" });
75
+
76
+ await db.createProc(GetUserOrders);
77
+ await db.dropProc({ name: "get_user_orders" });
78
+ ```
79
+
80
+ ## 스키마 관리
81
+
82
+ ```typescript
83
+ // 스키마 존재 여부
84
+ const exists = await db.schemaExists("mydb", "dbo");
85
+
86
+ // 스키마 내 모든 테이블 삭제
87
+ await db.clearSchema({ database: "mydb", schema: "dbo" });
88
+
89
+ // 테이블 TRUNCATE
90
+ await db.truncate({ name: "user" });
91
+
92
+ // FK 제약조건 토글
93
+ await db.switchFk({ name: "user" }, false); // FK 비활성화
94
+ // ... 벌크 작업 ...
95
+ await db.switchFk({ name: "user" }, true); // FK 활성화
96
+ ```
97
+
98
+ ## 연결 & 트랜잭션
99
+
100
+ ```typescript
101
+ // 자동 트랜잭션 (connect -> begin -> callback -> commit/rollback -> close)
102
+ const result = await db.connect(async () => {
103
+ await db.user().insert([{ name: "Alice", createdAt: new DateTime() }]);
104
+ return await db.user().execute();
105
+ }, "SERIALIZABLE"); // 격리 수준 선택
106
+
107
+ // 트랜잭션 없이 연결 (DDL 작업 등)
108
+ await db.connectWithoutTransaction(async () => {
109
+ await db.initialize();
110
+ });
111
+
112
+ // 수동 트랜잭션 (connectWithoutTransaction 내에서)
113
+ await db.connectWithoutTransaction(async () => {
114
+ // DDL 먼저 실행 (트랜잭션 밖)
115
+ await db.createTable(NewTable);
116
+
117
+ // 이후 트랜잭션 내에서 DML 실행
118
+ await db.transaction(async () => {
119
+ await db.user().insert([{ name: "Bob", createdAt: new DateTime() }]);
120
+ });
121
+ });
122
+ ```
123
+
124
+ 격리 수준: `"READ_UNCOMMITTED"`, `"READ_COMMITTED"`, `"REPEATABLE_READ"`, `"SERIALIZABLE"`
125
+
126
+ ## DbContextExecutor 인터페이스
127
+
128
+ Node.js(`@simplysm/orm-node`)나 서비스 클라이언트(`@simplysm/service-client`)에서 구현.
129
+
130
+ ```typescript
131
+ interface DbContextExecutor {
132
+ connect(): Promise<void>;
133
+ close(): Promise<void>;
134
+ beginTransaction(isolationLevel?: IsolationLevel): Promise<void>;
135
+ commitTransaction(): Promise<void>;
136
+ rollbackTransaction(): Promise<void>;
137
+ executeDefs<T>(defs: QueryDef[], resultMetas?: (ResultMeta | undefined)[]): Promise<T[][]>;
138
+ }
139
+ ```
140
+
141
+ ## 마이그레이션
142
+
143
+ `defineDbContext`의 `migrations` 옵션으로 스키마 변경사항을 관리한다. `initialize()` 호출 시 아직 실행되지 않은 마이그레이션만 실행된다.
144
+
145
+ ```typescript
146
+ const MyDb = defineDbContext({
147
+ tables: { user: User },
148
+ migrations: [
149
+ {
150
+ name: "20260105_001_create_user_table",
151
+ up: async (db) => {
152
+ await db.createTable(User);
153
+ },
154
+ },
155
+ {
156
+ name: "20260106_001_add_email_column",
157
+ up: async (db) => {
158
+ const c = createColumnFactory();
159
+ await db.addColumn({ name: "user" }, "email", c.varchar(200).nullable());
160
+ },
161
+ },
162
+ ],
163
+ });
164
+ ```
165
+
166
+ 실행된 마이그레이션은 `_migration` 테이블에 기록된다.
167
+
168
+ ```typescript
169
+ // 마이그레이션 코드 조회
170
+ const migrations = await db._migration().execute();
171
+ ```
172
+
173
+ ## DbTransactionError
174
+
175
+ 트랜잭션 관련 에러를 DBMS 독립적으로 처리하기 위한 에러 클래스.
176
+
177
+ ```typescript
178
+ import { DbTransactionError, DbErrorCode } from "@simplysm/orm-common";
179
+
180
+ // DbErrorCode:
181
+ // - NO_ACTIVE_TRANSACTION: 활성 트랜잭션 없음
182
+ // - TRANSACTION_ALREADY_STARTED: 트랜잭션 이미 시작됨
183
+ // - DEADLOCK: 데드락 발생
184
+ // - LOCK_TIMEOUT: 잠금 타임아웃
185
+ ```
186
+
187
+ ## QueryDef 생성기
188
+
189
+ DDL 메서드 외에도 `get*QueryDef()` 메서드로 QueryDef만 생성할 수 있다 (직접 실행하지 않음).
190
+
191
+ ```typescript
192
+ const def = db.getCreateTableQueryDef(User);
193
+ const def2 = db.getAddColumnQueryDef({ name: "user" }, "phone", c.varchar(20));
194
+ // ... 등. 모든 DDL 메서드에 대응하는 get*QueryDef() 메서드가 있다.
195
+ ```