@simplysm/sd-claude 14.0.88 → 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.
Files changed (135) hide show
  1. package/claude/references/sd-simplysm14/README.md +16 -17
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +39 -43
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +174 -80
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +41 -50
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +60 -26
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +109 -37
  7. package/claude/references/sd-simplysm14/apis/angular/infra.md +61 -44
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +39 -31
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +73 -85
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +54 -39
  11. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +55 -30
  12. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +71 -67
  13. package/claude/references/sd-simplysm14/apis/angular/sheet.md +82 -72
  14. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +35 -36
  15. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +71 -43
  16. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +38 -30
  17. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +45 -50
  18. package/claude/references/sd-simplysm14/apis/core-browser/README.md +42 -55
  19. package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +62 -0
  20. package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +39 -38
  21. package/claude/references/sd-simplysm14/apis/core-common/README.md +95 -103
  22. package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +59 -54
  23. package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +86 -0
  24. package/claude/references/sd-simplysm14/apis/core-common/datetime.md +57 -66
  25. package/claude/references/sd-simplysm14/apis/core-common/errors.md +86 -0
  26. package/claude/references/sd-simplysm14/apis/core-common/obj.md +60 -42
  27. package/claude/references/sd-simplysm14/apis/core-common/serialization.md +55 -0
  28. package/claude/references/sd-simplysm14/apis/core-node/README.md +10 -8
  29. package/claude/references/sd-simplysm14/apis/core-node/consola.md +29 -32
  30. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +34 -22
  31. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +29 -25
  32. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +40 -53
  33. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +22 -29
  34. package/claude/references/sd-simplysm14/apis/core-node/worker.md +31 -31
  35. package/claude/references/sd-simplysm14/apis/excel/README.md +26 -26
  36. package/claude/references/sd-simplysm14/apis/excel/cell.md +37 -29
  37. package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +29 -15
  38. package/claude/references/sd-simplysm14/apis/excel/style.md +33 -27
  39. package/claude/references/sd-simplysm14/apis/excel/utils.md +29 -19
  40. package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +78 -55
  41. package/claude/references/sd-simplysm14/apis/excel/wrapper.md +42 -45
  42. package/claude/references/sd-simplysm14/apis/lint/README.md +27 -21
  43. package/claude/references/sd-simplysm14/apis/lint/rules.md +89 -49
  44. package/claude/references/sd-simplysm14/apis/orm-common/README.md +6 -62
  45. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +149 -67
  46. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +111 -99
  47. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +115 -72
  48. package/claude/references/sd-simplysm14/apis/orm-common/schema.md +134 -92
  49. package/claude/references/sd-simplysm14/apis/orm-common/types.md +67 -52
  50. package/claude/references/sd-simplysm14/apis/orm-node/README.md +63 -26
  51. package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +51 -40
  52. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +10 -12
  53. package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +92 -45
  54. package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +226 -108
  55. package/claude/references/sd-simplysm14/apis/service-client/README.md +90 -88
  56. package/claude/references/sd-simplysm14/apis/service-client/orm.md +37 -29
  57. package/claude/references/sd-simplysm14/apis/service-client/transport.md +45 -20
  58. package/claude/references/sd-simplysm14/apis/service-common/README.md +89 -40
  59. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +126 -34
  60. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +109 -54
  61. package/claude/references/sd-simplysm14/apis/service-server/README.md +70 -66
  62. package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +47 -47
  63. package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +71 -34
  64. package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +31 -32
  65. package/claude/references/sd-simplysm14/apis/storage/README.md +34 -28
  66. package/claude/references/sd-simplysm14/manuals/client-app-structure.md +142 -140
  67. package/claude/references/sd-simplysm14/manuals/client-orm.md +1 -1
  68. package/claude/references/sd-simplysm14/manuals/client-service.md +19 -7
  69. package/claude/references/sd-simplysm14/manuals/client-shared-data.md +2 -2
  70. package/claude/references/sd-simplysm14/manuals/client-system-log.md +11 -3
  71. package/claude/references/sd-simplysm14/manuals/data-log.md +0 -1
  72. package/claude/references/sd-simplysm14/manuals/orm.md +16 -0
  73. package/claude/rules/sd-design-rules.md +10 -0
  74. package/claude/skills/sd-docs/SKILL.md +58 -46
  75. package/claude/skills/sd-docs/references/{doc-rules.md → subagent-prompt.md} +103 -103
  76. package/claude/skills/sd-impl/SKILL.md +1 -1
  77. package/claude/skills/sd-spec/SKILL.md +858 -858
  78. package/claude/skills/sd-spec/references/example-spec.md +26 -36
  79. package/package.json +1 -1
  80. package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +0 -47
  81. package/claude/references/sd-simplysm14/apis/orm-common/query-builder.md +0 -29
  82. package/claude/skills/sd-demo/evals/fixtures/inventory-list/.specs/inventory/spec.md +0 -99
  83. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/package.json +0 -12
  84. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/index.ts +0 -3
  85. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inbound/inbound.list.ts +0 -150
  86. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inventory/inventory-master.list.ts +0 -143
  87. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/outbound/outbound.list.ts +0 -150
  88. package/claude/skills/sd-demo/evals/fixtures/inventory-list/pnpm-workspace.yaml +0 -2
  89. package/claude/skills/sd-demo/evals/fixtures/inventory-list/sd.config.ts +0 -12
  90. package/claude/skills/sd-demo/evals/golden.jsonl +0 -1
  91. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/package.json +0 -8
  92. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/src/.gitkeep +0 -0
  93. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tests/.gitkeep +0 -0
  94. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tsconfig.json +0 -10
  95. package/claude/skills/sd-dev/evals/golden.jsonl +0 -1
  96. package/claude/skills/sd-docs/evals/fixtures/new-write/.claude/references/sd-simplysm14/README.md +0 -7
  97. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/package.json +0 -5
  98. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/src/index.ts +0 -3
  99. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/package.json +0 -6
  100. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/src/index.ts +0 -1
  101. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/package.json +0 -5
  102. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/src/index.ts +0 -8
  103. package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/README.md +0 -7
  104. package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/apis/foo/README.md +0 -3
  105. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/package.json +0 -5
  106. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/src/index.ts +0 -3
  107. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/package.json +0 -6
  108. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/src/index.ts +0 -1
  109. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/package.json +0 -5
  110. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/src/index.ts +0 -8
  111. package/claude/skills/sd-docs/evals/golden.jsonl +0 -2
  112. package/claude/skills/sd-impl/evals/fixtures/case-a-new-screen/.specs/260513120000_warehouse/spec.md +0 -101
  113. package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/.specs/260513120000_warehouse/spec.md +0 -101
  114. package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/packages/app/src/screens/box-register/box-register.view.ts +0 -46
  115. package/claude/skills/sd-impl/evals/fixtures/case-c-new-cross/.specs/260513120000_warehouse/spec.md +0 -89
  116. package/claude/skills/sd-impl/evals/fixtures/case-d-spec-modify/.specs/260513120000_warehouse/spec.md +0 -101
  117. package/claude/skills/sd-impl/evals/golden.jsonl +0 -4
  118. package/claude/skills/sd-manual/evals/fixtures/new-manual/src/notification.ts +0 -25
  119. package/claude/skills/sd-manual/evals/fixtures/update-manual/.claude/references/sd-simplysm14/manuals/notification.md +0 -14
  120. package/claude/skills/sd-manual/evals/fixtures/update-manual/src/notification.ts +0 -37
  121. package/claude/skills/sd-manual/evals/golden.jsonl +0 -2
  122. package/claude/skills/sd-review/evals/fixtures/code-review/src/foo.ts +0 -7
  123. package/claude/skills/sd-review/evals/fixtures/doc-review/docs/foo.md +0 -4
  124. package/claude/skills/sd-review/evals/golden.jsonl +0 -2
  125. package/claude/skills/sd-skill/evals/fixtures/existing-skill/.claude/skills/todo-format/SKILL.md +0 -14
  126. package/claude/skills/sd-skill/evals/fixtures/new-skill/.gitkeep +0 -0
  127. package/claude/skills/sd-skill/evals/golden.jsonl +0 -2
  128. package/claude/skills/sd-spec/evals/fixtures/case-a-split//355/232/214/354/235/230/353/241/235.md +0 -20
  129. package/claude/skills/sd-spec/evals/fixtures/case-b-detail/.specs/260513120000_warehouse/spec.md +0 -95
  130. package/claude/skills/sd-spec/evals/golden.jsonl +0 -2
  131. package/claude/skills/sd-unpack/evals/fixtures/eml-with-text-attachment/meeting.eml +0 -21
  132. package/claude/skills/sd-unpack/evals/fixtures/simple-eml/meeting.eml +0 -10
  133. package/claude/skills/sd-unpack/evals/golden.jsonl +0 -2
  134. package/claude/skills/sd-use/evals/fixtures/empty/.gitkeep +0 -0
  135. package/claude/skills/sd-use/evals/golden.jsonl +0 -6
@@ -1,67 +1,62 @@
1
1
  # @simplysm/capacitor-plugin-usb-storage
2
2
 
3
- USB Mass Storage 장치를 읽는 Capacitor 플러그인. Android 는 libaums 실제 USB 저장소에 접근하고, 브라우저(web)는 IndexedDB 기반 가상 USB 저장소로 에뮬레이션함. 외부 노출 심볼은 정적 클래스 `UsbStorage`, 타입 정의 3종, 네이티브 인터페이스 `UsbStoragePlugin`.
3
+ Capacitor 플러그인. 연결된 USB Mass Storage 장치를 열거하고 권한을 얻은 뒤 디렉토리/파일을 읽는다. Android 는 libaums 네이티브 구현, 브라우저는 IndexedDB 기반 가상 USB 저장소로 에뮬레이션된다. entry 노출하는 심볼은 정적 클래스 `UsbStorage` 입출력 타입 4종.
4
4
 
5
5
  ## 사용 트리거 인덱스
6
6
 
7
- - **UsbStorage** — USB 장치 목록 조회·권한 요청/확인·디렉토리/파일 읽기를 쓰는 정적 진입점. 모든 동작이 `vendorId`/`productId` 대상 장치를 지정.
8
- - **UsbDeviceFilter / UsbDeviceInfo / UsbFileInfo** — 위 메서드의 인자·반환 타입. 장치 식별·열거·항목 분기 함께 참조.
9
- - **UsbStoragePlugin** — `UsbStorage` 가 내부적으로 감싸는 Capacitor 네이티브 인터페이스. 보통 직접 호출하지 않음.
7
+ - **UsbStorage** — USB 저장 장치 접근의 진입점. "장치 목록 조회 권한 확인/요청 readdir/readFile" 흐름을 정적 메서드로 호출할 때.
8
+ - **UsbDeviceInfo / UsbDeviceFilter / UsbFileInfo / UsbStoragePlugin** — 위 메서드의 입출력 타입. 장치 식별(vendorId/productId), 반환 항목(`name`/`isDirectory`) 형태를 다룰 때 참조.
10
9
 
11
- ## UsbStorage (정적 클래스)
10
+ ## UsbStorage
12
11
 
13
- 모든 메서드 정적, 인스턴스화 불필요. 장치 지정은 항상 `UsbDeviceFilter`(vendorId+productId).
12
+ `abstract class` 이며 모든 멤버가 `static` 인스턴스화하지 않고 `UsbStorage.메서드()` 로 호출한다. 내부적으로 `registerPlugin<UsbStoragePlugin>("UsbStorage")` 로 얻은 네이티브(Android)/웹 구현에 위임하고, 래퍼 객체(`{ devices }`·`{ granted }`·`{ files }`·`{ data }`)에서 값을 꺼내 평탄화해 반환한다. 대상 장치는 항상 `UsbDeviceFilter`(= `{ vendorId, productId }`) 로 지정하며 readdir/readFile 도 매 호출마다 filter 를 받는다(상태 없음).
14
13
 
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` 변환.
14
+ - `static getDevices(): Promise<UsbDeviceInfo[]>` — 현재 연결된 USB 장치 목록 조회. 권한과 무관하게 열거되며, 반환 항목의 `vendorId`/`productId` 를 추려 이후 호출의 filter 로 사용. 흐름의 첫 단계.
15
+ - `static requestPermissions(filter: UsbDeviceFilter): Promise<boolean>` — 지정 장치 접근 권한을 사용자에게 요청(다이얼로그). 반환 true=승인, false=거부(이때 읽기 중단). 권한이 없을 때 읽기 직전 호출. 브라우저 웹 구현은 항상 true 반환.
16
+ - `static checkPermissions(filter: UsbDeviceFilter): Promise<boolean>` — 다이얼로그 없이 현재 권한 보유 여부만 확인. true=이미 보유(requestPermissions 생략 가능), false=미보유(요청 필요). 권한이 이미 있을 때 요청을 건너뛰려는 분기 기준. 브라우저 웹 구현은 항상 true 를 반환.
17
+ - `static readdir(filter: UsbDeviceFilter, dirPath: string): Promise<UsbFileInfo[]>` — 지정 장치의 `dirPath`(읽을 디렉토리 경로, 루트는 `"/"`) 바로 아래 항목 목록 조회. 각 항목은 `name`+`isDirectory` 로 파일/폴더 구분. 트리를 순회하려면 `isDirectory === true` 인 항목을 다시 readdir. 웹 구현은 장치가 없거나 대상이 디렉토리가 아니면 빈 배열.
18
+ - `static readFile(filter: UsbDeviceFilter, filePath: string): Promise<Bytes | undefined>` — 지정 장치의 `filePath`(읽을 파일 경로) 내용을 `Bytes`(`@simplysm/core-common`) 읽음. 파일이 없거나 네이티브가 데이터 없음(`null`)을 주면 `undefined` 반환 "빈 파일" 과 "없음" 을 구분하려면 이 `undefined` 그대로 검사(`""`·기본값 치환 금지). 내부에서 base64 응답을 `bytes.fromBase64` 로 복원.
29
19
 
30
20
  사용 예:
31
21
 
32
22
  ```ts
33
- const devices = await UsbStorage.getDevices();
34
- const filter = { vendorId: devices[0].vendorId, productId: devices[0].productId };
23
+ import { UsbStorage } from "@simplysm/capacitor-plugin-usb-storage";
24
+
25
+ const [device] = await UsbStorage.getDevices();
26
+ if (!device) return;
27
+ const filter = { vendorId: device.vendorId, productId: device.productId };
28
+
35
29
  if (!(await UsbStorage.checkPermissions(filter))) {
36
30
  if (!(await UsbStorage.requestPermissions(filter))) return; // 거부 시 중단
37
31
  }
38
- const files = await UsbStorage.readdir(filter, "/");
39
- const data = await UsbStorage.readFile(filter, "/data.bin"); // Bytes | undefined
40
- ```
41
32
 
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` 정적 메서드가 이를 감싸며 `{ ... }` 래퍼 객체에서 값을 꺼내므로 보통 직접 호출 불필요. 직접 다뤄야 할 때만 참조.
33
+ for (const entry of await UsbStorage.readdir(filter, "/")) {
34
+ if (entry.isDirectory) continue; // 폴더면 하위 readdir 로 순회
35
+ const data = await UsbStorage.readFile(filter, `/${entry.name}`);
36
+ if (data === undefined) continue; // 파일 없음/데이터 없음
37
+ // data: Bytes 사용
38
+ }
39
+ ```
62
40
 
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` 변환).
41
+ ## 입출력 타입
42
+
43
+ `UsbStoragePlugin` 은 Capacitor 가 `registerPlugin<UsbStoragePlugin>("UsbStorage")` 등록하는 네이티브 측 계약이며, 위 `UsbStorage` 정적 메서드가 이를 1:1 로 감싸 쓰기 쉬운 형태(배열·boolean·Bytes)로 변환한다. 보통 직접 호출하지 않고 타입 참조용으로만 본다.
44
+
45
+ - `UsbDeviceInfo` — `getDevices()` 가 돌려주는 장치 정보 1건.
46
+ - `deviceName: string` — OS 가 부여한 장치 시스템 이름. 화면 표시·로그용.
47
+ - `manufacturerName: string` — 제조사 이름. 사용자에게 장치를 식별시키거나 동일 제품명을 구분할 때.
48
+ - `productName: string` — 제품 이름. 사용자에게 어떤 장치인지 보여줄 때.
49
+ - `vendorId: number` — USB 벤더 ID. 이후 filter 의 `vendorId` 로 그대로 사용.
50
+ - `productId: number` — USB 제품 ID. `vendorId` 와 조합해 장치를 유일하게 지정, 이후 filter 의 `productId` 로 그대로 사용.
51
+ - `UsbDeviceFilter` — 접근 대상 장치를 지정하는 키. 권한·읽기 메서드 모두 이 형태를 받음. 보통 `UsbDeviceInfo` 에서 두 ID 만 추려 만듦.
52
+ - `vendorId: number` — 대상 장치 벤더 ID. `UsbDeviceInfo.vendorId` 를 그대로 넘김.
53
+ - `productId: number` — 대상 장치 제품 ID. `UsbDeviceInfo.productId` 를 그대로 넘김.
54
+ - `UsbFileInfo` — `readdir()` 가 돌려주는 디렉토리 항목 1건.
55
+ - `name: string` — 파일/폴더 이름(경로가 아닌 단일 세그먼트). readFile/하위 readdir 경로를 만들 때 부모 경로에 이어붙임.
56
+ - `isDirectory: boolean` — 디렉토리 여부. true 면 폴더(다시 readdir 대상), false 면 파일(readFile 대상). 트리 순회·파일 필터링의 분기 기준.
57
+ - `UsbStoragePlugin` — Capacitor `registerPlugin` 대상 인터페이스. `UsbStorage` 가 평탄화하기 전의 원형 시그니처로, 메서드는 옵션 객체 입력 / 래퍼 객체 출력을 가짐.
58
+ - `getDevices(): Promise<{ devices: UsbDeviceInfo[] }>` — 장치 목록을 `devices` 키로 반환.
59
+ - `requestPermissions(options: UsbDeviceFilter): Promise<{ granted: boolean }>` — 권한 요청 결과를 `granted` 로 반환.
60
+ - `checkPermissions(options: UsbDeviceFilter): Promise<{ granted: boolean }>` — 권한 보유 여부를 `granted` 로 반환.
61
+ - `readdir(options: UsbDeviceFilter & { path: string }): Promise<{ files: UsbFileInfo[] }>` — filter 에 `path`(대상 디렉토리 경로)를 합친 입력으로 항목을 `files` 로 반환.
62
+ - `readFile(options: UsbDeviceFilter & { path: string }): Promise<{ data: string | null }>` — filter 에 `path`(대상 파일 경로)를 합친 입력으로 파일을 base64 문자열 `data` 로 반환. 데이터 없으면 `null`(상위 `UsbStorage.readFile` 가 `undefined` 로 변환).
@@ -1,83 +1,70 @@
1
1
  # @simplysm/core-browser
2
2
 
3
- 브라우저 전용 유틸리티. DOM `Element`/`HTMLElement` 프로토타입 확장(import 사이드 이펙트로 등록)과 클립보드·다운로드·파일선택·fetch·IndexedDB 헬퍼를 제공.
3
+ 브라우저 전용 유틸리티 묶음. DOM 요소 탐색·위치 계산·가시성 판정 확장, IndexedDB 영속화 그 위의 가상 파일시스템, 파일 다운로드·선택 헬퍼를 제공.
4
+
5
+ > 패키지의 어떤 심볼이든 import 하면 `index.ts` 가 `import "./extensions/element-ext"`·`"./extensions/html-element-ext"` 를 실행해 `Element`/`HTMLElement` 프로토타입 확장이 사이드 이펙트로 자동 등록됨. 별도 초기화 없이 `el.findAll(...)` 식으로 호출 가능.
4
6
 
5
7
  ## 사용 트리거 인덱스
6
8
 
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)
9
+ - **DOM 요소 확장·헬퍼** — DOM 조회(`findAll`/`findFirst`), 조상/탭 이동 가능 요소 탐색, offset·가시성 판정, 부모 기준 상대 좌표 계산, 가림 보정 스크롤, 강제 리페인트, 클립보드 복사/붙여넣기 핸들러, 다중 요소 경계 측정이 필요할 때. 자세히: [dom-element.md](./dom-element.md)
10
+ - **IndexedDB 영속화** (`IndexedDbStore`, `IndexedDbVirtualFs`) 브라우저 IndexedDB KV 영구 저장하거나, 위에 경로 기반 가상 파일트리(파일/디렉터리)를 올릴 때. 자세히: [indexed-db.md](./indexed-db.md)
11
+ - **downloadBlob / fetchUrlBytes / openFileDialog** 생성한 Blob 파일로 저장하거나, 진행률을 보며 URL 바이너리를 받거나, 파일 선택 대화상자를 코드로 때. (아래 인라인 섹션)
12
12
 
13
- ## Element 확장 메서드
13
+ ## 파일·다운로드 유틸
14
14
 
15
- `import "@simplysm/core-browser"`(또는 패키지 어떤 심볼이든 import) `index.ts` `import "./extensions/..."` `Element.prototype` 에 등록함. 별도 초기화 호출 불필요.
15
+ 생성한 데이터를 파일로 내보내거나, 외부 바이너리를 받거나, 사용자에게 파일을 고르게 쓰는 단발성 함수들.
16
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).
17
+ ### downloadBlob
25
18
 
26
19
  ```ts
27
- import "@simplysm/core-browser";
28
- const rows = containerEl.findAll<HTMLElement>("tr");
29
- const first = containerEl.findFirstTabbableChild();
20
+ function downloadBlob(blob: Blob, fileName: string): void;
30
21
  ```
31
22
 
32
- ## HTMLElement 확장 메서드
33
-
34
- `HTMLElement.prototype` 에 등록되는 메서드. 위와 동일하게 import 만으로 활성화.
23
+ Blob object URL 로 만들어 동적 `a[download]` 클릭으로 저장하고, object URL 은 1초 뒤 revoke. 클릭 직후 함수가 반환됨(다운로드 완료를 기다리지 않음). 화면 다운로드 버튼 핸들러에서 즉시 저장할 때.
35
24
 
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 }`.
25
+ - blob: Blob저장할 데이터. 엑셀·이미지·텍스트 메모리에서 만든 Blob 그대로 전달.
26
+ - fileName: string 저장 파일명. `sanitize-filename` 으로 OS 금지 문자·예약어를 제거한 추가로 `[`·`]` 제거하며, 결과가 문자열이면 `"download"` 대체. 확장자 포함, 사용자 입력 파일명을 그대로 넣어도 안전.
42
27
 
43
28
  ```ts
44
- const { top, left } = popupEl.getRelativeOffset(".container");
45
- scrollEl.scrollIntoViewIfNeeded({ top: cellTop, left: cellLeft }, { top: headerH, left: fixedW });
29
+ downloadBlob(new Blob([buf], { type: "application/pdf" }), "보고서[2026].pdf");
46
30
  ```
47
31
 
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` 값.
32
+ ### fetchUrlBytes / DownloadProgress
58
33
 
59
34
  ```ts
60
- inputEl.addEventListener("copy", copyElement);
61
- const bounds = await getBounds([elA, elB], 3000);
35
+ interface DownloadProgress { receivedLength: number; contentLength: number }
36
+ function fetchUrlBytes(
37
+ url: string,
38
+ options?: { onProgress?: (progress: DownloadProgress) => void },
39
+ ): Promise<Uint8Array>;
62
40
  ```
63
41
 
64
- ## 다운로드·파일선택·fetch
42
+ URL 바이너리를 스트림 reader 로 다운로드하며 진행률을 보고. 큰 파일을 진행 바와 함께 받을 때.
65
43
 
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).
44
+ - url: string — 다운로드 대상 URL. `response.ok` 아니면 `Error("다운로드 실패: <status> <statusText>")`, 본문 reader 없으면 `Error("응답 본문을 읽을 없습니다")` throw.
45
+ - options.onProgress: (progress: DownloadProgress) => void — 청크 수신마다 호출되는 진행 콜백. `Content-Length` 헤더가 있는 경로에서만 호출됨(헤더가 없으면 청크를 모아 `bytes.concat` 으로 마지막에 한 번 병합 → chunked encoding 이라 중간 보고 없음). 진행 바 갱신이 필요할 때만 전달.
46
+ - DownloadProgress.receivedLength: number지금까지 받은 누적 바이트 수.
47
+ - DownloadProgress.contentLength: number 전체 바이트 수(`Content-Length` 헤더 값, 없으면 0). 헤더가 있으면 크기로 버퍼를 사전 할당하고, 수신량이 헤더 값을 초과·미달하면 무결성 위반으로 Error throw.
76
48
 
77
49
  ```ts
78
- downloadBlob(new Blob([buf]), "보고서.xlsx");
79
- const files = await openFileDialog({ accept: ".csv", multiple: true });
80
50
  const data = await fetchUrlBytes("/api/file", {
81
51
  onProgress: (p) => setPct(p.receivedLength / p.contentLength),
82
52
  });
83
53
  ```
54
+
55
+ ### openFileDialog
56
+
57
+ ```ts
58
+ function openFileDialog(options?: { accept?: string; multiple?: boolean }): Promise<File[] | undefined>;
59
+ ```
60
+
61
+ 동적 `input[type=file]` 을 만들어 클릭, 파일 선택 대화상자를 표시. 업로드 버튼 핸들러에서 호출.
62
+
63
+ - options.accept: string — 허용 MIME/확장자 필터(input `accept` 에 그대로 전달, 예: `".png,.jpg"`, `"image/*"`). 미지정 시 제한 없음.
64
+ - options.multiple: boolean — 다중 선택 허용. true 면 여러 파일 선택 가능, 기본 `false`(단일). 여러 파일을 한 번에 받을 화면이면 true.
65
+ - 반환: 선택 파일이 있으면 `File[]`, 사용자가 취소하거나(`cancel` 이벤트) 0개 선택이면 `undefined`. 결측을 빈 배열로 뭉개지 않으므로 `== null` 로 취소를 구분.
66
+
67
+ ```ts
68
+ const files = await openFileDialog({ accept: ".csv", multiple: true });
69
+ if (files == null) return; // 취소
70
+ ```
@@ -0,0 +1,62 @@
1
+ # @simplysm/core-browser — DOM 요소 확장·헬퍼
2
+
3
+ DOM 요소를 다룰 때 함께 읽히는 묶음. `Element.prototype`/`HTMLElement.prototype` 에 등록되는 확장 메서드와, 이벤트 핸들러·다중 요소용 정적 함수(`copyElement`/`pasteToElement`/`getBounds`)로 구성. 패키지를 import 하면 프로토타입 메서드가 자동 등록되므로 별도 초기화 불필요.
4
+
5
+ ## Element 확장 메서드
6
+
7
+ `Element.prototype` 에 등록. import 만으로 활성화.
8
+
9
+ - findAll<TEl>(selector: string): TEl[] — 선택자 일치 하위 요소를 배열로 반환. 선택자를 trim 한 결과가 빈 문자열이면 `[]`. `querySelectorAll` 결과를 NodeList 대신 배열로 받고 빈 선택자 예외를 회피할 때.
10
+ - findFirst<TEl>(selector: string): TEl | undefined — 첫 일치 하위 요소 또는 `undefined`. 빈 선택자도 `undefined`, 미일치도 `undefined`. `querySelector` 의 `null` 을 `undefined` 로 정규화한 형태.
11
+ - prependChild<TEl>(child: TEl): TEl — 자식을 첫 번째 위치(`insertBefore(child, firstElementChild)`)에 삽입하고 그 요소 반환. 맨 앞에 끼울 때.
12
+ - getParents(): Element[] — 모든 조상 요소를 가까운 것부터 먼 순서로 배열 반환. 조상 체인 순회·특정 조상 포함 판정에.
13
+ - findTabbableParent(): HTMLElement | undefined — `tabbable` 라이브러리 기준 첫 탭 이동 가능 조상. 없으면 `undefined`. 포커스 위임 대상을 위로 탐색할 때.
14
+ - findFirstTabbableChild(): HTMLElement | undefined — TreeWalker 로 순회한 첫 탭 이동 가능 하위 요소. 없으면 `undefined`. 컨테이너 진입 시 자동 포커스 대상을 찾을 때.
15
+ - isOffsetElement(): boolean — `getComputedStyle().position` 이 relative/absolute/fixed/sticky 중 하나면 true, 아니면 false. offset parent(절대배치 기준) 역할 여부 판정에.
16
+ - isVisible(): boolean — `getClientRects().length > 0` 이고 `visibility !== "hidden"` 이고 `opacity !== "0"` 를 모두 만족하면 true. 화면 표시 여부 판정에(display:none 은 clientRects 가 비어 false).
17
+
18
+ ```ts
19
+ import "@simplysm/core-browser";
20
+ const rows = containerEl.findAll<HTMLElement>("tr");
21
+ const first = containerEl.findFirstTabbableChild();
22
+ ```
23
+
24
+ ## HTMLElement 확장 메서드
25
+
26
+ `HTMLElement.prototype` 에 등록. 위와 동일하게 import 만으로 활성화.
27
+
28
+ - repaint(): void — `offsetHeight` 접근으로 강제 동기 레이아웃(reflow)을 유발해 즉시 리페인트. 스타일 변경 직후 즉각 반영을 강제할 때.
29
+ - getRelativeOffset(parent: HTMLElement | string): { top: number; left: number } — 부모 기준 CSS `top`/`left` 좌표 계산. 뷰포트 위치(getBoundingClientRect)·부모 내부 스크롤(scrollTop/Left)·중간 요소 border 두께·CSS transform 까지 반영해, 드롭다운/팝업 위치 지정에 바로 쓸 수 있는 좌표 반환. 부모를 못 찾으면(`HTMLElement` 아님) `ArgumentError` throw.
30
+ - parent: HTMLElement | string — 기준 부모. 문자열이면 `this.closest(parent)` 로 조상 탐색, 요소면 직접 사용(예: `document.body`, `".container"`).
31
+ - scrollIntoViewIfNeeded(target, offset?): void — 대상이 스크롤 영역의 상단/좌측 경계를 벗어났을 때만 그쪽으로 스크롤해 보이게 함. 하단/우측 방향은 처리하지 않고 브라우저 기본 포커스 스크롤에 위임. 고정 헤더/컬럼이 있는 테이블의 포커스 처리에.
32
+ - target: { top: number; left: number } — 컨테이너 내 대상 위치(offsetTop/offsetLeft 기준).
33
+ - offset: { top: number; left: number } — 가려지면 안 되는 영역 크기(고정 헤더 높이·고정 컬럼 너비). 기본 `{ top: 0, left: 0 }`.
34
+
35
+ ```ts
36
+ const { top, left } = popupEl.getRelativeOffset(".container");
37
+ scrollEl.scrollIntoViewIfNeeded({ top: cellTop, left: cellLeft }, { top: headerH, left: fixedW });
38
+ ```
39
+
40
+ ## 클립보드 / 경계 측정 정적 함수
41
+
42
+ 이벤트 핸들러로 붙이거나 다중 요소를 한 번에 처리하는 함수. 프로토타입 확장이 아니라 named export 이므로 직접 import.
43
+
44
+ - copyElement(event: ClipboardEvent): void — copy 이벤트 핸들러용. 이벤트 타겟 내 첫 `input/textarea` 의 `value` 를 클립보드 `text/plain` 으로 기록하고 `preventDefault`. clipboardData 가 없거나 타겟이 Element 가 아니거나 input 이 없으면 무동작.
45
+ - event: ClipboardEvent — copy 이벤트 객체. `el.addEventListener("copy", copyElement)` 로 등록.
46
+ - pasteToElement(event: ClipboardEvent): void — paste 이벤트 핸들러용. 타겟 내 첫 `input/textarea` 의 전체 `value` 를 클립보드 텍스트로 교체하고 `input` 이벤트 dispatch(`bubbles: true`) 후 `preventDefault`. 커서 위치·선택 영역은 무시하고 전체를 치환.
47
+ - event: ClipboardEvent — paste 이벤트 객체. `el.addEventListener("paste", pasteToElement)` 로 등록.
48
+ - getBounds(els: Element[], timeout?: number): Promise<ElementBounds[]> — `IntersectionObserver` 로 여러 요소의 뷰포트 기준 경계를 한 번에 측정. 중복은 제거하고 입력 순서대로 정렬해 반환. 빈 배열이면 즉시 `[]`. 모든 요소 관측 완료 시 resolve, 제한시간 초과 시 `TimeoutError` throw(어느 경우든 finally 에서 observer disconnect).
49
+ - els: Element[] — 측정 대상. 중복은 제거되고 결과는 입력 순서로 정렬됨.
50
+ - timeout: number — 제한시간(ms). 기본 `5000`. 초과 시 `TimeoutError`.
51
+ - ElementBounds (반환 항목 타입):
52
+ - target: Element — 측정된 요소.
53
+ - top: number — 뷰포트 기준 상단 위치(boundingClientRect.top).
54
+ - left: number — 뷰포트 기준 좌측 위치(boundingClientRect.left).
55
+ - width: number — 요소 너비(boundingClientRect.width).
56
+ - height: number — 요소 높이(boundingClientRect.height).
57
+
58
+ ```ts
59
+ inputEl.addEventListener("copy", copyElement);
60
+ inputEl.addEventListener("paste", pasteToElement);
61
+ const bounds = await getBounds([elA, elB], 3000);
62
+ ```
@@ -1,6 +1,6 @@
1
- # @simplysm/core-browser — IndexedDB 저장소/가상 파일시스템
1
+ # @simplysm/core-browser — IndexedDB 영속화
2
2
 
3
- 브라우저 IndexedDB 를 다룰 때 함께 읽히는 묶음. `IndexedDbStore` 는 연결·트랜잭션·KV CRUD 를 담당하고, `IndexedDbVirtualFs` 는 그 위에 경로 기반 가상 파일트리(entry put/get, prefix 삭제, 자식 나열, 디렉터리 보장)를 얹음.
3
+ 브라우저 IndexedDB 를 다룰 때 함께 읽히는 묶음. `IndexedDbStore` 는 연결·트랜잭션·KV CRUD 를 담당하고, `IndexedDbVirtualFs` 는 그 위에 경로 기반 가상 파일트리(entry put/get, prefix 삭제, 자식 나열, 디렉터리 보장)를 얹음.
4
4
 
5
5
  ## IndexedDbStore
6
6
 
@@ -17,23 +17,23 @@ store.close();
17
17
 
18
18
  시그니처:
19
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` — 연결을 닫고 내부 캐시 해제. 다음 작업 시 재오픈. 페이지 정리 시 호출.
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
37
 
38
38
  주의:
39
39
 
@@ -42,7 +42,7 @@ store.close();
42
42
 
43
43
  ## IndexedDbVirtualFs
44
44
 
45
- `IndexedDbStore` 한 스토어를 경로 키 기반 가상 파일시스템처럼 다루는 래퍼. 키는 `keyField` 속성에 들어가는 전체 경로 문자열이고, 각 레코드는 `VirtualFsEntry`(파일/디렉터리 + 선택적 base64 데이터)다.
45
+ `IndexedDbStore` 한 스토어를 경로 키 기반 가상 파일시스템처럼 다루는 래퍼. 키는 `keyField` 속성에 들어가는 전체 경로 문자열이고, 각 레코드는 `VirtualFsEntry`(파일/디렉터리 + 선택적 base64 데이터).
46
46
 
47
47
  ```ts
48
48
  const fs = new IndexedDbVirtualFs(store, "files", "key");
@@ -54,24 +54,25 @@ const ok = await fs.deleteByPrefix("/root/a"); // 하위 전체 삭제, 삭제
54
54
 
55
55
  시그니처:
56
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`보장할 디렉터리 경로(`/` 구분). 세그먼트는 무시.
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 — 보장할 디렉터리 경로(`/` 구분). 빈 세그먼트는 무시.
75
76
 
76
77
  주의:
77
78