@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.
Files changed (77) hide show
  1. package/claude/references/sd-simplysm14/README.md +16 -16
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +81 -153
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +179 -205
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +71 -57
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +49 -109
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +58 -86
  7. package/claude/references/sd-simplysm14/apis/angular/kanban.md +32 -40
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +38 -52
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +86 -110
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +54 -86
  11. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +82 -74
  12. package/claude/references/sd-simplysm14/apis/angular/sheet.md +56 -80
  13. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +15 -15
  14. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +21 -21
  15. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +79 -53
  16. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +9 -11
  17. package/claude/references/sd-simplysm14/apis/core-browser/README.md +15 -15
  18. package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +20 -20
  19. package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +18 -18
  20. package/claude/references/sd-simplysm14/apis/core-common/README.md +20 -49
  21. package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +66 -55
  22. package/claude/references/sd-simplysm14/apis/core-common/collection-ext.md +83 -56
  23. package/claude/references/sd-simplysm14/apis/core-common/errors.md +32 -21
  24. package/claude/references/sd-simplysm14/apis/core-common/obj.md +57 -39
  25. package/claude/references/sd-simplysm14/apis/core-common/serialization.md +36 -30
  26. package/claude/references/sd-simplysm14/apis/core-common/value-types.md +69 -41
  27. package/claude/references/sd-simplysm14/apis/core-node/README.md +4 -4
  28. package/claude/references/sd-simplysm14/apis/core-node/consola.md +15 -13
  29. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +11 -7
  30. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +8 -8
  31. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +29 -20
  32. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +14 -6
  33. package/claude/references/sd-simplysm14/apis/core-node/worker.md +3 -3
  34. package/claude/references/sd-simplysm14/apis/excel/README.md +3 -3
  35. package/claude/references/sd-simplysm14/apis/excel/cell.md +32 -32
  36. package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +23 -24
  37. package/claude/references/sd-simplysm14/apis/excel/style.md +24 -30
  38. package/claude/references/sd-simplysm14/apis/excel/utils.md +20 -23
  39. package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +60 -71
  40. package/claude/references/sd-simplysm14/apis/excel/wrapper.md +36 -36
  41. package/claude/references/sd-simplysm14/apis/lint/README.md +7 -9
  42. package/claude/references/sd-simplysm14/apis/lint/recommended.md +59 -37
  43. package/claude/references/sd-simplysm14/apis/lint/rules.md +81 -74
  44. package/claude/references/sd-simplysm14/apis/orm-common/README.md +6 -6
  45. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +112 -78
  46. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +131 -75
  47. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +126 -82
  48. package/claude/references/sd-simplysm14/apis/orm-common/schema.md +170 -113
  49. package/claude/references/sd-simplysm14/apis/orm-common/types.md +102 -48
  50. package/claude/references/sd-simplysm14/apis/orm-node/README.md +12 -13
  51. package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +3 -3
  52. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +5 -5
  53. package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +67 -65
  54. package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +130 -123
  55. package/claude/references/sd-simplysm14/apis/service-client/README.md +63 -63
  56. package/claude/references/sd-simplysm14/apis/service-client/orm.md +22 -22
  57. package/claude/references/sd-simplysm14/apis/service-client/transport.md +30 -26
  58. package/claude/references/sd-simplysm14/apis/service-common/README.md +8 -8
  59. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +13 -6
  60. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +1 -1
  61. package/claude/references/sd-simplysm14/apis/service-server/README.md +43 -47
  62. package/claude/references/sd-simplysm14/apis/service-server/built-in-services.md +35 -0
  63. package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +20 -19
  64. package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +23 -25
  65. package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +9 -9
  66. package/claude/references/sd-simplysm14/apis/storage/README.md +26 -26
  67. package/claude/references/sd-simplysm14/manuals/client-component.md +9 -1
  68. package/claude/references/sd-simplysm14/manuals/client-crud.md +1 -1
  69. package/claude/references/sd-simplysm14/manuals/client-orm.md +1 -0
  70. package/claude/references/sd-simplysm14/manuals/client-service.md +1 -0
  71. package/claude/references/sd-simplysm14/manuals/client-shared-data.md +1 -0
  72. package/claude/references/sd-simplysm14/manuals/client-ssg.md +1 -0
  73. package/claude/sd-system-prompt.md +11 -26
  74. package/claude/skills/sd-docs/references/subagent-prompt.md +4 -3
  75. package/claude/skills/sd-spec/SKILL.md +87 -18
  76. package/claude/skills/sd-spec/references/format.md +2 -2
  77. package/package.json +1 -1
@@ -1,128 +1,162 @@
1
- # @simplysm/orm-common — DbContext / 연결·트랜잭션·DDL·마이그레이션
1
+ # @simplysm/orm-common — db-context
2
2
 
3
- `DbContext` 추상 클래스를 상속해 테이블·뷰·프로시저를 클래스 프로퍼티로 등록하고, 연결·트랜잭션·DDL·마이그레이션을 실행하는 묶음. 생성자에 `DbContextExecutor` 구현체와 `{ database, schema? }` 옵션을 주입한다. 프로퍼티가 독립적으로 직렬화되므로 40+ 테이블에서도 TS7056 발생하지 않는다. 롤백 중 발생하는 트랜잭션 에러는 `DbTransactionError` 로 표준화된다.
3
+ `DbContext` 상속해 테이블·뷰·프로시저를 프로퍼티로 등록하고, 연결·트랜잭션 경계·DDL·마이그레이션을 다루는 군. 실제 DB I/O 생성자에 주입하는 `DbContextExecutor` 담당하고, `DbContext` QueryDef 생성·트랜잭션 상태 관리·DDL 실행 오케스트레이션만 한다.
4
4
 
5
- ## DbContext (abstract class)
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
- 서브클래스에서 `protected queryable(builder)` / `protected executable(builder)` 멤버를 등록한다.
13
+ protected queryable<T extends TableBuilder | ViewBuilder>(builder: T): () => Queryable<...>;
14
+ protected executable<T extends ProcedureBuilder>(builder: T): () => Executable<...>;
18
15
 
19
- - `executor`: `DbContextExecutor` — 실제 connect/close/begin/commit/rollback/executeDefs 수행하는 어댑터. 서버는 node 구현, 클라이언트는 service-client 구현을 넣는다.
20
- - `opt.database`: string 대상 데이터베이스 이름. `database` getter 로 노출.
21
- - `opt.schema`: string MSSQL/PostgreSQL 스키마(선택). 미지정 시 dialect 기본값.
22
- - `status`: `"ready" | "connect" | "transact"` — 현재 연결 단계. "ready"=미연결, "connect"=연결됨(트랜잭션 밖), "transact"=트랜잭션 중. `transact` 상태에서 DDL 실행 시 throw.
23
- - `migrations`: `Migration[]` 서브클래스에서 오버라이드하는 마이그레이션 정의 배열. `initialize()` 가 이미 적용된 것을 제외하고 순서대로 실행.
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
- ### 멤버 등록 (protected)
24
+ ### 등록 메서드 (protected — 서브클래스에서 프로퍼티 정의용)
26
25
 
27
- - `queryable(builder)` — `TableBuilder`/`ViewBuilder` 받아 `() => Queryable<...>` 팩토리를 반환. 호출할 때마다 새 alias 가 부여된 Queryable 생성. CUD `TableBuilder` 기반에서만 가능.
28
- - `executable(builder)` — `ProcedureBuilder` 받아 `() => Executable<...>` 팩토리를 반환.
26
+ - `this.queryable(builder)` — Table/View 빌더를 등록해 `() => Queryable` 팩토리를 반환. 호출할 때마다 새 alias 가 부여됨. 테이블을 별도 프로퍼티로 두면 40+ 테이블에서도 TS7056 직렬화 한계를 피함.
27
+ - `this.executable(builder)` — Procedure 빌더를 등록해 `() => Executable` 팩토리를 반환.
29
28
 
30
29
  ```typescript
31
- class AppDb extends DbContext {
30
+ class MainDb extends DbContext {
32
31
  user = this.queryable(User);
33
- activeUsers = this.queryable(ActiveUsers); // View
34
- getUserById = this.executable(GetUserById); // Procedure
35
- override migrations = [{ name: "001_init", up: async (db) => { await db.createTable(User); } }];
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 AppDb(executor, { database: "mydb" });
38
+ const db = new MainDb(executor, { database: "mydb" });
38
39
  ```
39
40
 
40
- ### 연결·트랜잭션
41
+ ### 연결·트랜잭션 경계
41
42
 
42
- - `connect(fn, isolationLevel?)` — 연결 → 트랜잭션 시작 → `fn` 실행 → 성공 시 commit, 예외 시 rollback 후 재throw 최종 close. `ready` 가 아니면 throw. 최초 호출 시 관계 정의를 1회 검증. 업무 단위의 표준 진입점.
43
- - `connectWithoutTransaction(callback)` — 트랜잭션 없이 연결만 잡고 `callback` 실행 후 close. DDL·초기화처럼 트랜잭션 밖에서 실행해야 하는 작업용.
44
- - `transaction(fn, isolationLevel?)` — 이미 `connect` 상태에서 추가 트랜잭션 경계를 잡을 때. `transact` 상태면 throw. 성공commit, 예외 rollback.
45
- - `isolationLevel`: `"READ_UNCOMMITTED" | "READ_COMMITTED" | "REPEATABLE_READ" | "SERIALIZABLE"` — 격리 수준(선택). 미지정 시 DB 기본값(보통 READ_COMMITTED). 더티 리드 허용~완전 직렬화 순으로 엄격해짐.
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
- const users = await db.user().where((u) => [expr.eq(u.isActive, true)]).execute();
50
- await db.user().where((u) => [expr.eq(u.id, 1)]).update((u) => ({ name: "수정" }));
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
- ### DDL 실행 메서드 (트랜잭션 밖에서만)
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
- 각각 `executeDefs` 로 즉시 실행한다. `transact` 상태에서 호출하면 throw.
64
+ `executeDefs` 로 즉시 실행. `"transact"` 상태에서는 DDL 차단됨.
57
65
 
58
- - `createTable(table)` / `dropTable(name)` / `renameTable(name, newName)` 테이블 생성/삭제/이름변경.
59
- - `truncate(name)` 테이블 비우기(전 행 삭제, identity 리셋).
60
- - `createView(view)` / `dropView(name)` — 뷰 생성/삭제.
61
- - `createProc(procedure)` / `dropProc(name)` 프로시저 생성/삭제.
62
- - `addColumn(table, columnName, column)` / `dropColumn(table, column)` / `modifyColumn(table, columnName, column)` / `renameColumn(table, column, newName)` 컬럼 변경. `column` `ColumnBuilder`.
63
- - `addPrimaryKey(table, columns)` / `dropPrimaryKey(table)` — PK 추가/삭제. `columns` 는 컬럼명 배열(복합 PK).
64
- - `addForeignKey(table, relationName, relationDef)` / `dropForeignKey(table, relationName)` FK 추가/삭제. `relationDef` 는 `ForeignKeyBuilder`.
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 생성기 (실행 없이 def 만)
74
+ ### DDL QueryDef 생성기 (`get*QueryDef`)
72
75
 
73
- 위 실행 메서드와 1:1 대응하는 `get*QueryDef(...)` 모두 있다(`getCreateTableQueryDef`, `getDropTableQueryDef`, `getAddColumnQueryDef`, `getAddForeignKeyQueryDef`, `getTruncateQueryDef`, `getSwitchFkQueryDef`, `getClearSchemaQueryDef`, `getSchemaExistsQueryDef` 등). 실행하지 않고 `QueryDef` AST 만 얻어 배치 실행·검증·SQL 변환에 쓸 때 사용. 단일 `getCreateObjectQueryDef(builder)` 는 Table/View/Procedure 빌더 종류를 보고 알맞은 CREATE def만든다.
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?)` — `migrations` 미적용분을 순서대로 실행하고, 적용 여부를 `_migration` 시스템 테이블에 기록. `boolean` 반환(변경 발생 여부 등).
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
- ## Migration (interface)
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`: string 고유 마이그레이션 식별자. 타임스탬프 접두 권장(`20260105_001_...`). 값으로 적용 여부를 추적하므로 한번 배포되면 변경 금지.
92
- - `up`: `(db) => Promise<void>` — 스키마 변경 함수. `db` 로 DDL 메서드를 호출. 실행은 `initialize()` 가 미적용분만 골라 호출.
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 네이티브 에러를 dialect 독립 코드로 래핑한다. `connect`/`transaction` 롤백 단계에서 "이미 롤백되어 활성 트랜잭션이 없음" 같은 상황을 코드로 식별해 무시 여부를 판단할 때 쓴다.
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 { NO_ACTIVE_TRANSACTION, TRANSACTION_ALREADY_STARTED, DEADLOCK, LOCK_TIMEOUT }
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`: `DbErrorCode` 표준화된 에러 종류. 아래 enum literal 로 분기.
108
- - `originalError`: unknown 래핑 전 원본 DBMS 에러(디버깅용). dialect 별 원인 추적 시 참조.
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
- ```typescript
115
- try {
116
- await db.rollbackTransaction();
117
- } catch (err) {
118
- if (err instanceof DbTransactionError && err.code === DbErrorCode.NO_ACTIVE_TRANSACTION) return;
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
- ## DbContextBase / DbContextStatus / DbContextDdlMethods / SD_BUILDER
158
+ ## 주의사항
124
159
 
125
- - `DbContextBase` (interface) `Queryable`/`Executable`/`ViewBuilder` 의존하는 컨텍스트 최소 인터페이스(`status`, `database`, `schema`, `getNextAlias`, `resetAliasCounter`, `executeDefs`, `getQueryDefObjectName`, `switchFk`). `DbContext` 가 구현한다. executor·뷰 정의처럼 컨텍스트 전체가 아니라 일부 능력만 요구하는 시그니처에 쓴다.
126
- - `DbContextStatus` (type) `"ready" | "connect" | "transact"`. `status` 와 동일.
127
- - `DbContextDdlMethods` (interface) — DDL 실행 메서드 + `get*QueryDef` 생성기를 모은 인터페이스. `Migration.up` `db` 파라미터 타입(`DbContextBase & DbContextDdlMethods`)에 쓰여, 마이그레이션 함수에서 DDL 만 노출되게 한다.
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 (SQL 표현식 빌더)
1
+ # @simplysm/orm-common — expr
2
2
 
3
- `expr` 객체로 dialect 독립 SQL 표현식을 JSON AST(`Expr`) 조립한다. dialect 별 QueryBuilder 가 MySQL/MSSQL/PostgreSQL 변환. where/having 콜백은 `WhereExprUnit[]`, select/orderBy/groupBy 콜백은 `ExprUnit<T>`, update/upsert/insert/where 비교값은 `ExprInput<T>`(= `ExprUnit<T> | T`)를 다룬다. 비교·쓰기 값은 리터럴을 그대로 넘긴다 — `expr.val` 감싸지 (orm.md). `expr.subquery`/`expr.exists` SELECT 절에 넣지 말고 `joinSingle` 로 집계를 부착한다(orm.md).
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
- - `ExprUnit<T>` 타입 안전 표현식 래퍼. `dataType`(`ColumnPrimitiveStr`)·`expr`(`Expr` AST) 보유. getter `n` 은 동일 표현식을 non-nullable 타입(`NonNullable<T>`)으로 다시 래핑(coalesce 후 null 아님이 보장될 때 타입만 좁힐 용도).
8
- - `WhereExprUnit` — WHERE 절용 래퍼(`WhereExpr` AST 보유). `where`/`having` 콜백 반환 원소.
9
- - `ExprInput<T>` = `ExprUnit<T> | T` — 표현식 또는 리터럴 둘 다 받는 입력 타입. 비교 target·쓰기 값 자리.
10
- - `SwitchExprBuilder<T>` — `expr.switch()` 가 반환하는 CASE 빌더(`case`/`default`).
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`=`"string"|"number"|"boolean"|"DateTime"|"DateOnly"|"Time"|"Uuid"|"Buffer"` 원시 타입 문자열. `value`=값(undefined 허용 NULL). `ExprUnit` 이 요구되는 자리(select 리터럴 상수 컬럼 등)에서만 사용.
15
- - `col(dataType, ...path)` — 컬럼 참조 `ExprUnit` 생성. `path`=별칭·컬럼 경로. 보통 콜백의 컬럼 프록시가 대신하므로 내부용.
16
- - `raw(dataType)\`...\`` — Raw SQL 이스케이프 해치. 태그드 템플릿. 보간 값은 자동 파라미터화. ORM 미지원 DB 함수·UNION 타입 명시 NULL(`` expr.raw("number")`NULL` ``)에 사용.
17
- - `toExpr(value)` — `ExprInput` `Expr` AST 로 변환(내부 헬퍼).
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
- ## WHERE비교 (`WhereExprUnit` 반환)
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
- - `eq(source, target)` — `=` 비교(NULL 안전: MySQL `<=>`, 그 외 `IS NULL OR =`). NULL 끼리도 일치 판정해야 할 때.
22
- - `gt(source, target)` / `lt(source, target)` / `gte(source, target)` / `lte(source, target)` — `>` / `<` / `>=` / `<=`.
23
- - `between(source, from?, to?)` 범위. `from` undefined 면 하한 없음, `to` undefined 면 상한 없음(한쪽만 주면 단방향 부등호).
24
- - `null(source)` — `IS NULL`. nullable 컬럼의 결측 판정.
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 — 문자열/IN/논리
53
+ ## WHERE — 논리 (→ WhereExprUnit)
27
54
 
28
- - `like(source, pattern)` — `LIKE`(% 다수, _ 단일, `\` 이스케이프). 부분/접두/접미 검색.
29
- - `regexp(source, pattern)` — 정규식 매칭(구문은 DBMS 의존).
30
- - `in(source, values)` — `IN (값목록)`. `values`=`ExprInput[]`.
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 — 문자열 (`ExprUnit` 반환)
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
- - `concat(...args)``CONCAT`(NULL 은 빈 문자열 처리).
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
- ## SELECT숫자 / 날짜
77
+ - `expr.abs(source)`절대값.
78
+ - `expr.round(source, digits)` — 반올림(소수 자릿수).
79
+ - `expr.ceil(source)` / `expr.floor(source)` — 올림/내림.
57
80
 
58
- - `abs(source)` / `round(source, digits)` / `ceil(source)` / `floor(source)` — 절대값/반올림(`digits`=소수자릿수)/올림/내림.
59
- - `year`/`month`/`day`/`hour`/`minute`/`second`(source) — 날짜·시간 구성요소 추출(number).
60
- - `isoWeek(source)` ISO 8601 번호(월요일 시작, 1~53). `isoWeekStartDate(source)` 해당 월요일(DateOnly). `isoYearMonth(source)`해당 1일(DateOnly).
61
- - `dateDiff(unit, from, to)` — 날짜 차이(`to - from`). `unit`=`"year"|"month"|"day"|"hour"|"minute"|"second"`.
62
- - `dateAdd(unit, source, value)` — 날짜 가감(`value` 음수 허용). 결과 타입은 `source` 동일.
63
- - `formatDate(source, format)` — 날짜 포맷 문자열(포맷 구문 DBMS 의존, 예 `"%Y-%m-%d"`).
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(`COALESCE`). 마지막 인자가 non-nullable 이면 결과도 non-nullable 로 추론.
68
- - `nullIf(source, value)` — `source === value` 면 NULL, 아니면 source(빈 문자열 → NULL 변환 등).
69
- - `is(condition)` — `WhereExprUnit` 을 boolean `ExprUnit` 으로 변환(조건 결과를 컬럼으로). select 절에서 도메인 boolean 컬럼 만들 때.
70
- - `switch<T>()` — CASE WHEN 빌더(`SwitchExprBuilder`). `case(condition, then)` 체이닝 후 `default(value)` 로 종료. then/default 타입에서 결과 타입 추론(모두 리터럴이면 non-null 하나에서 추론, 전부 null 이면 throw).
71
- - `if(condition, then, else_)` — 삼항(IIF/IF). then/else 중 ExprUnit 또는 non-null 리터럴에서 타입 추론.
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
- isActive: expr.is(expr.eq(u.status, "active")),
76
- grade: expr.switch<string>().case(expr.gte(u.score, 90), "A").case(expr.gte(u.score, 80), "B").default("F"),
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
- 집계는 NULL 행을 무시하고, 모든 값이 NULL 이거나 행이 없을 때만 NULL 반환.
121
+ ## SELECT 기타 (→ ExprUnit)
83
122
 
84
- - `count(arg?, distinct?)` `COUNT`. `arg` 생략 전체 행, 지정 컬럼의 non-null 행. `distinct` true 중복 제거 카운트.
85
- - `sum(arg)` / `avg(arg)` 합계/평균(number, NULL 가능).
86
- - `max(arg)` / `min(arg)` — 최대/최소(타입 유지, NULL 가능).
87
- - `greatest(...args)` / `least(...args)` 인자들 최대/최소값(행 단위, 집계 아님).
88
- - `rowNum()` — 단순순번(1부터). `random()` 0~1 난수(무작위 정렬 `orderBy(() => expr.random())`).
89
- - `cast(source, targetType)` — 타입 변환. `targetType`=`DataType`(예 `{ type: "varchar", length: 20 }`).
90
- - `subquery(dataType, queryable)` — 스칼라 서브쿼리(단일 행·단일 컬럼). SELECT 절에서 단일 값 반환. (행마다 N회 실행되므로 집계는 `joinSingle` 권장, orm.md.)
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")?][] }` 를 받는다(`partitionBy`=구간 분할 컬럼, `orderBy`=구간 내 정렬).
135
+ 모두 `spec: { partitionBy?: ExprInput[]; orderBy?: [ExprInput, ("ASC"|"DESC")?][] }` 를 받는다.
95
136
 
96
- - `rowNumber(spec)` — `ROW_NUMBER()`(파티션 내 1부터 순번).
97
- - `rank(spec)` — `RANK()`(동순위 후 건너뜀: 1,1,3). `denseRank(spec)` — `DENSE_RANK()`(연속: 1,1,2).
98
- - `ntile(n, spec)` — `NTILE(n)`(파티션을 `n` 그룹으로 분할, 1~n).
99
- - `lag(column, spec, options?)` / `lead(column, spec, options?)` — 이전/다음 행 값. `options.offset`(기본 1)·`options.default`(이전/다음 행 없을 때 기본값).
100
- - `firstValue(column, spec)` / `lastValue(column, spec)` — 프레임 첫/마지막 값.
101
- - `sumOver`/`avgOver`(column, spec) — 윈도우 합계/평균(누적합·이동평균).
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 안티패턴).