@simplysm/capacitor-plugin-file-system 13.0.0-beta.6 → 13.0.1

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 (102) hide show
  1. package/README.md +15 -2
  2. package/dist/FileSystem.d.ts.map +1 -0
  3. package/dist/FileSystem.js.map +0 -1
  4. package/dist/IFileSystemPlugin.d.ts.map +1 -0
  5. package/dist/IFileSystemPlugin.js.map +0 -1
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +2 -2
  8. package/dist/index.js.map +0 -1
  9. package/dist/web/FileSystemWeb.d.ts.map +1 -0
  10. package/dist/web/FileSystemWeb.js +1 -1
  11. package/dist/web/FileSystemWeb.js.map +1 -2
  12. package/dist/web/IndexedDbStore.d.ts.map +1 -0
  13. package/dist/web/IndexedDbStore.js.map +0 -1
  14. package/dist/web/VirtualFileSystem.d.ts.map +1 -0
  15. package/dist/web/VirtualFileSystem.js +1 -1
  16. package/dist/web/VirtualFileSystem.js.map +0 -1
  17. package/package.json +8 -4
  18. package/.cache/typecheck-browser.tsbuildinfo +0 -1
  19. package/dist/capacitor-plugin-file-system/src/FileSystem.d.ts.map +0 -1
  20. package/dist/capacitor-plugin-file-system/src/IFileSystemPlugin.d.ts.map +0 -1
  21. package/dist/capacitor-plugin-file-system/src/index.d.ts.map +0 -1
  22. package/dist/capacitor-plugin-file-system/src/web/FileSystemWeb.d.ts.map +0 -1
  23. package/dist/capacitor-plugin-file-system/src/web/IndexedDbStore.d.ts.map +0 -1
  24. package/dist/capacitor-plugin-file-system/src/web/VirtualFileSystem.d.ts.map +0 -1
  25. package/dist/core-common/src/common.types.d.ts +0 -74
  26. package/dist/core-common/src/common.types.d.ts.map +0 -1
  27. package/dist/core-common/src/env.d.ts +0 -6
  28. package/dist/core-common/src/env.d.ts.map +0 -1
  29. package/dist/core-common/src/errors/argument-error.d.ts +0 -25
  30. package/dist/core-common/src/errors/argument-error.d.ts.map +0 -1
  31. package/dist/core-common/src/errors/not-implemented-error.d.ts +0 -29
  32. package/dist/core-common/src/errors/not-implemented-error.d.ts.map +0 -1
  33. package/dist/core-common/src/errors/sd-error.d.ts +0 -27
  34. package/dist/core-common/src/errors/sd-error.d.ts.map +0 -1
  35. package/dist/core-common/src/errors/timeout-error.d.ts +0 -31
  36. package/dist/core-common/src/errors/timeout-error.d.ts.map +0 -1
  37. package/dist/core-common/src/extensions/arr-ext.d.ts +0 -15
  38. package/dist/core-common/src/extensions/arr-ext.d.ts.map +0 -1
  39. package/dist/core-common/src/extensions/arr-ext.helpers.d.ts +0 -19
  40. package/dist/core-common/src/extensions/arr-ext.helpers.d.ts.map +0 -1
  41. package/dist/core-common/src/extensions/arr-ext.types.d.ts +0 -215
  42. package/dist/core-common/src/extensions/arr-ext.types.d.ts.map +0 -1
  43. package/dist/core-common/src/extensions/map-ext.d.ts +0 -57
  44. package/dist/core-common/src/extensions/map-ext.d.ts.map +0 -1
  45. package/dist/core-common/src/extensions/set-ext.d.ts +0 -36
  46. package/dist/core-common/src/extensions/set-ext.d.ts.map +0 -1
  47. package/dist/core-common/src/features/debounce-queue.d.ts +0 -53
  48. package/dist/core-common/src/features/debounce-queue.d.ts.map +0 -1
  49. package/dist/core-common/src/features/event-emitter.d.ts +0 -66
  50. package/dist/core-common/src/features/event-emitter.d.ts.map +0 -1
  51. package/dist/core-common/src/features/serial-queue.d.ts +0 -47
  52. package/dist/core-common/src/features/serial-queue.d.ts.map +0 -1
  53. package/dist/core-common/src/index.d.ts +0 -32
  54. package/dist/core-common/src/index.d.ts.map +0 -1
  55. package/dist/core-common/src/types/date-only.d.ts +0 -152
  56. package/dist/core-common/src/types/date-only.d.ts.map +0 -1
  57. package/dist/core-common/src/types/date-time.d.ts +0 -96
  58. package/dist/core-common/src/types/date-time.d.ts.map +0 -1
  59. package/dist/core-common/src/types/lazy-gc-map.d.ts +0 -80
  60. package/dist/core-common/src/types/lazy-gc-map.d.ts.map +0 -1
  61. package/dist/core-common/src/types/time.d.ts +0 -68
  62. package/dist/core-common/src/types/time.d.ts.map +0 -1
  63. package/dist/core-common/src/types/uuid.d.ts +0 -35
  64. package/dist/core-common/src/types/uuid.d.ts.map +0 -1
  65. package/dist/core-common/src/utils/bytes.d.ts +0 -51
  66. package/dist/core-common/src/utils/bytes.d.ts.map +0 -1
  67. package/dist/core-common/src/utils/date-format.d.ts +0 -90
  68. package/dist/core-common/src/utils/date-format.d.ts.map +0 -1
  69. package/dist/core-common/src/utils/json.d.ts +0 -34
  70. package/dist/core-common/src/utils/json.d.ts.map +0 -1
  71. package/dist/core-common/src/utils/num.d.ts +0 -60
  72. package/dist/core-common/src/utils/num.d.ts.map +0 -1
  73. package/dist/core-common/src/utils/obj.d.ts +0 -258
  74. package/dist/core-common/src/utils/obj.d.ts.map +0 -1
  75. package/dist/core-common/src/utils/path.d.ts +0 -23
  76. package/dist/core-common/src/utils/path.d.ts.map +0 -1
  77. package/dist/core-common/src/utils/primitive.d.ts +0 -18
  78. package/dist/core-common/src/utils/primitive.d.ts.map +0 -1
  79. package/dist/core-common/src/utils/str.d.ts +0 -103
  80. package/dist/core-common/src/utils/str.d.ts.map +0 -1
  81. package/dist/core-common/src/utils/template-strings.d.ts +0 -84
  82. package/dist/core-common/src/utils/template-strings.d.ts.map +0 -1
  83. package/dist/core-common/src/utils/transferable.d.ts +0 -47
  84. package/dist/core-common/src/utils/transferable.d.ts.map +0 -1
  85. package/dist/core-common/src/utils/wait.d.ts +0 -19
  86. package/dist/core-common/src/utils/wait.d.ts.map +0 -1
  87. package/dist/core-common/src/utils/xml.d.ts +0 -36
  88. package/dist/core-common/src/utils/xml.d.ts.map +0 -1
  89. package/dist/core-common/src/zip/sd-zip.d.ts +0 -80
  90. package/dist/core-common/src/zip/sd-zip.d.ts.map +0 -1
  91. package/src/FileSystem.ts +0 -126
  92. package/src/IFileSystemPlugin.ts +0 -26
  93. package/src/index.ts +0 -2
  94. package/src/web/FileSystemWeb.ts +0 -107
  95. package/src/web/IndexedDbStore.ts +0 -88
  96. package/src/web/VirtualFileSystem.ts +0 -105
  97. /package/dist/{capacitor-plugin-file-system/src/FileSystem.d.ts → FileSystem.d.ts} +0 -0
  98. /package/dist/{capacitor-plugin-file-system/src/IFileSystemPlugin.d.ts → IFileSystemPlugin.d.ts} +0 -0
  99. /package/dist/{capacitor-plugin-file-system/src/index.d.ts → index.d.ts} +0 -0
  100. /package/dist/{capacitor-plugin-file-system/src/web → web}/FileSystemWeb.d.ts +0 -0
  101. /package/dist/{capacitor-plugin-file-system/src/web → web}/IndexedDbStore.d.ts +0 -0
  102. /package/dist/{capacitor-plugin-file-system/src/web → web}/VirtualFileSystem.d.ts +0 -0
@@ -1,36 +0,0 @@
1
- /**
2
- * XML 변환 유틸리티
3
- */
4
- import type { XmlBuilderOptions } from "fast-xml-parser";
5
- /**
6
- * XML 문자열을 객체로 파싱
7
- * @param str XML 문자열
8
- * @param options 옵션
9
- * @param options.stripTagPrefix 태그 prefix 제거 여부 (namespace)
10
- * @returns 파싱된 객체. 구조:
11
- * - 속성: `$` 객체에 그룹화
12
- * - 텍스트 노드: `_` 키에 저장
13
- * - 자식 요소: 배열로 변환 (루트 요소 제외)
14
- * @example
15
- * xmlParse('<root id="1"><item>hello</item></root>');
16
- * // { root: { $: { id: "1" }, item: [{ _: "hello" }] } }
17
- */
18
- export declare function xmlParse(str: string, options?: {
19
- stripTagPrefix?: boolean;
20
- }): unknown;
21
- /**
22
- * 객체를 XML 문자열로 직렬화
23
- * @param obj 직렬화할 객체
24
- * @param options fast-xml-parser XmlBuilderOptions (선택)
25
- * @returns XML 문자열
26
- * @example
27
- * xmlStringify({
28
- * root: {
29
- * $: { id: "1" },
30
- * item: [{ _: "hello" }, { _: "world" }],
31
- * },
32
- * });
33
- * // '<root id="1"><item>hello</item><item>world</item></root>'
34
- */
35
- export declare function xmlStringify(obj: unknown, options?: XmlBuilderOptions): string;
36
- //# sourceMappingURL=xml.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"xml.d.ts","sourceRoot":"","sources":["../../../../../core-common/src/utils/xml.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAKzD;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAarF;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAS9E"}
@@ -1,80 +0,0 @@
1
- import type { Bytes } from "../common.types";
2
- export interface ZipArchiveProgress {
3
- fileName: string;
4
- totalSize: number;
5
- extractedSize: number;
6
- }
7
- /**
8
- * ZIP 아카이브 처리 클래스
9
- *
10
- * ZIP 파일의 읽기, 쓰기, 압축/해제를 처리합니다.
11
- * 내부 캐시를 사용하여 동일 파일의 중복 압축 해제를 방지합니다.
12
- *
13
- * @example
14
- * // ZIP 파일 읽기
15
- * await using archive = new ZipArchive(zipBytes);
16
- * const content = await archive.get("file.txt");
17
- *
18
- * @example
19
- * // ZIP 파일 생성
20
- * await using archive = new ZipArchive();
21
- * archive.write("file.txt", textBytes);
22
- * archive.write("data.json", jsonBytes);
23
- * const zipBytes = await archive.compress();
24
- *
25
- * @example
26
- * // 전체 압축 해제 (진행률 표시)
27
- * await using archive = new ZipArchive(zipBytes);
28
- * const files = await archive.extractAll((progress) => {
29
- * console.log(`${progress.fileName}: ${progress.extractedSize}/${progress.totalSize}`);
30
- * });
31
- */
32
- export declare class ZipArchive {
33
- private readonly _reader?;
34
- private readonly _cache;
35
- private _entries?;
36
- /**
37
- * ZipArchive 생성
38
- * @param data ZIP 데이터 (생략 시 새 아카이브 생성)
39
- */
40
- constructor(data?: Blob | Bytes);
41
- private _getEntries;
42
- /**
43
- * 모든 파일을 압축 해제
44
- * @param progressCallback 진행률 콜백
45
- */
46
- extractAll(progressCallback?: (progress: ZipArchiveProgress) => void): Promise<Map<string, Bytes | undefined>>;
47
- /**
48
- * 특정 파일 압축 해제
49
- * @param fileName 파일 이름
50
- */
51
- get(fileName: string): Promise<Bytes | undefined>;
52
- /**
53
- * 파일 존재 여부 확인
54
- * @param fileName 파일 이름
55
- */
56
- exists(fileName: string): Promise<boolean>;
57
- /**
58
- * 파일 쓰기 (캐시에 저장)
59
- * @param fileName 파일 이름
60
- * @param bytes 파일 내용
61
- */
62
- write(fileName: string, bytes: Bytes): void;
63
- /**
64
- * 캐시된 파일들을 ZIP으로 압축
65
- *
66
- * @remarks
67
- * 내부적으로 `extractAll()`을 호출하여 모든 파일을 메모리에 로드한 후 압축합니다.
68
- * 대용량 ZIP 파일의 경우 메모리 사용량에 주의가 필요합니다.
69
- */
70
- compress(): Promise<Bytes>;
71
- /**
72
- * 리더 닫기 및 캐시 정리
73
- */
74
- close(): Promise<void>;
75
- /**
76
- * await using 지원
77
- */
78
- [Symbol.asyncDispose](): Promise<void>;
79
- }
80
- //# sourceMappingURL=sd-zip.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sd-zip.d.ts","sourceRoot":"","sources":["../../../../../core-common/src/zip/sd-zip.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAA0B;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwC;IAC/D,OAAO,CAAC,QAAQ,CAAC,CAA6D;IAE9E;;;OAGG;gBACS,IAAI,CAAC,EAAE,IAAI,GAAG,KAAK;YAUjB,WAAW;IAQzB;;;OAGG;IACG,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,GAAG,SAAS,CAAC,CAAC;IAkDpH;;;OAGG;IACG,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC;IAwBvD;;;OAGG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBhD;;;;OAIG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;IAM3C;;;;;;OAMG;IACG,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC;IAiBhC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B;;OAEG;IACG,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7C"}
package/src/FileSystem.ts DELETED
@@ -1,126 +0,0 @@
1
- import { registerPlugin } from "@capacitor/core";
2
- import type { IFileInfo, IFileSystemPlugin, TStorage } from "./IFileSystemPlugin";
3
- import type { Bytes } from "@simplysm/core-common";
4
- import { bytesToBase64, bytesFromBase64 } from "@simplysm/core-common";
5
-
6
- const FileSystemPlugin = registerPlugin<IFileSystemPlugin>("FileSystem", {
7
- web: async () => {
8
- const { FileSystemWeb } = await import("./web/FileSystemWeb");
9
- return new FileSystemWeb();
10
- },
11
- });
12
-
13
- /**
14
- * 파일 시스템 접근 플러그인
15
- * - Android 11+: MANAGE_EXTERNAL_STORAGE 권한으로 전체 파일 시스템 접근
16
- * - Android 10-: READ/WRITE_EXTERNAL_STORAGE 권한
17
- * - Browser: IndexedDB 기반 에뮬레이션
18
- */
19
- export abstract class FileSystem {
20
- /**
21
- * 권한 확인
22
- */
23
- static async hasPermission(): Promise<boolean> {
24
- const result = await FileSystemPlugin.hasPermission();
25
- return result.granted;
26
- }
27
-
28
- /**
29
- * 권한 요청
30
- * - Android 11+: 설정 화면으로 이동
31
- * - Android 10-: 권한 다이얼로그 표시
32
- */
33
- static async requestPermission(): Promise<void> {
34
- await FileSystemPlugin.requestPermission();
35
- }
36
-
37
- /**
38
- * 디렉토리 읽기
39
- */
40
- static async readdir(dirPath: string): Promise<IFileInfo[]> {
41
- const result = await FileSystemPlugin.readdir({ path: dirPath });
42
- return result.files;
43
- }
44
-
45
- /**
46
- * 저장소 경로 얻기
47
- * @param type 저장소 타입
48
- * - external: 외부 저장소 루트 (Environment.getExternalStorageDirectory)
49
- * - externalFiles: 앱 전용 외부 파일 디렉토리
50
- * - externalCache: 앱 전용 외부 캐시 디렉토리
51
- * - externalMedia: 앱 전용 외부 미디어 디렉토리
52
- * - appData: 앱 데이터 디렉토리
53
- * - appFiles: 앱 파일 디렉토리
54
- * - appCache: 앱 캐시 디렉토리
55
- */
56
- static async getStoragePath(type: TStorage): Promise<string> {
57
- const result = await FileSystemPlugin.getStoragePath({ type });
58
- return result.path;
59
- }
60
-
61
- /**
62
- * 파일 URI 얻기 (FileProvider)
63
- */
64
- static async getFileUri(filePath: string): Promise<string> {
65
- const result = await FileSystemPlugin.getFileUri({ path: filePath });
66
- return result.uri;
67
- }
68
-
69
- /**
70
- * 파일 쓰기
71
- */
72
- static async writeFile(filePath: string, data: string | Bytes): Promise<void> {
73
- if (typeof data !== "string") {
74
- // Bytes (Uint8Array) - cross-realm 환경에서도 안전하게 동작
75
- await FileSystemPlugin.writeFile({
76
- path: filePath,
77
- data: bytesToBase64(data),
78
- encoding: "base64",
79
- });
80
- } else {
81
- await FileSystemPlugin.writeFile({
82
- path: filePath,
83
- data,
84
- encoding: "utf8",
85
- });
86
- }
87
- }
88
-
89
- /**
90
- * 파일 읽기 (UTF-8 문자열)
91
- */
92
- static async readFileString(filePath: string): Promise<string> {
93
- const result = await FileSystemPlugin.readFile({ path: filePath, encoding: "utf8" });
94
- return result.data;
95
- }
96
-
97
- /**
98
- * 파일 읽기 (Bytes)
99
- */
100
- static async readFileBytes(filePath: string): Promise<Bytes> {
101
- const result = await FileSystemPlugin.readFile({ path: filePath, encoding: "base64" });
102
- return bytesFromBase64(result.data);
103
- }
104
-
105
- /**
106
- * 파일/디렉토리 삭제 (재귀)
107
- */
108
- static async remove(targetPath: string): Promise<void> {
109
- await FileSystemPlugin.remove({ path: targetPath });
110
- }
111
-
112
- /**
113
- * 디렉토리 생성 (재귀)
114
- */
115
- static async mkdir(targetPath: string): Promise<void> {
116
- await FileSystemPlugin.mkdir({ path: targetPath });
117
- }
118
-
119
- /**
120
- * 존재 여부 확인
121
- */
122
- static async exists(targetPath: string): Promise<boolean> {
123
- const result = await FileSystemPlugin.exists({ path: targetPath });
124
- return result.exists;
125
- }
126
- }
@@ -1,26 +0,0 @@
1
- export type TStorage =
2
- | "external"
3
- | "externalFiles"
4
- | "externalCache"
5
- | "externalMedia"
6
- | "appData"
7
- | "appFiles"
8
- | "appCache";
9
-
10
- export interface IFileInfo {
11
- name: string;
12
- isDirectory: boolean;
13
- }
14
-
15
- export interface IFileSystemPlugin {
16
- hasPermission(): Promise<{ granted: boolean }>;
17
- requestPermission(): Promise<void>;
18
- readdir(options: { path: string }): Promise<{ files: IFileInfo[] }>;
19
- getStoragePath(options: { type: TStorage }): Promise<{ path: string }>;
20
- getFileUri(options: { path: string }): Promise<{ uri: string }>;
21
- writeFile(options: { path: string; data: string; encoding?: "utf8" | "base64" }): Promise<void>;
22
- readFile(options: { path: string; encoding?: "utf8" | "base64" }): Promise<{ data: string }>;
23
- remove(options: { path: string }): Promise<void>;
24
- mkdir(options: { path: string }): Promise<void>;
25
- exists(options: { path: string }): Promise<{ exists: boolean }>;
26
- }
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export * from "./FileSystem";
2
- export * from "./IFileSystemPlugin";
@@ -1,107 +0,0 @@
1
- import { WebPlugin } from "@capacitor/core";
2
- import type { IFileInfo, IFileSystemPlugin, TStorage } from "../IFileSystemPlugin";
3
- import { VirtualFileSystem } from "./VirtualFileSystem";
4
- import { bytesToBase64, bytesFromBase64 } from "@simplysm/core-common";
5
-
6
- export class FileSystemWeb extends WebPlugin implements IFileSystemPlugin {
7
- private readonly _fs = new VirtualFileSystem("capacitor_web_virtual_fs");
8
- private readonly _textEncoder = new TextEncoder();
9
- private readonly _textDecoder = new TextDecoder();
10
-
11
- async hasPermission(): Promise<{ granted: boolean }> {
12
- return Promise.resolve({ granted: true });
13
- }
14
-
15
- async requestPermission(): Promise<void> {}
16
-
17
- async readdir(options: { path: string }): Promise<{ files: IFileInfo[] }> {
18
- const entry = await this._fs.getEntry(options.path);
19
- if (!entry || entry.kind !== "dir") {
20
- throw new Error("Directory does not exist");
21
- }
22
- const files = await this._fs.listChildren(options.path);
23
- return { files };
24
- }
25
-
26
- async getStoragePath(options: { type: TStorage }): Promise<{ path: string }> {
27
- const base = "/webfs";
28
- let storagePath: string;
29
- switch (options.type) {
30
- case "external":
31
- storagePath = base + "/external";
32
- break;
33
- case "externalFiles":
34
- storagePath = base + "/externalFiles";
35
- break;
36
- case "externalCache":
37
- storagePath = base + "/externalCache";
38
- break;
39
- case "externalMedia":
40
- storagePath = base + "/externalMedia";
41
- break;
42
- case "appData":
43
- storagePath = base + "/appData";
44
- break;
45
- case "appFiles":
46
- storagePath = base + "/appFiles";
47
- break;
48
- case "appCache":
49
- storagePath = base + "/appCache";
50
- break;
51
- default:
52
- throw new Error("Unknown storage type: " + options.type);
53
- }
54
- await this._fs.ensureDir(storagePath);
55
- return { path: storagePath };
56
- }
57
-
58
- /**
59
- * 파일의 Blob URL을 반환합니다.
60
- * @warning 반환된 URI는 사용 후 반드시 `URL.revokeObjectURL(uri)`를 호출하여 해제해야 합니다.
61
- * 해제하지 않으면 메모리 누수가 발생할 수 있습니다.
62
- */
63
- async getFileUri(options: { path: string }): Promise<{ uri: string }> {
64
- const entry = await this._fs.getEntry(options.path);
65
- if (!entry || entry.kind !== "file" || entry.dataBase64 == null) {
66
- throw new Error("File not found: " + options.path);
67
- }
68
- const bytes = bytesFromBase64(entry.dataBase64);
69
- const blob = new Blob([bytes]);
70
- return { uri: URL.createObjectURL(blob) };
71
- }
72
-
73
- async writeFile(options: { path: string; data: string; encoding?: "utf8" | "base64" }): Promise<void> {
74
- const idx = options.path.lastIndexOf("/");
75
- const dir = idx === -1 ? "." : options.path.substring(0, idx) || "/";
76
- await this._fs.ensureDir(dir);
77
- const dataBase64 =
78
- options.encoding === "base64" ? options.data : bytesToBase64(this._textEncoder.encode(options.data));
79
- await this._fs.putEntry({ path: options.path, kind: "file", dataBase64 });
80
- }
81
-
82
- async readFile(options: { path: string; encoding?: "utf8" | "base64" }): Promise<{ data: string }> {
83
- const entry = await this._fs.getEntry(options.path);
84
- if (!entry || entry.kind !== "file" || entry.dataBase64 == null) {
85
- throw new Error("File not found: " + options.path);
86
- }
87
- const data =
88
- options.encoding === "base64" ? entry.dataBase64 : this._textDecoder.decode(bytesFromBase64(entry.dataBase64));
89
- return { data };
90
- }
91
-
92
- async remove(options: { path: string }): Promise<void> {
93
- const ok = await this._fs.deleteByPrefix(options.path);
94
- if (!ok) {
95
- throw new Error("Deletion failed");
96
- }
97
- }
98
-
99
- async mkdir(options: { path: string }): Promise<void> {
100
- await this._fs.ensureDir(options.path);
101
- }
102
-
103
- async exists(options: { path: string }): Promise<{ exists: boolean }> {
104
- const entry = await this._fs.getEntry(options.path);
105
- return { exists: !!entry };
106
- }
107
- }
@@ -1,88 +0,0 @@
1
- export interface IStoreConfig {
2
- name: string;
3
- keyPath: string;
4
- }
5
-
6
- export class IndexedDbStore {
7
- constructor(
8
- private readonly _dbName: string,
9
- private readonly _dbVersion: number,
10
- private readonly _storeConfigs: IStoreConfig[],
11
- ) {}
12
-
13
- async open(): Promise<IDBDatabase> {
14
- return new Promise((resolve, reject) => {
15
- const req = indexedDB.open(this._dbName, this._dbVersion);
16
- req.onupgradeneeded = () => {
17
- const db = req.result;
18
- for (const config of this._storeConfigs) {
19
- if (!db.objectStoreNames.contains(config.name)) {
20
- db.createObjectStore(config.name, { keyPath: config.keyPath });
21
- }
22
- }
23
- };
24
- req.onsuccess = () => resolve(req.result);
25
- req.onerror = () => reject(req.error);
26
- req.onblocked = () => reject(new Error("Database blocked by another connection"));
27
- });
28
- }
29
-
30
- async withStore<T>(
31
- storeName: string,
32
- mode: IDBTransactionMode,
33
- fn: (store: IDBObjectStore) => Promise<T>,
34
- ): Promise<T> {
35
- const db = await this.open();
36
- return new Promise((resolve, reject) => {
37
- const tx = db.transaction(storeName, mode);
38
- const store = tx.objectStore(storeName);
39
- let result: T;
40
- Promise.resolve(fn(store))
41
- .then((r) => {
42
- result = r;
43
- })
44
- .catch((err) => {
45
- db.close();
46
- reject(err);
47
- });
48
- tx.oncomplete = () => {
49
- db.close();
50
- resolve(result);
51
- };
52
- tx.onerror = () => {
53
- db.close();
54
- reject(tx.error);
55
- };
56
- });
57
- }
58
-
59
- async get<T>(storeName: string, key: IDBValidKey): Promise<T | undefined> {
60
- return this.withStore(storeName, "readonly", async (store) => {
61
- return new Promise((resolve, reject) => {
62
- const req = store.get(key);
63
- req.onsuccess = () => resolve(req.result as T | undefined);
64
- req.onerror = () => reject(req.error);
65
- });
66
- });
67
- }
68
-
69
- async put(storeName: string, value: unknown): Promise<void> {
70
- return this.withStore(storeName, "readwrite", async (store) => {
71
- return new Promise((resolve, reject) => {
72
- const req = store.put(value);
73
- req.onsuccess = () => resolve();
74
- req.onerror = () => reject(req.error);
75
- });
76
- });
77
- }
78
-
79
- async getAll<T>(storeName: string): Promise<T[]> {
80
- return this.withStore(storeName, "readonly", async (store) => {
81
- return new Promise((resolve, reject) => {
82
- const req = store.getAll();
83
- req.onsuccess = () => resolve(req.result as T[]);
84
- req.onerror = () => reject(req.error);
85
- });
86
- });
87
- }
88
- }
@@ -1,105 +0,0 @@
1
- import type { IFileInfo } from "../IFileSystemPlugin";
2
- import { IndexedDbStore } from "./IndexedDbStore";
3
-
4
- interface FsEntry {
5
- path: string;
6
- kind: "file" | "dir";
7
- dataBase64?: string;
8
- }
9
-
10
- export class VirtualFileSystem {
11
- private readonly _STORE_NAME = "entries";
12
- private readonly _db: IndexedDbStore;
13
-
14
- constructor(dbName: string) {
15
- this._db = new IndexedDbStore(dbName, 1, [{ name: this._STORE_NAME, keyPath: "path" }]);
16
- }
17
-
18
- async getEntry(filePath: string): Promise<FsEntry | undefined> {
19
- return this._db.get<FsEntry>(this._STORE_NAME, filePath);
20
- }
21
-
22
- async putEntry(entry: FsEntry): Promise<void> {
23
- return this._db.put(this._STORE_NAME, entry);
24
- }
25
-
26
- async deleteByPrefix(pathPrefix: string): Promise<boolean> {
27
- return this._db.withStore(this._STORE_NAME, "readwrite", async (store) => {
28
- return new Promise((resolve, reject) => {
29
- const req = store.openCursor();
30
- let found = false;
31
- req.onsuccess = () => {
32
- const cursor = req.result;
33
- if (!cursor) {
34
- resolve(found);
35
- return;
36
- }
37
- const key = String(cursor.key);
38
- if (key === pathPrefix || key.startsWith(pathPrefix + "/")) {
39
- found = true;
40
- cursor.delete();
41
- }
42
- cursor.continue();
43
- };
44
- req.onerror = () => reject(req.error);
45
- });
46
- });
47
- }
48
-
49
- /**
50
- * 디렉토리의 직접 자식 목록을 반환합니다.
51
- * @param dirPath 조회할 디렉토리 경로
52
- * @returns 자식 파일/디렉토리 목록
53
- * @note 암시적 디렉토리 처리: 파일 경로만 존재하고 디렉토리 엔트리가 없는 경우에도
54
- * 중간 경로는 디렉토리로 판정됩니다. 예: "/a/b/c.txt"만 저장된 상태에서
55
- * listChildren("/a") 호출 시 "b"는 isDirectory: true로 반환됩니다.
56
- */
57
- async listChildren(dirPath: string): Promise<IFileInfo[]> {
58
- const prefix = dirPath === "/" ? "/" : dirPath + "/";
59
- return this._db.withStore(this._STORE_NAME, "readonly", async (store) => {
60
- return new Promise((resolve, reject) => {
61
- const req = store.openCursor();
62
- const map = new Map<string, boolean>();
63
- req.onsuccess = () => {
64
- const cursor = req.result;
65
- if (!cursor) {
66
- resolve(Array.from(map.entries()).map(([name, isDirectory]) => ({ name, isDirectory })));
67
- return;
68
- }
69
- const key = String(cursor.key);
70
- if (key.startsWith(prefix)) {
71
- const rest = key.slice(prefix.length);
72
- if (rest) {
73
- const segments = rest.split("/").filter(Boolean);
74
- if (segments.length > 0) {
75
- const firstSeg = segments[0];
76
- if (!map.has(firstSeg)) {
77
- const isDir = segments.length > 1 || (cursor.value as FsEntry).kind === "dir";
78
- map.set(firstSeg, isDir);
79
- }
80
- }
81
- }
82
- }
83
- cursor.continue();
84
- };
85
- req.onerror = () => reject(req.error);
86
- });
87
- });
88
- }
89
-
90
- async ensureDir(dirPath: string): Promise<void> {
91
- if (dirPath === "/") {
92
- await this.putEntry({ path: "/", kind: "dir" });
93
- return;
94
- }
95
- const segments = dirPath.split("/").filter(Boolean);
96
- let acc = "";
97
- for (const seg of segments) {
98
- acc += "/" + seg;
99
- const existing = await this.getEntry(acc);
100
- if (!existing) {
101
- await this.putEntry({ path: acc, kind: "dir" });
102
- }
103
- }
104
- }
105
- }