react-native-buffered-blob 1.0.0

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 (93) hide show
  1. package/android/AGENTS.md +74 -0
  2. package/android/build.gradle +34 -0
  3. package/android/src/main/AndroidManifest.xml +4 -0
  4. package/android/src/main/java/com/bufferedblob/BufferedBlobModule.kt +274 -0
  5. package/android/src/main/java/com/bufferedblob/BufferedBlobPackage.kt +32 -0
  6. package/android/src/main/java/com/bufferedblob/HandleRegistry.kt +84 -0
  7. package/android/src/main/java/com/bufferedblob/StreamingBridge.kt +211 -0
  8. package/cpp/AGENTS.md +71 -0
  9. package/cpp/AndroidPlatformBridge.cpp +437 -0
  10. package/cpp/AndroidPlatformBridge.h +79 -0
  11. package/cpp/BufferedBlobStreamingHostObject.cpp +344 -0
  12. package/cpp/BufferedBlobStreamingHostObject.h +118 -0
  13. package/cpp/CMakeLists.txt +49 -0
  14. package/cpp/jni_onload.cpp +32 -0
  15. package/ios/AGENTS.md +76 -0
  16. package/ios/BufferedBlobModule.h +44 -0
  17. package/ios/BufferedBlobModule.m +433 -0
  18. package/ios/BufferedBlobModule.mm +192 -0
  19. package/ios/BufferedBlobStreamingBridge.h +21 -0
  20. package/ios/BufferedBlobStreamingBridge.mm +442 -0
  21. package/ios/HandleRegistry.h +29 -0
  22. package/ios/HandleRegistry.m +67 -0
  23. package/ios/HandleTypes.h +83 -0
  24. package/ios/HandleTypes.m +333 -0
  25. package/lib/module/AGENTS.md +70 -0
  26. package/lib/module/NativeBufferedBlob.js +5 -0
  27. package/lib/module/NativeBufferedBlob.js.map +1 -0
  28. package/lib/module/api/AGENTS.md +62 -0
  29. package/lib/module/api/download.js +40 -0
  30. package/lib/module/api/download.js.map +1 -0
  31. package/lib/module/api/fileOps.js +70 -0
  32. package/lib/module/api/fileOps.js.map +1 -0
  33. package/lib/module/api/hash.js +13 -0
  34. package/lib/module/api/hash.js.map +1 -0
  35. package/lib/module/api/readFile.js +23 -0
  36. package/lib/module/api/readFile.js.map +1 -0
  37. package/lib/module/api/writeFile.js +18 -0
  38. package/lib/module/api/writeFile.js.map +1 -0
  39. package/lib/module/errors.js +45 -0
  40. package/lib/module/errors.js.map +1 -0
  41. package/lib/module/index.js +25 -0
  42. package/lib/module/index.js.map +1 -0
  43. package/lib/module/module.js +19 -0
  44. package/lib/module/module.js.map +1 -0
  45. package/lib/module/package.json +1 -0
  46. package/lib/module/paths.js +32 -0
  47. package/lib/module/paths.js.map +1 -0
  48. package/lib/module/types.js +15 -0
  49. package/lib/module/types.js.map +1 -0
  50. package/lib/module/wrappers.js +107 -0
  51. package/lib/module/wrappers.js.map +1 -0
  52. package/lib/typescript/package.json +1 -0
  53. package/lib/typescript/src/NativeBufferedBlob.d.ts +37 -0
  54. package/lib/typescript/src/NativeBufferedBlob.d.ts.map +1 -0
  55. package/lib/typescript/src/api/download.d.ts +13 -0
  56. package/lib/typescript/src/api/download.d.ts.map +1 -0
  57. package/lib/typescript/src/api/fileOps.d.ts +9 -0
  58. package/lib/typescript/src/api/fileOps.d.ts.map +1 -0
  59. package/lib/typescript/src/api/hash.d.ts +3 -0
  60. package/lib/typescript/src/api/hash.d.ts.map +1 -0
  61. package/lib/typescript/src/api/readFile.d.ts +3 -0
  62. package/lib/typescript/src/api/readFile.d.ts.map +1 -0
  63. package/lib/typescript/src/api/writeFile.d.ts +3 -0
  64. package/lib/typescript/src/api/writeFile.d.ts.map +1 -0
  65. package/lib/typescript/src/errors.d.ts +25 -0
  66. package/lib/typescript/src/errors.d.ts.map +1 -0
  67. package/lib/typescript/src/index.d.ts +11 -0
  68. package/lib/typescript/src/index.d.ts.map +1 -0
  69. package/lib/typescript/src/module.d.ts +23 -0
  70. package/lib/typescript/src/module.d.ts.map +1 -0
  71. package/lib/typescript/src/paths.d.ts +11 -0
  72. package/lib/typescript/src/paths.d.ts.map +1 -0
  73. package/lib/typescript/src/types.d.ts +37 -0
  74. package/lib/typescript/src/types.d.ts.map +1 -0
  75. package/lib/typescript/src/wrappers.d.ts +14 -0
  76. package/lib/typescript/src/wrappers.d.ts.map +1 -0
  77. package/package.json +114 -0
  78. package/react-native-buffered-blob.podspec +37 -0
  79. package/react-native.config.js +10 -0
  80. package/src/AGENTS.md +70 -0
  81. package/src/NativeBufferedBlob.ts +54 -0
  82. package/src/api/AGENTS.md +62 -0
  83. package/src/api/download.ts +46 -0
  84. package/src/api/fileOps.ts +83 -0
  85. package/src/api/hash.ts +14 -0
  86. package/src/api/readFile.ts +37 -0
  87. package/src/api/writeFile.ts +24 -0
  88. package/src/errors.ts +50 -0
  89. package/src/index.ts +28 -0
  90. package/src/module.ts +48 -0
  91. package/src/paths.ts +35 -0
  92. package/src/types.ts +42 -0
  93. package/src/wrappers.ts +123 -0
@@ -0,0 +1,333 @@
1
+ #import "HandleTypes.h"
2
+
3
+ // ──────────────────────────────────────────────────────────────────────
4
+ #pragma mark - ReaderHandleIOS
5
+ // ──────────────────────────────────────────────────────────────────────
6
+
7
+ @implementation ReaderHandleIOS {
8
+ NSLock *_lock;
9
+ int64_t _bytesReadBacking;
10
+ BOOL _isEOFBacking;
11
+ BOOL _isClosedBacking;
12
+ }
13
+
14
+ static NSInteger _readerNextId = 0;
15
+ static NSLock *_readerIdLock = nil;
16
+
17
+ + (void)initialize {
18
+ if (self == [ReaderHandleIOS class]) {
19
+ _readerIdLock = [NSLock new];
20
+ }
21
+ }
22
+
23
+ + (NSInteger)nextUniqueId {
24
+ [_readerIdLock lock];
25
+ NSInteger uid = ++_readerNextId;
26
+ [_readerIdLock unlock];
27
+ return uid;
28
+ }
29
+
30
+ - (nullable instancetype)initWithPath:(NSString *)path
31
+ bufferSize:(NSInteger)bufferSize
32
+ error:(NSError **)error {
33
+ self = [super init];
34
+ if (!self) return nil;
35
+
36
+ NSInputStream *stream = [NSInputStream inputStreamWithFileAtPath:path];
37
+ if (!stream) {
38
+ if (error) {
39
+ *error = [NSError errorWithDomain:@"BufferedBlob" code:1
40
+ userInfo:@{NSLocalizedDescriptionKey:
41
+ [NSString stringWithFormat:@"[FILE_NOT_FOUND] Could not open file: %@", path]}];
42
+ }
43
+ return nil;
44
+ }
45
+
46
+ _inputStream = stream;
47
+ _bufferSize = bufferSize;
48
+ _lock = [NSLock new];
49
+ _bytesReadBacking = 0;
50
+ _isEOFBacking = NO;
51
+ _isClosedBacking = NO;
52
+
53
+ NSString *label = [NSString stringWithFormat:@"com.bufferedblob.reader.%ld", (long)[ReaderHandleIOS nextUniqueId]];
54
+ _queue = dispatch_queue_create(label.UTF8String, DISPATCH_QUEUE_SERIAL);
55
+ dispatch_set_target_queue(_queue, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0));
56
+
57
+ // Get file size
58
+ NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:error];
59
+ if (!attrs) return nil;
60
+ _fileSize = [attrs[NSFileSize] longLongValue];
61
+
62
+ [stream open];
63
+ if (stream.streamStatus == NSStreamStatusError) {
64
+ [stream close];
65
+ if (error) {
66
+ *error = [NSError errorWithDomain:@"BufferedBlob" code:1
67
+ userInfo:@{NSLocalizedDescriptionKey:
68
+ [NSString stringWithFormat:@"[FILE_NOT_FOUND] Failed to open stream: %@", path]}];
69
+ }
70
+ return nil;
71
+ }
72
+
73
+ return self;
74
+ }
75
+
76
+ - (int64_t)bytesRead {
77
+ [_lock lock];
78
+ int64_t val = _bytesReadBacking;
79
+ [_lock unlock];
80
+ return val;
81
+ }
82
+
83
+ - (void)setBytesRead:(int64_t)bytesRead {
84
+ [_lock lock];
85
+ _bytesReadBacking = bytesRead;
86
+ [_lock unlock];
87
+ }
88
+
89
+ - (BOOL)isEOF {
90
+ [_lock lock];
91
+ BOOL val = _isEOFBacking;
92
+ [_lock unlock];
93
+ return val;
94
+ }
95
+
96
+ - (void)setIsEOF:(BOOL)isEOF {
97
+ [_lock lock];
98
+ _isEOFBacking = isEOF;
99
+ [_lock unlock];
100
+ }
101
+
102
+ - (BOOL)isClosed {
103
+ [_lock lock];
104
+ BOOL val = _isClosedBacking;
105
+ [_lock unlock];
106
+ return val;
107
+ }
108
+
109
+ - (void)setIsClosed:(BOOL)isClosed {
110
+ [_lock lock];
111
+ _isClosedBacking = isClosed;
112
+ [_lock unlock];
113
+ }
114
+
115
+ - (void)closeHandle {
116
+ [_lock lock];
117
+ if (_isClosedBacking) {
118
+ [_lock unlock];
119
+ return;
120
+ }
121
+ _isClosedBacking = YES;
122
+ NSInputStream *stream = _inputStream;
123
+ [_lock unlock];
124
+ // Dispatch stream close to the serial queue so it runs AFTER
125
+ // all pending I/O blocks have drained.
126
+ dispatch_async(_queue, ^{
127
+ [stream close];
128
+ });
129
+ }
130
+
131
+ - (void)dealloc {
132
+ [self closeHandle];
133
+ }
134
+
135
+ @end
136
+
137
+ // ──────────────────────────────────────────────────────────────────────
138
+ #pragma mark - WriterHandleIOS
139
+ // ──────────────────────────────────────────────────────────────────────
140
+
141
+ @implementation WriterHandleIOS {
142
+ NSLock *_lock;
143
+ int64_t _bytesWrittenBacking;
144
+ BOOL _isClosedBacking;
145
+ }
146
+
147
+ static NSInteger _writerNextId = 0;
148
+ static NSLock *_writerIdLock = nil;
149
+
150
+ + (void)initialize {
151
+ if (self == [WriterHandleIOS class]) {
152
+ _writerIdLock = [NSLock new];
153
+ }
154
+ }
155
+
156
+ + (NSInteger)nextUniqueId {
157
+ [_writerIdLock lock];
158
+ NSInteger uid = ++_writerNextId;
159
+ [_writerIdLock unlock];
160
+ return uid;
161
+ }
162
+
163
+ - (nullable instancetype)initWithPath:(NSString *)path
164
+ append:(BOOL)append
165
+ error:(NSError **)error {
166
+ self = [super init];
167
+ if (!self) return nil;
168
+
169
+ NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:path append:append];
170
+ if (!stream) {
171
+ if (error) {
172
+ *error = [NSError errorWithDomain:@"BufferedBlob" code:1
173
+ userInfo:@{NSLocalizedDescriptionKey:
174
+ [NSString stringWithFormat:@"[FILE_NOT_FOUND] Could not open file for writing: %@", path]}];
175
+ }
176
+ return nil;
177
+ }
178
+
179
+ _outputStream = stream;
180
+ _lock = [NSLock new];
181
+ _bytesWrittenBacking = 0;
182
+ _isClosedBacking = NO;
183
+
184
+ NSString *label = [NSString stringWithFormat:@"com.bufferedblob.writer.%ld", (long)[WriterHandleIOS nextUniqueId]];
185
+ _queue = dispatch_queue_create(label.UTF8String, DISPATCH_QUEUE_SERIAL);
186
+ dispatch_set_target_queue(_queue, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0));
187
+
188
+ [stream open];
189
+ if (stream.streamStatus == NSStreamStatusError) {
190
+ [stream close];
191
+ if (error) {
192
+ *error = [NSError errorWithDomain:@"BufferedBlob" code:1
193
+ userInfo:@{NSLocalizedDescriptionKey:
194
+ [NSString stringWithFormat:@"[FILE_NOT_FOUND] Failed to open write stream: %@", path]}];
195
+ }
196
+ return nil;
197
+ }
198
+
199
+ return self;
200
+ }
201
+
202
+ - (int64_t)bytesWritten {
203
+ [_lock lock];
204
+ int64_t val = _bytesWrittenBacking;
205
+ [_lock unlock];
206
+ return val;
207
+ }
208
+
209
+ - (void)setBytesWritten:(int64_t)bytesWritten {
210
+ [_lock lock];
211
+ _bytesWrittenBacking = bytesWritten;
212
+ [_lock unlock];
213
+ }
214
+
215
+ - (BOOL)isClosed {
216
+ [_lock lock];
217
+ BOOL val = _isClosedBacking;
218
+ [_lock unlock];
219
+ return val;
220
+ }
221
+
222
+ - (void)setIsClosed:(BOOL)isClosed {
223
+ [_lock lock];
224
+ _isClosedBacking = isClosed;
225
+ [_lock unlock];
226
+ }
227
+
228
+ - (void)closeHandle {
229
+ [_lock lock];
230
+ if (_isClosedBacking) {
231
+ [_lock unlock];
232
+ return;
233
+ }
234
+ _isClosedBacking = YES;
235
+ NSOutputStream *stream = _outputStream;
236
+ [_lock unlock];
237
+ dispatch_async(_queue, ^{
238
+ [stream close];
239
+ });
240
+ }
241
+
242
+ - (void)dealloc {
243
+ [self closeHandle];
244
+ }
245
+
246
+ @end
247
+
248
+ // ──────────────────────────────────────────────────────────────────────
249
+ #pragma mark - DownloaderHandleIOS
250
+ // ──────────────────────────────────────────────────────────────────────
251
+
252
+ @implementation DownloaderHandleIOS {
253
+ NSLock *_lock;
254
+ BOOL _isCancelledBacking;
255
+ NSURLSession *_session;
256
+ NSURLSessionTask *_task;
257
+ }
258
+
259
+ static NSInteger _downloaderNextId = 0;
260
+ static NSLock *_downloaderIdLock = nil;
261
+
262
+ + (void)initialize {
263
+ if (self == [DownloaderHandleIOS class]) {
264
+ _downloaderIdLock = [NSLock new];
265
+ }
266
+ }
267
+
268
+ + (NSInteger)nextUniqueId {
269
+ [_downloaderIdLock lock];
270
+ NSInteger uid = ++_downloaderNextId;
271
+ [_downloaderIdLock unlock];
272
+ return uid;
273
+ }
274
+
275
+ - (instancetype)initWithURL:(NSString *)url
276
+ destPath:(NSString *)destPath
277
+ headers:(NSDictionary<NSString *, NSString *> *)headers {
278
+ self = [super init];
279
+ if (self) {
280
+ _url = [url copy];
281
+ _destPath = [destPath copy];
282
+ _headers = [headers copy];
283
+ _lock = [NSLock new];
284
+ _isCancelledBacking = NO;
285
+
286
+ NSString *label = [NSString stringWithFormat:@"com.bufferedblob.downloader.%ld", (long)[DownloaderHandleIOS nextUniqueId]];
287
+ _queue = dispatch_queue_create(label.UTF8String, DISPATCH_QUEUE_SERIAL);
288
+ dispatch_set_target_queue(_queue, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0));
289
+ }
290
+ return self;
291
+ }
292
+
293
+ - (BOOL)isCancelled {
294
+ [_lock lock];
295
+ BOOL val = _isCancelledBacking;
296
+ [_lock unlock];
297
+ return val;
298
+ }
299
+
300
+ - (void)setIsCancelled:(BOOL)isCancelled {
301
+ [_lock lock];
302
+ _isCancelledBacking = isCancelled;
303
+ [_lock unlock];
304
+ }
305
+
306
+ - (void)storeSession:(NSURLSession *)session task:(NSURLSessionTask *)task {
307
+ [_lock lock];
308
+ _session = session;
309
+ _task = task;
310
+ [_lock unlock];
311
+ }
312
+
313
+ - (void)cancel {
314
+ [_lock lock];
315
+ if (_isCancelledBacking) {
316
+ [_lock unlock];
317
+ return;
318
+ }
319
+ _isCancelledBacking = YES;
320
+ NSURLSession *session = _session;
321
+ NSURLSessionTask *task = _task;
322
+ [_lock unlock];
323
+
324
+ // Cancel outside the lock to avoid potential deadlock with delegate callbacks
325
+ [task cancel];
326
+ [session invalidateAndCancel];
327
+ }
328
+
329
+ - (void)closeHandle {
330
+ [self cancel];
331
+ }
332
+
333
+ @end
@@ -0,0 +1,70 @@
1
+ <!-- Parent: ../AGENTS.md -->
2
+ <!-- Generated: 2026-02-15 -->
3
+
4
+ # src/
5
+
6
+ TypeScript API layer: Turbo Module spec, native module interface, streaming proxy, and high-level file operations.
7
+
8
+ ## Purpose
9
+
10
+ Provides the JavaScript interface to the native BufferedBlob module:
11
+ - **Turbo Module specification** (NativeBufferedBlob.ts) — codegen input for handle factories and FS ops
12
+ - **Streaming proxy** (module.ts) — JSI HostObject accessor, install() wiring
13
+ - **Type wrappers** (types.ts) — BlobReader/BlobWriter interfaces with property proxying
14
+ - **Error handling** (errors.ts) — ErrorCode enum and wrapError() utility
15
+ - **Paths** (paths.ts) — Dirs constants and path utilities
16
+
17
+ ## Key Files
18
+
19
+ | File | Description |
20
+ |------|-------------|
21
+ | `NativeBufferedBlob.ts` | Turbo Module spec: install(), handle factories, FS ops, hashFile. Codegen input. |
22
+ | `module.ts` | NativeModule (TurboModuleRegistry), StreamingProxy interface, getStreamingProxy() |
23
+ | `types.ts` | HashAlgorithm, FileType enums; FileInfo, BlobReader, BlobWriter interfaces; wrapReader/wrapWriter |
24
+ | `errors.ts` | BlobError class, ErrorCode enum, wrapError() helper |
25
+ | `paths.ts` | Dirs constants (documentDir, cacheDir, tempDir, downloadDir); join(), dirname(), basename(), extname() |
26
+ | `index.ts` | Barrel exports for public API |
27
+
28
+ ## Subdirectories
29
+
30
+ | Directory | Purpose |
31
+ |-----------|---------|
32
+ | `api/` | High-level file operations, download, hashing, streaming wrappers |
33
+
34
+ ## For AI Agents
35
+
36
+ ### Working In This Directory
37
+
38
+ 1. **Turbo Module spec**: Changes to `NativeBufferedBlob.ts` trigger codegen. After editing, run `yarn prepare` to regenerate native module headers.
39
+ 2. **Streaming proxy access**: `getStreamingProxy()` returns the JSI HostObject installed by native code. Always call `NativeModule.install()` first (done in module.ts).
40
+ 3. **Handle pattern**: Turbo Module returns numeric handles. Pass to streaming proxy methods. Always close handles to release native resources.
41
+ 4. **Type safety**: Use strict TypeScript. BlobReader/BlobWriter are interface wrappers; do NOT spread HostObject properties (getters would be lost).
42
+ 5. **Error propagation**: Catch errors from native calls; use `wrapError()` to normalize error messages.
43
+
44
+ ### Testing Requirements
45
+
46
+ - **Type checking**: Verify no TypeScript errors in strict mode
47
+ - **Module loading**: Verify `install()` succeeds and JSI HostObject is wired
48
+ - **Handle cleanup**: Verify handles are closed; no dangling native resources
49
+ - **Error cases**: Test file not found, invalid paths, invalid buffer sizes, cancelled operations
50
+
51
+ ### Common Patterns
52
+
53
+ 1. **Create reader**: `const handle = NativeModule.openRead(path, bufferSize)` -> `const reader = wrapReader(handle, proxy)` -> `reader.readNextChunk()` -> `reader.close()`
54
+ 2. **Create writer**: `const handle = NativeModule.openWrite(path, append)` -> `const writer = wrapWriter(handle, proxy)` -> `writer.write(data)` -> `writer.flush()` -> `writer.close()`
55
+ 3. **File ops**: `exists()`, `stat()`, `mkdir()`, `ls()`, `cp()`, `mv()`, `unlink()` all return Promises
56
+ 4. **Hash file**: `hashFile(path, 'sha256' | 'md5')` streams file without loading into memory
57
+ 5. **Download with progress**: Use `download({ url, destPath, onProgress })` API; streaming proxy handles details
58
+
59
+ ## Dependencies
60
+
61
+ ### Internal
62
+ - Turbo Module spec generated from `NativeBufferedBlob.ts` at build time
63
+ - `module.ts` calls `install()` on first import (side effect)
64
+ - `api/` layer depends on NativeModule and getStreamingProxy()
65
+
66
+ ### External
67
+ - **React Native** >= 0.76.0 (TurboModuleRegistry, JSI)
68
+ - **TypeScript** ^5.9.2 (type checking, codegen)
69
+
70
+ <!-- MANUAL: Document API changes, Turbo Module version bumps, deprecations -->
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import { TurboModuleRegistry } from 'react-native';
4
+ export default TurboModuleRegistry.getEnforcing('BufferedBlob');
5
+ //# sourceMappingURL=NativeBufferedBlob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeBufferedBlob.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;AAoDlD,eAAeA,mBAAmB,CAACC,YAAY,CAAO,cAAc,CAAC","ignoreList":[]}
@@ -0,0 +1,62 @@
1
+ <!-- Parent: ../AGENTS.md -->
2
+ <!-- Generated: 2026-02-15 -->
3
+
4
+ # src/api/
5
+
6
+ High-level convenience APIs for file operations, streaming, hashing, and downloads.
7
+
8
+ ## Purpose
9
+
10
+ Provide idiomatic TypeScript wrappers around Turbo Module calls and JSI HostObject streaming:
11
+ - **File operations**: exists, stat, mkdir, ls, cp, mv, unlink
12
+ - **Streaming**: readFile (deprecated), writeFile (deprecated)
13
+ - **Hashing**: hashFile with streaming (SHA256, MD5)
14
+ - **Downloads**: download with progress callback
15
+
16
+ ## Key Files
17
+
18
+ | File | Description |
19
+ |------|-------------|
20
+ | `fileOps.ts` | FS operations: exists, stat, mkdir, ls, cp, mv, unlink. All return Promises. |
21
+ | `hash.ts` | hashFile(path, algorithm) → Promise<string>. Streams file; supports 'sha256', 'md5'. |
22
+ | `download.ts` | download(options) → Promise<void>. Returns DownloadOptions interface, handles progress callback. |
23
+ | `readFile.ts` | DEPRECATED: createReader(path, bufferSize) → BlobReader. Use openRead() + wrapReader() directly. |
24
+ | `writeFile.ts` | DEPRECATED: createWriter(path, append) → BlobWriter. Use openWrite() + wrapWriter() directly. |
25
+
26
+ ## For AI Agents
27
+
28
+ ### Working In This Directory
29
+
30
+ 1. **File operations**: All async. Return Promises that resolve/reject with platform-specific error codes.
31
+ 2. **Streaming shortcuts**: readFile/writeFile are deprecated convenience wrappers. New code should use `openRead()` + `wrapReader()` directly for explicit handle management.
32
+ 3. **Download pattern**: `download()` creates handle, calls JSI streaming startDownload, manages progress callback, cleans up handle in finally block.
33
+ 4. **Error handling**: Use `wrapError()` from parent module to normalize native errors.
34
+ 5. **Handle lifecycle**: Always close streaming handles (readFile/writeFile do this implicitly; direct handle usage requires explicit close).
35
+
36
+ ### Testing Requirements
37
+
38
+ - **File ops**: Create temp files, test exists/stat/mkdir/ls/cp/mv/unlink
39
+ - **Hashing**: Verify hash output against known digests (sha256, md5)
40
+ - **Download**: Test with HTTP server, verify progress callback fires, verify file written
41
+ - **Error cases**: Test file not found, permission denied, invalid paths, cancelled downloads
42
+
43
+ ### Common Patterns
44
+
45
+ 1. **Check file exists**: `const exists = await exists(path)`
46
+ 2. **Get file info**: `const info = await stat(path)` → {path, name, size, type, lastModified}
47
+ 3. **List directory**: `const files = await ls(dirPath)` → FileInfo[]
48
+ 4. **Copy file**: `await cp(srcPath, destPath)`
49
+ 5. **Hash file**: `const hash = await hashFile(path, 'sha256')`
50
+ 6. **Download with progress**: `await download({ url, destPath, headers: {}, onProgress: (p) => console.log(p.progress) })`
51
+
52
+ ## Dependencies
53
+
54
+ ### Internal
55
+ - `../module.ts` — NativeModule, getStreamingProxy()
56
+ - `../types.ts` — BlobReader, BlobWriter, FileInfo, DownloadProgress, HashAlgorithm
57
+ - `../errors.ts` — wrapError()
58
+
59
+ ### External
60
+ - **React Native** >= 0.76.0 (TurboModuleRegistry for NativeModule calls)
61
+
62
+ <!-- MANUAL: Document deprecated APIs, migration guides for readFile/writeFile users -->
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+
3
+ import { NativeModule, getStreamingProxy } from "../module.js";
4
+ import { wrapError } from "../errors.js";
5
+ export function download(options) {
6
+ const {
7
+ url,
8
+ destPath,
9
+ headers = {},
10
+ onProgress
11
+ } = options;
12
+ try {
13
+ const handleId = NativeModule.createDownload(url, destPath, headers);
14
+ const streaming = getStreamingProxy();
15
+ const progressCallback = onProgress ? (bytesDownloaded, totalBytes, progress) => {
16
+ onProgress({
17
+ bytesDownloaded,
18
+ totalBytes,
19
+ progress
20
+ });
21
+ } : (_b, _t, _p) => {};
22
+ const promise = (async () => {
23
+ try {
24
+ await streaming.startDownload(handleId, progressCallback);
25
+ } finally {
26
+ NativeModule.closeHandle(handleId);
27
+ }
28
+ })();
29
+ const cancel = () => {
30
+ streaming.cancelDownload(handleId);
31
+ };
32
+ return {
33
+ promise,
34
+ cancel
35
+ };
36
+ } catch (e) {
37
+ throw wrapError(e, destPath);
38
+ }
39
+ }
40
+ //# sourceMappingURL=download.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NativeModule","getStreamingProxy","wrapError","download","options","url","destPath","headers","onProgress","handleId","createDownload","streaming","progressCallback","bytesDownloaded","totalBytes","progress","_b","_t","_p","promise","startDownload","closeHandle","cancel","cancelDownload","e"],"sourceRoot":"../../../src","sources":["api/download.ts"],"mappings":";;AAAA,SAASA,YAAY,EAAEC,iBAAiB,QAAQ,cAAW;AAC3D,SAASC,SAAS,QAAQ,cAAW;AAerC,OAAO,SAASC,QAAQA,CAACC,OAAwB,EAAkB;EACjE,MAAM;IAAEC,GAAG;IAAEC,QAAQ;IAAEC,OAAO,GAAG,CAAC,CAAC;IAAEC;EAAW,CAAC,GAAGJ,OAAO;EAE3D,IAAI;IACF,MAAMK,QAAQ,GAAGT,YAAY,CAACU,cAAc,CAACL,GAAG,EAAEC,QAAQ,EAAEC,OAAO,CAAC;IACpE,MAAMI,SAAS,GAAGV,iBAAiB,CAAC,CAAC;IAErC,MAAMW,gBAAgB,GAAGJ,UAAU,GAC/B,CAACK,eAAuB,EAAEC,UAAkB,EAAEC,QAAgB,KAAK;MACjEP,UAAU,CAAC;QAAEK,eAAe;QAAEC,UAAU;QAAEC;MAAS,CAAC,CAAC;IACvD,CAAC,GACD,CAACC,EAAU,EAAEC,EAAU,EAAEC,EAAU,KAAK,CAAC,CAAC;IAE9C,MAAMC,OAAO,GAAG,CAAC,YAAY;MAC3B,IAAI;QACF,MAAMR,SAAS,CAACS,aAAa,CAACX,QAAQ,EAAEG,gBAAgB,CAAC;MAC3D,CAAC,SAAS;QACRZ,YAAY,CAACqB,WAAW,CAACZ,QAAQ,CAAC;MACpC;IACF,CAAC,EAAE,CAAC;IAEJ,MAAMa,MAAM,GAAGA,CAAA,KAAM;MACnBX,SAAS,CAACY,cAAc,CAACd,QAAQ,CAAC;IACpC,CAAC;IAED,OAAO;MAAEU,OAAO;MAAEG;IAAO,CAAC;EAC5B,CAAC,CAAC,OAAOE,CAAC,EAAE;IACV,MAAMtB,SAAS,CAACsB,CAAC,EAAElB,QAAQ,CAAC;EAC9B;AACF","ignoreList":[]}
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+
3
+ import { NativeModule } from "../module.js";
4
+ import { wrapError } from "../errors.js";
5
+ import { FileType } from "../types.js";
6
+ function toFileType(raw) {
7
+ const values = Object.values(FileType);
8
+ return values.includes(raw) ? raw : FileType.UNKNOWN;
9
+ }
10
+ function mapFileInfo(raw) {
11
+ return {
12
+ path: raw.path,
13
+ name: raw.name,
14
+ size: raw.size,
15
+ type: toFileType(raw.type),
16
+ lastModified: raw.lastModified
17
+ };
18
+ }
19
+ export async function exists(path) {
20
+ try {
21
+ return await NativeModule.exists(path);
22
+ } catch (e) {
23
+ throw wrapError(e, path);
24
+ }
25
+ }
26
+ export async function stat(path) {
27
+ try {
28
+ const raw = await NativeModule.stat(path);
29
+ return mapFileInfo(raw);
30
+ } catch (e) {
31
+ throw wrapError(e, path);
32
+ }
33
+ }
34
+ export async function unlink(path) {
35
+ try {
36
+ await NativeModule.unlink(path);
37
+ } catch (e) {
38
+ throw wrapError(e, path);
39
+ }
40
+ }
41
+ export async function mkdir(path) {
42
+ try {
43
+ await NativeModule.mkdir(path);
44
+ } catch (e) {
45
+ throw wrapError(e, path);
46
+ }
47
+ }
48
+ export async function ls(path) {
49
+ try {
50
+ const rawList = await NativeModule.ls(path);
51
+ return rawList.map(mapFileInfo);
52
+ } catch (e) {
53
+ throw wrapError(e, path);
54
+ }
55
+ }
56
+ export async function cp(srcPath, destPath) {
57
+ try {
58
+ await NativeModule.cp(srcPath, destPath);
59
+ } catch (e) {
60
+ throw wrapError(e, srcPath);
61
+ }
62
+ }
63
+ export async function mv(srcPath, destPath) {
64
+ try {
65
+ await NativeModule.mv(srcPath, destPath);
66
+ } catch (e) {
67
+ throw wrapError(e, srcPath);
68
+ }
69
+ }
70
+ //# sourceMappingURL=fileOps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NativeModule","wrapError","FileType","toFileType","raw","values","Object","includes","UNKNOWN","mapFileInfo","path","name","size","type","lastModified","exists","e","stat","unlink","mkdir","ls","rawList","map","cp","srcPath","destPath","mv"],"sourceRoot":"../../../src","sources":["api/fileOps.ts"],"mappings":";;AAAA,SAASA,YAAY,QAAQ,cAAW;AACxC,SAASC,SAAS,QAAQ,cAAW;AAErC,SAASC,QAAQ,QAAQ,aAAU;AAEnC,SAASC,UAAUA,CAACC,GAAW,EAAY;EACzC,MAAMC,MAAgB,GAAGC,MAAM,CAACD,MAAM,CAACH,QAAQ,CAAC;EAChD,OAAOG,MAAM,CAACE,QAAQ,CAACH,GAAG,CAAC,GAAIA,GAAG,GAAgBF,QAAQ,CAACM,OAAO;AACpE;AAEA,SAASC,WAAWA,CAACL,GAMpB,EAAY;EACX,OAAO;IACLM,IAAI,EAAEN,GAAG,CAACM,IAAI;IACdC,IAAI,EAAEP,GAAG,CAACO,IAAI;IACdC,IAAI,EAAER,GAAG,CAACQ,IAAI;IACdC,IAAI,EAAEV,UAAU,CAACC,GAAG,CAACS,IAAI,CAAC;IAC1BC,YAAY,EAAEV,GAAG,CAACU;EACpB,CAAC;AACH;AAEA,OAAO,eAAeC,MAAMA,CAACL,IAAY,EAAoB;EAC3D,IAAI;IACF,OAAO,MAAMV,YAAY,CAACe,MAAM,CAACL,IAAI,CAAC;EACxC,CAAC,CAAC,OAAOM,CAAC,EAAE;IACV,MAAMf,SAAS,CAACe,CAAC,EAAEN,IAAI,CAAC;EAC1B;AACF;AAEA,OAAO,eAAeO,IAAIA,CAACP,IAAY,EAAqB;EAC1D,IAAI;IACF,MAAMN,GAAG,GAAG,MAAMJ,YAAY,CAACiB,IAAI,CAACP,IAAI,CAAC;IACzC,OAAOD,WAAW,CAACL,GAAG,CAAC;EACzB,CAAC,CAAC,OAAOY,CAAC,EAAE;IACV,MAAMf,SAAS,CAACe,CAAC,EAAEN,IAAI,CAAC;EAC1B;AACF;AAEA,OAAO,eAAeQ,MAAMA,CAACR,IAAY,EAAiB;EACxD,IAAI;IACF,MAAMV,YAAY,CAACkB,MAAM,CAACR,IAAI,CAAC;EACjC,CAAC,CAAC,OAAOM,CAAC,EAAE;IACV,MAAMf,SAAS,CAACe,CAAC,EAAEN,IAAI,CAAC;EAC1B;AACF;AAEA,OAAO,eAAeS,KAAKA,CAACT,IAAY,EAAiB;EACvD,IAAI;IACF,MAAMV,YAAY,CAACmB,KAAK,CAACT,IAAI,CAAC;EAChC,CAAC,CAAC,OAAOM,CAAC,EAAE;IACV,MAAMf,SAAS,CAACe,CAAC,EAAEN,IAAI,CAAC;EAC1B;AACF;AAEA,OAAO,eAAeU,EAAEA,CAACV,IAAY,EAAuB;EAC1D,IAAI;IACF,MAAMW,OAAO,GAAG,MAAMrB,YAAY,CAACoB,EAAE,CAACV,IAAI,CAAC;IAC3C,OAAOW,OAAO,CAACC,GAAG,CAACb,WAAW,CAAC;EACjC,CAAC,CAAC,OAAOO,CAAC,EAAE;IACV,MAAMf,SAAS,CAACe,CAAC,EAAEN,IAAI,CAAC;EAC1B;AACF;AAEA,OAAO,eAAea,EAAEA,CAACC,OAAe,EAAEC,QAAgB,EAAiB;EACzE,IAAI;IACF,MAAMzB,YAAY,CAACuB,EAAE,CAACC,OAAO,EAAEC,QAAQ,CAAC;EAC1C,CAAC,CAAC,OAAOT,CAAC,EAAE;IACV,MAAMf,SAAS,CAACe,CAAC,EAAEQ,OAAO,CAAC;EAC7B;AACF;AAEA,OAAO,eAAeE,EAAEA,CAACF,OAAe,EAAEC,QAAgB,EAAiB;EACzE,IAAI;IACF,MAAMzB,YAAY,CAAC0B,EAAE,CAACF,OAAO,EAAEC,QAAQ,CAAC;EAC1C,CAAC,CAAC,OAAOT,CAAC,EAAE;IACV,MAAMf,SAAS,CAACe,CAAC,EAAEQ,OAAO,CAAC;EAC7B;AACF","ignoreList":[]}
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ import { NativeModule } from "../module.js";
4
+ import { wrapError } from "../errors.js";
5
+ import { HashAlgorithm } from "../types.js";
6
+ export async function hashFile(path, algorithm = HashAlgorithm.SHA256) {
7
+ try {
8
+ return await NativeModule.hashFile(path, algorithm);
9
+ } catch (e) {
10
+ throw wrapError(e, path);
11
+ }
12
+ }
13
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NativeModule","wrapError","HashAlgorithm","hashFile","path","algorithm","SHA256","e"],"sourceRoot":"../../../src","sources":["api/hash.ts"],"mappings":";;AAAA,SAASA,YAAY,QAAQ,cAAW;AACxC,SAASC,SAAS,QAAQ,cAAW;AACrC,SAASC,aAAa,QAAQ,aAAU;AAExC,OAAO,eAAeC,QAAQA,CAC5BC,IAAY,EACZC,SAAwB,GAAGH,aAAa,CAACI,MAAM,EAC9B;EACjB,IAAI;IACF,OAAO,MAAMN,YAAY,CAACG,QAAQ,CAACC,IAAI,EAAEC,SAAS,CAAC;EACrD,CAAC,CAAC,OAAOE,CAAC,EAAE;IACV,MAAMN,SAAS,CAACM,CAAC,EAAEH,IAAI,CAAC;EAC1B;AACF","ignoreList":[]}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ import { NativeModule, getStreamingProxy } from "../module.js";
4
+ import { wrapError, BlobError, ErrorCode } from "../errors.js";
5
+ import { wrapReader } from "../wrappers.js";
6
+ const DEFAULT_BUFFER_SIZE = 65536; // 64KB
7
+
8
+ export function createReader(path, bufferSize = DEFAULT_BUFFER_SIZE) {
9
+ try {
10
+ if (!Number.isFinite(bufferSize) || bufferSize < 4096 || bufferSize > 4194304) {
11
+ throw new BlobError(ErrorCode.INVALID_ARGUMENT, `bufferSize must be between 4096 and 4194304, got ${bufferSize}`, path);
12
+ }
13
+ const handleId = NativeModule.openRead(path, bufferSize);
14
+ if (handleId < 0) {
15
+ throw new BlobError(ErrorCode.IO_ERROR, 'Failed to open file for reading', path);
16
+ }
17
+ const streaming = getStreamingProxy();
18
+ return wrapReader(handleId, streaming);
19
+ } catch (e) {
20
+ throw wrapError(e, path);
21
+ }
22
+ }
23
+ //# sourceMappingURL=readFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NativeModule","getStreamingProxy","wrapError","BlobError","ErrorCode","wrapReader","DEFAULT_BUFFER_SIZE","createReader","path","bufferSize","Number","isFinite","INVALID_ARGUMENT","handleId","openRead","IO_ERROR","streaming","e"],"sourceRoot":"../../../src","sources":["api/readFile.ts"],"mappings":";;AAAA,SAASA,YAAY,EAAEC,iBAAiB,QAAQ,cAAW;AAC3D,SAASC,SAAS,EAAEC,SAAS,EAAEC,SAAS,QAAQ,cAAW;AAC3D,SAASC,UAAU,QAAQ,gBAAa;AAGxC,MAAMC,mBAAmB,GAAG,KAAK,CAAC,CAAC;;AAEnC,OAAO,SAASC,YAAYA,CAC1BC,IAAY,EACZC,UAAkB,GAAGH,mBAAmB,EAC5B;EACZ,IAAI;IACF,IACE,CAACI,MAAM,CAACC,QAAQ,CAACF,UAAU,CAAC,IAC5BA,UAAU,GAAG,IAAI,IACjBA,UAAU,GAAG,OAAO,EACpB;MACA,MAAM,IAAIN,SAAS,CACjBC,SAAS,CAACQ,gBAAgB,EAC1B,oDAAoDH,UAAU,EAAE,EAChED,IACF,CAAC;IACH;IACA,MAAMK,QAAQ,GAAGb,YAAY,CAACc,QAAQ,CAACN,IAAI,EAAEC,UAAU,CAAC;IACxD,IAAII,QAAQ,GAAG,CAAC,EAAE;MAChB,MAAM,IAAIV,SAAS,CACjBC,SAAS,CAACW,QAAQ,EAClB,iCAAiC,EACjCP,IACF,CAAC;IACH;IACA,MAAMQ,SAAS,GAAGf,iBAAiB,CAAC,CAAC;IACrC,OAAOI,UAAU,CAACQ,QAAQ,EAAEG,SAAS,CAAC;EACxC,CAAC,CAAC,OAAOC,CAAC,EAAE;IACV,MAAMf,SAAS,CAACe,CAAC,EAAET,IAAI,CAAC;EAC1B;AACF","ignoreList":[]}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ import { NativeModule, getStreamingProxy } from "../module.js";
4
+ import { wrapError, BlobError, ErrorCode } from "../errors.js";
5
+ import { wrapWriter } from "../wrappers.js";
6
+ export function createWriter(path, append = false) {
7
+ try {
8
+ const handleId = NativeModule.openWrite(path, append);
9
+ if (handleId < 0) {
10
+ throw new BlobError(ErrorCode.IO_ERROR, 'Failed to open file for writing', path);
11
+ }
12
+ const streaming = getStreamingProxy();
13
+ return wrapWriter(handleId, streaming);
14
+ } catch (e) {
15
+ throw wrapError(e, path);
16
+ }
17
+ }
18
+ //# sourceMappingURL=writeFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NativeModule","getStreamingProxy","wrapError","BlobError","ErrorCode","wrapWriter","createWriter","path","append","handleId","openWrite","IO_ERROR","streaming","e"],"sourceRoot":"../../../src","sources":["api/writeFile.ts"],"mappings":";;AAAA,SAASA,YAAY,EAAEC,iBAAiB,QAAQ,cAAW;AAC3D,SAASC,SAAS,EAAEC,SAAS,EAAEC,SAAS,QAAQ,cAAW;AAC3D,SAASC,UAAU,QAAQ,gBAAa;AAGxC,OAAO,SAASC,YAAYA,CAC1BC,IAAY,EACZC,MAAe,GAAG,KAAK,EACX;EACZ,IAAI;IACF,MAAMC,QAAQ,GAAGT,YAAY,CAACU,SAAS,CAACH,IAAI,EAAEC,MAAM,CAAC;IACrD,IAAIC,QAAQ,GAAG,CAAC,EAAE;MAChB,MAAM,IAAIN,SAAS,CACjBC,SAAS,CAACO,QAAQ,EAClB,iCAAiC,EACjCJ,IACF,CAAC;IACH;IACA,MAAMK,SAAS,GAAGX,iBAAiB,CAAC,CAAC;IACrC,OAAOI,UAAU,CAACI,QAAQ,EAAEG,SAAS,CAAC;EACxC,CAAC,CAAC,OAAOC,CAAC,EAAE;IACV,MAAMX,SAAS,CAACW,CAAC,EAAEN,IAAI,CAAC;EAC1B;AACF","ignoreList":[]}