@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,142 +1,186 @@
1
- # @simplysm/orm-common — Queryable (쿼리 작성·실행 · 프로시저 · 검색)
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` 가 처리한다. 표준 조회 흐름·안티패턴은 orm.md/orm-union.md 참조.
3
+ `db.X()` 반환하는 `Queryable` 체이닝으로 SELECT/INSERT/UPDATE/DELETE/UPSERT 쿼리를 구성·실행하는 군. 모든 옵션 메서드는 새 `Queryable` 반환하는 불변 체이닝이며, 콜백은 컬럼 프록시(`QueryableRecord`)받아 `expr` 표현식을 만든다. 종단 메서드(`execute`/`single`/`count` 등)에서 QueryDef 빌드해 `db.executeDefs` 로 실행한다. 프로시저 실행은 `Executable`, 텍스트 검색 파싱은 `parseSearchQuery`.
4
4
 
5
- ## Queryable<TData, TFrom>
5
+ `Queryable<TData, TFrom>` — `TData` 는 결과 행 타입, `TFrom` 은 소스 TableBuilder(CUD 연산에 필요, 커스텀 컬럼/조인 결과는 `never`).
6
6
 
7
- `TData` = 결과 데이터 타입, `TFrom` = 소스 `TableBuilder`(CUD 가능 여부). `select`/`join`/`groupBy`/`distinct`/`wrap`/`union` 등을 거치면 `TFrom` 이 `never` 가 되어 CUD 가 막힌다(파생 컬럼 위에서는 INSERT/UPDATE 불가).
7
+ ## SELECT 옵션 (체이닝)
8
8
 
9
- ### 옵션select / distinct / lock
10
-
11
- - `select(fn)` — projection. `fn` 원본 컬럼 프록시를 받아 새 컬럼 객체 반환. 반환 객체 안에서 `expr.*` 도출(coalesce/CASE/산식) 번에 작성. 결과 `TData` 가 새 shape 로 바뀜.
12
- - `distinct()` — `DISTINCT` 적용. 이후 `count()` 하려면 `wrap()` 먼저(아니면 throw).
13
- - `lock()` — `FOR UPDATE` 잠금. 트랜잭션 내에서 선택 행 배타 잠금이 필요할 때.
9
+ - `select(fn)`SELECT 컬럼 매핑. `fn` 은 컬럼 프록시를 받아 새 구조(`{ alias: 컬럼/표현식 }`)를 반환. 결과 타입이 매핑 형태로 바뀌고 `TFrom` 은 `never`(이후 CUD 불가). 리터럴 상수도 자동으로 `ExprUnit` 으로 래핑됨.
10
+ - `distinct()` — 중복 행 제거(DISTINCT). 이후 `count()` 는 `wrap()` 필요.
11
+ - `lock()` — 선택 행에 배타적 잠금(FOR UPDATE). 트랜잭션 내에서만 의미.
12
+ - `top(count)` — 상위 N행. ORDER BY 없이도 가능.
13
+ - `limit(skip, take)` — OFFSET/LIMIT 페이지네이션. **먼저 `orderBy()` 있어야 함**(없으면 throw).
14
+ - `orderBy(fnOrKey, dir?)` — 정렬 추가(여러 번 누적). `fnOrKey` 는 컬럼 반환 함수 또는 체인 경로 문자열(`"id"`, `"user.name"` — `obj.getChainValue` 로 해석). `dir` 기본 `ASC`.
14
15
 
15
16
  ```typescript
16
- db.user().select((u) => ({ userName: u.name, userEmail: u.email }))
17
+ const users = await db.user()
18
+ .select((u) => ({ userName: u.name, userEmail: u.email }))
19
+ .orderBy((u) => u.createdAt, "DESC")
20
+ .limit(0, 20)
21
+ .execute();
17
22
  ```
18
23
 
19
- ### 제한 — top / limit
24
+ ## WHERE / 검색
20
25
 
21
- - `top(count)` — 상위 `count` 행만. ORDER BY 없이도 사용 가능.
22
- - `limit(skip, take)` — 페이지네이션. `skip`=OFFSET, `take`=LIMIT. **호출 `orderBy()` 필수**(없으면 throw).
26
+ - `where(predicate)` — 조건 배열을 반환하는 콜백. 여러 호출 시 AND 누적. 배열 안 여러 조건도 AND.
27
+ - `search(fn, searchText)` — `fn` 이 반환한 문자열 컬럼들에 텍스트 검색을 적용. 구문은 `parseSearchQuery` 규칙(공백=OR, `+`=필수, `-`=제외, `"..."`=구문, `*`=와일드카드). 검색어면 그대로 반환. 내부적으로 `LOWER(col) LIKE pattern` 조합.
23
28
 
24
29
  ```typescript
25
- db.user().orderBy((u) => u.createdAt, "DESC").limit(page * 50, 50)
30
+ db.user()
31
+ .where((u) => [expr.eq(u.isActive, true)])
32
+ .search((u) => [u.name, u.email], "John Doe -withdrawn");
26
33
  ```
27
34
 
28
- ### 정렬 orderBy
35
+ ## GROUP BY / HAVING
29
36
 
30
- - `orderBy(fnOrKey, orderBy?)` — 정렬 조건 추가(여러 호출 시 순서대로). `fnOrKey`=컬럼 반환 함수 또는 체인 경로 문자열(`"user.name"`, 동적 정렬용). `orderBy`=`"ASC" | "DESC"`(기본 ASC).
31
-
32
- ### 필터 — where / search
33
-
34
- - `where(predicate)` — WHERE 조건 추가(여러 번 호출 시 AND 결합). `predicate` 는 컬럼 프록시를 받아 `WhereExprUnit[]` 반환. select 로 만든 파생 컬럼 이름도 직접 참조 가능(framework 가 AST inline).
35
- - `search(fn, searchText)` — 텍스트 검색. `fn`=검색 대상 문자열 컬럼 배열 반환, `searchText`=검색 구문(`parseSearchQuery` 규칙: 공백=OR, `+`=필수, `-`=제외, `"..."`=구문, `*`=와일드카드). 빈 문자열이면 조건 미추가. 내부적으로 `LOWER(col) LIKE pattern` 조합으로 변환.
37
+ - `groupBy(fn)` — 그룹화 컬럼 배열 반환. 반환 `TFrom` `never`.
38
+ - `having(predicate)` — GROUP BY 이후 필터(여러 번 누적, AND). 반환 `TFrom` 은 `never`.
36
39
 
37
40
  ```typescript
38
- db.user()
39
- .where((u) => [expr.eq(u.isActive, true)])
40
- .search((u) => [u.name, u.email], "John -withdrawn")
41
+ db.order()
42
+ .select((o) => ({ userId: o.userId, total: expr.sum(o.amount) }))
43
+ .groupBy((o) => [o.userId])
44
+ .having((o) => [expr.gte(o.total, 10000)]);
41
45
  ```
42
46
 
43
- ### 그룹 — groupBy / having
44
-
45
- - `groupBy(fn)` — GROUP BY. `fn` 은 그룹 컬럼 배열 반환. 이후 `count()` 하려면 `wrap()` 먼저(아니면 throw).
46
- - `having(predicate)` — 그룹 필터(여러 번 호출 시 AND). 집계 컬럼 기준 필터링.
47
+ ## JOIN
47
48
 
48
- ### 조인join / joinSingle / include
49
+ - `join(as, fn)` 1:N LEFT JOIN. 결과에 `as` 프로퍼티가 **배열**로 추가됨. `fn(qr, cols)` 의 `qr.from(Table)` 로 조인 대상을 잡고 `where` 로 조건을 건다.
50
+ - `joinSingle(as, fn)` — N:1/1:1 LEFT JOIN. 결과에 `as` 가 **단일 객체(optional)** 로 추가됨. 집계·도출 컬럼을 outer 행에 부착하는 표준 수단(orm.md: SELECT 절 subquery/exists 금지).
51
+ - `include(fn)` — TableBuilder 의 FK/FKT 관계를 자동 JOIN. `fn` 은 타입 안전 path proxy 를 받아 관계 경로를 지정(`(p) => p.user.company` → 다단계). 비-컬럼(관계) 필드만 접근 가능. 관계 미정의 시 throw, TableBuilder 기반이 아니면 throw. 같은 경로 중복 호출은 무시.
49
52
 
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.
53
+ `join`/`joinSingle` 의 `fn` 인자 `qr`(`JoinQueryable`)는 `from(Table)` / `select(columns)` / `union(...queries)` 제공한다.
53
54
 
54
55
  ```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)
56
+ db.post().include((p) => p.user.company);
57
+
58
+ db.product()
59
+ .joinSingle("state", (q, p) =>
60
+ q.from(StockLine).where((x) => [expr.eq(x.productId, p.id)])
61
+ .select((x) => ({ sumQty: expr.sum(x.qty), cnt: expr.count() })),
62
+ )
63
+ .select((p) => ({ id: p.id, totalQty: expr.coalesce(p.state!.sumQty, 0) }));
58
64
  ```
59
65
 
60
- ### 서브쿼리 wrap / union / recursive
66
+ ## 서브쿼리 / UNION / 재귀 CTE
61
67
 
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` 상위 결과를 자기참조.
68
+ - `wrap()` — 현재 Queryable 을 서브쿼리(파생 테이블)로 감쌈. `distinct()`/`groupBy()` 이후 `count()` 호출 전에 필요.
69
+ - `Queryable.union(...queries)` (static) — 2개 이상 Queryable 을 UNION(중복 제거). **2개 미만이면 `ArgumentError`**. 쿼리의 컬럼 구조를 기준으로 alias 변환. 결과는 파생 테이블이라 이후 fluent 연산은 외부에 적용됨(예시 스타일은 orm-union.md).
70
+ - `recursive(fn)` — WITH RECURSIVE CTE. `fn(cte)` 의 `cte.from(Table)`/`cte.select(...)`/`cte.union(...)` 재귀 본문 정의. 재귀 대상에 `self` 프로퍼티(베이스 참조)가 추가됨. 계층(조직도·트리) 조회용.
65
71
 
66
72
  ```typescript
67
- const items = await Queryable.union(inQr, outQr).orderBy((r) => r.date, "DESC").limit(0, 50).execute();
73
+ const combined = Queryable.union(
74
+ db.user().where((u) => [expr.eq(u.type, "admin")]),
75
+ db.user().where((u) => [expr.eq(u.type, "manager")]),
76
+ );
77
+
78
+ db.employee()
79
+ .where((e) => [expr.null(e.managerId)])
80
+ .recursive((cte) => cte.from(Employee).where((e) => [expr.eq(e.managerId, e.self[0].id)]));
68
81
  ```
69
82
 
70
- ### 종단 — SELECT 실행
71
-
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`) 산출. 서브쿼리 합성·저수준 실행용.
83
+ ## 실행 — SELECT 종단
78
84
 
79
- ### 종단INSERT
80
-
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 와 맞아야 함.
85
+ - `execute(): Promise<TData[]>` SELECT 실행, 결과 배열 반환.
86
+ - `single(): Promise<TData | undefined>` — 단일 결과. 2건 이상이면 `ArgumentError`.
87
+ - `first(): Promise<TData | undefined>` 결과만(`top(1)`).
88
+ - `count(fn?): Promise<number>` 수. `fn` 으로 특정 컬럼 카운트. `distinct()`/`groupBy()` 직후 호출 throw(먼저 `wrap()`). 결과 없으면 0.
89
+ - `exists(): Promise<boolean>` 조건에 맞는 존재 여부(`top(1)` 길이 검사).
90
+ - `getSelectQueryDef(): SelectQueryDef` — 빌드된 SELECT AST. `getResultMeta(outputColumns?)` — 결과 파싱용 `ResultMeta`.
84
91
 
85
92
  ```typescript
86
- const [created] = await db.user().insert([{ name: "홍길동" }], ["id"]);
93
+ const total = await db.user().where((u) => [expr.eq(u.isActive, true)]).count();
94
+ const user = await db.user().where((u) => [expr.eq(u.id, 1)]).single();
87
95
  ```
88
96
 
89
- ### 종단UPDATE / DELETE / UPSERT
97
+ ## 실행INSERT (TableBuilder 기반만)
90
98
 
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 얻을 수 있음.
99
+ - `insert(records)` / `insert(records, outputColumns)` 레코드 배열 삽입. MSSQL 1000행 제한 때문에 1000개 단위 청크로 분할. `outputColumns` 지정 시 삽입된 레코드 배열 반환(`Pick<columns, K>[]`). 빈 배열이면 즉시 반환. AI 컬럼에 명시값이 있으면 자동 `overrideIdentity`.
100
+ - `insertIfNotExists(record)` / `(record, outputColumns)` — WHERE 조건에 맞는 데이터가 없을 때만 단건 삽입. `outputColumns` 지정 시 단건 반환.
101
+ - `insertInto(targetTable)` / `(targetTable, outputColumns)` — 현재 SELECT 결과를 다른 테이블에 INSERT(INSERT INTO ... SELECT). 대상 테이블 컬럼이 현재 데이터와 호환되어야 함(타입 제약).
102
+ - `getInsertQueryDef` / `getInsertIfNotExistsQueryDef` / `getInsertIntoQueryDef` AST 생성기.
95
103
 
96
104
  ```typescript
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" }));
105
+ const [inserted] = await db.user().insert([{ name: "Gildong Hong" }], ["id"]);
106
+
107
+ await db.user()
108
+ .where((u) => [expr.eq(u.email, "t@t.com")])
109
+ .insertIfNotExists({ name: "t", email: "t@t.com" });
99
110
  ```
100
111
 
101
- ### DDL 헬퍼
112
+ ## 실행 — UPDATE / DELETE / UPSERT (TableBuilder 기반만)
102
113
 
103
- - `switchFk(enabled)` — 테이블의 FK 제약 활성/비활성 토글. `enabled` true=활성, false=비활성. 대량 적재 FK 일시 해제용. Table/View 기반에서만.
114
+ - `update(recordFwd)` / `(recordFwd, outputColumns)` `recordFwd(cols)` 갱신 컬럼/값을 반환. 값은 `ExprInput`(리터럴 직접 가능). `outputColumns` 지정 갱신된 레코드 배열 반환. WHERE/JOIN/limit 가 함께 반영됨.
115
+ - `delete()` / `delete(outputColumns)` — DELETE. `outputColumns` 지정 시 삭제된 레코드 배열 반환.
116
+ - `upsert(updateFn)` / `upsert(updateFn, insertFn?, outputColumns?)` — WHERE 매칭 시 UPDATE, 아니면 INSERT(MERGE). `insertFn(updateRecord)` 미지정 시 update 와 동일 데이터로 삽입. `insertFn` 은 update 레코드를 인자로 받아 변형 가능.
117
+ - `getUpdateQueryDef` / `getDeleteQueryDef` / `getUpsertQueryDef` — AST 생성기.
118
+ - `switchFk(enabled)` — 이 Queryable 소스 테이블의 FK 제약 활성/비활성(트랜잭션 내 가능).
104
119
 
105
- ## queryable / getMatchedPrimaryKeys (factory)
120
+ ```typescript
121
+ await db.user().where((u) => [expr.eq(u.id, 1)]).update((u) => ({ name: "새이름" }));
122
+
123
+ await db.user().where((u) => [expr.eq(u.isExpired, true)]).delete(["id", "name"]);
106
124
 
107
- - `queryable(db, tableOrView, as?)` — Table/View 별 Queryable 팩토리 함수 생성. `DbContext` 내부에서 멤버 등록에 쓰임(보통 `this.queryable(...)` 로 호출). `as` 미지정 시 호출마다 새 alias.
108
- - `getMatchedPrimaryKeys(fkCols, targetTable)` FK 컬럼 배열과 대상 테이블 PK 를 매칭해 PK 컬럼명 배열 반환. 개수 불일치 시 throw. include/조인 조건 생성 내부에서 사용.
125
+ await db.user()
126
+ .where((u) => [expr.eq(u.email, "t@t.com")])
127
+ .upsert(() => ({ loginCount: 1 }), (update) => ({ ...update, email: "t@t.com" }));
128
+ ```
109
129
 
110
- 타입 export: `QueryableRecord<TData>`(컬럼 프록시 타입), `QueryableWriteRecord<TData>`(쓰기용 `ExprInput` 레코드), `UnwrapQueryableRecord<R>`(select 결과 역추론), `PathProxy<T>`(include 경로 프록시).
130
+ ## Executable (프로시저 실행)
111
131
 
112
- ## Executable<TParams, TReturns> / executable
132
+ `DbContext.executable(Procedure)` 가 반환하는 팩토리(`db.getUserById()`)가 `Executable` 만든다.
113
133
 
114
- 저장 프로시저 실행 래퍼. `db.getUserById()` 로 인스턴스를 얻어 실행한다.
134
+ ```typescript
135
+ class Executable<TParams, TReturns> {
136
+ execute(params: InferColumnExprs<TParams>): Promise<InferColumnExprs<TReturns>[][]>;
137
+ getExecProcQueryDef(params?): ExecProcQueryDef;
138
+ }
139
+ function executable(db, builder): () => Executable;
140
+ ```
115
141
 
116
- - `execute(params)` — 프로시저 실행. `params`=`InferColumnExprs<TParams>`(`ExprInput` 또는 리터럴). 결과는 `TReturns[][]`(다중 결과셋). 정의에 파라미터가 없는데 전달하면 throw.
117
- - `getExecProcQueryDef(params?)` — 실행 없이 `ExecProcQueryDef` 만 산출.
118
- - `executable(db, builder)` — `Executable` 팩토리 생성(`DbContext` 멤버 등록용, 보통 `this.executable(...)`).
142
+ - `execute(params)` — 프로시저 실행. 파라미터는 `ExprInput`(리터럴 또는 `ExprUnit`). 결과는 다중 결과셋(`행[][]`). 파라미터 없는 프로시저에 params 를 주면 throw.
119
143
 
120
144
  ```typescript
121
145
  const [rows] = await db.getUserById().execute({ userId: 1n });
122
146
  ```
123
147
 
124
- ## parseSearchQuery / ParsedSearchQuery
125
-
126
- `search()` 가 내부적으로 쓰는 검색 구문 파서. 검색 문자열을 SQL LIKE 패턴으로 변환한다. 커스텀 검색 UI 를 직접 만들 때 외부에서도 호출 가능.
148
+ ## 검색 파서 — parseSearchQuery
127
149
 
128
150
  ```typescript
129
151
  function parseSearchQuery(searchText: string): ParsedSearchQuery;
130
- interface ParsedSearchQuery { or: string[]; must: string[]; not: string[]; }
152
+ interface ParsedSearchQuery { or: string[]; must: string[]; not: string[]; } // 각각 LIKE 패턴
131
153
  ```
132
154
 
133
- - `or`: string[] 일반 검색어(OR 조건) LIKE 패턴. 공백으로 구분된 토큰.
134
- - `must`: string[] — 필수 포함(AND 조건) LIKE 패턴. `+term` 또는 `"구문"`(따옴표 = 정확 일치 = 필수).
135
- - `not`: string[] — 제외(NOT 조건) LIKE 패턴. `-term`.
155
+ 검색 문자열을 SQL LIKE 패턴으로 파싱. `Queryable.search()` 내부에서 사용하지만 직접도 가능.
136
156
 
137
- 구문 규칙: 와일드카드 없는 단어 `apple` → `%apple%`(부분 일치), `app*` → `app%`(시작 일치), `*apple` → `%apple`(끝 일치). 이스케이프 `\\` `\*` `\%` `\"` `\+` `\-` 는 리터럴 문자로 처리. 닫히지 않은 따옴표는 일반 텍스트로 취급.
157
+ | 구문 | 의미 |
158
+ | ---- | ---- |
159
+ | `term1 term2` | OR (하나 이상 일치) → `or` |
160
+ | `+term` | 필수 포함(AND) → `must` |
161
+ | `-term` | 제외(NOT) → `not` |
162
+ | `"exact phrase"` | 정확한 구문(필수) → `must` |
163
+ | `term*` / `*term` / `a*ple` | 와일드카드 `%` 로 변환(접두/접미/중간 일치) |
164
+ | 와일드카드 없는 `term` | `%term%` (부분 문자열) |
165
+
166
+ 이스케이프: `\\` `\*` `\%` `\"` `\+` `\-` 는 각 리터럴 문자. 닫히지 않은 따옴표는 따옴표 포함 일반 텍스트로 처리.
138
167
 
139
168
  ```typescript
140
- parseSearchQuery('apple "맛있는 과일" -banana +strawberry');
141
- // { or: ["%apple%"], must: ["%맛있는 과일%", "%strawberry%"], not: ["%banana%"] }
169
+ parseSearchQuery('apple "delicious fruit" -banana +strawberry');
170
+ // { or: ["%apple%"], must: ["%delicious fruit%", "%strawberry%"], not: ["%banana%"] }
142
171
  ```
172
+
173
+ ## 관련 타입 / 헬퍼 export
174
+
175
+ - `QueryableRecord<TData>` — 컬럼이 `ExprUnit` 으로 래핑된 프록시 타입(콜백 인자).
176
+ - `QueryableWriteRecord<TData>` — 쓰기(update/upsert) 값 레코드(`ExprInput`).
177
+ - `UnwrapQueryableRecord<R>` — `select` 결과를 다시 데이터 타입으로 역변환.
178
+ - `PathProxy<TObject>` — `include()` 의 타입 안전 경로 프록시(관계 필드만 접근).
179
+ - `queryable(db, tableOrView, as?)` — Table/View 용 Queryable 팩토리 함수(`DbContext.queryable` 의 기반).
180
+ - `getMatchedPrimaryKeys(fkCols, targetTable)` — FK 컬럼 배열과 대상 PK 매칭(개수 불일치 시 throw, include 내부용).
181
+
182
+ ## 주의사항
183
+
184
+ - CUD(insert/update/delete/upsert)·`insertInto` 는 `TFrom` 이 살아있는(=`select`/`groupBy`/join 결과가 아닌) TableBuilder 기반 Queryable 에서만. 아니면 throw.
185
+ - 도출 컬럼 위 필터·정렬은 `wrap()` 없이 `.select(...).where(...)` 로 — framework 가 projected AST 를 inline(orm.md). `wrap()` 은 `distinct`/`groupBy` 후 `count` 처럼 명시 요구 시에만.
186
+ - where 비교·CUD 값은 리터럴 그대로 — `expr.val` 로 감싸지 말 것(orm.md). `expr.val` 은 `select` 에서 상수 컬럼 만들 때처럼 `ExprUnit` 이 요구되는 자리에서만.