@dynlabs/react-native-immutable-file-cache 1.0.0-alpha.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 (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +415 -0
  3. package/lib/commonjs/adapters/memoryAdapter.js +266 -0
  4. package/lib/commonjs/adapters/memoryAdapter.js.map +1 -0
  5. package/lib/commonjs/adapters/rnfsAdapter.js +259 -0
  6. package/lib/commonjs/adapters/rnfsAdapter.js.map +1 -0
  7. package/lib/commonjs/adapters/webAdapter.js +432 -0
  8. package/lib/commonjs/adapters/webAdapter.js.map +1 -0
  9. package/lib/commonjs/core/adapter.js +2 -0
  10. package/lib/commonjs/core/adapter.js.map +1 -0
  11. package/lib/commonjs/core/cacheEngine.js +578 -0
  12. package/lib/commonjs/core/cacheEngine.js.map +1 -0
  13. package/lib/commonjs/core/errors.js +83 -0
  14. package/lib/commonjs/core/errors.js.map +1 -0
  15. package/lib/commonjs/core/hash.js +83 -0
  16. package/lib/commonjs/core/hash.js.map +1 -0
  17. package/lib/commonjs/core/indexStore.js +175 -0
  18. package/lib/commonjs/core/indexStore.js.map +1 -0
  19. package/lib/commonjs/core/mutex.js +143 -0
  20. package/lib/commonjs/core/mutex.js.map +1 -0
  21. package/lib/commonjs/core/prune.js +127 -0
  22. package/lib/commonjs/core/prune.js.map +1 -0
  23. package/lib/commonjs/core/types.js +6 -0
  24. package/lib/commonjs/core/types.js.map +1 -0
  25. package/lib/commonjs/factory.js +56 -0
  26. package/lib/commonjs/factory.js.map +1 -0
  27. package/lib/commonjs/index.js +110 -0
  28. package/lib/commonjs/index.js.map +1 -0
  29. package/lib/commonjs/index.native.js +74 -0
  30. package/lib/commonjs/index.native.js.map +1 -0
  31. package/lib/commonjs/index.web.js +75 -0
  32. package/lib/commonjs/index.web.js.map +1 -0
  33. package/lib/commonjs/types/react-native-fs.d.js +2 -0
  34. package/lib/commonjs/types/react-native-fs.d.js.map +1 -0
  35. package/lib/module/adapters/memoryAdapter.js +261 -0
  36. package/lib/module/adapters/memoryAdapter.js.map +1 -0
  37. package/lib/module/adapters/rnfsAdapter.js +251 -0
  38. package/lib/module/adapters/rnfsAdapter.js.map +1 -0
  39. package/lib/module/adapters/webAdapter.js +426 -0
  40. package/lib/module/adapters/webAdapter.js.map +1 -0
  41. package/lib/module/core/adapter.js +2 -0
  42. package/lib/module/core/adapter.js.map +1 -0
  43. package/lib/module/core/cacheEngine.js +571 -0
  44. package/lib/module/core/cacheEngine.js.map +1 -0
  45. package/lib/module/core/errors.js +71 -0
  46. package/lib/module/core/errors.js.map +1 -0
  47. package/lib/module/core/hash.js +76 -0
  48. package/lib/module/core/hash.js.map +1 -0
  49. package/lib/module/core/indexStore.js +168 -0
  50. package/lib/module/core/indexStore.js.map +1 -0
  51. package/lib/module/core/mutex.js +135 -0
  52. package/lib/module/core/mutex.js.map +1 -0
  53. package/lib/module/core/prune.js +116 -0
  54. package/lib/module/core/prune.js.map +1 -0
  55. package/lib/module/core/types.js +2 -0
  56. package/lib/module/core/types.js.map +1 -0
  57. package/lib/module/factory.js +49 -0
  58. package/lib/module/factory.js.map +1 -0
  59. package/lib/module/index.js +41 -0
  60. package/lib/module/index.js.map +1 -0
  61. package/lib/module/index.native.js +54 -0
  62. package/lib/module/index.native.js.map +1 -0
  63. package/lib/module/index.web.js +55 -0
  64. package/lib/module/index.web.js.map +1 -0
  65. package/lib/module/types/react-native-fs.d.js +2 -0
  66. package/lib/module/types/react-native-fs.d.js.map +1 -0
  67. package/lib/typescript/src/adapters/memoryAdapter.d.ts +23 -0
  68. package/lib/typescript/src/adapters/memoryAdapter.d.ts.map +1 -0
  69. package/lib/typescript/src/adapters/rnfsAdapter.d.ts +18 -0
  70. package/lib/typescript/src/adapters/rnfsAdapter.d.ts.map +1 -0
  71. package/lib/typescript/src/adapters/webAdapter.d.ts +30 -0
  72. package/lib/typescript/src/adapters/webAdapter.d.ts.map +1 -0
  73. package/lib/typescript/src/core/adapter.d.ts +105 -0
  74. package/lib/typescript/src/core/adapter.d.ts.map +1 -0
  75. package/lib/typescript/src/core/cacheEngine.d.ts +99 -0
  76. package/lib/typescript/src/core/cacheEngine.d.ts.map +1 -0
  77. package/lib/typescript/src/core/errors.d.ts +54 -0
  78. package/lib/typescript/src/core/errors.d.ts.map +1 -0
  79. package/lib/typescript/src/core/hash.d.ts +20 -0
  80. package/lib/typescript/src/core/hash.d.ts.map +1 -0
  81. package/lib/typescript/src/core/indexStore.d.ts +34 -0
  82. package/lib/typescript/src/core/indexStore.d.ts.map +1 -0
  83. package/lib/typescript/src/core/mutex.d.ts +49 -0
  84. package/lib/typescript/src/core/mutex.d.ts.map +1 -0
  85. package/lib/typescript/src/core/prune.d.ts +39 -0
  86. package/lib/typescript/src/core/prune.d.ts.map +1 -0
  87. package/lib/typescript/src/core/types.d.ts +109 -0
  88. package/lib/typescript/src/core/types.d.ts.map +1 -0
  89. package/lib/typescript/src/factory.d.ts +46 -0
  90. package/lib/typescript/src/factory.d.ts.map +1 -0
  91. package/lib/typescript/src/index.d.ts +20 -0
  92. package/lib/typescript/src/index.d.ts.map +1 -0
  93. package/lib/typescript/src/index.native.d.ts +37 -0
  94. package/lib/typescript/src/index.native.d.ts.map +1 -0
  95. package/lib/typescript/src/index.web.d.ts +38 -0
  96. package/lib/typescript/src/index.web.d.ts.map +1 -0
  97. package/package.json +125 -0
  98. package/src/adapters/memoryAdapter.ts +307 -0
  99. package/src/adapters/rnfsAdapter.ts +283 -0
  100. package/src/adapters/webAdapter.ts +480 -0
  101. package/src/core/adapter.ts +128 -0
  102. package/src/core/cacheEngine.ts +634 -0
  103. package/src/core/errors.ts +82 -0
  104. package/src/core/hash.ts +78 -0
  105. package/src/core/indexStore.ts +184 -0
  106. package/src/core/mutex.ts +134 -0
  107. package/src/core/prune.ts +145 -0
  108. package/src/core/types.ts +165 -0
  109. package/src/factory.ts +60 -0
  110. package/src/index.native.ts +58 -0
  111. package/src/index.ts +82 -0
  112. package/src/index.web.ts +59 -0
  113. package/src/types/react-native-fs.d.ts +75 -0
@@ -0,0 +1,251 @@
1
+ /**
2
+ * React Native FS Adapter
3
+ *
4
+ * This is the ONLY file that imports react-native-fs.
5
+ * Implements IStorageAdapter using RNFS APIs.
6
+ */
7
+
8
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
9
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
10
+ /* eslint-disable @typescript-eslint/no-unsafe-call */
11
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
12
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
13
+ /* eslint-disable @typescript-eslint/require-await */
14
+
15
+ // RNFS types are not fully typed, disable unsafe rules for this adapter
16
+ import RNFS from "react-native-fs";
17
+ import { UnsupportedSourceError, AdapterIOError } from "../core/errors";
18
+ /**
19
+ * Creates a storage adapter backed by react-native-fs.
20
+ */
21
+ export function createRnfsAdapter(options) {
22
+ const baseDir = options?.baseDir ?? RNFS.CachesDirectoryPath;
23
+ const namespace = options?.namespace ?? "default";
24
+ const rootPath = `${baseDir}/immutable-cache/${namespace}`;
25
+
26
+ /**
27
+ * Converts adapter-relative path to absolute filesystem path.
28
+ */
29
+ const toAbsolute = path => {
30
+ return `${rootPath}/${path}`;
31
+ };
32
+
33
+ /**
34
+ * Generates a unique temp file path for atomic writes.
35
+ */
36
+ const getTempPath = path => {
37
+ return `${toAbsolute(path)}.tmp.${Date.now()}.${Math.random().toString(36).slice(2)}`;
38
+ };
39
+ const adapter = {
40
+ kind: "native-fs",
41
+ rootId: rootPath,
42
+ // ─────────────────────────────────────────────────────────────────
43
+ // Directory Management
44
+ // ─────────────────────────────────────────────────────────────────
45
+
46
+ async ensureDir(path) {
47
+ try {
48
+ const absPath = toAbsolute(path);
49
+ await RNFS.mkdir(absPath);
50
+ } catch (error) {
51
+ throw new AdapterIOError("ensureDir", path, error);
52
+ }
53
+ },
54
+ async exists(path) {
55
+ try {
56
+ const absPath = toAbsolute(path);
57
+ return await RNFS.exists(absPath);
58
+ } catch {
59
+ return false;
60
+ }
61
+ },
62
+ async remove(path) {
63
+ try {
64
+ const absPath = toAbsolute(path);
65
+ const exists = await RNFS.exists(absPath);
66
+ if (exists) {
67
+ await RNFS.unlink(absPath);
68
+ }
69
+ } catch (error) {
70
+ throw new AdapterIOError("remove", path, error);
71
+ }
72
+ },
73
+ async removeDir(path) {
74
+ try {
75
+ const absPath = toAbsolute(path);
76
+ const exists = await RNFS.exists(absPath);
77
+ if (exists) {
78
+ // RNFS.unlink can remove directories recursively
79
+ await RNFS.unlink(absPath);
80
+ }
81
+ } catch (error) {
82
+ throw new AdapterIOError("removeDir", path, error);
83
+ }
84
+ },
85
+ async listDir(path) {
86
+ try {
87
+ const absPath = toAbsolute(path);
88
+ const exists = await RNFS.exists(absPath);
89
+ if (!exists) {
90
+ return [];
91
+ }
92
+ const items = await RNFS.readDir(absPath);
93
+ return items.map(item => item.name);
94
+ } catch (error) {
95
+ throw new AdapterIOError("listDir", path, error);
96
+ }
97
+ },
98
+ // ─────────────────────────────────────────────────────────────────
99
+ // File I/O
100
+ // ─────────────────────────────────────────────────────────────────
101
+
102
+ async readText(path, _encoding) {
103
+ try {
104
+ const absPath = toAbsolute(path);
105
+ return await RNFS.readFile(absPath, "utf8");
106
+ } catch (error) {
107
+ throw new AdapterIOError("readText", path, error);
108
+ }
109
+ },
110
+ async writeTextAtomic(path, content, _encoding) {
111
+ const tempPath = getTempPath(path);
112
+ const absPath = toAbsolute(path);
113
+ try {
114
+ // Ensure parent directory exists
115
+ const parentDir = absPath.substring(0, absPath.lastIndexOf("/"));
116
+ await RNFS.mkdir(parentDir);
117
+
118
+ // Write to temp file
119
+ await RNFS.writeFile(tempPath, content, "utf8");
120
+
121
+ // Atomic move
122
+ await RNFS.moveFile(tempPath, absPath);
123
+ } catch (error) {
124
+ // Clean up temp file on error
125
+ try {
126
+ await RNFS.unlink(tempPath);
127
+ } catch {
128
+ // Ignore cleanup errors
129
+ }
130
+ throw new AdapterIOError("writeTextAtomic", path, error);
131
+ }
132
+ },
133
+ async stat(path) {
134
+ try {
135
+ const absPath = toAbsolute(path);
136
+ const statResult = await RNFS.stat(absPath);
137
+ return {
138
+ sizeBytes: statResult.size,
139
+ mtimeMs: new Date(statResult.mtime).getTime()
140
+ };
141
+ } catch (error) {
142
+ throw new AdapterIOError("stat", path, error);
143
+ }
144
+ },
145
+ async writeBinaryAtomic(path, source, options) {
146
+ const tempPath = getTempPath(path);
147
+ const absPath = toAbsolute(path);
148
+ try {
149
+ // Ensure parent directory exists
150
+ const parentDir = absPath.substring(0, absPath.lastIndexOf("/"));
151
+ await RNFS.mkdir(parentDir);
152
+ let contentType;
153
+ switch (source.type) {
154
+ case "url":
155
+ {
156
+ // Download file with progress
157
+ const downloadResult = await new Promise((resolve, reject) => {
158
+ const {
159
+ promise
160
+ } = RNFS.downloadFile({
161
+ fromUrl: source.url,
162
+ toFile: tempPath,
163
+ headers: {
164
+ ...source.headers,
165
+ ...options?.headers
166
+ },
167
+ progress: res => {
168
+ if (options?.onProgress && res.contentLength > 0) {
169
+ const pct = res.bytesWritten / res.contentLength * 100;
170
+ options.onProgress(pct);
171
+ }
172
+ },
173
+ progressDivider: 1
174
+ });
175
+ promise.then(resolve).catch(reject);
176
+ });
177
+ if (downloadResult.statusCode < 200 || downloadResult.statusCode >= 300) {
178
+ throw new Error(`Download failed with status ${downloadResult.statusCode}`);
179
+ }
180
+ break;
181
+ }
182
+ case "file":
183
+ {
184
+ // Copy file
185
+ await RNFS.copyFile(source.filePath, tempPath);
186
+ break;
187
+ }
188
+ case "bytes":
189
+ {
190
+ // Convert Uint8Array to base64 and write
191
+ const base64 = uint8ArrayToBase64(source.bytes);
192
+ await RNFS.writeFile(tempPath, base64, "base64");
193
+ break;
194
+ }
195
+ case "blob":
196
+ {
197
+ // Blob is not supported on native
198
+ throw new UnsupportedSourceError("blob", "native-fs");
199
+ }
200
+ default:
201
+ {
202
+ const _exhaustive = source;
203
+ throw new UnsupportedSourceError(_exhaustive.type, "native-fs");
204
+ }
205
+ }
206
+
207
+ // Get size before moving
208
+ const statResult = await RNFS.stat(tempPath);
209
+
210
+ // Atomic move
211
+ await RNFS.moveFile(tempPath, absPath);
212
+
213
+ // Report 100% progress
214
+ options?.onProgress?.(100);
215
+ return {
216
+ sizeBytes: statResult.size,
217
+ contentType
218
+ };
219
+ } catch (error) {
220
+ // Clean up temp file on error
221
+ try {
222
+ await RNFS.unlink(tempPath);
223
+ } catch {
224
+ // Ignore cleanup errors
225
+ }
226
+ if (error instanceof UnsupportedSourceError) {
227
+ throw error;
228
+ }
229
+ throw new AdapterIOError("writeBinaryAtomic", path, error);
230
+ }
231
+ },
232
+ async getPublicUri(path) {
233
+ const absPath = toAbsolute(path);
234
+ // Return file:// URI for native platforms
235
+ return `file://${absPath}`;
236
+ }
237
+ };
238
+ return adapter;
239
+ }
240
+
241
+ /**
242
+ * Converts Uint8Array to base64 string.
243
+ */
244
+ function uint8ArrayToBase64(bytes) {
245
+ let binary = "";
246
+ for (let i = 0; i < bytes.length; i++) {
247
+ binary += String.fromCharCode(bytes[i]);
248
+ }
249
+ return btoa(binary);
250
+ }
251
+ //# sourceMappingURL=rnfsAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["RNFS","UnsupportedSourceError","AdapterIOError","createRnfsAdapter","options","baseDir","CachesDirectoryPath","namespace","rootPath","toAbsolute","path","getTempPath","Date","now","Math","random","toString","slice","adapter","kind","rootId","ensureDir","absPath","mkdir","error","exists","remove","unlink","removeDir","listDir","items","readDir","map","item","name","readText","_encoding","readFile","writeTextAtomic","content","tempPath","parentDir","substring","lastIndexOf","writeFile","moveFile","stat","statResult","sizeBytes","size","mtimeMs","mtime","getTime","writeBinaryAtomic","source","contentType","type","downloadResult","Promise","resolve","reject","promise","downloadFile","fromUrl","url","toFile","headers","progress","res","onProgress","contentLength","pct","bytesWritten","progressDivider","then","catch","statusCode","Error","copyFile","filePath","base64","uint8ArrayToBase64","bytes","_exhaustive","getPublicUri","binary","i","length","String","fromCharCode","btoa"],"sourceRoot":"../../../src","sources":["adapters/rnfsAdapter.ts"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,426 @@
1
+ /**
2
+ * Web Storage Adapter
3
+ *
4
+ * Implements IStorageAdapter using:
5
+ * - Cache Storage API for binary file storage
6
+ * - IndexedDB for metadata and index storage
7
+ */
8
+
9
+ import { UnsupportedSourceError, AdapterIOError } from "../core/errors";
10
+ const CACHE_URL_PREFIX = "https://cache.local";
11
+
12
+ /**
13
+ * Creates a storage adapter for web platforms using Cache Storage and IndexedDB.
14
+ */
15
+ export function createWebAdapter(options) {
16
+ const cacheName = options?.cacheName ?? "immutable-file-cache";
17
+ const idbName = options?.idbName ?? "immutable-file-cache-meta";
18
+ const namespace = options?.namespace ?? "default";
19
+ const rootId = `${cacheName}/${namespace}`;
20
+
21
+ // Track created blob URLs for cleanup
22
+ const blobUrls = new Set();
23
+
24
+ /**
25
+ * Converts adapter path to cache URL key.
26
+ */
27
+ const toCacheKey = path => {
28
+ return `${CACHE_URL_PREFIX}/${namespace}/${path}`;
29
+ };
30
+
31
+ /**
32
+ * Converts cache URL key back to adapter path.
33
+ */
34
+ const fromCacheKey = url => {
35
+ const prefix = `${CACHE_URL_PREFIX}/${namespace}/`;
36
+ return url.startsWith(prefix) ? url.slice(prefix.length) : url;
37
+ };
38
+
39
+ /**
40
+ * Gets the IDB object store key for metadata.
41
+ */
42
+ const toMetaKey = path => {
43
+ return `${namespace}/${path}`;
44
+ };
45
+
46
+ /**
47
+ * Opens or creates the IndexedDB database.
48
+ */
49
+ const openDB = () => {
50
+ return new Promise((resolve, reject) => {
51
+ const request = indexedDB.open(idbName, 1);
52
+ request.onerror = () => reject(request.error);
53
+ request.onsuccess = () => resolve(request.result);
54
+ request.onupgradeneeded = event => {
55
+ const db = event.target.result;
56
+ if (!db.objectStoreNames.contains("metadata")) {
57
+ db.createObjectStore("metadata");
58
+ }
59
+ if (!db.objectStoreNames.contains("directories")) {
60
+ db.createObjectStore("directories");
61
+ }
62
+ };
63
+ });
64
+ };
65
+
66
+ /**
67
+ * Gets metadata from IDB.
68
+ */
69
+ const getMetadata = async path => {
70
+ const db = await openDB();
71
+ return new Promise((resolve, reject) => {
72
+ const tx = db.transaction("metadata", "readonly");
73
+ const store = tx.objectStore("metadata");
74
+ const request = store.get(toMetaKey(path));
75
+ request.onerror = () => reject(request.error);
76
+ request.onsuccess = () => resolve(request.result ?? null);
77
+ tx.oncomplete = () => db.close();
78
+ });
79
+ };
80
+
81
+ /**
82
+ * Sets metadata in IDB.
83
+ */
84
+ const setMetadata = async (path, meta) => {
85
+ const db = await openDB();
86
+ return new Promise((resolve, reject) => {
87
+ const tx = db.transaction("metadata", "readwrite");
88
+ const store = tx.objectStore("metadata");
89
+ const request = store.put(meta, toMetaKey(path));
90
+ request.onerror = () => reject(request.error);
91
+ tx.oncomplete = () => {
92
+ db.close();
93
+ resolve();
94
+ };
95
+ });
96
+ };
97
+
98
+ /**
99
+ * Deletes metadata from IDB.
100
+ */
101
+ const deleteMetadata = async path => {
102
+ const db = await openDB();
103
+ return new Promise((resolve, reject) => {
104
+ const tx = db.transaction("metadata", "readwrite");
105
+ const store = tx.objectStore("metadata");
106
+ const request = store.delete(toMetaKey(path));
107
+ request.onerror = () => reject(request.error);
108
+ tx.oncomplete = () => {
109
+ db.close();
110
+ resolve();
111
+ };
112
+ });
113
+ };
114
+
115
+ /**
116
+ * Marks a directory as existing in IDB.
117
+ */
118
+ const markDirectory = async path => {
119
+ const db = await openDB();
120
+ return new Promise((resolve, reject) => {
121
+ const tx = db.transaction("directories", "readwrite");
122
+ const store = tx.objectStore("directories");
123
+ const request = store.put(true, toMetaKey(path));
124
+ request.onerror = () => reject(request.error);
125
+ tx.oncomplete = () => {
126
+ db.close();
127
+ resolve();
128
+ };
129
+ });
130
+ };
131
+
132
+ /**
133
+ * Checks if directory exists in IDB.
134
+ */
135
+ const hasDirectory = async path => {
136
+ const db = await openDB();
137
+ return new Promise((resolve, reject) => {
138
+ const tx = db.transaction("directories", "readonly");
139
+ const store = tx.objectStore("directories");
140
+ const request = store.get(toMetaKey(path));
141
+ request.onerror = () => reject(request.error);
142
+ request.onsuccess = () => resolve(request.result === true);
143
+ tx.oncomplete = () => db.close();
144
+ });
145
+ };
146
+ const adapter = {
147
+ kind: "web",
148
+ rootId,
149
+ // ─────────────────────────────────────────────────────────────────
150
+ // Directory Management
151
+ // ─────────────────────────────────────────────────────────────────
152
+
153
+ async ensureDir(path) {
154
+ try {
155
+ await markDirectory(path);
156
+ } catch (error) {
157
+ throw new AdapterIOError("ensureDir", path, error);
158
+ }
159
+ },
160
+ async exists(path) {
161
+ try {
162
+ // Check if it's a file in cache storage
163
+ const cache = await caches.open(cacheName);
164
+ const response = await cache.match(toCacheKey(path));
165
+ if (response) {
166
+ return true;
167
+ }
168
+
169
+ // Check if it's a directory
170
+ return await hasDirectory(path);
171
+ } catch {
172
+ return false;
173
+ }
174
+ },
175
+ async remove(path) {
176
+ try {
177
+ const cache = await caches.open(cacheName);
178
+ await cache.delete(toCacheKey(path));
179
+ await deleteMetadata(path);
180
+ } catch (error) {
181
+ throw new AdapterIOError("remove", path, error);
182
+ }
183
+ },
184
+ async removeDir(path) {
185
+ try {
186
+ const cache = await caches.open(cacheName);
187
+ const keys = await cache.keys();
188
+ const prefix = toCacheKey(path + "/");
189
+
190
+ // Delete all entries with this prefix
191
+ for (const request of keys) {
192
+ if (request.url.startsWith(prefix) || request.url === toCacheKey(path)) {
193
+ await cache.delete(request);
194
+ const entryPath = fromCacheKey(request.url);
195
+ await deleteMetadata(entryPath);
196
+ }
197
+ }
198
+
199
+ // Remove directory marker
200
+ const db = await openDB();
201
+ await new Promise((resolve, reject) => {
202
+ const tx = db.transaction("directories", "readwrite");
203
+ const store = tx.objectStore("directories");
204
+ store.delete(toMetaKey(path));
205
+ tx.oncomplete = () => {
206
+ db.close();
207
+ resolve();
208
+ };
209
+ tx.onerror = () => reject(tx.error);
210
+ });
211
+ } catch (error) {
212
+ throw new AdapterIOError("removeDir", path, error);
213
+ }
214
+ },
215
+ async listDir(path) {
216
+ try {
217
+ const cache = await caches.open(cacheName);
218
+ const keys = await cache.keys();
219
+ const prefix = toCacheKey(path + "/");
220
+ const entries = [];
221
+ for (const request of keys) {
222
+ if (request.url.startsWith(prefix)) {
223
+ // Extract the relative path from this directory
224
+ const relativePath = request.url.slice(prefix.length);
225
+ // Only include direct children (no further slashes)
226
+ if (!relativePath.includes("/")) {
227
+ entries.push(relativePath);
228
+ }
229
+ }
230
+ }
231
+ return entries;
232
+ } catch (error) {
233
+ throw new AdapterIOError("listDir", path, error);
234
+ }
235
+ },
236
+ // ─────────────────────────────────────────────────────────────────
237
+ // File I/O
238
+ // ─────────────────────────────────────────────────────────────────
239
+
240
+ async readText(path, _encoding) {
241
+ try {
242
+ const cache = await caches.open(cacheName);
243
+ const response = await cache.match(toCacheKey(path));
244
+ if (!response) {
245
+ throw new Error("File not found");
246
+ }
247
+ return await response.text();
248
+ } catch (error) {
249
+ throw new AdapterIOError("readText", path, error);
250
+ }
251
+ },
252
+ async writeTextAtomic(path, content, _encoding) {
253
+ try {
254
+ const cache = await caches.open(cacheName);
255
+ const blob = new Blob([content], {
256
+ type: "text/plain; charset=utf-8"
257
+ });
258
+ const response = new Response(blob);
259
+
260
+ // Cache Storage puts are atomic by nature
261
+ await cache.put(toCacheKey(path), response);
262
+
263
+ // Store metadata
264
+ await setMetadata(path, {
265
+ sizeBytes: blob.size,
266
+ mtimeMs: Date.now(),
267
+ contentType: "text/plain"
268
+ });
269
+ } catch (error) {
270
+ throw new AdapterIOError("writeTextAtomic", path, error);
271
+ }
272
+ },
273
+ async stat(path) {
274
+ try {
275
+ const meta = await getMetadata(path);
276
+ if (!meta) {
277
+ throw new Error("File not found");
278
+ }
279
+ return {
280
+ sizeBytes: meta.sizeBytes,
281
+ mtimeMs: meta.mtimeMs
282
+ };
283
+ } catch (error) {
284
+ throw new AdapterIOError("stat", path, error);
285
+ }
286
+ },
287
+ async writeBinaryAtomic(path, source, options) {
288
+ try {
289
+ const cache = await caches.open(cacheName);
290
+ let blob;
291
+ let contentType;
292
+ switch (source.type) {
293
+ case "url":
294
+ {
295
+ // Fetch with progress tracking
296
+ const response = await fetch(source.url, {
297
+ headers: {
298
+ ...source.headers,
299
+ ...options?.headers
300
+ }
301
+ });
302
+ if (!response.ok) {
303
+ throw new Error(`Fetch failed with status ${response.status}`);
304
+ }
305
+ contentType = response.headers.get("content-type") ?? undefined;
306
+
307
+ // Try to track progress if content-length is available
308
+ const contentLength = response.headers.get("content-length");
309
+ if (contentLength && options?.onProgress && response.body) {
310
+ const total = parseInt(contentLength, 10);
311
+ let loaded = 0;
312
+ const reader = response.body.getReader();
313
+ const chunks = [];
314
+
315
+ // eslint-disable-next-line no-constant-condition
316
+ while (true) {
317
+ const {
318
+ done,
319
+ value
320
+ } = await reader.read();
321
+ if (done) {
322
+ break;
323
+ }
324
+ chunks.push(value);
325
+ loaded += value.length;
326
+ options.onProgress(loaded / total * 100);
327
+ }
328
+ blob = new Blob(chunks.map(c => c.buffer), {
329
+ type: contentType
330
+ });
331
+ } else {
332
+ blob = await response.blob();
333
+ options?.onProgress?.(100);
334
+ }
335
+ break;
336
+ }
337
+ case "blob":
338
+ {
339
+ blob = source.blob;
340
+ contentType = source.blob.type || undefined;
341
+ options?.onProgress?.(100);
342
+ break;
343
+ }
344
+ case "bytes":
345
+ {
346
+ blob = new Blob([source.bytes.buffer]);
347
+ options?.onProgress?.(100);
348
+ break;
349
+ }
350
+ case "file":
351
+ {
352
+ // File paths not supported on web
353
+ throw new UnsupportedSourceError("file", "web");
354
+ }
355
+ default:
356
+ {
357
+ const _exhaustive = source;
358
+ throw new UnsupportedSourceError(_exhaustive.type, "web");
359
+ }
360
+ }
361
+
362
+ // Store in cache
363
+ const response = new Response(blob, {
364
+ headers: contentType ? {
365
+ "Content-Type": contentType
366
+ } : {}
367
+ });
368
+ await cache.put(toCacheKey(path), response);
369
+
370
+ // Store metadata
371
+ const meta = {
372
+ sizeBytes: blob.size,
373
+ mtimeMs: Date.now(),
374
+ contentType
375
+ };
376
+ await setMetadata(path, meta);
377
+ return {
378
+ sizeBytes: blob.size,
379
+ contentType
380
+ };
381
+ } catch (error) {
382
+ if (error instanceof UnsupportedSourceError) {
383
+ throw error;
384
+ }
385
+ throw new AdapterIOError("writeBinaryAtomic", path, error);
386
+ }
387
+ },
388
+ async getPublicUri(path) {
389
+ try {
390
+ const cache = await caches.open(cacheName);
391
+ const response = await cache.match(toCacheKey(path));
392
+ if (!response) {
393
+ throw new Error("File not found");
394
+ }
395
+ const blob = await response.blob();
396
+ const blobUrl = URL.createObjectURL(blob);
397
+
398
+ // Track for cleanup
399
+ blobUrls.add(blobUrl);
400
+ return blobUrl;
401
+ } catch (error) {
402
+ throw new AdapterIOError("getPublicUri", path, error);
403
+ }
404
+ }
405
+ };
406
+
407
+ // Add cleanup method (non-standard, for internal use)
408
+ adapter.revokeUri = uri => {
409
+ if (blobUrls.has(uri)) {
410
+ URL.revokeObjectURL(uri);
411
+ blobUrls.delete(uri);
412
+ }
413
+ };
414
+ adapter.revokeAllUris = () => {
415
+ for (const uri of blobUrls) {
416
+ URL.revokeObjectURL(uri);
417
+ }
418
+ blobUrls.clear();
419
+ };
420
+ return adapter;
421
+ }
422
+
423
+ /**
424
+ * Extended interface for web adapter with cleanup methods.
425
+ */
426
+ //# sourceMappingURL=webAdapter.js.map