@simplysm/sd-claude 14.0.91 → 14.0.92

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 (93) hide show
  1. package/claude/references/sd-simplysm14/README.md +7 -6
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +59 -39
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +119 -186
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +70 -31
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +55 -57
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +86 -105
  7. package/claude/references/sd-simplysm14/apis/angular/infra.md +48 -57
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +37 -47
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +82 -74
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +61 -50
  11. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +74 -57
  12. package/claude/references/sd-simplysm14/apis/angular/sheet.md +63 -72
  13. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +23 -18
  14. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +21 -19
  15. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +23 -18
  16. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +72 -32
  17. package/claude/references/sd-simplysm14/apis/core-browser/README.md +18 -18
  18. package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +29 -29
  19. package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +41 -41
  20. package/claude/references/sd-simplysm14/apis/core-common/README.md +97 -90
  21. package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +75 -51
  22. package/claude/references/sd-simplysm14/apis/core-common/collection-ext.md +81 -0
  23. package/claude/references/sd-simplysm14/apis/core-common/errors.md +27 -29
  24. package/claude/references/sd-simplysm14/apis/core-common/obj.md +44 -45
  25. package/claude/references/sd-simplysm14/apis/core-common/serialization.md +34 -33
  26. package/claude/references/sd-simplysm14/apis/core-common/value-types.md +86 -0
  27. package/claude/references/sd-simplysm14/apis/core-node/README.md +6 -6
  28. package/claude/references/sd-simplysm14/apis/core-node/consola.md +3 -0
  29. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +2 -2
  30. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +1 -1
  31. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +2 -2
  32. package/claude/references/sd-simplysm14/apis/core-node/worker.md +6 -3
  33. package/claude/references/sd-simplysm14/apis/excel/README.md +10 -10
  34. package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +4 -2
  35. package/claude/references/sd-simplysm14/apis/excel/utils.md +1 -1
  36. package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +6 -6
  37. package/claude/references/sd-simplysm14/apis/lint/README.md +6 -32
  38. package/claude/references/sd-simplysm14/apis/lint/recommended.md +60 -0
  39. package/claude/references/sd-simplysm14/apis/lint/rules.md +17 -17
  40. package/claude/references/sd-simplysm14/apis/orm-common/README.md +15 -6
  41. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +68 -102
  42. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +75 -89
  43. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +87 -99
  44. package/claude/references/sd-simplysm14/apis/orm-common/schema.md +110 -147
  45. package/claude/references/sd-simplysm14/apis/orm-common/types.md +48 -51
  46. package/claude/references/sd-simplysm14/apis/orm-node/README.md +8 -13
  47. package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +5 -5
  48. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +9 -6
  49. package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +9 -8
  50. package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +23 -19
  51. package/claude/references/sd-simplysm14/apis/service-client/README.md +20 -12
  52. package/claude/references/sd-simplysm14/apis/service-client/orm.md +6 -6
  53. package/claude/references/sd-simplysm14/apis/service-client/transport.md +1 -1
  54. package/claude/references/sd-simplysm14/apis/service-common/README.md +35 -32
  55. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +23 -22
  56. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +23 -23
  57. package/claude/references/sd-simplysm14/apis/service-server/README.md +51 -43
  58. package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +6 -6
  59. package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +31 -21
  60. package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +8 -8
  61. package/claude/references/sd-simplysm14/apis/storage/README.md +55 -49
  62. package/claude/references/sd-simplysm14/manuals/client-component.md +843 -740
  63. package/claude/references/sd-simplysm14/manuals/client-crud.md +8 -0
  64. package/claude/references/sd-simplysm14/manuals/client-demo.md +6 -16
  65. package/claude/references/sd-simplysm14/manuals/client-shared-data.md +26 -0
  66. package/claude/references/sd-simplysm14/manuals/logging.md +1 -1
  67. package/claude/references/sd-simplysm14/manuals/orm.md +15 -1
  68. package/claude/rules/sd-design-rules.md +7 -0
  69. package/claude/sd-system-prompt.md +5 -8
  70. package/claude/skills/sd-debug/SKILL.md +43 -0
  71. package/claude/skills/sd-debug/workflow.js +390 -0
  72. package/claude/skills/sd-demo/SKILL.md +18 -20
  73. package/claude/skills/sd-dev/SKILL.md +127 -24
  74. package/claude/skills/sd-docs/SKILL.md +5 -3
  75. package/claude/skills/sd-docs/references/subagent-prompt.md +2 -3
  76. package/claude/skills/sd-impl/SKILL.md +18 -18
  77. package/claude/skills/sd-manual/SKILL.md +1 -0
  78. package/claude/skills/sd-review/SKILL.md +24 -18
  79. package/claude/skills/sd-review/workflow.js +324 -0
  80. package/claude/skills/sd-spec/SKILL.md +96 -679
  81. package/claude/skills/sd-spec/references/example-spec.md +28 -50
  82. package/claude/skills/sd-spec/references/format-analyze.md +232 -0
  83. package/claude/skills/sd-spec/references/format-design.md +248 -0
  84. package/claude/skills/sd-spec/workflow-analyze.js +615 -0
  85. package/claude/skills/sd-spec/workflow-design.js +667 -0
  86. package/claude/skills/sd-unpack/scripts/handlers/office_com.py +5 -1
  87. package/package.json +1 -1
  88. package/scripts/postinstall.mjs +157 -18
  89. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +0 -68
  90. package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +0 -77
  91. package/claude/references/sd-simplysm14/apis/core-common/datetime.md +0 -86
  92. package/claude/skills/sd-skill/SKILL.md +0 -245
  93. package/claude/skills/sd-skill/scripts/run_eval.py +0 -380
@@ -1,119 +1,121 @@
1
1
  # @simplysm/orm-common — Queryable (쿼리 작성·실행 · 프로시저 · 검색)
2
2
 
3
- `db.user()` 처럼 컨텍스트 멤버를 호출하면 `Queryable<TData, TFrom>` 를 받는다. immutable 체이닝으로 옵션·필터·조인·그룹을 쌓고, 종단 메서드(`execute`/`single`/`count`/`insert`/...)로 실행한다. where/select/orderBy 콜백 안에서는 [expr.md](./expr.md) 의 `expr` 로 표현식을 만든다. 프로시저는 `Executable`, 텍스트 검색 구문은 `parseSearchQuery` 가 처리한다.
3
+ `db.user()` 처럼 컨텍스트 멤버를 호출하면 `Queryable<TData, TFrom>` 를 받는다. immutable 체이닝으로 옵션·필터·조인·그룹을 쌓고, 종단 메서드(`execute`/`single`/`count`/`insert`/...)로 실행한다. where/select/orderBy 콜백 안에서는 [expr.md](./expr.md) 의 `expr` 로 표현식을 만든다. 프로시저는 `Executable`, 텍스트 검색 구문은 `parseSearchQuery` 가 처리한다. 표준 조회 흐름·안티패턴은 orm.md/orm-union.md 참조.
4
4
 
5
- 표준 조회 흐름(orm.md): `db.X()` → `joinSingle` 로 도출 컬럼 부착 → `select` 로 모든 도출을 한 번에 projection → projected 컬럼 이름으로 `where`/`orderBy` → `count` → `orderBy().limit().execute()`. SELECT 절에 `expr.subquery`/`expr.exists` 를 넣지 말고 `joinSingle` 로 1회 부착, `wrap()` 은 `distinct`/`groupBy` 뒤 `count` 등 프레임워크가 요구할 때만 사용.
5
+ ## Queryable<TData, TFrom>
6
6
 
7
- ## Queryable 조립 메서드 (체이닝)
7
+ `TData` = 결과 데이터 타입, `TFrom` = 소스 `TableBuilder`(CUD 가능 여부). `select`/`join`/`groupBy`/`distinct`/`wrap`/`union` 등을 거치면 `TFrom` 이 `never` 가 되어 CUD 가 막힌다(파생 컬럼 위에서는 INSERT/UPDATE 불가).
8
8
 
9
- 옵션:
9
+ ### 옵션 — select / distinct / lock
10
10
 
11
- - `select(fn)` — SELECT column 재정의. `fn(columns) => R` R 결과 형태. 리터럴 상수는 자동으로 ExprUnit 래핑. 호출 CUD 불가(`TFrom` 소실).
12
- - `distinct()` — DISTINCT 적용. 이후 `count()` 하려면 `wrap()` 필요.
13
- - `lock()` — 행 잠금(FOR UPDATE). 트랜잭션 안에서 선택 행 배타 잠금. CUD 가능 상태 유지.
11
+ - `select(fn)` — projection. `fn` 원본 컬럼 프록시를 받아컬럼 객체 반환. 반환 객체 안에서 `expr.*` 도출(coalesce/CASE/산식)을 번에 작성. 결과 `TData` 가 새 shape 로 바뀜.
12
+ - `distinct()` — `DISTINCT` 적용. 이후 `count()` 하려면 `wrap()` 먼저(아니면 throw).
13
+ - `lock()` — `FOR UPDATE` 행 잠금. 트랜잭션 내에서 선택 행 배타 잠금이 필요할 때.
14
14
 
15
- 제한:
15
+ ```typescript
16
+ db.user().select((u) => ({ userName: u.name, userEmail: u.email }))
17
+ ```
16
18
 
17
- - `top(count)`상위 N행. ORDER BY 없이도 사용 가능.
18
- - `limit(skip, take)` — 페이지네이션 OFFSET/LIMIT. `skip`=건너뛸 수, `take`=가져올 수. **먼저 `orderBy()` 호출 필수** — 없으면 throw.
19
+ ### 제한top / limit
19
20
 
20
- 정렬:
21
+ - `top(count)` — 상위 `count` 행만. ORDER BY 없이도 사용 가능.
22
+ - `limit(skip, take)` — 페이지네이션. `skip`=OFFSET, `take`=LIMIT. **호출 전 `orderBy()` 필수**(없으면 throw).
21
23
 
22
- - `orderBy(fnOrKey, orderBy?)` — 정렬 추가(여러 번 호출 시 순서대로). `fnOrKey`=정렬 column 반환 함수 또는 체인 경로 문자열(`"user.name"`, 동적 정렬용). `orderBy`="ASC"|"DESC"(기본 ASC).
24
+ ```typescript
25
+ db.user().orderBy((u) => u.createdAt, "DESC").limit(page * 50, 50)
26
+ ```
23
27
 
24
- 필터:
28
+ ### 정렬 — orderBy
25
29
 
26
- - `where(predicate)` — WHERE 조건 추가(여러 번 호출 시 AND 결합). `predicate(columns) => WhereExprUnit[]`. 배열 여러 조건도 AND.
27
- - `search(fn, searchText)` — 텍스트 검색. `fn(columns) => ExprUnit<string|undefined>[]`(검색 대상 column 들), `searchText`=검색 구문(아래 `parseSearchQuery` 문법). 빈 문자열이면 무변경. 공백=OR, `+`=필수(AND), `-`=제외(NOT), `"구문"`=정확 일치(필수). 내부적으로 `expr.like(expr.lower(col), pattern)` 로 대소문자 무시 매칭.
30
+ - `orderBy(fnOrKey, orderBy?)` — 정렬 조건 추가(여러 번 호출 시 순서대로). `fnOrKey`=컬럼 반환 함수 또는 체인 경로 문자열(`"user.name"`, 동적 정렬용). `orderBy`=`"ASC" | "DESC"`(기본 ASC).
28
31
 
29
- 그룹:
32
+ ### 필터 — where / search
30
33
 
31
- - `groupBy(fn)` — GROUP BY. `fn(columns) => ExprUnit<ColumnPrimitive>[]`. 호출 CUD 불가.
32
- - `having(predicate)` — GROUP BY 이후 필터. `predicate(columns) => WhereExprUnit[]`.
34
+ - `where(predicate)` — WHERE 조건 추가(여러 번 호출 시 AND 결합). `predicate` 는 컬럼 프록시를 받아 `WhereExprUnit[]` 반환. select 만든 파생 컬럼 이름도 직접 참조 가능(framework 가 AST inline).
35
+ - `search(fn, searchText)` — 텍스트 검색. `fn`=검색 대상 문자열 컬럼 배열 반환, `searchText`=검색 구문(`parseSearchQuery` 규칙: 공백=OR, `+`=필수, `-`=제외, `"..."`=구문, `*`=와일드카드). 문자열이면 조건 미추가. 내부적으로 `LOWER(col) LIKE pattern` 조합으로 변환.
33
36
 
34
- 조인:
37
+ ```typescript
38
+ db.user()
39
+ .where((u) => [expr.eq(u.isActive, true)])
40
+ .search((u) => [u.name, u.email], "John -withdrawn")
41
+ ```
35
42
 
36
- - `join(as, fn)` 1:N LEFT JOIN, 결과에 **배열**(`{ [as]?: R[] }`)로 부착. `fn(qr, parentCols) => Queryable` 안에서 `qr.from(Table).where(...)` 로 조인 본문 구성.
37
- - `joinSingle(as, fn)` — N:1/1:1 LEFT JOIN, 결과에 **단일 객체**(`{ [as]?: R }`)로 부착. 도출 컬럼(집계·boolean)을 outer 행에 붙일 때 표준 수단.
38
- - `include(fn)` — 빌더에 정의한 FK/FKT/RelationKey 관계 자동 JOIN. `fn(item) => item.user.company` 형태로 타입 안전 경로 지정(`PathProxy`, ColumnPrimitive 가 아닌 관계 필드만 접근 가능). 다단계·다중 호출 가능. 관계 미정의 시 throw.
43
+ ### 그룹groupBy / having
39
44
 
40
- 서브쿼리/UNION:
45
+ - `groupBy(fn)` — GROUP BY. `fn` 은 그룹 컬럼 배열 반환. 이후 `count()` 하려면 `wrap()` 먼저(아니면 throw).
46
+ - `having(predicate)` — 그룹 필터(여러 번 호출 시 AND). 집계 컬럼 기준 필터링.
41
47
 
42
- - `wrap()`현재 Queryable 서브쿼리(derived table)로 감쌈. `distinct()`/`groupBy()` 뒤 `count()` 처럼 프레임워크가 요구할 때만.
43
- - `static Queryable.union(...queries)` — 2개 이상 Queryable 을 UNION(중복 제거)으로 결합. 결과 위 fluent 연산자는 외부 union 결과에 적용. select 컬럼 이름·타입·순서를 양쪽 동일하게 맞춰야 함(orm-union.md). 2개 미만이면 throw.
48
+ ### 조인join / joinSingle / include
44
49
 
45
- 재귀:
50
+ - `join(as, fn)` — 1:N LEFT JOIN. 결과에 `as` 키로 배열 추가. `fn(qr, cols)` 의 `qr.from(Table).where(...)` 로 조인 조건·서브쿼리 정의.
51
+ - `joinSingle(as, fn)` — N:1/1:1 LEFT JOIN. 결과에 `as` 키로 단일 객체(또는 undefined) 추가. 도메인 boolean·집계는 SELECT subquery 대신 `joinSingle` 안 `from+where+select(aggregate)` 로 부착(orm.md).
52
+ - `include(fn)` — `TableBuilder` 의 FK/FKT 관계를 자동 조인. `fn(item)` 은 PathProxy 로 관계 경로만 접근(컬럼은 컴파일 에러). 다단계(`p.user.company`)·다중 호출 지원. 관계 미정의면 throw.
46
53
 
47
- - `recursive(fn)` — WITH RECURSIVE CTE 생성(계층 데이터). `fn(cte) => cte.from(Table).where(...)` 안에서 `e.self[0]` 로 직전 단계 행을 참조.
54
+ ```typescript
55
+ db.post()
56
+ .joinSingle("user", (qr, p) => qr.from(User).where((u) => [expr.eq(u.id, p.userId)]))
57
+ .include((p) => p.user.company)
58
+ ```
48
59
 
49
- `db.user().join(...)`/`recursive(...)`/`Queryable.union(...)` 안에서 쓰는 `JoinQueryable`·`RecursiveQueryable` `from`/`select`/`union` 콜백 인자로만 노출되며 직접 import 하지 않는다.
60
+ ### 서브쿼리 wrap / union / recursive
50
61
 
51
- ## Queryable종단 메서드
62
+ - `wrap()`현재 Queryable 을 서브쿼리(derived table)로 감쌈. `distinct()`/`groupBy()` 후 `count()` 호출에 필요한 자리에서만 사용(불필요한 wrap 금지, orm.md).
63
+ - `static Queryable.union(...queries)` — 2개 이상 Queryable 을 UNION 결합(중복 제거). 결과는 derived table 로 취급되어 이후 `orderBy`/`limit`/`where` 등은 union 결과 위에 적용. 각 소스에 predicate pushdown 하려면 union 전에 미리 호출. 2개 미만이면 `ArgumentError`. 이종 엔티티 합치기는 orm-union.md.
64
+ - `recursive(fn)` — 재귀 CTE(WITH RECURSIVE). 계층 데이터(조직도·카테고리 트리)용. `fn(cte)` 의 `cte.from(Table)` 안에서 `self` 로 상위 결과를 자기참조.
52
65
 
53
- 조회:
66
+ ```typescript
67
+ const items = await Queryable.union(inQr, outQr).orderBy((r) => r.date, "DESC").limit(0, 50).execute();
68
+ ```
54
69
 
55
- - `execute(): Promise<TData[]>` — SELECT 실행, 결과 배열.
56
- - `single(): Promise<TData | undefined>` — 단일 결과. 2건 이상이면 throw.
57
- - `first(): Promise<TData | undefined>` — 첫 행만(내부적으로 `top(1)`). 복수여도 에러 없음.
58
- - `count(fn?): Promise<number>` — 행 수. `fn?`=셀 column 지정(미지정 시 전체). `distinct()`/`groupBy()` 직후 호출하면 throw(→ `wrap()` 먼저).
59
- - `exists(): Promise<boolean>` — 조건 매칭 행 존재 여부(내부 `top(1)`).
70
+ ### 종단 — SELECT 실행
60
71
 
61
- CUD(모두 `TFrom` TableBuilder 때만 `select`/`groupBy` 후엔 불가):
72
+ - `execute()` SELECT 실행, 결과 배열 반환.
73
+ - `single()` — 단일 결과 또는 undefined. 2건 이상이면 `ArgumentError`.
74
+ - `first()` — 첫 결과 또는 undefined(`top(1)`).
75
+ - `exists()` — 조건 일치 행 존재 여부 `boolean`(`top(1)`).
76
+ - `count(fn?)` — 행 수. `fn`=셀 컬럼 지정(생략 시 전체). `distinct()`/`groupBy()` 직후 호출 시 throw(→ `wrap()` 먼저).
77
+ - `getSelectQueryDef()` / `getResultMeta(outputColumns?)` — 실행 없이 `SelectQueryDef` AST / 결과 변환 메타(`ResultMeta`) 산출. 서브쿼리 합성·저수준 실행용.
62
78
 
63
- - `insert(records)` / `insert(records, outputColumns)` 다건 INSERT. `records: $inferInsert[]`. MSSQL 1000행 제한 때문에 1000개씩 청크 분할. `outputColumns: K[]` 지정 시 삽입된 행에서 그 column 만 배열로 반환(AI/PK 값 회수). 빈 배열이면 no-op.
64
- - `insertIfNotExists(record)` / `(record, outputColumns)` — 현재 WHERE 조건 매칭 행이 없을 때만 1건 INSERT. output 지정 시 삽입 행 1개 반환.
65
- - `insertInto(targetTable)` / `(targetTable, outputColumns)` — 현재 SELECT 결과를 다른 테이블에 INSERT INTO ... SELECT. `targetTable` 의 column 구조가 현재 결과와 호환해야 함.
66
- - `update(recordFwd)` / `(recordFwd, outputColumns)` — UPDATE. `recordFwd(cols) => QueryableWriteRecord<$inferUpdate>`(갱신할 column→값). 값은 `ExprInput`(리터럴 직접 가능, `expr.val` 불필요). output 지정 시 갱신 행 반환.
67
- - `delete()` / `delete(outputColumns)` — DELETE. output 지정 시 삭제 행 반환.
68
- - `upsert(updateFn)` / `upsert(insertFn, outputColumns?)` / `upsert(updateFn, insertFn)` / `upsert(updateFn, insertFn, outputColumns)` — WHERE 매칭 행 있으면 UPDATE, 없으면 INSERT(MERGE). `updateFn(cols) => 갱신값`, `insertFn(updateRecord) => 삽입값`(생략 시 update 값 재사용). output 지정 시 영향 행 반환.
79
+ ### 종단 — INSERT
69
80
 
70
- QueryDef 생성기(실행 없이 AST 만): `getSelectQueryDef()`, `getInsertQueryDef(records, outputColumns?)`, `getInsertIfNotExistsQueryDef(...)`, `getInsertIntoQueryDef(...)`, `getUpdateQueryDef(...)`, `getDeleteQueryDef(...)`, `getUpsertQueryDef(...)`, `getResultMeta(outputColumns?)`. executor 우회·디버깅·배치 조립용.
81
+ - `insert(records, outputColumns?)` — 배열 삽입(MSSQL 1000행 제한 대비 1000개씩 청크 분할). `records`=`$inferInsert[]`. `outputColumns` 지정 시 삽입된 행의 해당 컬럼 배열 반환(예: 생성된 `id`). 배열이면 즉시 반환.
82
+ - `insertIfNotExists(record, outputColumns?)` — 현재 WHERE 조건에 맞는 행이 없을 때만 1건 삽입. `outputColumns` 지정 시 삽입 행 반환.
83
+ - `insertInto(targetTable, outputColumns?)` — 현재 SELECT 결과를 다른 테이블에 INSERT INTO ... SELECT. `targetTable` 컬럼이 현재 데이터 shape 와 맞아야 함.
71
84
 
72
- 기타:
85
+ ```typescript
86
+ const [created] = await db.user().insert([{ name: "홍길동" }], ["id"]);
87
+ ```
73
88
 
74
- - `switchFk(enabled)` Queryable 소스 테이블 FK 제약을 활성/비활성. 트랜잭션 안에서 가능.
75
- - `readonly meta` — 내부 조립 상태(from/where/joins/columns 등). 직접 수정하지 않음.
89
+ ### 종단UPDATE / DELETE / UPSERT
90
+
91
+ - `update(recordFwd, outputColumns?)` — UPDATE. `recordFwd(cols)` 가 갱신 컬럼/값 객체 반환(`ExprInput`, 리터럴 직접 전달 — `expr.val` 불필요). `outputColumns` 지정 시 갱신 행 반환.
92
+ - `delete(outputColumns?)` — DELETE. `outputColumns` 지정 시 삭제 행 반환.
93
+ - `upsert(updateFn, insertFn?, outputColumns?)` — WHERE 조건 일치 시 UPDATE, 없으면 INSERT. `insertFn` 생략 시 update 값으로 insert. `insertFn(updateRecord)` 는 update 결과를 받아 insert 레코드 산출. `outputColumns` 지정 시 영향 행 반환.
94
+ - 각 종단마다 `getUpdateQueryDef`/`getDeleteQueryDef`/`getUpsertQueryDef`/`getInsertQueryDef`/`getInsertIfNotExistsQueryDef`/`getInsertIntoQueryDef` 로 실행 없이 def 만 얻을 수 있음.
76
95
 
77
96
  ```typescript
78
- // 표준 조회 (orm.md 흐름)
79
- const items = await db.order()
80
- .joinSingle("state", (q, p) =>
81
- q.from(OrderLine).where((l) => [expr.eq(l.orderId, p.id)])
82
- .select((l) => ({ sumQty: expr.sum(l.qty), doneCnt: expr.count(l.doneAt) })),
83
- )
84
- .select((p) => ({
85
- id: p.id,
86
- sumQty: expr.coalesce(p.state!.sumQty, 0),
87
- isDone: expr.gt(p.state!.doneCnt, 0),
88
- }))
89
- .where((r) => [expr.eq(r.isDone, true)])
90
- .orderBy((r) => r.id, "DESC")
91
- .limit(0, 50)
92
- .execute();
93
-
94
- // CUD — 리터럴 직접 전달
95
- await db.user().where((u) => [expr.eq(u.id, 1)]).update((u) => ({ name: "새이름" }));
96
- const [inserted] = await db.user().insert([{ name: "홍길동" }], ["id"]);
97
+ await db.user().where((u) => [expr.eq(u.id, 1)]).update(() => ({ name: "수정" }));
98
+ await db.user().where((u) => [expr.eq(u.email, "a@b.com")]).upsert(() => ({ name: "A", email: "a@b.com" }));
97
99
  ```
98
100
 
99
- ## queryable / executable (팩토리 함수)
101
+ ### DDL 헬퍼
100
102
 
101
- `DbContext` 내부에서 `this.queryable()`/`this.executable()` 쓰는 일반적이지만, 모듈 함수 형태도 export 됨.
103
+ - `switchFk(enabled)` 이 테이블의 FK 제약 활성/비활성 토글. `enabled` true=활성, false=비활성. 대량 적재 FK 일시 해제용. Table/View 기반에서만.
102
104
 
103
- - `queryable(db, tableOrView, as?)` — `() => Queryable` 팩토리. `as?` 미지정 시 호출마다 새 alias(`db.getNextAlias()`), 지정 시 고정. 반환 Queryable 의 column 은 빌더 정의로부터 구성(View 는 `viewFn` 평가).
104
- - `executable(db, builder)` — `() => Executable` 팩토리.
105
+ ## queryable / getMatchedPrimaryKeys (factory)
105
106
 
106
- ## Executable (프로시저 실행)
107
+ - `queryable(db, tableOrView, as?)` — Table/View 별 Queryable 팩토리 함수 생성. `DbContext` 내부에서 멤버 등록에 쓰임(보통 `this.queryable(...)` 로 호출). `as` 미지정 시 호출마다 새 alias.
108
+ - `getMatchedPrimaryKeys(fkCols, targetTable)` — FK 컬럼 배열과 대상 테이블 PK 를 매칭해 PK 컬럼명 배열 반환. 개수 불일치 시 throw. include/조인 조건 생성 내부에서 사용.
107
109
 
108
- ```typescript
109
- class Executable<TParams, TReturns> {
110
- getExecProcQueryDef(params?): ExecProcQueryDef;
111
- execute(params): Promise<InferColumnExprs<TReturns>[][]>;
112
- }
113
- ```
110
+ 타입 export: `QueryableRecord<TData>`(컬럼 프록시 타입), `QueryableWriteRecord<TData>`(쓰기용 `ExprInput` 레코드), `UnwrapQueryableRecord<R>`(select 결과 역추론), `PathProxy<T>`(include 경로 프록시).
114
111
 
115
- - `execute(params)` — 프로시저 실행. `params`=`returns`/`params` 정의에 맞는 `InferColumnExprs<TParams>`(리터럴 또는 ExprUnit). 결과는 **결과셋 배열의 배열**(다중 SELECT 가능) — 단일 결과셋이면 `result[0]`.
116
- - `getExecProcQueryDef(params?)` — 실행 없이 QueryDef 반환. params 가 있는데 프로시저에 파라미터 정의가 없으면 throw.
112
+ ## Executable<TParams, TReturns> / executable
113
+
114
+ 저장 프로시저 실행 래퍼. `db.getUserById()` 로 인스턴스를 얻어 실행한다.
115
+
116
+ - `execute(params)` — 프로시저 실행. `params`=`InferColumnExprs<TParams>`(`ExprInput` 또는 리터럴). 결과는 `TReturns[][]`(다중 결과셋). 정의에 파라미터가 없는데 전달하면 throw.
117
+ - `getExecProcQueryDef(params?)` — 실행 없이 `ExecProcQueryDef` 만 산출.
118
+ - `executable(db, builder)` — `Executable` 팩토리 생성(`DbContext` 멤버 등록용, 보통 `this.executable(...)`).
117
119
 
118
120
  ```typescript
119
121
  const [rows] = await db.getUserById().execute({ userId: 1n });
@@ -121,34 +123,20 @@ const [rows] = await db.getUserById().execute({ userId: 1n });
121
123
 
122
124
  ## parseSearchQuery / ParsedSearchQuery
123
125
 
124
- `search()` 가 내부로 쓰지만 직접 호출도 가능. 검색 구문 문자열을 SQL LIKE 패턴으로 분해.
126
+ `search()` 가 내부적으로 쓰는 검색 구문 파서. 검색 문자열을 SQL LIKE 패턴으로 변환한다. 커스텀 검색 UI 를 직접 만들 때 외부에서도 호출 가능.
125
127
 
126
128
  ```typescript
127
129
  function parseSearchQuery(searchText: string): ParsedSearchQuery;
128
130
  interface ParsedSearchQuery { or: string[]; must: string[]; not: string[]; }
129
131
  ```
130
132
 
131
- - `or: string[]` — 일반 검색어(공백 구분, OR). 와일드카드 없으면 `%term%`(부분 일치).
132
- - `must: string[]` — `+term` 또는 `"정확 구문"`(AND 필수).
133
- - `not: string[]``-term`(NOT 제외).
134
- - 와일드카드 `*` → `%`(`app*`→`app%` 시작 일치). 이스케이프: `\\` `\*` `\%` `\"` `\+` `\-` 는 리터럴. 닫히지 않은 `"` 는 일반 텍스트로 처리.
133
+ - `or`: string[] — 일반 검색어(OR 조건) LIKE 패턴. 공백으로 구분된 토큰.
134
+ - `must`: string[] — 필수 포함(AND 조건) LIKE 패턴. `+term` 또는 `"구문"`(따옴표 = 정확 일치 = 필수).
135
+ - `not`: string[] — 제외(NOT 조건) LIKE 패턴. `-term`.
135
136
 
136
- ```typescript
137
- parseSearchQuery('apple "delicious fruit" -banana +strawberry');
138
- // { or: ["%apple%"], must: ["%delicious fruit%", "%strawberry%"], not: ["%banana%"] }
139
- ```
140
-
141
- ## getMatchedPrimaryKeys
137
+ 구문 규칙: 와일드카드 없는 단어 `apple` → `%apple%`(부분 일치), `app*` → `app%`(시작 일치), `*apple` → `%apple`(끝 일치). 이스케이프 `\\` `\*` `\%` `\"` `\+` `\-` 는 리터럴 문자로 처리. 닫히지 않은 따옴표는 일반 텍스트로 취급.
142
138
 
143
139
  ```typescript
144
- function getMatchedPrimaryKeys(fkCols: string[], targetTable: TableBuilder): string[];
140
+ parseSearchQuery('apple "맛있는 과일" -banana +strawberry');
141
+ // { or: ["%apple%"], must: ["%맛있는 과일%", "%strawberry%"], not: ["%banana%"] }
145
142
  ```
146
-
147
- - FK column 배열을 대상 테이블 PK 와 매칭해 PK column 이름 배열 반환. 개수 불일치 시 throw. `include()` 가 관계 조건을 만들 때 내부 사용 — 직접 호출은 드묾.
148
-
149
- ## 결과/입력 변환 타입
150
-
151
- - `QueryableRecord<TData>` — 결과 데이터 → column 프록시 타입. 각 ColumnPrimitive 가 `ExprUnit<T>`, 중첩 관계는 재귀. where/select 등 콜백 인자 타입.
152
- - `QueryableWriteRecord<TData>` — 쓰기용. 각 필드가 `ExprInput<T>`. `update`/`upsert` 콜백 반환 타입.
153
- - `UnwrapQueryableRecord<R>` — `select` 결과를 다시 데이터 타입으로 역추론(ExprUnit→값).
154
- - `PathProxy<TObject>` — `include` 경로 지정용 프록시 타입(관계 필드만 접근).
@@ -1,160 +1,80 @@
1
1
  # @simplysm/orm-common — 스키마 정의 (Table / View / Procedure / column / index / relation)
2
2
 
3
- DB 객체(Table/View/Procedure)와 그 구성요소(column/index/관계)를 fluent 빌더로 선언하는 묶음. 모든 빌더는 immutable — 각 메서드가 새 인스턴스를 반환한다. 정의한 빌더는 `DbContext` 안에서 `this.queryable()`/`this.executable()` 로 등록한다. column 은 기본 `NOT NULL`; `.nullable()`/`.default(...)` 는 도메인 근거가 있을 때만 붙인다(orm.md).
3
+ DB 객체(Table/View/Procedure)와 그 구성요소(column/index/관계)를 fluent 빌더로 선언하는 묶음. 모든 빌더는 immutable — 각 메서드가 새 인스턴스를 반환한다. 정의한 빌더는 `DbContext` 안에서 `this.queryable()`/`this.executable()` 로 등록해 사용한다. column 은 기본 `NOT NULL` 이며 `.nullable()`/`.default(...)` 는 도메인 근거가 있을 때만 붙인다(orm.md 정책).
4
4
 
5
5
  ## Table / TableBuilder
6
6
 
7
7
  ```typescript
8
8
  function Table<TName extends string>(name: TName): TableBuilder<TName, {}, {}>;
9
-
10
- class TableBuilder<TName, TColumns, TRelations> {
11
- readonly meta: { name; description?; database?; schema?; columns?; primaryKey?; relations?; indexes? };
12
- readonly $inferSelect; // columns + 관계(optional)
13
- readonly $inferColumns; // column 값 타입만
14
- readonly $inferInsert; // INSERT 타입 (autoIncrement/nullable/default 는 optional)
15
- readonly $inferUpdate; // UPDATE 타입 (전부 optional)
16
-
17
- description(desc: string): TableBuilder;
18
- database(db: string): TableBuilder;
19
- schema(schema: string): TableBuilder;
20
- columns(fn: (c) => TNewColumns): TableBuilder;
21
- primaryKey(...columns: (keyof TColumns)[]): TableBuilder;
22
- indexes(fn: (i) => IndexBuilder[]): TableBuilder;
23
- relations(fn: (r) => TRelations): TableBuilder;
24
- }
25
9
  ```
26
10
 
27
- - `Table(name)` `TableBuilder` 생성. 이후 fluent 메서드로 채운다.
28
- - `description(desc)` — 테이블 설명. CREATE TABLE 시 DDL comment 로 들어감.
29
- - `database(db)` — 데이터베이스명 고정. 미지정 `DbContext` 기본 database.
30
- - `schema(schema)` — 스키마명(MSSQL/PostgreSQL). MySQL 무시.
31
- - `columns(fn)` — column factory `c` 를 받아 `{ name: c.타입() }` 레코드를 반환. 아래 column factory 참조. 호출 후 `$inferColumns` 등 타입이 갱신.
32
- - `primaryKey(...columns)` — PK column 이름(). 여러 넘기면 복합 PK.
33
- - `indexes(fn)` — index factory `i` 받아 `IndexBuilder[]` 반환.
34
- - `relations(fn)` relation factory `r` 받아 FK/역참조 관계 레코드 반환. Table 은 `foreignKey`/`foreignKeyTarget`/`relationKey`/`relationKeyTarget` 모두 사용 가능.
35
- - `$inferSelect` SELECT 결과 타입(컬럼 + include 가능한 관계는 optional). `queryable` 콜백 인자의 원천.
36
- - `$inferInsert` / `$inferUpdate` — `insert`/`update` 입력 타입. INSERT 는 autoIncrement·nullable·default column 이 optional, UPDATE 는 전부 optional.
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` 의 결과/입력 타입이 여기서 파생된다.
37
22
 
38
23
  ```typescript
39
24
  const User = Table("User")
40
25
  .database("mydb")
41
26
  .columns((c) => ({
42
- id: c.bigint().autoIncrement(),
27
+ id: c.int().autoIncrement(),
43
28
  name: c.varchar(100),
44
29
  email: c.varchar(200).nullable(),
30
+ isActive: c.boolean().default(true),
31
+ companyId: c.int().nullable(),
45
32
  }))
46
33
  .primaryKey("id")
47
- .indexes((i) => [i.index("email").unique()]);
48
- ```
49
-
50
- ## View / ViewBuilder
51
-
52
- ```typescript
53
- function View(name: string): ViewBuilder;
54
-
55
- class ViewBuilder<TDbContext, TData, TRelations> {
56
- readonly meta: { name; description?; database?; schema?; viewFn?; relations? };
57
- readonly $inferSelect; // TData
58
-
59
- description(desc): ViewBuilder;
60
- database(db): ViewBuilder;
61
- schema(schema): ViewBuilder;
62
- query(viewFn: (db) => Queryable<TViewData, any>): ViewBuilder;
63
- relations(fn: (r) => T): ViewBuilder;
64
- }
65
- ```
66
-
67
- - `View(name)` — 빈 `ViewBuilder` 생성.
68
- - `query(viewFn)` — 뷰 본문 SELECT 를 `db` 를 받는 함수로 정의. 반환 `Queryable` 의 select 결과가 뷰 데이터 타입이 됨. `queryable(this, View)` 등록 시 이 함수가 평가되어 column 이 구성된다.
69
- - `relations(fn)` — 뷰는 `relationKey`/`relationKeyTarget` 만 사용 가능(DB FK 미생성). `foreignKey` 는 타입상 불가.
70
- - `description`/`database`/`schema` — Table 과 동일 의미.
71
-
72
- ```typescript
73
- const ActiveUsers = View("ActiveUsers")
74
- .database("mydb")
75
- .query((db: MainDb) =>
76
- db.user().where((u) => [expr.eq(u.status, "active")]).select((u) => ({ id: u.id, name: u.name })),
77
- );
78
- ```
79
-
80
- ## Procedure / ProcedureBuilder
81
-
82
- ```typescript
83
- function Procedure(name: string): ProcedureBuilder<never, never>;
84
-
85
- class ProcedureBuilder<TParams, TReturns> {
86
- readonly meta: { name; description?; database?; schema?; params?; returns?; query? };
87
- readonly $params; readonly $returns;
88
-
89
- description(desc): ProcedureBuilder;
90
- database(db): ProcedureBuilder;
91
- schema(schema): ProcedureBuilder;
92
- params(fn: (c) => T): ProcedureBuilder;
93
- returns(fn: (c) => T): ProcedureBuilder;
94
- body(sql: string): ProcedureBuilder;
95
- }
96
- ```
97
-
98
- - `Procedure(name)` — 빈 빌더 생성.
99
- - `params(fn)` — 입력 파라미터를 column factory 로 정의. `Executable.execute(params)` 의 입력 타입이 됨.
100
- - `returns(fn)` — 결과 column 정의. `execute` 결과 행 타입이 됨.
101
- - `body(sql)` — 프로시저 본문 SQL. DBMS별 구문 차이 주의(MySQL: `userId`, MSSQL: `@userId`, PostgreSQL: `RETURN QUERY` 필요).
102
- - `description`/`database`/`schema` — Table 과 동일.
103
-
104
- ```typescript
105
- const GetUserById = Procedure("GetUserById")
106
- .database("mydb")
107
- .params((c) => ({ userId: c.bigint() }))
108
- .returns((c) => ({ id: c.bigint(), name: c.varchar(100) }))
109
- .body("SELECT id, name FROM User WHERE id = userId");
34
+ .indexes((i) => [i.index("email").unique()])
35
+ .relations((r) => ({
36
+ company: r.foreignKey(["companyId"], () => Company),
37
+ posts: r.foreignKeyTarget(() => Post, "user"),
38
+ }));
110
39
  ```
111
40
 
112
- ## column factory / ColumnBuilder
41
+ ## column 팩토리 / ColumnBuilder
113
42
 
114
- `columns`/`params`/`returns` 콜백 인자 `c` 가 column factory. 메서드는 `ColumnBuilder` 를 반환하고 `.autoIncrement()`/`.nullable()`/`.default()`/`.description()` 속성을 더한다(모두 immutable).
43
+ `columns((c) => ...)` `c` 가 노출하는 타입 생성 메서드. 각자 `ColumnBuilder` 를 반환하고, 위에 수식 메서드를 체이닝한다.
115
44
 
116
45
  타입 메서드:
117
46
 
118
- - `int()` — INT(4바이트 정수).
119
- - `bigint()` — BIGINT(8바이트 정수). autoIncrement PK 에 주로 사용.
120
- - `float()` — FLOAT(단정밀도 실수).
121
- - `double()` — DOUBLE(배정밀도 실수).
122
- - `decimal(precision, scale?)` — 고정 소수점. `precision`=전체 자릿수, `scale`=소수 자릿수(선택). 금액 정밀도 필요 시.
47
+ - `int()` — INT(4바이트 정수). 일반 정수 PK/카운트.
48
+ - `bigint()` — BIGINT(8바이트 정수). 범위 ID.
49
+ - `float()` — 단정밀도 부동소수점. 정밀도 덜 중요한 실수.
50
+ - `double()` — 배정밀도 부동소수점. 일반 실수 연산.
51
+ - `decimal(precision, scale?)` — 고정 소수점. `precision`=전체 자릿수, `scale`=소수 자릿수(선택). 금액처럼 반올림 오차가 곤란한 값.
123
52
  - `varchar(length)` — 가변 길이 문자열. `length`=최대 길이.
124
- - `char(length)` — 고정 길이 문자열.
125
- - `text()` — 대용량 텍스트.
126
- - `binary()` — 바이너리(MySQL LONGBLOB / MSSQL VARBINARY(MAX) / PostgreSQL BYTEA). 값 타입 `Bytes`.
53
+ - `char(length)` — 고정 길이 문자열. 코드값처럼 길이가 일정한 값.
54
+ - `text()` — 대용량 텍스트(본문 등).
55
+ - `binary()` — 바이너리(MySQL LONGBLOB / MSSQL VARBINARY(MAX) / PostgreSQL BYTEA). 값 타입은 `Bytes`.
127
56
  - `boolean()` — 불리언(MySQL TINYINT(1) / MSSQL BIT / PostgreSQL BOOLEAN).
128
57
  - `datetime()` — 날짜+시간. 값 타입 `DateTime`.
129
58
  - `date()` — 날짜만. 값 타입 `DateOnly`.
130
59
  - `time()` — 시간만. 값 타입 `Time`.
131
60
  - `uuid()` — UUID(MySQL BINARY(16) / MSSQL UNIQUEIDENTIFIER / PostgreSQL UUID). 값 타입 `Uuid`.
132
61
 
133
- `ColumnBuilder` 속성 메서드:
134
-
135
- - `autoIncrement()` — INSERT 시 자동 증가. INSERT 타입에서 optional 처리. PK 자동 증가 column 에.
136
- - `nullable()` — NULL 허용. 값 타입에 `undefined` 추가, INSERT optional. 도메인상 값이 없을 수 있을 때만.
137
- - `default(value)` — INSERT 시 미지정이면 사용할 기본값. INSERT optional 처리. 사용자가 명시 지시한 경우에만.
138
- - `description(desc)` — column 설명(DDL comment).
62
+ 수식 메서드(`ColumnBuilder`):
139
63
 
140
- ```typescript
141
- .columns((c) => ({
142
- id: c.bigint().autoIncrement(),
143
- price: c.decimal(10, 2),
144
- email: c.varchar(200).nullable(),
145
- status: c.varchar(20).default("active"),
146
- }))
147
- ```
64
+ - `autoIncrement()` — 자동 증가. INSERT 타입에서 optional 처리. 보통 정수 PK 에만.
65
+ - `nullable()` NULL 허용. 값 타입에 `undefined` 추가, INSERT 타입에서 optional. 도메인상 값이 없을 수 있을 때만.
66
+ - `default(value)` — INSERT 시 미지정이면 사용할 기본값. INSERT 타입에서 optional. 사용자가 명시적으로 지시한 경우에만.
67
+ - `description(desc)` — 컬럼 코멘트(DDL COMMENT).
148
68
 
149
- ## index factory / IndexBuilder
69
+ ## index 팩토리 / IndexBuilder
150
70
 
151
- `indexes` 콜백 인자 `i` 의 `index(...columns)` 로 시작해 fluent 로 옵션을 더한다.
71
+ `indexes((i) => [...])` 의 `i.index(...columns)` 로 시작. immutable 체이닝.
152
72
 
153
- - `i.index(...columns)` — index 대상 column 이름(들). 여러 개면 복합 index.
154
- - `.name(name)` — index 이름 지정. 미지정 시 자동 생성.
155
- - `.unique()` — 유니크 index.
156
- - `.orderBy(...orderBy)` — column별 정렬 방향 배열("ASC"|"DESC"). column 수와 길이 일치해야 함.
157
- - `.description(description)` — index 설명(DDL comment).
73
+ - `index(...columns)` — 인덱스 대상 컬럼(가변 인자, 복합 인덱스 가능). `IndexBuilder` 반환.
74
+ - `name(name)` — 인덱스 이름 지정. 미지정 시 자동 명명.
75
+ - `unique()` — 유니크 인덱스로 설정. 중복 방지 제약이 필요할 때.
76
+ - `orderBy(...orderBy)` — 컬럼별 정렬 방향(`"ASC" | "DESC"`). 인자 수가 컬럼 수와 일치해야 함. 범위/정렬 조회 최적화용.
77
+ - `description(desc)` — 인덱스 코멘트.
158
78
 
159
79
  ```typescript
160
80
  .indexes((i) => [
@@ -163,42 +83,85 @@ const GetUserById = Procedure("GetUserById")
163
83
  ])
164
84
  ```
165
85
 
166
- ## relation factory
86
+ ## relation 팩토리 / 관계 빌더
167
87
 
168
- `relations` 콜백 인자 `r`. Table 은 4종 모두, View `relationKey`/`relationKeyTarget` 만. 대상 빌더는 순환 참조 방지를 위해 모두 `() => Target` 지연 함수로 넘긴다. `description`/`single` 메서드 체이닝이 아니라 마지막 `opts` 인자로 전달(체이닝은 TS7022 유발로 제거됨).
88
+ `relations((r) => ({...}))` `r` 가 노출하는 관계 정의 메서드. Table 은 FK 계열 + RelationKey 계열 모두, View RelationKey 계열만 사용 가능. 관계는 `include()`(queryable.md)로 자동 조인되며, 정의만 한다고 DB 쿼리가 나가지는 않는다. `description`/`single` 옵션은 메서드 체이닝이 아니라 마지막 `opts` 인자로 전달한다(순환 참조로 인한 TS7022 회피 목적).
169
89
 
170
- - `r.foreignKey(columns, () => Target, opts?)` — N:1 FK 관계(DB FK 제약 **생성**). `columns`=현재 테이블 FK column 배열, 대상은 테이블의 PK 매칭. `opts.description?`. → `ForeignKeyBuilder`.
171
- - `r.foreignKeyTarget(() => Target, relationName, opts?)` — FK 역참조(1:N, DB FK 생성 측의 역방향). `relationName`=대상 테이블에서 이쪽을 가리키는 FK 관계 이름. `opts.single: true` 면 1:1 단일 객체, 아니면 배열. `opts.description?`. → `ForeignKeyTargetBuilder`.
172
- - `r.relationKey(columns, () => Target, opts?)` — N:1 논리 관계(DB FK **미생성**). FK 동일하나 제약 없음. View 에서도 사용. → `RelationKeyBuilder`.
173
- - `r.relationKeyTarget(() => Target, relationName, opts?)` — 1:N/1:1 논리 역참조(DB FK 미생성). `opts.single`/`opts.description` 동일. → `RelationKeyTargetBuilder`.
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` 동일.
174
94
 
175
- `opts`:
95
+ opts 공통 필드:
176
96
 
177
- - `description?: string` — 관계 설명.
178
- - `single?: boolean` (target 계열만) true=결과를 단일 객체(1:1), false/미지정=배열(1:N). `include()`/`$inferSelect` 의 해당 키 타입이 단일/배열로 갈린다.
97
+ - `description`: string — 관계 코멘트.
98
+ - `single`: true (target 계열만) 역참조를 배열이 아닌 단일 객체로 로드. 1:1 관계일 때.
179
99
 
180
100
  ```typescript
181
101
  const Post = Table("Post")
182
- .columns((c) => ({ id: c.bigint().autoIncrement(), authorId: c.bigint() }))
183
- .primaryKey("id")
184
- .relations((r) => ({ author: r.foreignKey(["authorId"], () => User, { description: "작성자" }) }));
185
-
186
- const User = Table("User")
187
- .columns((c) => ({ id: c.bigint().autoIncrement(), name: c.varchar(100) }))
102
+ .columns((c) => ({ id: c.int().autoIncrement(), userId: c.int(), title: c.varchar(300) }))
188
103
  .primaryKey("id")
189
104
  .relations((r) => ({
190
- posts: r.foreignKeyTarget(() => Post, "author"),
191
- profile: r.foreignKeyTarget(() => Profile, "user", { single: true }),
105
+ user: r.foreignKey(["userId"], () => User, { description: "작성자" }),
192
106
  }));
193
107
  ```
194
108
 
195
- 주의: FK column 개수와 대상 테이블 PK 개수가 다르면 `include()`/join "FK/PK column count mismatch" throw. PK 복합이면 FK column 같은 순서·개수로.
109
+ 빌더 클래스: `ForeignKeyBuilder`, `ForeignKeyTargetBuilder`, `RelationKeyBuilder`, `RelationKeyTargetBuilder` export 되며, `meta` 프로퍼티로 정의 내용을 노출한다(DDL 자동화·검증용). 보통 직접 `new` 하지 않고 팩토리로 생성한다.
110
+
111
+ ## View / ViewBuilder
112
+
113
+ ```typescript
114
+ function View(name: string): ViewBuilder<...>;
115
+ ```
116
+
117
+ 쿼리 결과를 가상 테이블로 정의한다. `query` 콜백 안에서 `DbContext` 를 받아 `Queryable` 을 반환하면 그것이 뷰 본문이 된다.
118
+
119
+ - `database(db)` / `schema(schema)` / `description(desc)` — Table 과 동일.
120
+ - `query((db) => db.x().select(...))` — 뷰 본문 SELECT 정의. `db` 는 `DbContext`, 반환은 `Queryable`. select 결과 컬럼이 뷰 컬럼이 됨.
121
+ - `relations((r) => ({...}))` — 논리 관계(RelationKey 계열)만 정의 가능.
122
+
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
+ ```
130
+
131
+ ## Procedure / ProcedureBuilder
132
+
133
+ ```typescript
134
+ function Procedure(name: string): ProcedureBuilder<never, never>;
135
+ ```
136
+
137
+ 저장 프로시저를 정의한다. `executable()` 로 등록 후 `Executable.execute(params)`(queryable.md)로 호출.
138
+
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`).
143
+
144
+ 타입 추론 필드: `$params`, `$returns`(`Executable` 의 입력/출력 타입 파생).
145
+
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
+ ```
153
+
154
+ ## 타입 추론 유틸 / 기타 export
155
+
156
+ column-builder 가 함께 export 하는 타입(주로 빌더 내부·executor·고급 타입 작업용):
196
157
 
197
- ## 빌더/추론 타입 (직접 참조 드묾)
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 로 추론).
198
166
 
199
- - `ColumnBuilderRecord` `Record<string, ColumnBuilder<...>>`. `columns`/`params`/`returns` 반환 타입.
200
- - `RelationBuilderRecord` — 4종 relation 빌더의 union 레코드. `relations` 반환 타입.
201
- - `InferColumns<T>` / `InferInsertColumns<T>` / `InferUpdateColumns<T>` / `InferColumnExprs<T>` — column 레코드에서 각각 값 타입 / INSERT 입력 / UPDATE 입력 / `ExprInput` 입력 타입을 추론. `$inferColumns` 등 내부 사용.
202
- - `RequiredInsertKeys<T>` / `OptionalInsertKeys<T>` — INSERT 시 필수/선택 column key 분리(autoIncrement·nullable·default 가 선택).
203
- - `DataToColumnBuilderRecord<TData>` — 데이터 레코드 → column 빌더 레코드 역변환. `insertInto` 의 대상 테이블 제약에 사용.
204
- - `InferDeepRelations<T>` / `ExtractRelationTarget<T>` / `ExtractRelationTargetResult<T>` — 관계 정의에서 심층 결과 타입을 optional 로 추론(같은 테이블 재방문 시 순환 차단). `$inferSelect` 의 관계 부분.
167
+ `Table`/`View`/`Procedure` 빌더와 `_Migration`(시스템 마이그레이션 테이블 정의)도 함께 노출된다.