@simplysm/sd-claude 14.0.89 → 14.0.90

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 (79) hide show
  1. package/claude/references/sd-simplysm14/README.md +16 -17
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +52 -30
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +200 -38
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +41 -53
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +66 -22
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +127 -40
  7. package/claude/references/sd-simplysm14/apis/angular/infra.md +60 -43
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +56 -20
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +74 -74
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +50 -40
  11. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +55 -15
  12. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +59 -42
  13. package/claude/references/sd-simplysm14/apis/angular/sheet.md +77 -62
  14. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +8 -7
  15. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +71 -43
  16. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +22 -14
  17. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +19 -19
  18. package/claude/references/sd-simplysm14/apis/core-browser/README.md +17 -17
  19. package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +28 -28
  20. package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +37 -37
  21. package/claude/references/sd-simplysm14/apis/core-common/README.md +87 -219
  22. package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +54 -98
  23. package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +57 -99
  24. package/claude/references/sd-simplysm14/apis/core-common/datetime.md +60 -103
  25. package/claude/references/sd-simplysm14/apis/core-common/errors.md +42 -47
  26. package/claude/references/sd-simplysm14/apis/core-common/obj.md +42 -88
  27. package/claude/references/sd-simplysm14/apis/core-common/serialization.md +55 -0
  28. package/claude/references/sd-simplysm14/apis/core-node/README.md +6 -7
  29. package/claude/references/sd-simplysm14/apis/core-node/consola.md +17 -12
  30. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +14 -13
  31. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +9 -8
  32. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +14 -13
  33. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +4 -8
  34. package/claude/references/sd-simplysm14/apis/core-node/worker.md +14 -12
  35. package/claude/references/sd-simplysm14/apis/excel/README.md +22 -22
  36. package/claude/references/sd-simplysm14/apis/excel/cell.md +37 -29
  37. package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +29 -15
  38. package/claude/references/sd-simplysm14/apis/excel/style.md +33 -27
  39. package/claude/references/sd-simplysm14/apis/excel/utils.md +29 -19
  40. package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +78 -55
  41. package/claude/references/sd-simplysm14/apis/excel/wrapper.md +42 -45
  42. package/claude/references/sd-simplysm14/apis/orm-common/README.md +6 -8
  43. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +118 -67
  44. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +83 -86
  45. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +102 -93
  46. package/claude/references/sd-simplysm14/apis/orm-common/schema.md +138 -81
  47. package/claude/references/sd-simplysm14/apis/orm-common/types.md +49 -44
  48. package/claude/references/sd-simplysm14/apis/orm-node/README.md +42 -42
  49. package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +44 -33
  50. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +11 -10
  51. package/claude/references/sd-simplysm14/apis/service-client/README.md +56 -52
  52. package/claude/references/sd-simplysm14/apis/service-client/orm.md +33 -28
  53. package/claude/references/sd-simplysm14/apis/service-client/transport.md +23 -21
  54. package/claude/references/sd-simplysm14/apis/service-common/README.md +83 -48
  55. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +126 -34
  56. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +109 -54
  57. package/claude/references/sd-simplysm14/apis/service-server/README.md +69 -81
  58. package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +46 -43
  59. package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +63 -37
  60. package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +40 -30
  61. package/claude/references/sd-simplysm14/apis/storage/README.md +17 -17
  62. package/claude/references/sd-simplysm14/manuals/client-app-structure.md +142 -140
  63. package/claude/references/sd-simplysm14/manuals/client-orm.md +1 -1
  64. package/claude/references/sd-simplysm14/manuals/client-service.md +19 -7
  65. package/claude/references/sd-simplysm14/manuals/client-shared-data.md +2 -2
  66. package/claude/references/sd-simplysm14/manuals/client-system-log.md +11 -3
  67. package/claude/references/sd-simplysm14/manuals/data-log.md +0 -1
  68. package/claude/references/sd-simplysm14/manuals/orm.md +16 -0
  69. package/claude/rules/sd-design-rules.md +10 -0
  70. package/claude/skills/sd-demo/SKILL.md +0 -6
  71. package/claude/skills/sd-docs/SKILL.md +58 -0
  72. package/claude/{workflows/sd-docs.rules.md → skills/sd-docs/references/subagent-prompt.md} +103 -103
  73. package/claude/skills/sd-impl/SKILL.md +7 -4
  74. package/claude/skills/sd-spec/SKILL.md +842 -15
  75. package/claude/skills/sd-spec/references/example-spec.md +26 -36
  76. package/package.json +1 -1
  77. package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +0 -53
  78. package/claude/skills/sd-spec/references/spec-authoring.md +0 -519
  79. package/claude/workflows/sd-docs.js +0 -84
@@ -1,128 +1,125 @@
1
1
  # @simplysm/orm-common — expr (SQL 표현식 빌더)
2
2
 
3
- `expr` 객체로 dialect 독립 SQL 표현식을 JSON AST(`Expr`)로 조립한다. where/having 콜백은 `WhereExprUnit[]` 를, select/orderBy/groupBy/update 콜백은 `ExprUnit<T>` 또는 리터럴을 반환한다. 모든 인자는 `ExprInput<T>`(= `ExprUnit<T> | T`) — 컬럼·중첩 식·리터럴을 섞어 넘길 수 있다.
3
+ `expr` 객체로 dialect 독립 SQL 표현식을 JSON AST(`Expr`)로 조립한다. dialect별 QueryBuilder 가 MySQL/MSSQL/PostgreSQL 로 변환. where/having 콜백은 `WhereExprUnit[]`, select/orderBy/groupBy 콜백은 `ExprUnit<T>`, update/upsert/where 비교값은 `ExprInput<T>`(= `ExprUnit<T> | T`) 다룬다.
4
4
 
5
- 핵심 래퍼:
6
-
7
- - `class ExprUnit<TPrimitive>` — 타입 추적 표현식 래퍼. `dataType: ColumnPrimitiveStr`, `expr: Expr`(AST), `get n` (= 반환 타입에서 `undefined` 제거한 non-null 버전, nullable 컬럼을 비-nullable 로 단언할 때).
8
- - `class WhereExprUnit` — WHERE 조건 래퍼(`expr: WhereExpr`). 비교/논리 연산이 반환.
9
- - `type ExprInput<T> = ExprUnit<T> | T` — 표현식 또는 리터럴 입력.
10
- - `interface SwitchExprBuilder<T>` — `switch()` 가 반환하는 CASE 빌더(`.case(cond, then)` 체이닝 + `.default(value)` 종료).
11
- - `expr.toExpr(value): Expr` — `ExprInput` 을 `Expr` AST 로 변환(내부용, 직접 AST 다룰 때).
12
-
13
- ## 값 생성 (val / col / raw)
14
-
15
- - `val(dataType, value): ExprUnit` — 리터럴을 ExprUnit 으로. `dataType`=`"string"|"number"|"boolean"|"DateTime"|"DateOnly"|"Time"|"Uuid"|"Bytes"`. `value`=값(undefined 허용 시 nullable 반환). update/insert 값에 타입을 명시할 때.
16
- - `col(dataType, ...path): ExprUnit` — 컬럼 참조 직접 생성(보통 콜백 프록시가 대신). `path`=alias·컬럼명 분절.
17
- - `raw(dataType)\`SQL ${val}\`: ExprUnit` — 이스케이프 해치. ORM 미지원 DB 함수를 태그드 템플릿으로. 보간 값은 자동 파라미터화. `dataType`=반환 타입.
5
+ **리터럴 래핑 규칙(orm.md)**: where 비교·update/upsert/insert 값은 `ExprInput` 자리라 **리터럴을 그대로** 넘긴다 — `expr.val` 로 감싸지 말 것. `expr.val` 은 `select` 콜백에서 상수 column 을 만들 때처럼 `ExprUnit` 이 요구되는 자리에서만.
18
6
 
19
7
  ```typescript
20
- expr.val("string", "active")
21
- expr.raw("string")`JSON_EXTRACT(${u.metadata}, '$.email')`
8
+ // 좋음 // 나쁨 (불필요한 래핑)
9
+ .where((u) => [expr.eq(u.status, "active")]) // expr.eq(u.status, expr.val("string","active"))
10
+ .update((u) => ({ name: "새이름" })) // ({ name: expr.val("string","새이름") })
22
11
  ```
23
12
 
24
- ## WHERE비교 (eq / gt / lt / gte / lte / between)
13
+ 연산 함수 인자는 대부분 `ExprInput<T>` column 프록시(`ExprUnit`)·중첩 식·리터럴을 섞어 넘길 있다. `undefined` 컬럼 타입은 `.n` getter 로 non-null 단언 가능.
25
14
 
26
- 전부 `(source: ExprUnit<T>, target: ExprInput<T>) => WhereExprUnit`(between 제외).
15
+ ## ExprUnit / WhereExprUnit / ExprInput
27
16
 
28
- - `eq(source, target)` — `=` (NULL 안전: MySQL `<=>`, MSSQL/PG `IS NULL OR =`).
29
- - `gt` / `lt` / `gte` / `lte` `>` / `<` / `>=` / `<=`.
30
- - `between(source, from?, to?)` BETWEEN. `from`/`to` undefined 방향 무제한(`from`만 주면 `>=`, `to`만 주면 `<=`).
17
+ - `ExprUnit<TPrimitive>` — 타입 안전 값 표현식 래퍼. `.dataType`(ColumnPrimitiveStr), `.expr`(AST), `.$infer`(타입 추론 마커). `.n` getter 동일 식을 `NonNullable<T>` 로 좁힌 새 ExprUnit(`p.state!.sumQty` 처럼 nullable join 컬럼을 non-null 로 다룰 때 `.n` 대신 `!` 도 가능).
18
+ - `WhereExprUnit` — WHERE/HAVING boolean 표현식 래퍼(`.expr: WhereExpr`). 비교·논리 함수가 반환.
19
+ - `ExprInput<T>` `ExprUnit<T> | T`. 연산 인자·쓰기 값이 받는 타입(리터럴 직접 허용).
31
20
 
32
- ## WHERE — NULL / 문자열 / IN / EXISTS
21
+ ## 생성
33
22
 
34
- - `null(source): WhereExprUnit` — `IS NULL`.
35
- - `like(source, pattern)` — `LIKE`(`%`=다수, `_`=단일, `\` 이스케이프). 부분/접두 검색.
36
- - `regexp(source, pattern)`정규식 매칭(구문은 DBMS별 상이).
37
- - `in(source, values: ExprInput[])` `IN (값목록)`.
38
- - `inQuery(source, query: Queryable)` — `IN (SELECT ...)`. 서브쿼리가 단일 컬럼만 SELECT 해야 함(아니면 throw).
39
- - `exists(query: Queryable)` — `EXISTS (SELECT ...)`. SELECT 절은 제거해 패킷 절약. 상관 서브쿼리로 존재 검사.
23
+ - `val(dataType, value)` — 리터럴을 ExprUnit 으로 래핑. `dataType`="string"|"number"|"boolean"|"DateTime"|"DateOnly"|"Time"|"Uuid"|"Bytes". `value` undefined 허용(결과 타입에 undefined 포함). `select` 상수 컬럼 등 ExprUnit 강제 자리에서만.
24
+ - `col(dataType, ...path)` — column 참조 생성(내부용). 보통 콜백 프록시로 충분.
25
+ - `raw(dataType)\`SQL\``이스케이프 해치. 태그드 템플릿, 보간값은 자동 파라미터화. ORM 미지원 DB 함수 직접 사용 시. 보간값은 `ExprInput`. union 의 NULL 자리채움(`` expr.raw("number")`NULL` ``)에도 사용(orm-union.md).
26
+ - `toExpr(value)` `ExprInput` `Expr` AST 변환(내부 헬퍼).
40
27
 
41
- ```typescript
42
- db.user().where((u) => [expr.in(u.status, ["active", "pending"]), expr.exists(db.order().where((o) => [expr.eq(o.userId, u.id)]))])
43
- ```
28
+ ## WHERE — 비교 (반환 `WhereExprUnit`)
29
+
30
+ - `eq(source, target)` — 동등(NULL 안전: MySQL `<=>`, 그 외 `IS NULL OR =`).
31
+ - `gt(source, target)` / `lt(source, target)` / `gte(source, target)` / `lte(source, target)` — `>` / `<` / `>=` / `<=`.
32
+ - `between(source, from?, to?)` — 범위. `from`/`to` 중 하나가 undefined 면 그 방향 제한 없음(한쪽만 주면 `>=`/`<=` 로 동작).
33
+ - `null(source)` — IS NULL.
34
+ - `like(source, pattern)` — LIKE. `%`=0+ 문자, `_`=1 문자, 특수문자 `\` 이스케이프.
35
+ - `regexp(source, pattern)` — 정규식 매칭(구문은 DBMS 의존).
36
+ - `in(source, values)` — IN(값 목록).
37
+ - `inQuery(source, query)` — IN (SELECT ...). `query` 는 단일 column SELECT Queryable — 아니면 throw.
38
+ - `exists(query)` — EXISTS (서브쿼리 행 존재). SELECT 절은 제거되어 패킷 절약. (orm.md: SELECT 절 내부 `exists` 는 행당 N회 실행되므로 금지 — `joinSingle` 로 부착)
44
39
 
45
- ## WHERE — 논리 (not / and / or)
40
+ ## WHERE — 논리 (반환 `WhereExprUnit`)
46
41
 
47
- - `not(arg: WhereExprUnit)` — 조건 부정.
48
- - `and(conditions: WhereExprUnit[])` — AND 결합. 빈 배열이면 throw.
49
- - `or(conditions: WhereExprUnit[])` — OR 결합. 빈 배열이면 throw.
42
+ - `not(arg)` — NOT.
43
+ - `and(conditions)` — AND 결합. 빈 배열이면 throw. `where` 에 배열 넘기면 자동 AND 라 보통 불필요.
44
+ - `or(conditions)` — OR 결합. 빈 배열이면 throw.
50
45
 
51
- ## SELECT — 문자열
46
+ ## SELECT — 문자열 (반환 `ExprUnit`)
52
47
 
53
- - `concat(...args)` — CONCAT(NULL→빈문자). 반환 `ExprUnit<string>`.
54
- - `left(source, length)` / `right(source, length)` — 좌/우에서 length 글자.
48
+ - `concat(...args)` — CONCAT(NULL→빈문자열).
49
+ - `left(source, length)` / `right(source, length)` — 왼쪽/오른쪽 N자.
55
50
  - `trim(source)` — 양쪽 공백 제거.
56
- - `padStart(source, length, fillString)` — LPAD, 대상 길이까지 왼쪽 채움.
51
+ - `padStart(source, length, fillString)` — LPAD(목표 길이까지 왼쪽 채움).
57
52
  - `replace(source, from, to)` — 문자열 치환.
58
- - `upper(source)` / `lower(source)` — 대/소문자.
59
- - `length(source)` — 문자 수(반환 `number`). `byteLength(source)` — 바이트 수(UTF-8 CJK 3B).
60
- - `substring(source, start, length?)` — 부분 추출(1-기반 인덱스, length 생략 시 끝까지).
53
+ - `upper(source)` / `lower(source)` — 대/소문자 변환.
54
+ - `length(source)` — 문자 수. `byteLength(source)` — 바이트 수(UTF-8 CJK 3바이트).
55
+ - `substring(source, start, length?)` — 부분 문자열(1-기반 인덱스, length 생략 시 끝까지).
61
56
  - `indexOf(source, search)` — 위치(1-기반, 없으면 0).
62
57
 
63
- (string 입력류는 `<T extends string|undefined>` 로 nullable 전파.)
58
+ ## SELECT 숫자 (반환 `ExprUnit`)
64
59
 
65
- ## SELECT숫자
60
+ - `abs(source)`절대값. `round(source, digits)` — 반올림(소수 자릿수). `ceil(source)` — 올림. `floor(source)` — 내림.
66
61
 
67
- - `abs(source)`절대값. `round(source, digits)` — 반올림(digits 자리). `ceil(source)` — 올림. `floor(source)` — 내림. 모두 nullable 전파.
62
+ ## SELECT날짜 (반환 `ExprUnit`)
68
63
 
69
- ## SELECT날짜
64
+ - `year(source)` / `month(source)` / `day(source)` 연/월/일(source: DateTime|DateOnly).
65
+ - `hour(source)` / `minute(source)` / `second(source)` — 시/분/초(source: DateTime|Time).
66
+ - `isoWeek(source)` — ISO 주 번호(1~53). `isoWeekStartDate(source)` — 그 주 월요일. `isoYearMonth(source)` — 해당 월 1일.
67
+ - `dateDiff(unit, from, to)` — 날짜 차(to - from). `unit`="year"|"month"|"day"|"hour"|"minute"|"second".
68
+ - `dateAdd(unit, source, value)` — 날짜 가감(value 음수 허용). 결과 타입은 source 와 동일.
69
+ - `formatDate(source, format)` — 포맷 문자열로 변환(`"%Y-%m-%d"` 등, 규칙 DBMS 의존).
70
70
 
71
- - `year/month/day(source)`DateTime|DateOnly 에서 연/월/일(`number`). `hour/minute/second(source)` — DateTime|Time 에서 시/분/초.
72
- - `isoWeek(source)` — ISO 주번호(1~53). `isoWeekStartDate(source)` — 주의 월요일(`DateOnly`). `isoYearMonth(source)` — 해당 월 1일(`DateOnly`).
73
- - `dateDiff(unit, from, to)` — 날짜 차(`to - from`). `unit`=`DateUnit`(`"year"|"month"|"day"|"hour"|"minute"|"second"`).
74
- - `dateAdd(unit, source, value)` — 날짜 더하기(음수 허용, 결과 타입은 source 와 동일).
75
- - `formatDate(source, format)` — 포맷 문자열(`"%Y-%m-%d"`, 규칙 DBMS별 상이) → `string`.
71
+ ## SELECT조건 (반환 `ExprUnit`)
76
72
 
77
- (nullable source 결과도 nullable.)
73
+ - `coalesce(...args)` — 첫 non-null. 마지막 인수가 non-nullable 이면 결과도 non-nullable. join 도출 컬럼 기본값(`coalesce(p.state!.sum, 0)`)에 자주.
74
+ - `nullIf(source, value)` — source===value 이면 NULL(빈 문자열→NULL 변환 등).
75
+ - `is(condition)` — `WhereExprUnit` → boolean column 으로 변환(SELECT 절에서 조건 결과를 컬럼화).
76
+ - `switch<T>()` — CASE WHEN 빌더. `.case(condition, then).case(...).default(value)` 체이닝으로 `ExprUnit<T>` 마무리.
77
+ - `if(condition, then, else_)` — 삼항(IF/IIF). then/else 중 최소 하나 non-null 아니면 throw(타입 추론용).
78
78
 
79
- ## SELECT — 조건 (coalesce / nullIf / is / switch / if)
79
+ ## SELECT — 집계 (반환 `ExprUnit`)
80
80
 
81
- - `coalesce(...args)` non-null(COALESCE). 마지막 인자가 non-nullable 이면 결과도 non-nullable.
82
- - `nullIf(source, value)` — `source === value` 면 NULL, 아니면 source(빈문자→NULL 정규화 등). 결과 nullable.
83
- - `is(condition: WhereExprUnit): ExprUnit<boolean>` — WHERE 조건을 boolean 컬럼으로(SELECT 절에서).
84
- - `switch<T>(): SwitchExprBuilder<T>` — CASE WHEN. `.case(cond, then)` 체이닝 후 `.default(value)` 로 종료. then/default 중 하나는 non-null 이어야 타입 추론(전부 null 이면 throw).
85
- - `if<T>(condition, then, else_): ExprUnit<T>` — 삼항(IF). then/else 중 하나는 non-null 필요(아니면 throw).
81
+ NULL 행은 무시, 전부 NULL/무행이면 NULL.
86
82
 
87
- ```typescript
88
- db.user().select((u) => ({
89
- grade: expr.switch<string>().case(expr.gte(u.score, 90), "A").case(expr.gte(u.score, 80), "B").default("F"),
90
- isAdult: expr.is(expr.gte(u.age, 18)),
91
- }))
92
- ```
83
+ - `count(arg?, distinct?)` — 행 수. `arg` 미지정=전체, `distinct: true`=중복 제거.
84
+ - `sum(arg)` / `avg(arg)` 합/평균(number, 결과 nullable).
85
+ - `max(arg)` / `min(arg)` — 최대/최소(결과 nullable).
93
86
 
94
- ## SELECT — 집계 (count / sum / avg / max / min)
87
+ ## SELECT — 기타 (반환 `ExprUnit`)
95
88
 
96
- 집계는 행이 없거나 전부 NULL 때만 NULL(NULL 행은 무시). count 항상 `number`, 나머지는 nullable.
89
+ - `greatest(...args)` / `least(...args)` 여러 중 최대/최소( 비교). 인자 ExprUnit 하나는 있어야 타입 추론(없으면 throw).
90
+ - `rowNum()` — 전체 행 순번(1-기반, 단순 버전). `random()` — 0~1 난수(무작위 정렬에).
91
+ - `cast(source, targetType)` — 타입 변환. `targetType`=`DataType`(`{ type:"varchar", length:20 }` 등).
92
+ - `subquery(dataType, queryable)` — 스칼라 서브쿼리(1행 1열). `queryable`=`getSelectQueryDef()` 보유 객체. (orm.md: SELECT 절 내부 subquery 는 행당 N회 실행 → `joinSingle` 권장)
97
93
 
98
- - `count(arg?, distinct?)` 수. `arg` 생략 시 전체, `distinct:true` 면 중복 제거.
99
- - `sum(arg)` / `avg(arg)` — number 컬럼 합/평균(`number|undefined`).
100
- - `max(arg)` / `min(arg)` — 임의 타입 최대/최소(`T|undefined`, 타입은 arg 따라감).
94
+ ## SELECTWindow 함수 (반환 `ExprUnit`)
101
95
 
102
- ## SELECT 기타 (greatest / least / rowNum / random / cast / subquery)
96
+ 모두 `spec: { partitionBy?: ExprInput[]; orderBy?: [ExprInput, ("ASC"|"DESC")?][] }`(OVER 절) 인자를 받음.
103
97
 
104
- - `greatest(...args)` / `least(...args)` 여러 중 최대/최소(인자 중 ExprUnit 1개 이상 필요, 없으면 throw).
105
- - `rowNum(): ExprUnit<number>` 순번(1-기반). `random(): ExprUnit<number>` 0~1 난수(무작위 정렬용).
106
- - `cast(source, targetType: DataType): ExprUnit` — 타입 변환. `targetType`=`{ type: "varchar", length: 20 }` 등. 결과 타입은 targetType 으로 추론, nullable 전파.
107
- - `subquery(dataType, queryable): ExprUnit` 스칼라 서브쿼리(1행 1컬럼). SELECT 절에서 상관 집계 등에. 결과 nullable.
98
+ - `rowNumber(spec)` — 파티션 순번(1-기반).
99
+ - `rank(spec)`순위(동순위 후 건너뜀: 1,1,3). `denseRank(spec)`순위(동순위 후 연속: 1,1,2).
100
+ - `ntile(n, spec)` — 파티션을 n 그룹으로 분할(그룹 번호 1~n).
101
+ - `lag(column, spec, options?)` / `lead(column, spec, options?)` 이전/다음 값. `options.offset?`(기본 1), `options.default?`(없을 때 기본값).
102
+ - `firstValue(column, spec)` / `lastValue(column, spec)` — 프레임 내 첫/마지막 값.
103
+ - `sumOver(column, spec)` / `avgOver(column, spec)` / `minOver(column, spec)` / `maxOver(column, spec)` — window 합/평균/최소/최대(누적합·이동평균 등).
104
+ - `countOver(spec, column?)` — window 행 수. `column` 미지정=전체.
108
105
 
109
106
  ```typescript
110
- db.user().select((u) => ({ id: u.id, postCount: expr.subquery("number", db.post().where((p) => [expr.eq(p.userId, u.id)]).select(() => ({ c: expr.count() }))) }))
107
+ db.order().select((o) => ({
108
+ ...o,
109
+ runningTotal: expr.sumOver(o.amount, { partitionBy: [o.userId], orderBy: [[o.createdAt, "ASC"]] }),
110
+ }));
111
111
  ```
112
112
 
113
- ## SELECT — Window 함수
113
+ ## SwitchExprBuilder
114
114
 
115
- 전부 `spec: { partitionBy?: ExprInput[]; orderBy?: [ExprInput, ("ASC"|"DESC")?][] }` 받아 OVER 절 구성.
115
+ `expr.switch<T>()` 반환 객체.
116
116
 
117
- - `rowNumber(spec): ExprUnit<number>`ROW_NUMBER(파티션 내 1-기반 순번).
118
- - `rank(spec)` / `denseRank(spec)` — RANK(동순위 건너뜀: 1,1,3) / DENSE_RANK(연속: 1,1,2).
119
- - `ntile(n, spec)` — NTILE, 파티션을 n 그룹으로(1~n).
120
- - `lag(column, spec, options?)` / `lead(column, spec, options?)` — 이전/다음 행 값. `options.offset`(기본 1), `options.default`(없을 때 기본값). 결과 nullable.
121
- - `firstValue(column, spec)` / `lastValue(column, spec)` — 프레임 첫/마지막 값(nullable).
122
- - `sumOver(column, spec)` / `avgOver(column, spec)` — 윈도우 합/평균(누적합·이동평균).
123
- - `countOver(spec, column?)` — 윈도우 행 수(`column` 생략 시 전체).
124
- - `minOver(column, spec)` / `maxOver(column, spec)` — 윈도우 최소/최대(nullable).
117
+ - `case(condition: WhereExprUnit, then: ExprInput<T>)`분기 추가(체이닝).
118
+ - `default(value: ExprInput<T>)` — ELSE + 마무리, `ExprUnit<T>` 반환. case/default 최소 하나 non-null 아니면 throw.
125
119
 
126
120
  ```typescript
127
- db.order().select((o) => ({ ...o, runningTotal: expr.sumOver(o.amount, { partitionBy: [o.userId], orderBy: [[o.createdAt, "ASC"]] }) }))
121
+ grade: expr.switch<string>()
122
+ .case(expr.gte(u.score, 90), "A")
123
+ .case(expr.gte(u.score, 80), "B")
124
+ .default("F"),
128
125
  ```
@@ -1,145 +1,154 @@
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` 가 처리한다.
4
4
 
5
- `Queryable<TData, TFrom>`: `TData`=결과 타입(컬럼+조인). `TFrom`=소스 TableBuilder(CUD 가능 여부). select/groupBy/join 등으로 컬럼 구조가 바뀌면 `TFrom` `never` 되어 CUD 불가.
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` 프레임워크가 요구할 때만 사용.
6
6
 
7
- ## 옵션 (select / distinct / lock)
7
+ ## Queryable 조립 메서드 (체이닝)
8
8
 
9
- - `select(fn: (cols) => R): Queryable<...>` — 출력 컬럼 재정의. `fn` 은 원본 컬럼(`QueryableRecord`)을 받아 새 구조(ExprUnit·리터럴·중첩 객체) 반환. raw 리터럴은 자동 ExprUnit 래핑. 이후 CUD 불가.
10
- - `distinct(): Queryable` — DISTINCT 적용. count() 전이면 `wrap()` 필요.
11
- - `lock(): Queryable` — FOR UPDATE 행 잠금. 트랜잭션 내 배타 잠금 획득용. `TFrom` 유지.
9
+ 옵션:
12
10
 
13
- ```typescript
14
- db.user().select((u) => ({ userName: u.name, upper: expr.upper(u.email) }))
15
- ```
11
+ - `select(fn)` — SELECT column 재정의. `fn(columns) => R` 의 R 이 새 결과 형태. 리터럴 상수는 자동으로 ExprUnit 래핑. 호출 후 CUD 불가(`TFrom` 소실).
12
+ - `distinct()` DISTINCT 적용. 이후 `count()` 하려면 `wrap()` 필요.
13
+ - `lock()` — 행 잠금(FOR UPDATE). 트랜잭션 안에서 선택 행 배타 잠금. CUD 가능 상태 유지.
16
14
 
17
- ## 행 제한 (top / limit)
15
+ 제한:
18
16
 
19
- - `top(count: number): Queryable` — 상위 N 행(ORDER BY 없이도 가능). first()/exists() 가 내부적으로 `top(1)` 사용.
20
- - `limit(skip, take): Queryable` — OFFSET/LIMIT 페이지네이션. `skip`=건너뛸 수, `take`=가져올 수. **ORDER BY 선행 필수**, 없으면 throw.
17
+ - `top(count)` — 상위 N행. ORDER BY 없이도 사용 가능.
18
+ - `limit(skip, take)` — 페이지네이션 OFFSET/LIMIT. `skip`=건너뛸 수, `take`=가져올 수. **먼저 `orderBy()` 호출 필수** 없으면 throw.
21
19
 
22
- ## 정렬 (orderBy)
20
+ 정렬:
23
21
 
24
- - `orderBy(fnOrKey, orderBy?): Queryable` — 정렬 조건 추가(여러 번 호출 시 순서 누적). `fnOrKey`=컬럼 반환 함수 또는 체인 경로 문자열(`"user.name"`, 동적 정렬용 `obj.getChainValue`). `orderBy`=`"ASC"|"DESC"`, 기본 ASC.
22
+ - `orderBy(fnOrKey, orderBy?)` — 정렬 추가(여러 번 호출 시 순서대로). `fnOrKey`=정렬 column 반환 함수 또는 체인 경로 문자열(`"user.name"`, 동적 정렬용). `orderBy`="ASC"|"DESC"(기본 ASC).
25
23
 
26
- ```typescript
27
- db.user().orderBy((u) => u.name).orderBy((u) => u.age, "DESC").orderBy("id", "DESC")
28
- ```
24
+ 필터:
29
25
 
30
- ## 필터 (where / search)
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)` 로 대소문자 무시 매칭.
31
28
 
32
- - `where(predicate: (cols) => WhereExprUnit[]): Queryable` — WHERE 조건 추가. 배열 내 여러 조건·여러 번 호출 모두 AND 결합.
33
- - `search(fn: (cols) => ExprUnit<string|undefined>[], searchText): Queryable` — 텍스트 검색. `fn`=검색 대상 컬럼들. `searchText` 를 `parseSearchQuery` 로 파싱해 컬럼별 `LIKE lower(...)` 조건 생성(OR 묶음 + 필수 AND + 제외 NOT). `searchText` 가 공백이면 self 반환(조건 무추가).
29
+ 그룹:
34
30
 
35
- ```typescript
36
- db.user().where((u) => [expr.eq(u.isActive, true)]).search((u) => [u.name, u.email], "John -withdrawn")
37
- ```
31
+ - `groupBy(fn)` — GROUP BY. `fn(columns) => ExprUnit<ColumnPrimitive>[]`. 호출 후 CUD 불가.
32
+ - `having(predicate)` GROUP BY 이후 필터. `predicate(columns) => WhereExprUnit[]`.
38
33
 
39
- ## 그룹 (groupBy / having)
34
+ 조인:
40
35
 
41
- - `groupBy(fn: (cols) => ExprUnit[]): Queryable<TData, never>` GROUP BY. 이후 CUD 불가. count() 전이면 `wrap()` 필요.
42
- - `having(predicate: (cols) => WhereExprUnit[]): Queryable<TData, never>` GROUP BY 이후 필터. 여러 호출 AND 누적.
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
39
 
44
- ```typescript
45
- db.order()
46
- .select((o) => ({ userId: o.userId, total: expr.sum(o.amount) }))
47
- .groupBy((o) => [o.userId])
48
- .having((o) => [expr.gte(o.total, 10000)])
49
- ```
40
+ 서브쿼리/UNION:
50
41
 
51
- ## 조인 (join / joinSingle / include)
42
+ - `wrap()` — 현재 Queryable 을 서브쿼리(derived table)로 감쌈. `distinct()`/`groupBy()` 뒤 `count()` 처럼 프레임워크가 요구할 때만.
43
+ - `static Queryable.union(...queries)` — 2개 이상 Queryable 을 UNION(중복 제거)으로 결합. 결과 위 fluent 연산자는 외부 union 결과에 적용. select 컬럼 이름·타입·순서를 양쪽 동일하게 맞춰야 함(orm-union.md). 2개 미만이면 throw.
52
44
 
53
- - `join(as, fn): Queryable<TData & { [as]?: R[] }>` — 1:N LEFT JOIN, 결과에 배열로 추가. `as`=속성 이름. `fn=(qr, cols) => qr.from(Table).where(...)` 로 조인 조건 작성(`qr` 은 `JoinQueryable`).
54
- - `joinSingle(as, fn): Queryable<... & { [as]?: R }>` — N:1/1:1 LEFT JOIN, 단일 객체로 추가. `as` 동일 키면 덮어씀.
55
- - `include(fn: (item: PathProxy) => PathProxy): Queryable` — 빌더 관계 기반 자동 조인. `fn` 은 PathProxy 로 관계 경로 선택(`(p) => p.user.company` 처럼 중첩 가능, 컬럼 필드는 접근 불가=컴파일 에러). FK→단일, FKTarget→배열(single 이면 단일). 관계 미정의·TableBuilder 아님이면 throw. 같은 경로 중복 호출은 무시.
45
+ 재귀:
56
46
 
57
- `JoinQueryable`(join 콜백의 `qr`): `.from(Table)` 조인 대상 지정, `.select(columns)` 커스텀 컬럼, `.union(...queries)` 2개 이상 UNION(미만이면 throw).
47
+ - `recursive(fn)` WITH RECURSIVE CTE 생성(계층 데이터). `fn(cte) => cte.from(Table).where(...)` 안에서 `e.self[0]` 직전 단계 행을 참조.
58
48
 
59
- ```typescript
60
- db.post().joinSingle("user", (qr, p) => qr.from(User).where((u) => [expr.eq(u.id, p.userId)]))
61
- db.user().include((u) => u.company).include((u) => u.posts)
62
- ```
49
+ `db.user().join(...)`/`recursive(...)`/`Queryable.union(...)` 안에서 쓰는 `JoinQueryable`·`RecursiveQueryable` 의 `from`/`select`/`union` 은 콜백 인자로만 노출되며 직접 import 하지 않는다.
63
50
 
64
- ## 서브쿼리·결합 (wrap / union / recursive)
51
+ ## Queryable 종단 메서드
65
52
 
66
- - `wrap(): Queryable<TData, never>` — 현재 쿼리를 서브쿼리로 래핑(새 alias). distinct()/groupBy() 후 count() 하기 전 필수.
67
- - `static Queryable.union(...queries): Queryable` — 2개 이상 Queryable 을 UNION(중복 제거). 미만이면 throw. 첫 쿼리의 컬럼 구조 기준.
68
- - `recursive(fn: (cte) => Queryable): Queryable<TData, never>` — 재귀 CTE. base 쿼리(현재)+`fn` 이 정의한 재귀부. `cte` 는 `RecursiveQueryable`: `.from(Table)`/`.select(cols)`/`.union(...)` 제공하며 자기참조용 `self` 속성을 결과에 부여(`e.self[0].id`). 계층 데이터(조직도·트리)에.
53
+ 조회:
69
54
 
70
- ```typescript
71
- db.employee()
72
- .where((e) => [expr.null(e.managerId)])
73
- .recursive((cte) => cte.from(Employee).where((e) => [expr.eq(e.managerId, e.self[0].id)]))
74
- ```
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)`).
75
60
 
76
- ## SELECT 실행
61
+ CUD(모두 `TFrom` 이 TableBuilder 일 때만 — `select`/`groupBy` 후엔 불가):
77
62
 
78
- - `execute(): Promise<TData[]>` SELECT 실행, 배열 반환.
79
- - `single(): Promise<TData | undefined>`단일 결과. 2이상이면 throw.
80
- - `first(): Promise<TData | undefined>` 결과만(`top(1)`).
81
- - `count(fn?): Promise<number>` 행 수. `fn` 지정 시 해당 컬럼 COUNT. distinct()/groupBy() 직후 호출하면 throw(`wrap()` 먼저). 결과 없으면 0.
82
- - `exists(): Promise<boolean>`조건 충족 존재 여부(`top(1)` 길이).
83
- - `getSelectQueryDef(): SelectQueryDef` / `getResultMeta(outputColumns?): ResultMeta` — 실행 없이 AST·결과 메타 생성(executor·서브쿼리 내부에서 사용).
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 조건 매칭 행이 없을 때만 1INSERT. 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 지정 시 영향 행 반환.
84
69
 
85
- ## INSERT
70
+ QueryDef 생성기(실행 없이 AST 만): `getSelectQueryDef()`, `getInsertQueryDef(records, outputColumns?)`, `getInsertIfNotExistsQueryDef(...)`, `getInsertIntoQueryDef(...)`, `getUpdateQueryDef(...)`, `getDeleteQueryDef(...)`, `getUpsertQueryDef(...)`, `getResultMeta(outputColumns?)`. executor 우회·디버깅·배치 조립용.
86
71
 
87
- - `insert(records): Promise<void>` / `insert(records, outputColumns): Promise<Pick<...>[]>` — 레코드 배열 삽입. MSSQL 1000행 제한 대응 1000개씩 청크 분할. `outputColumns` 지정 시 삽입된 컬럼 반환. AI 컬럼에 명시값 있으면 overrideIdentity 자동 설정. 빈 배열은 무동작.
88
- - `insertIfNotExists(record[, outputColumns])` — WHERE 조건에 일치하는 데이터 없을 때만 단건 삽입. 현재 체인의 where 가 존재 검사 조건.
89
- - `insertInto(targetTable[, outputColumns])` — 현재 SELECT 결과를 다른 테이블에 INSERT INTO ... SELECT. `targetTable` 컬럼이 현재 데이터 형태와 호환(`DataToColumnBuilderRecord`)이어야 함.
72
+ 기타:
73
+
74
+ - `switchFk(enabled)` — Queryable 소스 테이블 FK 제약을 활성/비활성. 트랜잭션 안에서 가능.
75
+ - `readonly meta` — 내부 조립 상태(from/where/joins/columns 등). 직접 수정하지 않음.
90
76
 
91
77
  ```typescript
92
- const [row] = await db.user().insert([{ name: "홍길동" }], ["id"]);
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"]);
93
97
  ```
94
98
 
95
- ## UPDATE / DELETE
99
+ ## queryable / executable (팩토리 함수)
100
+
101
+ `DbContext` 내부에서 `this.queryable()`/`this.executable()` 로 쓰는 게 일반적이지만, 모듈 함수 형태도 export 됨.
102
+
103
+ - `queryable(db, tableOrView, as?)` — `() => Queryable` 팩토리. `as?` 미지정 시 호출마다 새 alias(`db.getNextAlias()`), 지정 시 고정. 반환 Queryable 의 column 은 빌더 정의로부터 구성(View 는 `viewFn` 평가).
104
+ - `executable(db, builder)` — `() => Executable` 팩토리.
96
105
 
97
- - `update(recordFwd[, outputColumns])` — `recordFwd=(cols) => ({ col: ExprInput })` 로 갱신값 지정(기존 값 참조 가능: `expr.mul(p.price, 1.1)`). where 로 대상 한정. `outputColumns` 시 갱신 행 반환.
98
- - `delete([outputColumns])` — where 조건 행 삭제. `outputColumns` 시 삭제된 행 반환.
106
+ ## Executable (프로시저 실행)
99
107
 
100
108
  ```typescript
101
- await db.user().where((u) => [expr.eq(u.id, 1)]).update((u) => ({ name: expr.val("string", "새이름") }));
109
+ class Executable<TParams, TReturns> {
110
+ getExecProcQueryDef(params?): ExecProcQueryDef;
111
+ execute(params): Promise<InferColumnExprs<TReturns>[][]>;
112
+ }
102
113
  ```
103
114
 
104
- ## UPSERT
105
-
106
- - `upsert(updateFn[, insertFn][, outputColumns])` — where 일치 시 UPDATE, 없으면 INSERT(MERGE 패턴). `updateFn=(cols) => 갱신값`. `insertFn=(updateRecord) => 삽입값`(생략 시 update 값 재사용, updateRecord 를 받아 추가 컬럼 합치기 가능). `outputColumns` 시 영향 행 반환.
115
+ - `execute(params)` — 프로시저 실행. `params`=`returns`/`params` 정의에 맞는 `InferColumnExprs<TParams>`(리터럴 또는 ExprUnit). 결과는 **결과셋 배열의 배열**(다중 SELECT 가능) — 단일 결과셋이면 `result[0]`.
116
+ - `getExecProcQueryDef(params?)` — 실행 없이 QueryDef 반환. params 가 있는데 프로시저에 파라미터 정의가 없으면 throw.
107
117
 
108
118
  ```typescript
109
- await db.user()
110
- .where((u) => [expr.eq(u.email, "t@t.com")])
111
- .upsert(() => ({ loginCount: expr.val("number", 1) }), (up) => ({ ...up, email: expr.val("string", "t@t.com") }));
119
+ const [rows] = await db.getUserById().execute({ userId: 1n });
112
120
  ```
113
121
 
114
- `get...QueryDef` 류(`getInsertQueryDef`/`getUpdateQueryDef`/`getUpsertQueryDef` 등)는 실행 없이 AST 반환. `switchFk(enabled)` 는 이 테이블 FK 제약 on/off(Table/View 기반 아니면 throw).
122
+ ## parseSearchQuery / ParsedSearchQuery
115
123
 
116
- ## 결과 타입 유틸
124
+ `search()` 내부로 쓰지만 직접 호출도 가능. 검색 구문 문자열을 SQL LIKE 패턴으로 분해.
117
125
 
118
- - `type QueryableRecord<TData>` — 각 컬럼을 `ExprUnit` 으로, 중첩 관계를 재귀 래핑한 콜백 인자 타입.
119
- - `type QueryableWriteRecord<TData>` — 컬럼을 `ExprInput`(쓰기) 으로 매핑(update/upsert 입력).
120
- - `type UnwrapQueryableRecord<R>` select 결과 구조를 데이터 타입으로 역변환(ExprUnit→값).
121
- - `type PathProxy<TObject>` — include 의 타입 안전 경로 프록시(관계 필드만 노출).
122
- - `getMatchedPrimaryKeys(fkCols, targetTable): string[]` — FK 컬럼 수와 대상 PK 를 매칭해 PK 이름 배열 반환. 개수 불일치 시 throw. include 내부 조인 조건 생성에 사용.
126
+ ```typescript
127
+ function parseSearchQuery(searchText: string): ParsedSearchQuery;
128
+ interface ParsedSearchQuery { or: string[]; must: string[]; not: string[]; }
129
+ ```
123
130
 
124
- ## 프로시저 실행 (Executable / executable)
131
+ - `or: string[]` — 일반 검색어(공백 구분, OR). 와일드카드 없으면 `%term%`(부분 일치).
132
+ - `must: string[]` — `+term` 또는 `"정확 구문"`(AND 필수).
133
+ - `not: string[]` — `-term`(NOT 제외).
134
+ - 와일드카드 `*` → `%`(`app*`→`app%` 시작 일치). 이스케이프: `\\` `\*` `\%` `\"` `\+` `\-` 는 리터럴. 닫히지 않은 `"` 는 일반 텍스트로 처리.
125
135
 
126
- `DbContext.executable(Procedure)` 가 `() => Executable` 팩토리를 반환한다. `Executable<TParams, TReturns>` 는 프로시저 실행 래퍼.
136
+ ```typescript
137
+ parseSearchQuery('apple "delicious fruit" -banana +strawberry');
138
+ // { or: ["%apple%"], must: ["%delicious fruit%", "%strawberry%"], not: ["%banana%"] }
139
+ ```
127
140
 
128
- - `executable(db: DbContextBase, builder: ProcedureBuilder): () => Executable` — 팩토리(보통 `DbContext.executable()` 보호 메서드가 호출).
129
- - `Executable.execute(params): Promise<TReturns[][]>` — 실행. `params` 는 `ProcedureBuilder.params()` 키별 값(리터럴 또는 ExprUnit). 결과는 결과셋 배열의 배열(다중 SELECT 대응).
130
- - `Executable.getExecProcQueryDef(params?): ExecProcQueryDef` — 실행 AST 생성. 파라미터 미정의 프로시저에 값 전달 시 throw.
141
+ ## getMatchedPrimaryKeys
131
142
 
132
143
  ```typescript
133
- const [rows] = await db.getUserById().execute({ userId: 1 });
144
+ function getMatchedPrimaryKeys(fkCols: string[], targetTable: TableBuilder): string[];
134
145
  ```
135
146
 
136
- ## 검색 파서 (parseSearchQuery)
147
+ - FK column 배열을 대상 테이블 PK 와 매칭해 PK column 이름 배열 반환. 개수 불일치 시 throw. `include()` 가 관계 조건을 만들 때 내부 사용 — 직접 호출은 드묾.
137
148
 
138
- `Queryable.search()` 내부에서 검색 문법 문자열을 SQL LIKE 패턴으로 변환. 직접 호출도 가능.
149
+ ## 결과/입력 변환 타입
139
150
 
140
- - `parseSearchQuery(searchText: string): ParsedSearchQuery` `{ or, must, not }`(각각 LIKE 패턴 배열) 반환.
141
- - `or: string[]` 공백 구분 일반 토큰(OR, 하나 이상 일치).
142
- - `must: string[]` `+토큰` 또는 `"정확한 구문"`(AND, 필수 포함).
143
- - `not: string[]` `-토큰`(NOT, 제외).
144
- - 문법: `term1 term2`(OR), `+term`(필수), `-term`(제외), `"구문"`(정확·필수), `*`(와일드카드→`%`). 와일드카드 없는 토큰은 `%토큰%`(부분 일치)로 변환. 이스케이프 `\\ \* \% \" \+ \-`. 닫히지 않은 따옴표는 일반 텍스트 처리.
145
- - `interface ParsedSearchQuery` — 위 `or`/`must`/`not` 세 패턴 배열을 담는 타입.
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` 경로 지정용 프록시 타입(관계 필드만 접근).