@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,167 +1,224 @@
1
- # @simplysm/orm-common — 스키마 정의 (Table / View / Procedure / column / index / relation)
1
+ # @simplysm/orm-common — schema
2
2
 
3
- DB 객체(Table/View/Procedure)와 그 구성요소(column/index/관계)를 fluent 빌더로 선언하는 묶음. 모든 빌더는 immutable 메서드가 새 인스턴스를 반환한다. 정의한 빌더는 `DbContext` 안에서 `this.queryable()`/`this.executable()` 등록해 사용한다. column 기본 `NOT NULL` 이며 `.nullable()`/`.default(...)` 도메인 근거가 있을 때만 붙인다(orm.md 정책).
3
+ 테이블·뷰·프로시저 스키마를 fluent 빌더로 정의하는 군. `Table()`/`View()`/`Procedure()` 팩토리가 빌더를 만들고,빌더의 메서드는 새 인스턴스를 반환하는 불변 체이닝이다. 정의 결과는 `DbContext` `queryable()`/`executable()` 등록해 쓰며, `$inferSelect`/`$inferInsert` 등의 phantom 필드로 컬럼·관계 타입이 추론된다. 컬럼/관계/인덱스는 빌더의 콜백 안에서 팩토리(`c`/`r`/`i`)로 만든다.
4
4
 
5
- ## Table / TableBuilder
5
+ ## TableBuilder / Table
6
6
 
7
7
  ```typescript
8
8
  function Table<TName extends string>(name: TName): TableBuilder<TName, {}, {}>;
9
+ class TableBuilder<TName, TColumns, TRelations> {
10
+ readonly meta: { name; description?; database?; schema?; columns?; primaryKey?; relations?; indexes? };
11
+ readonly $inferSelect; // InferColumns & InferDeepRelations (전체 — 관계 포함)
12
+ readonly $inferColumns; // 컬럼만
13
+ readonly $inferInsert; // INSERT 입력 (autoIncrement 제외, nullable/default 는 optional)
14
+ readonly $inferUpdate; // UPDATE 입력 (모든 컬럼 optional)
15
+ description(desc: string): TableBuilder;
16
+ database(db: string): TableBuilder;
17
+ schema(schema: string): TableBuilder;
18
+ columns(fn: (c) => TNewColumnDefs): TableBuilder;
19
+ primaryKey(...columns: (keyof TColumns)[]): TableBuilder;
20
+ indexes(fn: (i) => IndexBuilder[]): TableBuilder;
21
+ relations(fn: (r) => TRelations): TableBuilder;
22
+ }
9
23
  ```
10
24
 
11
- `Table(name)` 으로 시작해 fluent 체이닝으로 정의한다. 메서드는 `TableBuilder` 반환.
12
-
13
- - `database(db)` — 데이터베이스 이름 설정. dialect 네임스페이스 산출에 사용.
14
- - `schema(schema)` — 스키마 이름 설정(MSSQL: dbo, PostgreSQL: public). MySQL 은 무시.
15
- - `description(desc)` — 테이블 코멘트(DDL COMMENT). 문서화 목적.
16
- - `columns((c) => ({...}))` — column 정의. `c` column 팩토리(아래). 반환 객체의 키가 컬럼명.
17
- - `primaryKey(...columns)` — PK 컬럼 지정(가변 인자). 이상이면 복합 PK.
18
- - `indexes((i) => [...])` 인덱스 정의. `i` index 팩토리.
19
- - `relations((r) => ({...}))` — 관계(FK/역참조/논리관계) 정의. `r` relation 팩토리.
20
-
21
- 타입 추론 필드(런타임 값 아님): `$inferSelect`(컬럼+관계), `$inferColumns`(컬럼만), `$inferInsert`(autoIncrement/nullable/default 는 optional), `$inferUpdate`(전부 optional). `Queryable` 의 결과/입력 타입이 여기서 파생된다.
25
+ - `Table(name)` 테이블 빌더 시작점. `name` 실제 DB 테이블명이자 `$inferSelect` phantom 타입 이름.
26
+ - `description(desc)` — 테이블 설명. DDL Comment 로 사용됨.
27
+ - `database(db)` — 테이블이 속한 데이터베이스명. 미지정 `DbContext` 의 database 옵션을 따름.
28
+ - `schema(schema)` — 스키마명. MSSQL(`dbo`)/PostgreSQL(`public`) 에서만 의미 있고 MySQL 은 무시.
29
+ - `columns(fn)` — `createColumnFactory()` 주입된 `c` 로 컬럼 레코드를 반환. 호출 시 `TColumns` 가 새 정의로 교체됨.
30
+ - `primaryKey(...columns)` — PK 컬럼명을 가변 인자로. 2개 이상 넘기면 복합 PK. 인자는 `columns()` 키로 타입 체크됨.
31
+ - `indexes(fn)` — `createIndexFactory()` `i` 인덱스 배열을 반환.
32
+ - `relations(fn)` `createRelationFactory()` `r` 관계 레코드 반환. Table 은 `foreignKey`/`foreignKeyTarget`/`relationKey`/`relationKeyTarget` 모두 사용 가능.
33
+ - `meta` — 빌더가 누적한 정의 객체. DDL 생성·`queryable()` 읽음.
22
34
 
23
35
  ```typescript
24
36
  const User = Table("User")
25
37
  .database("mydb")
26
38
  .columns((c) => ({
27
- id: c.int().autoIncrement(),
39
+ id: c.bigint().autoIncrement(),
28
40
  name: c.varchar(100),
29
41
  email: c.varchar(200).nullable(),
30
- isActive: c.boolean().default(true),
31
- companyId: c.int().nullable(),
42
+ status: c.varchar(20).default("active"),
32
43
  }))
33
44
  .primaryKey("id")
34
45
  .indexes((i) => [i.index("email").unique()])
35
- .relations((r) => ({
36
- company: r.foreignKey(["companyId"], () => Company),
37
- posts: r.foreignKeyTarget(() => Post, "user"),
38
- }));
46
+ .relations((r) => ({ posts: r.foreignKeyTarget(() => Post, "author") }));
39
47
  ```
40
48
 
41
- ## column 팩토리 / ColumnBuilder
49
+ ## ViewBuilder / View
42
50
 
43
- `columns((c) => ...)` 의 `c` 가 노출하는 타입 생성 메서드. 각자 `ColumnBuilder` 를 반환하고, 그 위에 수식 메서드를 체이닝한다.
44
-
45
- 타입 메서드:
46
-
47
- - `int()` INT(4바이트 정수). 일반 정수 PK/카운트.
48
- - `bigint()` — BIGINT(8바이트 정수). 큰 범위 ID.
49
- - `float()` — 단정밀도 부동소수점. 정밀도 덜 중요한 실수.
50
- - `double()` — 배정밀도 부동소수점. 일반 실수 연산.
51
- - `decimal(precision, scale?)` 고정 소수점. `precision`=전체 자릿수, `scale`=소수 자릿수(선택). 금액처럼 반올림 오차가 곤란한 값.
52
- - `varchar(length)` 가변 길이 문자열. `length`=최대 길이.
53
- - `char(length)` — 고정 길이 문자열. 코드값처럼 길이가 일정한 값.
54
- - `text()` — 대용량 텍스트(본문 등).
55
- - `binary()` — 바이너리(MySQL LONGBLOB / MSSQL VARBINARY(MAX) / PostgreSQL BYTEA). 값 타입은 `Bytes`.
56
- - `boolean()` — 불리언(MySQL TINYINT(1) / MSSQL BIT / PostgreSQL BOOLEAN).
57
- - `datetime()` — 날짜+시간. 값 타입 `DateTime`.
58
- - `date()` — 날짜만. 값 타입 `DateOnly`.
59
- - `time()` — 시간만. 값 타입 `Time`.
60
- - `uuid()` — UUID(MySQL BINARY(16) / MSSQL UNIQUEIDENTIFIER / PostgreSQL UUID). 값 타입 `Uuid`.
51
+ ```typescript
52
+ function View(name: string): ViewBuilder;
53
+ class ViewBuilder<TDbContext, TData, TRelations> {
54
+ readonly meta: { name; description?; database?; schema?; viewFn?; relations? };
55
+ readonly $inferSelect; // TData
56
+ description(desc: string): ViewBuilder;
57
+ database(db: string): ViewBuilder;
58
+ schema(schema: string): ViewBuilder;
59
+ query(viewFn: (db: TDb) => Queryable<TViewData, any>): ViewBuilder;
60
+ relations(fn: (r) => TRelations): ViewBuilder;
61
+ }
62
+ ```
61
63
 
62
- 수식 메서드(`ColumnBuilder`):
64
+ - `View(name)` — 뷰 빌더 시작점.
65
+ - `description`/`database`/`schema` — Table 과 동일 의미.
66
+ - `query(viewFn)` — 뷰의 데이터 소스인 SELECT Queryable 을 `db` 를 받아 반환. `viewFn` 이 `TData`(뷰 행 타입)를 결정.
67
+ - `relations(fn)` — 뷰의 관계 정의. 뷰는 `relationKey`/`relationKeyTarget` 만 사용 가능(DB FK 미생성). 반환 타입에 관계가 합쳐져 `$inferSelect` 에 반영됨.
63
68
 
64
- - `autoIncrement()` — 자동 증가. INSERT 타입에서 optional 처리. 보통 정수 PK 에만.
65
- - `nullable()` — NULL 허용. 값 타입에 `undefined` 추가, INSERT 타입에서 optional. 도메인상 값이 없을 수 있을 때만.
66
- - `default(value)` — INSERT 시 미지정이면 사용할 기본값. INSERT 타입에서 optional. 사용자가 명시적으로 지시한 경우에만.
67
- - `description(desc)` — 컬럼 코멘트(DDL COMMENT).
69
+ ```typescript
70
+ const ActiveUsers = View("ActiveUsers")
71
+ .database("mydb")
72
+ .query((db: MyDb) =>
73
+ db.user().where((u) => [expr.eq(u.status, "active")]).select((u) => ({ id: u.id, name: u.name })),
74
+ );
75
+ ```
68
76
 
69
- ## index 팩토리 / IndexBuilder
77
+ ## ProcedureBuilder / Procedure
70
78
 
71
- `indexes((i) => [...])` 의 `i.index(...columns)` 로 시작. immutable 체이닝.
79
+ ```typescript
80
+ function Procedure(name: string): ProcedureBuilder<never, never>;
81
+ class ProcedureBuilder<TParams, TReturns> {
82
+ readonly meta: { name; description?; database?; schema?; params?; returns?; query? };
83
+ readonly $params; readonly $returns;
84
+ description(desc: string): ProcedureBuilder;
85
+ database(db: string): ProcedureBuilder;
86
+ schema(schema: string): ProcedureBuilder;
87
+ params(fn: (c) => TParams): ProcedureBuilder;
88
+ returns(fn: (c) => TReturns): ProcedureBuilder;
89
+ body(sql: string): ProcedureBuilder;
90
+ }
91
+ ```
72
92
 
73
- - `index(...columns)` — 인덱스 대상 컬럼(가변 인자, 복합 인덱스 가능). `IndexBuilder` 반환.
74
- - `name(name)` — 인덱스 이름 지정. 미지정 자동 명명.
75
- - `unique()` — 유니크 인덱스로 설정. 중복 방지 제약이 필요할 때.
76
- - `orderBy(...orderBy)` — 컬럼별 정렬 방향(`"ASC" | "DESC"`). 인자 수가 컬럼 수와 일치해야 함. 범위/정렬 조회 최적화용.
77
- - `description(desc)` — 인덱스 코멘트.
93
+ - `Procedure(name)` — 저장 프로시저 빌더 시작점. `executable()` 에 등록.
94
+ - `params(fn)` — 입력 파라미터를 컬럼 팩토리로 정의. `Executable.execute()` 의 인자 타입이 됨.
95
+ - `returns(fn)` — 반환 결과셋 컬럼을 정의. 결과 타입이 됨.
96
+ - `body(sql)` — 프로시저 본문 SQL. DBMS 파라미터 구문 차이 주의(MySQL: `userId`, MSSQL: `@userId`, PostgreSQL: `RETURN QUERY` 필요).
78
97
 
79
98
  ```typescript
80
- .indexes((i) => [
81
- i.index("email").unique(),
82
- i.index("status", "createdAt").orderBy("ASC", "DESC"),
83
- ])
99
+ const GetUserById = Procedure("GetUserById")
100
+ .database("mydb")
101
+ .params((c) => ({ userId: c.bigint() }))
102
+ .returns((c) => ({ id: c.bigint(), name: c.varchar(100) }))
103
+ .body("SELECT id, name FROM User WHERE id = userId");
84
104
  ```
85
105
 
86
- ## relation 팩토리 / 관계 빌더
106
+ ## createColumnFactory (컬럼 팩토리 `c`)
107
+
108
+ `columns()`/`params()`/`returns()` 콜백에 주입되는 `c`. 각 메서드가 `ColumnBuilder` 를 반환하며 SQL 타입과 TS 원시 타입을 함께 고정한다.
109
+
110
+ | 메서드 | TS 타입 | SQL 매핑 |
111
+ | ------ | ------- | -------- |
112
+ | `c.int()` | number | INT (4바이트) |
113
+ | `c.bigint()` | number | BIGINT (8바이트) |
114
+ | `c.float()` | number | FLOAT/REAL (4바이트) |
115
+ | `c.double()` | number | DOUBLE (8바이트) |
116
+ | `c.decimal(precision, scale?)` | number | DECIMAL(precision, scale) — 고정 소수점 |
117
+ | `c.varchar(length)` | string | VARCHAR(length) — 가변 길이 |
118
+ | `c.char(length)` | string | CHAR(length) — 고정 길이 |
119
+ | `c.text()` | string | TEXT/LONGTEXT — 대용량 |
120
+ | `c.binary()` | Bytes | LONGBLOB / VARBINARY(MAX) / BYTEA |
121
+ | `c.boolean()` | boolean | TINYINT(1) / BIT / BOOLEAN |
122
+ | `c.datetime()` | DateTime | DATETIME |
123
+ | `c.date()` | DateOnly | DATE |
124
+ | `c.time()` | Time | TIME |
125
+ | `c.uuid()` | Uuid | BINARY(16) / UNIQUEIDENTIFIER / UUID |
126
+
127
+ ### ColumnBuilder 수식 메서드
128
+
129
+ - `.autoIncrement()` — INSERT 시 자동 증가. `$inferInsert` 에서 해당 컬럼이 optional 이 됨.
130
+ - `.nullable()` — NULL 허용. 값 타입에 `undefined` 가 추가되고 `$inferInsert` 에서 optional.
131
+ - `.default(value)` — INSERT 시 미지정이면 사용할 기본값. `$inferInsert` 에서 optional. (정책: 사용자가 명시 지시한 경우에만 사용.)
132
+ - `.description(desc)` — 컬럼 설명. DDL Comment.
87
133
 
88
- `relations((r) => ({...}))` 의 `r` 가 노출하는 관계 정의 메서드. Table 은 FK 계열 + RelationKey 계열 모두, View 는 RelationKey 계열만 사용 가능. 관계는 `include()`(queryable.md)로 자동 조인되며, 정의만 한다고 DB 쿼리가 나가지는 않는다. `description`/`single` 등 옵션은 메서드 체이닝이 아니라 마지막 `opts` 인자로 전달한다(순환 참조로 인한 TS7022 회피 목적).
89
-
90
- - `foreignKey(columns, targetFn, opts?)` — N:1 FK. DB 에 실제 FK 제약 생성. `columns`=현재 테이블의 FK 컬럼 배열, `targetFn`=대상 테이블 지연 팩토리(`() => User`). `opts.description` 선택.
91
- - `foreignKeyTarget(targetTableFn, relationName, opts?)` — 1:N 역참조. DB 객체는 안 만들고 `include` 시 배열로 로드. `relationName`=대상 테이블에 정의된 FK 관계 이름. `opts.single: true` 면 단일 객체(1:1)로 로드, `opts.description` 선택.
92
- - `relationKey(columns, targetFn, opts?)` — N:1 논리 관계. `foreignKey` 와 동일하나 DB FK 제약을 생성하지 않음. View 에서도 사용 가능. 물리 FK 를 걸 수 없는 관계(뷰↔테이블 등)에 사용.
93
- - `relationKeyTarget(targetTableFn, relationName, opts?)` — 1:N 논리 역참조. `foreignKeyTarget` 의 FK 미생성 버전. `opts.single`/`opts.description` 동일.
134
+ ```typescript
135
+ .columns((c) => ({
136
+ id: c.bigint().autoIncrement(),
137
+ email: c.varchar(200).nullable(),
138
+ status: c.varchar(20).default("active"),
139
+ }))
140
+ ```
94
141
 
95
- opts 공통 필드:
142
+ ## createIndexFactory (인덱스 팩토리 `i`)
96
143
 
97
- - `description`: string 관계 코멘트.
98
- - `single`: true — (target 계열만) 역참조를 배열이 아닌 단일 객체로 로드. 1:1 관계일 때.
144
+ `indexes()` 콜백에 주입되는 `i`. `i.index(...columns)` `IndexBuilder` 를 반환.
99
145
 
100
146
  ```typescript
101
- const Post = Table("Post")
102
- .columns((c) => ({ id: c.int().autoIncrement(), userId: c.int(), title: c.varchar(300) }))
103
- .primaryKey("id")
104
- .relations((r) => ({
105
- user: r.foreignKey(["userId"], () => User, { description: "작성자" }),
106
- }));
147
+ class IndexBuilder<TKeys> {
148
+ readonly meta: { columns; name?; unique?; orderBy?; description? };
149
+ name(name: string): IndexBuilder;
150
+ unique(): IndexBuilder;
151
+ orderBy(...orderBy: ("ASC" | "DESC")[]): IndexBuilder;
152
+ description(description: string): IndexBuilder;
153
+ }
107
154
  ```
108
155
 
109
- 빌더 클래스: `ForeignKeyBuilder`, `ForeignKeyTargetBuilder`, `RelationKeyBuilder`, `RelationKeyTargetBuilder` export 되며, `meta` 프로퍼티로 정의 내용을 노출한다(DDL 자동화·검증용). 보통 직접 `new` 하지 않고 위 팩토리로 생성한다.
110
-
111
- ## View / ViewBuilder
156
+ - `i.index(...columns)` 인덱스 컬럼을 가변 인자로(복합 인덱스). 컬럼명은 테이블 컬럼 키로 타입 체크.
157
+ - `.name(name)` — 인덱스 이름 직접 지정.
158
+ - `.unique()` 유니크 인덱스로 표시.
159
+ - `.orderBy(...dirs)` — 컬럼별 정렬 방향. 인자 개수가 컬럼 개수와 일치해야 함.
160
+ - `.description(desc)` — 인덱스 설명.
112
161
 
113
162
  ```typescript
114
- function View(name: string): ViewBuilder<...>;
163
+ .indexes((i) => [
164
+ i.index("email").unique(),
165
+ i.index("status", "createdAt").orderBy("ASC", "DESC"),
166
+ ])
115
167
  ```
116
168
 
117
- 쿼리 결과를 가상 테이블로 정의한다. `query` 콜백 안에서 `DbContext` 를 받아 `Queryable` 을 반환하면 그것이 뷰 본문이 된다.
169
+ ## createRelationFactory (관계 팩토리 `r`)
118
170
 
119
- - `database(db)` / `schema(schema)` / `description(desc)` Table 동일.
120
- - `query((db) => db.x().select(...))` — 뷰 본문 SELECT 정의. `db` 는 `DbContext`, 반환은 `Queryable`. select 결과 컬럼이 뷰 컬럼이 됨.
121
- - `relations((r) => ({...}))` — 논리 관계(RelationKey 계열)만 정의 가능.
171
+ `relations()` 콜백에 주입되는 `r`. Table 은 4종 모두, View 는 `relationKey`/`relationKeyTarget` 노출. description·single 옵션은 메서드 체이닝이 아니라 `opts` 인자로 전달한다(순환 참조 시 TS7022 회피).
122
172
 
123
- ```typescript
124
- const ActiveUsers = View("ActiveUsers")
125
- .database("mydb")
126
- .query((db: AppDb) =>
127
- db.user().where((u) => [expr.eq(u.isActive, true)]).select((u) => ({ id: u.id, name: u.name })),
128
- );
129
- ```
173
+ - `r.foreignKey(columns, targetFn, opts?)` — N:1 FK 관계(DB FK 제약 **생성**). `columns` 는 FK 컬럼 배열, `targetFn` 은 대상 테이블 지연 반환(`() => User`). `opts.description?`. → `include()` 시 단일 객체로 로드.
174
+ - `r.foreignKeyTarget(targetTableFn, relationName, opts?)` — 1:N FK 역참조. `relationName` 은 대상 테이블에서 이쪽을 가리키는 FK 관계 이름. `opts.single: true` 면 1:1(단일 객체), 미지정/`false` 면 배열. `opts.description?`.
175
+ - `r.relationKey(columns, targetFn, opts?)` — `foreignKey` 와 동일하나 DB FK 제약 **미생성**(논리적 관계). View 에서도 사용 가능.
176
+ - `r.relationKeyTarget(targetTableFn, relationName, opts?)` — `foreignKeyTarget` 의 DB FK 미생성 버전. `opts.single`/`description` 동일.
130
177
 
131
- ## Procedure / ProcedureBuilder
178
+ 대상은 항상 지연 함수(`() => Table`)로 넘긴다 — 순환 참조(A↔B) 방지.
132
179
 
133
180
  ```typescript
134
- function Procedure(name: string): ProcedureBuilder<never, never>;
181
+ const Post = Table("Post")
182
+ .columns((c) => ({ id: c.bigint().autoIncrement(), authorId: c.bigint() }))
183
+ .primaryKey("id")
184
+ .relations((r) => ({
185
+ author: r.foreignKey(["authorId"], () => User, { description: "작성자" }),
186
+ }));
187
+
188
+ const User = Table("User")
189
+ .columns((c) => ({ id: c.bigint().autoIncrement(), name: c.varchar(100) }))
190
+ .primaryKey("id")
191
+ .relations((r) => ({
192
+ posts: r.foreignKeyTarget(() => Post, "author"),
193
+ profile: r.foreignKeyTarget(() => Profile, "user", { single: true }),
194
+ }));
135
195
  ```
136
196
 
137
- 저장 프로시저를 정의한다. `executable()` 로 등록 후 `Executable.execute(params)`(queryable.md)로 호출.
197
+ ## 빌더 클래스 (관계)
138
198
 
139
- - `database(db)` / `schema(schema)` / `description(desc)` 동일.
140
- - `params((c) => ({...}))` — 입력 파라미터 정의. `c` 는 column 팩토리. 키가 파라미터명.
141
- - `returns((c) => ({...}))` — 반환 결과 컬럼 정의.
142
- - `body(sql)` — 프로시저 본문 SQL. dialect 별 파라미터 구문 차이 주의(MySQL/PostgreSQL: `userId`, MSSQL: `@userId`).
199
+ `createRelationFactory` 반환하는 인스턴스의 클래스도 export 된다. `meta` 보유하는 데이터 홀더이며 메서드 체이닝은 없다(옵션은 팩토리 `opts`).
143
200
 
144
- 타입 추론 필드: `$params`, `$returns`(`Executable` 입력/출력 타입 파생).
201
+ - `ForeignKeyBuilder` N:1 FK(`meta: { ownerFn, columns, targetFn, description? }`).
202
+ - `ForeignKeyTargetBuilder` — 1:N FK 역참조(`meta: { targetTableFn, relationName, description?, isSingle? }`).
203
+ - `RelationKeyBuilder` / `RelationKeyTargetBuilder` — 위 둘의 DB FK 미생성 버전.
145
204
 
146
- ```typescript
147
- const GetUserById = Procedure("GetUserById")
148
- .database("mydb")
149
- .params((c) => ({ userId: c.bigint() }))
150
- .returns((c) => ({ id: c.bigint(), name: c.varchar(100) }))
151
- .body("SELECT id, name FROM User WHERE id = userId");
152
- ```
205
+ ## 타입 추론 유틸리티
153
206
 
154
- ## 타입 추론 유틸 / 기타 export
207
+ 빌더의 phantom 필드 뒤에 있는 추론 타입. 직접 import 해 쓸 일은 드물지만 export 됨.
155
208
 
156
- column-builder 함께 export 하는 타입(주로 빌더 내부·executor·고급 타입 작업용):
209
+ - `InferColumns<TBuilders>` 컬럼 빌더 레코드 실제 타입 레코드.
210
+ - `InferColumnExprs<TBuilders>` — 컬럼 빌더 레코드 → `ExprInput<V>` 레코드(프로시저 파라미터 입력 타입).
211
+ - `InferInsertColumns<TBuilders>` — INSERT 타입(필수/optional 분리).
212
+ - `InferUpdateColumns<TBuilders>` — UPDATE 타입(전부 optional).
213
+ - `RequiredInsertKeys`/`OptionalInsertKeys` — INSERT 필수/optional 키 추출.
214
+ - `DataToColumnBuilderRecord<TData>` — 데이터 레코드 → 컬럼 빌더 레코드(`insertInto` 대상 타입 제약용).
215
+ - `InferDeepRelations<TRelations>` — 관계 정의 → 심층(중첩) 관계 타입. 모든 관계는 optional(include 전 undefined). 같은 테이블 재방문 시 순환을 끊음.
216
+ - `ExtractRelationTarget` / `ExtractRelationTargetResult` — 단일/배열 관계 대상 타입 추출(내부용).
217
+ - `ColumnBuilderRecord` / `RelationBuilderRecord` — 컬럼/관계 빌더 레코드 타입.
157
218
 
158
- - `ColumnBuilderRecord` — `Record<string, ColumnBuilder<...>>`. `columns()` 반환 타입.
159
- - `InferColumns<T>` — column 빌더 레코드에서 실제 값 타입 추론.
160
- - `InferColumnExprs<T>` — 각 컬럼을 `ExprInput<V>` 로 추론(프로시저 파라미터 타입 등).
161
- - `InferInsertColumns<T>` / `InferUpdateColumns<T>` — INSERT(필수/optional 분리)·UPDATE(전부 optional) 타입.
162
- - `RequiredInsertKeys<T>` / `OptionalInsertKeys<T>` — INSERT 필수/optional 키 집합.
163
- - `DataToColumnBuilderRecord<TData>` — 데이터 레코드를 column 빌더 레코드로 역변환(`insertInto` 대상 테이블 제약에 사용).
164
- - `RelationBuilderRecord` — 관계 빌더 레코드 타입.
165
- - `InferDeepRelations<TRelations>` / `ExtractRelationTarget<T>` / `ExtractRelationTargetResult<T>` — 관계를 통한 심층 타입 추론(관계는 `include` 전이라 모두 optional 로 추론).
219
+ ## 주의사항
166
220
 
167
- `Table`/`View`/`Procedure` 빌더와 `_Migration`(시스템 마이그레이션 테이블 정의)도 함께 노출된다.
221
+ - 모든 빌더 메서드는 인스턴스를 반환하는 불변 체이닝 — 중간 결과를 변수에 담아 분기 재사용 가능.
222
+ - 관계 `target`/`owner` 는 반드시 지연 함수로 — 즉시 참조하면 모듈 로드 순서·순환 참조로 깨짐.
223
+ - View 의 관계는 `relationKey*` 만(DB FK 없음). Table 만 `foreignKey*` 로 실제 FK 제약 생성.
224
+ - 컬럼은 기본 `NOT NULL`. `.nullable()`/`.default()` 는 도메인 근거가 있을 때만(orm.md 정책).
@@ -1,64 +1,118 @@
1
- # @simplysm/orm-common — 하위 타입 / QueryDef·Expr AST / QueryBuilder / 결과 파싱
1
+ # @simplysm/orm-common — types
2
2
 
3
- executor·QueryBuilder 직접 구현하거나, `QueryDef`/`Expr` AST·column 타입을 다루거나, 원시 DB 결과를 TS 객체로 환원할 참조하는 묶음. 일반 쿼리 작성에서는 expr/Queryable 타입들을 가려주므로 직접 일이 적다.
3
+ executor·dialect 어댑터 구현, AST 타입, dialect SQL 렌더링, 결과 파싱을 다루는 군. 일반 쿼리 작성에는 직접 일이 적고, executor 구현체·SQL 검사·결과 변환 계층에서 사용한다.
4
4
 
5
- ## Database / Executor 타입
5
+ ## 컬럼 원시 타입 / DataType
6
6
 
7
- - `Dialect` (type) — `"mysql" | "mssql" | "postgresql"`. 지원 DBMS.
8
- - `dialects` (const) `Dialect[]` 전체 목록. dialect 검증 테스트 루프에 사용.
9
- - `IsolationLevel` (type) `"READ_UNCOMMITTED" | "READ_COMMITTED" | "REPEATABLE_READ" | "SERIALIZABLE"`. 트랜잭션 격리 수준(`connect`/`transaction` 인자).
10
- - `DbContextExecutor` (interface) 실제 DB 연결·실행 어댑터 계약. `connect()`/`close()`/`beginTransaction(isolationLevel?)`/`commitTransaction()`/`rollbackTransaction()`/`executeDefs(defs, resultMetas?)` 구현 필요. 서버(node)·클라이언트(service-client)가 각각 구현해 `DbContext` 생성자에 주입.
11
- - `DataRecord` (type) `{ [key: string]: ColumnPrimitive | DataRecord | DataRecord[] }`. 재귀적 결과 레코드(중첩 include 표현).
12
- - `ResultMeta` (interface) 결과 변환 메타. `columns`=컬럼명→`ColumnPrimitiveStr` 매핑(타입 파싱용), `joins`=조인 alias→`{ isSingle }`(단일/배열 중첩 구분). `Queryable.getResultMeta()` 생성, `parseQueryResult` 가 소비.
13
- - `QueryBuildResult` (interface) `QueryBuilder.build()` 반환. `sql`=빌드된 SQL, `resultSetIndex`=결과를 가져올 인덱스(기본 0; MySQL INSERT+OUTPUT 은 1), `resultSetStride`=다중 결과에서 N번째마다 추출(MySQL 배치 INSERT 의 SELECT 만 모을 때).
14
- - `Migration` (interface) db-context.md 참조(여기서도 재노출).
7
+ ```typescript
8
+ type ColumnPrimitiveMap = { string; number; boolean; DateTime; DateOnly; Time; Uuid; Bytes };
9
+ type ColumnPrimitiveStr = keyof ColumnPrimitiveMap; // 타입 이름 문자열
10
+ type ColumnPrimitive = ColumnPrimitiveMap[ColumnPrimitiveStr] | undefined; // undefined=NULL
11
+ type DataType =
12
+ | { type: "int" } | { type: "bigint" } | { type: "float" } | { type: "double" }
13
+ | { type: "decimal"; precision: number; scale? } | { type: "varchar"; length: number }
14
+ | { type: "char"; length: number } | { type: "text" } | { type: "binary" }
15
+ | { type: "boolean" } | { type: "datetime" } | { type: "date" } | { type: "time" } | { type: "uuid" };
16
+ ```
15
17
 
16
- ## Column 원시 타입
18
+ - `ColumnPrimitiveMap` 타입 이름 → 실제 TS 타입 매핑. `ColumnPrimitiveStr` 은 그 키(`expr.val`/`expr.col` 의 dataType 인자 타입).
19
+ - `ColumnPrimitive` — 저장 가능한 모든 원시 값. `undefined` 가 SQL NULL.
20
+ - `DataType` — SQL 컬럼 타입의 구조화 표현(DDL·`cast` 에서 사용).
21
+ - `dataTypeStrToColumnPrimitiveStr` — `DataType["type"]` → `ColumnPrimitiveStr` 매핑 상수(예 `int → "number"`, `date → "DateOnly"`).
22
+ - `InferColumnPrimitiveFromDataType<T>` — `DataType` 으로부터 TS 타입 추론.
23
+ - `inferColumnPrimitiveStr(value)` — 런타임 값에서 타입 이름 추론. **NULL 이면 throw**(추론 불가).
24
+ - `ColumnMeta` — `{ type; dataType; autoIncrement?; nullable?; default?; description? }`. ColumnBuilder 가 생성해 보유.
17
25
 
18
- - `DataType` (type) — SQL 데이터 타입 union(`{ type: "int" }`, `{ type: "decimal"; precision; scale? }`, `{ type: "varchar"; length }`, `char`/`text`/`binary`/`boolean`/`datetime`/`date`/`time`/`uuid`, `bigint`/`float`/`double`). `cast`/DDL 컬럼 정의에 사용.
19
- - `ColumnPrimitiveMap` (type) — 타입명→실제 TS 타입 매핑(`string`/`number`/`boolean`/`DateTime`/`DateOnly`/`Time`/`Uuid`/`Bytes`).
20
- - `ColumnPrimitiveStr` (type) — `keyof ColumnPrimitiveMap`(타입명 문자열). `ExprUnit.dataType` 의 타입.
21
- - `ColumnPrimitive` (type) — 저장 가능한 모든 원시 값 `| undefined`(undefined=NULL).
22
- - `ColumnMeta` (interface) — 컬럼 메타(`type`/`dataType`/`autoIncrement?`/`nullable?`/`default?`/`description?`). `ColumnBuilder.meta`.
23
- - `dataTypeStrToColumnPrimitiveStr` (const) — SQL 타입명→TS 타입명 매핑 테이블(`int`→`"number"`, `datetime`→`"DateTime"` 등).
24
- - `InferColumnPrimitiveFromDataType<T>` (type) — `DataType` 에서 TS 타입 추론.
25
- - `inferColumnPrimitiveStr(value)` (function) — 런타임 값에서 `ColumnPrimitiveStr` 추론. NULL 이면 추론 불가로 throw. 리터럴을 ExprUnit 으로 자동 래핑할 때 사용.
26
+ ## 데이터·executor 타입 (types/db)
26
27
 
27
- ## Expr AST 타입
28
+ ```typescript
29
+ type Dialect = "mysql" | "mssql" | "postgresql";
30
+ const dialects: Dialect[];
31
+ type IsolationLevel = "READ_UNCOMMITTED" | "READ_COMMITTED" | "REPEATABLE_READ" | "SERIALIZABLE";
32
+ type DataRecord = { [key: string]: ColumnPrimitive | DataRecord | DataRecord[] }; // 재귀(중첩 관계)
33
+ interface ResultMeta { columns: Record<string, ColumnPrimitiveStr>; joins: Record<string, { isSingle: boolean }>; }
34
+ interface QueryBuildResult { sql: string; resultSetIndex?: number; resultSetStride?: number; }
35
+ ```
28
36
 
29
- `Expr` 는 모든 표현식 노드의 union, `WhereExpr` WHERE 절용(비교+논리) union. `expr.*` 빌더가 AST 를 만들고, `ExprRendererBase` SQL 렌더링한다. 각 노드는 `type` 판별 필드를 가진 interface:
37
+ - `Dialect` 지원 DBMS(MySQL 8.0.14+, MSSQL 2012+, PostgreSQL 9.0+). `dialects` 전체 목록(테스트 매트릭스용).
38
+ - `IsolationLevel` — 트랜잭션 격리 수준(기본 `READ_COMMITTED`). `connect`/`transaction`·executor `beginTransaction` 인자.
39
+ - `DataRecord` — 쿼리 결과 레코드(중첩 관계는 객체/배열 중첩).
40
+ - `ResultMeta` — 결과 변환 메타. `columns` 는 평면 키(`posts.id`) → 타입, `joins` 는 JOIN 경로 → 단일/배열 구분. `Queryable.getResultMeta()` 가 생성.
41
+ - `QueryBuildResult` — `build()` 결과. `resultSetIndex`(가져올 결과셋 위치)·`resultSetStride`(N번째마다 추출) 로 다중 결과셋 처리(MySQL INSERT+SELECT, 배치 INSERT 등).
42
+ - `DbContextExecutor` / `Migration` — db-context.md 참조.
30
43
 
31
- - 값: `ExprColumn`(컬럼 참조 `path`), `ExprValue`(리터럴 `value`), `ExprRaw`(`sql`+`params`).
32
- - 비교: `ExprEq`/`ExprGt`/`ExprLt`/`ExprGte`/`ExprLte`/`ExprBetween`/`ExprIsNull`/`ExprLike`/`ExprRegexp`/`ExprIn`/`ExprInQuery`/`ExprExists`.
33
- - 논리: `ExprNot`/`ExprAnd`/`ExprOr`.
34
- - 문자열: `ExprConcat`/`ExprLeft`/`ExprRight`/`ExprTrim`/`ExprPadStart`/`ExprReplace`/`ExprUpper`/`ExprLower`/`ExprLength`/`ExprByteLength`/`ExprSubstring`/`ExprIndexOf`.
35
- - 숫자: `ExprAbs`/`ExprRound`/`ExprCeil`/`ExprFloor`.
36
- - 날짜: `ExprYear`/`ExprMonth`/`ExprDay`/`ExprHour`/`ExprMinute`/`ExprSecond`/`ExprIsoWeek`/`ExprIsoWeekStartDate`/`ExprIsoYearMonth`/`ExprDateDiff`/`ExprDateAdd`/`ExprFormatDate`.
37
- - 조건: `ExprCoalesce`/`ExprNullIf`/`ExprIs`/`ExprSwitch`/`ExprIf`.
38
- - 집계: `ExprCount`/`ExprSum`/`ExprAvg`/`ExprMax`/`ExprMin`.
39
- - 기타: `ExprGreatest`/`ExprLeast`/`ExprRowNum`/`ExprRandom`/`ExprCast`/`ExprSubquery`.
40
- - 윈도우: `ExprWindow`(`fn`=`WinFn`, `spec`=`WinSpec`). `WinFn` union = `WinFnRowNumber`/`WinFnRank`/`WinFnDenseRank`/`WinFnNtile`/`WinFnLag`/`WinFnLead`/`WinFnFirstValue`/`WinFnLastValue`/`WinFnSum`/`WinFnAvg`/`WinFnCount`/`WinFnMin`/`WinFnMax`. `WinSpec`=`{ partitionBy?: Expr[]; orderBy?: [Expr, dir?][] }`.
41
- - `DateUnit` (type) — `"year"|"month"|"day"|"hour"|"minute"|"second"`. dateDiff/dateAdd 단위.
44
+ ## QueryDef AST (types/query-def)
42
45
 
43
- ## QueryDef AST 타입
46
+ `Queryable`/`DbContext` 가 만드는 dialect 독립 쿼리 정의. `QueryDef` 전체 union.
44
47
 
45
- `QueryDef` 모든 쿼리 정의 union. `Queryable`/`DbContext` 생성하고 `executeDefs`·QueryBuilder 소비. 각자 `type` 판별 필드:
48
+ - `QueryDefObjectName` `{ database?; schema?; name }`. DBMS 네임스페이스(MySQL `db.name`, MSSQL `db.schema.name`, PostgreSQL `schema.name`).
49
+ - DML: `SelectQueryDef`(+`SelectQueryDefJoin` — `isSingle?` 추가), `InsertQueryDef`(`records`/`overrideIdentity?`/`aiColName?`/`output?`), `InsertIfNotExistsQueryDef`, `InsertIntoQueryDef`, `UpdateQueryDef`, `DeleteQueryDef`, `UpsertQueryDef`. `CudOutputDef` 는 OUTPUT 절(`columns`/`pkColNames`/`aiColName?`).
50
+ - DDL: `CreateTableQueryDef`/`DropTableQueryDef`/`RenameTableQueryDef`/`TruncateQueryDef`, `AddColumnQueryDef`/`DropColumnQueryDef`/`ModifyColumnQueryDef`/`RenameColumnQueryDef`, `AddPrimaryKeyQueryDef`/`DropPrimaryKeyQueryDef`/`AddForeignKeyQueryDef`/`DropForeignKeyQueryDef`/`AddIndexQueryDef`/`DropIndexQueryDef`, `CreateViewQueryDef`/`DropViewQueryDef`, `CreateProcQueryDef`/`DropProcQueryDef`/`ExecProcQueryDef`, `ClearSchemaQueryDef`.
51
+ - Utils/Meta: `SwitchFkQueryDef`(`{ table; enabled }` — DDL 아님), `SchemaExistsQueryDef`.
52
+ - `DDL_TYPES` — DDL QueryDef 타입 이름 배열(상수). 트랜잭션 내 DDL 차단(`executeDefs`) 검증에 사용. `switchFk` 는 제외(트랜잭션 가능). `DdlType` 은 그 union 타입.
46
53
 
47
- - 공통: `QueryDefObjectName`(`database?`/`schema?`/`name` dialect 네임스페이스), `CudOutputDef`(`columns`/`pkColNames`/`aiColName?` CUD OUTPUT ).
48
- - DML: `SelectQueryDef`(from/as/select/distinct/top/lock/where/joins/orderBy/limit/groupBy/having/with), `SelectQueryDefJoin`(SelectQueryDef + `isSingle?`), `InsertQueryDef`(`records`/`overrideIdentity?`/`aiColName?`/`output?` — overrideIdentity 는 AI 컬럼에 명시값 삽입 시, aiColName 은 PostgreSQL 시퀀스 보정용), `InsertIfNotExistsQueryDef`, `InsertIntoQueryDef`, `UpdateQueryDef`, `DeleteQueryDef`, `UpsertQueryDef`.
49
- - DDL: `CreateTableQueryDef`/`DropTableQueryDef`/`RenameTableQueryDef`/`TruncateQueryDef`, `AddColumnQueryDef`/`DropColumnQueryDef`/`ModifyColumnQueryDef`/`RenameColumnQueryDef`, `AddPrimaryKeyQueryDef`/`DropPrimaryKeyQueryDef`, `AddForeignKeyQueryDef`/`DropForeignKeyQueryDef`, `AddIndexQueryDef`/`DropIndexQueryDef`, `CreateViewQueryDef`/`DropViewQueryDef`, `CreateProcQueryDef`/`DropProcQueryDef`/`ExecProcQueryDef`, `ClearSchemaQueryDef`.
50
- - Utils/Meta: `SwitchFkQueryDef`(`enabled` — DDL 아님, 트랜잭션 내 사용 가능), `SchemaExistsQueryDef`.
51
- - `DDL_TYPES` (const) — DDL 타입 문자열 배열(`["createTable", ...]`). 트랜잭션 내 DDL 차단·검증에 사용(`switchFk` 는 제외). `DdlType` (type) = `(typeof DDL_TYPES)[number]`.
54
+ `SelectQueryDef` 주요 필드: `from`(객체명/서브쿼리/배열(UNION)/문자열) · `as` · `select?` · `distinct?` · `top?` · `lock?` · `where?` · `joins?` · `orderBy?` · `limit?` · `groupBy?` · `having?` · `with?`(재귀 CTE).
52
55
 
53
- ## QueryBuilder (dialect 별 SQL 렌더링)
56
+ ## Expr AST (types/expr)
54
57
 
55
- - `createQueryBuilder(dialect)` (function) `Dialect` 맞는 `QueryBuilderBase` 인스턴스 반환(mysql→`MysqlQueryBuilder` 등). QueryDef 를 SQL 로 변환할 때의 진입점.
56
- - `QueryBuilderBase` (abstract class) — QueryDef→SQL 추상 기본. `build(def)` 가 `def.type` 으로 동적 dispatch 해 `QueryBuildResult` 반환. dialect 공통 로직(WHERE/ORDER BY/GROUP BY/JOIN 렌더 골격) 구현, 차이나는 부분은 abstract(`tableName`/`renderJoin`/`renderLimit` 등). 새 dialect 추가 시 상속.
57
- - `ExprRendererBase` (abstract class) Expr→SQL 추상 기본. `render(expr)` 가 `expr.type` 으로 dispatch. `wrap(name)`(식별자 감싸기: MySQL `` `name` ``, MSSQL `[name]`, PostgreSQL `"name"`)·`escapeString(value)`·`escapeValue(value)` 는 abstract.
58
- - dialect 구현체: `MysqlQueryBuilder`/`MysqlExprRenderer`, `MssqlQueryBuilder`/`MssqlExprRenderer`, `PostgresqlQueryBuilder`/`PostgresqlExprRenderer`. 보통 `createQueryBuilder` 얻고 직접 `new` 하지 않음.
58
+ `expr.*` 만드는 표현식 AST. 인터페이스는 `type` 판별 필드를 가진다.
59
+
60
+ - `Expr` — 전체 표현식 union(값/문자열/숫자/날짜/조건/집계/기타/윈도우/서브쿼리).
61
+ - `WhereExpr` WHERE 전용 union(비교 `ExprEq`/`ExprGt`/.../`ExprBetween`/`ExprIsNull`/`ExprLike`/`ExprRegexp`/`ExprIn`/`ExprInQuery`/`ExprExists` + 논리 `ExprNot`/`ExprAnd`/`ExprOr`).
62
+ - 값: `ExprColumn`(`path`), `ExprValue`(`value`), `ExprRaw`(`sql`/`params`).
63
+ - `DateUnit` — `"year"|"month"|"day"|"hour"|"minute"|"second"`(`dateDiff`/`dateAdd`).
64
+ - 윈도우: `WinFn`(union — `WinFnRowNumber`/`WinFnRank`/`WinFnLag` 등), `WinSpec`(`partitionBy?`/`orderBy?`), `ExprWindow`(`fn`+`spec`).
65
+ - 개별 표현식 인터페이스 전체(`ExprConcat`, `ExprDateDiff`, `ExprSwitch`, `ExprCount`, `ExprCast`, `ExprSubquery`, ...)도 export — 각각 `expr.md` 의 함수에 1:1 대응.
66
+
67
+ ## QueryBuilder (dialect 렌더링)
68
+
69
+ ```typescript
70
+ function createQueryBuilder(dialect: Dialect): QueryBuilderBase;
71
+ abstract class QueryBuilderBase {
72
+ build(def: QueryDef): QueryBuildResult; // def.type 으로 동적 dispatch
73
+ }
74
+ abstract class ExprRendererBase {
75
+ render(expr: Expr | WhereExpr): string;
76
+ renderWhere(exprs: WhereExpr[]): string; // " AND " 결합
77
+ abstract wrap(name: string): string; // 식별자 감싸기 (`name` / [name] / "name")
78
+ abstract escapeString(value: string): string;
79
+ abstract escapeValue(value: unknown): string;
80
+ }
81
+ ```
82
+
83
+ - `createQueryBuilder(dialect)` — dialect 에 맞는 QueryBuilder 인스턴스 반환. executor 가 `QueryDef` 를 SQL 로 만들 때 진입점.
84
+ - `QueryBuilderBase.build(def)` — `QueryDef` → `QueryBuildResult`. `def.type` 이름의 메서드로 dispatch(미지원 타입은 throw).
85
+ - `ExprRendererBase` — `Expr`/`WhereExpr` → SQL 문자열. `wrap`/`escapeString`/`escapeValue` 등 dialect 차이만 abstract.
86
+ - dialect 구현 클래스도 export(직접 인스턴스화보다 `createQueryBuilder` 권장): `MysqlQueryBuilder`/`MssqlQueryBuilder`/`PostgresqlQueryBuilder`, `MysqlExprRenderer`/`MssqlExprRenderer`/`PostgresqlExprRenderer`.
87
+
88
+ ```typescript
89
+ const builder = createQueryBuilder("mysql");
90
+ const { sql } = builder.build(db.user().getSelectQueryDef());
91
+ ```
59
92
 
60
93
  ## 결과 파싱
61
94
 
62
- - `parseQueryResult(rawResults, meta)` (function) — 원시 DB 결과 배열을 `ResultMeta` 기준으로 타입 변환·JOIN 중첩한 객체 배열로 환원. async 전용(대량 처리 중 이벤트 루프 양보). 입력이 비었거나 파싱 후 전부 빈 객체면 `undefined` 반환. 타입 파싱 실패 시 throw. `meta` 없이 부를 이유 없음(입력=출력).
63
- - 평탄 키 `"posts.id"` → 중첩 `{ posts: { id } }` 로 변환. `joins[key].isSingle` 에 따라 배열/단일 객체로 그룹핑. `isSingle: true` 인데 서로 다른 결과가 여럿이면 throw.
64
- - `pickResultSets(rawResults, buildResult)` (function) — 다중 결과셋에서 `QueryBuildResult` 메타(`resultSetIndex`/`resultSetStride`)에 따라 필요한 셋만 추출. `resultSetIndex` 없으면 첫 셋, `stride` 없으면 해당 인덱스 단일 셋, `stride` 있으면 인덱스부터 stride 간격 셋을 concat(MySQL 배치 INSERT 의 SELECT 결과만 모을 때).
95
+ ```typescript
96
+ function parseQueryResult<TRecord>(
97
+ rawResults: Record<string, unknown>[],
98
+ meta: ResultMeta,
99
+ ): Promise<TRecord[] | undefined>;
100
+ function pickResultSets<T>(
101
+ rawResults: T[][],
102
+ buildResult: Pick<QueryBuildResult, "resultSetIndex" | "resultSetStride">,
103
+ ): T[];
104
+ ```
105
+
106
+ - `parseQueryResult(rawResults, meta)` — DB 원시 결과를 `meta` 로 타입 변환·중첩(JOIN 평면 키 → 객체/배열)한다. async 전용(대량 처리 중 100건마다 이벤트 루프 양보). 입력이 비었거나 파싱 후 전부 빈 객체면 `undefined`. 타입 파싱 실패 시 throw. `isSingle` JOIN 에 서로 다른 다중 결과가 오면 throw. (null 은 보존, undefined 키는 제거.)
107
+ - `pickResultSets(rawResults, buildResult)` — 다중 결과셋에서 필요한 셋만 추출. `resultSetIndex` 없으면 첫 셋, `stride` 없으면 해당 인덱스 단일, 있으면 인덱스부터 stride 간격으로 concat(MySQL 배치 INSERT;SELECT 패턴).
108
+
109
+ ```typescript
110
+ const meta = { columns: { id: "number", "posts.id": "number" }, joins: { posts: { isSingle: false } } };
111
+ const rows = await parseQueryResult(rawResults, meta);
112
+ ```
113
+
114
+ ## 주의사항
115
+
116
+ - 이 군은 executor·SQL 검사·결과 파싱 계층용. 앱 쿼리 작성은 `queryable.md`/`expr.md` 의 고수준 API 사용.
117
+ - `parseQueryResult` 의 `meta` 는 필수 — 없으면 호출 자체가 불필요(입력=출력). 보통 `Queryable.getResultMeta()` 로 생성.
118
+ - `QueryDefObjectName` 의 네임스페이스 해석은 dialect 마다 다름(MySQL 은 schema 무시 등).
@@ -1,15 +1,15 @@
1
1
  # @simplysm/orm-node
2
2
 
3
- Node.js 환경에서 `@simplysm/orm-common` 의 `DbContext` 를 MySQL/MSSQL/PostgreSQL 연결·실행하는 ORM 런타임. 고수준 진입점 `createOrm`(트랜잭션 경계 관리)과 저수준 연결 `createDbConn`/`DbConn`(raw SQL·파라미터 쿼리·bulk insert·수동 트랜잭션)을 함께 노출. query DSL 자체는 `@simplysm/orm-common` 의 `DbContext` 가 제공하고, 이 패키지는 그 컨텍스트를 실제 DB 연결에 묶는 역할.
3
+ `@simplysm/orm-common` 의 `DbContext`(query DSL)Node.js 환경의 실제 DB(MySQL/MSSQL/PostgreSQL) 연결에 묶어 실행하는 ORM 런타임. query 빌더·`expr` 자체는 `orm-common` 이 제공하고, 이 패키지는 연결 수립·트랜잭션 경계·드라이버별 실행을 담당. 고수준 진입 `createOrm`(트랜잭션 경계 자동 관리)과 저수준 연결 `createDbConn`/`DbConn`(raw SQL·파라미터 쿼리·bulk insert·수동 트랜잭션)을 함께 노출.
4
4
 
5
5
  ## 사용 트리거 인덱스
6
6
 
7
- - **createOrm / Orm / OrmOptions** — `DbContext` 서브클래스로 ORM 인스턴스를 만들고 `connect`/`connectWithoutTransaction` 으로 트랜잭션 경계를 잡아 query 를 돌릴 때. (아래 "ORM 진입" 군)
8
- - **createDbConn / DbConn / DbConnConfig 계열 / getDialectFromConfig / NodeDbContextExecutor / DB_CONN_\* 상수** — `createOrm` 추상화를 거치지 않고 raw SQL·파라미터 쿼리·bulk insert·수동 트랜잭션을 직접 다루거나, dialect별 접속 설정 타입을 작성하거나, `DbContext` executor 를 손수 조립할 때. 자세히: [db-conn.md](./db-conn.md)
7
+ - **createOrm / Orm / OrmOptions** — `DbContext` 서브클래스로 ORM 인스턴스를 만들고 `connect`/`connectWithoutTransaction` 으로 트랜잭션 경계를 잡아 query 를 실행할 때. (아래 "ORM 진입" 군)
8
+ - **createDbConn / DbConn / DbConnConfig 계열 / getDialectFromConfig / NodeDbContextExecutor / DB_CONN_\* 상수** — `createOrm` 추상화를 거치지 않고 raw SQL·파라미터 쿼리·bulk insert·수동 트랜잭션을 직접 다루거나, dialect별 접속 설정 타입을 작성하거나, `DbContext` executor 를 손수 조립할 때. 자세히: [db-conn.md](./db-conn.md)
9
9
 
10
10
  ## ORM 진입
11
11
 
12
- `DbContext` 서브클래스와 접속 설정을 받아 연결·트랜잭션 경계를 관리하는 고수준 진입. 대부분의 작업은 이 군으로 충분하며, raw SQL·executor 커스터마이징이 필요할 때만 [db-conn.md](./db-conn.md) 계층으로 내려감.
12
+ `DbContext` 서브클래스와 접속 설정을 받아 연결·트랜잭션 경계를 관리하는 고수준 진입. 대부분의 작업은 이 군으로 충분하고, raw SQL·executor 커스터마이징이 필요할 때만 [db-conn.md](./db-conn.md) 계층으로 내려감.
13
13
 
14
14
  ### createOrm
15
15
 
@@ -21,10 +21,10 @@ function createOrm<T extends DbContext>(
21
21
  ): Orm<T>
22
22
  ```
23
23
 
24
- `DbContext` 서브클래스를 받아 `Orm<T>` 를 반환. DB 인스턴스는 `connect`/`connectWithoutTransaction` 호출마다 새로 생성되므로 반환된 `Orm` 객체 자체는 재사용 가능.
24
+ `DbContext` 서브클래스를 받아 `Orm<T>` 를 반환. DB 인스턴스는 `connect`/`connectWithoutTransaction` 호출마다 새로 생성하므로 반환된 `Orm` 객체 자체는 재사용 가능.
25
25
 
26
- - DbClass — `DbContext` 를 상속한 생성자. `(executor, { database, schema? })` 시그니처 고정. `this.queryable(Entity)` 로 query 진입점을 정의한 사용자 DB 클래스를 넘김 — 어떤 엔티티 집합을 다룰지 결정하는 자리.
27
- - config — `DbConnConfig`(dialect별 분기 유니온, [db-conn.md](./db-conn.md) 참조). 접속 대상·인증 정보. DBMS 종류·호스트·계정이 여기서 정해짐.
26
+ - DbClass — `DbContext` 를 상속한 생성자(`(executor, { database, schema? })` 시그니처 고정). `this.queryable(Entity)` 로 query 진입점을 정의한 사용자 DB 클래스를 넘김 — 어떤 엔티티 집합을 다룰지 결정하는 자리.
27
+ - config — `DbConnConfig`(dialect별 분기 유니온, [db-conn.md](./db-conn.md) 참조). 접속 대상·인증·기본 DB. DBMS 종류·호스트·계정이 여기서 정해짐.
28
28
  - options? — `OrmOptions`. config 의 `database`/`schema` 를 덮어쓰는 우선 옵션. 같은 접속 정보로 DB·스키마만 바꿔 쓸 때(다중 테넌트 등) 지정.
29
29
 
30
30
  database 해석: `options.database` → `config.database` 순으로 찾고, 둘 다 없거나 빈 문자열이면 `"database는 필수입니다"` throw. schema 해석도 `options.schema` → `config.schema` 순(없으면 `undefined` 유지).
@@ -35,9 +35,8 @@ class TestDb extends DbContext {
35
35
  }
36
36
  const orm = createOrm(TestDb, mysqlConfig, { database: "TestDb" });
37
37
  await orm.connect(async (db) => {
38
- await db.user().insert([{ id: 100, name: "orm-test" }]);
39
- return db.user().execute();
40
- }); // 트랜잭션 안에서 실행 후 콜백 정상 종료 시 자동 커밋
38
+ return db.user().select((u) => ({ id: u.id, name: u.name })).execute();
39
+ }); // 트랜잭션 안에서 실행, 콜백 정상 종료 시 자동 커밋
41
40
  ```
42
41
 
43
42
  ### Orm
@@ -55,16 +54,16 @@ interface Orm<T extends DbContext> {
55
54
  `createOrm` 반환 타입. 각 메서드 호출마다 DB 인스턴스를 새로 만들어 연결→콜백→정리.
56
55
 
57
56
  - DbClass / config / options — `createOrm` 에 넘긴 값을 그대로 읽기 전용으로 보관. 같은 설정으로 재연결·진단할 때 참조.
58
- - connect — 콜백을 **트랜잭션 안에서** 실행. 콜백이 정상 종료하면 커밋, throw 하면 롤백 후 그 오류를 다시 throw. 여러 쓰기를 원자적으로 묶어야 때 기본으로 사용.
57
+ - connect — 콜백을 **트랜잭션 안에서** 실행. 콜백이 정상 종료하면 커밋, throw 하면 롤백 후 그 오류를 다시 throw. 여러 쓰기를 원자적으로 묶을 때 기본으로 사용.
59
58
  - isolationLevel? — `IsolationLevel`(`@simplysm/orm-common`). 트랜잭션 격리 수준. 테스트로 확인된 값: `"READ_UNCOMMITTED" | "READ_COMMITTED" | "REPEATABLE_READ" | "SERIALIZABLE"`. 미지정 시 연결의 `defaultIsolationLevel`, 그것도 없으면 `READ_UNCOMMITTED`. 더티 리드를 막아야 하면 `READ_COMMITTED` 이상으로 올림.
60
- - connectWithoutTransaction — 콜백을 **트랜잭션 없이** 실행. 읽기 전용이거나, initialize 등 트랜잭션 안에서 동작하지 않는 작업, 또는 콜백 내부에서 `db.transaction(...)` 으로 부분 트랜잭션을 직접 열어 일부 구간만 원자화할 때.
59
+ - connectWithoutTransaction — 콜백을 **트랜잭션 없이** 실행. 읽기 전용이거나, 트랜잭션 밖에서 동작해야 하는 작업, 또는 콜백 내부에서 `db.transaction(...)` 으로 일부 구간만 부분 트랜잭션으로 직접 묶을 때.
61
60
  - callback — 연결된 DbContext 인스턴스(`T`)를 받아 query 를 수행하고 임의 값 `R` 을 반환. 그 반환값이 `connect`/`connectWithoutTransaction` 의 결과가 됨.
62
61
 
63
62
  ```typescript
64
63
  // 읽기는 트랜잭션 없이, 그 안에서 일부 쓰기만 부분 트랜잭션으로
65
64
  await orm.connectWithoutTransaction(async (db) => {
66
65
  await db.transaction(async () => {
67
- await db.user().insert([{ id: 300, name: "partial-tx" }]);
66
+ await db.user().insert([{ name: "Alice", isActive: true }]);
68
67
  });
69
68
  return db.user().execute();
70
69
  });