@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.
- package/claude/references/sd-simplysm14/README.md +17 -17
- package/claude/references/sd-simplysm14/apis/angular/README.md +27 -53
- package/claude/references/sd-simplysm14/apis/angular/controls.md +37 -105
- package/claude/references/sd-simplysm14/apis/angular/crud.md +46 -43
- package/claude/references/sd-simplysm14/apis/angular/directives.md +22 -32
- package/claude/references/sd-simplysm14/apis/angular/features.md +40 -55
- package/claude/references/sd-simplysm14/apis/angular/infra.md +40 -40
- package/claude/references/sd-simplysm14/apis/angular/layout.md +25 -53
- package/claude/references/sd-simplysm14/apis/angular/overlay.md +70 -82
- package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +44 -39
- package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +21 -36
- package/claude/references/sd-simplysm14/apis/angular/shared-data.md +52 -65
- package/claude/references/sd-simplysm14/apis/angular/sheet.md +65 -70
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +33 -35
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +7 -7
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +29 -29
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +45 -50
- package/claude/references/sd-simplysm14/apis/core-browser/README.md +42 -55
- package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +62 -0
- package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +13 -12
- package/claude/references/sd-simplysm14/apis/core-common/README.md +222 -98
- package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +102 -53
- package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +128 -0
- package/claude/references/sd-simplysm14/apis/core-common/datetime.md +98 -64
- package/claude/references/sd-simplysm14/apis/core-common/errors.md +91 -0
- package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +34 -28
- package/claude/references/sd-simplysm14/apis/core-common/obj.md +104 -40
- package/claude/references/sd-simplysm14/apis/core-node/README.md +11 -8
- package/claude/references/sd-simplysm14/apis/core-node/consola.md +23 -31
- package/claude/references/sd-simplysm14/apis/core-node/cpx.md +33 -22
- package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +28 -25
- package/claude/references/sd-simplysm14/apis/core-node/fsx.md +39 -53
- package/claude/references/sd-simplysm14/apis/core-node/pathx.md +26 -29
- package/claude/references/sd-simplysm14/apis/core-node/worker.md +27 -29
- package/claude/references/sd-simplysm14/apis/excel/README.md +14 -14
- package/claude/references/sd-simplysm14/apis/lint/README.md +27 -21
- package/claude/references/sd-simplysm14/apis/lint/rules.md +89 -49
- package/claude/references/sd-simplysm14/apis/orm-common/README.md +5 -59
- package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +98 -67
- package/claude/references/sd-simplysm14/apis/orm-common/expr.md +107 -92
- package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +99 -65
- package/claude/references/sd-simplysm14/apis/orm-common/schema.md +83 -98
- package/claude/references/sd-simplysm14/apis/orm-common/types.md +62 -52
- package/claude/references/sd-simplysm14/apis/orm-node/README.md +62 -25
- package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +27 -27
- package/claude/references/sd-simplysm14/apis/sd-cli/README.md +12 -15
- package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +92 -45
- package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +226 -108
- package/claude/references/sd-simplysm14/apis/service-client/README.md +84 -86
- package/claude/references/sd-simplysm14/apis/service-client/orm.md +14 -11
- package/claude/references/sd-simplysm14/apis/service-client/transport.md +33 -10
- package/claude/references/sd-simplysm14/apis/service-common/README.md +37 -23
- package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +9 -9
- package/claude/references/sd-simplysm14/apis/service-common/protocol.md +13 -13
- package/claude/references/sd-simplysm14/apis/service-server/README.md +81 -65
- package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +32 -35
- package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +44 -33
- package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +34 -45
- package/claude/references/sd-simplysm14/apis/storage/README.md +24 -18
- package/claude/skills/sd-demo/SKILL.md +6 -0
- package/claude/skills/sd-impl/SKILL.md +4 -7
- package/claude/skills/sd-spec/SKILL.md +31 -858
- package/claude/skills/sd-spec/references/spec-authoring.md +519 -0
- package/claude/workflows/sd-docs.js +84 -0
- package/package.json +1 -1
- package/claude/references/sd-simplysm14/apis/orm-common/query-builder.md +0 -29
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/.specs/inventory/spec.md +0 -99
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/package.json +0 -12
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/index.ts +0 -3
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inbound/inbound.list.ts +0 -150
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inventory/inventory-master.list.ts +0 -143
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/outbound/outbound.list.ts +0 -150
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/pnpm-workspace.yaml +0 -2
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/sd.config.ts +0 -12
- package/claude/skills/sd-demo/evals/golden.jsonl +0 -1
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/package.json +0 -8
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/src/.gitkeep +0 -0
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tests/.gitkeep +0 -0
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tsconfig.json +0 -10
- package/claude/skills/sd-dev/evals/golden.jsonl +0 -1
- package/claude/skills/sd-docs/SKILL.md +0 -46
- package/claude/skills/sd-docs/evals/fixtures/new-write/.claude/references/sd-simplysm14/README.md +0 -7
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/src/index.ts +0 -3
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/package.json +0 -6
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/src/index.ts +0 -1
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/src/index.ts +0 -8
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/README.md +0 -7
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/apis/foo/README.md +0 -3
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/src/index.ts +0 -3
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/package.json +0 -6
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/src/index.ts +0 -1
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/src/index.ts +0 -8
- package/claude/skills/sd-docs/evals/golden.jsonl +0 -2
- package/claude/skills/sd-impl/evals/fixtures/case-a-new-screen/.specs/260513120000_warehouse/spec.md +0 -101
- package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/.specs/260513120000_warehouse/spec.md +0 -101
- package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/packages/app/src/screens/box-register/box-register.view.ts +0 -46
- package/claude/skills/sd-impl/evals/fixtures/case-c-new-cross/.specs/260513120000_warehouse/spec.md +0 -89
- package/claude/skills/sd-impl/evals/fixtures/case-d-spec-modify/.specs/260513120000_warehouse/spec.md +0 -101
- package/claude/skills/sd-impl/evals/golden.jsonl +0 -4
- package/claude/skills/sd-manual/evals/fixtures/new-manual/src/notification.ts +0 -25
- package/claude/skills/sd-manual/evals/fixtures/update-manual/.claude/references/sd-simplysm14/manuals/notification.md +0 -14
- package/claude/skills/sd-manual/evals/fixtures/update-manual/src/notification.ts +0 -37
- package/claude/skills/sd-manual/evals/golden.jsonl +0 -2
- package/claude/skills/sd-review/evals/fixtures/code-review/src/foo.ts +0 -7
- package/claude/skills/sd-review/evals/fixtures/doc-review/docs/foo.md +0 -4
- package/claude/skills/sd-review/evals/golden.jsonl +0 -2
- package/claude/skills/sd-skill/evals/fixtures/existing-skill/.claude/skills/todo-format/SKILL.md +0 -14
- package/claude/skills/sd-skill/evals/fixtures/new-skill/.gitkeep +0 -0
- package/claude/skills/sd-skill/evals/golden.jsonl +0 -2
- package/claude/skills/sd-spec/evals/fixtures/case-a-split//355/232/214/354/235/230/353/241/235.md +0 -20
- package/claude/skills/sd-spec/evals/fixtures/case-b-detail/.specs/260513120000_warehouse/spec.md +0 -95
- package/claude/skills/sd-spec/evals/golden.jsonl +0 -2
- package/claude/skills/sd-unpack/evals/fixtures/eml-with-text-attachment/meeting.eml +0 -21
- package/claude/skills/sd-unpack/evals/fixtures/simple-eml/meeting.eml +0 -10
- package/claude/skills/sd-unpack/evals/golden.jsonl +0 -2
- package/claude/skills/sd-use/evals/fixtures/empty/.gitkeep +0 -0
- package/claude/skills/sd-use/evals/golden.jsonl +0 -6
- /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`)로
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- `
|
|
8
|
-
- `
|
|
9
|
-
- `
|
|
10
|
-
- `
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- `
|
|
16
|
-
- `
|
|
17
|
-
- `
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- `
|
|
30
|
-
- `
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- `
|
|
36
|
-
- `
|
|
37
|
-
- `
|
|
38
|
-
- `
|
|
39
|
-
- `
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
- `
|
|
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
|
-
- `
|
|
55
|
-
- `
|
|
56
|
-
- `
|
|
57
|
-
- `
|
|
58
|
-
- `
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
- `
|
|
66
|
-
- `
|
|
67
|
-
- `
|
|
68
|
-
- `
|
|
69
|
-
- `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
+
집계는 행이 없거나 전부 NULL 일 때만 NULL(NULL 행은 무시). count 만 항상 `number`, 나머지는 nullable.
|
|
80
97
|
|
|
81
|
-
- `
|
|
82
|
-
- `
|
|
83
|
-
- `
|
|
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 —
|
|
102
|
+
## SELECT — 기타 (greatest / least / rowNum / random / cast / subquery)
|
|
88
103
|
|
|
89
|
-
|
|
90
|
-
- `
|
|
91
|
-
- `
|
|
92
|
-
- `
|
|
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.
|
|
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
|
-
- `
|
|
111
|
-
- `
|
|
112
|
-
- `
|
|
113
|
-
- `
|
|
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()`
|
|
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
|
|
5
|
+
`Queryable<TData, TFrom>`: `TData`=결과 행 타입(컬럼+조인). `TFrom`=소스 TableBuilder(CUD 가능 여부). select/groupBy/join 등으로 컬럼 구조가 바뀌면 `TFrom` 이 `never` 가 되어 CUD 불가.
|
|
6
6
|
|
|
7
|
-
## 옵션 (
|
|
7
|
+
## 옵션 (select / distinct / lock)
|
|
8
8
|
|
|
9
|
-
- `select(fn: (cols) => R): Queryable<...>` —
|
|
10
|
-
- `distinct(): Queryable` — DISTINCT 적용.
|
|
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
|
-
|
|
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
|
|
16
|
-
- `limit(skip
|
|
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
|
-
## 정렬 (
|
|
22
|
+
## 정렬 (orderBy)
|
|
19
23
|
|
|
20
|
-
- `orderBy(fnOrKey, orderBy
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
##
|
|
30
|
+
## 필터 (where / search)
|
|
25
31
|
|
|
26
|
-
- `where(predicate: (cols) => WhereExprUnit[]): Queryable` — WHERE 조건 추가. 배열
|
|
27
|
-
- `search(fn: (cols) => ExprUnit<string|undefined>[], searchText
|
|
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
|
-
## 그룹 (
|
|
39
|
+
## 그룹 (groupBy / having)
|
|
34
40
|
|
|
35
|
-
- `groupBy(fn: (cols) => ExprUnit[]): Queryable
|
|
36
|
-
- `having(predicate: (cols) => WhereExprUnit[]): Queryable
|
|
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
|
-
|
|
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
|
|
41
|
-
- `joinSingle
|
|
42
|
-
- `include(fn: (item) => PathProxy): 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().
|
|
46
|
-
db.user().
|
|
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
|
-
##
|
|
64
|
+
## 서브쿼리·결합 (wrap / union / recursive)
|
|
50
65
|
|
|
51
|
-
- `wrap(): Queryable<TData, never>` — 현재 쿼리를 서브쿼리로
|
|
52
|
-
- `static Queryable.union
|
|
53
|
-
- `recursive(fn: (
|
|
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
|
-
|
|
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
|
-
##
|
|
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>` — 첫
|
|
64
|
-
- `count(fn
|
|
65
|
-
- `exists(): Promise<boolean>` — 조건 충족 행 존재 여부(`top(1)`
|
|
66
|
-
- `getSelectQueryDef(): SelectQueryDef` / `getResultMeta(outputColumns?): ResultMeta` —
|
|
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
|
-
##
|
|
85
|
+
## INSERT
|
|
69
86
|
|
|
70
|
-
|
|
71
|
-
- `
|
|
72
|
-
- `
|
|
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 [
|
|
92
|
+
const [row] = await db.user().insert([{ name: "홍길동" }], ["id"]);
|
|
78
93
|
```
|
|
79
94
|
|
|
80
|
-
##
|
|
95
|
+
## UPDATE / DELETE
|
|
81
96
|
|
|
82
|
-
- `update(recordFwd
|
|
83
|
-
- `delete(outputColumns
|
|
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
|
-
|
|
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
|
|
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(() => ({
|
|
111
|
+
.upsert(() => ({ loginCount: expr.val("number", 1) }), (up) => ({ ...up, email: expr.val("string", "t@t.com") }));
|
|
98
112
|
```
|
|
99
113
|
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
- `
|
|
109
|
-
- `
|
|
110
|
-
- `
|
|
111
|
-
- `
|
|
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` 세 패턴 배열을 담는 타입.
|