@simplysm/sd-claude 14.0.86 → 14.0.88
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 -18
- package/claude/references/sd-simplysm14/apis/angular/README.md +61 -0
- package/claude/references/sd-simplysm14/apis/angular/controls.md +119 -0
- package/claude/references/sd-simplysm14/apis/angular/crud.md +50 -0
- package/claude/references/sd-simplysm14/apis/angular/directives.md +44 -0
- package/claude/references/sd-simplysm14/apis/angular/features.md +55 -0
- package/claude/references/sd-simplysm14/apis/angular/infra.md +74 -0
- package/claude/references/sd-simplysm14/apis/angular/layout.md +55 -0
- package/claude/references/sd-simplysm14/apis/angular/overlay.md +115 -0
- package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +64 -0
- package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +43 -0
- package/claude/references/sd-simplysm14/apis/angular/shared-data.md +70 -0
- package/claude/references/sd-simplysm14/apis/angular/sheet.md +78 -0
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +80 -0
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +66 -0
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +71 -0
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +67 -0
- package/claude/references/sd-simplysm14/apis/core-browser/README.md +83 -0
- package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +79 -0
- package/claude/references/sd-simplysm14/apis/core-common/README.md +138 -0
- package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +72 -0
- package/claude/references/sd-simplysm14/apis/core-common/datetime.md +95 -0
- package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +47 -0
- package/claude/references/sd-simplysm14/apis/core-common/obj.md +53 -0
- package/claude/references/sd-simplysm14/apis/core-node/README.md +14 -0
- package/claude/references/sd-simplysm14/apis/core-node/consola.md +51 -0
- package/claude/references/sd-simplysm14/apis/core-node/cpx.md +39 -0
- package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +38 -0
- package/claude/references/sd-simplysm14/apis/core-node/fsx.md +86 -0
- package/claude/references/sd-simplysm14/apis/core-node/pathx.md +42 -0
- package/claude/references/sd-simplysm14/apis/core-node/worker.md +54 -0
- package/claude/references/sd-simplysm14/apis/excel/README.md +43 -0
- package/claude/references/sd-simplysm14/apis/excel/cell.md +54 -0
- package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +51 -0
- package/claude/references/sd-simplysm14/apis/excel/style.md +67 -0
- package/claude/references/sd-simplysm14/apis/excel/utils.md +35 -0
- package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +97 -0
- package/claude/references/sd-simplysm14/apis/excel/wrapper.md +83 -0
- package/claude/references/sd-simplysm14/apis/lint/README.md +43 -0
- package/claude/references/sd-simplysm14/apis/lint/rules.md +90 -0
- package/claude/references/sd-simplysm14/apis/orm-common/README.md +67 -0
- package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +80 -0
- package/claude/references/sd-simplysm14/apis/orm-common/expr.md +113 -0
- package/claude/references/sd-simplysm14/apis/orm-common/query-builder.md +29 -0
- package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +111 -0
- package/claude/references/sd-simplysm14/apis/orm-common/schema.md +162 -0
- package/claude/references/sd-simplysm14/apis/orm-common/types.md +52 -0
- package/claude/references/sd-simplysm14/apis/orm-node/README.md +53 -0
- package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +94 -0
- package/claude/references/sd-simplysm14/apis/sd-cli/README.md +29 -0
- package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +70 -0
- package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +173 -0
- package/claude/references/sd-simplysm14/apis/service-client/README.md +152 -0
- package/claude/references/sd-simplysm14/apis/service-client/orm.md +45 -0
- package/claude/references/sd-simplysm14/apis/service-client/transport.md +36 -0
- package/claude/references/sd-simplysm14/apis/service-common/README.md +70 -0
- package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +48 -0
- package/claude/references/sd-simplysm14/apis/service-common/protocol.md +72 -0
- package/claude/references/sd-simplysm14/apis/service-server/README.md +102 -0
- package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +74 -0
- package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +51 -0
- package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +50 -0
- package/claude/references/sd-simplysm14/apis/storage/README.md +114 -0
- package/claude/rules/sd-design-rules.md +11 -0
- package/claude/skills/sd-docs/SKILL.md +17 -29
- package/claude/skills/sd-docs/references/{subagent-prompt.md → doc-rules.md} +25 -40
- package/claude/skills/sd-spec/SKILL.md +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @simplysm/capacitor-plugin-usb-storage
|
|
2
|
+
|
|
3
|
+
USB Mass Storage 장치를 읽는 Capacitor 플러그인. Android 는 libaums 로 실제 USB 저장소에 접근하고, 브라우저(web)는 IndexedDB 기반 가상 USB 저장소로 에뮬레이션함. 외부 노출 심볼은 정적 클래스 `UsbStorage`, 타입 정의 3종, 네이티브 인터페이스 `UsbStoragePlugin`.
|
|
4
|
+
|
|
5
|
+
## 사용 트리거 인덱스
|
|
6
|
+
|
|
7
|
+
- **UsbStorage** — USB 장치 목록 조회·권한 요청/확인·디렉토리/파일 읽기를 할 때 쓰는 정적 진입점. 모든 동작이 `vendorId`/`productId` 로 대상 장치를 지정.
|
|
8
|
+
- **UsbDeviceFilter / UsbDeviceInfo / UsbFileInfo** — 위 메서드의 인자·반환 타입. 장치 식별·열거·항목 분기 시 함께 참조.
|
|
9
|
+
- **UsbStoragePlugin** — `UsbStorage` 가 내부적으로 감싸는 Capacitor 네이티브 인터페이스. 보통 직접 호출하지 않음.
|
|
10
|
+
|
|
11
|
+
## UsbStorage (정적 클래스)
|
|
12
|
+
|
|
13
|
+
모든 메서드 정적, 인스턴스화 불필요. 장치 지정은 항상 `UsbDeviceFilter`(vendorId+productId).
|
|
14
|
+
|
|
15
|
+
`static getDevices(): Promise<UsbDeviceInfo[]>`
|
|
16
|
+
- 현재 연결된 USB 장치 목록을 반환. 권한 요청 전 장치의 `vendorId`/`productId` 를 알아내는 용도.
|
|
17
|
+
|
|
18
|
+
`static requestPermissions(filter: UsbDeviceFilter): Promise<boolean>`
|
|
19
|
+
- `filter` 장치의 접근 권한을 OS 에 요청. 반환 `true`=승인, `false`=거부. web 구현은 항상 `true`. readdir/readFile 전 권한 확보용.
|
|
20
|
+
|
|
21
|
+
`static checkPermissions(filter: UsbDeviceFilter): Promise<boolean>`
|
|
22
|
+
- 권한 요청 다이얼로그 없이 현재 보유 권한만 확인. `true`=이미 보유, `false`=미보유(이때 `requestPermissions` 필요). web 구현은 항상 `true`.
|
|
23
|
+
|
|
24
|
+
`static readdir(filter: UsbDeviceFilter, dirPath: string): Promise<UsbFileInfo[]>`
|
|
25
|
+
- `filter` 장치의 `dirPath` 디렉토리 하위 항목 목록을 반환. `dirPath`=읽을 디렉토리 경로(루트는 `"/"`).
|
|
26
|
+
|
|
27
|
+
`static readFile(filter: UsbDeviceFilter, filePath: string): Promise<Bytes | undefined>`
|
|
28
|
+
- `filter` 장치의 `filePath` 파일 내용을 `Bytes`(`@simplysm/core-common`)로 반환. `filePath`=읽을 파일 경로. 네이티브가 데이터 없음(null)을 주면 `undefined` 반환 — 결측을 빈 Bytes 로 치환하지 않고 그대로 전파. 내부적으로 base64 → `bytes.fromBase64` 변환.
|
|
29
|
+
|
|
30
|
+
사용 예:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
const devices = await UsbStorage.getDevices();
|
|
34
|
+
const filter = { vendorId: devices[0].vendorId, productId: devices[0].productId };
|
|
35
|
+
if (!(await UsbStorage.checkPermissions(filter))) {
|
|
36
|
+
if (!(await UsbStorage.requestPermissions(filter))) return; // 거부 시 중단
|
|
37
|
+
}
|
|
38
|
+
const files = await UsbStorage.readdir(filter, "/");
|
|
39
|
+
const data = await UsbStorage.readFile(filter, "/data.bin"); // Bytes | undefined
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 타입 정의
|
|
43
|
+
|
|
44
|
+
`interface UsbDeviceFilter` — 권한·읽기 메서드의 장치 식별 키
|
|
45
|
+
- `vendorId: number` — 대상 USB 장치의 벤더 ID. 장치 특정 키의 일부.
|
|
46
|
+
- `productId: number` — 대상 USB 장치의 제품 ID. `vendorId` 와 조합해 장치를 유일하게 지정.
|
|
47
|
+
|
|
48
|
+
`interface UsbDeviceInfo` — `getDevices` 반환 배열 요소
|
|
49
|
+
- `deviceName: string` — OS 가 보고하는 장치 시스템 이름.
|
|
50
|
+
- `manufacturerName: string` — 제조사 이름.
|
|
51
|
+
- `productName: string` — 제품 이름.
|
|
52
|
+
- `vendorId: number` — 벤더 ID. 그대로 `UsbDeviceFilter.vendorId` 구성에 사용.
|
|
53
|
+
- `productId: number` — 제품 ID. 그대로 `UsbDeviceFilter.productId` 구성에 사용.
|
|
54
|
+
|
|
55
|
+
`interface UsbFileInfo` — `readdir` 반환 배열 요소
|
|
56
|
+
- `name: string` — 항목(파일/폴더) 이름.
|
|
57
|
+
- `isDirectory: boolean` — `true`=디렉토리(하위 readdir 가능), `false`=파일(readFile 대상). 재귀 탐색·파일 필터링 분기에 사용.
|
|
58
|
+
|
|
59
|
+
## UsbStoragePlugin (네이티브 인터페이스)
|
|
60
|
+
|
|
61
|
+
`registerPlugin<UsbStoragePlugin>("UsbStorage")` 로 등록되는 Capacitor 인터페이스. `UsbStorage` 정적 메서드가 이를 감싸며 `{ ... }` 래퍼 객체에서 값을 꺼내므로 보통 직접 호출 불필요. 직접 다뤄야 할 때만 참조.
|
|
62
|
+
|
|
63
|
+
- `getDevices(): Promise<{ devices: UsbDeviceInfo[] }>` — 장치 목록을 `devices` 키로 반환.
|
|
64
|
+
- `requestPermissions(options: UsbDeviceFilter): Promise<{ granted: boolean }>` — 권한 요청 결과를 `granted` 로 반환.
|
|
65
|
+
- `checkPermissions(options: UsbDeviceFilter): Promise<{ granted: boolean }>` — 권한 보유 여부를 `granted` 로 반환.
|
|
66
|
+
- `readdir(options: UsbDeviceFilter & { path: string }): Promise<{ files: UsbFileInfo[] }>` — `path` 디렉토리 항목을 `files` 로 반환. `options` 는 필터에 `path`(대상 경로)를 합친 형태.
|
|
67
|
+
- `readFile(options: UsbDeviceFilter & { path: string }): Promise<{ data: string | null }>` — `path` 파일을 base64 문자열 `data` 로 반환. 데이터 없으면 `null`(상위 `UsbStorage.readFile` 가 `undefined` 로 변환).
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @simplysm/core-browser
|
|
2
|
+
|
|
3
|
+
브라우저 전용 유틸리티. DOM `Element`/`HTMLElement` 프로토타입 확장(import 시 사이드 이펙트로 등록)과 클립보드·다운로드·파일선택·fetch·IndexedDB 헬퍼를 제공.
|
|
4
|
+
|
|
5
|
+
## 사용 트리거 인덱스
|
|
6
|
+
|
|
7
|
+
- **Element 확장 메서드** — DOM 요소 탐색·삽입·가시성/탭이동 판정을 프로토타입 메서드로 호출할 때. 패키지를 import 하면 자동 등록됨. (아래 인라인)
|
|
8
|
+
- **HTMLElement 확장 메서드** — 리페인트 강제, 부모 기준 상대 좌표 계산, offset 가림 보정 스크롤이 필요할 때. (아래 인라인)
|
|
9
|
+
- **clipboard / bounds 정적 함수** (`copyElement`, `pasteToElement`, `getBounds`) — copy/paste 이벤트 핸들러를 붙이거나 여러 요소의 화면 경계를 한 번에 측정할 때. (아래 인라인)
|
|
10
|
+
- **다운로드·파일선택·fetch** (`downloadBlob`, `openFileDialog`, `fetchUrlBytes`) — Blob 저장, 파일 선택 다이얼로그, 진행률 포함 바이너리 다운로드가 필요할 때. (아래 인라인)
|
|
11
|
+
- **IndexedDB 저장소/가상 파일시스템** (`IndexedDbStore`, `IndexedDbVirtualFs`) — 브라우저 IndexedDB 에 KV 저장하거나 경로 기반 가상 파일트리를 다룰 때. 자세히: [indexed-db.md](./indexed-db.md)
|
|
12
|
+
|
|
13
|
+
## Element 확장 메서드
|
|
14
|
+
|
|
15
|
+
`import "@simplysm/core-browser"`(또는 패키지 내 어떤 심볼이든 import) 시 `index.ts` 가 `import "./extensions/..."` 로 `Element.prototype` 에 등록함. 별도 초기화 호출 불필요.
|
|
16
|
+
|
|
17
|
+
- `findAll<TEl>(selector: string): TEl[]` — 선택자 일치 하위 요소를 배열로 반환. 선택자를 trim 한 결과가 빈 문자열이면 `[]`. `querySelectorAll` 을 NodeList 대신 배열로 받고 빈 선택자 예외를 회피할 때.
|
|
18
|
+
- `findFirst<TEl>(selector: string): TEl | undefined` — 첫 일치 하위 요소 또는 `undefined`. 빈 선택자면 `undefined`, 미일치도 `undefined`. `querySelector` 의 `null` 을 `undefined` 로 정규화한 형태.
|
|
19
|
+
- `prependChild<TEl>(child: TEl): TEl` — 자식을 첫 번째 위치(`insertBefore(child, firstElementChild)`)로 삽입하고 그 요소 반환. 맨 앞에 끼울 때.
|
|
20
|
+
- `getParents(): Element[]` — 모든 조상 요소를 가까운 것부터 먼 순서로 배열 반환. 조상 체인 순회·특정 조상 포함 판정에.
|
|
21
|
+
- `findTabbableParent(): HTMLElement | undefined` — `tabbable` 기준 첫 탭 이동 가능 조상. 포커스 위임 대상을 위로 탐색할 때.
|
|
22
|
+
- `findFirstTabbableChild(): HTMLElement | undefined` — TreeWalker 로 순회한 첫 탭 이동 가능 하위 요소. 컨테이너 진입 시 자동 포커스 대상 찾을 때.
|
|
23
|
+
- `isOffsetElement(): boolean` — `position` 이 relative/absolute/fixed/sticky 중 하나면 true. offset parent(절대배치 기준) 역할 여부 판정에.
|
|
24
|
+
- `isVisible(): boolean` — `getClientRects().length > 0` + `visibility !== "hidden"` + `opacity !== "0"` 를 모두 만족하면 true. 화면 표시 여부 판정에(display:none 은 clientRects 가 비어 false).
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import "@simplysm/core-browser";
|
|
28
|
+
const rows = containerEl.findAll<HTMLElement>("tr");
|
|
29
|
+
const first = containerEl.findFirstTabbableChild();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## HTMLElement 확장 메서드
|
|
33
|
+
|
|
34
|
+
`HTMLElement.prototype` 에 등록되는 메서드. 위와 동일하게 import 만으로 활성화.
|
|
35
|
+
|
|
36
|
+
- `repaint(): void` — `offsetHeight` 접근으로 강제 동기 레이아웃(reflow)을 유발해 즉시 리페인트. 스타일 변경 직후 반영을 강제할 때.
|
|
37
|
+
- `getRelativeOffset(parent: HTMLElement | string): { top: number; left: number }` — 부모 기준 CSS top/left 좌표 계산. 뷰포트 위치·부모 스크롤·중간 요소 border·CSS transform 까지 반영. 드롭다운/팝업 위치 지정에. 부모를 못 찾으면 `ArgumentError` throw.
|
|
38
|
+
- parent: `HTMLElement | string` — 기준 부모. 문자열이면 `this.closest(parent)` 로 조상 탐색, 요소면 직접 사용. `document.body` 나 `".container"` 식으로 지정.
|
|
39
|
+
- `scrollIntoViewIfNeeded(target, offset?): void` — 대상이 스크롤 영역의 상단/좌측 경계를 벗어났을 때만 스크롤하여 보이게 함. 하단/우측은 처리하지 않고 브라우저 기본 포커스 스크롤에 위임. 고정 헤더/컬럼 테이블의 포커스 처리에.
|
|
40
|
+
- target: `{ top: number; left: number }` — 컨테이너 내 대상 위치(offsetTop/offsetLeft).
|
|
41
|
+
- offset: `{ top: number; left: number }` — 가려지면 안 되는 영역 크기(고정 헤더 높이·고정 컬럼 너비). 기본 `{ top: 0, left: 0 }`.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const { top, left } = popupEl.getRelativeOffset(".container");
|
|
45
|
+
scrollEl.scrollIntoViewIfNeeded({ top: cellTop, left: cellLeft }, { top: headerH, left: fixedW });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## clipboard / bounds 정적 함수
|
|
49
|
+
|
|
50
|
+
- `copyElement(event: ClipboardEvent): void` — copy 이벤트 핸들러용. 이벤트 타겟 내 첫 `input/textarea` 의 `value` 를 클립보드 `text/plain` 으로 기록하고 `preventDefault`. clipboardData 없거나 타겟이 Element 아니거나 input 없으면 무동작.
|
|
51
|
+
- event: `ClipboardEvent` — copy 이벤트 객체. `el.addEventListener("copy", copyElement)` 로 등록.
|
|
52
|
+
- `pasteToElement(event: ClipboardEvent): void` — paste 이벤트 핸들러용. 타겟 내 첫 `input/textarea` 의 전체 `value` 를 클립보드 텍스트로 교체하고 `input` 이벤트 dispatch 후 `preventDefault`. 커서 위치·선택 영역은 무시(전체 치환).
|
|
53
|
+
- event: `ClipboardEvent` — paste 이벤트 객체.
|
|
54
|
+
- `getBounds(els: Element[], timeout?: number): Promise<ElementBounds[]>` — `IntersectionObserver` 로 여러 요소의 뷰포트 기준 경계를 한 번에 측정. 중복 제거 후 입력 순서대로 정렬해 반환. 빈 배열이면 즉시 `[]`. 모든 요소 관측 완료 시 resolve, 제한시간 초과 시 `TimeoutError` throw.
|
|
55
|
+
- els: `Element[]` — 측정 대상. 중복은 제거되고 입력 순서로 정렬됨.
|
|
56
|
+
- timeout: `number` — 제한시간(ms). 기본 `5000`. 초과 시 `TimeoutError`.
|
|
57
|
+
- `ElementBounds`(반환 타입) — `target: Element`(측정 요소), `top`/`left`(뷰포트 기준 위치), `width`/`height`(요소 크기). 모두 `boundingClientRect` 값.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
inputEl.addEventListener("copy", copyElement);
|
|
61
|
+
const bounds = await getBounds([elA, elB], 3000);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 다운로드·파일선택·fetch
|
|
65
|
+
|
|
66
|
+
- `downloadBlob(blob: Blob, fileName: string): void` — Blob 을 objectURL 로 만들어 동적 `a[download]` 클릭으로 저장. objectURL 은 1초 뒤 revoke. fileName 은 `sanitize-filename` 으로 금지문자·예약어 제거 후 `[`,`]` 도 제거하며, 결과가 비면 `"download"` 로 대체.
|
|
67
|
+
- blob: `Blob` — 저장할 데이터.
|
|
68
|
+
- fileName: `string` — 저장 파일명. 파일시스템 금지 문자·예약어는 자동 정리됨.
|
|
69
|
+
- `openFileDialog(options?): Promise<File[] | undefined>` — 동적 `input[type=file]` 을 클릭해 파일 선택 다이얼로그 표시. 선택하면 `File[]`, 취소(cancel 이벤트)하거나 빈 선택이면 `undefined`.
|
|
70
|
+
- options.accept: `string` — 허용 MIME/확장자 필터(input `accept`). 미지정 시 제한 없음. 예: `".png,.jpg"`.
|
|
71
|
+
- options.multiple: `boolean` — 다중 선택 허용. 기본 `false`. 여러 파일 받을 때 `true`.
|
|
72
|
+
- `fetchUrlBytes(url, options?): Promise<Uint8Array>` — URL 바이너리를 스트림으로 다운로드. `response.ok` 아니거나 본문 없으면 Error throw. Content-Length 가 있으면 그 크기로 사전 할당하며 수신량이 그보다 초과/미달이면 Error, 없으면 청크를 모아 `bytes.concat` 으로 병합(chunked encoding).
|
|
73
|
+
- url: `string` — 다운로드 대상 URL.
|
|
74
|
+
- options.onProgress: `(progress: DownloadProgress) => void` — 청크 수신마다 호출(Content-Length 가 있는 경로에서만).
|
|
75
|
+
- `DownloadProgress`(콜백 인자 타입) — `receivedLength`(누적 수신 바이트), `contentLength`(전체 바이트, Content-Length).
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
downloadBlob(new Blob([buf]), "보고서.xlsx");
|
|
79
|
+
const files = await openFileDialog({ accept: ".csv", multiple: true });
|
|
80
|
+
const data = await fetchUrlBytes("/api/file", {
|
|
81
|
+
onProgress: (p) => setPct(p.receivedLength / p.contentLength),
|
|
82
|
+
});
|
|
83
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# @simplysm/core-browser — IndexedDB 저장소/가상 파일시스템
|
|
2
|
+
|
|
3
|
+
브라우저 IndexedDB 를 다룰 때 함께 읽히는 묶음. `IndexedDbStore` 는 연결·트랜잭션·KV CRUD 를 담당하고, `IndexedDbVirtualFs` 는 그 위에 경로 기반 가상 파일트리(entry put/get, prefix 삭제, 자식 나열, 디렉터리 보장)를 얹음.
|
|
4
|
+
|
|
5
|
+
## IndexedDbStore
|
|
6
|
+
|
|
7
|
+
IndexedDB 연결을 지연 오픈·재사용하고, 스토어 단위 트랜잭션과 기본 KV 작업을 비동기로 감싼 클래스.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
const store = new IndexedDbStore("appDb", 1, [{ name: "files", keyPath: "key" }]);
|
|
11
|
+
await store.put("files", { key: "a", data: "..." });
|
|
12
|
+
const v = await store.get<MyType>("files", "a");
|
|
13
|
+
const all = await store.getAll<MyType>("files");
|
|
14
|
+
await store.delete("files", "a");
|
|
15
|
+
store.close();
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
시그니처:
|
|
19
|
+
|
|
20
|
+
- `new IndexedDbStore(dbName: string, dbVersion: number, storeConfigs: StoreConfig[])` — DB 이름·버전·스토어 설정으로 생성(연결은 지연).
|
|
21
|
+
- dbName: `string` — IndexedDB 데이터베이스 이름.
|
|
22
|
+
- dbVersion: `number` — DB 버전. 올리면 `onupgradeneeded` 에서 누락 스토어를 생성.
|
|
23
|
+
- storeConfigs: `StoreConfig[]` — 생성할 오브젝트 스토어 목록.
|
|
24
|
+
- `StoreConfig` — 스토어 설정.
|
|
25
|
+
- name: `string` — 오브젝트 스토어 이름. upgrade 시 미존재면 `createObjectStore` 로 생성.
|
|
26
|
+
- keyPath: `string` — 스토어 keyPath(레코드에서 키로 쓸 속성명).
|
|
27
|
+
- `open(): Promise<IDBDatabase>` — 연결을 열어 반환. 이미 열렸으면 캐시 반환, 진행 중이면 같은 Promise 공유. `onupgradeneeded` 시 없는 스토어만 생성. `onversionchange`/`onclose` 시 내부 캐시(`_db`/`_opening`) 해제로 다음 호출에 재오픈. `onblocked` 면 "다른 연결에 의해 데이터베이스가 차단되었습니다" Error, `onerror` 면 원본 에러로 reject. 보통 CRUD 가 자동 호출하므로 직접 호출 불필요.
|
|
28
|
+
- `withStore<TResult>(storeName, mode, fn): Promise<TResult>` — 트랜잭션 1건 안에서 `fn(store)` 실행 후 완료까지 대기. `fn` 이 throw 하면 `tx.abort()` 후 그 에러로 reject(롤백), 정상이면 `oncomplete` 시 결과 resolve, `onerror` 면 `tx.error` 로 reject. 커서 등 저수준 IDB 작업을 감쌀 때.
|
|
29
|
+
- storeName: `string` — 트랜잭션 대상 스토어.
|
|
30
|
+
- mode: `IDBTransactionMode` — `"readonly"` | `"readwrite"` | `"versionchange"`. 쓰기면 `"readwrite"`.
|
|
31
|
+
- fn: `(store: IDBObjectStore) => Promise<TResult>` — 스토어를 받아 작업하는 콜백.
|
|
32
|
+
- `get<TValue>(storeName, key): Promise<TValue | undefined>` — 키로 단건 조회. 미존재 시 `undefined`.
|
|
33
|
+
- `put(storeName, value): Promise<void>` — 레코드 upsert(value 에 keyPath 필드 포함 필요).
|
|
34
|
+
- `delete(storeName, key): Promise<void>` — 키로 단건 삭제.
|
|
35
|
+
- `getAll<TItem>(storeName): Promise<TItem[]>` — 스토어 전체 레코드 배열 반환.
|
|
36
|
+
- `close(): void` — 연결을 닫고 내부 캐시 해제. 다음 작업 시 재오픈. 페이지 정리 시 호출.
|
|
37
|
+
|
|
38
|
+
주의:
|
|
39
|
+
|
|
40
|
+
- `withStore` 의 fn 이 throw 하면 트랜잭션 전체 abort — 다건 쓰기를 하나의 `withStore` 안에 묶으면 원자성 보장.
|
|
41
|
+
- 버전 변경(`onupgradeneeded`)은 누락 스토어 생성만 함 — 기존 스토어 스키마 변경·인덱스 추가는 별도 처리 필요.
|
|
42
|
+
|
|
43
|
+
## IndexedDbVirtualFs
|
|
44
|
+
|
|
45
|
+
`IndexedDbStore` 한 스토어를 경로 키 기반 가상 파일시스템처럼 다루는 래퍼. 키는 `keyField` 속성에 들어가는 전체 경로 문자열이고, 각 레코드는 `VirtualFsEntry`(파일/디렉터리 + 선택적 base64 데이터)다.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const fs = new IndexedDbVirtualFs(store, "files", "key");
|
|
49
|
+
await fs.ensureDir((p) => `/root${p}`, "/a/b"); // /root/a, /root/a/b 디렉터리 보장
|
|
50
|
+
await fs.putEntry("/root/a/x.txt", "file", base64);
|
|
51
|
+
const children = await fs.listChildren("/root/a/"); // [{ name, isDirectory }]
|
|
52
|
+
const ok = await fs.deleteByPrefix("/root/a"); // 하위 전체 삭제, 삭제분 있으면 true
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
시그니처:
|
|
56
|
+
|
|
57
|
+
- `new IndexedDbVirtualFs(db: IndexedDbStore, storeName: string, keyField: string)` — 백엔드 store·스토어 이름·키 필드명으로 생성.
|
|
58
|
+
- db: `IndexedDbStore` — 백엔드 저장소.
|
|
59
|
+
- storeName: `string` — 사용할 오브젝트 스토어 이름.
|
|
60
|
+
- keyField: `string` — 레코드에서 경로 키를 담는 속성명(스토어 keyPath 와 일치해야 함).
|
|
61
|
+
- `VirtualFsEntry` — 저장 엔트리.
|
|
62
|
+
- kind: `"file" | "dir"` — 엔트리 종류. `"file"` = 파일, `"dir"` = 디렉터리. 자식 나열·디렉터리 판정에 사용.
|
|
63
|
+
- dataBase64: `string` — 파일 내용 base64. 디렉터리거나 빈 파일이면 생략(undefined).
|
|
64
|
+
- `getEntry(fullKey): Promise<VirtualFsEntry | undefined>` — 전체 경로 키로 단건 조회. 미존재 시 `undefined`.
|
|
65
|
+
- `putEntry(fullKey, kind, dataBase64?): Promise<void>` — 엔트리 저장. `keyField` 에 `fullKey`, 그리고 `kind`/`dataBase64` 를 함께 기록.
|
|
66
|
+
- kind: `"file" | "dir"` — 저장할 엔트리 종류.
|
|
67
|
+
- dataBase64: `string` — 파일 데이터(base64). 디렉터리면 생략.
|
|
68
|
+
- `deleteByPrefix(keyPrefix): Promise<boolean>` — 커서로 키가 `keyPrefix` 자신이거나 `keyPrefix + "/"` 로 시작하는 엔트리 전부 삭제(부분 prefix 오삭제 방지). 하나라도 지웠으면 `true`. 디렉터리 트리 통째 삭제에.
|
|
69
|
+
- `listChildren(prefix): Promise<{ name: string; isDirectory: boolean }[]>` — `prefix` 직계 자식만 집계. 키에서 prefix 제거 후 첫 세그먼트를 이름으로 삼고, 하위 세그먼트가 더 있거나 엔트리 `kind === "dir"` 면 디렉터리로 판정. 디렉터리 목록 표시용(재귀 아님).
|
|
70
|
+
- 반환 항목 name: `string` — 직계 자식 이름(첫 경로 세그먼트).
|
|
71
|
+
- 반환 항목 isDirectory: `boolean` — 디렉터리 여부.
|
|
72
|
+
- `ensureDir(fullKeyBuilder, dirPath): Promise<void>` — `dirPath` 상의 각 중간 디렉터리를 부모부터 누적 경로마다 없으면 생성. `dirPath === "/"` 면 루트 1건만 생성. 단일 `withStore` 트랜잭션으로 처리(원자적). 파일 쓰기 전 상위 디렉터리 보장에.
|
|
73
|
+
- fullKeyBuilder: `(path: string) => string` — 누적 경로(예: `/a`, `/a/b`)를 실제 저장 key 로 변환하는 콜백.
|
|
74
|
+
- dirPath: `string` — 보장할 디렉터리 경로(`/` 구분). 빈 세그먼트는 무시.
|
|
75
|
+
|
|
76
|
+
주의:
|
|
77
|
+
|
|
78
|
+
- 모든 범위 조회는 `IDBKeyRange.bound(prefix, prefix + "")` 기반 — 호출측이 fullKey 규칙을 일관되게 유지해 prefix 가 정확한 경로 경계를 갖게 해야 함.
|
|
79
|
+
- `listChildren` 은 직계만 반환(재귀 아님). 트리 전체 순회는 세그먼트별 반복 호출 필요.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# @simplysm/core-common
|
|
2
|
+
|
|
3
|
+
브라우저·Node 공용 유틸리티 패키지. 에러 계층, 불변 날짜/시간 값, 큐·이벤트·로거, 컬렉션 프로토타입 확장, 객체·문자열·숫자·바이트·경로·직렬화 네임스페이스, 타입 유틸리티를 제공한다. 패키지를 import 하면 부수효과로 `Array`/`Set`/`Map` 프로토타입 확장이 주입된다.
|
|
4
|
+
|
|
5
|
+
## 사용 트리거 인덱스
|
|
6
|
+
|
|
7
|
+
- **날짜·시간** — `DateTime`/`DateOnly`/`Time` 불변 값과 포맷(`dt`). 날짜 계산·주차·포맷이 필요할 때. 자세히: [datetime.md](./datetime.md)
|
|
8
|
+
- **컬렉션 확장** — `Array`/`Set`/`Map` 프로토타입 메서드(groupBy·distinct·orderBy·diffs·toTree·getOrCreate 등). 배열·맵·셋 가공 시. 자세히: [array-ext.md](./array-ext.md)
|
|
9
|
+
- **obj 네임스페이스** — 깊은 clone·equal·merge·merge3, 경로 접근, pick/omit, 키 변환. 객체 비교·병합·정리 시. 자세히: [obj.md](./obj.md)
|
|
10
|
+
- **직렬화 (json/xml/transfer)** — 커스텀 타입 보존 JSON·XML·Worker 전송. 영속화·통신·워커 전달 시. 자세히: [json-transfer.md](./json-transfer.md)
|
|
11
|
+
- **에러** — `SdError`/`ArgumentError`/`NotImplementedError`/`TimeoutError`. throw 시 메시지 체인·인자 덤프가 필요할 때. (아래 인라인)
|
|
12
|
+
- **env** — `env`/`parseBoolEnv`. 환경변수 read/write·불리언 파싱 시. (아래 인라인)
|
|
13
|
+
- **값 타입** — `Uuid`, `LazyGcMap`. UUID 생성·검증, 자동 만료 캐시 맵. (아래 인라인)
|
|
14
|
+
- **큐·이벤트·로거** — `DebounceQueue`/`SerialQueue`/`EventEmitter`/`createLogger`. 디바운스·직렬 실행·이벤트·태그 로깅 시. (아래 인라인)
|
|
15
|
+
- **문자열·숫자·바이트·경로 (str/num/bytes/path/wait/err/primitive)** — 케이스 변환·조사·파싱·포맷·hex/base64·POSIX 경로·대기. (아래 인라인)
|
|
16
|
+
- **코드 템플릿 태그·ZIP** — `js`/`ts`/`html`/`tsql`/`mysql`/`pgsql` 하이라이팅 태그, `ZipArchive`. (아래 인라인)
|
|
17
|
+
- **타입 유틸리티** — `Bytes`/`PrimitiveType*`/`DeepPartial`/`Type`. 원시 타입 매핑·생성자 타입이 필요할 때. (아래 인라인)
|
|
18
|
+
|
|
19
|
+
## 에러
|
|
20
|
+
|
|
21
|
+
`SdError` 를 루트로 한 계층. 메시지는 상위→하위→원인 순서로 `" => "` 결합되어 출력된다.
|
|
22
|
+
|
|
23
|
+
- `new SdError(cause: Error, ...messages)` / `new SdError(...messages)` — ES2024 `cause` 활용. `cause` 가 있으면 stack 에 원인 stack 을 이어 붙임. `messages` 는 역순 결합(뒤 인자가 더 하위).
|
|
24
|
+
- `new ArgumentError(argObj)` / `new ArgumentError(message, argObj)` — 유효하지 않은 인자용. `argObj` 를 YAML 로 메시지에 포함(기본 메시지 "잘못된 인자입니다.").
|
|
25
|
+
- `new NotImplementedError(message?)` — 미구현 분기·추상 스텁용. 메시지 "미구현(: message)".
|
|
26
|
+
- `new TimeoutError(count?, message?)` — 대기 초과용. `count` 는 시도 횟수. `wait.until` 이 최대 시도 초과 시 자동 throw.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
throw new SdError(err, "API 호출 실패"); // "API 호출 실패 => <원인 메시지>"
|
|
30
|
+
throw new ArgumentError("잘못된 사용자", { userId }); // YAML 인자 덤프 포함
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## env
|
|
34
|
+
|
|
35
|
+
- `env(key): string | undefined` — 환경변수 읽기(`process.env` 우선, 없으면 `import.meta.env`).
|
|
36
|
+
- `env(key, value): void` — `process.env` 에 쓰기(process 가 있을 때만).
|
|
37
|
+
- `parseBoolEnv(value): boolean` — `"true"`/`"1"`/`"yes"`/`"on"`(대소문자 무시) → `true`, 그 외 → `false`.
|
|
38
|
+
|
|
39
|
+
## 값 타입
|
|
40
|
+
|
|
41
|
+
### Uuid
|
|
42
|
+
|
|
43
|
+
암호학적으로 안전한 UUID v4(`crypto.getRandomValues` 기반) 불변 값 객체.
|
|
44
|
+
- `Uuid.generate(): Uuid` — 새 v4 생성.
|
|
45
|
+
- `Uuid.fromBytes(bytes): Uuid` — 16바이트 `Uint8Array` 로 생성(길이≠16 이면 `ArgumentError`).
|
|
46
|
+
- `new Uuid(uuidStr)` — 형식(`8-4-4-4-12` hex) 검증, 불일치 시 `ArgumentError`.
|
|
47
|
+
- `toString(): string` / `toBytes(): Uint8Array` — 문자열·16바이트 변환.
|
|
48
|
+
|
|
49
|
+
### LazyGcMap
|
|
50
|
+
|
|
51
|
+
LRU 접근시간 기반 자동 만료 Map. **반드시 `dispose()` 호출** 해야 GC 타이머가 정리됨(아니면 메모리 누수).
|
|
52
|
+
- `new LazyGcMap({ gcInterval?, expireTime, onExpire? })` — `expireTime`(ms): 마지막 접근 후 이 시간 지나면 삭제. `gcInterval`(ms): GC 주기, 기본 `expireTime/10`(최소 1000). `onExpire(key, value)`: 만료 시 콜백(async 가능, 에러 시 로그 후 계속).
|
|
53
|
+
- `get(key)` — 조회(접근시간 갱신). `has(key)` — 존재 확인(갱신 안 함). `set(key, value)` — 저장(첫 set 에 GC 타이머 시작). `delete(key)` / `clear()`(인스턴스 재사용 가능) / `dispose()`(이후 사용 불가).
|
|
54
|
+
- `getOrCreate(key, factory)` — 없으면 `factory()` 로 생성·저장(dispose 후 호출 시 throw).
|
|
55
|
+
- `size` getter, `keys()`/`values()`/`entries()` 이터레이터.
|
|
56
|
+
|
|
57
|
+
## 큐·이벤트·로거
|
|
58
|
+
|
|
59
|
+
### EventEmitter\<TEvents\>
|
|
60
|
+
|
|
61
|
+
`EventTarget` 래퍼. `TEvents` 는 `{ 이벤트명: 데이터타입 }` 맵. 보통 상속해 사용.
|
|
62
|
+
- `on(type, listener)` / `off(type, listener)` — 등록/해제(같은 리스너 중복 등록은 무시).
|
|
63
|
+
- `emit(type, data?)` — 발행(데이터 타입이 `void` 면 인자 생략).
|
|
64
|
+
- `listenerCount(type): number` — 리스너 수.
|
|
65
|
+
- `dispose()` — 모든 리스너 제거.
|
|
66
|
+
|
|
67
|
+
### DebounceQueue (extends EventEmitter\<{ error: SdError }\>)
|
|
68
|
+
|
|
69
|
+
연속 호출 시 마지막만 실행하는 디바운스 큐. 실행 중 들어온 요청은 지연 없이 직후 즉시 처리.
|
|
70
|
+
- `new DebounceQueue(delay?)` — `delay`(ms) 생략 시 다음 이벤트 루프에 즉시.
|
|
71
|
+
- `run(fn)` — 대기 함수 교체(이전 대기 폐기). `dispose()` — 타이머·대기 정리.
|
|
72
|
+
- 작업 에러는 `"error"` 리스너가 있으면 emit, 없으면 내부 로거로 출력.
|
|
73
|
+
|
|
74
|
+
### SerialQueue (extends EventEmitter\<{ error: SdError }\>)
|
|
75
|
+
|
|
76
|
+
추가된 함수를 순차 실행. 한 작업이 실패해도 후속은 계속.
|
|
77
|
+
- `new SerialQueue(gap?)` — `gap`(ms) 작업 사이 간격(기본 0).
|
|
78
|
+
- `run(fn)` — 큐에 추가·실행. `dispose()` — 대기분 비움(실행 중 작업은 완료).
|
|
79
|
+
- 에러 처리는 DebounceQueue 와 동일.
|
|
80
|
+
|
|
81
|
+
### createLogger
|
|
82
|
+
|
|
83
|
+
- `createLogger(tag): ConsolaInstance` — `consola.withTag` 를 지연 생성하는 lazy 로거. 모듈 레벨에서 선언해도 이후 `setupConsola` 변경(level/reporters)이 반영됨. `vi.spyOn` 호환.
|
|
84
|
+
|
|
85
|
+
## 문자열·숫자·바이트·경로·대기
|
|
86
|
+
|
|
87
|
+
### str 네임스페이스
|
|
88
|
+
- `str.getKoreanSuffix(text, type)` — 받침에 따라 한국어 조사 반환. `type`: `"을"`(을/를)·`"은"`(은/는)·`"이"`(이/가)·`"와"`(과/와)·`"랑"`(이랑/랑)·`"로"`(으로/로, ㄹ받침 예외)·`"라"`(이라/라).
|
|
89
|
+
- `str.replaceFullWidth(s)` — 전각 영문·숫자·공백·괄호 → 반각.
|
|
90
|
+
- `str.toPascalCase`/`toCamelCase`/`toKebabCase`/`toSnakeCase(s)` — 케이스 변환(연속 대문자 분리, 기존 구분자 유지).
|
|
91
|
+
- `str.isNullOrEmpty(s): s is "" | undefined` — null/undefined/빈 문자열 타입 가드.
|
|
92
|
+
- `str.insert(s, index, insertString)` — 지정 위치에 문자열 삽입.
|
|
93
|
+
|
|
94
|
+
### num 네임스페이스
|
|
95
|
+
- `num.parseInt(text)` / `num.parseFloat(text)` — 비숫자 문자 제거 후 파싱(선행 `-` 만 음수 부호 유지). 실패 시 `undefined`. parseInt 는 소수부 버림.
|
|
96
|
+
- `num.parseRoundedInt(text)` — float 파싱 후 반올림 정수.
|
|
97
|
+
- `num.isNullOrEmpty(val): val is 0 | undefined` — null/undefined/0 타입 가드.
|
|
98
|
+
- `num.format(val, digit?)` — 천 단위 구분 문자열. `digit: { max?, min? }` 소수 자릿수(min 부족분 0 채움). val 이 undefined 면 undefined.
|
|
99
|
+
|
|
100
|
+
### bytes 네임스페이스 (`Bytes` = `Uint8Array`)
|
|
101
|
+
- `bytes.concat(arrays)` — 여러 Uint8Array 결합.
|
|
102
|
+
- `bytes.toHex(bytes)` / `bytes.fromHex(hex)` — hex 왕복(소문자 출력. fromHex 는 홀수 길이·비hex 문자 시 `ArgumentError`).
|
|
103
|
+
- `bytes.toBase64(bytes)` / `bytes.fromBase64(base64)` — base64 왕복(fromBase64 는 공백·패딩 정규화, 잘못된 문자·길이 시 `ArgumentError`).
|
|
104
|
+
|
|
105
|
+
### path 네임스페이스 (POSIX `/` 전용, 브라우저·Capacitor용)
|
|
106
|
+
- `path.join(...segments)` — 슬래시 결합(중복 슬래시 정리). `path.basename(filePath, ext?)` — 파일명(ext 일치 시 제거). `path.extname(filePath)` — 확장자(숨김 파일은 빈 문자열).
|
|
107
|
+
|
|
108
|
+
### wait 네임스페이스
|
|
109
|
+
- `wait.until(forwarder, milliseconds?, maxCount?)` — 조건이 true 될 때까지 대기. `milliseconds` 확인 간격(기본 100), `maxCount` 최대 시도(초과 시 `TimeoutError`, undefined 면 무제한).
|
|
110
|
+
- `wait.time(millisecond)` — 지정 ms 대기 Promise.
|
|
111
|
+
|
|
112
|
+
### err 네임스페이스
|
|
113
|
+
- `err.message(error): string` — `unknown` 에러에서 메시지 추출(`Error` 면 `.message`, 아니면 `String()`). catch 블록용.
|
|
114
|
+
|
|
115
|
+
### primitive 네임스페이스
|
|
116
|
+
- `primitive.typeStr(value): PrimitiveTypeStr` — 런타임 값 → 원시 타입 문자열(`"string"`|`"number"`|`"boolean"`|`"DateTime"`|`"DateOnly"`|`"Time"`|`"Uuid"`|`"Bytes"`). 미지원 타입은 `ArgumentError`.
|
|
117
|
+
|
|
118
|
+
## 코드 템플릿 태그·ZIP
|
|
119
|
+
|
|
120
|
+
코드 하이라이팅용 태그 함수(동작은 문자열 결합 + 공통 들여쓰기 정규화로 동일):
|
|
121
|
+
- `js`/`ts`/`html`/`tsql`/`mysql`/`pgsql` — 각각 JS·TS·HTML·MSSQL·MySQL·PostgreSQL 하이라이팅 의도. `` js`...` `` 형태로 사용.
|
|
122
|
+
|
|
123
|
+
### ZipArchive
|
|
124
|
+
ZIP 읽기/쓰기/압축/해제 클래스. 내부 캐시로 중복 해제 방지. **사용 후 `close()` 필수**.
|
|
125
|
+
- `new ZipArchive(data?)` — `data`(`Blob | Bytes`) 생략 시 새 아카이브.
|
|
126
|
+
- `get(fileName): Promise<Bytes | undefined>` — 특정 파일 추출. `exists(fileName): Promise<boolean>` — 존재 확인.
|
|
127
|
+
- `extractAll(progressCallback?): Promise<Map<string, Bytes | undefined>>` — 전체 추출. `progressCallback(progress)` 의 `progress: ZipArchiveProgress { fileName, totalSize, extractedSize }`.
|
|
128
|
+
- `write(fileName, bytes)` — 캐시에 파일 추가. `compress(): Promise<Bytes>` — 캐시 내용을 ZIP 으로 압축(내부에서 extractAll 호출, 대용량 메모리 주의). `close()` — 리더 닫고 캐시 비움.
|
|
129
|
+
|
|
130
|
+
## 타입 유틸리티
|
|
131
|
+
|
|
132
|
+
`common.types` 의 타입(네임스페이스 아닌 직접 export):
|
|
133
|
+
- `Bytes` = `Uint8Array` — 바이너리 타입 별칭.
|
|
134
|
+
- `PrimitiveTypeMap` — `{ string, number, boolean, DateTime, DateOnly, Time, Uuid, Bytes }` 원시 타입 매핑(orm-common 과 공유).
|
|
135
|
+
- `PrimitiveTypeStr` = `keyof PrimitiveTypeMap` — 원시 타입 문자열 key.
|
|
136
|
+
- `PrimitiveType` = `PrimitiveTypeMap[PrimitiveTypeStr] | undefined` — 원시 타입 union.
|
|
137
|
+
- `DeepPartial<TObject>` — 모든 속성을 재귀적으로 optional 로(원시 타입은 그대로, object/array 에만 재귀).
|
|
138
|
+
- `Type<TInstance>` — 클래스 생성자 타입(`new (...args) => TInstance`). DI·팩토리·`instanceof` 체크용.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @simplysm/core-common — 컬렉션 프로토타입 확장
|
|
2
|
+
|
|
3
|
+
`import "@simplysm/core-common"`(또는 패키지의 다른 심볼 import) 시 부수효과로 `Array`/`Set`/`Map` 프로토타입에 메서드가 주입된다. 별도 함수 호출이 아니라 인스턴스 메서드로 바로 쓴다. 메서드들은 `enumerable: false` 로 정의되어 `for...in` 에 노출되지 않음. 컬렉션을 그룹화·중복제거·정렬·diff 할 때 함께 읽힌다.
|
|
4
|
+
|
|
5
|
+
## Array — 조회 (ReadonlyArrayExt)
|
|
6
|
+
|
|
7
|
+
- `single(predicate?)` — 조건에 맞는 단일 요소. 없으면 `undefined`, 2개 이상이면 `ArgumentError` throw. "있다면 하나뿐" 을 단언할 때.
|
|
8
|
+
- `first(predicate?)` / `last(predicate?)` — 첫/마지막 요소(predicate 생략 시 인덱스 끝). 없으면 `undefined`.
|
|
9
|
+
- `filterExists()` — `null`/`undefined` 제거. 반환 타입 `NonNullable<T>[]`.
|
|
10
|
+
- `ofType(type)` — 특정 타입 요소만. `type` 은 `PrimitiveTypeStr`("string"|"number"|"boolean"|"DateTime"|"DateOnly"|"Time"|"Uuid"|"Bytes") 또는 생성자(`Type<N>`). 문자열은 typeof/instanceof, 생성자는 `instanceof` + `constructor` 일치로 판정.
|
|
11
|
+
- `sum(selector?)` — 합계. 비어 있으면 0. 숫자가 아니면 `ArgumentError`.
|
|
12
|
+
- `min(selector?)` / `max(selector?)` — 최소/최대(문자열·숫자). 비어 있으면 `undefined`. 그 외 타입은 `ArgumentError`.
|
|
13
|
+
- `shuffle()` — Fisher-Yates 셔플한 새 배열.
|
|
14
|
+
|
|
15
|
+
비동기:
|
|
16
|
+
- `filterAsync(predicate)` / `mapAsync(selector)` — 순차 실행(await 보장). 호출 순서가 중요할 때.
|
|
17
|
+
- `parallelAsync(fn)` — `Promise.all` 병렬. 하나라도 reject 면 전체 즉시 reject.
|
|
18
|
+
- `mapMany(selector?)` — 매핑 후 1단계 평탄화 + `filterExists`. selector 생략 시 자기 자신 평탄화.
|
|
19
|
+
- `mapManyAsync(selector?)` — `mapMany` 의 비동기(순차) 버전.
|
|
20
|
+
|
|
21
|
+
## Array — 그룹화·변환
|
|
22
|
+
|
|
23
|
+
- `groupBy(keySelector, valueSelector?)` — key 별 `{ key, values }[]`. 원시 key 는 O(n)(Map), 객체 key 는 O(n²)(깊은 비교). 객체 key 가 필요 없으면 `toArrayMap` 권장.
|
|
24
|
+
- `toMap(keySelector, valueSelector?)` — `Map<K,V>`. key 중복 시 `ArgumentError`.
|
|
25
|
+
- `toMapAsync(...)` — `toMap` 의 비동기(순차) 버전. selector 가 Promise 반환 가능.
|
|
26
|
+
- `toArrayMap(keySelector, valueSelector?)` — `Map<K, V[]>`. 같은 key 값들을 배열로 누적.
|
|
27
|
+
- `toSetMap(keySelector, valueSelector?)` — `Map<K, Set<V>>`. 값 중복 자동 제거.
|
|
28
|
+
- `toMapValues(keySelector, valueSelector)` — key 별로 모은 `items[]` 를 `valueSelector(items)` 로 집계해 `Map<K,V>`.
|
|
29
|
+
- `toObject(keySelector, valueSelector?)` — `Record<string,V>`. key(문자열) 중복 시 `ArgumentError`(단 기존 값이 null 이면 덮어쓰기 허용).
|
|
30
|
+
- `toTree(keyProp, parentKey)` — 평면 배열 → 트리. `parentKey` 가 null/undefined 인 항목이 루트, 각 노드에 `children` 추가(원본은 clone). 반환 `TreeArray<T>[]`.
|
|
31
|
+
|
|
32
|
+
## Array — 중복제거·정렬·diff
|
|
33
|
+
|
|
34
|
+
- `distinct(options?)` — 중복 제거(새 배열). `options`: `boolean`(=`{matchAddress}`) 또는 `{ matchAddress?, keyFn? }`. `matchAddress:true` 면 참조 비교(O(n)), `keyFn` 이면 key 비교(O(n)), 둘 다 없으면 객체는 깊은 비교(O(n²)).
|
|
35
|
+
- `orderBy(selector?)` / `orderByDesc(selector?)` — 오름/내림차순 새 배열. selector 는 string|number|DateTime|DateOnly|Time|undefined 반환. null/undefined 는 오름차순에서 앞.
|
|
36
|
+
- `diffs(target, options?)` — 두 배열 비교 결과 `ArrayDiffsResult<T,P>[]`. `options.keys`(key 비교 속성)·`options.excludes`(비교 제외 속성). 전체 일치 우선, 없으면 key 일치를 UPDATE 로. target 잔여는 INSERT, source 잔여는 DELETE.
|
|
37
|
+
- `oneWayDiffs(orgItems, keyPropNameOrGetValFn, options?)` — source(this) 를 기준으로 원본 대비 변경 분류. `keyPropNameOrGetValFn`: 키 속성명 또는 `(item) => 키값`. key 값이 없거나 원본에 없으면 `create`, 다르면 `update`, 같으면 `same`(옵션 `includeSame:true` 일 때만 포함). `options.excludes`/`options.includes` 로 비교 범위 조정. 반환 `ArrayOneWayDiffResult<T>[]`.
|
|
38
|
+
- `merge(target, options?)` — `diffs` 결과를 적용해 병합한 새 배열. UPDATE 는 `obj.merge` 로 깊은 병합, INSERT 는 추가. `options` 는 `diffs` 와 동일.
|
|
39
|
+
|
|
40
|
+
## Array — 원본 변경 (MutableArrayExt, @mutates)
|
|
41
|
+
|
|
42
|
+
원본 배열을 직접 수정하고 보통 `this` 를 반환(체이닝).
|
|
43
|
+
- `distinctThis(options?)` — 원본에서 중복 제거(역순 splice).
|
|
44
|
+
- `orderByThis(selector?)` / `orderByDescThis(selector?)` — 원본 in-place 정렬.
|
|
45
|
+
- `insert(index, ...items)` — 지정 위치 삽입.
|
|
46
|
+
- `remove(itemOrSelector)` — 값 일치 또는 조건 함수에 맞는 항목 전부 제거(역순 순회).
|
|
47
|
+
- `toggle(item)` — 있으면 제거, 없으면 push.
|
|
48
|
+
- `clear()` — 전부 비움.
|
|
49
|
+
|
|
50
|
+
## Set 확장
|
|
51
|
+
|
|
52
|
+
- `adds(...values)` — 여러 값 일괄 추가, `this` 반환.
|
|
53
|
+
- `toggle(value, addOrDel?)` — `addOrDel` 생략 시 자동 토글(있으면 제거/없으면 추가), `"add"`=강제 추가, `"del"`=강제 제거. 조건부 추가/제거를 한 줄로. `this` 반환.
|
|
54
|
+
|
|
55
|
+
## Map 확장
|
|
56
|
+
|
|
57
|
+
- `getOrCreate(key, newValueOrFactory)` — key 가 없으면 값을 설정 후 반환, 있으면 기존 값. 두 번째 인자가 함수면 팩토리로 인식해 호출됨 — 함수 자체를 값으로 저장하려면 `() => fn` 처럼 팩토리로 감쌀 것.
|
|
58
|
+
- `update(key, updateFn)` — 현재 값(`v | undefined`)을 받아 새 값을 설정. key 가 없어도 `updateFn(undefined)` 호출됨. 카운터 증가·배열 누적 등에.
|
|
59
|
+
|
|
60
|
+
## 내보낸 타입
|
|
61
|
+
|
|
62
|
+
- `ArrayDiffsResult<TOriginal, TOther>` — `{ source: undefined, target }`(INSERT) | `{ source, target: undefined }`(DELETE) | `{ source, target }`(UPDATE).
|
|
63
|
+
- `ArrayOneWayDiffResult<TItem>` — `{ type: "create"|"update"|"same", item, orgItem }` (create 는 `orgItem: undefined`).
|
|
64
|
+
- `TreeArray<TNode>` — `TNode & { children: TreeArray<TNode>[] }`.
|
|
65
|
+
- `ComparableType` — `string | number | boolean | DateTime | DateOnly | Time | undefined`. 정렬/비교 가능 타입.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const orders = items.toArrayMap((it) => it.customerId); // Map<id, item[]>
|
|
69
|
+
const sorted = items.orderBy((it) => it.createdAt).distinct({ keyFn: (it) => it.id });
|
|
70
|
+
const tree = rows.toTree("id", "parentId");
|
|
71
|
+
const changes = current.oneWayDiffs(original, "id"); // create/update 분류
|
|
72
|
+
```
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# @simplysm/core-common — 날짜·시간
|
|
2
|
+
|
|
3
|
+
날짜/시간 값을 다룰 때 함께 읽히는 묶음. 불변 클래스 `DateTime`/`DateOnly`/`Time` 와 포맷 문자열을 다루는 `dt` 네임스페이스. 세 클래스 모두 로컬 타임존 기준으로 동작하며, 변환/산술 메서드는 원본을 바꾸지 않고 새 인스턴스를 반환한다.
|
|
4
|
+
|
|
5
|
+
## DateTime
|
|
6
|
+
|
|
7
|
+
날짜+시간(밀리초 정밀도) 불변 클래스. 내부에 `readonly date: Date` 보유.
|
|
8
|
+
|
|
9
|
+
생성자:
|
|
10
|
+
- `new DateTime()` — 현재 시각.
|
|
11
|
+
- `new DateTime(year, month, day, hour?, minute?, second?, millisecond?)` — `month` 는 1~12(내부에서 0-base 로 변환). 생략한 시/분/초/밀리초는 0.
|
|
12
|
+
- `new DateTime(tick: number)` — epoch 밀리초.
|
|
13
|
+
- `new DateTime(date: Date)` — Date 복사(원본과 분리).
|
|
14
|
+
|
|
15
|
+
- `DateTime.parse(str): DateTime` — 문자열 파싱. 지원: ISO 8601, `yyyy-MM-dd HH:mm:ss(.fff)`, `yyyyMMddHHmmss`, `yyyy-MM-dd AM/PM HH:mm:ss`, 한국어 `오전/오후`. 실패 시 `ArgumentError` throw.
|
|
16
|
+
|
|
17
|
+
읽기 전용 getter:
|
|
18
|
+
- `year`/`month`(1~12)/`day`/`hour`/`minute`/`second`/`millisecond` — 각 구성요소.
|
|
19
|
+
- `tick: number` — epoch 밀리초. 두 시점 비교·차이 계산에 사용.
|
|
20
|
+
- `dayOfWeek: number` — 요일(일=0 ~ 토=6).
|
|
21
|
+
- `timezoneOffsetMinutes: number` — UTC 대비 오프셋 분(KST=+540). `Date.getTimezoneOffset()` 의 부호 반대.
|
|
22
|
+
- `isValid: boolean` — 유효한 날짜인지. 잘못된 tick 으로 만든 인스턴스 검증에 사용.
|
|
23
|
+
|
|
24
|
+
변환 메서드(새 인스턴스 반환):
|
|
25
|
+
- `setYear/setMonth/setHour/setMinute/setSecond/setMillisecond(n)` — 해당 구성요소만 교체. `setMonth` 는 범위 밖 월을 연도로 넘기고, 대상 월 일수 초과 시 말일로 보정(1/31 → setMonth(2) → 2/28·29).
|
|
26
|
+
- `setDay(n)` — 일 교체. 월 범위를 벗어나는 일은 JS Date 동작대로 다음/이전 월로 넘어감(1월 day=32 → 2/1).
|
|
27
|
+
|
|
28
|
+
산술 메서드(새 인스턴스 반환):
|
|
29
|
+
- `addYears/addMonths(n)` — `setYear/setMonth` 경유라 말일 보정 규칙을 따름.
|
|
30
|
+
- `addDays/addHours/addMinutes/addSeconds/addMilliseconds(n)` — 음수 가능. 시 이하는 tick 기반 가산이라 DST 경계를 그대로 통과.
|
|
31
|
+
|
|
32
|
+
포맷:
|
|
33
|
+
- `toFormatString(formatStr): string` — 포맷 문자열로 변환(아래 `dt.format` 토큰 참조).
|
|
34
|
+
- `toString(): string` — `yyyy-MM-ddTHH:mm:ss.fffzzz` 형식.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
const d = DateTime.parse("2025-01-15 10:30:00");
|
|
38
|
+
d.addDays(1).toFormatString("yyyy-MM-dd (ddd)"); // "2025-01-16 (목)"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## DateOnly
|
|
42
|
+
|
|
43
|
+
시간 제외 날짜만(`yyyy-MM-dd`) 불변 클래스. 주차(week) 계산 메서드 포함.
|
|
44
|
+
|
|
45
|
+
생성자: `new DateOnly()`(오늘) / `(year, month, day)` / `(tick)` / `(date)` — 모두 시간 부분을 버리고 자정으로 정규화.
|
|
46
|
+
|
|
47
|
+
- `DateOnly.parse(str): DateOnly` — `yyyy-MM-dd`/`yyyyMMdd`(타임존 무관, 문자열 직접 추출) 또는 ISO 8601(UTC 해석 후 로컬 변환). 서버·클라 타임존이 다르면 `yyyy-MM-dd` 권장. 실패 시 `ArgumentError`.
|
|
48
|
+
|
|
49
|
+
getter: `isValid`/`year`/`month`(1~12)/`day`/`tick`/`dayOfWeek`(일=0~토=6).
|
|
50
|
+
|
|
51
|
+
변환/산술: `setYear/setMonth/setDay`, `addYears/addMonths/addDays` — DateTime 과 동일한 말일·월 넘김 규칙.
|
|
52
|
+
|
|
53
|
+
주차 계산 — 공통 인자 `weekStartDay`(주 시작 요일, 0=일~6=토, 기본 1=월)·`minDaysInFirstWeek`(첫 주로 인정할 최소 일수 1~7, 기본 4=ISO 8601 표준):
|
|
54
|
+
- `getBaseYearMonthSeqForWeekSeq(weekStartDay?, minDaysInFirstWeek?): { year, monthSeq }` — 이 날짜가 속한 주의 기준 연·월. 월 경계 주를 이전/다음 달 중 어디로 귀속할지 판정.
|
|
55
|
+
- `getWeekSeqStartDate(weekStartDay?, minDaysInFirstWeek?): DateOnly` — 이 날짜가 속한 주의 시작일.
|
|
56
|
+
- `getWeekSeqOfYear(weekStartDay?, minDaysInFirstWeek?): { year, weekSeq }` — 연 기준 주차 번호.
|
|
57
|
+
- `getWeekSeqOfMonth(weekStartDay?, minDaysInFirstWeek?): { year, monthSeq, weekSeq }` — 월 기준 주차 번호.
|
|
58
|
+
- `DateOnly.getDateByYearWeekSeq(arg, weekStartDay?, minDaysInFirstWeek?): DateOnly` — `arg: { year, month?, weekSeq }` 로 해당 주의 시작일 역산. `month` 생략 시 연 단위 주차.
|
|
59
|
+
|
|
60
|
+
포맷: `toFormatString(formatStr)`, `toString()` → `yyyy-MM-dd`.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
new DateOnly(2025, 1, 15).getWeekSeqOfMonth(); // { year: 2025, monthSeq: 1, weekSeq: 3 }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Time
|
|
67
|
+
|
|
68
|
+
날짜 제외 시간만(`HH:mm:ss.fff`) 불변 클래스. 24시간을 넘거나 음수인 값은 자동으로 0~24시 범위로 순환 정규화됨.
|
|
69
|
+
|
|
70
|
+
생성자: `new Time()`(현재 시각의 시간부) / `(hour, minute, second?, millisecond?)` / `(tick)`(하루 내 밀리초) / `(date)`(Date 의 시간부만).
|
|
71
|
+
|
|
72
|
+
- `Time.parse(str): Time` — `HH:mm:ss(.fff)`, `AM/PM HH:mm:ss`, ISO 8601(시간부만 추출) 지원. 실패 시 `ArgumentError`.
|
|
73
|
+
|
|
74
|
+
getter: `hour`/`minute`/`second`/`millisecond`/`tick`(하루 내 밀리초)/`isValid`.
|
|
75
|
+
|
|
76
|
+
변환: `setHour/setMinute/setSecond/setMillisecond(n)`.
|
|
77
|
+
산술: `addHours/addMinutes/addSeconds/addMilliseconds(n)` — 24시간 순환(23:30 + 1h → 00:30).
|
|
78
|
+
|
|
79
|
+
포맷: `toFormatString(formatStr)`, `toString()` → `HH:mm:ss.fff`.
|
|
80
|
+
|
|
81
|
+
## dt (네임스페이스)
|
|
82
|
+
|
|
83
|
+
`import { dt } from "@simplysm/core-common"`. 위 세 클래스의 `toFormatString` 내부 구현이자, 직접 호출도 가능한 포맷터.
|
|
84
|
+
|
|
85
|
+
- `dt.format(formatString, args): string` — `args: { year?, month?, day?, hour?, minute?, second?, millisecond?, timezoneOffsetMinutes? }` 중 제공된 구성요소만 치환(미제공 토큰은 원문 유지). 요일(`ddd`)은 year·month·day 가 모두 있을 때만 계산.
|
|
86
|
+
- `dt.normalizeMonth(year, month, day): { year, month, day }` — 1~12 범위 밖 월을 연도로 넘기고 말일 보정.
|
|
87
|
+
- `dt.convert12To24(rawHour, isPM): number` — 12시간제(1~12)+오전/오후 → 24시간제(0~23). 12 AM=0, 12 PM=12.
|
|
88
|
+
- 타입 `dt.DtNormalizedMonth = { year, month, day }`.
|
|
89
|
+
|
|
90
|
+
포맷 토큰(C# 호환): `yyyy`(4자리 연)·`yy`(2자리), `MM`/`M`(0채움/일반 월), `ddd`(요일 한글 일~토), `dd`/`d`(일), `tt`(AM/PM), `hh`/`h`(12시간), `HH`/`H`(24시간), `mm`/`m`(분), `ss`/`s`(초), `fff`/`ff`/`f`(밀리초 3/2/1자리), `zzz`(±HH:mm)·`zz`(±HH)·`z`(±H) 타임존 오프셋.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
dt.format("yyyy-MM-dd tt h:mm", { year: 2024, month: 3, day: 15, hour: 14, minute: 30 });
|
|
94
|
+
// "2024-03-15 PM 2:30"
|
|
95
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @simplysm/core-common — 직렬화 (json / xml / transfer)
|
|
2
|
+
|
|
3
|
+
커스텀 타입을 보존하며 직렬화/역직렬화할 때 함께 읽히는 묶음. `json`(문자열 ↔ 객체), `xml`(XML ↔ 객체), `transfer`(Web Worker 전송용). 세 모듈 모두 `DateTime`/`DateOnly`/`Time`/`Uuid`/`Set`/`Map`/`Error`/`Uint8Array` 등을 `__type__` 태그 객체로 변환해 왕복 보존한다.
|
|
4
|
+
|
|
5
|
+
## json 네임스페이스
|
|
6
|
+
|
|
7
|
+
`import { json } from "@simplysm/core-common"`. 전역 프로토타입을 건드리지 않아 Worker 환경 안전.
|
|
8
|
+
|
|
9
|
+
- `json.stringify(obj, options?): string` — 커스텀 타입(Date/DateTime/DateOnly/Time/Uuid/Set/Map/Error/Uint8Array)을 `{ __type__, data }` 로 변환 후 직렬화. `options`:
|
|
10
|
+
- `space?: string | number` — 들여쓰기(숫자=공백 수, 문자열=들여쓰기 문자열).
|
|
11
|
+
- `replacer?: (key, value) => unknown` — 기본 타입 변환 **전에** 호출되는 커스텀 변환기.
|
|
12
|
+
- `redactBytes?: boolean` — true 면 `Uint8Array` 내용을 `"__hidden__"` 로 대체(로깅용). 이 결과는 `json.parse` 로 복원 불가.
|
|
13
|
+
- 순환 참조는 `TypeError`, `toJSON` 메서드가 있으면 호출(위 커스텀 타입은 예외), `undefined` 속성은 제외.
|
|
14
|
+
- `json.parse<T>(json): T` — `__type__`/`data` 태그를 보고 커스텀 타입 복원. 모든 JSON `null` 을 `undefined` 로 변환(simplysm null-free 규칙). 사용자 데이터에 우연히 `{ __type__, data }` 형태가 있으면 오변환 위험. `redactBytes` 로 가려진 바이트를 만나면 `SdError`. 파싱 실패 시 `SdError`(환경변수 `DEV` 가 truthy 면 전체 JSON, 아니면 길이만 메시지에 포함).
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
const text = json.stringify({ at: new DateTime(), bin: new Uint8Array([1, 2]) }, { space: 2 });
|
|
18
|
+
const back = json.parse<{ at: DateTime; bin: Uint8Array }>(text); // 타입 복원됨
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## xml 네임스페이스
|
|
22
|
+
|
|
23
|
+
`import { xml } from "@simplysm/core-common"`. `fast-xml-parser` 기반.
|
|
24
|
+
|
|
25
|
+
- `xml.parse(str, options?): unknown` — XML → 객체. 속성은 `$` 객체로, 텍스트 노드는 `_` key 로, 자식 요소는 배열로 변환(루트 제외). `options.stripTagPrefix?: boolean` 면 태그의 네임스페이스 접두사(`ns:tag`)를 제거(속성 접두사는 유지).
|
|
26
|
+
- `xml.stringify(obj, options?): string` — 객체 → XML. `options` 는 fast-xml-parser 의 `XmlBuilderOptions`(기본값을 덮어쓸 때만 사용).
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
xml.parse('<root id="1"><item>hi</item></root>');
|
|
30
|
+
// { root: { $: { id: "1" }, item: [{ _: "hi" }] } }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## transfer 네임스페이스
|
|
34
|
+
|
|
35
|
+
`import { transfer } from "@simplysm/core-common"`. Web Worker 간 데이터 전송용. `structuredClone` 이 못 다루는 커스텀 타입을 처리하고, `Uint8Array` 의 버퍼를 zero-copy 전송 목록에 담는다.
|
|
36
|
+
|
|
37
|
+
- `transfer.encode(obj): { result, transferList }` — 커스텀 타입을 `{ __type__, data }` 로 변환한 `result` 와 transfer 대상 `ArrayBuffer[]`(transferList) 반환. `worker.postMessage(result, transferList)` 형태로 사용. 순환 참조 시 경로 포함 `TypeError`. 같은 객체 다중 참조는 인코딩 결과 캐시 재사용. `SharedArrayBuffer` 는 transferList 에 넣지 않음.
|
|
38
|
+
- `transfer.decode(obj): unknown` — 수신한 태그 객체를 커스텀 타입으로 복원(Date/DateTime/DateOnly/Time/Uuid/RegExp/Error, Map/Set/Array/객체 재귀). `Uint8Array` 는 그대로.
|
|
39
|
+
|
|
40
|
+
`json` 과 차이: transfer 는 날짜류를 tick(숫자)으로 저장하고 `RegExp` 를 지원하며 `Uint8Array` 를 직렬화하지 않고 버퍼째 넘긴다. JSON 문자열이 아니라 구조화 클론 가능한 객체를 만든다.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
const { result, transferList } = transfer.encode(payload);
|
|
44
|
+
worker.postMessage(result, transferList);
|
|
45
|
+
// 수신 측
|
|
46
|
+
const data = transfer.decode(event.data);
|
|
47
|
+
```
|