@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.
- package/claude/references/sd-simplysm14/README.md +16 -17
- package/claude/references/sd-simplysm14/apis/angular/README.md +52 -30
- package/claude/references/sd-simplysm14/apis/angular/controls.md +200 -38
- package/claude/references/sd-simplysm14/apis/angular/crud.md +41 -53
- package/claude/references/sd-simplysm14/apis/angular/directives.md +66 -22
- package/claude/references/sd-simplysm14/apis/angular/features.md +127 -40
- package/claude/references/sd-simplysm14/apis/angular/infra.md +60 -43
- package/claude/references/sd-simplysm14/apis/angular/layout.md +56 -20
- package/claude/references/sd-simplysm14/apis/angular/overlay.md +74 -74
- package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +50 -40
- package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +55 -15
- package/claude/references/sd-simplysm14/apis/angular/shared-data.md +59 -42
- package/claude/references/sd-simplysm14/apis/angular/sheet.md +77 -62
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +8 -7
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +71 -43
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +22 -14
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +19 -19
- package/claude/references/sd-simplysm14/apis/core-browser/README.md +17 -17
- package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +28 -28
- package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +37 -37
- package/claude/references/sd-simplysm14/apis/core-common/README.md +87 -219
- package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +54 -98
- package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +57 -99
- package/claude/references/sd-simplysm14/apis/core-common/datetime.md +60 -103
- package/claude/references/sd-simplysm14/apis/core-common/errors.md +42 -47
- package/claude/references/sd-simplysm14/apis/core-common/obj.md +42 -88
- package/claude/references/sd-simplysm14/apis/core-common/serialization.md +55 -0
- package/claude/references/sd-simplysm14/apis/core-node/README.md +6 -7
- package/claude/references/sd-simplysm14/apis/core-node/consola.md +17 -12
- package/claude/references/sd-simplysm14/apis/core-node/cpx.md +14 -13
- package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +9 -8
- package/claude/references/sd-simplysm14/apis/core-node/fsx.md +14 -13
- package/claude/references/sd-simplysm14/apis/core-node/pathx.md +4 -8
- package/claude/references/sd-simplysm14/apis/core-node/worker.md +14 -12
- package/claude/references/sd-simplysm14/apis/excel/README.md +22 -22
- package/claude/references/sd-simplysm14/apis/excel/cell.md +37 -29
- package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +29 -15
- package/claude/references/sd-simplysm14/apis/excel/style.md +33 -27
- package/claude/references/sd-simplysm14/apis/excel/utils.md +29 -19
- package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +78 -55
- package/claude/references/sd-simplysm14/apis/excel/wrapper.md +42 -45
- package/claude/references/sd-simplysm14/apis/orm-common/README.md +6 -8
- package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +118 -67
- package/claude/references/sd-simplysm14/apis/orm-common/expr.md +83 -86
- package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +102 -93
- package/claude/references/sd-simplysm14/apis/orm-common/schema.md +138 -81
- package/claude/references/sd-simplysm14/apis/orm-common/types.md +49 -44
- package/claude/references/sd-simplysm14/apis/orm-node/README.md +42 -42
- package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +44 -33
- package/claude/references/sd-simplysm14/apis/sd-cli/README.md +11 -10
- package/claude/references/sd-simplysm14/apis/service-client/README.md +56 -52
- package/claude/references/sd-simplysm14/apis/service-client/orm.md +33 -28
- package/claude/references/sd-simplysm14/apis/service-client/transport.md +23 -21
- package/claude/references/sd-simplysm14/apis/service-common/README.md +83 -48
- package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +126 -34
- package/claude/references/sd-simplysm14/apis/service-common/protocol.md +109 -54
- package/claude/references/sd-simplysm14/apis/service-server/README.md +69 -81
- package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +46 -43
- package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +63 -37
- package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +40 -30
- package/claude/references/sd-simplysm14/apis/storage/README.md +17 -17
- package/claude/references/sd-simplysm14/manuals/client-app-structure.md +142 -140
- package/claude/references/sd-simplysm14/manuals/client-orm.md +1 -1
- package/claude/references/sd-simplysm14/manuals/client-service.md +19 -7
- package/claude/references/sd-simplysm14/manuals/client-shared-data.md +2 -2
- package/claude/references/sd-simplysm14/manuals/client-system-log.md +11 -3
- package/claude/references/sd-simplysm14/manuals/data-log.md +0 -1
- package/claude/references/sd-simplysm14/manuals/orm.md +16 -0
- package/claude/rules/sd-design-rules.md +10 -0
- package/claude/skills/sd-demo/SKILL.md +0 -6
- package/claude/skills/sd-docs/SKILL.md +58 -0
- package/claude/{workflows/sd-docs.rules.md → skills/sd-docs/references/subagent-prompt.md} +103 -103
- package/claude/skills/sd-impl/SKILL.md +7 -4
- package/claude/skills/sd-spec/SKILL.md +842 -15
- package/claude/skills/sd-spec/references/example-spec.md +26 -36
- package/package.json +1 -1
- package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +0 -53
- package/claude/skills/sd-spec/references/spec-authoring.md +0 -519
- 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[]
|
|
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
|
-
|
|
21
|
-
expr.
|
|
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
|
-
|
|
13
|
+
연산 함수 인자는 대부분 `ExprInput<T>` — column 프록시(`ExprUnit`)·중첩 식·리터럴을 섞어 넘길 수 있다. `undefined` 컬럼 타입은 `.n` getter 로 non-null 단언 가능.
|
|
25
14
|
|
|
26
|
-
|
|
15
|
+
## ExprUnit / WhereExprUnit / ExprInput
|
|
27
16
|
|
|
28
|
-
- `
|
|
29
|
-
- `
|
|
30
|
-
- `
|
|
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
|
-
##
|
|
21
|
+
## 값 생성
|
|
33
22
|
|
|
34
|
-
- `
|
|
35
|
-
- `
|
|
36
|
-
- `
|
|
37
|
-
- `
|
|
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
|
-
|
|
42
|
-
|
|
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 — 논리 (
|
|
40
|
+
## WHERE — 논리 (반환 `WhereExprUnit`)
|
|
46
41
|
|
|
47
|
-
- `not(arg
|
|
48
|
-
- `and(conditions
|
|
49
|
-
- `or(conditions
|
|
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
|
|
54
|
-
- `left(source, length)` / `right(source, 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)` — 문자
|
|
60
|
-
- `substring(source, start, 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
|
-
|
|
58
|
+
## SELECT — 숫자 (반환 `ExprUnit`)
|
|
64
59
|
|
|
65
|
-
|
|
60
|
+
- `abs(source)` — 절대값. `round(source, digits)` — 반올림(소수 자릿수). `ceil(source)` — 올림. `floor(source)` — 내림.
|
|
66
61
|
|
|
67
|
-
|
|
62
|
+
## SELECT — 날짜 (반환 `ExprUnit`)
|
|
68
63
|
|
|
69
|
-
|
|
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
|
-
|
|
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
|
|
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 —
|
|
79
|
+
## SELECT — 집계 (반환 `ExprUnit`)
|
|
80
80
|
|
|
81
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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 —
|
|
87
|
+
## SELECT — 기타 (반환 `ExprUnit`)
|
|
95
88
|
|
|
96
|
-
|
|
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
|
-
|
|
99
|
-
- `sum(arg)` / `avg(arg)` — number 컬럼 합/평균(`number|undefined`).
|
|
100
|
-
- `max(arg)` / `min(arg)` — 임의 타입 최대/최소(`T|undefined`, 타입은 arg 따라감).
|
|
94
|
+
## SELECT — Window 함수 (반환 `ExprUnit`)
|
|
101
95
|
|
|
102
|
-
|
|
96
|
+
모두 `spec: { partitionBy?: ExprInput[]; orderBy?: [ExprInput, ("ASC"|"DESC")?][] }`(OVER 절) 인자를 받음.
|
|
103
97
|
|
|
104
|
-
- `
|
|
105
|
-
- `
|
|
106
|
-
- `
|
|
107
|
-
- `
|
|
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.
|
|
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
|
-
##
|
|
113
|
+
## SwitchExprBuilder
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
`expr.switch<T>()` 반환 객체.
|
|
116
116
|
|
|
117
|
-
- `
|
|
118
|
-
- `
|
|
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
|
-
|
|
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
|
|
3
|
+
`db.user()` 처럼 컨텍스트 멤버를 호출하면 `Queryable<TData, TFrom>` 를 받는다. immutable 체이닝으로 옵션·필터·조인·그룹을 쌓고, 종단 메서드(`execute`/`single`/`count`/`insert`/...)로 실행한다. where/select/orderBy 콜백 안에서는 [expr.md](./expr.md) 의 `expr` 로 표현식을 만든다. 프로시저는 `Executable`, 텍스트 검색 구문은 `parseSearchQuery` 가 처리한다.
|
|
4
4
|
|
|
5
|
-
`
|
|
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
|
-
##
|
|
7
|
+
## Queryable — 조립 메서드 (체이닝)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- `distinct(): Queryable` — DISTINCT 적용. count() 전이면 `wrap()` 필요.
|
|
11
|
-
- `lock(): Queryable` — FOR UPDATE 행 잠금. 트랜잭션 내 배타 잠금 획득용. `TFrom` 유지.
|
|
9
|
+
옵션:
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
15
|
+
제한:
|
|
18
16
|
|
|
19
|
-
- `top(count
|
|
20
|
-
- `limit(skip, take)
|
|
17
|
+
- `top(count)` — 상위 N행. ORDER BY 없이도 사용 가능.
|
|
18
|
+
- `limit(skip, take)` — 페이지네이션 OFFSET/LIMIT. `skip`=건너뛸 수, `take`=가져올 수. **먼저 `orderBy()` 호출 필수** — 없으면 throw.
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
정렬:
|
|
23
21
|
|
|
24
|
-
- `orderBy(fnOrKey, orderBy?)
|
|
22
|
+
- `orderBy(fnOrKey, orderBy?)` — 정렬 추가(여러 번 호출 시 순서대로). `fnOrKey`=정렬 column 반환 함수 또는 체인 경로 문자열(`"user.name"`, 동적 정렬용). `orderBy`="ASC"|"DESC"(기본 ASC).
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
db.user().orderBy((u) => u.name).orderBy((u) => u.age, "DESC").orderBy("id", "DESC")
|
|
28
|
-
```
|
|
24
|
+
필터:
|
|
29
25
|
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
- `search(fn: (cols) => ExprUnit<string|undefined>[], searchText): Queryable` — 텍스트 검색. `fn`=검색 대상 컬럼들. `searchText` 를 `parseSearchQuery` 로 파싱해 컬럼별 `LIKE lower(...)` 조건 생성(OR 묶음 + 필수 AND + 제외 NOT). `searchText` 가 공백이면 self 반환(조건 무추가).
|
|
29
|
+
그룹:
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
```
|
|
31
|
+
- `groupBy(fn)` — GROUP BY. `fn(columns) => ExprUnit<ColumnPrimitive>[]`. 호출 후 CUD 불가.
|
|
32
|
+
- `having(predicate)` — GROUP BY 이후 필터. `predicate(columns) => WhereExprUnit[]`.
|
|
38
33
|
|
|
39
|
-
|
|
34
|
+
조인:
|
|
40
35
|
|
|
41
|
-
- `
|
|
42
|
-
- `
|
|
36
|
+
- `join(as, fn)` — 1:N LEFT JOIN, 결과에 **배열**(`{ [as]?: R[] }`)로 부착. `fn(qr, parentCols) => Queryable` 안에서 `qr.from(Table).where(...)` 로 조인 본문 구성.
|
|
37
|
+
- `joinSingle(as, fn)` — N:1/1:1 LEFT JOIN, 결과에 **단일 객체**(`{ [as]?: R }`)로 부착. 도출 컬럼(집계·boolean)을 outer 행에 붙일 때 표준 수단.
|
|
38
|
+
- `include(fn)` — 빌더에 정의한 FK/FKT/RelationKey 관계 자동 JOIN. `fn(item) => item.user.company` 형태로 타입 안전 경로 지정(`PathProxy`, ColumnPrimitive 가 아닌 관계 필드만 접근 가능). 다단계·다중 호출 가능. 관계 미정의 시 throw.
|
|
43
39
|
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
+
- `recursive(fn)` — WITH RECURSIVE CTE 생성(계층 데이터). `fn(cte) => cte.from(Table).where(...)` 안에서 `e.self[0]` 로 직전 단계 행을 참조.
|
|
58
48
|
|
|
59
|
-
|
|
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
|
-
##
|
|
51
|
+
## Queryable — 종단 메서드
|
|
65
52
|
|
|
66
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
61
|
+
CUD(모두 `TFrom` 이 TableBuilder 일 때만 — `select`/`groupBy` 후엔 불가):
|
|
77
62
|
|
|
78
|
-
- `
|
|
79
|
-
- `
|
|
80
|
-
- `
|
|
81
|
-
- `
|
|
82
|
-
- `
|
|
83
|
-
- `
|
|
63
|
+
- `insert(records)` / `insert(records, outputColumns)` — 다건 INSERT. `records: $inferInsert[]`. MSSQL 1000행 제한 때문에 1000개씩 청크 분할. `outputColumns: K[]` 지정 시 삽입된 행에서 그 column 만 배열로 반환(AI/PK 값 회수). 빈 배열이면 no-op.
|
|
64
|
+
- `insertIfNotExists(record)` / `(record, outputColumns)` — 현재 WHERE 조건 매칭 행이 없을 때만 1건 INSERT. output 지정 시 삽입 행 1개 반환.
|
|
65
|
+
- `insertInto(targetTable)` / `(targetTable, outputColumns)` — 현재 SELECT 결과를 다른 테이블에 INSERT INTO ... SELECT. `targetTable` 의 column 구조가 현재 결과와 호환해야 함.
|
|
66
|
+
- `update(recordFwd)` / `(recordFwd, outputColumns)` — UPDATE. `recordFwd(cols) => QueryableWriteRecord<$inferUpdate>`(갱신할 column→값). 값은 `ExprInput`(리터럴 직접 가능, `expr.val` 불필요). output 지정 시 갱신 행 반환.
|
|
67
|
+
- `delete()` / `delete(outputColumns)` — DELETE. output 지정 시 삭제 행 반환.
|
|
68
|
+
- `upsert(updateFn)` / `upsert(insertFn, outputColumns?)` / `upsert(updateFn, insertFn)` / `upsert(updateFn, insertFn, outputColumns)` — WHERE 매칭 행 있으면 UPDATE, 없으면 INSERT(MERGE). `updateFn(cols) => 갱신값`, `insertFn(updateRecord) => 삽입값`(생략 시 update 값 재사용). output 지정 시 영향 행 반환.
|
|
84
69
|
|
|
85
|
-
|
|
70
|
+
QueryDef 생성기(실행 없이 AST 만): `getSelectQueryDef()`, `getInsertQueryDef(records, outputColumns?)`, `getInsertIfNotExistsQueryDef(...)`, `getInsertIntoQueryDef(...)`, `getUpdateQueryDef(...)`, `getDeleteQueryDef(...)`, `getUpsertQueryDef(...)`, `getResultMeta(outputColumns?)`. executor 우회·디버깅·배치 조립용.
|
|
86
71
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
- `
|
|
72
|
+
기타:
|
|
73
|
+
|
|
74
|
+
- `switchFk(enabled)` — 이 Queryable 의 소스 테이블 FK 제약을 활성/비활성. 트랜잭션 안에서 가능.
|
|
75
|
+
- `readonly meta` — 내부 조립 상태(from/where/joins/columns 등). 직접 수정하지 않음.
|
|
90
76
|
|
|
91
77
|
```typescript
|
|
92
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
98
|
-
- `delete([outputColumns])` — where 조건 행 삭제. `outputColumns` 시 삭제된 행 반환.
|
|
106
|
+
## Executable (프로시저 실행)
|
|
99
107
|
|
|
100
108
|
```typescript
|
|
101
|
-
|
|
109
|
+
class Executable<TParams, TReturns> {
|
|
110
|
+
getExecProcQueryDef(params?): ExecProcQueryDef;
|
|
111
|
+
execute(params): Promise<InferColumnExprs<TReturns>[][]>;
|
|
112
|
+
}
|
|
102
113
|
```
|
|
103
114
|
|
|
104
|
-
|
|
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.
|
|
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
|
-
|
|
122
|
+
## parseSearchQuery / ParsedSearchQuery
|
|
115
123
|
|
|
116
|
-
|
|
124
|
+
`search()` 가 내부로 쓰지만 직접 호출도 가능. 검색 구문 문자열을 SQL LIKE 패턴으로 분해.
|
|
117
125
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
131
|
+
- `or: string[]` — 일반 검색어(공백 구분, OR). 와일드카드 없으면 `%term%`(부분 일치).
|
|
132
|
+
- `must: string[]` — `+term` 또는 `"정확 구문"`(AND 필수).
|
|
133
|
+
- `not: string[]` — `-term`(NOT 제외).
|
|
134
|
+
- 와일드카드 `*` → `%`(`app*`→`app%` 시작 일치). 이스케이프: `\\` `\*` `\%` `\"` `\+` `\-` 는 리터럴. 닫히지 않은 `"` 는 일반 텍스트로 처리.
|
|
125
135
|
|
|
126
|
-
|
|
136
|
+
```typescript
|
|
137
|
+
parseSearchQuery('apple "delicious fruit" -banana +strawberry');
|
|
138
|
+
// { or: ["%apple%"], must: ["%delicious fruit%", "%strawberry%"], not: ["%banana%"] }
|
|
139
|
+
```
|
|
127
140
|
|
|
128
|
-
|
|
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
|
-
|
|
144
|
+
function getMatchedPrimaryKeys(fkCols: string[], targetTable: TableBuilder): string[];
|
|
134
145
|
```
|
|
135
146
|
|
|
136
|
-
|
|
147
|
+
- FK column 배열을 대상 테이블 PK 와 매칭해 PK column 이름 배열 반환. 개수 불일치 시 throw. `include()` 가 관계 조건을 만들 때 내부 사용 — 직접 호출은 드묾.
|
|
137
148
|
|
|
138
|
-
|
|
149
|
+
## 결과/입력 변환 타입
|
|
139
150
|
|
|
140
|
-
- `
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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` 경로 지정용 프록시 타입(관계 필드만 접근).
|