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
package/package.json ADDED
@@ -0,0 +1,114 @@
1
+ {
2
+ "name": "react-native-buffered-blob",
3
+ "version": "1.0.0",
4
+ "description": "No more OOM, just use buffered-blob",
5
+ "main": "./lib/module/index.js",
6
+ "types": "./lib/typescript/src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "source": "./src/index.ts",
10
+ "types": "./lib/typescript/src/index.d.ts",
11
+ "default": "./lib/module/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "src",
17
+ "lib",
18
+ "android",
19
+ "ios",
20
+ "cpp",
21
+ "react-native-buffered-blob.podspec",
22
+ "react-native.config.js",
23
+ "!**/__tests__",
24
+ "!**/__fixtures__",
25
+ "!**/__mocks__",
26
+ "!**/.*"
27
+ ],
28
+ "scripts": {
29
+ "prepare": "bob build",
30
+ "typecheck": "tsc --noEmit",
31
+ "test": "jest",
32
+ "test:watch": "jest --watch",
33
+ "test:coverage": "jest --coverage",
34
+ "release": "release-it"
35
+ },
36
+ "codegenConfig": {
37
+ "name": "BufferedBlobSpec",
38
+ "type": "modules",
39
+ "jsSrcsDir": "src",
40
+ "android": {
41
+ "javaPackageName": "com.bufferedblob"
42
+ }
43
+ },
44
+ "keywords": [
45
+ "react-native",
46
+ "ios",
47
+ "android",
48
+ "blob",
49
+ "file"
50
+ ],
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/l2hyunwoo/react-native-nitro-blob.git",
54
+ "directory": "packages/react-native-buffered-blob"
55
+ },
56
+ "author": "HyunWoo Lee <l2hyunwoo@gmail.com> (https://github.com/l2hyunwoo)",
57
+ "license": "MIT",
58
+ "peerDependencies": {
59
+ "react": "*",
60
+ "react-native": ">=0.76.0"
61
+ },
62
+ "devDependencies": {
63
+ "react": "19.2.0",
64
+ "react-native": "0.83.0",
65
+ "react-native-builder-bob": "^0.40.18",
66
+ "typescript": "^5.9.2"
67
+ },
68
+ "react-native-builder-bob": {
69
+ "source": "src",
70
+ "output": "lib",
71
+ "targets": [
72
+ [
73
+ "module",
74
+ {
75
+ "esm": true
76
+ }
77
+ ],
78
+ [
79
+ "typescript",
80
+ {
81
+ "project": "tsconfig.build.json"
82
+ }
83
+ ]
84
+ ]
85
+ },
86
+ "release-it": {
87
+ "git": {
88
+ "commitMessage": "chore: release react-native-buffered-blob ${version}",
89
+ "tagName": "react-native-buffered-blob@${version}",
90
+ "requireCleanWorkingDir": true
91
+ },
92
+ "npm": {
93
+ "publish": true,
94
+ "publishPath": "."
95
+ },
96
+ "github": {
97
+ "release": true,
98
+ "releaseName": "react-native-buffered-blob@${version}"
99
+ },
100
+ "hooks": {
101
+ "before:init": [
102
+ "yarn prepare"
103
+ ],
104
+ "after:bump": "yarn prepare"
105
+ },
106
+ "plugins": {
107
+ "@release-it/conventional-changelog": {
108
+ "preset": {
109
+ "name": "angular"
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }
@@ -0,0 +1,37 @@
1
+ require "json"
2
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
3
+
4
+ Pod::Spec.new do |s|
5
+ s.name = "react-native-buffered-blob"
6
+ s.version = package["version"]
7
+ s.summary = package["description"]
8
+ s.homepage = package["repository"]["url"]
9
+ s.license = package["license"]
10
+ s.authors = package["author"]
11
+ s.platforms = { :ios => '15.1' }
12
+ s.source = { :git => package["repository"]["url"], :tag => s.version }
13
+ s.source_files = [
14
+ "ios/**/*.{h,m,mm}",
15
+ "cpp/**/*.{h,hpp,cpp}",
16
+ ]
17
+
18
+ # Exclude Android-only files from the iOS build.
19
+ s.exclude_files = [
20
+ "cpp/jni_onload.cpp",
21
+ "cpp/AndroidPlatformBridge.{h,cpp}",
22
+ ]
23
+
24
+ # Keep C++ headers out of the modulemap so the Clang module builder
25
+ # does not try to parse them in C mode (which cannot resolve C++
26
+ # standard library includes like <functional>).
27
+ s.private_header_files = [
28
+ "cpp/**/*.{h,hpp}",
29
+ "ios/BufferedBlobStreamingBridge.h",
30
+ ]
31
+
32
+ s.pod_target_xcconfig = {
33
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
34
+ }
35
+
36
+ install_modules_dependencies(s)
37
+ end
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ dependency: {
3
+ platforms: {
4
+ android: {
5
+ componentDescriptors: [],
6
+ cmakeListsPath: '../cpp/CMakeLists.txt',
7
+ },
8
+ },
9
+ },
10
+ };
package/src/AGENTS.md ADDED
@@ -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,54 @@
1
+ import type { TurboModule } from 'react-native';
2
+ import { TurboModuleRegistry } from 'react-native';
3
+
4
+ export interface Spec extends TurboModule {
5
+ // Called once to install JSI HostObject on global.__BufferedBlobStreaming
6
+ install(): boolean;
7
+
8
+ // --- Stream Handle Factories ---
9
+ // Return numeric handle IDs that reference native objects in an internal registry.
10
+ // The handle IDs are passed to the JSI HostObject for streaming operations.
11
+ openRead(path: string, bufferSize: number): number;
12
+ openWrite(path: string, append: boolean): number;
13
+ createDownload(url: string, destPath: string, headers: Object): number;
14
+
15
+ // --- Handle Cleanup ---
16
+ // Close a handle by ID. Safe to call multiple times.
17
+ closeHandle(handleId: number): void;
18
+
19
+ // --- File System Operations ---
20
+ exists(path: string): Promise<boolean>;
21
+ stat(path: string): Promise<{
22
+ path: string;
23
+ name: string;
24
+ size: number;
25
+ type: string;
26
+ lastModified: number;
27
+ }>;
28
+ unlink(path: string): Promise<void>;
29
+ mkdir(path: string): Promise<void>;
30
+ ls(path: string): Promise<
31
+ Array<{
32
+ path: string;
33
+ name: string;
34
+ size: number;
35
+ type: string;
36
+ lastModified: number;
37
+ }>
38
+ >;
39
+ cp(srcPath: string, destPath: string): Promise<void>;
40
+ mv(srcPath: string, destPath: string): Promise<void>;
41
+
42
+ // --- Hashing ---
43
+ hashFile(path: string, algorithm: string): Promise<string>;
44
+
45
+ // --- Constants ---
46
+ getConstants(): {
47
+ documentDir: string;
48
+ cacheDir: string;
49
+ tempDir: string;
50
+ downloadDir: string;
51
+ };
52
+ }
53
+
54
+ export default TurboModuleRegistry.getEnforcing<Spec>('BufferedBlob');
@@ -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,46 @@
1
+ import { NativeModule, getStreamingProxy } from '../module';
2
+ import { wrapError } from '../errors';
3
+ import type { DownloadProgress } from '../types';
4
+
5
+ export interface DownloadOptions {
6
+ url: string;
7
+ destPath: string;
8
+ headers?: Record<string, string>;
9
+ onProgress?: (progress: DownloadProgress) => void;
10
+ }
11
+
12
+ export interface DownloadHandle {
13
+ promise: Promise<void>;
14
+ cancel: () => void;
15
+ }
16
+
17
+ export function download(options: DownloadOptions): DownloadHandle {
18
+ const { url, destPath, headers = {}, onProgress } = options;
19
+
20
+ try {
21
+ const handleId = NativeModule.createDownload(url, destPath, headers);
22
+ const streaming = getStreamingProxy();
23
+
24
+ const progressCallback = onProgress
25
+ ? (bytesDownloaded: number, totalBytes: number, progress: number) => {
26
+ onProgress({ bytesDownloaded, totalBytes, progress });
27
+ }
28
+ : (_b: number, _t: number, _p: number) => {};
29
+
30
+ const promise = (async () => {
31
+ try {
32
+ await streaming.startDownload(handleId, progressCallback);
33
+ } finally {
34
+ NativeModule.closeHandle(handleId);
35
+ }
36
+ })();
37
+
38
+ const cancel = () => {
39
+ streaming.cancelDownload(handleId);
40
+ };
41
+
42
+ return { promise, cancel };
43
+ } catch (e) {
44
+ throw wrapError(e, destPath);
45
+ }
46
+ }
@@ -0,0 +1,83 @@
1
+ import { NativeModule } from '../module';
2
+ import { wrapError } from '../errors';
3
+ import type { FileInfo } from '../types';
4
+ import { FileType } from '../types';
5
+
6
+ function toFileType(raw: string): FileType {
7
+ const values: string[] = Object.values(FileType);
8
+ return values.includes(raw) ? (raw as FileType) : FileType.UNKNOWN;
9
+ }
10
+
11
+ function mapFileInfo(raw: {
12
+ path: string;
13
+ name: string;
14
+ size: number;
15
+ type: string;
16
+ lastModified: number;
17
+ }): FileInfo {
18
+ return {
19
+ path: raw.path,
20
+ name: raw.name,
21
+ size: raw.size,
22
+ type: toFileType(raw.type),
23
+ lastModified: raw.lastModified,
24
+ };
25
+ }
26
+
27
+ export async function exists(path: string): Promise<boolean> {
28
+ try {
29
+ return await NativeModule.exists(path);
30
+ } catch (e) {
31
+ throw wrapError(e, path);
32
+ }
33
+ }
34
+
35
+ export async function stat(path: string): Promise<FileInfo> {
36
+ try {
37
+ const raw = await NativeModule.stat(path);
38
+ return mapFileInfo(raw);
39
+ } catch (e) {
40
+ throw wrapError(e, path);
41
+ }
42
+ }
43
+
44
+ export async function unlink(path: string): Promise<void> {
45
+ try {
46
+ await NativeModule.unlink(path);
47
+ } catch (e) {
48
+ throw wrapError(e, path);
49
+ }
50
+ }
51
+
52
+ export async function mkdir(path: string): Promise<void> {
53
+ try {
54
+ await NativeModule.mkdir(path);
55
+ } catch (e) {
56
+ throw wrapError(e, path);
57
+ }
58
+ }
59
+
60
+ export async function ls(path: string): Promise<FileInfo[]> {
61
+ try {
62
+ const rawList = await NativeModule.ls(path);
63
+ return rawList.map(mapFileInfo);
64
+ } catch (e) {
65
+ throw wrapError(e, path);
66
+ }
67
+ }
68
+
69
+ export async function cp(srcPath: string, destPath: string): Promise<void> {
70
+ try {
71
+ await NativeModule.cp(srcPath, destPath);
72
+ } catch (e) {
73
+ throw wrapError(e, srcPath);
74
+ }
75
+ }
76
+
77
+ export async function mv(srcPath: string, destPath: string): Promise<void> {
78
+ try {
79
+ await NativeModule.mv(srcPath, destPath);
80
+ } catch (e) {
81
+ throw wrapError(e, srcPath);
82
+ }
83
+ }
@@ -0,0 +1,14 @@
1
+ import { NativeModule } from '../module';
2
+ import { wrapError } from '../errors';
3
+ import { HashAlgorithm } from '../types';
4
+
5
+ export async function hashFile(
6
+ path: string,
7
+ algorithm: HashAlgorithm = HashAlgorithm.SHA256
8
+ ): Promise<string> {
9
+ try {
10
+ return await NativeModule.hashFile(path, algorithm);
11
+ } catch (e) {
12
+ throw wrapError(e, path);
13
+ }
14
+ }
@@ -0,0 +1,37 @@
1
+ import { NativeModule, getStreamingProxy } from '../module';
2
+ import { wrapError, BlobError, ErrorCode } from '../errors';
3
+ import { wrapReader } from '../wrappers';
4
+ import type { BlobReader } from '../types';
5
+
6
+ const DEFAULT_BUFFER_SIZE = 65536; // 64KB
7
+
8
+ export function createReader(
9
+ path: string,
10
+ bufferSize: number = DEFAULT_BUFFER_SIZE
11
+ ): BlobReader {
12
+ try {
13
+ if (
14
+ !Number.isFinite(bufferSize) ||
15
+ bufferSize < 4096 ||
16
+ bufferSize > 4194304
17
+ ) {
18
+ throw new BlobError(
19
+ ErrorCode.INVALID_ARGUMENT,
20
+ `bufferSize must be between 4096 and 4194304, got ${bufferSize}`,
21
+ path
22
+ );
23
+ }
24
+ const handleId = NativeModule.openRead(path, bufferSize);
25
+ if (handleId < 0) {
26
+ throw new BlobError(
27
+ ErrorCode.IO_ERROR,
28
+ 'Failed to open file for reading',
29
+ path
30
+ );
31
+ }
32
+ const streaming = getStreamingProxy();
33
+ return wrapReader(handleId, streaming);
34
+ } catch (e) {
35
+ throw wrapError(e, path);
36
+ }
37
+ }
@@ -0,0 +1,24 @@
1
+ import { NativeModule, getStreamingProxy } from '../module';
2
+ import { wrapError, BlobError, ErrorCode } from '../errors';
3
+ import { wrapWriter } from '../wrappers';
4
+ import type { BlobWriter } from '../types';
5
+
6
+ export function createWriter(
7
+ path: string,
8
+ append: boolean = false
9
+ ): BlobWriter {
10
+ try {
11
+ const handleId = NativeModule.openWrite(path, append);
12
+ if (handleId < 0) {
13
+ throw new BlobError(
14
+ ErrorCode.IO_ERROR,
15
+ 'Failed to open file for writing',
16
+ path
17
+ );
18
+ }
19
+ const streaming = getStreamingProxy();
20
+ return wrapWriter(handleId, streaming);
21
+ } catch (e) {
22
+ throw wrapError(e, path);
23
+ }
24
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,50 @@
1
+ export enum ErrorCode {
2
+ FILE_NOT_FOUND = 'FILE_NOT_FOUND',
3
+ PERMISSION_DENIED = 'PERMISSION_DENIED',
4
+ FILE_ALREADY_EXISTS = 'FILE_ALREADY_EXISTS',
5
+ NOT_A_FILE = 'NOT_A_FILE',
6
+ NOT_A_DIRECTORY = 'NOT_A_DIRECTORY',
7
+ DIRECTORY_NOT_EMPTY = 'DIRECTORY_NOT_EMPTY',
8
+ IO_ERROR = 'IO_ERROR',
9
+ INVALID_ARGUMENT = 'INVALID_ARGUMENT',
10
+ DOWNLOAD_FAILED = 'DOWNLOAD_FAILED',
11
+ DOWNLOAD_CANCELLED = 'DOWNLOAD_CANCELLED',
12
+ READER_CLOSED = 'READER_CLOSED',
13
+ WRITER_CLOSED = 'WRITER_CLOSED',
14
+ UNKNOWN = 'UNKNOWN',
15
+ }
16
+
17
+ export class BlobError extends Error {
18
+ public readonly code: ErrorCode;
19
+ public readonly path?: string;
20
+
21
+ constructor(code: ErrorCode, message: string, path?: string) {
22
+ super(message);
23
+ this.name = 'BlobError';
24
+ this.code = code;
25
+ this.path = path;
26
+ Object.setPrototypeOf(this, BlobError.prototype);
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Parse native error with format "[ERROR_CODE] message" into BlobError.
32
+ */
33
+ export function wrapError(e: unknown, path?: string): BlobError {
34
+ if (e instanceof BlobError) {
35
+ return e;
36
+ }
37
+
38
+ const message = e instanceof Error ? e.message : String(e);
39
+ const match = /^\[([A-Z_]+)\]\s*(.*)$/.exec(message);
40
+
41
+ if (match) {
42
+ const [, code, msg] = match;
43
+ const errorCode = Object.values(ErrorCode).includes(code as ErrorCode)
44
+ ? (code as ErrorCode)
45
+ : ErrorCode.UNKNOWN;
46
+ return new BlobError(errorCode, msg || message, path);
47
+ }
48
+
49
+ return new BlobError(ErrorCode.UNKNOWN, message, path);
50
+ }
package/src/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ // Errors
2
+ export { ErrorCode, BlobError, wrapError } from './errors';
3
+
4
+ // Paths
5
+ export { Dirs, join, dirname, basename, extname } from './paths';
6
+
7
+ // Types
8
+ export type {
9
+ FileInfo,
10
+ DownloadProgress,
11
+ BlobReader,
12
+ BlobWriter,
13
+ } from './types';
14
+ export { HashAlgorithm, FileType } from './types';
15
+
16
+ // API - Streaming
17
+ export { createReader } from './api/readFile';
18
+ export { createWriter } from './api/writeFile';
19
+
20
+ // API - File Operations
21
+ export { exists, stat, unlink, mkdir, ls, cp, mv } from './api/fileOps';
22
+
23
+ // API - Hashing
24
+ export { hashFile } from './api/hash';
25
+
26
+ // API - Download
27
+ export { download } from './api/download';
28
+ export type { DownloadOptions, DownloadHandle } from './api/download';
package/src/module.ts ADDED
@@ -0,0 +1,48 @@
1
+ import NativeModule from './NativeBufferedBlob';
2
+
3
+ // Install JSI HostObject on first import
4
+ const installed = NativeModule.install();
5
+ if (!installed) {
6
+ throw new Error(
7
+ '[BufferedBlob] Failed to install JSI streaming bridge. Ensure the native module is linked.'
8
+ );
9
+ }
10
+
11
+ export interface StreamingProxy {
12
+ readNextChunk(handleId: number): Promise<ArrayBuffer | null>;
13
+ write(handleId: number, data: ArrayBuffer): Promise<number>;
14
+ flush(handleId: number): Promise<void>;
15
+ close(handleId: number): void;
16
+ startDownload(
17
+ handleId: number,
18
+ onProgress: (
19
+ bytesDownloaded: number,
20
+ totalBytes: number,
21
+ progress: number
22
+ ) => void
23
+ ): Promise<void>;
24
+ cancelDownload(handleId: number): void;
25
+ getReaderInfo(handleId: number): {
26
+ fileSize: number;
27
+ bytesRead: number;
28
+ isEOF: boolean;
29
+ };
30
+ getWriterInfo(handleId: number): { bytesWritten: number };
31
+ }
32
+
33
+ declare global {
34
+ var __BufferedBlobStreaming: StreamingProxy | undefined;
35
+ }
36
+
37
+ // Access the JSI HostObject installed by native
38
+ function getStreamingProxy(): StreamingProxy {
39
+ const proxy = globalThis.__BufferedBlobStreaming;
40
+ if (!proxy) {
41
+ throw new Error(
42
+ '[BufferedBlob] Streaming proxy not available. Make sure install() was called.'
43
+ );
44
+ }
45
+ return proxy as StreamingProxy;
46
+ }
47
+
48
+ export { NativeModule, getStreamingProxy };