@simplysm/sd-claude 14.0.87 → 14.0.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude/references/sd-simplysm14/README.md +17 -18
- package/claude/references/sd-simplysm14/apis/angular/README.md +35 -0
- package/claude/references/sd-simplysm14/apis/angular/controls.md +51 -0
- package/claude/references/sd-simplysm14/apis/angular/crud.md +53 -0
- package/claude/references/sd-simplysm14/apis/angular/directives.md +34 -0
- package/claude/references/sd-simplysm14/apis/angular/features.md +40 -0
- package/claude/references/sd-simplysm14/apis/angular/infra.md +74 -0
- package/claude/references/sd-simplysm14/apis/angular/layout.md +27 -0
- package/claude/references/sd-simplysm14/apis/angular/overlay.md +103 -0
- package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +69 -0
- package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +28 -0
- package/claude/references/sd-simplysm14/apis/angular/shared-data.md +57 -0
- package/claude/references/sd-simplysm14/apis/angular/sheet.md +73 -0
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +78 -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 +62 -0
- package/claude/references/sd-simplysm14/apis/core-browser/README.md +70 -0
- package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +62 -0
- package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +80 -0
- package/claude/references/sd-simplysm14/apis/core-common/README.md +262 -0
- package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +121 -0
- package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +128 -0
- package/claude/references/sd-simplysm14/apis/core-common/datetime.md +129 -0
- package/claude/references/sd-simplysm14/apis/core-common/errors.md +91 -0
- package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +53 -0
- package/claude/references/sd-simplysm14/apis/core-common/obj.md +117 -0
- package/claude/references/sd-simplysm14/apis/core-node/README.md +17 -0
- package/claude/references/sd-simplysm14/apis/core-node/consola.md +43 -0
- package/claude/references/sd-simplysm14/apis/core-node/cpx.md +50 -0
- package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +41 -0
- package/claude/references/sd-simplysm14/apis/core-node/fsx.md +72 -0
- package/claude/references/sd-simplysm14/apis/core-node/pathx.md +39 -0
- package/claude/references/sd-simplysm14/apis/core-node/worker.md +52 -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 +49 -0
- package/claude/references/sd-simplysm14/apis/lint/rules.md +130 -0
- package/claude/references/sd-simplysm14/apis/orm-common/README.md +13 -0
- package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +111 -0
- package/claude/references/sd-simplysm14/apis/orm-common/expr.md +128 -0
- package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +145 -0
- package/claude/references/sd-simplysm14/apis/orm-common/schema.md +147 -0
- package/claude/references/sd-simplysm14/apis/orm-common/types.md +62 -0
- package/claude/references/sd-simplysm14/apis/orm-node/README.md +90 -0
- package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +94 -0
- package/claude/references/sd-simplysm14/apis/sd-cli/README.md +26 -0
- package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +117 -0
- package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +291 -0
- package/claude/references/sd-simplysm14/apis/service-client/README.md +150 -0
- package/claude/references/sd-simplysm14/apis/service-client/orm.md +48 -0
- package/claude/references/sd-simplysm14/apis/service-client/transport.md +59 -0
- package/claude/references/sd-simplysm14/apis/service-common/README.md +84 -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 +118 -0
- package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +71 -0
- package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +62 -0
- package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +39 -0
- package/claude/references/sd-simplysm14/apis/storage/README.md +120 -0
- package/claude/skills/sd-demo/SKILL.md +6 -0
- package/claude/skills/sd-impl/SKILL.md +4 -7
- package/claude/skills/sd-spec/SKILL.md +31 -858
- package/claude/skills/sd-spec/references/spec-authoring.md +519 -0
- package/claude/workflows/sd-docs.js +84 -0
- package/claude/{skills/sd-docs/references/subagent-prompt.md → workflows/sd-docs.rules.md} +25 -40
- package/package.json +1 -1
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/.specs/inventory/spec.md +0 -99
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/package.json +0 -12
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/index.ts +0 -3
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inbound/inbound.list.ts +0 -150
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inventory/inventory-master.list.ts +0 -143
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/outbound/outbound.list.ts +0 -150
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/pnpm-workspace.yaml +0 -2
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/sd.config.ts +0 -12
- package/claude/skills/sd-demo/evals/golden.jsonl +0 -1
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/package.json +0 -8
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/src/.gitkeep +0 -0
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tests/.gitkeep +0 -0
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tsconfig.json +0 -10
- package/claude/skills/sd-dev/evals/golden.jsonl +0 -1
- package/claude/skills/sd-docs/SKILL.md +0 -58
- package/claude/skills/sd-docs/evals/fixtures/new-write/.claude/references/sd-simplysm14/README.md +0 -7
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/src/index.ts +0 -3
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/package.json +0 -6
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/src/index.ts +0 -1
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/src/index.ts +0 -8
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/README.md +0 -7
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/apis/foo/README.md +0 -3
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/src/index.ts +0 -3
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/package.json +0 -6
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/src/index.ts +0 -1
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/src/index.ts +0 -8
- package/claude/skills/sd-docs/evals/golden.jsonl +0 -2
- package/claude/skills/sd-impl/evals/fixtures/case-a-new-screen/.specs/260513120000_warehouse/spec.md +0 -101
- package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/.specs/260513120000_warehouse/spec.md +0 -101
- package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/packages/app/src/screens/box-register/box-register.view.ts +0 -46
- package/claude/skills/sd-impl/evals/fixtures/case-c-new-cross/.specs/260513120000_warehouse/spec.md +0 -89
- package/claude/skills/sd-impl/evals/fixtures/case-d-spec-modify/.specs/260513120000_warehouse/spec.md +0 -101
- package/claude/skills/sd-impl/evals/golden.jsonl +0 -4
- package/claude/skills/sd-manual/evals/fixtures/new-manual/src/notification.ts +0 -25
- package/claude/skills/sd-manual/evals/fixtures/update-manual/.claude/references/sd-simplysm14/manuals/notification.md +0 -14
- package/claude/skills/sd-manual/evals/fixtures/update-manual/src/notification.ts +0 -37
- package/claude/skills/sd-manual/evals/golden.jsonl +0 -2
- package/claude/skills/sd-review/evals/fixtures/code-review/src/foo.ts +0 -7
- package/claude/skills/sd-review/evals/fixtures/doc-review/docs/foo.md +0 -4
- package/claude/skills/sd-review/evals/golden.jsonl +0 -2
- package/claude/skills/sd-skill/evals/fixtures/existing-skill/.claude/skills/todo-format/SKILL.md +0 -14
- package/claude/skills/sd-skill/evals/fixtures/new-skill/.gitkeep +0 -0
- package/claude/skills/sd-skill/evals/golden.jsonl +0 -2
- package/claude/skills/sd-spec/evals/fixtures/case-a-split//355/232/214/354/235/230/353/241/235.md +0 -20
- package/claude/skills/sd-spec/evals/fixtures/case-b-detail/.specs/260513120000_warehouse/spec.md +0 -95
- package/claude/skills/sd-spec/evals/golden.jsonl +0 -2
- package/claude/skills/sd-unpack/evals/fixtures/eml-with-text-attachment/meeting.eml +0 -21
- package/claude/skills/sd-unpack/evals/fixtures/simple-eml/meeting.eml +0 -10
- package/claude/skills/sd-unpack/evals/golden.jsonl +0 -2
- package/claude/skills/sd-use/evals/fixtures/empty/.gitkeep +0 -0
- package/claude/skills/sd-use/evals/golden.jsonl +0 -6
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
- fullKey: `string` — 저장할 전체 경로 키.
|
|
67
|
+
- kind: `"file" | "dir"` — 저장할 엔트리 종류.
|
|
68
|
+
- dataBase64: `string` — 파일 데이터(base64). 디렉터리면 생략.
|
|
69
|
+
- `deleteByPrefix(keyPrefix): Promise<boolean>` — 커서로 키가 `keyPrefix` 자신이거나 `keyPrefix + "/"` 로 시작하는 엔트리 전부 삭제(같은 접두어를 가진 다른 형제 경로 오삭제 방지). 하나라도 지웠으면 `true`, 없으면 `false`. 디렉터리 트리 통째 삭제에.
|
|
70
|
+
- `listChildren(prefix): Promise<{ name: string; isDirectory: boolean }[]>` — `prefix` 직계 자식만 집계. 키에서 prefix 제거 후 첫 세그먼트를 이름으로 삼고, 하위 세그먼트가 더 있거나 엔트리 `kind === "dir"` 면 디렉터리로 판정. 디렉터리 목록 표시용(재귀 아님).
|
|
71
|
+
- 반환 항목 name: `string` — 직계 자식 이름(첫 경로 세그먼트).
|
|
72
|
+
- 반환 항목 isDirectory: `boolean` — 디렉터리 여부.
|
|
73
|
+
- `ensureDir(fullKeyBuilder, dirPath): Promise<void>` — `dirPath` 상의 각 중간 디렉터리를 부모부터 누적 경로마다 없으면 생성. `dirPath === "/"` 면 루트 1건만 생성. 단일 `withStore("readwrite")` 트랜잭션으로 처리(원자적). 파일 쓰기 전 상위 디렉터리 보장에.
|
|
74
|
+
- fullKeyBuilder: `(path: string) => string` — 누적 경로(예: `/a`, `/a/b`)를 실제 저장 key 로 변환하는 콜백.
|
|
75
|
+
- dirPath: `string` — 보장할 디렉터리 경로(`/` 구분). 빈 세그먼트는 무시.
|
|
76
|
+
|
|
77
|
+
주의:
|
|
78
|
+
|
|
79
|
+
- 모든 범위 조회는 `IDBKeyRange.bound(prefix, prefix + "")` 기반 — 호출측이 fullKey 규칙을 일관되게 유지해 prefix 가 정확한 경로 경계를 갖게 해야 함.
|
|
80
|
+
- `listChildren` 은 직계만 반환(재귀 아님). 트리 전체 순회는 세그먼트별 반복 호출 필요.
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# @simplysm/core-common
|
|
2
|
+
|
|
3
|
+
런타임 무관(Node.js·브라우저·Worker) 공통 유틸리티 패키지. 값 타입(날짜·UUID), 에러 트리, 비동기 큐/이벤트, Array/Set/Map 프로토타입 확장, 객체/문자열/숫자/바이트/경로/JSON/XML 유틸 네임스페이스를 제공. 패키지를 import 하면 부수효과로 `Array`/`Set`/`Map` 프로토타입 확장이 주입된다.
|
|
4
|
+
|
|
5
|
+
## 사용 트리거 인덱스
|
|
6
|
+
|
|
7
|
+
- **에러 클래스** (`SdError`/`ArgumentError`/`NotImplementedError`/`TimeoutError`) — 원인 체인을 가진 에러를 throw 하거나 `instanceof` 로 분기할 때. 자세히: [errors.md](./errors.md)
|
|
8
|
+
- **날짜/시간 값 타입** (`DateTime`/`DateOnly`/`Time`, `dt` 네임스페이스) — 불변 날짜·시간 값을 만들고 파싱·산술·포맷할 때. 자세히: [datetime.md](./datetime.md)
|
|
9
|
+
- **Array 확장 메서드** (`Array.prototype` 전역 확장) — `single`/`groupBy`/`distinct`/`orderBy`/`diffs`/`toTree` 등 컬렉션 가공이 필요할 때. 자세히: [array-ext.md](./array-ext.md)
|
|
10
|
+
- **객체 유틸** (`obj` 네임스페이스, `DeepPartial`/`Type`) — 깊은 복사·동등성·병합·체인 경로 접근·타입 안전 키 순회가 필요할 때. 자세히: [obj.md](./obj.md)
|
|
11
|
+
- **JSON/Worker 직렬화** (`json`/`transfer` 네임스페이스) — 커스텀 타입(날짜·UUID·Map·Set·Error)을 보존하며 JSON 또는 Worker 메시지로 직렬화할 때. 자세히: [json-transfer.md](./json-transfer.md)
|
|
12
|
+
- **비동기 런타임** (`DebounceQueue`/`SerialQueue`/`EventEmitter`/`LazyGcMap`/`createLogger`) — 디바운스·직렬 실행·타입 안전 이벤트·자동 만료 캐시·태그 로거가 필요할 때. 자세히: [async-runtime.md](./async-runtime.md)
|
|
13
|
+
- **`Uuid`** — UUID v4 생성·검증·바이트 변환이 필요할 때. (아래 인라인 "값 타입 보조 — Uuid")
|
|
14
|
+
- **환경변수** (`env`/`parseBoolEnv`) — process.env / import.meta.env 를 런타임 무관하게 읽고 쓸 때. (아래 인라인 "환경변수")
|
|
15
|
+
- **Set/Map 확장** (`Set.prototype.adds`/`toggle`, `Map.prototype.getOrCreate`/`update`) — Set/Map 을 체이닝으로 다룰 때. (아래 인라인 "Set/Map 확장")
|
|
16
|
+
- **문자열 유틸** (`str` 네임스페이스) — 한국어 조사·케이스 변환·전각 변환·빈 문자열 가드가 필요할 때. (아래 인라인 "str")
|
|
17
|
+
- **숫자 유틸** (`num` 네임스페이스) — 느슨한 정수/실수 파싱·천단위 포맷이 필요할 때. (아래 인라인 "num")
|
|
18
|
+
- **바이트 유틸** (`bytes` 네임스페이스) — Uint8Array hex/base64/concat 변환이 필요할 때. (아래 인라인 "bytes")
|
|
19
|
+
- **경로 유틸** (`path` 네임스페이스) — 브라우저에서 POSIX 경로 join/basename/extname 이 필요할 때. (아래 인라인 "path")
|
|
20
|
+
- **XML 유틸** (`xml` 네임스페이스) — XML 파싱/직렬화가 필요할 때. (아래 인라인 "xml")
|
|
21
|
+
- **대기 유틸** (`wait` 네임스페이스) — 조건 폴링·지연이 필요할 때. (아래 인라인 "wait")
|
|
22
|
+
- **에러 메시지 추출** (`err` 네임스페이스) — catch 의 `unknown` 에서 메시지 문자열을 뽑을 때. (아래 인라인 "err")
|
|
23
|
+
- **원시 타입 추론** (`primitive` 네임스페이스, `PrimitiveType*`/`Bytes`) — 런타임 값에서 ORM 원시 타입 문자열을 얻을 때. (아래 인라인 "primitive / 공통 타입")
|
|
24
|
+
- **코드 템플릿 태그** (`js`/`ts`/`html`/`tsql`/`mysql`/`pgsql`) — IDE 하이라이팅 + 들여쓰기 정규화가 필요할 때. (아래 인라인 "템플릿 태그")
|
|
25
|
+
- **`ZipArchive`** — ZIP 읽기/쓰기/압축/해제가 필요할 때. (아래 인라인 "ZipArchive")
|
|
26
|
+
|
|
27
|
+
## 값 타입 보조 — Uuid
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
class Uuid {
|
|
31
|
+
static generate(): Uuid; // crypto.getRandomValues 기반 v4 생성
|
|
32
|
+
static fromBytes(bytes: Bytes): Uuid; // 16바이트 Uint8Array → Uuid (16바이트 아니면 ArgumentError)
|
|
33
|
+
constructor(uuid: string); // 형식 검증 후 보관 (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, 위반 시 ArgumentError)
|
|
34
|
+
toString(): string; // UUID 문자열 반환
|
|
35
|
+
toBytes(): Bytes; // 16바이트 Uint8Array 반환
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- `generate()` — 새 랜덤 UUID 가 필요할 때(엔티티 PK 등). 암호학적으로 안전한 난수 사용.
|
|
40
|
+
- `fromBytes(bytes)` / `toBytes()` — 바이너리 저장/전송과 문자열 표현 사이를 오갈 때. 입력 바이트 길이가 16 이 아니면 `ArgumentError` throw.
|
|
41
|
+
- `new Uuid(str)` — 외부에서 받은 문자열을 검증해 값으로 승격할 때. 형식 불일치 시 `ArgumentError` throw.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const id = Uuid.generate();
|
|
45
|
+
const restored = new Uuid(id.toString());
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 환경변수 — env / parseBoolEnv
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
function env(key: string): string | undefined; // 읽기: process.env 우선, 없으면 import.meta.env
|
|
52
|
+
function env(key: string, value: string): void; // 쓰기: process.env[key] = value (process 존재 시)
|
|
53
|
+
function parseBoolEnv(value: unknown): boolean; // "true"/"1"/"yes"/"on"(대소문자 무시) → true, 그 외 false
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- `env(key)` — 단일 인자. Node 면 `process.env`, 브라우저(Vite) 면 `import.meta.env` 에서 조회. 둘 다 없으면 `undefined`. "값 없음"을 빈 문자열로 치환하지 않음.
|
|
57
|
+
- `env(key, value)` — 2번째 인자 전달 시 쓰기 모드. `process` 가 없는 환경(순수 브라우저)에서는 아무 동작 안 함.
|
|
58
|
+
- `parseBoolEnv(value)` — boolean 플래그용 환경변수를 해석할 때. 위 4개 리터럴만 true.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
if (parseBoolEnv(env("DEV"))) { /* 개발 모드 */ }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Set/Map 확장
|
|
65
|
+
|
|
66
|
+
전역 `Set.prototype` / `Map.prototype` 에 메서드를 추가(import 시 자동 적용, `enumerable: false`).
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
interface Set<T> {
|
|
70
|
+
adds(...values: T[]): this; // 여러 값 일괄 추가, this 반환(체이닝)
|
|
71
|
+
toggle(value: T, addOrDel?: "add" | "del"): this; // 토글: 인자 생략 시 있으면 제거/없으면 추가
|
|
72
|
+
}
|
|
73
|
+
interface Map<K, V> {
|
|
74
|
+
getOrCreate(key: K, newValue: V): V; // 값 직접 지정 (key 없을 때만 set)
|
|
75
|
+
getOrCreate(key: K, newValueFn: () => V): V; // 팩토리 지연 생성 (비싼 연산용)
|
|
76
|
+
update(key: K, updateFn: (v: V | undefined) => V): void; // 현재 값(없으면 undefined) → 새 값으로 갱신
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- `Set.toggle(value, addOrDel)` — `addOrDel` 생략 시 존재 여부로 자동 토글, `"add"` 면 강제 추가, `"del"` 면 강제 제거. 조건부 선택 상태(`isOn ? "add" : "del"`) 표현에 사용.
|
|
81
|
+
- `Map.getOrCreate` — 2번째 인자가 함수면 팩토리로 인식되어 호출됨. 함수 자체를 값으로 저장하려면 `() => myFn` 처럼 한 번 더 감싼다.
|
|
82
|
+
- `Map.update` — key 가 없어도 `updateFn(undefined)` 가 호출되어 새 값이 set 됨. 카운터 증가(`(v) => (v ?? 0) + 1`)·배열 누적에 사용.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
new Set([1, 2]).adds(3, 4).toggle(2); // {1, 3, 4}
|
|
86
|
+
countMap.update("k", (v) => (v ?? 0) + 1);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## str
|
|
90
|
+
|
|
91
|
+
`import * as str` 로 사용하는 문자열 유틸 네임스페이스.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
str.getKoreanSuffix(text: string, type: "을"|"은"|"이"|"와"|"랑"|"로"|"라"): string;
|
|
95
|
+
str.replaceFullWidth(str: string): string;
|
|
96
|
+
str.toPascalCase(str: string): string;
|
|
97
|
+
str.toCamelCase(str: string): string;
|
|
98
|
+
str.toKebabCase(str: string): string;
|
|
99
|
+
str.toSnakeCase(str: string): string;
|
|
100
|
+
str.isNullOrEmpty(str: string | undefined): str is "" | undefined;
|
|
101
|
+
str.insert(str: string, index: number, insertString: string): string;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- `getKoreanSuffix(text, type)` — 마지막 글자 받침 유무로 조사 선택. `type` 은 쌍의 대표 글자: `"을"`=을/를, `"은"`=은/는, `"이"`=이/가, `"와"`=과/와, `"랑"`=이랑/랑, `"로"`=으로/로(단 받침 ㄹ이면 "로"), `"라"`=이라/라. 한글 아닌 글자로 끝나면 받침 없음 처리.
|
|
105
|
+
- `replaceFullWidth(str)` — 전각 영문/숫자/공백/괄호를 반각으로. 스캔된 바코드·일본어 입력 정규화에 사용.
|
|
106
|
+
- `toPascalCase`/`toCamelCase`/`toKebabCase`/`toSnakeCase` — `-` `_` `.` 구분자 또는 대문자 경계 기준 케이스 변환. kebab/snake 는 연속 대문자를 각각 분리(`XMLParser`→`x-m-l-parser`)하고 기존 구분자는 보존.
|
|
107
|
+
- `isNullOrEmpty(str)` — 타입 가드. true 면 `"" | undefined`, false 면 비어있지 않은 `string` 으로 좁혀짐.
|
|
108
|
+
- `insert(str, index, insertString)` — 지정 위치에 삽입한 새 문자열 반환(원본 불변).
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
str.getKoreanSuffix("사과", "을"); // "를"
|
|
112
|
+
str.toKebabCase("HelloWorld"); // "hello-world"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## num
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
num.parseInt(text: unknown): number | undefined; // 비숫자 제거 후 정수 (소수점 이하 버림)
|
|
119
|
+
num.parseFloat(text: unknown): number | undefined; // 비숫자 제거 후 실수
|
|
120
|
+
num.parseRoundedInt(text: unknown): number | undefined; // parseFloat 후 반올림
|
|
121
|
+
num.isNullOrEmpty(val: number | undefined): val is 0 | undefined;
|
|
122
|
+
num.format(val: number, digit?: { max?: number; min?: number }): string;
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- `parseInt`/`parseFloat` — `0-9 . -` 외 문자를 제거하고 파싱. 선행 `-` 만 음수 부호로 유지하고 중간 하이픈은 제거(`"010-1234"`→`101234`). 파싱 불가 시 결측(`undefined`) 그대로 반환. 숫자 입력은 그대로(정수는 trunc).
|
|
126
|
+
- `parseRoundedInt` — 소수 반올림이 필요할 때(`"12.6"`→13).
|
|
127
|
+
- `isNullOrEmpty(val)` — 타입 가드. null/undefined/0 이면 true → `0 | undefined`. "0 과 미입력을 같이 비움 처리"가 필요한 화면 가드용.
|
|
128
|
+
- `format(val, digit)` — `toLocaleString` 기반 천단위 구분. `digit.max` 최대 소수 자릿수, `digit.min` 최소(부족분 0 채움). `val` 이 결측이면 결과도 `undefined`(오버로드).
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
num.parseInt("1,234원"); // 1234
|
|
132
|
+
num.format(1234.5, { min: 2 }); // "1,234.50"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## bytes
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
bytes.concat(arrays: Bytes[]): Bytes;
|
|
139
|
+
bytes.toHex(bytes: Bytes): string; // 소문자 hex
|
|
140
|
+
bytes.fromHex(hex: string): Bytes; // 홀수 길이/비hex 문자 → ArgumentError
|
|
141
|
+
bytes.toBase64(bytes: Bytes): string;
|
|
142
|
+
bytes.fromBase64(base64: string): Bytes; // 비base64 문자/잘못된 길이 → ArgumentError
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- `concat(arrays)` — 여러 Uint8Array 를 하나로 이어붙인 새 배열. 청크 결합에 사용.
|
|
146
|
+
- `toHex`/`fromHex` — 바이너리를 16진 문자열로 표기/복원. `fromHex` 는 길이가 짝수이고 `[0-9a-fA-F]` 만 허용(위반 시 throw).
|
|
147
|
+
- `toBase64`/`fromBase64` — Node 의존 없는 자체 구현 base64. `fromBase64` 는 공백/패딩 정규화 후 검증, 길이 나머지 1 이면 throw.
|
|
148
|
+
|
|
149
|
+
## path
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
path.join(...segments: string[]): string; // POSIX(슬래시) join, 중복 슬래시 정리
|
|
153
|
+
path.basename(filePath: string, ext?: string): string; // 마지막 세그먼트, ext 일치 시 제거
|
|
154
|
+
path.extname(filePath: string): string; // 확장자(점 포함), 숨김파일은 ""
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
- POSIX 슬래시(`/`) 전용. Windows 백슬래시 경로는 미지원. 브라우저·Capacitor 환경에서 Node `path` 대체용.
|
|
158
|
+
- `basename(p, ext)` — `ext` 가 끝과 일치하면 그만큼 잘라 확장자 없는 이름 반환.
|
|
159
|
+
- `extname` — `.gitignore` 같은 선행 점 파일은 빈 문자열(`""`).
|
|
160
|
+
|
|
161
|
+
## xml
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
xml.parse(str: string, options?: { stripTagPrefix?: boolean }): unknown;
|
|
165
|
+
xml.stringify(obj: unknown, options?: XmlBuilderOptions): string;
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
- `parse` — `fast-xml-parser` 기반. 속성은 `$` 객체, 텍스트는 `_` 키, 자식 요소는 배열로. `stripTagPrefix: true` 면 `ns:tag` 의 네임스페이스 접두사 제거(속성 접두사는 유지).
|
|
169
|
+
- `stringify` — `$`(속성)·`_`(텍스트) 규약으로 XML 문자열 생성. `options` 로 fast-xml-parser 빌더 옵션 덮어쓰기.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
xml.parse('<root id="1"><item>hi</item></root>');
|
|
173
|
+
// { root: { $: { id: "1" }, item: [{ _: "hi" }] } }
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## wait
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
wait.until(forwarder: () => boolean | Promise<boolean>, milliseconds?: number, maxCount?: number): Promise<void>;
|
|
180
|
+
wait.time(millisecond: number): Promise<void>;
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
- `until(fn, interval, maxCount)` — `fn` 이 true 될 때까지 `interval`(기본 100ms) 간격으로 폴링. 첫 호출에서 true 면 즉시 반환. `maxCount` 지정 시 초과하면 `TimeoutError` throw(미지정이면 무제한).
|
|
184
|
+
- `time(ms)` — `setTimeout` 기반 지연 Promise.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
await wait.until(() => ready, 100, 50); // 최대 50회(5초) 폴링
|
|
188
|
+
await wait.time(300);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## err
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
err.message(error: unknown): string; // Error 면 .message, 아니면 String(error)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- catch 블록의 `unknown` 에러에서 안전하게 메시지 문자열을 뽑을 때.
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
try { /* ... */ } catch (e) { logger.error(err.message(e)); }
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## primitive / 공통 타입
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
primitive.typeStr(value: PrimitiveTypeMap[PrimitiveTypeStr]): PrimitiveTypeStr; // 런타임 값 → 원시 타입 문자열
|
|
207
|
+
|
|
208
|
+
type Bytes = Uint8Array;
|
|
209
|
+
type PrimitiveTypeMap = { string; number; boolean; DateTime; DateOnly; Time; Uuid; Bytes };
|
|
210
|
+
type PrimitiveTypeStr = keyof PrimitiveTypeMap;
|
|
211
|
+
type PrimitiveType = PrimitiveTypeMap[PrimitiveTypeStr] | undefined;
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
- `primitive.typeStr(value)` — 값의 런타임 타입을 보고 `"string"|"number"|"boolean"|"DateTime"|"DateOnly"|"Time"|"Uuid"|"Bytes"` 중 하나 반환. 위 8종 외 값이면 `ArgumentError` throw. ORM 컬럼 타입 추론과 공유.
|
|
215
|
+
- `Bytes` — 바이너리 표준 별칭(`Uint8Array`). Buffer 대신 사용.
|
|
216
|
+
- `PrimitiveType` — 원시 타입 union + `undefined`. 결측 보존을 위해 `undefined` 포함.
|
|
217
|
+
- `DeepPartial<T>` / `Type<T>` 타입 유틸은 [obj.md](./obj.md) 의 "타입 유틸리티" 참조.
|
|
218
|
+
|
|
219
|
+
## 템플릿 태그
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
js / ts / html / tsql / mysql / pgsql (strings: TemplateStringsArray, ...values: unknown[]): string;
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
- 6개 태그 모두 동작은 동일: 보간 후 공통 들여쓰기 제거 + 앞뒤 빈 줄 trim. 차이는 IDE 의 언어 하이라이팅 힌트뿐(태그 이름이 곧 언어). 보간 값이 null/undefined 면 빈 문자열로 치환.
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
const sql = mysql`
|
|
229
|
+
SELECT *
|
|
230
|
+
FROM users
|
|
231
|
+
`; // 공통 들여쓰기가 제거된 두 줄
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## ZipArchive
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
class ZipArchive {
|
|
238
|
+
constructor(data?: Blob | Bytes); // 데이터 생략 시 새(쓰기용) 아카이브
|
|
239
|
+
extractAll(progressCallback?: (p: { fileName: string; totalSize: number; extractedSize: number }) => void): Promise<Map<string, Bytes | undefined>>;
|
|
240
|
+
get(fileName: string): Promise<Bytes | undefined>;
|
|
241
|
+
exists(fileName: string): Promise<boolean>;
|
|
242
|
+
write(fileName: string, bytes: Bytes): void; // 캐시에만 기록
|
|
243
|
+
compress(): Promise<Bytes>;
|
|
244
|
+
close(): Promise<void>;
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
- `constructor(data)` — `Blob`/`Bytes` 전달 시 읽기용, 생략 시 쓰기용 빈 아카이브.
|
|
249
|
+
- `get`/`exists` — 단일 파일 추출/존재 확인. 결과는 내부 캐시에 보관해 재추출 방지.
|
|
250
|
+
- `extractAll(cb)` — 전체 추출. `cb` 는 파일별 진행(`fileName`, 전체 바이트 `totalSize`, 누적 추출 `extractedSize`)을 보고.
|
|
251
|
+
- `write(name, bytes)` — 캐시에만 기록. 실제 압축 산출은 `compress()` 호출 시.
|
|
252
|
+
- `compress()` — 캐시(필요 시 `extractAll`)의 모든 파일을 ZIP 바이트로. 대용량은 전부 메모리 로드되므로 주의.
|
|
253
|
+
- `close()` — 리더 닫고 캐시 비움. `try/finally` 에서 호출 권장.
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const archive = new ZipArchive(zipBytes);
|
|
257
|
+
try {
|
|
258
|
+
const content = await archive.get("file.txt");
|
|
259
|
+
} finally {
|
|
260
|
+
await archive.close();
|
|
261
|
+
}
|
|
262
|
+
```
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# @simplysm/core-common — Array 확장 메서드
|
|
2
|
+
|
|
3
|
+
패키지를 import 하면 부수효과로 `Array.prototype` 에 메서드가 주입된다(`enumerable: false`, `for...in` 비노출). 함수 호출이 아니라 배열 인스턴스 메서드로 직접 사용. 조회/그룹/정렬/diff 등 읽기 메서드(`ReadonlyArrayExt`)는 새 배열을 반환하고, 변형 메서드(`MutableArrayExt`, 이름에 `This` 가 붙거나 insert/remove/toggle/clear)는 원본을 직접 수정한다.
|
|
4
|
+
|
|
5
|
+
## 조회·필터
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
single(predicate?): TItem | undefined; // 조건 일치 1건. 2건 이상이면 ArgumentError
|
|
9
|
+
first(predicate?): TItem | undefined; // 첫 일치(없으면 undefined)
|
|
10
|
+
last(predicate?): TItem | undefined; // 마지막 일치
|
|
11
|
+
filterExists(): NonNullable<TItem>[]; // null/undefined 제거
|
|
12
|
+
ofType(type: PrimitiveTypeStr | Type<T>): T[]; // 타입별 필터
|
|
13
|
+
filterAsync(predicate: (item, i) => Promise<boolean>): Promise<TItem[]>; // 순차 비동기 필터
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- `single(predicate)` — "정확히 1건" 보장이 필요할 때. 조건 일치가 2건 이상이면 `ArgumentError` throw, 0건이면 `undefined`. predicate 생략 시 배열 전체 대상.
|
|
17
|
+
- `first`/`last` — predicate 생략 시 각각 `[0]`/마지막 요소.
|
|
18
|
+
- `ofType(type)` — `"string"|"number"|"boolean"|"DateTime"|"DateOnly"|"Time"|"Uuid"|"Bytes"` 문자열이면 해당 원시 타입으로, 생성자(`Type<N>`)면 `instanceof`/constructor 일치로 필터. 혼합 배열에서 특정 타입만 뽑을 때.
|
|
19
|
+
- `filterAsync` — predicate 가 Promise 인 경우. 병렬이 아니라 **순차** 실행(부수효과 순서 보장).
|
|
20
|
+
|
|
21
|
+
## 매핑·평탄화
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
mapAsync(selector: (item, i) => Promise<R>): Promise<R[]>; // 순차
|
|
25
|
+
parallelAsync(fn: (item, i) => Promise<R>): Promise<R[]>; // Promise.all 병렬
|
|
26
|
+
mapMany(): U[]; // 중첩 배열 1단 평탄화
|
|
27
|
+
mapMany(selector: (item, i) => R[]): R[]; // 매핑 후 평탄화
|
|
28
|
+
mapManyAsync(selector: (item, i) => Promise<R[]>): Promise<R[]>; // 순차 매핑 후 평탄화
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- `mapAsync` vs `parallelAsync` — 둘 다 비동기 매핑이나 `mapAsync` 는 한 건씩 순차, `parallelAsync` 는 `Promise.all` 동시 실행. `parallelAsync` 는 하나라도 reject 되면 전체 즉시 reject.
|
|
32
|
+
- `mapMany` — 평탄화 시 내부적으로 `filterExists` 가 적용되어 null/undefined 도 제거됨. selector 없으면 자신을 1단 flat.
|
|
33
|
+
|
|
34
|
+
## 그룹화·Map/객체 변환
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
groupBy(keySelector, valueSelector?): { key; values }[];
|
|
38
|
+
toMap(keySelector, valueSelector?): Map<K, V>;
|
|
39
|
+
toMapAsync(keySelector, valueSelector?): Promise<Map<K, V>>;
|
|
40
|
+
toArrayMap(keySelector, valueSelector?): Map<K, V[]>;
|
|
41
|
+
toSetMap(keySelector, valueSelector?): Map<K, Set<V>>;
|
|
42
|
+
toMapValues(keySelector, valueSelector: (items) => V): Map<K, V>;
|
|
43
|
+
toObject(keySelector: (item, i) => string, valueSelector?): Record<string, V>;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- `groupBy(keySelector)` — `{ key, values }` 배열. 원시 key 는 Map 으로 O(n), 객체 key 는 깊은 비교로 O(n²). 원시 key 만 필요하면 `toArrayMap` 이 항상 O(n).
|
|
47
|
+
- `toMap` — key→단일 값. key 중복 시 뒤 값이 덮어씀. `toArrayMap`/`toSetMap` — key→배열/Set(다대일 집계).
|
|
48
|
+
- `toMapValues(keySelector, valueSelector)` — 같은 key 의 항목 배열을 받아 단일 값으로 집계(`(items) => items.sum(...)` 등).
|
|
49
|
+
- `toObject` — key 가 반드시 string. `valueSelector` 생략 시 값은 원소 자체.
|
|
50
|
+
|
|
51
|
+
## 트리·중복·정렬
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
toTree(keyProp: K, parentKey: P): TreeArray<TItem>[]; // TreeArray<T> = T & { children: TreeArray<T>[] }
|
|
55
|
+
distinct(options?: boolean | { matchAddress?: boolean; keyFn?: (item) => string | number }): TItem[];
|
|
56
|
+
orderBy(selector?): TItem[];
|
|
57
|
+
orderByDesc(selector?): TItem[];
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- `toTree(keyProp, parentKey)` — 평면 배열을 `children` 트리로. `parentKey` 값이 null/undefined 인 항목이 루트. 내부 `toArrayMap` 사용 O(n), 원본은 복사되고 `children` 추가.
|
|
61
|
+
- `distinct(options)` — 중복 제거. `true`/`{matchAddress:true}` 면 참조(Set) 비교, `keyFn` 지정 시 key 기준 O(n). 객체 배열을 옵션 없이 쓰면 깊은 비교 O(n²)(대량 데이터엔 `keyFn` 권장).
|
|
62
|
+
- `orderBy`/`orderByDesc(selector)` — selector 반환 타입은 `string|number|DateOnly|DateTime|Time|undefined`. 새 배열 반환. 결측은 비교에서 그대로 처리.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
const tree = items.toTree("id", "parentId");
|
|
66
|
+
const uniq = users.distinct({ keyFn: (u) => u.id });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## diff·merge
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
type ArrayDiffsResult<O, T> =
|
|
73
|
+
| { source: undefined; target: T } // INSERT
|
|
74
|
+
| { source: O; target: undefined } // DELETE
|
|
75
|
+
| { source: O; target: T }; // UPDATE
|
|
76
|
+
type ArrayOneWayDiffResult<T> =
|
|
77
|
+
| { type: "create"; item: T; orgItem: undefined }
|
|
78
|
+
| { type: "update"; item: T; orgItem: T }
|
|
79
|
+
| { type: "same"; item: T; orgItem: T };
|
|
80
|
+
|
|
81
|
+
diffs(target, options?): ArrayDiffsResult<TItem, TOther>[];
|
|
82
|
+
oneWayDiffs(orgItems, keyPropNameOrGetValFn, options?): ArrayOneWayDiffResult<TItem>[];
|
|
83
|
+
merge(target, options?): (TItem | TOther | (TItem & TOther))[];
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- `diffs(target, options)` — 두 배열을 비교해 INSERT/DELETE/UPDATE 분류. `options.keys` 매칭 key 목록, `options.excludes` 비교 제외 속성. target 에 중복 key 가 있으면 첫 매칭만 사용. 서버 동기화 대상 산출에 사용.
|
|
87
|
+
- `oneWayDiffs(orgItems, keyPropNameOrGetValFn, options)` — 현재(this) 배열을 원본(`orgItems`, 배열 또는 `Map`) 대비 create/update/same 으로 분류. `keyPropNameOrGetValFn` 은 key 속성명 또는 key 추출 함수. `options.includeSame` 동일건 포함 여부, `excludes`/`includes` 비교 속성 제한.
|
|
88
|
+
- `merge(target, options)` — 두 배열을 key 기준 병합(없으면 추가, 있으면 속성 합침). `options.keys`/`excludes` 는 `diffs` 와 동일 의미.
|
|
89
|
+
|
|
90
|
+
## 집계
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
sum(selector?: (item, i) => number): number; // 빈 배열이면 0
|
|
94
|
+
min(selector?): TProp | undefined;
|
|
95
|
+
max(selector?): TProp | undefined;
|
|
96
|
+
shuffle(): TItem[];
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- `sum` — selector 생략 시 원소를 숫자로 더함. 빈 배열은 0.
|
|
100
|
+
- `min`/`max` — selector 없으면 원소가 `number|string` 일 때만. 비면 `undefined`.
|
|
101
|
+
- `shuffle` — 무작위 순서의 새 배열.
|
|
102
|
+
|
|
103
|
+
## 변형 메서드 (@mutates 원본 직접 수정)
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
distinctThis(options?): TItem[]; // distinct 의 in-place 판
|
|
107
|
+
orderByThis(selector?): TItem[]; // 오름차순 in-place
|
|
108
|
+
orderByDescThis(selector?): TItem[]; // 내림차순 in-place
|
|
109
|
+
insert(index: number, ...items: TItem[]): this;
|
|
110
|
+
remove(item: TItem): this;
|
|
111
|
+
remove(selector: (item, i) => boolean): this;
|
|
112
|
+
toggle(item: TItem): this; // 있으면 제거, 없으면 추가
|
|
113
|
+
clear(): this; // 비우기
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- `*This` 계열·`insert`/`remove`/`toggle`/`clear` 는 원본 배열을 직접 바꾸고 `this`(또는 배열)를 반환해 체이닝 가능. 새 배열이 필요하면 `This` 없는 `orderBy`/`distinct` 사용.
|
|
117
|
+
- `remove` — 값 또는 조건 함수 오버로드. `toggle` — 멤버십 토글로 선택 상태 관리에 사용.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
list.remove((x) => x.deleted).orderByThis((x) => x.seq);
|
|
121
|
+
```
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# @simplysm/core-common — 비동기 런타임
|
|
2
|
+
|
|
3
|
+
비동기 실행 흐름·이벤트·캐시·로깅을 다룰 때 함께 읽히는 묶음. `DebounceQueue`/`SerialQueue`(실행 큐)는 `EventEmitter` 를 상속해 `error` 이벤트를 발행하며, `LazyGcMap`(자동 만료 캐시)·`createLogger`(태그 로거)와 함께 쓰인다.
|
|
4
|
+
|
|
5
|
+
## EventEmitter
|
|
6
|
+
|
|
7
|
+
브라우저/Node 공통의 타입 안전 이벤트 이미터(내부적으로 `EventTarget` 사용).
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
class EventEmitter<TEvents extends { [K in keyof TEvents]: unknown } = Record<string, unknown>> {
|
|
11
|
+
on<K extends keyof TEvents & string>(type: K, listener: (data: TEvents[K]) => void): void;
|
|
12
|
+
off<K extends keyof TEvents & string>(type: K, listener: (data: TEvents[K]) => void): void;
|
|
13
|
+
emit<K extends keyof TEvents & string>(type: K, ...args: TEvents[K] extends void ? [] : [data: TEvents[K]]): void;
|
|
14
|
+
listenerCount<K extends keyof TEvents & string>(type: K): number;
|
|
15
|
+
dispose(): void;
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- `TEvents` — 이벤트명→데이터 타입 맵. `void` 타입 이벤트는 `emit("done")` 처럼 인자 없이 발행.
|
|
20
|
+
- `on` — 같은 (type, listener) 조합 중복 등록은 무시. `off` — 해당 listener 제거.
|
|
21
|
+
- `emit` — 등록 리스너에 데이터 디스패치. `listenerCount(type)` — 현재 리스너 수(없으면 0). `dispose` — 전체 리스너 해제.
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
interface MyEvents { data: string; done: void; }
|
|
25
|
+
class My extends EventEmitter<MyEvents> {}
|
|
26
|
+
const e = new My();
|
|
27
|
+
e.on("data", (s) => console.log(s));
|
|
28
|
+
e.emit("data", "hi");
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## DebounceQueue
|
|
32
|
+
|
|
33
|
+
짧은 시간 내 여러 호출 중 **마지막만** 실행하는 디바운스 큐. `EventEmitter<{ error: SdError }>` 상속.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
class DebounceQueue extends EventEmitter<{ error: SdError }> {
|
|
37
|
+
constructor(delay?: number); // 지연 ms (생략 시 다음 이벤트 루프에 즉시)
|
|
38
|
+
run(fn: () => void | Promise<void>): void; // 대기 작업 등록(기존 대기 작업 교체)
|
|
39
|
+
override dispose(): void; // 타이머·대기 작업 정리
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- `constructor(delay)` — 디바운스 지연. 생략하면 0(다음 틱 실행).
|
|
44
|
+
- `run(fn)` — 호출할 때마다 이전 대기 fn 을 교체. 지연 후 마지막 fn 만 실행. **실행 중**에 들어온 추가 요청은 지연 없이 현재 실행 직후 즉시 처리(의도적 설계 — 누락 방지).
|
|
45
|
+
- 에러 처리: fn 이 throw 하면 `SdError` 로 감싸, `error` 리스너가 있으면 `emit("error")`, 없으면 내부 로거로 출력. (문제 발생 = error 심각도)
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
const q = new DebounceQueue(300);
|
|
49
|
+
q.on("error", (e) => console.error(e));
|
|
50
|
+
input.addEventListener("input", () => q.run(() => search(input.value)));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## SerialQueue
|
|
54
|
+
|
|
55
|
+
추가된 작업을 **순차** 실행하는 큐(이전 완료 후 다음 시작). 에러가 나도 후속 작업은 계속 진행. `EventEmitter<{ error: SdError }>` 상속.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
class SerialQueue extends EventEmitter<{ error: SdError }> {
|
|
59
|
+
constructor(gap?: number); // 작업 사이 간격 ms (기본 0)
|
|
60
|
+
run(fn: () => void | Promise<void>): void; // 큐에 추가하고 실행 시작
|
|
61
|
+
override dispose(): void; // 대기 큐 비움(실행 중 작업은 완료됨)
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- `constructor(gap)` — 각 작업 사이 대기 간격(ms). 0 이면 연속 실행.
|
|
66
|
+
- `run(fn)` — FIFO 로 순차 실행. 한 작업이 throw 하면 `SdError` 로 감싸 `error` 이벤트 발행(리스너 없으면 로그) 후 다음 작업 진행 — 후속 작업을 막지 않으려는 의도.
|
|
67
|
+
- `dispose` — 아직 시작 안 한 대기분만 제거(실행 중인 건은 완료).
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const q = new SerialQueue();
|
|
71
|
+
q.run(async () => save(a)); // 순서 보장
|
|
72
|
+
q.run(async () => save(b));
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## LazyGcMap
|
|
76
|
+
|
|
77
|
+
마지막 접근 이후 일정 시간이 지나면 항목을 자동 삭제하는 Map(LRU 접근 시간 갱신). 타이머를 쓰므로 사용 후 `dispose()` 필수.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
class LazyGcMap<TKey, TValue> {
|
|
81
|
+
constructor(options: {
|
|
82
|
+
gcInterval?: number; // GC 주기 ms (기본: expireTime/10, 최소 1000)
|
|
83
|
+
expireTime: number; // 만료 시간 ms (마지막 접근 이후)
|
|
84
|
+
onExpire?: (key: TKey, value: TValue) => void | Promise<void>;
|
|
85
|
+
});
|
|
86
|
+
get size: number;
|
|
87
|
+
has(key): boolean; // 접근 시간 갱신 안 함
|
|
88
|
+
get(key): TValue | undefined; // 접근 시간 갱신(LRU)
|
|
89
|
+
set(key, value): void; // 저장 + GC 타이머 시작
|
|
90
|
+
delete(key): boolean; // 비면 타이머 중지
|
|
91
|
+
getOrCreate(key, factory: () => TValue): TValue; // dispose 후 호출 시 throw
|
|
92
|
+
clear(): void; // 항목만 비움(재사용 가능)
|
|
93
|
+
dispose(): void; // 타이머 중지 + 비움(이후 set/get 무력화)
|
|
94
|
+
values(): IterableIterator<TValue>;
|
|
95
|
+
keys(): IterableIterator<TKey>;
|
|
96
|
+
entries(): IterableIterator<[TKey, TValue]>;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- `expireTime` — 필수. 마지막 접근 후 이 시간이 지나면 만료. `gcInterval` — 만료 스캔 주기(미지정 시 `expireTime/10`, 최소 1000ms).
|
|
101
|
+
- `onExpire(key, value)` — 만료 직전 호출(비동기 가능). 콜백이 throw 해도 로그만 남기고 GC 계속. 콜백 도중 같은 key 가 재등록되면 새 항목은 삭제하지 않음.
|
|
102
|
+
- `get` 은 접근 시간을 갱신(LRU), `has` 는 갱신하지 않음. `set` 이 호출돼야 GC 타이머가 시작되고, 항목이 모두 비면 타이머가 자동 중지.
|
|
103
|
+
- `getOrCreate` 는 dispose 이후 호출하면 throw(silent 동작 금지). `dispose` 미호출 시 타이머가 계속 돌아 메모리 누수.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
const cache = new LazyGcMap<string, Session>({ expireTime: 60000 });
|
|
107
|
+
try {
|
|
108
|
+
const s = cache.getOrCreate(id, () => loadSession(id));
|
|
109
|
+
} finally {
|
|
110
|
+
cache.dispose();
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## createLogger
|
|
115
|
+
|
|
116
|
+
`consola` 기반 태그 로거를 지연 생성하는 팩토리. 모듈 최상위에서 호출해도 안전(첫 메서드 접근 시점까지 `consola.withTag` 생성을 미뤄, 이후 `setupConsola()` 의 level/reporter 변경이 반영됨).
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
createLogger(tag: string): ConsolaInstance;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- `tag` — 로그에 붙는 태그(클래스/모듈명 등). 반환값은 `consola` 인스턴스라 `.info`/`.warn`/`.error`/`.success` 등을 그대로 사용.
|
|
123
|
+
- 즉시 `consola.withTag()` 를 부르면 호출 시점 옵션이 고정되는 문제를 피하기 위한 Proxy 래퍼. 테스트의 `vi.spyOn` 과도 호환.
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const logger = createLogger("MyService");
|
|
127
|
+
logger.error("처리 실패", err);
|
|
128
|
+
```
|