@harryy/react-file-browser 0.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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +247 -0
  3. package/dist/adapters/cloud-adapter-base.d.ts +27 -0
  4. package/dist/adapters/cloud-adapter-base.d.ts.map +1 -0
  5. package/dist/adapters/in-memory/in-memory-file-browser-adapter.d.ts +53 -0
  6. package/dist/adapters/in-memory/in-memory-file-browser-adapter.d.ts.map +1 -0
  7. package/dist/adapters/in-memory/index.d.ts +2 -0
  8. package/dist/adapters/in-memory/index.d.ts.map +1 -0
  9. package/dist/adapters/in-memory/index.js +270 -0
  10. package/dist/adapters/in-memory/index.js.map +1 -0
  11. package/dist/adapters/r2/index.d.ts +7 -0
  12. package/dist/adapters/r2/index.d.ts.map +1 -0
  13. package/dist/adapters/r2/index.js +11 -0
  14. package/dist/adapters/r2/index.js.map +1 -0
  15. package/dist/adapters/s3/index.d.ts +7 -0
  16. package/dist/adapters/s3/index.d.ts.map +1 -0
  17. package/dist/adapters/s3/index.js +11 -0
  18. package/dist/adapters/s3/index.js.map +1 -0
  19. package/dist/adapters/s3-compatible-adapter.d.ts +50 -0
  20. package/dist/adapters/s3-compatible-adapter.d.ts.map +1 -0
  21. package/dist/adapters/supabase/index.d.ts +7 -0
  22. package/dist/adapters/supabase/index.d.ts.map +1 -0
  23. package/dist/adapters/supabase/index.js +216 -0
  24. package/dist/adapters/supabase/index.js.map +1 -0
  25. package/dist/adapters/supabase/supabase-file-browser-adapter.d.ts +88 -0
  26. package/dist/adapters/supabase/supabase-file-browser-adapter.d.ts.map +1 -0
  27. package/dist/chunks/browser-4lIkMGbo.js +436 -0
  28. package/dist/chunks/browser-4lIkMGbo.js.map +1 -0
  29. package/dist/chunks/cloud-adapter-base-CBUoZYLS.js +39 -0
  30. package/dist/chunks/cloud-adapter-base-CBUoZYLS.js.map +1 -0
  31. package/dist/chunks/path-CPm7s9NF.js +52 -0
  32. package/dist/chunks/path-CPm7s9NF.js.map +1 -0
  33. package/dist/chunks/s3-compatible-adapter-D9agsn9e.js +317 -0
  34. package/dist/chunks/s3-compatible-adapter-D9agsn9e.js.map +1 -0
  35. package/dist/components/file-browser.d.ts +28 -0
  36. package/dist/components/file-browser.d.ts.map +1 -0
  37. package/dist/core/path.d.ts +9 -0
  38. package/dist/core/path.d.ts.map +1 -0
  39. package/dist/core/types.d.ts +84 -0
  40. package/dist/core/types.d.ts.map +1 -0
  41. package/dist/core/upload-drop.d.ts +7 -0
  42. package/dist/core/upload-drop.d.ts.map +1 -0
  43. package/dist/core/use-file-browser.d.ts +78 -0
  44. package/dist/core/use-file-browser.d.ts.map +1 -0
  45. package/dist/index.d.ts +9 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +3212 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/theme/index.d.ts +25 -0
  50. package/dist/theme/index.d.ts.map +1 -0
  51. package/dist/theme/index.js +28 -0
  52. package/dist/theme/index.js.map +1 -0
  53. package/dist/transfers/file-browser-provider.d.ts +13 -0
  54. package/dist/transfers/file-browser-provider.d.ts.map +1 -0
  55. package/dist/transfers/transfer-manager.d.ts +118 -0
  56. package/dist/transfers/transfer-manager.d.ts.map +1 -0
  57. package/package.json +137 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hariom Sharma
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,247 @@
1
+ # react-file-browser
2
+
3
+ A standalone, storage-agnostic React file manager component. Ships UI and client logic only, with
4
+ zero server code. Storage access goes through a host-provided adapter, so the same component drives
5
+ S3, Cloudflare R2, Supabase Storage, an in-memory store, or any backend you implement.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/react-file-browser.svg)](https://www.npmjs.com/package/react-file-browser)
8
+ [![npm downloads](https://img.shields.io/npm/dm/react-file-browser.svg)](https://www.npmjs.com/package/react-file-browser)
9
+ [![CI](https://github.com/harryy2510/react-file-browser/actions/workflows/ci.yml/badge.svg)](https://github.com/harryy2510/react-file-browser/actions/workflows/ci.yml)
10
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/react-file-browser)](https://bundlephobia.com/package/react-file-browser)
11
+ [![types](https://img.shields.io/npm/types/react-file-browser.svg)](https://www.npmjs.com/package/react-file-browser)
12
+ [![license](https://img.shields.io/npm/l/react-file-browser.svg)](LICENSE)
13
+
14
+ **[Live demo →](https://harryy2510.github.io/react-file-browser/)**
15
+
16
+ ## Features
17
+
18
+ - **Storage-agnostic** — one component, pluggable adapters. Ships adapters for S3, R2, Supabase, and
19
+ in-memory, or implement the `FileBrowserAdapter` interface for any backend.
20
+ - **Capability-gated UI** — controls appear only when the adapter supports them. Omit `rename` and the
21
+ rename affordance disappears; no config flags to keep in sync.
22
+ - **Resumable uploads** — session-scoped transfer manager with multipart support, a floating transfer
23
+ widget, progress, and a resume prompt after a hard refresh.
24
+ - **Grid and list views**, search, filter, sort, drag-and-drop move, cut/copy/paste, multi-select,
25
+ keyboard shortcuts, file preview, and a details panel.
26
+ - **Bulk download** — uses the adapter's `bulkDownloadUrl` when present, otherwise builds a client-side
27
+ zip from signed URLs.
28
+ - **Upload policies** — reject files by MIME type, size, remaining quota, or a custom validator before
29
+ they enqueue.
30
+ - **Themeable** with CSS variables that inherit your Tailwind v4 / design-system tokens. No global CSS
31
+ shipped.
32
+ - **Read-only mode**, comfortable/compact density, ESM-only, fully typed.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ bun add react-file-browser
38
+ # or: npm install react-file-browser
39
+ ```
40
+
41
+ `react` and `react-dom` (>=18 <20) are peer dependencies. The AWS SDK and Supabase client are
42
+ optional peers, needed only for their respective adapters.
43
+
44
+ ## Quick start
45
+
46
+ ```tsx
47
+ import { FileBrowser, FileBrowserProvider } from "react-file-browser";
48
+ import { InMemoryFileBrowserAdapter } from "react-file-browser/adapters/in-memory";
49
+
50
+ const adapter = new InMemoryFileBrowserAdapter();
51
+
52
+ export function App() {
53
+ return (
54
+ <FileBrowserProvider>
55
+ <FileBrowser adapter={adapter} />
56
+ </FileBrowserProvider>
57
+ );
58
+ }
59
+ ```
60
+
61
+ `FileBrowserProvider` belongs above your router. It owns the session-scoped `TransferManager`, the
62
+ floating transfer widget, and the refresh guard. `FileBrowser` can mount on any page or modal and can
63
+ unmount while transfers keep running.
64
+
65
+ ## Mount points
66
+
67
+ After a hard refresh, browsers cannot restore the original `File` object automatically. Use
68
+ `resolveRestoredUpload` to reattach the host adapter and a user-selected file when the resume prompt
69
+ is accepted:
70
+
71
+ ```tsx
72
+ <FileBrowserProvider
73
+ resolveRestoredUpload={async (upload) => {
74
+ const file = await askUserForFile(upload.name);
75
+ return file ? { adapter, file } : undefined;
76
+ }}
77
+ >
78
+ <App />
79
+ </FileBrowserProvider>
80
+ ```
81
+
82
+ ## Adapters
83
+
84
+ The core contract is the `FileBrowserAdapter` interface. Available built-in adapters:
85
+
86
+ - `react-file-browser/adapters/in-memory`
87
+ - `react-file-browser/adapters/s3`
88
+ - `react-file-browser/adapters/r2`
89
+ - `react-file-browser/adapters/supabase`
90
+
91
+ The in-memory adapter is usable for tests and demos. It can opt into multipart methods with
92
+ `capabilities: { multipart: true }` and `multipartPartSize` so resumable upload flows are testable
93
+ without cloud credentials. Cloud adapter subpaths fail closed when used without options, so importing
94
+ them is safe.
95
+
96
+ ### S3 and R2
97
+
98
+ S3 and R2 use the S3-compatible SDK surface when you provide a client and bucket:
99
+
100
+ ```ts
101
+ import { S3Client } from "@aws-sdk/client-s3";
102
+ import { S3FileBrowserAdapter } from "react-file-browser/adapters/s3";
103
+
104
+ const adapter = new S3FileBrowserAdapter({
105
+ bucket: "assets",
106
+ client: new S3Client({ region: "us-east-1" }),
107
+ prefix: "users/123",
108
+ });
109
+ ```
110
+
111
+ `R2FileBrowserAdapter` accepts the same options. Both require `@aws-sdk/client-s3` and
112
+ `@aws-sdk/s3-request-presigner` to be installed.
113
+
114
+ ### Supabase
115
+
116
+ ```ts
117
+ import { createClient } from "@supabase/supabase-js";
118
+ import { SupabaseFileBrowserAdapter } from "react-file-browser/adapters/supabase";
119
+
120
+ const supabase = createClient(url, anonKey);
121
+ const adapter = new SupabaseFileBrowserAdapter({
122
+ bucket: "assets",
123
+ client: supabase,
124
+ prefix: "users/123",
125
+ });
126
+ ```
127
+
128
+ ### Custom backends
129
+
130
+ Implement the `FileBrowserAdapter` interface directly, or pass a custom `implementation` to any cloud
131
+ adapter to delegate required and optional methods:
132
+
133
+ ```ts
134
+ import { S3FileBrowserAdapter } from "react-file-browser/adapters/s3";
135
+
136
+ const adapter = new S3FileBrowserAdapter({
137
+ implementation: hostBackedAdapter,
138
+ });
139
+ ```
140
+
141
+ Only `list`, `delete`, `signedUrl`, and `upload` are required. Everything else is optional and toggles
142
+ UI capabilities by presence. If an adapter omits `createFolder`, `move`, `rename`, `copy`, multipart,
143
+ or `bulkDownloadUrl`, those controls are hidden. Recursive folder drops are rejected when
144
+ `createFolder` is absent rather than flattened.
145
+
146
+ ## API reference
147
+
148
+ ### `<FileBrowser>` props
149
+
150
+ | Prop | Type | Default | Description |
151
+ | --- | --- | --- | --- |
152
+ | `adapter` | `FileBrowserAdapter` | — (required) | Storage backend. |
153
+ | `initialPath` | `string` | `"/"` | Directory to open on mount. |
154
+ | `density` | `"comfortable" \| "compact"` | `"comfortable"` | Row/tile density. |
155
+ | `readOnly` | `boolean` | `false` | Hides all mutating affordances. |
156
+ | `showDetailsPanel` | `boolean` | `true` | Toggles the right-hand details panel. |
157
+ | `uploadPolicy` | `FileBrowserUploadPolicy` | — | Reject files before they enqueue. |
158
+ | `warnZipSizeBytes` | `number` | — | Warn before building a large client-side zip. |
159
+
160
+ ### `<FileBrowserProvider>` props
161
+
162
+ | Prop | Type | Default | Description |
163
+ | --- | --- | --- | --- |
164
+ | `manager` | `TransferManager` | auto-created | Bring your own transfer manager. |
165
+ | `options` | `TransferManagerOptions` | — | Storage, storage key, id factory, clock. |
166
+ | `resolveRestoredUpload` | `(upload) => ... \| undefined` | — | Reattach a `File` when resuming after reload. |
167
+ | `showFloatingWidget` | `boolean` | `true` | Toggles the floating transfer widget. |
168
+
169
+ ### `FileBrowserAdapter` interface
170
+
171
+ **Required:** `list`, `delete`, `signedUrl`, `upload`.
172
+
173
+ **Optional (each toggles a UI capability):** `createFolder`, `rename`, `move`, `copy`, `stat`,
174
+ `exists`, `createMultipartUpload`, `uploadPart`, `completeMultipartUpload`, `abortMultipartUpload`,
175
+ `bulkDownloadUrl`.
176
+
177
+ Throw `FileBrowserAdapterError` with a code (`access_denied`, `aborted`, `conflict`, `invalid_path`,
178
+ `not_found`, `not_supported`) for correct error mapping, and `FileBrowserBulkActionError` for partial
179
+ bulk failures so successful paths stay applied and failed paths roll back.
180
+
181
+ ### Headless usage
182
+
183
+ `useFileBrowser({ adapter, initialPath })` exposes the full state and actions if you want to build your
184
+ own UI. The `FileBrowser` component is a consumer of this hook.
185
+
186
+ ### Entry points
187
+
188
+ | Import | Contents |
189
+ | --- | --- |
190
+ | `react-file-browser` | `FileBrowser`, `FileBrowserProvider`, `useFileBrowser`, `TransferManager`, adapter types and errors, path utilities. |
191
+ | `react-file-browser/adapters/in-memory` | `InMemoryFileBrowserAdapter`, `createInMemoryFileBrowserAdapter`. |
192
+ | `react-file-browser/adapters/s3` | `S3FileBrowserAdapter`. |
193
+ | `react-file-browser/adapters/r2` | `R2FileBrowserAdapter`. |
194
+ | `react-file-browser/adapters/supabase` | `SupabaseFileBrowserAdapter`. |
195
+ | `react-file-browser/theme` | `FILE_BROWSER_THEME_CONTRACT`, `getFileBrowserDensityAttributes`. |
196
+
197
+ ## Styling
198
+
199
+ The package does not ship global CSS. Components use utility classes and `--fb-*` CSS variables that
200
+ fall back to your Tailwind v4 / design-system tokens:
201
+
202
+ ```css
203
+ --fb-accent: var(--color-primary-500, oklch(.54 .19 285));
204
+ --fb-surface: var(--color-white, #fff);
205
+ --fb-border: var(--color-gray-200, #e5e5ee);
206
+ --fb-radius: var(--radius-lg, 10px);
207
+ --fb-gap: var(--spacing, .25rem);
208
+ ```
209
+
210
+ Add the library source to your Tailwind v4 content/source setup so host builds include the classes.
211
+ The demo app defines a full theme; the package itself does not. `FILE_BROWSER_THEME_CONTRACT`
212
+ documents every token and its default.
213
+
214
+ ## UI behavior
215
+
216
+ - `readOnly` hides upload, create, rename, move, copy, and delete affordances while keeping browsing,
217
+ preview, details, and download available.
218
+ - `uploadPolicy` can reject files before enqueueing transfers by MIME type, extension, max file size,
219
+ remaining quota, or a custom validator. Rejections render as an alert and valid files in the same
220
+ batch still upload.
221
+ - Upload conflicts surface a replace / keep both / skip dialog with apply-to-all support.
222
+ - Partial bulk failures can be reported with `FileBrowserBulkActionError`; successful paths stay
223
+ applied and failed paths are surfaced in a dialog.
224
+ - Single-file download uses `signedUrl`. Folder and multi-select bulk download uses `bulkDownloadUrl`
225
+ when present, otherwise it builds a client zip from signed URLs after the configured size warning.
226
+ - Move-capable adapters enable the destination tree picker, Cut/Paste, and drag onto folders.
227
+ - Keyboard shortcuts include Enter preview/open, F2 rename, Delete confirm, Cmd/Ctrl+A selection, and
228
+ Cmd/Ctrl+C/X/V for adapter-gated copy, cut, and paste.
229
+
230
+ ## Required backend lifecycle
231
+
232
+ The package never creates server routes or lifecycle rules. Hosts must configure:
233
+
234
+ - Abort incomplete multipart uploads after N days.
235
+ - Expire temporary bulk-download zip objects after 4 to 8 hours.
236
+ - Keep zip output outside browsed/listed user prefixes.
237
+
238
+ Skipping these leaks storage cost.
239
+
240
+ ## Contributing
241
+
242
+ Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for local setup, conventions, and
243
+ the release process. Please follow the [Code of Conduct](CODE_OF_CONDUCT.md).
244
+
245
+ ## License
246
+
247
+ [MIT](LICENSE) © Hariom Sharma
@@ -0,0 +1,27 @@
1
+ import type { FileBrowserAdapter, FileBrowserListOptions, FileBrowserListResult, FileBrowserUploadOptions, FileNode } from '../core/types';
2
+ export type CloudAdapterName = 'S3' | 'R2' | 'Supabase';
3
+ export type CloudFileBrowserAdapterOptions = {
4
+ implementation?: FileBrowserAdapter;
5
+ };
6
+ export declare class UnsupportedCloudFileBrowserAdapter implements FileBrowserAdapter {
7
+ private readonly adapterName;
8
+ readonly rename?: NonNullable<FileBrowserAdapter['rename']>;
9
+ readonly createFolder?: NonNullable<FileBrowserAdapter['createFolder']>;
10
+ readonly move?: NonNullable<FileBrowserAdapter['move']>;
11
+ readonly copy?: NonNullable<FileBrowserAdapter['copy']>;
12
+ readonly stat?: NonNullable<FileBrowserAdapter['stat']>;
13
+ readonly exists?: NonNullable<FileBrowserAdapter['exists']>;
14
+ readonly createMultipartUpload?: NonNullable<FileBrowserAdapter['createMultipartUpload']>;
15
+ readonly uploadPart?: NonNullable<FileBrowserAdapter['uploadPart']>;
16
+ readonly completeMultipartUpload?: NonNullable<FileBrowserAdapter['completeMultipartUpload']>;
17
+ readonly abortMultipartUpload?: NonNullable<FileBrowserAdapter['abortMultipartUpload']>;
18
+ readonly bulkDownloadUrl?: NonNullable<FileBrowserAdapter['bulkDownloadUrl']>;
19
+ private readonly implementation?;
20
+ constructor(adapterName: CloudAdapterName, options?: CloudFileBrowserAdapterOptions);
21
+ list(path: string, opts?: FileBrowserListOptions): Promise<FileBrowserListResult>;
22
+ delete(paths: string[]): Promise<void>;
23
+ signedUrl(path: string): Promise<string>;
24
+ upload(path: string, file: File, opts?: FileBrowserUploadOptions): Promise<FileNode>;
25
+ private error;
26
+ }
27
+ //# sourceMappingURL=cloud-adapter-base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud-adapter-base.d.ts","sourceRoot":"","sources":["../../src/adapters/cloud-adapter-base.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,EACxB,QAAQ,EACR,MAAM,eAAe,CAAA;AAEtB,MAAM,MAAM,gBAAgB,GAAG,IAAI,GAAG,IAAI,GAAG,UAAU,CAAA;AAEvD,MAAM,MAAM,8BAA8B,GAAG;IAC5C,cAAc,CAAC,EAAE,kBAAkB,CAAA;CACnC,CAAA;AAED,qBAAa,kCAAmC,YAAW,kBAAkB;IAgB3E,OAAO,CAAC,QAAQ,CAAC,WAAW;IAf7B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC3D,QAAQ,CAAC,YAAY,CAAC,EAAE,WAAW,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAC,CAAA;IACvE,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAA;IACvD,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAA;IACvD,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAA;IACvD,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC3D,QAAQ,CAAC,qBAAqB,CAAC,EAAE,WAAW,CAAC,kBAAkB,CAAC,uBAAuB,CAAC,CAAC,CAAA;IACzF,QAAQ,CAAC,UAAU,CAAC,EAAE,WAAW,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC,CAAA;IACnE,QAAQ,CAAC,uBAAuB,CAAC,EAAE,WAAW,CAAC,kBAAkB,CAAC,yBAAyB,CAAC,CAAC,CAAA;IAC7F,QAAQ,CAAC,oBAAoB,CAAC,EAAE,WAAW,CAAC,kBAAkB,CAAC,sBAAsB,CAAC,CAAC,CAAA;IACvF,QAAQ,CAAC,eAAe,CAAC,EAAE,WAAW,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,CAAC,CAAA;IAE7E,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAoB;gBAGlC,WAAW,EAAE,gBAAgB,EAC9C,OAAO,GAAE,8BAAmC;IAuC7C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IASjF,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAQtC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQxC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAUpF,OAAO,CAAC,KAAK;CAMb"}
@@ -0,0 +1,53 @@
1
+ import type { FileBrowserAdapter, FileBrowserListOptions, FileBrowserListResult, FileBrowserUploadOptions, FileNode } from '../../core/types';
2
+ export type InMemoryFileBrowserCapability = 'createFolder' | 'rename' | 'move' | 'copy' | 'stat' | 'exists' | 'bulkDownloadUrl' | 'multipart';
3
+ export type InMemoryFileBrowserAdapterOptions = {
4
+ capabilities?: Partial<Record<InMemoryFileBrowserCapability, boolean>>;
5
+ multipartPartSize?: number;
6
+ pageSize?: number;
7
+ now?: () => Date;
8
+ };
9
+ export declare class InMemoryFileBrowserAdapter implements FileBrowserAdapter {
10
+ readonly createFolder?: NonNullable<FileBrowserAdapter['createFolder']>;
11
+ readonly rename?: NonNullable<FileBrowserAdapter['rename']>;
12
+ readonly move?: NonNullable<FileBrowserAdapter['move']>;
13
+ readonly copy?: NonNullable<FileBrowserAdapter['copy']>;
14
+ readonly stat?: NonNullable<FileBrowserAdapter['stat']>;
15
+ readonly exists?: NonNullable<FileBrowserAdapter['exists']>;
16
+ readonly bulkDownloadUrl?: NonNullable<FileBrowserAdapter['bulkDownloadUrl']>;
17
+ readonly createMultipartUpload?: NonNullable<FileBrowserAdapter['createMultipartUpload']>;
18
+ readonly uploadPart?: NonNullable<FileBrowserAdapter['uploadPart']>;
19
+ readonly completeMultipartUpload?: NonNullable<FileBrowserAdapter['completeMultipartUpload']>;
20
+ readonly abortMultipartUpload?: NonNullable<FileBrowserAdapter['abortMultipartUpload']>;
21
+ private readonly entries;
22
+ private readonly multipartUploads;
23
+ private readonly multipartPartSize;
24
+ private readonly pageSize?;
25
+ private readonly now;
26
+ private etagSequence;
27
+ private multipartSequence;
28
+ constructor(options?: InMemoryFileBrowserAdapterOptions);
29
+ list(path: string, opts?: FileBrowserListOptions): Promise<FileBrowserListResult>;
30
+ private createFolderEntry;
31
+ delete(paths: string[]): Promise<void>;
32
+ signedUrl(path: string): Promise<string>;
33
+ upload(path: string, file: File, opts?: FileBrowserUploadOptions): Promise<FileNode>;
34
+ private nextAvailablePath;
35
+ private renameEntry;
36
+ private moveEntries;
37
+ private copyEntries;
38
+ private statEntry;
39
+ private existsEntries;
40
+ private createBulkDownloadUrl;
41
+ private createMultipartUploadEntry;
42
+ private uploadMultipartPart;
43
+ private completeMultipartUploadEntry;
44
+ private abortMultipartUploadEntry;
45
+ private getMultipartUpload;
46
+ private collectTree;
47
+ private assertFolderExists;
48
+ private getEntry;
49
+ private timestamp;
50
+ private nextEtag;
51
+ }
52
+ export declare function createInMemoryFileBrowserAdapter(options?: InMemoryFileBrowserAdapterOptions): InMemoryFileBrowserAdapter;
53
+ //# sourceMappingURL=in-memory-file-browser-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory-file-browser-adapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/in-memory/in-memory-file-browser-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { InMemoryFileBrowserAdapter, createInMemoryFileBrowserAdapter, type InMemoryFileBrowserAdapterOptions, type InMemoryFileBrowserCapability } from './in-memory-file-browser-adapter';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/in-memory/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,0BAA0B,EAC1B,gCAAgC,EAChC,KAAK,iCAAiC,EACtC,KAAK,6BAA6B,EAClC,MAAM,kCAAkC,CAAA"}
@@ -0,0 +1,270 @@
1
+ import { a as e, c as t, i as n, n as r, o as i, r as a, s as o } from "../../chunks/path-CPm7s9NF.js";
2
+ import { t as s } from "../../chunks/browser-4lIkMGbo.js";
3
+ //#region src/adapters/in-memory/in-memory-file-browser-adapter.ts
4
+ var c = {
5
+ createFolder: !0,
6
+ rename: !0,
7
+ move: !0,
8
+ copy: !0,
9
+ stat: !0,
10
+ exists: !0,
11
+ bulkDownloadUrl: !0,
12
+ multipart: !1
13
+ }, l = class {
14
+ createFolder;
15
+ rename;
16
+ move;
17
+ copy;
18
+ stat;
19
+ exists;
20
+ bulkDownloadUrl;
21
+ createMultipartUpload;
22
+ uploadPart;
23
+ completeMultipartUpload;
24
+ abortMultipartUpload;
25
+ entries = /* @__PURE__ */ new Map();
26
+ multipartUploads = /* @__PURE__ */ new Map();
27
+ multipartPartSize;
28
+ pageSize;
29
+ now;
30
+ etagSequence = 0;
31
+ multipartSequence = 0;
32
+ constructor(e = {}) {
33
+ let t = {
34
+ ...c,
35
+ ...e.capabilities
36
+ };
37
+ this.pageSize = e.pageSize, this.multipartPartSize = Math.max(1, e.multipartPartSize ?? 5 * 1024 * 1024), this.now = e.now ?? (() => /* @__PURE__ */ new Date()), this.entries.set("/", {
38
+ path: "/",
39
+ name: "",
40
+ kind: "folder",
41
+ modifiedAt: this.timestamp()
42
+ }), t.createFolder && (this.createFolder = this.createFolderEntry.bind(this)), t.rename && (this.rename = this.renameEntry.bind(this)), t.move && (this.move = this.moveEntries.bind(this)), t.copy && (this.copy = this.copyEntries.bind(this)), t.stat && (this.stat = this.statEntry.bind(this)), t.exists && (this.exists = this.existsEntries.bind(this)), t.bulkDownloadUrl && (this.bulkDownloadUrl = this.createBulkDownloadUrl.bind(this)), t.multipart && (this.createMultipartUpload = this.createMultipartUploadEntry.bind(this), this.uploadPart = this.uploadMultipartPart.bind(this), this.completeMultipartUpload = this.completeMultipartUploadEntry.bind(this), this.abortMultipartUpload = this.abortMultipartUploadEntry.bind(this));
43
+ }
44
+ list(e, n = {}) {
45
+ p(n.signal);
46
+ let r = i(e);
47
+ if (this.getEntry(r).kind !== "folder") throw new t("invalid_path", `Cannot list a file path: ${r}`);
48
+ let o = Array.from(this.entries.values()).filter((e) => e.path !== "/").filter((e) => a(e.path) === r).sort(f).map(d);
49
+ if (!this.pageSize) return Promise.resolve({ items: o });
50
+ let s = n.cursor ? Number.parseInt(n.cursor, 10) : 0, c = Number.isFinite(s) && s > 0 ? s : 0, l = o.slice(c, c + this.pageSize), u = c + l.length;
51
+ return Promise.resolve({
52
+ items: l,
53
+ cursor: u < o.length ? String(u) : void 0
54
+ });
55
+ }
56
+ createFolderEntry(e) {
57
+ let n = i(e);
58
+ if (n === "/") return Promise.resolve(d(this.getEntry("/")));
59
+ if (this.entries.has(n)) throw new t("conflict", `A file browser entry already exists at ${n}`);
60
+ this.assertFolderExists(a(n));
61
+ let o = {
62
+ path: n,
63
+ name: r(n),
64
+ kind: "folder",
65
+ modifiedAt: this.timestamp(),
66
+ etag: this.nextEtag()
67
+ };
68
+ return this.entries.set(n, o), Promise.resolve(d(o));
69
+ }
70
+ delete(e) {
71
+ for (let r of e.map(i)) {
72
+ if (r === "/") throw new t("invalid_path", "Cannot delete the root folder");
73
+ this.getEntry(r);
74
+ for (let e of Array.from(this.entries.keys())) n(r, e) && this.entries.delete(e);
75
+ }
76
+ return Promise.resolve();
77
+ }
78
+ signedUrl(e) {
79
+ let n = this.getEntry(e);
80
+ if (n.kind !== "file" || !n.blob) throw new t("invalid_path", `Cannot create a signed URL for a folder: ${n.path}`);
81
+ return Promise.resolve(URL.createObjectURL(n.blob));
82
+ }
83
+ upload(e, n, o = {}) {
84
+ p(o.signal);
85
+ let s = i(e);
86
+ if (this.assertFolderExists(a(s)), this.entries.has(s) && o.onConflict !== "replace") if (o.onConflict === "keep-both") s = this.nextAvailablePath(s);
87
+ else throw new t("conflict", `A file browser entry already exists at ${s}`);
88
+ o.onProgress?.(0, n.size), p(o.signal);
89
+ let c = {
90
+ path: s,
91
+ name: r(s) || n.name,
92
+ kind: "file",
93
+ size: n.size,
94
+ mimeType: n.type || void 0,
95
+ modifiedAt: this.timestamp(),
96
+ etag: this.nextEtag(),
97
+ blob: n
98
+ };
99
+ return this.entries.set(s, c), o.onProgress?.(n.size, n.size), Promise.resolve(d(c));
100
+ }
101
+ nextAvailablePath(n) {
102
+ let i = a(n), o = r(n), s = o.lastIndexOf("."), c = s > 0, l = c ? o.slice(0, s) : o, u = c ? o.slice(s) : "";
103
+ for (let t = 1; t < 2 ** 53 - 1; t += 1) {
104
+ let n = e(i, `${l} (${t})${u}`);
105
+ if (!this.entries.has(n)) return n;
106
+ }
107
+ throw new t("conflict", `Could not allocate a unique file browser path for ${n}`);
108
+ }
109
+ renameEntry(e, n) {
110
+ let r = i(e), a = this.getEntry(r), o = n.trim();
111
+ if (!o || o.includes("/") || o.includes("\\")) throw new t("invalid_path", `Invalid file browser name: ${n}`);
112
+ let s = {
113
+ ...a,
114
+ name: o,
115
+ modifiedAt: this.timestamp(),
116
+ etag: this.nextEtag()
117
+ };
118
+ return this.entries.set(r, s), Promise.resolve(d(s));
119
+ }
120
+ async moveEntries(e, t) {
121
+ await this.copyEntries(e, t);
122
+ for (let t of e) await this.delete([t]);
123
+ }
124
+ copyEntries(n, a) {
125
+ let s = i(a);
126
+ this.assertFolderExists(s);
127
+ let c = /* @__PURE__ */ new Map();
128
+ for (let a of n.map(i)) {
129
+ let n = this.getEntry(a), i = e(s, r(n.path));
130
+ for (let e of this.collectTree(n.path)) {
131
+ let a = o(e.path, n.path, i);
132
+ if (this.entries.has(a) || c.has(a)) throw new t("conflict", `A file browser entry already exists at ${a}`);
133
+ c.set(a, {
134
+ ...e,
135
+ path: a,
136
+ name: e.path === n.path ? r(a) : e.name,
137
+ modifiedAt: this.timestamp(),
138
+ etag: this.nextEtag()
139
+ });
140
+ }
141
+ }
142
+ for (let [e, t] of c) this.entries.set(e, t);
143
+ return Promise.resolve();
144
+ }
145
+ statEntry(e) {
146
+ return Promise.resolve(d(this.getEntry(e)));
147
+ }
148
+ existsEntries(e) {
149
+ return Promise.resolve(Object.fromEntries(e.map((e) => {
150
+ let t = i(e);
151
+ return [t, this.entries.has(t)];
152
+ })));
153
+ }
154
+ async createBulkDownloadUrl(e) {
155
+ let t = {};
156
+ for (let n of e) {
157
+ this.getEntry(n);
158
+ let e = this.collectTree(n);
159
+ for (let n of e) n.kind === "folder" || !n.blob || (t[m(n.path)] = new Uint8Array(await n.blob.arrayBuffer()));
160
+ }
161
+ let n = new Blob([s(t)], { type: "application/zip" });
162
+ return {
163
+ url: URL.createObjectURL(n),
164
+ expiresAt: new Date(this.now().getTime() + 14400 * 1e3).toISOString()
165
+ };
166
+ }
167
+ createMultipartUploadEntry(e, n) {
168
+ let r = i(e);
169
+ if (this.assertFolderExists(a(r)), this.entries.get(r)?.kind === "folder") throw new t("conflict", `A file browser entry already exists at ${r}`);
170
+ this.multipartSequence += 1;
171
+ let o = `memory-multipart-${this.multipartSequence}`;
172
+ return this.multipartUploads.set(o, {
173
+ path: r,
174
+ size: n,
175
+ partSize: this.multipartPartSize,
176
+ parts: /* @__PURE__ */ new Map()
177
+ }), Promise.resolve({
178
+ uploadId: o,
179
+ partSize: this.multipartPartSize
180
+ });
181
+ }
182
+ uploadMultipartPart({ chunk: e, onProgress: t, partNumber: n, signal: r, uploadId: i }) {
183
+ return Promise.resolve().then(() => {
184
+ p(r);
185
+ let a = this.getMultipartUpload(i), o = `memory-part-${n}`;
186
+ return a.parts.set(n, {
187
+ blob: e,
188
+ etag: o
189
+ }), t?.(e.size), p(r), { etag: o };
190
+ });
191
+ }
192
+ completeMultipartUploadEntry({ parts: e, uploadId: n }) {
193
+ return Promise.resolve().then(() => {
194
+ let i = this.getMultipartUpload(n), a = [];
195
+ for (let r of e.sort((e, t) => e.partNumber - t.partNumber)) {
196
+ let e = i.parts.get(r.partNumber);
197
+ if (!e || e.etag !== r.etag) throw new t("not_found", `No uploaded part ${r.partNumber} exists for ${n}`);
198
+ a.push(e.blob);
199
+ }
200
+ let o = new Blob(a);
201
+ if (o.size !== i.size) throw new t("invalid_path", `Multipart upload ${n} size mismatch`);
202
+ let s = {
203
+ path: i.path,
204
+ name: r(i.path),
205
+ kind: "file",
206
+ size: o.size,
207
+ modifiedAt: this.timestamp(),
208
+ etag: this.nextEtag(),
209
+ blob: o
210
+ };
211
+ return this.entries.set(i.path, s), this.multipartUploads.delete(n), d(s);
212
+ });
213
+ }
214
+ abortMultipartUploadEntry(e) {
215
+ return Promise.resolve().then(() => {
216
+ this.getMultipartUpload(e), this.multipartUploads.delete(e);
217
+ });
218
+ }
219
+ getMultipartUpload(e) {
220
+ let n = this.multipartUploads.get(e);
221
+ if (!n) throw new t("not_found", `No multipart upload exists for ${e}`);
222
+ return n;
223
+ }
224
+ collectTree(e) {
225
+ let t = i(e);
226
+ return Array.from(this.entries.values()).filter((e) => n(t, e.path)).sort((e, t) => e.path.localeCompare(t.path));
227
+ }
228
+ assertFolderExists(e) {
229
+ let n = this.getEntry(e);
230
+ if (n.kind !== "folder") throw new t("invalid_path", `Expected a folder at ${n.path}`);
231
+ }
232
+ getEntry(e) {
233
+ let n = i(e), r = this.entries.get(n);
234
+ if (!r) throw new t("not_found", `No file browser entry exists at ${n}`);
235
+ return r;
236
+ }
237
+ timestamp() {
238
+ return this.now().toISOString();
239
+ }
240
+ nextEtag() {
241
+ return this.etagSequence += 1, `memory-${this.etagSequence}`;
242
+ }
243
+ };
244
+ function u(e) {
245
+ return new l(e);
246
+ }
247
+ function d(e) {
248
+ let t = {
249
+ path: e.path,
250
+ name: e.name,
251
+ kind: e.kind
252
+ };
253
+ return e.size !== void 0 && (t.size = e.size), e.mimeType !== void 0 && (t.mimeType = e.mimeType), e.modifiedAt !== void 0 && (t.modifiedAt = e.modifiedAt), e.etag !== void 0 && (t.etag = e.etag), e.thumbnailUrl !== void 0 && (t.thumbnailUrl = e.thumbnailUrl), t;
254
+ }
255
+ function f(e, t) {
256
+ return e.kind === t.kind ? e.name.localeCompare(t.name, void 0, {
257
+ numeric: !0,
258
+ sensitivity: "base"
259
+ }) : e.kind === "folder" ? -1 : 1;
260
+ }
261
+ function p(e) {
262
+ if (e?.aborted) throw new t("aborted", "Operation was aborted");
263
+ }
264
+ function m(e) {
265
+ return e.replace(/^\/+/, "") || "download";
266
+ }
267
+ //#endregion
268
+ export { l as InMemoryFileBrowserAdapter, u as createInMemoryFileBrowserAdapter };
269
+
270
+ //# sourceMappingURL=index.js.map