@simplysm/sd-claude 14.0.88 → 14.0.89

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 (122) hide show
  1. package/claude/references/sd-simplysm14/README.md +17 -17
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +27 -53
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +37 -105
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +46 -43
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +22 -32
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +40 -55
  7. package/claude/references/sd-simplysm14/apis/angular/infra.md +40 -40
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +25 -53
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +70 -82
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +44 -39
  11. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +21 -36
  12. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +52 -65
  13. package/claude/references/sd-simplysm14/apis/angular/sheet.md +65 -70
  14. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +33 -35
  15. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +7 -7
  16. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +29 -29
  17. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +45 -50
  18. package/claude/references/sd-simplysm14/apis/core-browser/README.md +42 -55
  19. package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +62 -0
  20. package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +13 -12
  21. package/claude/references/sd-simplysm14/apis/core-common/README.md +222 -98
  22. package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +102 -53
  23. package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +128 -0
  24. package/claude/references/sd-simplysm14/apis/core-common/datetime.md +98 -64
  25. package/claude/references/sd-simplysm14/apis/core-common/errors.md +91 -0
  26. package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +34 -28
  27. package/claude/references/sd-simplysm14/apis/core-common/obj.md +104 -40
  28. package/claude/references/sd-simplysm14/apis/core-node/README.md +11 -8
  29. package/claude/references/sd-simplysm14/apis/core-node/consola.md +23 -31
  30. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +33 -22
  31. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +28 -25
  32. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +39 -53
  33. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +26 -29
  34. package/claude/references/sd-simplysm14/apis/core-node/worker.md +27 -29
  35. package/claude/references/sd-simplysm14/apis/excel/README.md +14 -14
  36. package/claude/references/sd-simplysm14/apis/lint/README.md +27 -21
  37. package/claude/references/sd-simplysm14/apis/lint/rules.md +89 -49
  38. package/claude/references/sd-simplysm14/apis/orm-common/README.md +5 -59
  39. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +98 -67
  40. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +107 -92
  41. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +99 -65
  42. package/claude/references/sd-simplysm14/apis/orm-common/schema.md +83 -98
  43. package/claude/references/sd-simplysm14/apis/orm-common/types.md +62 -52
  44. package/claude/references/sd-simplysm14/apis/orm-node/README.md +62 -25
  45. package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +27 -27
  46. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +12 -15
  47. package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +92 -45
  48. package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +226 -108
  49. package/claude/references/sd-simplysm14/apis/service-client/README.md +84 -86
  50. package/claude/references/sd-simplysm14/apis/service-client/orm.md +14 -11
  51. package/claude/references/sd-simplysm14/apis/service-client/transport.md +33 -10
  52. package/claude/references/sd-simplysm14/apis/service-common/README.md +37 -23
  53. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +9 -9
  54. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +13 -13
  55. package/claude/references/sd-simplysm14/apis/service-server/README.md +81 -65
  56. package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +32 -35
  57. package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +44 -33
  58. package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +34 -45
  59. package/claude/references/sd-simplysm14/apis/storage/README.md +24 -18
  60. package/claude/skills/sd-demo/SKILL.md +6 -0
  61. package/claude/skills/sd-impl/SKILL.md +4 -7
  62. package/claude/skills/sd-spec/SKILL.md +31 -858
  63. package/claude/skills/sd-spec/references/spec-authoring.md +519 -0
  64. package/claude/workflows/sd-docs.js +84 -0
  65. package/package.json +1 -1
  66. package/claude/references/sd-simplysm14/apis/orm-common/query-builder.md +0 -29
  67. package/claude/skills/sd-demo/evals/fixtures/inventory-list/.specs/inventory/spec.md +0 -99
  68. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/package.json +0 -12
  69. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/index.ts +0 -3
  70. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inbound/inbound.list.ts +0 -150
  71. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inventory/inventory-master.list.ts +0 -143
  72. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/outbound/outbound.list.ts +0 -150
  73. package/claude/skills/sd-demo/evals/fixtures/inventory-list/pnpm-workspace.yaml +0 -2
  74. package/claude/skills/sd-demo/evals/fixtures/inventory-list/sd.config.ts +0 -12
  75. package/claude/skills/sd-demo/evals/golden.jsonl +0 -1
  76. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/package.json +0 -8
  77. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/src/.gitkeep +0 -0
  78. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tests/.gitkeep +0 -0
  79. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tsconfig.json +0 -10
  80. package/claude/skills/sd-dev/evals/golden.jsonl +0 -1
  81. package/claude/skills/sd-docs/SKILL.md +0 -46
  82. package/claude/skills/sd-docs/evals/fixtures/new-write/.claude/references/sd-simplysm14/README.md +0 -7
  83. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/package.json +0 -5
  84. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/src/index.ts +0 -3
  85. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/package.json +0 -6
  86. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/src/index.ts +0 -1
  87. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/package.json +0 -5
  88. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/src/index.ts +0 -8
  89. package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/README.md +0 -7
  90. package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/apis/foo/README.md +0 -3
  91. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/package.json +0 -5
  92. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/src/index.ts +0 -3
  93. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/package.json +0 -6
  94. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/src/index.ts +0 -1
  95. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/package.json +0 -5
  96. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/src/index.ts +0 -8
  97. package/claude/skills/sd-docs/evals/golden.jsonl +0 -2
  98. package/claude/skills/sd-impl/evals/fixtures/case-a-new-screen/.specs/260513120000_warehouse/spec.md +0 -101
  99. package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/.specs/260513120000_warehouse/spec.md +0 -101
  100. package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/packages/app/src/screens/box-register/box-register.view.ts +0 -46
  101. package/claude/skills/sd-impl/evals/fixtures/case-c-new-cross/.specs/260513120000_warehouse/spec.md +0 -89
  102. package/claude/skills/sd-impl/evals/fixtures/case-d-spec-modify/.specs/260513120000_warehouse/spec.md +0 -101
  103. package/claude/skills/sd-impl/evals/golden.jsonl +0 -4
  104. package/claude/skills/sd-manual/evals/fixtures/new-manual/src/notification.ts +0 -25
  105. package/claude/skills/sd-manual/evals/fixtures/update-manual/.claude/references/sd-simplysm14/manuals/notification.md +0 -14
  106. package/claude/skills/sd-manual/evals/fixtures/update-manual/src/notification.ts +0 -37
  107. package/claude/skills/sd-manual/evals/golden.jsonl +0 -2
  108. package/claude/skills/sd-review/evals/fixtures/code-review/src/foo.ts +0 -7
  109. package/claude/skills/sd-review/evals/fixtures/doc-review/docs/foo.md +0 -4
  110. package/claude/skills/sd-review/evals/golden.jsonl +0 -2
  111. package/claude/skills/sd-skill/evals/fixtures/existing-skill/.claude/skills/todo-format/SKILL.md +0 -14
  112. package/claude/skills/sd-skill/evals/fixtures/new-skill/.gitkeep +0 -0
  113. package/claude/skills/sd-skill/evals/golden.jsonl +0 -2
  114. package/claude/skills/sd-spec/evals/fixtures/case-a-split//355/232/214/354/235/230/353/241/235.md +0 -20
  115. package/claude/skills/sd-spec/evals/fixtures/case-b-detail/.specs/260513120000_warehouse/spec.md +0 -95
  116. package/claude/skills/sd-spec/evals/golden.jsonl +0 -2
  117. package/claude/skills/sd-unpack/evals/fixtures/eml-with-text-attachment/meeting.eml +0 -21
  118. package/claude/skills/sd-unpack/evals/fixtures/simple-eml/meeting.eml +0 -10
  119. package/claude/skills/sd-unpack/evals/golden.jsonl +0 -2
  120. package/claude/skills/sd-use/evals/fixtures/empty/.gitkeep +0 -0
  121. package/claude/skills/sd-use/evals/golden.jsonl +0 -6
  122. /package/claude/{skills/sd-docs/references/doc-rules.md → workflows/sd-docs.rules.md} +0 -0
@@ -1,113 +1,128 @@
1
1
  # @simplysm/orm-common — expr (SQL 표현식 빌더)
2
2
 
3
- dialect 독립 SQL 표현식을 JSON AST(`Expr`)로 만드는 `expr` 객체. `where`/`having`/`search` 콜백에선 비교·논리 함수(`WhereExprUnit` 반환), `select`/`orderBy`/`groupBy` 콜백에선 값·함수·집계(`ExprUnit<T>` 반환)를 쓴다. `ExprInput<T>` = `ExprUnit<T> | T`(리터럴 직접 전달 가능). `ExprUnit.n` 게터는 nullable non-null 타입으로 좁힌다.
4
-
5
- ## 값 생성
6
-
7
- - `expr.val(dataType, value): ExprUnit`리터럴 래핑. `dataType` `"string"|"number"|"boolean"|"DateTime"|"DateOnly"|"Time"|"Uuid"|"Bytes"`, `value` 그 값(undefined=NULL 허용). UPDATE/UPSERT 쓰기값에 주로 사용.
8
- - `expr.col(dataType, ...path): ExprUnit` — column 참조 직접 생성(보통은 프록시 사용, 내부용).
9
- - `expr.raw(dataType)\`SQL ${expr}\`: ExprUnit` — Raw SQL 이스케이프 해치. 태그드 템플릿, 보간값은 자동 파라미터화. ORM 미지원 DB 함수가 필요할 때만.
10
- - `expr.toExpr(value): Expr` — `ExprInput` `Expr` AST 변환(내부 헬퍼).
11
-
12
- ## WHERE — 비교 (WhereExprUnit 반환)
13
-
14
- - `expr.eq(source, target)` — 동등(=). **NULL 안전**(MySQL `<=>`, MSSQL/PG `IS NULL OR =`).
15
- - `expr.gt(source, target)` — 초과(>).
16
- - `expr.lt(source, target)` — 미만(<).
17
- - `expr.gte(source, target)` — 이상(>=).
18
- - `expr.lte(source, target)` — 이하(<=).
19
- - `expr.between(source, from?, to?)` — 범위(BETWEEN). `from`/`to` undefined 면 해당 방향 무제한(한쪽만 주면 `>=`/`<=`).
20
- - `expr.null(source)` — IS NULL 검사.
21
- - `expr.like(source, pattern)` — LIKE 패턴(`%`=0+, `_`=1자, 특수문자 `\` 이스케이프).
22
- - `expr.regexp(source, pattern)` — 정규식 매칭(구문은 DBMS 의존).
23
- - `expr.in(source, values)` — IN 값 목록.
24
- - `expr.inQuery(source, query)` IN (서브쿼리). `query` 단일 column SELECT Queryable(아니면 throw).
25
- - `expr.exists(query)` — EXISTS 서브쿼리(행 1개 이상이면 true). SELECT 절은 패킷 절약 위해 제거됨.
26
-
27
- ## WHERE — 논리
28
-
29
- - `expr.not(arg: WhereExprUnit)` — 부정(NOT).
30
- - `expr.and(conditions: WhereExprUnit[])` — AND 결합. 배열 throw.
31
- - `expr.or(conditions: WhereExprUnit[])` — OR 결합. 빈 배열 throw.
32
-
33
- ## SELECT — 문자열 (ExprUnit 반환)
34
-
35
- - `expr.concat(...args)` — CONCAT(NULL 문자열 취급).
36
- - `expr.left(source, length)` / `expr.right(source, length)` — 좌/우 N자 추출.
37
- - `expr.trim(source)` — 양쪽 공백 제거.
38
- - `expr.padStart(source, length, fillString)` — 좌측 패딩(LPAD), 목표 길이까지 `fillString` 반복.
39
- - `expr.replace(source, from, to)` — 치환.
40
- - `expr.upper(source)` / `expr.lower(source)` — 대/소문자 변환.
41
- - `expr.length(source)` — 문자 수(CHAR_LENGTH).
42
- - `expr.byteLength(source)` 바이트 길이(UTF-8 CJK 3바이트).
43
- - `expr.substring(source, start, length?)` — 부분 문자열(1-base 인덱스, length 생략 시 끝까지).
44
- - `expr.indexOf(source, search)` — 위치(1-base, 없으면 0).
3
+ `expr` 객체로 dialect 독립 SQL 표현식을 JSON AST(`Expr`)로 조립한다. where/having 콜백은 `WhereExprUnit[]` 를, select/orderBy/groupBy/update 콜백은 `ExprUnit<T>` 또는 리터럴을 반환한다. 모든 인자는 `ExprInput<T>`(= `ExprUnit<T> | T`) 컬럼·중첩 식·리터럴을 섞어 넘길 있다.
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`=반환 타입.
18
+
19
+ ```typescript
20
+ expr.val("string", "active")
21
+ expr.raw("string")`JSON_EXTRACT(${u.metadata}, '$.email')`
22
+ ```
23
+
24
+ ## WHERE비교 (eq / gt / lt / gte / lte / between)
25
+
26
+ 전부 `(source: ExprUnit<T>, target: ExprInput<T>) => WhereExprUnit`(between 제외).
27
+
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`만 주면 `<=`).
31
+
32
+ ## WHERE — NULL / 문자열 / IN / EXISTS
33
+
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 절은 제거해 패킷 절약. 상관 서브쿼리로 존재 검사.
40
+
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
+ ```
44
+
45
+ ## WHERE — 논리 (not / and / or)
46
+
47
+ - `not(arg: WhereExprUnit)` — 조건 부정.
48
+ - `and(conditions: WhereExprUnit[])` — AND 결합. 빈 배열이면 throw.
49
+ - `or(conditions: WhereExprUnit[])` — OR 결합. 빈 배열이면 throw.
50
+
51
+ ## SELECT — 문자열
52
+
53
+ - `concat(...args)` — CONCAT(NULL→빈문자). 반환 `ExprUnit<string>`.
54
+ - `left(source, length)` / `right(source, length)` — 좌/우에서 length 글자.
55
+ - `trim(source)` — 양쪽 공백 제거.
56
+ - `padStart(source, length, fillString)` — LPAD, 대상 길이까지 왼쪽 채움.
57
+ - `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 생략 시 끝까지).
61
+ - `indexOf(source, search)` — 위치(1-기반, 없으면 0).
62
+
63
+ (string 입력류는 `<T extends string|undefined>` 로 nullable 전파.)
45
64
 
46
65
  ## SELECT — 숫자
47
66
 
48
- - `expr.abs(source)` — 절대값.
49
- - `expr.round(source, digits: number)` — 반올림(소수 `digits` 자리).
50
- - `expr.ceil(source)` / `expr.floor(source)` — 올림/내림.
67
+ - `abs(source)` — 절대값. `round(source, digits)` — 반올림(digits 자리). `ceil(source)` — 올림. `floor(source)` — 내림. 모두 nullable 전파.
51
68
 
52
69
  ## SELECT — 날짜
53
70
 
54
- - `expr.year/month/day(source)` — 연/월/일 추출(`DateTime|DateOnly`).
55
- - `expr.hour/minute/second(source)` — 시/분/초 추출(`DateTime|Time`).
56
- - `expr.isoWeek(source)` — ISO 주 번호(월요일 시작, 1~53). `DateOnly`.
57
- - `expr.isoWeekStartDate(source)` — 해당 월요일 날짜. `DateOnly`.
58
- - `expr.isoYearMonth(source)` — 해당 1일. `DateOnly`.
59
- - `expr.dateDiff(unit: DateUnit, from, to)` — 날짜 차(`to - from`). `unit` = `"year"|"month"|"day"|"hour"|"minute"|"second"`.
60
- - `expr.dateAdd(unit, source, value)` 날짜 가감(`value` 음수 허용). 반환 타입은 source 와 동일.
61
- - `expr.formatDate(source, format: string)` — 포맷 문자열로 변환(규칙 DBMS 의존).
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`.
76
+
77
+ (nullable source 결과도 nullable.)
62
78
 
63
- ## SELECT — 조건
79
+ ## SELECT — 조건 (coalesce / nullIf / is / switch / if)
64
80
 
65
- - `expr.coalesce(...args)` — 첫 non-null 반환(COALESCE). 마지막 인자가 non-nullable 이면 결과도 non-nullable.
66
- - `expr.nullIf(source, value)` — `source===value` 면 NULL, 아니면 source.
67
- - `expr.is(condition: WhereExprUnit): ExprUnit<boolean>` — WHERE 조건을 boolean 값으로 SELECT.
68
- - `expr.switch<T>()` — CASE WHEN 빌더. `.case(condition, then).case(...).default(value)` 체이닝으로 분기 구성. 타입은 then/default ExprUnit 또는 non-null 리터럴에서 추론(전부 null 이면 throw).
69
- - `expr.if(condition, then, else_)` — 삼항(IF/IIF). then/else 중 최소 하나 non-null(전부 null throw).
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).
70
86
 
71
- ## SELECT — 집계
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
+ ```
72
93
 
73
- 집계는 모든 NULL/행 없음일 때만 NULL(NULL 행은 무시).
74
- - `expr.count(arg?, distinct?: boolean)` — COUNT. `arg` 생략 시 전체 행, `distinct:true` 면 중복 제거.
75
- - `expr.sum(arg)` — 합계(nullable number).
76
- - `expr.avg(arg)` — 평균(nullable number).
77
- - `expr.max(arg)` / `expr.min(arg)` — 최대/최소(nullable, 입력 타입 유지).
94
+ ## SELECT 집계 (count / sum / avg / max / min)
78
95
 
79
- ## SELECT 기타
96
+ 집계는 행이 없거나 전부 NULL 일 때만 NULL(NULL 행은 무시). count 만 항상 `number`, 나머지는 nullable.
80
97
 
81
- - `expr.greatest(...args)` / `expr.least(...args)` 인자 최대/최소값(GREATEST/LEAST). 인자 ExprUnit 없으면 throw.
82
- - `expr.rowNum(): ExprUnit<number>`단순 순번(1-base).
83
- - `expr.random(): ExprUnit<number>`0~1 난수(무작위 정렬 ).
84
- - `expr.cast(source, targetType: DataType): ExprUnit` — 타입 변환(CAST). `targetType` 예: `{ type: "varchar", length: 20 }`.
85
- - `expr.subquery(dataType, queryable): ExprUnit` — 스칼라 서브쿼리(정확히 1행 1열). `queryable` 은 `getSelectQueryDef()` 를 가진 객체.
98
+ - `count(arg?, distinct?)` 행 수. `arg` 생략 전체, `distinct:true` 중복 제거.
99
+ - `sum(arg)` / `avg(arg)` number 컬럼 합/평균(`number|undefined`).
100
+ - `max(arg)` / `min(arg)` 임의 타입 최대/최소(`T|undefined`, 타입은 arg 따라감).
86
101
 
87
- ## SELECT — Window 함수
102
+ ## SELECT — 기타 (greatest / least / rowNum / random / cast / subquery)
88
103
 
89
- `spec: WinSpecInput` = `{ partitionBy?: ExprInput[]; orderBy?: [ExprInput, ("ASC"|"DESC")?][] }`.
90
- - `expr.rowNumber(spec)`ROW_NUMBER()(파티션 1-base 순번).
91
- - `expr.rank(spec)` — RANK()(동순위 건너뜀: 1,1,3).
92
- - `expr.denseRank(spec)` — DENSE_RANK()(동순위 연속: 1,1,2).
93
- - `expr.ntile(n: number, spec)` — NTILE(n)(파티션을 n 그룹으로, 1~n).
94
- - `expr.lag(column, spec, options?)` / `expr.lead(column, spec, options?)` — 이전/다음 행 값. `options` = `{ offset?: number; default?: ExprInput }`(offset 기본 1, default 는 경계값).
95
- - `expr.firstValue(column, spec)` / `expr.lastValue(column, spec)` — 프레임 첫/마지막 값.
96
- - `expr.sumOver/avgOver(column, spec)` — window 합/평균(누적합·이동평균).
97
- - `expr.countOver(spec, column?)` — window 카운트(column 생략 시 전체).
98
- - `expr.minOver/maxOver(column, spec)` — window 최소/최대.
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.
99
108
 
100
109
  ```typescript
101
- db.order().select((o) => ({
102
- ...o,
103
- rowNum: expr.rowNumber({ partitionBy: [o.userId], orderBy: [[o.createdAt, "DESC"]] }),
104
- runningTotal: expr.sumOver(o.amount, { partitionBy: [o.userId], orderBy: [[o.createdAt, "ASC"]] }),
105
- }));
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() }))) }))
106
111
  ```
107
112
 
108
- ## 관련 타입 / 클래스
113
+ ## SELECT Window 함수
114
+
115
+ 전부 `spec: { partitionBy?: ExprInput[]; orderBy?: [ExprInput, ("ASC"|"DESC")?][] }` 를 받아 OVER 절 구성.
109
116
 
110
- - `class ExprUnit<T>` — 표현식 래퍼. `dataType`/`expr`(AST)/`$infer`(타입). `.n` 게터로 non-null 좁히기.
111
- - `class WhereExprUnit` — WHERE 표현식 래퍼(`expr` AST 만).
112
- - `type ExprInput<T> = ExprUnit<T> | T`.
113
- - `interface SwitchExprBuilder<T>` `expr.switch()` 빌더 인터페이스(`case`/`default`).
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).
125
+
126
+ ```typescript
127
+ db.order().select((o) => ({ ...o, runningTotal: expr.sumOver(o.amount, { partitionBy: [o.userId], orderBy: [[o.createdAt, "ASC"]] }) }))
128
+ ```
@@ -1,111 +1,145 @@
1
- # @simplysm/orm-common — Queryable (쿼리 작성)
1
+ # @simplysm/orm-common — Queryable (쿼리 작성·실행 · 프로시저 · 검색)
2
2
 
3
- `db.user()` 등록된 팩토리 호출로 받는 체이닝 쿼리 빌더. 옵션·필터·조인을 immutable 체이닝으로 쌓은 종결 메서드로 실행한다. 콜백 인자 `columns`/`u` 등은 `QueryableRecord`(column → `ExprUnit` 프록시)이며, 표현식은 [expr.md](./expr.md) 로 만든다.
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 연산은 TableBuilder 기반에서만 가능, View/서브쿼리는 `never`).
5
+ `Queryable<TData, TFrom>`: `TData`=결과 타입(컬럼+조인). `TFrom`=소스 TableBuilder(CUD 가능 여부). select/groupBy/join 등으로 컬럼 구조가 바뀌면 `TFrom` 이 `never` 가 되어 CUD 불가.
6
6
 
7
- ## 옵션 (SELECT 컬럼 / DISTINCT / LOCK)
7
+ ## 옵션 (select / distinct / lock)
8
8
 
9
- - `select(fn: (cols) => R): Queryable<...>` — SELECT column 재지정. `fn` 은 원본 column 프록시를 받아 `{별칭: ExprUnit | 리터럴 | 중첩객체}` 반환. CUD 소스성 상실(`TFrom=never`).
10
- - `distinct(): Queryable` — DISTINCT 적용. 이후 `count()` 직접 호출 불가(`wrap()` 필요).
11
- - `lock(): Queryable` — FOR UPDATE 행 잠금. 트랜잭션 내 배타 잠금이 필요할 때.
9
+ - `select(fn: (cols) => R): Queryable<...>` — 출력 컬럼 재정의. `fn` 은 원본 컬럼(`QueryableRecord`)을 받아 구조(ExprUnit·리터럴·중첩 객체) 반환. raw 리터럴은 자동 ExprUnit 래핑. 이후 CUD 불가.
10
+ - `distinct(): Queryable` — DISTINCT 적용. count() 전이면 `wrap()` 필요.
11
+ - `lock(): Queryable` — FOR UPDATE 행 잠금. 트랜잭션 내 배타 잠금 획득용. `TFrom` 유지.
12
12
 
13
- ## 행 제한 (TOP / LIMIT)
13
+ ```typescript
14
+ db.user().select((u) => ({ userName: u.name, upper: expr.upper(u.email) }))
15
+ ```
16
+
17
+ ## 행 제한 (top / limit)
14
18
 
15
- - `top(count: number): Queryable` — 상위 N행. ORDER BY 없이도 사용 가능.
16
- - `limit(skip: number, take: number): Queryable` — 페이지네이션(OFFSET `skip`, LIMIT `take`). **ORDER BY 선행 필수**(없으면 throw).
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
21
 
18
- ## 정렬 (ORDER BY)
22
+ ## 정렬 (orderBy)
19
23
 
20
- - `orderBy(fnOrKey, orderBy?: "ASC"|"DESC"): Queryable` — 정렬 조건 추가(여러 번 호출 시 순서대로 누적).
21
- - `fnOrKey` — 정렬 column 을 반환하는 함수 `(cols) => ExprUnit`, 또는 체인 경로 문자열(`"id"`, `"user.name"` — `obj.getChainValue` 로 해석. 동적 정렬 루프용).
22
- - `orderBy` — 방향. 기본 ASC.
24
+ - `orderBy(fnOrKey, orderBy?): Queryable` — 정렬 조건 추가(여러 번 호출 시 순서 누적). `fnOrKey`=컬럼 반환 함수 또는 체인 경로 문자열(`"user.name"`, 동적 정렬용 `obj.getChainValue`). `orderBy`=`"ASC"|"DESC"`, 기본 ASC.
25
+
26
+ ```typescript
27
+ db.user().orderBy((u) => u.name).orderBy((u) => u.age, "DESC").orderBy("id", "DESC")
28
+ ```
23
29
 
24
- ## 검색 (WHERE / search)
30
+ ## 필터 (where / search)
25
31
 
26
- - `where(predicate: (cols) => WhereExprUnit[]): Queryable` — WHERE 조건 추가. 배열 내부는 AND, 여러 번 호출도 AND 누적. 조건은 `expr.eq` 등으로 생성.
27
- - `search(fn: (cols) => ExprUnit<string|undefined>[], searchText: string): Queryable` — 다중 column 텍스트 검색. `fn` 은 검색 대상 column 들, `searchText` 검색 문법 문자열(공백=OR, `+`=필수, `-`=제외, `"..."`=구문, `*`=와일드카드). 문자열이면 변화 없이 self 반환. 내부적으로 `lower(col) LIKE pattern` 조합을 AND/OR 조립. (문법 상세: README 검색 파서)
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 반환(조건 무추가).
28
34
 
29
35
  ```typescript
30
- db.user().where((u) => [expr.eq(u.isActive, true)]).search((u) => [u.name, u.email], "John -withdrawn");
36
+ db.user().where((u) => [expr.eq(u.isActive, true)]).search((u) => [u.name, u.email], "John -withdrawn")
31
37
  ```
32
38
 
33
- ## 그룹 (GROUP BY / HAVING)
39
+ ## 그룹 (groupBy / having)
34
40
 
35
- - `groupBy(fn: (cols) => ExprUnit[]): Queryable` — GROUP BY. 이후 `count()` 직접 호출 불가(`wrap()` 필요).
36
- - `having(predicate: (cols) => WhereExprUnit[]): Queryable`그룹 필터(GROUP BY 이후). AND 누적.
41
+ - `groupBy(fn: (cols) => ExprUnit[]): Queryable<TData, never>` — GROUP BY. 이후 CUD 불가. count() 전이면 `wrap()` 필요.
42
+ - `having(predicate: (cols) => WhereExprUnit[]): Queryable<TData, never>` — GROUP BY 이후 필터. 여러 번 호출 시 AND 누적.
37
43
 
38
- ## 조인 (JOIN / JOIN SINGLE / INCLUDE)
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
+ ```
50
+
51
+ ## 조인 (join / joinSingle / include)
39
52
 
40
- - `join<A, R>(as: A, fn: (qr, cols) => Queryable<R>): Queryable<TData & { [as]?: R[] }>` — 1:N LEFT JOIN, 결과에 **배열**로 추가. `qr` `JoinQueryable`(`.from(table)`/`.select(cols)`/`.union(...)`), `cols` 는 바깥 column. 조인 조건은 반환 Queryable `where` 로.
41
- - `joinSingle<A, R>(as, fn): Queryable<... & { [as]?: R }>` — N:1/1:1 LEFT JOIN, 결과에 **단일 객체**로 추가.
42
- - `include(fn: (item) => PathProxy): Queryable` — TableBuilder FK/FKT 관계를 자동 조인. `fn` 은 타입 안전 경로 프록시(`item.user.company` 처럼 관계 필드만 접근 가능, ColumnPrimitive 필드는 접근 불가). 다단계·중복 include 지원. 관계 미정의 throw, View 기반 queryable 에선 사용 불가.
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. 같은 경로 중복 호출은 무시.
56
+
57
+ `JoinQueryable`(join 콜백의 `qr`): `.from(Table)` 조인 대상 지정, `.select(columns)` 커스텀 컬럼, `.union(...queries)` 2개 이상 UNION(미만이면 throw).
43
58
 
44
59
  ```typescript
45
- db.post().include((p) => p.user.company);
46
- db.user().join("posts", (qr, u) => qr.from(Post).where((p) => [expr.eq(p.userId, u.id)]));
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)
47
62
  ```
48
63
 
49
- ## 서브쿼리 / UNION / 재귀
64
+ ## 서브쿼리·결합 (wrap / union / recursive)
50
65
 
51
- - `wrap(): Queryable<TData, never>` — 현재 쿼리를 서브쿼리로 래핑. `distinct()`/`groupBy()``count()` 하려면 필수.
52
- - `static Queryable.union<TData>(...queries): Queryable<TData, never>` 여러 Queryable UNION(중복 제거). 최소 2개, 미만 시 throw. (`JoinQueryable.union` 동일 의미)
53
- - `recursive(fn: (qr: RecursiveQueryable) => Queryable): Queryable<TData, never>` — 재귀 CTE(WITH RECURSIVE). 계층 데이터 조회. `qr.from(table)`/`qr.select(cols)`/`qr.union(...)` 재귀 본문 정의, 재귀 측은 `self` 프로퍼티로 base 를 자기 참조.
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`). 계층 데이터(조직도·트리)에.
54
69
 
55
70
  ```typescript
56
- const count = await db.user().select((u) => ({ name: u.name })).distinct().wrap().count();
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)]))
57
74
  ```
58
75
 
59
- ## 종결 — SELECT 실행
76
+ ## SELECT 실행
60
77
 
61
- - `execute(): Promise<TData[]>` — SELECT 실행, 결과 배열.
78
+ - `execute(): Promise<TData[]>` — SELECT 실행, 배열 반환.
62
79
  - `single(): Promise<TData | undefined>` — 단일 결과. 2건 이상이면 throw.
63
- - `first(): Promise<TData | undefined>` — 첫 결과(내부적으로 `top(1)`).
64
- - `count(fn?: (cols) => ExprUnit): Promise<number>` — 행 수. `fn` 지정 시 해당 column 카운트. `distinct()`/`groupBy()` 직후 호출 throw(`wrap()` 먼저).
65
- - `exists(): Promise<boolean>` — 조건 충족 행 존재 여부(`top(1)` 길이 검사).
66
- - `getSelectQueryDef(): SelectQueryDef` / `getResultMeta(outputColumns?): ResultMeta` — 실행용 def·결과 메타 생성(executor·서브쿼리 합성에서 사용).
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·서브쿼리 내부에서 사용).
67
84
 
68
- ## 종결 — INSERT
85
+ ## INSERT
69
86
 
70
- INSERT `outputColumns` 미전달 `void`, 전달해당 column 뽑은 레코드를 반환(반환 자동증가 PK 수신 등).
71
- - `insert(records: TFrom["$inferInsert"][], outputColumns?): Promise<void | Pick<...>[]>` 다건 INSERT. MSSQL 1000행 제한 때문에 1000건씩 청크 분할. AI column 명시값 있으면 자동 overrideIdentity.
72
- - `insertIfNotExists(record, outputColumns?): Promise<void | Pick<...>>` 현재 WHERE 조건 매칭이 없을 때만 INSERT.
73
- - `insertInto(targetTable, outputColumns?): Promise<void | Pick<...>[]>` — 현재 SELECT 결과를 다른 테이블에 INSERT INTO ... SELECT. `targetTable` column 구조가 현재 데이터와 호환되어야 함(타입 매칭).
74
- - `getInsertQueryDef` / `getInsertIfNotExistsQueryDef` / `getInsertIntoQueryDef` — 각 def 생성기.
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`)이어야 함.
75
90
 
76
91
  ```typescript
77
- const [inserted] = await db.user().insert([{ name: "Hong" }], ["id"]);
92
+ const [row] = await db.user().insert([{ name: "홍길동" }], ["id"]);
78
93
  ```
79
94
 
80
- ## 종결 — UPDATE / DELETE
95
+ ## UPDATE / DELETE
81
96
 
82
- - `update(recordFwd: (cols) => QueryableWriteRecord, outputColumns?): Promise<void | Pick<...>[]>` UPDATE. `recordFwd` column 갱신값(`ExprInput`) 매핑 반환. 기존 값 참조 가능(`expr.mul(p.price, ...)`). WHERE 미리 체이닝.
83
- - `delete(outputColumns?): Promise<void | Pick<...>[]>` DELETE. WHERE 미리 체이닝.
84
- - `getUpdateQueryDef` / `getDeleteQueryDef` — def 생성기.
97
+ - `update(recordFwd[, outputColumns])` — `recordFwd=(cols) => ({ col: ExprInput })` 갱신값 지정(기존 값 참조 가능: `expr.mul(p.price, 1.1)`). where 대상 한정. `outputColumns` 시 갱신 행 반환.
98
+ - `delete([outputColumns])` where 조건 삭제. `outputColumns` 삭제된 행 반환.
85
99
 
86
- ## 종결 — UPSERT
100
+ ```typescript
101
+ await db.user().where((u) => [expr.eq(u.id, 1)]).update((u) => ({ name: expr.val("string", "새이름") }));
102
+ ```
103
+
104
+ ## UPSERT
87
105
 
88
- - `upsert(updateFn, insertFn?, outputColumns?): Promise<void | Pick<...>[]>` WHERE 매칭 있으면 UPDATE, 없으면 INSERT.
89
- - `updateFn: (cols) => QueryableWriteRecord` — 갱신/삽입 공통값(insertFn 생략 시 INSERT 도 이 값 사용).
90
- - `insertFn?: (updateRecord) => QueryableWriteRecord` — INSERT 전용값(update 결과를 받아 변형). UPDATE/INSERT 데이터가 다를 때.
91
- - `outputColumns?` — 반환 column.
92
- - `getUpsertQueryDef(...)` — def 생성기.
106
+ - `upsert(updateFn[, insertFn][, outputColumns])`where 일치 UPDATE, 없으면 INSERT(MERGE 패턴). `updateFn=(cols) => 갱신값`. `insertFn=(updateRecord) => 삽입값`(생략 시 update 값 재사용, updateRecord 를 받아 추가 컬럼 합치기 가능). `outputColumns` 시 영향 행 반환.
93
107
 
94
108
  ```typescript
95
109
  await db.user()
96
110
  .where((u) => [expr.eq(u.email, "t@t.com")])
97
- .upsert(() => ({ name: expr.val("string", "x"), email: expr.val("string", "t@t.com") }));
111
+ .upsert(() => ({ loginCount: expr.val("number", 1) }), (up) => ({ ...up, email: expr.val("string", "t@t.com") }));
98
112
  ```
99
113
 
100
- ## DDL Helper / 기타
114
+ `get...QueryDef` 류(`getInsertQueryDef`/`getUpdateQueryDef`/`getUpsertQueryDef` 등)는 실행 없이 AST 반환. `switchFk(enabled)` 는 이 테이블 FK 제약 on/off(Table/View 기반 아니면 throw).
115
+
116
+ ## 결과 타입 유틸
117
+
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 내부 조인 조건 생성에 사용.
123
+
124
+ ## 프로시저 실행 (Executable / executable)
125
+
126
+ `DbContext.executable(Procedure)` 가 `() => Executable` 팩토리를 반환한다. `Executable<TParams, TReturns>` 는 프로시저 실행 래퍼.
127
+
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.
131
+
132
+ ```typescript
133
+ const [rows] = await db.getUserById().execute({ userId: 1 });
134
+ ```
101
135
 
102
- - `switchFk(enabled: boolean): Promise<void>` — 이 테이블 FK 제약 활성/비활성(트랜잭션 내 가능). TableBuilder/ViewBuilder 기반에서만.
103
- - `queryable(db, tableOrView, as?): () => Queryable` — Table/View 용 Queryable 팩토리 함수(보통 `DbContext.queryable()` 가 호출). `as` 미지정 시 자동 alias.
104
- - `getMatchedPrimaryKeys(fkCols, targetTable): string[]` — FK column 배열과 대상 PK 매칭(개수 불일치 시 throw). include 내부 헬퍼.
136
+ ## 검색 파서 (parseSearchQuery)
105
137
 
106
- ## 관련 타입
138
+ `Queryable.search()` 내부에서 검색 문법 문자열을 SQL LIKE 패턴으로 변환. 직접 호출도 가능.
107
139
 
108
- - `QueryableRecord<TData>` column `ExprUnit` 프록시 레코드(콜백 인자 타입).
109
- - `QueryableWriteRecord<TData>`column `ExprInput`(쓰기 콜백 반환 타입).
110
- - `UnwrapQueryableRecord<R>``select` 결과 `ExprUnit` 언래핑 → 데이터 타입.
111
- - `PathProxy<TObject>` `include` 경로 수집용 타입 안전 프록시(관계 필드만 노출).
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` 세 패턴 배열을 담는 타입.