@simplysm/sd-claude 14.0.98 → 14.0.99
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/claude/references/sd-simplysm14/README.md +16 -16
- package/claude/references/sd-simplysm14/apis/angular/README.md +81 -153
- package/claude/references/sd-simplysm14/apis/angular/controls.md +179 -205
- package/claude/references/sd-simplysm14/apis/angular/crud.md +71 -57
- package/claude/references/sd-simplysm14/apis/angular/directives.md +49 -109
- package/claude/references/sd-simplysm14/apis/angular/features.md +58 -86
- package/claude/references/sd-simplysm14/apis/angular/kanban.md +32 -40
- package/claude/references/sd-simplysm14/apis/angular/layout.md +38 -52
- package/claude/references/sd-simplysm14/apis/angular/overlay.md +86 -110
- package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +54 -86
- package/claude/references/sd-simplysm14/apis/angular/shared-data.md +82 -74
- package/claude/references/sd-simplysm14/apis/angular/sheet.md +56 -80
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +15 -15
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +21 -21
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +79 -53
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +9 -11
- package/claude/references/sd-simplysm14/apis/core-browser/README.md +15 -15
- package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +20 -20
- package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +18 -18
- package/claude/references/sd-simplysm14/apis/core-common/README.md +20 -49
- package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +66 -55
- package/claude/references/sd-simplysm14/apis/core-common/collection-ext.md +83 -56
- package/claude/references/sd-simplysm14/apis/core-common/errors.md +32 -21
- package/claude/references/sd-simplysm14/apis/core-common/obj.md +57 -39
- package/claude/references/sd-simplysm14/apis/core-common/serialization.md +36 -30
- package/claude/references/sd-simplysm14/apis/core-common/value-types.md +69 -41
- package/claude/references/sd-simplysm14/apis/core-node/README.md +4 -4
- package/claude/references/sd-simplysm14/apis/core-node/consola.md +15 -13
- package/claude/references/sd-simplysm14/apis/core-node/cpx.md +11 -7
- package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +8 -8
- package/claude/references/sd-simplysm14/apis/core-node/fsx.md +29 -20
- package/claude/references/sd-simplysm14/apis/core-node/pathx.md +14 -6
- package/claude/references/sd-simplysm14/apis/core-node/worker.md +3 -3
- package/claude/references/sd-simplysm14/apis/excel/README.md +3 -3
- package/claude/references/sd-simplysm14/apis/excel/cell.md +32 -32
- package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +23 -24
- package/claude/references/sd-simplysm14/apis/excel/style.md +24 -30
- package/claude/references/sd-simplysm14/apis/excel/utils.md +20 -23
- package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +60 -71
- package/claude/references/sd-simplysm14/apis/excel/wrapper.md +36 -36
- package/claude/references/sd-simplysm14/apis/lint/README.md +7 -9
- package/claude/references/sd-simplysm14/apis/lint/recommended.md +59 -37
- package/claude/references/sd-simplysm14/apis/lint/rules.md +81 -74
- package/claude/references/sd-simplysm14/apis/orm-common/README.md +6 -6
- package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +112 -78
- package/claude/references/sd-simplysm14/apis/orm-common/expr.md +131 -75
- package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +126 -82
- package/claude/references/sd-simplysm14/apis/orm-common/schema.md +170 -113
- package/claude/references/sd-simplysm14/apis/orm-common/types.md +102 -48
- package/claude/references/sd-simplysm14/apis/orm-node/README.md +12 -13
- package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +3 -3
- package/claude/references/sd-simplysm14/apis/sd-cli/README.md +5 -5
- package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +67 -65
- package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +130 -123
- package/claude/references/sd-simplysm14/apis/service-client/README.md +63 -63
- package/claude/references/sd-simplysm14/apis/service-client/orm.md +22 -22
- package/claude/references/sd-simplysm14/apis/service-client/transport.md +30 -26
- package/claude/references/sd-simplysm14/apis/service-common/README.md +8 -8
- package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +13 -6
- package/claude/references/sd-simplysm14/apis/service-common/protocol.md +1 -1
- package/claude/references/sd-simplysm14/apis/service-server/README.md +43 -47
- package/claude/references/sd-simplysm14/apis/service-server/built-in-services.md +35 -0
- package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +20 -19
- package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +23 -25
- package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +9 -9
- package/claude/references/sd-simplysm14/apis/storage/README.md +26 -26
- package/claude/references/sd-simplysm14/manuals/client-component.md +9 -1
- package/claude/references/sd-simplysm14/manuals/client-crud.md +1 -1
- package/claude/references/sd-simplysm14/manuals/client-orm.md +1 -0
- package/claude/references/sd-simplysm14/manuals/client-service.md +1 -0
- package/claude/references/sd-simplysm14/manuals/client-shared-data.md +1 -0
- package/claude/references/sd-simplysm14/manuals/client-ssg.md +1 -0
- package/claude/sd-system-prompt.md +11 -26
- package/claude/skills/sd-docs/references/subagent-prompt.md +4 -3
- package/claude/skills/sd-spec/SKILL.md +87 -18
- package/claude/skills/sd-spec/references/format.md +2 -2
- package/package.json +1 -1
|
@@ -1,128 +1,162 @@
|
|
|
1
|
-
# @simplysm/orm-common —
|
|
1
|
+
# @simplysm/orm-common — db-context
|
|
2
2
|
|
|
3
|
-
`DbContext`
|
|
3
|
+
`DbContext` 를 상속해 테이블·뷰·프로시저를 프로퍼티로 등록하고, 연결·트랜잭션 경계·DDL·마이그레이션을 다루는 군. 실제 DB I/O 는 생성자에 주입하는 `DbContextExecutor` 가 담당하고, `DbContext` 는 QueryDef 생성·트랜잭션 상태 관리·DDL 실행 오케스트레이션만 한다.
|
|
4
4
|
|
|
5
|
-
## DbContext
|
|
5
|
+
## DbContext
|
|
6
6
|
|
|
7
7
|
```typescript
|
|
8
8
|
abstract class DbContext implements DbContextBase {
|
|
9
|
+
status: DbContextStatus; // "ready" | "connect" | "transact"
|
|
10
|
+
migrations: Migration[]; // 서브클래스에서 오버라이드
|
|
9
11
|
constructor(executor: DbContextExecutor, opt: { database: string; schema?: string });
|
|
10
|
-
status: DbContextStatus; // "ready" | "connect" | "transact"
|
|
11
|
-
get database(): string | undefined;
|
|
12
|
-
get schema(): string | undefined;
|
|
13
|
-
migrations: Migration[]; // 서브클래스에서 오버라이드
|
|
14
|
-
}
|
|
15
|
-
```
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
protected queryable<T extends TableBuilder | ViewBuilder>(builder: T): () => Queryable<...>;
|
|
14
|
+
protected executable<T extends ProcedureBuilder>(builder: T): () => Executable<...>;
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
connect<R>(fn: () => Promise<R>, isolationLevel?: IsolationLevel): Promise<R>;
|
|
17
|
+
connectWithoutTransaction<R>(callback: () => Promise<R>): Promise<R>;
|
|
18
|
+
transaction<R>(fn: () => Promise<R>, isolationLevel?: IsolationLevel): Promise<R>;
|
|
19
|
+
initialize(options?: { dbs?: string[]; force?: boolean }): Promise<boolean>;
|
|
20
|
+
// + DDL 실행 메서드 / DDL QueryDef 생성기 (아래)
|
|
21
|
+
}
|
|
22
|
+
```
|
|
24
23
|
|
|
25
|
-
###
|
|
24
|
+
### 등록 메서드 (protected — 서브클래스에서 프로퍼티 정의용)
|
|
26
25
|
|
|
27
|
-
- `queryable(builder)` —
|
|
28
|
-
- `executable(builder)` —
|
|
26
|
+
- `this.queryable(builder)` — Table/View 빌더를 등록해 `() => Queryable` 팩토리를 반환. 호출할 때마다 새 alias 가 부여됨. 각 테이블을 별도 프로퍼티로 두면 40+ 테이블에서도 TS7056 직렬화 한계를 피함.
|
|
27
|
+
- `this.executable(builder)` — Procedure 빌더를 등록해 `() => Executable` 팩토리를 반환.
|
|
29
28
|
|
|
30
29
|
```typescript
|
|
31
|
-
class
|
|
30
|
+
class MainDb extends DbContext {
|
|
32
31
|
user = this.queryable(User);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
post = this.queryable(Post);
|
|
33
|
+
activeUsers = this.queryable(ActiveUsers);
|
|
34
|
+
getUserById = this.executable(GetUserById);
|
|
35
|
+
|
|
36
|
+
migrations = [{ name: "001", up: async (db) => { await db.createTable(User); } }];
|
|
36
37
|
}
|
|
37
|
-
const db = new
|
|
38
|
+
const db = new MainDb(executor, { database: "mydb" });
|
|
38
39
|
```
|
|
39
40
|
|
|
40
|
-
### 연결·트랜잭션
|
|
41
|
+
### 연결·트랜잭션 경계
|
|
41
42
|
|
|
42
|
-
- `connect(fn, isolationLevel?)` — 연결 →
|
|
43
|
-
- `connectWithoutTransaction(callback)` — 트랜잭션 없이 연결만
|
|
44
|
-
- `transaction(fn, isolationLevel?)` — 이미 `connect` 상태에서
|
|
45
|
-
- `isolationLevel
|
|
43
|
+
- `connect(fn, isolationLevel?)` — 연결 → `BEGIN TRANSACTION` → `fn()` → 성공 시 COMMIT, 예외 시 ROLLBACK → 항상 close. 콜백 반환값이 그대로 반환됨. `status` 가 `"ready"` 가 아니면 throw. 첫 호출 시 관계 정의 검증(`validateRelations`)을 1회 수행. **앱에서 권장하는 기본 진입점**.
|
|
44
|
+
- `connectWithoutTransaction(callback)` — 트랜잭션 없이 연결만(자동 BEGIN/COMMIT 없음). 트랜잭션이 불가하거나 불필요한 작업용.
|
|
45
|
+
- `transaction(fn, isolationLevel?)` — 이미 연결된(`connect`/`connectWithoutTransaction` 내부) 상태에서 트랜잭션만 따로 시작. 이미 `"transact"` 면 throw. ROLLBACK 시 활성 트랜잭션이 없으면(`NO_ACTIVE_TRANSACTION`) 그 에러는 무시하고 원본 에러를 던짐.
|
|
46
|
+
- `isolationLevel` — `READ_UNCOMMITTED`/`READ_COMMITTED`/`REPEATABLE_READ`/`SERIALIZABLE`. 미지정 시 executor 기본값.
|
|
46
47
|
|
|
47
48
|
```typescript
|
|
48
|
-
await db.connect(async () => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}); // 콜백 정상 종료 시 commit, throw 시 rollback
|
|
49
|
+
const users = await db.connect(async () => {
|
|
50
|
+
return db.user().where((u) => [expr.eq(u.isActive, true)]).execute();
|
|
51
|
+
});
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
###
|
|
54
|
+
### DbContextBase 핵심 메서드 (executor·내부에서 사용)
|
|
55
|
+
|
|
56
|
+
- `database` / `schema` — 생성자 옵션 게터.
|
|
57
|
+
- `getNextAlias()` — `T1`, `T2`, ... 순차 alias 발급. `resetAliasCounter()` 로 초기화(연결 시작 시 자동).
|
|
58
|
+
- `executeDefs<T>(defs, resultMetas?)` — QueryDef 배열을 executor 로 실행. `"transact"` 상태에서 DDL 타입(`DDL_TYPES`)이 섞이면 throw.
|
|
59
|
+
- `getQueryDefObjectName(tableOrView)` — 빌더 → `{ database?, schema?, name }` 변환.
|
|
60
|
+
- `switchFk(table, enabled)` — FK 제약 활성/비활성(트랜잭션 내 사용 가능, DDL 아님).
|
|
61
|
+
|
|
62
|
+
### DDL 실행 메서드 (Promise<void>)
|
|
55
63
|
|
|
56
|
-
|
|
64
|
+
`executeDefs` 로 즉시 실행. `"transact"` 상태에서는 DDL 차단됨.
|
|
57
65
|
|
|
58
|
-
- `createTable(table)` / `dropTable(name)` / `renameTable(name, newName)`
|
|
59
|
-
- `
|
|
60
|
-
- `
|
|
61
|
-
- `
|
|
62
|
-
- `
|
|
63
|
-
- `
|
|
64
|
-
- `
|
|
65
|
-
- `addIndex(table, indexBuilder)` / `dropIndex(table, columns)` — 인덱스 추가/삭제.
|
|
66
|
-
- `clearSchema({ database, schema? })` — 스키마 내 모든 객체 삭제.
|
|
67
|
-
- `schemaExists(database, schema?)` — 스키마 존재 여부 `boolean` 반환.
|
|
68
|
-
- `switchFk(table, enabled)` — FK 제약 활성/비활성 토글. `enabled` true=활성, false=비활성. DDL 이 아니라 `transact` 상태에서도 호출 가능(대량 적재 시 FK 일시 해제 용도).
|
|
69
|
-
- `getQueryDefObjectName(tableOrView)` — 빌더에서 dialect 네임스페이스가 반영된 `QueryDefObjectName` 산출.
|
|
66
|
+
- 테이블: `createTable(table)` / `dropTable(name)` / `renameTable(name, newName)` / `truncate(name)`
|
|
67
|
+
- 뷰: `createView(view)` / `dropView(name)`
|
|
68
|
+
- 프로시저: `createProc(proc)` / `dropProc(name)`
|
|
69
|
+
- 컬럼: `addColumn(name, columnName, column)` / `dropColumn(name, column)` / `modifyColumn(name, columnName, column)` / `renameColumn(name, column, newName)`
|
|
70
|
+
- 키·인덱스: `addPrimaryKey(name, columns)` / `dropPrimaryKey(name)` / `addForeignKey(name, relationName, relationDef)` / `dropForeignKey(name, relationName)` / `addIndex(name, indexBuilder)` / `dropIndex(name, columns)`
|
|
71
|
+
- 스키마: `clearSchema({ database, schema? })` / `schemaExists(database, schema?): Promise<boolean>`
|
|
72
|
+
- `switchFk(name, enabled)` — DDL 아님(트랜잭션 내 가능).
|
|
70
73
|
|
|
71
|
-
### DDL QueryDef 생성기 (
|
|
74
|
+
### DDL QueryDef 생성기 (`get*QueryDef`)
|
|
72
75
|
|
|
73
|
-
위 실행 메서드와 1:1
|
|
76
|
+
위 실행 메서드와 1:1 대응하되 실행하지 않고 `QueryDef` 만 반환. 마이그레이션·배치에서 여러 DDL 을 모아 한 번에 `executeDefs` 하거나 SQL 을 검사할 때. 예: `getCreateTableQueryDef`, `getAddColumnQueryDef`, `getAddForeignKeyQueryDef`, `getDropIndexQueryDef`, `getTruncateQueryDef`, `getSwitchFkQueryDef` 등. `getCreateObjectQueryDef(builder)` 는 Table/View/Procedure 중 무엇이든 받아 적절한 CREATE QueryDef 를 반환.
|
|
74
77
|
|
|
75
|
-
###
|
|
78
|
+
### initialize
|
|
76
79
|
|
|
77
|
-
- `initialize(options?)` —
|
|
78
|
-
- `options.dbs`: string[] — 대상 데이터베이스 목록 한정(선택).
|
|
79
|
-
- `options.force`: boolean — true 면 강제 재초기화. 스키마를 다시 깔아야 할 때만 사용.
|
|
80
|
-
- `executeDefs(defs, resultMetas?)` — `QueryDef[]` 를 executor 로 실행해 `T[][]`(def 별 결과) 반환. `transact` 상태에서 DDL 타입이 섞이면 throw. 빌더가 만든 def 를 저수준으로 직접 실행할 때 사용.
|
|
80
|
+
- `initialize(options?)` — DbContext 에 등록된 스키마·`migrations` 를 기준으로 DB 를 초기화/마이그레이션. `options.dbs` 로 대상 DB 제한, `options.force` 로 강제 재생성. 변경이 있었으면 `true` 반환.
|
|
81
81
|
|
|
82
|
-
##
|
|
82
|
+
## DbContextStatus
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
type DbContextStatus = "ready" | "connect" | "transact";
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- `"ready"` — 미연결. `connect`/`connectWithoutTransaction` 호출 가능.
|
|
89
|
+
- `"connect"` — 연결됨, 트랜잭션 없음. `transaction` 호출 가능.
|
|
90
|
+
- `"transact"` — 트랜잭션 활성. DDL 실행 차단.
|
|
91
|
+
|
|
92
|
+
## DbContextExecutor
|
|
93
|
+
|
|
94
|
+
`DbContext` 가 위임하는 실제 DB I/O 인터페이스. `@simplysm/orm-node`(서버)·orm-service 클라이언트 등이 구현.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
interface DbContextExecutor {
|
|
98
|
+
connect(): Promise<void>;
|
|
99
|
+
close(): Promise<void>;
|
|
100
|
+
beginTransaction(isolationLevel?: IsolationLevel): Promise<void>;
|
|
101
|
+
commitTransaction(): Promise<void>;
|
|
102
|
+
rollbackTransaction(): Promise<void>;
|
|
103
|
+
executeDefs<T = DataRecord>(defs: QueryDef[], resultMetas?: (ResultMeta | undefined)[]): Promise<T[][]>;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- `executeDefs(defs, resultMetas?)` — QueryDef 배열을 SQL 로 빌드·실행. `resultMetas[i]` 가 주어진 def 의 결과는 `parseQueryResult` 로 타입 변환/중첩되고, 없으면 원시 결과 그대로. 반환은 def 별 결과 배열의 배열.
|
|
108
|
+
|
|
109
|
+
## Migration
|
|
83
110
|
|
|
84
111
|
```typescript
|
|
85
112
|
interface Migration {
|
|
86
|
-
name: string;
|
|
113
|
+
name: string; // 고유 이름 (타임스탬프 권장)
|
|
87
114
|
up: (db: DbContextBase & DbContextDdlMethods) => Promise<void>;
|
|
88
115
|
}
|
|
89
116
|
```
|
|
90
117
|
|
|
91
|
-
- `name
|
|
92
|
-
- `up
|
|
118
|
+
- `name` — 적용 여부를 추적하는 고유 키. 적용된 이름은 `_migration` 시스템 테이블에 적재됨.
|
|
119
|
+
- `up(db)` — 스키마 변경을 수행하는 함수. `db` 로 DDL 실행 메서드 사용.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
migrations = [
|
|
123
|
+
{ name: "20260105_001_create_user", up: async (db) => { await db.createTable(User); } },
|
|
124
|
+
{ name: "20260105_002_add_email", up: async (db) => {
|
|
125
|
+
await db.addColumn(User, "email", { type: "varchar", length: 200 });
|
|
126
|
+
} },
|
|
127
|
+
];
|
|
128
|
+
```
|
|
93
129
|
|
|
94
130
|
## DbTransactionError / DbErrorCode
|
|
95
131
|
|
|
96
|
-
DBMS 네이티브 에러를
|
|
132
|
+
DBMS 별 네이티브 에러를 표준 코드로 래핑. ROLLBACK 시 활성 트랜잭션 없음 등을 코드로 분기.
|
|
97
133
|
|
|
98
134
|
```typescript
|
|
99
135
|
class DbTransactionError extends Error {
|
|
136
|
+
readonly name = "DbTransactionError";
|
|
100
137
|
constructor(code: DbErrorCode, message: string, originalError?: unknown);
|
|
101
138
|
readonly code: DbErrorCode;
|
|
102
139
|
readonly originalError?: unknown;
|
|
103
140
|
}
|
|
104
|
-
enum DbErrorCode {
|
|
141
|
+
enum DbErrorCode {
|
|
142
|
+
NO_ACTIVE_TRANSACTION = "NO_ACTIVE_TRANSACTION", // ROLLBACK 시 활성 트랜잭션 없음
|
|
143
|
+
TRANSACTION_ALREADY_STARTED = "TRANSACTION_ALREADY_STARTED",
|
|
144
|
+
DEADLOCK = "DEADLOCK", // 데드락
|
|
145
|
+
LOCK_TIMEOUT = "LOCK_TIMEOUT", // 잠금 타임아웃
|
|
146
|
+
}
|
|
105
147
|
```
|
|
106
148
|
|
|
107
|
-
- `code
|
|
108
|
-
- `originalError
|
|
109
|
-
- `NO_ACTIVE_TRANSACTION` — 롤백/커밋할 활성 트랜잭션이 없음. 이미 롤백된 경우 무시 분기에 사용.
|
|
110
|
-
- `TRANSACTION_ALREADY_STARTED` — 트랜잭션이 이미 시작됨(중첩 시작 시도).
|
|
111
|
-
- `DEADLOCK` — 교착 상태로 트랜잭션이 강제 중단됨. 재시도 정책 분기에 사용.
|
|
112
|
-
- `LOCK_TIMEOUT` — 잠금 대기 시간 초과. 재시도/백오프 분기에 사용.
|
|
149
|
+
- `code` — DBMS 독립 분류. `connect`/`transaction` 의 롤백 로직이 `NO_ACTIVE_TRANSACTION` 을 무시하는 데 사용.
|
|
150
|
+
- `originalError` — 원본 DBMS 에러(디버깅용).
|
|
113
151
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
throw err;
|
|
120
|
-
}
|
|
121
|
-
```
|
|
152
|
+
## 관련 export
|
|
153
|
+
|
|
154
|
+
- `DbContextBase` / `DbContextDdlMethods` — `DbContext` 가 구현하는 핵심·DDL 인터페이스. executor·`Queryable`·`ViewBuilder` 가 의존.
|
|
155
|
+
- `SD_BUILDER` — `queryable()`/`executable()` 이 반환 팩토리에 빌더를 부착하는 심볼 키(내부용).
|
|
156
|
+
- `_Migration` — 적용된 마이그레이션을 기록하는 시스템 테이블 빌더(`_migration`, PK `code`).
|
|
122
157
|
|
|
123
|
-
##
|
|
158
|
+
## 주의사항
|
|
124
159
|
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
- `SD_BUILDER` (symbol) — `queryable()`/`executable()` 가 반환하는 팩토리 함수에 원본 빌더를 매다는 심볼 키. 등록된 멤버에서 원본 `TableBuilder`/`ViewBuilder`/`ProcedureBuilder` 를 역참조해야 할 때(스키마 수집·DDL 자동화 등) 사용.
|
|
160
|
+
- 테이블은 반드시 **개별 프로퍼티**로(`user = this.queryable(User)`) — 한 객체에 묶으면 TS7056.
|
|
161
|
+
- 앱 코드는 옵션을 흩뿌리지 말고 `connect` 경계 안에서만 쿼리 (client-orm.md 의 `AppOrmProvider` 패턴).
|
|
162
|
+
- DDL 은 트랜잭션 밖에서. `transaction()` 안에서 DDL 메서드 호출 시 `executeDefs` 가 throw.
|
|
@@ -1,111 +1,167 @@
|
|
|
1
|
-
# @simplysm/orm-common — expr
|
|
1
|
+
# @simplysm/orm-common — expr
|
|
2
2
|
|
|
3
|
-
`expr
|
|
3
|
+
dialect 독립 SQL 표현식 빌더 군. `expr.*` 함수가 SQL 문자열 대신 JSON AST(`Expr`)를 만들고, QueryBuilder 가 DBMS 별로 렌더링한다. `where`/`select`/`groupBy`/`orderBy`/`having`/`update` 콜백 안에서 컬럼 프록시(`ExprUnit`)를 받아 조합한다. 비교/논리 함수는 `WhereExprUnit`(WHERE 절용), 그 외는 `ExprUnit<T>`(값 표현식)를 반환한다.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`ExprInput<T> = ExprUnit<T> | T` — 비교 대상·값 인자는 리터럴을 그대로 받는다(`expr.val` 래핑 불필요, orm.md).
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
## ExprUnit / WhereExprUnit / ExprInput
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
class ExprUnit<TPrimitive> {
|
|
11
|
+
readonly dataType: ColumnPrimitiveStr;
|
|
12
|
+
readonly expr: Expr;
|
|
13
|
+
get n(): ExprUnit<NonNullable<TPrimitive>>; // nullable 제거(타입만)
|
|
14
|
+
}
|
|
15
|
+
class WhereExprUnit { readonly expr: WhereExpr; }
|
|
16
|
+
type ExprInput<TPrimitive> = ExprUnit<TPrimitive> | TPrimitive;
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- `ExprUnit<T>` — 타입 안전 표현식 래퍼. `dataType` 은 결과 원시 타입 이름, `expr` 은 AST. 컬럼 프록시의 각 필드가 이 타입.
|
|
20
|
+
- `.n` — 타입 수준에서 `undefined` 를 제거한 새 `ExprUnit`(런타임 AST 동일). nullable 컬럼을 non-null 로 단언할 때.
|
|
21
|
+
- `WhereExprUnit` — `where`/`having` 가 받는 boolean 조건 래퍼.
|
|
22
|
+
- `ExprInput<T>` — 표현식 또는 리터럴.
|
|
11
23
|
|
|
12
24
|
## 값 생성
|
|
13
25
|
|
|
14
|
-
- `val(dataType, value)` — 리터럴을 `ExprUnit` 으로 래핑. `dataType
|
|
15
|
-
- `col(dataType, ...path)` — 컬럼 참조
|
|
16
|
-
- `raw(dataType)\`...\`` — Raw SQL 이스케이프 해치. 태그드
|
|
17
|
-
- `toExpr(value)` — `ExprInput`
|
|
26
|
+
- `expr.val(dataType, value)` — 리터럴을 `ExprUnit` 으로 래핑. `dataType` 은 `"string"|"number"|"boolean"|"DateTime"|"DateOnly"|"Time"|"Uuid"|"Bytes"`. `value` 가 `undefined` 면 결과 타입이 nullable. **`select` 등 `ExprUnit` 이 요구되는 자리에서만** 사용(비교·CUD 값은 리터럴 직접 전달).
|
|
27
|
+
- `expr.col(dataType, ...path)` — 컬럼 참조(`{ type:"column", path }`). 보통 프록시가 자동 생성하므로 직접 호출은 드묾.
|
|
28
|
+
- `expr.raw(dataType)\`...\`` — Raw SQL 이스케이프 해치. 태그드 템플릿이며 보간값(`${u.x}`)은 자동 파라미터화. ORM 미지원 DB 함수·UNION NULL 자리채움(`expr.raw("number")\`NULL\``, orm-union.md)에 사용.
|
|
29
|
+
- `expr.toExpr(value)` — `ExprInput` → `Expr` 변환(내부 헬퍼).
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
db.user().select((u) => ({ name: u.name, label: expr.val("string", "fixed") }));
|
|
33
|
+
expr.raw("string")`JSON_EXTRACT(${u.metadata}, '$.email')`;
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## WHERE — 비교 (→ WhereExprUnit)
|
|
18
37
|
|
|
19
|
-
|
|
38
|
+
- `expr.eq(source, target)` — 동등 비교. **NULL 안전**(MySQL `<=>`, 그 외 `IS NULL OR =`).
|
|
39
|
+
- `expr.gt` / `expr.lt` / `expr.gte` / `expr.lte` — `>` / `<` / `>=` / `<=`.
|
|
40
|
+
- `expr.between(source, from?, to?)` — 범위(BETWEEN). `from`/`to` 가 undefined 면 그 방향 제한 없음(한쪽만 주면 `>=`/`<=`).
|
|
41
|
+
- `expr.null(source)` — IS NULL.
|
|
42
|
+
- `expr.like(source, pattern)` — LIKE(`%`/`_` 와일드카드, `\` 이스케이프).
|
|
43
|
+
- `expr.regexp(source, pattern)` — 정규식 매칭(구문은 DBMS 의존).
|
|
44
|
+
- `expr.in(source, values)` — IN 값 목록.
|
|
45
|
+
- `expr.inQuery(source, query)` — IN (SELECT). 서브쿼리는 **단일 컬럼만** SELECT 해야 함(아니면 throw).
|
|
46
|
+
- `expr.exists(query)` — EXISTS(서브쿼리가 1행 이상이면 true). SELECT 절을 제거해 패킷 절약.
|
|
20
47
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
48
|
+
```typescript
|
|
49
|
+
db.user().where((u) => [expr.eq(u.status, "active"), expr.between(u.age, 18, 65)]);
|
|
50
|
+
db.user().where((u) => [expr.inQuery(u.id, db.order().select((o) => ({ userId: o.userId })))]);
|
|
51
|
+
```
|
|
25
52
|
|
|
26
|
-
## WHERE —
|
|
53
|
+
## WHERE — 논리 (→ WhereExprUnit)
|
|
27
54
|
|
|
28
|
-
- `
|
|
29
|
-
- `
|
|
30
|
-
- `
|
|
31
|
-
- `inQuery(source, query)` — `IN (SELECT 단일컬럼)`. 서브쿼리가 단일 컬럼 select 가 아니면 throw.
|
|
32
|
-
- `exists(query)` — `EXISTS (...)`. 서브쿼리 SELECT 절은 패킷 절약 위해 제거됨. WHERE 절 존재 검사용(SELECT 절에는 쓰지 말 것).
|
|
33
|
-
- `not(arg)` — 조건 부정.
|
|
34
|
-
- `and(conditions)` / `or(conditions)` — 조건 배열 AND/OR 결합. 빈 배열이면 `ArgumentError`. (`where` 에 배열을 넘기면 자동 AND 이므로 `and` 는 OR 안에서 묶을 때 등에 사용.)
|
|
55
|
+
- `expr.not(arg)` — 조건 부정(NOT).
|
|
56
|
+
- `expr.and(conditions)` — AND 결합. **빈 배열이면 `ArgumentError`**.
|
|
57
|
+
- `expr.or(conditions)` — OR 결합. **빈 배열이면 `ArgumentError`**.
|
|
35
58
|
|
|
36
59
|
```typescript
|
|
37
|
-
db.user().where((u) => [
|
|
38
|
-
expr.eq(u.status, "active"),
|
|
39
|
-
expr.between(u.age, 18, undefined),
|
|
40
|
-
expr.or([expr.like(u.name, "김%"), expr.like(u.name, "이%")]),
|
|
41
|
-
])
|
|
60
|
+
db.user().where((u) => [expr.or([expr.eq(u.status, "active"), expr.eq(u.status, "pending")])]);
|
|
42
61
|
```
|
|
43
62
|
|
|
44
|
-
## SELECT — 문자열 (
|
|
63
|
+
## SELECT — 문자열 (→ ExprUnit)
|
|
64
|
+
|
|
65
|
+
- `expr.concat(...args)` — CONCAT(NULL 은 빈 문자열).
|
|
66
|
+
- `expr.left(source, length)` / `expr.right(source, length)` — 왼/오른쪽 N자.
|
|
67
|
+
- `expr.trim(source)` — 양쪽 공백 제거.
|
|
68
|
+
- `expr.padStart(source, length, fillString)` — LPAD(목표 길이까지 왼쪽 채움).
|
|
69
|
+
- `expr.replace(source, from, to)` — 치환.
|
|
70
|
+
- `expr.upper(source)` / `expr.lower(source)` — 대/소문자.
|
|
71
|
+
- `expr.length(source)` — 문자 수. `expr.byteLength(source)` — 바이트 수(UTF-8 CJK 3바이트).
|
|
72
|
+
- `expr.substring(source, start, length?)` — 부분 문자열(**1부터 시작**).
|
|
73
|
+
- `expr.indexOf(source, search)` — 위치(1부터, 없으면 0).
|
|
45
74
|
|
|
46
|
-
|
|
47
|
-
- `left(source, length)` / `right(source, length)` — 왼쪽/오른쪽 N자 추출.
|
|
48
|
-
- `trim(source)` — 양쪽 공백 제거.
|
|
49
|
-
- `padStart(source, length, fillString)` — `LPAD`. `length` 도달까지 `fillString` 으로 왼쪽 패딩(주문번호 zero-pad 등).
|
|
50
|
-
- `replace(source, from, to)` — 문자열 치환.
|
|
51
|
-
- `upper(source)` / `lower(source)` — 대/소문자 변환.
|
|
52
|
-
- `length(source)` — 문자 수. `byteLength(source)` — 바이트 수(UTF-8 CJK 3바이트).
|
|
53
|
-
- `substring(source, start, length?)` — 부분 문자열(1부터 시작, `length` 생략 시 끝까지).
|
|
54
|
-
- `indexOf(source, search)` — 위치 찾기(1부터, 없으면 0).
|
|
75
|
+
## SELECT — 숫자 (→ ExprUnit)
|
|
55
76
|
|
|
56
|
-
|
|
77
|
+
- `expr.abs(source)` — 절대값.
|
|
78
|
+
- `expr.round(source, digits)` — 반올림(소수 자릿수).
|
|
79
|
+
- `expr.ceil(source)` / `expr.floor(source)` — 올림/내림.
|
|
57
80
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- `
|
|
61
|
-
- `
|
|
62
|
-
- `
|
|
63
|
-
- `
|
|
81
|
+
## SELECT — 날짜 (→ ExprUnit)
|
|
82
|
+
|
|
83
|
+
- `expr.year` / `expr.month` / `expr.day` (DateTime|DateOnly), `expr.hour` / `expr.minute` / `expr.second` (DateTime|Time) — 각 단위 추출(number).
|
|
84
|
+
- `expr.isoWeek(source)` — ISO 8601 주 번호(1~53). `expr.isoWeekStartDate(source)` — 그 주의 월요일(DateOnly). `expr.isoYearMonth(source)` — `"YYYYMM"` 문자열.
|
|
85
|
+
- `expr.dateDiff(unit, from, to)` — 날짜 차(`to - from`). `unit` 은 `"year"|"month"|"day"|"hour"|"minute"|"second"`.
|
|
86
|
+
- `expr.dateAdd(unit, source, value)` — 날짜 가감(음수 가능).
|
|
87
|
+
- `expr.formatDate(source, format)` — 포맷 문자열(예 `"%Y-%m-%d"`, DBMS 의존).
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
db.user().select((u) => ({
|
|
91
|
+
age: expr.dateDiff("year", u.birthDate, expr.val("DateOnly", DateOnly.today())),
|
|
92
|
+
}));
|
|
93
|
+
```
|
|
64
94
|
|
|
65
|
-
## SELECT — 조건
|
|
95
|
+
## SELECT — 조건 (→ ExprUnit)
|
|
66
96
|
|
|
67
|
-
- `coalesce(...args)` — 첫 non-null
|
|
68
|
-
- `nullIf(source, value)` — `source === value` 면 NULL, 아니면 source
|
|
69
|
-
- `is(condition)` — `WhereExprUnit` 을 boolean
|
|
70
|
-
- `switch<T>()` — CASE WHEN
|
|
71
|
-
- `if(condition, then, else_)` — 삼항(IIF
|
|
97
|
+
- `expr.coalesce(...args)` — 첫 non-null(COALESCE). 마지막 인자가 non-nullable 이면 결과도 non-nullable(오버로드).
|
|
98
|
+
- `expr.nullIf(source, value)` — `source === value` 면 NULL, 아니면 source.
|
|
99
|
+
- `expr.is(condition)` — `WhereExprUnit` 을 boolean 컬럼으로(SELECT 절에서 조건 결과 노출).
|
|
100
|
+
- `expr.switch<T>()` — CASE WHEN 빌더. `.case(condition, then)` 체이닝 후 `.default(value)` 로 종료해 `ExprUnit` 반환. then/default 중 하나 이상 non-null 이어야 dataType 추론 가능.
|
|
101
|
+
- `expr.if(condition, then, else_)` — 삼항(IF/IIF). then/else 중 하나 이상 non-null 필요.
|
|
72
102
|
|
|
73
103
|
```typescript
|
|
74
104
|
db.user().select((u) => ({
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
105
|
+
grade: expr.switch<string>()
|
|
106
|
+
.case(expr.gte(u.score, 90), "A")
|
|
107
|
+
.case(expr.gte(u.score, 80), "B")
|
|
108
|
+
.default("F"),
|
|
109
|
+
isAdult: expr.is(expr.gte(u.age, 18)),
|
|
110
|
+
}));
|
|
78
111
|
```
|
|
79
112
|
|
|
80
|
-
## SELECT — 집계
|
|
113
|
+
## SELECT — 집계 (→ ExprUnit)
|
|
114
|
+
|
|
115
|
+
NULL 값 행은 무시되고, 행이 없거나 전부 NULL 일 때만 NULL 반환.
|
|
116
|
+
|
|
117
|
+
- `expr.count(arg?, distinct?)` — COUNT. `arg` 없으면 `COUNT(*)`, `distinct: true` 면 중복 제거.
|
|
118
|
+
- `expr.sum(arg)` / `expr.avg(arg)` — SUM/AVG(nullable number).
|
|
119
|
+
- `expr.max(arg)` / `expr.min(arg)` — MAX/MIN(인자 타입 유지, nullable).
|
|
81
120
|
|
|
82
|
-
|
|
121
|
+
## SELECT — 기타 (→ ExprUnit)
|
|
83
122
|
|
|
84
|
-
- `
|
|
85
|
-
- `
|
|
86
|
-
- `
|
|
87
|
-
- `
|
|
88
|
-
- `
|
|
89
|
-
|
|
90
|
-
|
|
123
|
+
- `expr.greatest(...args)` / `expr.least(...args)` — 여러 값 중 최대/최소(GREATEST/LEAST). 인자 중 하나 이상 `ExprUnit` 이어야 dataType 추론(아니면 throw).
|
|
124
|
+
- `expr.rowNum()` — 모든 행에 1부터 순번(단순).
|
|
125
|
+
- `expr.random()` — 0~1 난수(주로 `orderBy` 무작위 정렬).
|
|
126
|
+
- `expr.cast(source, targetType)` — 타입 변환(CAST). `targetType` 은 `DataType`(예 `{ type:"varchar", length:20 }`).
|
|
127
|
+
- `expr.subquery(dataType, queryable)` — 스칼라 서브쿼리(정확히 1행 1컬럼). SELECT 절에서 단일 값. (행당 N회 실행 주의 — 집계는 `joinSingle` 권장, orm.md.)
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
db.order().select((o) => ({ idStr: expr.cast(o.id, { type: "varchar", length: 20 }) }));
|
|
131
|
+
```
|
|
91
132
|
|
|
92
|
-
## SELECT — 윈도우 함수
|
|
133
|
+
## SELECT — 윈도우 함수 (→ ExprUnit, OVER)
|
|
93
134
|
|
|
94
|
-
모두 `spec: { partitionBy?: ExprInput[]; orderBy?: [ExprInput, ("ASC"|"DESC")?][] }` 를
|
|
135
|
+
모두 `spec: { partitionBy?: ExprInput[]; orderBy?: [ExprInput, ("ASC"|"DESC")?][] }` 를 받는다.
|
|
95
136
|
|
|
96
|
-
- `rowNumber(spec)` —
|
|
97
|
-
- `rank(spec)` —
|
|
98
|
-
- `ntile(n, spec)` —
|
|
99
|
-
- `lag(column, spec, options?)` / `lead(column, spec, options?)` — 이전/다음 행 값. `options.offset`(기본 1)·`options.default`(
|
|
100
|
-
- `firstValue(column, spec)` / `lastValue(column, spec)` — 프레임
|
|
101
|
-
- `sumOver
|
|
102
|
-
- `countOver(spec, column?)` — 윈도우 카운트(`column` 생략 시 전체 행).
|
|
103
|
-
- `minOver`/`maxOver`(column, spec) — 윈도우 최소/최대.
|
|
137
|
+
- `expr.rowNumber(spec)` — ROW_NUMBER(파티션 내 1부터).
|
|
138
|
+
- `expr.rank(spec)` — RANK(동순위 후 건너뜀: 1,1,3). `expr.denseRank(spec)` — DENSE_RANK(연속: 1,1,2).
|
|
139
|
+
- `expr.ntile(n, spec)` — 파티션을 n 그룹으로(1~n).
|
|
140
|
+
- `expr.lag(column, spec, options?)` / `expr.lead(column, spec, options?)` — 이전/다음 행 값. `options.offset`(기본 1)·`options.default`(없을 때 값).
|
|
141
|
+
- `expr.firstValue(column, spec)` / `expr.lastValue(column, spec)` — 프레임 첫/끝 값.
|
|
142
|
+
- `expr.sumOver` / `expr.avgOver` / `expr.minOver` / `expr.maxOver` (column, spec) — 윈도우 집계. `expr.countOver(spec, column?)` — 윈도우 COUNT(`column` 생략 시 전체).
|
|
104
143
|
|
|
105
144
|
```typescript
|
|
106
145
|
db.order().select((o) => ({
|
|
107
146
|
...o,
|
|
108
147
|
rowNum: expr.rowNumber({ partitionBy: [o.userId], orderBy: [[o.createdAt, "DESC"]] }),
|
|
109
148
|
runningTotal: expr.sumOver(o.amount, { partitionBy: [o.userId], orderBy: [[o.createdAt, "ASC"]] }),
|
|
110
|
-
}))
|
|
149
|
+
}));
|
|
111
150
|
```
|
|
151
|
+
|
|
152
|
+
## SwitchExprBuilder
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
interface SwitchExprBuilder<TPrimitive> {
|
|
156
|
+
case(condition: WhereExprUnit, then: ExprInput<TPrimitive>): SwitchExprBuilder<TPrimitive>;
|
|
157
|
+
default(value: ExprInput<TPrimitive>): ExprUnit<TPrimitive>;
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`expr.switch<T>()` 의 반환 인터페이스. `case` 누적 후 `default` 로 종료해야 `ExprUnit` 이 나온다.
|
|
162
|
+
|
|
163
|
+
## 주의사항
|
|
164
|
+
|
|
165
|
+
- 비교(`eq` 등)·CUD 값은 리터럴 직접 — `expr.val` 금지(orm.md). `expr.val`/`expr.raw` 는 `ExprUnit` 이 요구되는 select 컬럼·UNION 자리채움에서만.
|
|
166
|
+
- `and`/`or` 에 빈 배열, `inQuery` 에 다중 컬럼 서브쿼리, `greatest`/`least`/`coalesce` 에 ExprUnit 없는 인자 전부 throw.
|
|
167
|
+
- 집계·도출은 SELECT 절 subquery/exists 대신 `joinSingle` 로 outer 행에 부착(orm.md 안티패턴).
|