@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,6 +1,6 @@
|
|
|
1
1
|
# @simplysm/orm-node — 저수준 DB 연결
|
|
2
2
|
|
|
3
|
-
`createOrm` 추상화를 거치지 않고 raw SQL·파라미터 쿼리·bulk insert·수동 트랜잭션을 직접 다루거나, dialect별 접속 설정 타입을 작성하거나, `DbContext` 의 executor 를
|
|
3
|
+
`createOrm` 추상화를 거치지 않고 raw SQL·파라미터 쿼리·bulk insert·수동 트랜잭션을 직접 다루거나, dialect별 접속 설정 타입을 작성하거나, `DbContext` 의 executor 를 손수 구성할 때 함께 읽히는 묶음. 연결 인스턴스 생성·연결 인터페이스·접속 설정·executor·dialect 헬퍼·상수로 구성.
|
|
4
4
|
|
|
5
5
|
## createDbConn
|
|
6
6
|
|
|
@@ -17,19 +17,25 @@ await conn.connect();
|
|
|
17
17
|
|
|
18
18
|
## DbConn
|
|
19
19
|
|
|
20
|
-
저수준 연결 인터페이스. `EventEmitter<{ close: void }>` 를 상속하며 연결 종료 시 `close` 이벤트를 발생시킨다. 구현체 `MssqlDbConn`/`MysqlDbConn`/`PostgresqlDbConn`
|
|
20
|
+
저수준 연결 인터페이스. `EventEmitter<{ close: void }>` 를 상속하며 연결 종료 시 `close` 이벤트를 발생시킨다. 구현체 `MssqlDbConn`/`MysqlDbConn`/`PostgresqlDbConn` 은 직접 export 되지 않고 `createDbConn` 으로만 획득(타입 `DbConn` 만 import 가능).
|
|
21
21
|
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
22
|
+
- config: DbConnConfig — 이 연결의 접속 설정(읽기 전용). 어떤 dialect·DB 로 연결됐는지 확인용.
|
|
23
|
+
- isConnected: boolean — 현재 연결 여부. `connect` 성공 시 `true`, `close`/종료(`end`) 이벤트 시 `false`. 재연결 판단·정리 분기에 사용.
|
|
24
|
+
- isInTransaction: boolean — 트랜잭션 진행 여부. `beginTransaction` 후 `true`, 커밋·롤백 후 `false`. 중첩 방지·상태 확인에 사용.
|
|
25
|
+
- connect(): Promise\<void\> — 연결 수립. 이미 연결돼 있으면 `DB_CONN_ERRORS.ALREADY_CONNECTED` throw.
|
|
26
|
+
- close(): Promise\<void\> — 연결 종료. 미연결 상태면 아무 동작 없이 반환(throw 안 함). 재호출 안전.
|
|
27
|
+
- beginTransaction(isolationLevel?: IsolationLevel): Promise\<void\> — 트랜잭션 시작. `isolationLevel` 미지정 시 `config.defaultIsolationLevel`, 그것도 없으면 `READ_UNCOMMITTED` 로 시작. 테스트로 확인된 값: `"READ_UNCOMMITTED" | "READ_COMMITTED" | "REPEATABLE_READ" | "SERIALIZABLE"`. 더티 리드 차단이 필요하면 `READ_COMMITTED` 이상.
|
|
28
|
+
- commitTransaction(): Promise\<void\> — 진행 중 트랜잭션 커밋, `isInTransaction` 을 `false` 로.
|
|
29
|
+
- rollbackTransaction(): Promise\<void\> — 진행 중 트랜잭션 롤백, `isInTransaction` 을 `false` 로.
|
|
30
|
+
- execute(queries: string[]): Promise\<Record\<string, unknown\>[][]\> — 원시 SQL 문자열 배열을 순차 실행. 빈/공백 문자열은 건너뜀. 각 쿼리의 결과셋들을 평탄화해 하나의 배열로 묶어 반환. DDL·다건 SQL 일괄 실행에 사용.
|
|
31
|
+
- executeParametrized(query: string, params?: unknown[]): Promise\<Record\<string, unknown\>[][]\> — 파라미터 바인딩 쿼리 1건 실행. SQL 인젝션 회피·값 재바인딩 시. 반환은 결과셋 배열(MySQL 멀티스테이트먼트는 statement별로 분리되며 INSERT/UPDATE/DELETE 자리는 빈 배열, PostgreSQL 은 단일 결과셋). `params` 미지정 시 MSSQL 은 배치(`execSqlBatch`) 경로, 지정 시 `execSql` 파라미터 경로로 실행.
|
|
32
|
+
- bulkInsert(tableName: string, columnMetas: Record\<string, ColumnMeta\>, records: Record\<string, unknown\>[]): Promise\<void\> — 네이티브 bulk API 로 대량 삽입. `tableName` 은 `database.table` 또는 `database.schema.table`(dialect별 인용 부호 포함된 완전 수식명). `columnMetas` 는 컬럼명 → `ColumnMeta`(`dataType`·`nullable`) 매핑이며 **키 순서가 컬럼 순서를 결정**, `records` 의 각 객체는 이 키들로 값 추출. `records` 가 빈 배열이면 아무 동작 없이 반환. `DateTime`/`DateOnly`/`Time`/`Uuid`/`Uint8Array`/`null` 값은 dialect별로 적절히 변환됨.
|
|
33
|
+
|
|
34
|
+
dialect별 bulk insert 경로(서버 측 권한·설정에 의존):
|
|
35
|
+
|
|
36
|
+
- MSSQL — tedious `BulkLoad`. `ColumnMeta.dataType` 을 tedious 타입으로 매핑.
|
|
37
|
+
- MySQL — `LOAD DATA LOCAL INFILE`. 임시 CSV 파일을 생성·실행 후 삭제하며, UUID/binary 컬럼은 `UNHEX()` 로 복원.
|
|
38
|
+
- PostgreSQL — `COPY FROM STDIN`(CSV 스트림). binary 는 bytea hex 형식으로 인코딩.
|
|
33
39
|
|
|
34
40
|
```typescript
|
|
35
41
|
await conn.beginTransaction("READ_COMMITTED");
|
|
@@ -43,19 +49,23 @@ await conn.commitTransaction();
|
|
|
43
49
|
|
|
44
50
|
공통 필드:
|
|
45
51
|
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
52
|
+
- dialect — `"mysql" | "mssql" | "mssql-azure" | "postgresql"`. DBMS 선택. `"mssql-azure"` 는 Azure SQL 용으로 드라이버는 mssql 과 동일하되 연결 시 `encrypt` 가 활성화됨. 쿼리 빌더용 dialect 로는 `getDialectFromConfig` 가 `"mssql"` 로 정규화.
|
|
53
|
+
- host: string — 접속 호스트.
|
|
54
|
+
- port?: number — 포트. 미지정 시 드라이버 기본값(PostgreSQL 은 미지정 시 5432 로 보정).
|
|
55
|
+
- username: string — 인증 사용자.
|
|
56
|
+
- password: string — 인증 비밀번호.
|
|
57
|
+
- database?: string — 접속 기본 DB 이름.
|
|
58
|
+
- defaultIsolationLevel?: IsolationLevel — `beginTransaction` 의 `isolationLevel` 미지정 시 적용할 기본 격리 수준. 연결 단위로 기본 격리를 고정하고 싶을 때.
|
|
53
59
|
|
|
54
60
|
dialect별 추가 필드:
|
|
55
61
|
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
62
|
+
- MysqlDbConnConfig — `dialect: "mysql"`. 공통 필드만(스키마 개념 없음).
|
|
63
|
+
- MssqlDbConnConfig — `dialect: "mssql" | "mssql-azure"`. `schema?: string`(예 `dbo`) 추가.
|
|
64
|
+
- PostgresqlDbConnConfig — `dialect: "postgresql"`. `schema?: string`(예 `public`) 추가.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const cfg: MssqlDbConnConfig = { dialect: "mssql", host: "localhost", port: 21433, username: "sa", password: "...", database: "TestDb", schema: "dbo" };
|
|
68
|
+
```
|
|
59
69
|
|
|
60
70
|
## getDialectFromConfig
|
|
61
71
|
|
|
@@ -63,7 +73,7 @@ dialect별 추가 필드:
|
|
|
63
73
|
function getDialectFromConfig(config: DbConnConfig): Dialect
|
|
64
74
|
```
|
|
65
75
|
|
|
66
|
-
`config.dialect` 를 `@simplysm/orm-common` 의 `Dialect` 로 변환. `"mssql-azure"` → `"mssql"` 로 정규화하고 나머지는 그대로 반환. 쿼리 빌더의 dialect 결정에
|
|
76
|
+
`config.dialect` 를 `@simplysm/orm-common` 의 `Dialect` 로 변환. `"mssql-azure"` → `"mssql"` 로 정규화하고 나머지는 그대로 반환. 쿼리 빌더의 dialect 결정에 사용 — Azure 여부와 무관하게 같은 SQL 방언을 써야 할 때.
|
|
67
77
|
|
|
68
78
|
## NodeDbContextExecutor
|
|
69
79
|
|
|
@@ -73,19 +83,20 @@ new NodeDbContextExecutor(config: DbConnConfig)
|
|
|
73
83
|
|
|
74
84
|
`@simplysm/orm-common` 의 `DbContextExecutor` 를 Node 환경에서 구현한 클래스. `createOrm` 이 내부에서 자동 주입하므로 보통 직접 쓸 일은 없고, `DbContext` 를 `createOrm` 없이 수동 인스턴스화할 때만 사용. 생성자는 `DbConnConfig` 만 받고, 실제 연결은 `connect()` 시점에 `createDbConn` 으로 지연 생성한다.
|
|
75
85
|
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
86
|
+
- constructor(config: DbConnConfig) — `getDialectFromConfig` 로 dialect 를 결정해 보관. 이 시점엔 연결을 열지 않음.
|
|
87
|
+
- connect(): Promise\<void\> — `createDbConn(config)` 로 연결 생성 후 수립.
|
|
88
|
+
- close(): Promise\<void\> — 연결 종료 후 내부 참조 해제.
|
|
89
|
+
- beginTransaction(isolationLevel?: IsolationLevel) / commitTransaction() / rollbackTransaction() — 내부 `DbConn` 에 트랜잭션 제어 위임. `isolationLevel?` 의미는 위 `DbConn.beginTransaction` 과 동일.
|
|
90
|
+
- executeParametrized(query: string, params?: unknown[]): Promise\<Record\<string, unknown\>[][]\> — 파라미터 쿼리 위임.
|
|
91
|
+
- bulkInsert(tableName: string, columnMetas: Record\<string, ColumnMeta\>, records: DataRecord[]): Promise\<void\> — bulk insert 위임.
|
|
92
|
+
- executeDefs\<T\>(defs: QueryDef[], resultMetas?: (ResultMeta | undefined)[]): Promise\<T[][]\> — `QueryDef` 배열을 dialect 쿼리 빌더로 SQL 변환해 실행. `resultMetas` 가 전부 `null`/미지정이면(결과 불필요) 모든 def 를 하나의 SQL 로 합쳐 단일 요청으로 보내고 def 수만큼 빈 배열 반환(쓰기 전용 최적화, 인터페이스 계약 유지). 그 외엔 def 마다 개별 실행 후, 해당 위치에 `resultMeta` 가 있으면 `parseQueryResult` 로 타입 변환해 반환, 없으면 raw 결과셋 그대로 반환.
|
|
82
93
|
- 모든 실행 메서드는 미연결 상태에서 호출 시 `SdError(DB_CONN_ERRORS.NOT_CONNECTED)` throw.
|
|
83
94
|
|
|
84
95
|
## 상수
|
|
85
96
|
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
-
|
|
97
|
+
- DB_CONN_CONNECT_TIMEOUT — 연결 수립 타임아웃 `10 * 1000`(10초). 드라이버의 connect 타임아웃으로 전달.
|
|
98
|
+
- DB_CONN_DEFAULT_TIMEOUT — 쿼리 기본 타임아웃 `10 * 60 * 1000`(10분). 마지막 활동 후 이 값의 2배가 지나면 연결을 자동 종료(idle 타임아웃)하는 데도 사용.
|
|
99
|
+
- DB_CONN_ERRORS — 오류 메시지 상수 객체(`as const`). `NOT_CONNECTED`(미연결 상태에서 실행 시), `ALREADY_CONNECTED`(이미 연결된 상태에서 재연결 시). `expect(...).rejects.toThrow(DB_CONN_ERRORS.NOT_CONNECTED)` 처럼 throw 비교·메시지 매칭에 사용.
|
|
89
100
|
|
|
90
101
|
## 주의사항
|
|
91
102
|
|
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
# @simplysm/sd-cli
|
|
2
2
|
|
|
3
|
-
simplysm 빌드/배포 오케스트레이터 CLI. entry
|
|
3
|
+
simplysm 모노레포의 빌드/배포 오케스트레이터 CLI. entry(`src/index.ts`)가 외부로 노출하는 것은 CLI 실행 코드가 아니라 세 가지뿐: ① `sd.config.ts` 작성용 설정 타입군, ② 패키지 단위 TypeScript/Angular AOT 증분 컴파일러(`SdTsCompiler`)와 그 옵션·결과 타입, ③ Vitest 전용 Angular Vite 플러그인(`sdAngularPlugin`).
|
|
4
4
|
|
|
5
5
|
## 사용 트리거 인덱스
|
|
6
6
|
|
|
7
|
-
- **sd.config.ts 설정 타입** — 프로젝트 루트 `sd.config.ts` 를 작성·수정하며
|
|
8
|
-
- **SdTsCompiler / ISdTsCompilerOptions / ISdTsCompilerResult** — sd-cli 외부에서 패키지 단위 TS(또는 Angular AOT) 증분 컴파일을 직접 구동하거나 그 결과(emit·진단·lint·SCSS)를 다룰 때. 자세히: [SdTsCompiler.md](./SdTsCompiler.md)
|
|
9
|
-
- **sdAngularPlugin / SdAngularPluginOptions** — Vitest
|
|
7
|
+
- **sd.config.ts 설정 타입** — 프로젝트 루트 `sd.config.ts` 를 작성·수정하며 어떤 패키지를 어떤 타겟(`node`/`browser`/`neutral`/`client`/`server`/`scripts`)으로 빌드할지, 배포(npm/로컬/FTP)·Capacitor/Electron/PWA·서버 옵션을 지정할 때. `SdConfigFn` 으로 default export 함수 타입을 잡고 각 패키지를 타겟별 인터페이스로 채움. 자세히: [sd-config-types.md](./sd-config-types.md)
|
|
8
|
+
- **SdTsCompiler / ISdTsCompilerOptions / ISdTsCompilerResult / ISdTsCompilerEmitOptions** — sd-cli 외부에서 패키지 단위 TS(또는 Angular AOT) 증분 컴파일을 직접 구동하거나 그 결과(emit·진단·lint·SCSS)를 다룰 때. 자세히: [SdTsCompiler.md](./SdTsCompiler.md)
|
|
9
|
+
- **sdAngularPlugin / SdAngularPluginOptions** — Vitest 에서 Angular 패키지의 `.ts` 를 AOT 컴파일해 주입하는 Vite 플러그인을 설정할 때. 아래 인라인 섹션 참조.
|
|
10
10
|
|
|
11
11
|
## sdAngularPlugin
|
|
12
12
|
|
|
13
|
-
```
|
|
14
|
-
function sdAngularPlugin(options: SdAngularPluginOptions): Plugin
|
|
15
|
-
interface SdAngularPluginOptions { pkg: string }
|
|
13
|
+
```typescript
|
|
14
|
+
function sdAngularPlugin(options: SdAngularPluginOptions): Plugin; // vite Plugin 반환
|
|
15
|
+
interface SdAngularPluginOptions { pkg: string; }
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Angular AOT 컴파일을 수행하는
|
|
18
|
+
Angular AOT 컴파일을 수행하는 **Vitest 전용** Vite 플러그인(`vitest.config.ts` 의 `angular` project plugins 에 등록). 내부에서 `SdTsCompiler` 로 대상 패키지의 `.ts`(tests 포함, `includeTests: true`, `output: { js: true, dts: false }`)를 AOT 컴파일하고, Vite `transform` 훅에서 컴파일된 JS 를 반환한다. `enforce: "pre"` 로 다른 transform 보다 먼저 동작하며, 컴파일러가 만든 인라인 base64 소스맵을 분리해 Vite 호환 형태(`{ code, map }`)로 넘긴다. `compilerOptionsTransformer` 로 `noEmit:false`·`declaration:false`·`inlineSourceMap:true`·`rootDir = cwd` 를 강제하고, `buildEnd` 마다 내부 컴파일러를 폐기해 다음 watch 재빌드 때 재생성하며, watch 변경 파일을 모아 `buildStart` 의 캐시 무효화에 쓴다.
|
|
19
19
|
|
|
20
20
|
- pkg: string — 컴파일 대상 패키지 디렉토리명. `sd.config.ts` 의 `packages` 키와 동일(`@simplysm/` 접두사 제외한 짧은 이름, 예: `"angular"`). 플러그인은 `process.cwd()/packages/<pkg>` 를 컴파일 루트로 잡으므로, 테스트하려는 Angular 패키지명을 그대로 넣는다.
|
|
21
21
|
|
|
22
|
-
```
|
|
22
|
+
```typescript
|
|
23
23
|
// vitest.config.ts
|
|
24
24
|
import { sdAngularPlugin } from "@simplysm/sd-cli";
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
plugins: [sdAngularPlugin({ pkg: "angular" })];
|
|
26
27
|
```
|
|
@@ -1,36 +1,39 @@
|
|
|
1
1
|
# @simplysm/service-client
|
|
2
2
|
|
|
3
|
-
WebSocket
|
|
3
|
+
WebSocket 으로 서비스 서버(`@simplysm/service-server`)에 접속해 서비스 메서드 RPC 호출·서버 푸시 이벤트 구독/발행·파일 업/다운로드·서버측 ORM 원격 실행을 수행하는 클라이언트. 브라우저(DOM Worker)와 Node.js(글로벌 `WebSocket` 없으면 `ws` 로 polyfill, `worker_threads`) 양쪽에서 동작.
|
|
4
4
|
|
|
5
5
|
## 사용 트리거 인덱스
|
|
6
6
|
|
|
7
|
-
- **createServiceClient / ServiceClient** — 서버 접속, 서비스
|
|
7
|
+
- **createServiceClient / ServiceClient** — 서버 접속, 서비스 호출, 인증, 연결 상태 추적이 필요할 때. 이 패키지의 주 진입점. (아래 인라인 섹션)
|
|
8
8
|
- **ServiceConnectionOptions** — 클라이언트 생성 시 접속 대상(host/port/ssl)·재연결 정책을 정할 때. (아래 인라인 섹션)
|
|
9
9
|
- **getService / ServiceProxy** — 서버 서비스 인터페이스를 타입 안전 프록시로 호출할 때. (아래 인라인 섹션)
|
|
10
|
-
- **이벤트
|
|
11
|
-
- **파일 업/다운로드 (uploadFile / downloadFileBuffer / FileClient)** — 인증된 파일
|
|
10
|
+
- **이벤트 구독·발행 (addListener / removeListener / emitEvent / getEvent / ClientEventProxy / EventClient)** — 서버 푸시 이벤트를 구독·발행할 때. (아래 인라인 섹션)
|
|
11
|
+
- **파일 업/다운로드 (uploadFile / downloadFileBuffer / FileClient)** — 인증된 파일 업로드, 서버 상대경로 파일 다운로드 시. (아래 인라인 섹션)
|
|
12
12
|
- **진행률 (ServiceProgress / ServiceProgressState)** — 대용량 요청·응답의 청크 전송 진행률을 추적할 때. (아래 인라인 섹션)
|
|
13
|
-
- **환경 호환 타입·헬퍼 (BlobInput / FileCollection / BrowserWorker / isWorkerSupported 등)** — Node/browser 공용 코드에서 DOM 전용 타입 회피, Worker 지원
|
|
13
|
+
- **환경 호환 타입·헬퍼 (BlobInput / FileCollection / BrowserWorker / isWorkerSupported 등)** — Node/browser 공용 코드에서 DOM 전용 타입 회피, Worker 지원 분기 시. (아래 인라인 섹션)
|
|
14
14
|
- **ORM 원격 실행 (createOrmClientConnector / OrmClientConnector / OrmConnectOptions / OrmClientDbContextExecutor)** — 서버측 ORM DbContext 를 클라이언트에서 트랜잭션 단위로 실행할 때. 자세히: [orm.md](./orm.md)
|
|
15
|
-
- **저수준 전송 계층 (SocketProvider / ServiceTransport / ClientProtocolWrapper 및 create\*)** — 일반적으로 직접 쓰지 않음. `ServiceClient` 가 내부에서 조립.
|
|
15
|
+
- **저수준 전송 계층 (SocketProvider / ServiceTransport / ClientProtocolWrapper 및 create\*)** — 일반적으로 직접 쓰지 않음. `ServiceClient` 가 내부에서 조립. 소켓·하트비트·프로토콜·청크 동작을 이해해야 할 때. 자세히: [transport.md](./transport.md)
|
|
16
|
+
|
|
17
|
+
> 앱(Angular) 레이어에서는 `ServiceClient` 를 화면에서 직접 만들지 않고 `AppServiceProvider`(root provider) 의 `client` getter 를 경유해 서비스·이벤트·ORM 진입점을 모은다. 아래 예시는 client 직접 호출 형태로 보여주지만, 실제 앱 코드는 manuals/client-service.md·client-orm.md 의 provider 패턴을 따른다.
|
|
16
18
|
|
|
17
19
|
## 메인 클라이언트 (createServiceClient / ServiceClient)
|
|
18
20
|
|
|
19
21
|
`createServiceClient(name, options): ServiceClient` — 클라이언트 인스턴스 생성. `new ServiceClient(name, options)` 와 동일.
|
|
20
22
|
|
|
21
|
-
- name: string — 클라이언트 식별 이름. WebSocket 접속 쿼리의 `clientName`·파일 업로드 헤더 `x-sd-client-name` 으로 서버에 전달.
|
|
22
|
-
- options: ServiceConnectionOptions — 접속 대상·재연결 정책(아래 섹션).
|
|
23
|
+
- name: string — 클라이언트 식별 이름. WebSocket 접속 쿼리의 `clientName`·파일 업로드 헤더 `x-sd-client-name` 으로 서버에 전달. 서버 로그·연결 구분에 사용.
|
|
24
|
+
- options: ServiceConnectionOptions — 접속 대상·재연결 정책 (아래 섹션).
|
|
23
25
|
|
|
24
26
|
`ServiceClient` 는 `EventEmitter<ServiceClientEvents>` 를 상속하며 다음을 노출:
|
|
25
27
|
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
28
|
+
- name: string (readonly) — 생성 시 받은 클라이언트 이름.
|
|
29
|
+
- options: ServiceConnectionOptions (readonly) — 생성 시 받은 접속 옵션.
|
|
30
|
+
- connected: boolean (getter) — 현재 WebSocket 이 OPEN 상태인지. 재연결 중·종료 시 false. 이벤트 등록 가능 여부 판단에 사용 (`addListener` 는 false 면 throw).
|
|
31
|
+
- hostUrl: string (getter) — `http(s)://host:port` 형태의 HTTP 베이스 URL. `ssl` 이 true 면 https. 파일 업/다운로드가 이 URL 을 베이스로 사용.
|
|
32
|
+
- connect(): Promise\<void\> — 서버에 WebSocket 연결. 초기 연결 실패 시 throw. 통신(서비스 호출·이벤트 등록) 전에 반드시 1회 호출.
|
|
33
|
+
- close(): Promise\<void\> — 연결 수동 종료(이후 자동 재연결 안 함) 및 프로토콜 워커 자원 해제. 종료한 인스턴스는 재사용하지 말 것.
|
|
34
|
+
- send(serviceName, methodName, params, progress?): Promise\<unknown\> — 저수준 서비스 호출. `serviceName.methodName` 메시지를 보내고 응답 반환. 보통 `getService` 프록시로 간접 호출. progress 인자를 주지 않아도 client 의 `request/response/server-progress` 이벤트는 항상 발생.
|
|
35
|
+
- auth(token): Promise\<void\> — 인증 토큰 전송 후 내부 보관. 보관 토큰은 재연결 시 자동 재인증·파일 업로드 Bearer 인증에 재사용.
|
|
36
|
+
- getService / getEvent / addListener / removeListener / emitEvent / uploadFile / downloadFileBuffer — 아래 각 섹션 참조.
|
|
34
37
|
|
|
35
38
|
```ts
|
|
36
39
|
const client = createServiceClient("my-app", { host: "localhost", port: 50080, ssl: false });
|
|
@@ -40,10 +43,10 @@ await client.auth(jwtToken);
|
|
|
40
43
|
|
|
41
44
|
**ServiceClientEvents** (EventEmitter 이벤트):
|
|
42
45
|
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
46
|
+
- "request-progress": ServiceProgressState — 요청(업로드) 청크 진행률. 요청 본문이 청크 2개 이상으로 분할될 때만 발생.
|
|
47
|
+
- "response-progress": ServiceProgressState — 응답(다운로드) 청크 수신 진행률. 분할 응답 완료 시 100% 한 번 더 보고.
|
|
48
|
+
- "server-progress": ServiceProgressState — 서버가 처리 중 직접 보고하는 진행률(서버측 `progress` 메시지 수신 시).
|
|
49
|
+
- "state": "connected" | "closed" | "reconnecting" — 연결 상태 변화. "connected" = 연결/재연결 성공(이 전이 시 보관 토큰으로 자동 재인증 + 이벤트 리스너 자동 복구), "closed" = 정상 종료 또는 재연결 한도 초과, "reconnecting" = 재연결 시도 중. 오프라인 배너 토글 등에 사용.
|
|
47
50
|
|
|
48
51
|
```ts
|
|
49
52
|
client.on("state", (s) => { if (s === "reconnecting") showOfflineBanner(); });
|
|
@@ -55,62 +58,63 @@ client.on("state", (s) => { if (s === "reconnecting") showOfflineBanner(); });
|
|
|
55
58
|
|
|
56
59
|
- port: number — 서버 포트. 필수.
|
|
57
60
|
- host: string — 서버 호스트. 필수.
|
|
58
|
-
- ssl?: boolean — TLS 사용 여부. true 면 `wss`/`https`, false·미지정이면 `ws`/`http`. TLS 서버에 붙을
|
|
59
|
-
- maxReconnectCount?: number — 연결 끊김 시 최대 재연결 시도 횟수. 미지정 시 10.
|
|
61
|
+
- ssl?: boolean — TLS 사용 여부. true 면 `wss`/`https`, false·미지정이면 `ws`/`http`. TLS 서버에 붙을 때만 true.
|
|
62
|
+
- maxReconnectCount?: number — 연결 끊김 시 최대 재연결 시도 횟수. 미지정 시 10. 0 이면 재연결 비활성화(끊기면 즉시 포기). 테스트·단발성 연결이면 0.
|
|
60
63
|
|
|
61
64
|
## getService / ServiceProxy
|
|
62
65
|
|
|
63
66
|
서버 서비스 인터페이스의 각 메서드를 `Promise` 반환 함수로 노출하는 타입 안전 프록시.
|
|
64
67
|
|
|
65
|
-
`getService<TService>(serviceName): ServiceProxy<TService>` — `serviceName` 으로 등록된 서버 서비스의 프록시 반환. 프록시 메서드
|
|
68
|
+
`getService<TService>(serviceName): ServiceProxy<TService>` — `serviceName` 으로 등록된 서버 서비스의 프록시 반환. 프록시 메서드 호출은 내부적으로 `client.send(serviceName, 메서드명, 인자배열)` 로 위임.
|
|
66
69
|
|
|
67
|
-
- TService — 서버 서비스 메서드 인터페이스 타입. 컴파일 타임 시그니처 검증용(런타임 검증 아님).
|
|
68
|
-
-
|
|
70
|
+
- TService — 서버 서비스 메서드 인터페이스 타입. 컴파일 타임 시그니처 검증용(런타임 검증 아님). 앱에선 server 패키지가 export 한 `ServiceMethods<typeof XxxService>` 사용.
|
|
71
|
+
- serviceName: string — 서버의 `defineService("XxxName", ...)` 이름과 일치해야 함.
|
|
72
|
+
- ServiceProxy\<TService\> — TService 의 각 함수 멤버를 `(...args) => Promise<Awaited<R>>` 로 매핑. 함수 아닌 속성은 `never` 로 제외.
|
|
69
73
|
|
|
70
74
|
```ts
|
|
71
|
-
const svc = client.getService<
|
|
72
|
-
const result = await svc.echo("hi"); //
|
|
75
|
+
const svc = client.getService<TestServiceMethods>("TestService");
|
|
76
|
+
const result = await svc.echo("hi"); // 서버 TestService.echo("hi") 호출, Promise<string>
|
|
73
77
|
```
|
|
74
78
|
|
|
75
|
-
## 이벤트
|
|
79
|
+
## 이벤트 구독·발행 (addListener / removeListener / emitEvent / getEvent)
|
|
76
80
|
|
|
77
|
-
서버 푸시 이벤트는 `@simplysm/service-common` 의 `defineEvent` 산출물(`ServiceEventDef`. `$info` = 구독 필터 정보 타입, `$data` = 페이로드 타입) 단위로
|
|
81
|
+
서버 푸시 이벤트는 `@simplysm/service-common` 의 `defineEvent` 산출물(`ServiceEventDef`. `$info` = 구독 필터 정보 타입, `$data` = 페이로드 타입) 단위로 다룬다. 등록한 리스너는 재연결 시 자동 복구됨.
|
|
78
82
|
|
|
79
|
-
`addListener<TEventDef>(eventDef, info, cb): Promise<string>` — 리스너 등록.
|
|
83
|
+
`addListener<TEventDef>(eventDef, info, cb): Promise<string>` — 리스너 등록. 미연결(`connected === false`)이면 throw. 반환 key 로 나중에 제거.
|
|
80
84
|
|
|
81
|
-
- eventDef: TEventDef — 이벤트 정의(`defineEvent` 결과). `$info`/`$data`
|
|
82
|
-
- info: TEventDef["$info"] — 이 구독을 식별·필터링할
|
|
83
|
-
- cb: (data) => PromiseLike
|
|
85
|
+
- eventDef: TEventDef — 이벤트 정의(`defineEvent` 결과). `$info`/`$data` 타입의 출처.
|
|
86
|
+
- info: TEventDef["$info"] — 이 구독을 식별·필터링할 정보. 서버가 emit 대상 선별에 사용.
|
|
87
|
+
- cb: (data: $data) => PromiseLike\<void\> — 이벤트 수신 콜백. 콜백 내 예외는 로깅만 되고 호출부로 전파되지 않음.
|
|
84
88
|
|
|
85
|
-
`removeListener(key): Promise<void>` — 등록 key 로 리스너 제거. 서버 전송 실패(연결 끊김 등)
|
|
89
|
+
`removeListener(key): Promise<void>` — 등록 key 로 리스너 제거. 서버 전송 실패(연결 끊김 등)는 무시(서버가 끊김 시 리스너를 자동 정리하므로 안전).
|
|
86
90
|
|
|
87
91
|
`emitEvent<TEventDef>(eventDef, infoSelector, data): Promise<void>` — 이벤트 발행. 서버에서 동일 이벤트 구독자 목록을 조회한 뒤 `infoSelector(info)` 가 true 인 대상에게만 data 전송.
|
|
88
92
|
|
|
89
|
-
- infoSelector: (item: $info) => boolean — 발행 대상 구독자를 info 기준으로 필터.
|
|
93
|
+
- infoSelector: (item: $info) => boolean — 발행 대상 구독자를 info 기준으로 필터. true 반환 구독자에게만 전달.
|
|
90
94
|
- data: TEventDef["$data"] — 전송 페이로드.
|
|
91
95
|
|
|
92
|
-
`getEvent<TEventDef>(eventDef): ClientEventProxy<TEventDef>` — 특정 eventDef 에 바인딩된 프록시
|
|
96
|
+
`getEvent<TEventDef>(eventDef): ClientEventProxy<TEventDef>` — 특정 eventDef 에 바인딩된 프록시 반환. eventDef 를 매번 넘기지 않고 짧게 쓰려 할 때(앱의 `AppServiceProvider.xxxEvent` getter 패턴).
|
|
93
97
|
|
|
94
98
|
`ClientEventProxy<TEventDef>` 멤버: `addListener(info, cb)`, `removeListener(key)`, `emit(infoSelector, data)` — 위 client 메서드의 eventDef 고정판.
|
|
95
99
|
|
|
96
100
|
```ts
|
|
97
|
-
const
|
|
98
|
-
const key = await client.addListener(
|
|
99
|
-
await client.emitEvent(
|
|
101
|
+
const chatEvent = defineEvent<{ channel: string }, string>("Chat");
|
|
102
|
+
const key = await client.addListener(chatEvent, { channel: "room1" }, async (msg) => render(msg));
|
|
103
|
+
await client.emitEvent(chatEvent, (info) => info.channel === "room1", "hello");
|
|
100
104
|
await client.removeListener(key);
|
|
101
105
|
```
|
|
102
106
|
|
|
103
|
-
`EventClient` / `createEventClient(transport)` 는 `ServiceClient` 가 내부 조립에 쓰는 저수준 구현. 위
|
|
107
|
+
`EventClient` / `createEventClient(transport)` 는 `ServiceClient` 가 내부 조립에 쓰는 저수준 구현. 위 메서드에 더해 `resubscribeAll(): Promise<void>`(보관된 모든 리스너를 서버에 재등록, 재연결 복구용)를 가짐. 일반 사용에선 직접 만들지 않음.
|
|
104
108
|
|
|
105
109
|
## 파일 업/다운로드 (uploadFile / downloadFileBuffer)
|
|
106
110
|
|
|
107
|
-
`uploadFile(files): Promise<ServiceUploadResult[]>` — `POST <hostUrl>/upload` (multipart) 로 파일 업로드. 보관 토큰을 `Authorization: Bearer`
|
|
111
|
+
`uploadFile(files): Promise<ServiceUploadResult[]>` — `POST <hostUrl>/upload` (multipart/form-data) 로 파일 업로드. 보관 토큰을 `Authorization: Bearer` 헤더로 전송하므로 사전 `auth()` 필수(미인증 시 throw). 응답 비정상 시 throw.
|
|
108
112
|
|
|
109
|
-
- files: `File[] | FileCollection | { name: string; data: BlobInput }[]` — 업로드 대상. 브라우저 `File` 배열, `FileCollection`(FileList 호환), 또는 `{ name, data }` 커스텀 객체 배열. 커스텀 객체의 data 가 `Blob` 이 아니면 `new Blob([data])` 로
|
|
113
|
+
- files: `File[] | FileCollection | { name: string; data: BlobInput }[]` — 업로드 대상. 브라우저 `File` 배열, `FileCollection`(FileList 호환), 또는 `{ name, data }` 커스텀 객체 배열. 커스텀 객체의 data 가 `Blob` 이 아니면 `new Blob([data])` 로 감싸 전송.
|
|
110
114
|
|
|
111
115
|
`downloadFileBuffer(relPath): Promise<Bytes>` — `<hostUrl>/<relPath>` 를 GET 해 `Uint8Array` 반환. 응답 비정상(`!res.ok`) 시 throw.
|
|
112
116
|
|
|
113
|
-
- relPath: string — 서버 기준 상대경로. 선행 `/` 유무 모두 허용(없으면
|
|
117
|
+
- relPath: string — 서버 기준 상대경로. 선행 `/` 유무 모두 허용(없으면 자동으로 `/` 추가).
|
|
114
118
|
|
|
115
119
|
```ts
|
|
116
120
|
await client.auth(token);
|
|
@@ -124,9 +128,9 @@ const bytes = await client.downloadFileBuffer("/files/a.txt");
|
|
|
124
128
|
|
|
125
129
|
대용량 요청/응답이 청크로 분할될 때 진행 상황을 보고하는 콜백·상태 타입.
|
|
126
130
|
|
|
127
|
-
`ServiceProgress` — `send(..., progress)` 에 넘기는 콜백 집합(
|
|
131
|
+
`ServiceProgress` — `send(..., progress)` 에 넘기는 콜백 집합(해당 호출 단건 추적용, 전역 이벤트와 별개). 각 콜백은 `(s: ServiceProgressState) => void`:
|
|
128
132
|
|
|
129
|
-
- request? — 요청 청크 업로드 진행 시
|
|
133
|
+
- request? — 요청 청크 업로드 진행 시 호출. 요청 청크가 2개 이상일 때만 발생.
|
|
130
134
|
- response? — 응답 청크 수신 진행 시 호출. 분할 응답이었으면 완료 시 100%(`completedSize === totalSize`)를 한 번 더 보고.
|
|
131
135
|
- server? — 서버가 처리 중 보고한 진행률 수신 시 호출(`name: "progress"` 메시지).
|
|
132
136
|
|
|
@@ -136,15 +140,15 @@ const bytes = await client.downloadFileBuffer("/files/a.txt");
|
|
|
136
140
|
- totalSize: number — 전체 바이트 수.
|
|
137
141
|
- completedSize: number — 완료 바이트 수. `completedSize === totalSize` 면 완료.
|
|
138
142
|
|
|
139
|
-
|
|
143
|
+
전역 추적이면 콜백 대신 ServiceClient 의 `request/response/server-progress` 이벤트(`client.on("response-progress", ...)`)를 써도 됨 — `send` 는 progress 인자 유무와 무관하게 이 이벤트들을 항상 발생시킴.
|
|
140
144
|
|
|
141
145
|
## 환경 호환 타입·헬퍼 (browser-compat)
|
|
142
146
|
|
|
143
147
|
Node/browser 공용 코드에서 DOM 전용 타입을 피하고 Worker 지원 여부를 분기하기 위한 타입·함수. 프로토콜 인코딩/파싱 Worker 오프로딩 판단 시 내부에서 사용.
|
|
144
148
|
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
149
|
+
- BlobInput = `Blob | Uint8Array<ArrayBuffer> | ArrayBuffer | string` — `Blob` 생성자가 받는 데이터 타입(DOM `BlobPart` 대체). `uploadFile` 의 커스텀 객체 data 타입.
|
|
150
|
+
- FileCollection (interface) — DOM `FileList` 대체. `length`, `item(index): File | null`, 인덱스 접근, `[Symbol.iterator]` 보유. 브라우저 `FileList` 와 구조적 호환.
|
|
151
|
+
- BrowserWorker (interface) — DOM `Worker` 최소 인터페이스(`onmessage`/`onerror` 핸들러, `postMessage(message, transfer?)`, `terminate()`). DOM lib 없이 타입체크 통과용.
|
|
152
|
+
- isBrowserWorkerSupported(): boolean — `globalThis` 에 `Worker` 존재 여부. 브라우저 DOM Worker 가용 판단.
|
|
153
|
+
- isNodeWorkerSupported(): boolean — `process.versions.node` 존재 여부. Node `worker_threads` 가용 판단.
|
|
154
|
+
- isWorkerSupported(): boolean — 위 둘 중 하나라도 true. 프로토콜 인코딩/파싱 오프로딩 가능 여부 판단(미지원 시 메인 스레드 폴백).
|
|
@@ -1,48 +1,53 @@
|
|
|
1
1
|
# @simplysm/service-client — ORM 원격 실행
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
서버측 ORM DbContext 를 클라이언트에서 원격으로 실행하는 묶음. 쿼리 자체는 서버 `Orm` 서비스가 DB 에 대해 수행하고, 클라이언트는 커넥션·트랜잭션·쿼리 정의(`QueryDef`)만 RPC 로 전송한다. 쿼리는 `connect`/`connectWithoutTransaction` 콜백 내부에서만 가능. ORM 작업을 트랜잭션 경계로 묶어 실행할 때 함께 읽힌다.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> 앱(Angular)에서는 `OrmConnectOptions`(DbClass·connOpt·dbContextOpt)를 화면에 흩뿌리지 않고 `AppOrmProvider`(root provider) 한 곳에 고정한 뒤 `connectAsync`/`connectWithoutTransAsync` 만 호출한다. 쿼리 작성법은 apis/orm-common 참조. 아래 예시는 connector 직접 호출 형태지만 실제 앱은 manuals/client-orm.md 의 provider 패턴을 따른다.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## createOrmClientConnector / OrmClientConnector
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- connOpt: `DbConnOptions & { configName: string }` — 서버측 DB 접속 설정. `configName` = 서버에 등록된 DB 설정 이름(서버가 이 이름으로 실제 접속 정보를 찾음).
|
|
11
|
-
- dbContextOpt?: `{ database: string; schema: string }` — DbContext 가 쓸 데이터베이스/스키마 명시. 미지정 시 서버 `getInfo()` 가 돌려주는 기본 database/schema 사용. `database` 가 dbContextOpt·서버 양쪽에서 모두 비면 `"database는 필수입니다."` throw(결측을 임의 보정하지 않음).
|
|
9
|
+
`createOrmClientConnector(serviceClient): OrmClientConnector` — 주어진 `ServiceClient` 위에 ORM 커넥터를 만든다. 내부에서 `serviceClient.getService<OrmService>("Orm")` 프록시로 서버 ORM 서비스를 호출하므로, 사용 전 `ServiceClient.connect()` 로 소켓이 연결돼 있어야 함.
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
`OrmClientConnector` 메서드:
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
- connect<T, R>(config, callback): Promise\<R\> — 트랜잭션 안에서 callback 실행. DbContext 생성 → `db.connect(...)`(connect + beginTransaction + callback + commit/rollback) 수행. callback 정상 반환 시 커밋(반환값이 그대로 반환됨), throw 시 롤백되어 콜백 내 다건 작업이 원자 처리됨. callback 에서 발생한 에러 중 외래키 제약 위반 메시지(`a parent row: a foreign key constraint` / `conflicted with the REFERENCE`)는 "경고! 연관된 작업으로 인해 작업이 거부되었습니다. 후속 작업을 확인해 주세요." 로 감싸 throw(원본은 `cause` 에 보존), 그 외 에러는 그대로 throw.
|
|
14
|
+
- connectWithoutTransaction<T, R>(config, callback): Promise\<R\> — 트랜잭션 없이 callback 실행(`db.connectWithoutTransaction`). 트랜잭션 안에서 동작하지 않는 작업(예: DB initialize)·조회 전용 작업에 사용. callback 반환값이 그대로 반환됨.
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
공통 인자:
|
|
18
17
|
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
- config: `OrmConnectOptions<T>` — 위 옵션. callback: `(db: T) => Promise<R> | R` — DbContext 를 받아 쿼리하는 콜백.
|
|
18
|
+
- config: OrmConnectOptions\<T\> — 아래 섹션. DbContext 클래스·서버 ORM 설정·DB명/스키마.
|
|
19
|
+
- callback: (db: T) => Promise\<R\> | R — 생성·연결된 DbContext 를 받아 쿼리하는 콜백. 동기/비동기 반환 모두 허용.
|
|
22
20
|
|
|
23
21
|
```ts
|
|
24
22
|
const connector = createOrmClientConnector(client);
|
|
25
|
-
await connector.connect(
|
|
26
|
-
{ DbClass:
|
|
27
|
-
async (db) => {
|
|
28
|
-
await db.foo.insertAsync({ /* ... */ });
|
|
29
|
-
return db.foo.where(/* ... */).resultAsync();
|
|
30
|
-
},
|
|
23
|
+
const rows = await connector.connect(
|
|
24
|
+
{ DbClass: MainDbContext, connOpt: { configName: "MAIN" }, dbContextOpt: { database: "mydb" } },
|
|
25
|
+
async (db) => db.order().select((item) => ({ id: item.id })).execute(),
|
|
31
26
|
); // 콜백 throw 시 자동 롤백
|
|
32
27
|
```
|
|
33
28
|
|
|
29
|
+
## OrmConnectOptions
|
|
30
|
+
|
|
31
|
+
`connect`/`connectWithoutTransaction` 의 첫 인자. DbContext 1회 실행에 필요한 설정.
|
|
32
|
+
|
|
33
|
+
- DbClass: `new (executor, opt) => T` — 실행할 DbContext 클래스 생성자. `executor`(아래 `OrmClientDbContextExecutor`)와 `{ database, schema? }` 를 받아 인스턴스화됨. 앱별 DbContext(예: `MainDbContext`)를 그대로 전달.
|
|
34
|
+
- connOpt: `DbConnOptions & { configName: string }` — 서버측 ORM 연결 설정. `configName` 은 서버에 등록된 ORM 설정 이름(서버가 이 이름으로 실제 DB 접속 정보를 찾음). 나머지 필드는 `@simplysm/service-common` 의 `DbConnOptions`.
|
|
35
|
+
- dbContextOpt?: `{ database: string; schema?: string }` — DbContext 에 적용할 DB명·스키마. 생략 시 서버 `getInfo()` 가 돌려준 `database`/`schema` 를 사용. database 가 옵션·서버 양쪽 모두 비어 있으면 throw("database는 필수입니다." — 결측을 임의 보정하지 않음).
|
|
36
|
+
- database: string — 대상 데이터베이스명.
|
|
37
|
+
- schema?: string — 대상 스키마명(미지정 시 서버 기본값).
|
|
38
|
+
|
|
34
39
|
## OrmClientDbContextExecutor
|
|
35
40
|
|
|
36
|
-
`DbContextExecutor`(`@simplysm/orm-common`) 구현체. 모든 메서드를 `
|
|
41
|
+
`DbContextExecutor`(`@simplysm/orm-common`) 구현체. 모든 메서드를 `client.getService<OrmService>("Orm")` RPC 로 위임. 커넥터가 내부에서 DbContext 에 주입하므로 직접 생성·호출은 보통 불필요.
|
|
37
42
|
|
|
38
43
|
`new OrmClientDbContextExecutor(client, opt)` — 생성. opt = `DbConnOptions & { configName: string }`. 생성 시 `Orm` 서비스 프록시 확보.
|
|
39
44
|
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
45
|
+
- getInfo(): `Promise<{ dialect; database?; schema? }>` — 서버 ORM 설정 정보(방언·기본 DB명·스키마) 조회. 커넥터가 dbContextOpt 미지정 시 fallback 으로 사용.
|
|
46
|
+
- connect(): Promise\<void\> — 서버에 커넥션 생성, 반환된 connId 를 내부 보관. 이후 트랜잭션·쿼리 호출의 핸들. 이후 모든 실행 메서드는 connId 없으면(미연결) throw.
|
|
47
|
+
- beginTransaction(isolationLevel?): Promise\<void\> — 트랜잭션 시작. isolationLevel(orm-common `IsolationLevel`) 미지정 시 서버 기본값.
|
|
48
|
+
- commitTransaction(): Promise\<void\> — 트랜잭션 커밋.
|
|
49
|
+
- rollbackTransaction(): Promise\<void\> — 트랜잭션 롤백.
|
|
50
|
+
- close(): Promise\<void\> — 커넥션 종료 후 보관 connId 해제.
|
|
51
|
+
- executeDefs\<T\>(defs, options?): Promise\<T[][]\> — 쿼리 정의 배열(`QueryDef[]`)을 서버에서 실행. options 는 정의별 결과 매핑 메타(`(ResultMeta | undefined)[]`, 항목별 nullable)로 행 역직렬화 방식 지정. 정의 1개당 결과 배열 1개를 가진 2차원 배열 반환.
|
|
52
|
+
- executeParametrized(query, params?): Promise\<unknown[][]\> — 파라미터 바인딩 raw SQL 실행. query = SQL 문자열, params = 바인딩 값 배열.
|
|
53
|
+
- bulkInsert(tableName, columnDefs, records): Promise\<void\> — 대량 삽입. columnDefs(`Record<string, ColumnMeta>`)로 컬럼별 메타를, records 로 삽입할 행 객체 배열을 전달.
|
|
@@ -1,36 +1,38 @@
|
|
|
1
1
|
# @simplysm/service-client — 저수준 전송 계층
|
|
2
2
|
|
|
3
|
-
`ServiceClient` 가 생성자에서 내부적으로 조립하는 저수준 모듈들. WebSocket 연결·하트비트·재연결(SocketProvider), 요청/응답 매칭과 메시지 디스패치(ServiceTransport), 인코딩/디코딩의 Worker 오프로딩(ClientProtocolWrapper). 일반 사용에서는 `ServiceClient` 만 쓰면
|
|
3
|
+
`ServiceClient` 가 생성자에서 내부적으로 조립하는 저수준 모듈들. WebSocket 연결·하트비트·재연결(SocketProvider), 요청/응답 매칭과 메시지 디스패치(ServiceTransport), 인코딩/디코딩의 Worker 오프로딩(ClientProtocolWrapper). 일반 사용에서는 `ServiceClient` 만 쓰면 되며, 이 계층은 소켓·청크·하트비트 동작을 이해해야 할 때만 읽는다.
|
|
4
4
|
|
|
5
5
|
## SocketProvider / createSocketProvider
|
|
6
6
|
|
|
7
|
-
WebSocket 1개의 연결·하트비트·자동
|
|
7
|
+
WebSocket 1개의 연결·하트비트·자동 재연결 담당.
|
|
8
8
|
|
|
9
|
-
`createSocketProvider(url, clientName, maxReconnectCount): SocketProvider` — 프로바이더 생성.
|
|
9
|
+
`createSocketProvider(url, clientName, maxReconnectCount): SocketProvider` — 프로바이더 생성. Node 환경에서 글로벌 `WebSocket` 이 없으면 모듈 로드 시 `ws` 패키지로 polyfill.
|
|
10
10
|
|
|
11
11
|
- url: string — `ws(s)://host:port/ws`. 접속 시 `ver=2`, 생성된 `clientId`(UUID), `clientName` 쿼리를 붙임.
|
|
12
12
|
- clientName: string — 접속 쿼리에 실리는 식별명.
|
|
13
|
-
- maxReconnectCount: number — 최대 재연결
|
|
13
|
+
- maxReconnectCount: number — 최대 재연결 시도 횟수. 0 이면 재연결 안 함.
|
|
14
14
|
|
|
15
15
|
내부 상수: 하트비트 ping 5초 간격, 30초 무수신 시 타임아웃, 재연결 3초 간격. 1바이트 `0x01` ping 전송, `0x02` pong 수신은 무시(타임스탬프만 갱신).
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
17
|
+
멤버:
|
|
18
|
+
|
|
19
|
+
- clientName: string (readonly) — 생성 시 받은 식별명.
|
|
20
|
+
- connected: boolean (getter) — 소켓이 OPEN 상태인지.
|
|
21
|
+
- connect(): Promise\<void\> — 접속 시작. 실패 시 throw, 성공 시 재연결 카운트 리셋하고 `state: "connected"` emit.
|
|
22
|
+
- close(): Promise\<void\> — 수동 종료. 이후 자동 재연결 안 함. 소켓 CLOSED 까지 대기 후 `state: "closed"` emit.
|
|
23
|
+
- send(data: Bytes): Promise\<void\> — 바이트 전송. 일정 시간 내 미연결이면 throw("서버에 연결되지 않았습니다...").
|
|
24
|
+
- on(type, listener) / off(type, listener) — 이벤트 구독/해제.
|
|
23
25
|
|
|
24
26
|
`SocketProviderEvents`:
|
|
25
27
|
|
|
26
28
|
- message: Bytes — 수신 바이트(ping/pong 1바이트 제어 프레임 제외).
|
|
27
|
-
- state:
|
|
29
|
+
- state: "connected" | "closed" | "reconnecting" — 연결 상태 변화. "connected" = 연결/재연결 성공, "closed" = 수동 종료 또는 재연결 한도 초과, "reconnecting" = 재연결 시도 중.
|
|
28
30
|
|
|
29
|
-
하트비트 타임아웃 감지 시 소켓을 강제 정리하고(늦은 onclose 로 인한 중복 재연결
|
|
31
|
+
하트비트 타임아웃 감지 시 소켓을 강제 정리하고(늦은 onclose 로 인한 중복 재연결 방지를 위해 핸들러 해제) 수동 종료가 아니면 재연결을 시도. 최대 시도 초과 시 `state: "closed"` emit.
|
|
30
32
|
|
|
31
33
|
## ServiceTransport / createServiceTransport
|
|
32
34
|
|
|
33
|
-
요청별 uuid 매칭, 응답/에러/진행률/서버이벤트
|
|
35
|
+
요청별 uuid 매칭, 응답/에러/진행률/서버이벤트 디스패치 담당.
|
|
34
36
|
|
|
35
37
|
`createServiceTransport(socket, protocol): ServiceTransport` — 트랜스포트 생성. 소켓 `message` 를 받아 decode 후 종류별 분기. 소켓이 `closed`/`reconnecting` 되면 대기 중인 모든 요청을 reject(메모리 해제).
|
|
36
38
|
|
|
@@ -39,21 +41,21 @@ WebSocket 1개의 연결·하트비트·자동 재연결을 담당.
|
|
|
39
41
|
|
|
40
42
|
멤버:
|
|
41
43
|
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
+
- send(message, progress?): Promise\<unknown\> — 요청 1건 전송 후 응답 Promise 반환. uuid 생성 → 리스너 등록 → encode → 청크 순차 전송. 응답(`response`) 수신 시 resolve, 에러(`error`) 수신 시 서버 에러 필드를 머지한 `Error` 로 reject. message = `ServiceClientMessage`, progress = `ServiceProgress`(선택).
|
|
45
|
+
- on(type, listener) / off(type, listener) — 이벤트 구독/해제.
|
|
44
46
|
|
|
45
47
|
`ServiceTransportEvents`:
|
|
46
48
|
|
|
47
49
|
- event: `{ keys: string[]; data: unknown }` — 서버가 푸시한 `evt:on` 메시지. `EventClient` 가 이걸 구독해 keys 에 매칭되는 로컬 리스너로 디스패치.
|
|
48
50
|
|
|
49
|
-
decode 실패 시에도 헤더 16바이트에서 uuid 를 선추출해 해당 요청만 reject. 분할 응답이면 완료 시 `progress.response` 로 100% 를 한 번 더 보고.
|
|
51
|
+
decode 실패 시에도 헤더 16바이트에서 uuid 를 선추출해 해당 요청만 reject. 분할 응답이면 완료 시 `progress.response` 로 100% 를 한 번 더 보고. 서버측 진행 메시지(`name: "progress"`)는 `progress.server` 로 전달.
|
|
50
52
|
|
|
51
53
|
## ClientProtocolWrapper / createClientProtocolWrapper
|
|
52
54
|
|
|
53
|
-
인코드/디코드를 크기 기준으로 Worker 에 오프로딩하는 래퍼. `@simplysm/service-common` 의 `ServiceProtocol` 을 감쌈.
|
|
55
|
+
인코드/디코드를 크기 기준으로 Worker 에 오프로딩하는 래퍼. `@simplysm/service-common` 의 `ServiceProtocol` 을 감쌈. Worker 미가용·임계값(30KB) 이하면 메인 스레드 처리로 폴백.
|
|
54
56
|
|
|
55
|
-
`createClientProtocolWrapper(protocol): ClientProtocolWrapper` — 래퍼 생성.
|
|
57
|
+
`createClientProtocolWrapper(protocol): ClientProtocolWrapper` — 래퍼 생성.
|
|
56
58
|
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
59
|
+
- encode(uuid, message): Promise\<{ chunks: Bytes[]; totalSize: number }\> — 메시지를 청크 배열로 인코드. body 가 Uint8Array, 30KB 초과 문자열, 길이 100 초과 배열, 또는 첫 항목이 Uint8Array 인 배열이면 Worker 사용. message = `ServiceMessage`.
|
|
60
|
+
- decode(bytes): Promise\<ServiceMessageDecodeResult\<ServiceMessage\>\> — 수신 바이트 디코드. 청크 재조립(stateful)은 한 메시지의 청크가 흩어지지 않도록 항상 메인 스레드 단일 누적기에서 수행하고(#35), 재조립 완료 후 30KB 초과 JSON 파싱(stateless)만 Worker 에 위임. 미완료(progress) 상태면 그대로 반환.
|
|
61
|
+
- dispose(): void — 프로토콜과 Worker 리졸버 정리. `ServiceClient.close()` 에서 호출.
|