@vryzel/file-next 0.4.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.
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/errors/index.cjs +183 -0
- package/dist/errors/index.cjs.map +1 -0
- package/dist/errors/index.d.cts +126 -0
- package/dist/errors/index.d.ts +126 -0
- package/dist/errors/index.js +176 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/filesystem-BbZJLy4A.d.cts +830 -0
- package/dist/filesystem-D1Jjx3md.d.ts +830 -0
- package/dist/index.cjs +5745 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +131 -0
- package/dist/index.d.ts +131 -0
- package/dist/index.js +5706 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.cjs +628 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +252 -0
- package/dist/server/index.d.ts +252 -0
- package/dist/server/index.js +624 -0
- package/dist/server/index.js.map +1 -0
- package/dist/sync/index.cjs +441 -0
- package/dist/sync/index.cjs.map +1 -0
- package/dist/sync/index.d.cts +73 -0
- package/dist/sync/index.d.ts +73 -0
- package/dist/sync/index.js +439 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/with-auth-BueIpNZ7.d.cts +14 -0
- package/dist/with-auth-yfezt7YS.d.ts +14 -0
- package/package.json +84 -0
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
import { FileSystemError } from './errors/index.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Discriminated `Result<T, E>` for typed error returns.
|
|
6
|
+
*
|
|
7
|
+
* Why Result over throw at boundaries:
|
|
8
|
+
* - No `any` at the call site (vs try/catch which loses the type).
|
|
9
|
+
* - `switch (r.ok)` narrows the type in consumer code.
|
|
10
|
+
* - The wire shape `{ ok, value | error }` is JSON-serializable,
|
|
11
|
+
* so it survives the RSC boundary without an extra DTO.
|
|
12
|
+
* - Helpers (`map`, `andThen`, `unwrap`) make the common shapes
|
|
13
|
+
* (chain, bail-out, recover) terse and exhaustive.
|
|
14
|
+
*
|
|
15
|
+
* The default error type is `FileSystemError` (see `@/errors`).
|
|
16
|
+
* Consumers can specialize: `Result<User, ZodError>`.
|
|
17
|
+
*
|
|
18
|
+
* All helpers are pure functions; they do NOT allocate closures in
|
|
19
|
+
* hot paths beyond what the spec demands (no `try`/`catch` wrapping).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
type Result<T, E = FileSystemError> = {
|
|
23
|
+
readonly ok: true;
|
|
24
|
+
readonly value: T;
|
|
25
|
+
} | {
|
|
26
|
+
readonly ok: false;
|
|
27
|
+
readonly error: E;
|
|
28
|
+
};
|
|
29
|
+
declare const ok: <T>(value: T) => Result<T, never>;
|
|
30
|
+
declare const err: <E>(error: E) => Result<never, E>;
|
|
31
|
+
declare const map: <T, U, E>(r: Result<T, E>, f: (t: T) => U) => Result<U, E>;
|
|
32
|
+
declare const mapErr: <T, E, F>(r: Result<T, E>, f: (e: E) => F) => Result<T, F>;
|
|
33
|
+
declare const andThen: <T, U, E>(r: Result<T, E>, f: (t: T) => Result<U, E>) => Result<U, E>;
|
|
34
|
+
declare const unwrap: <T, E>(r: Result<T, E>) => T;
|
|
35
|
+
declare const unwrapOr: <T, E>(r: Result<T, E>, fallback: T) => T;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Branded string types and their constructors/guards.
|
|
39
|
+
*
|
|
40
|
+
* Brands are TypeScript-only fictions: a `Path` and an `S3Key` are
|
|
41
|
+
* both strings at runtime, but the type system treats them as
|
|
42
|
+
* distinct nominal types so the compiler catches accidental
|
|
43
|
+
* cross-assignment (e.g. passing an S3Key where a TenantId is
|
|
44
|
+
* expected).
|
|
45
|
+
*
|
|
46
|
+
* Conventions:
|
|
47
|
+
* - `as*` is a zero-cost cast: trust the caller, brand the value.
|
|
48
|
+
* Use it at trust boundaries you have already validated.
|
|
49
|
+
* - `assert*` is a runtime guard: throws TypeError on invalid
|
|
50
|
+
* input. Use it at the edge (parsing user/env input).
|
|
51
|
+
*
|
|
52
|
+
* Validation rules are intentionally minimal in v0.1 and can be
|
|
53
|
+
* tightened as the spec evolves. None of them touch the network or
|
|
54
|
+
* allocate; they are safe in hot paths.
|
|
55
|
+
*/
|
|
56
|
+
type Path = string & {
|
|
57
|
+
readonly __brand: "Path";
|
|
58
|
+
};
|
|
59
|
+
type Prefix = string & {
|
|
60
|
+
readonly __brand: "Prefix";
|
|
61
|
+
};
|
|
62
|
+
type S3Key = string & {
|
|
63
|
+
readonly __brand: "S3Key";
|
|
64
|
+
};
|
|
65
|
+
type TenantId = string & {
|
|
66
|
+
readonly __brand: "TenantId";
|
|
67
|
+
};
|
|
68
|
+
type UserId = string & {
|
|
69
|
+
readonly __brand: "UserId";
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* POSIX file path. Non-empty, '/' is allowed as the root.
|
|
73
|
+
*/
|
|
74
|
+
declare const asPath: (s: string) => Path;
|
|
75
|
+
declare const assertPath: (s: string) => Path;
|
|
76
|
+
/**
|
|
77
|
+
* S3 prefix. Empty string is the root prefix. Non-empty prefixes
|
|
78
|
+
* MUST end with '/' so concatenation with a key never produces
|
|
79
|
+
* 'usersfile.jpg' by accident.
|
|
80
|
+
*/
|
|
81
|
+
declare const asPrefix: (s: string) => Prefix;
|
|
82
|
+
declare const assertPrefix: (s: string) => Prefix;
|
|
83
|
+
/**
|
|
84
|
+
* S3 object key. No leading slash, no empty segments, no '..' hops.
|
|
85
|
+
*/
|
|
86
|
+
declare const asS3Key: (s: string) => S3Key;
|
|
87
|
+
declare const assertS3Key: (s: string) => S3Key;
|
|
88
|
+
/**
|
|
89
|
+
* Tenant identifier. Alphanumerics, dashes, underscores only.
|
|
90
|
+
*/
|
|
91
|
+
declare const asTenantId: (s: string) => TenantId;
|
|
92
|
+
declare const assertTenantId: (s: string) => TenantId;
|
|
93
|
+
/**
|
|
94
|
+
* Opaque user identifier. Non-empty; the format is provider-defined.
|
|
95
|
+
*/
|
|
96
|
+
declare const asUserId: (s: string) => UserId;
|
|
97
|
+
declare const assertUserId: (s: string) => UserId;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* S3CompatibleAdapter — the 17-method interface every storage
|
|
101
|
+
* provider implements in `file-next`.
|
|
102
|
+
*
|
|
103
|
+
* Design reference: `sdd/file-next/design` §E.
|
|
104
|
+
*
|
|
105
|
+
* Why a custom interface instead of using the AWS SDK v3 `S3Client`
|
|
106
|
+
* directly:
|
|
107
|
+
* 1. **Provider portability** — Cloudflare R2, Backblaze B2, MinIO,
|
|
108
|
+
* and any future provider all speak the S3 wire protocol but
|
|
109
|
+
* expose it through different SDKs (or none at all). The
|
|
110
|
+
* interface is the single chokepoint; the rest of the codebase
|
|
111
|
+
* programs against the contract, not the SDK.
|
|
112
|
+
* 2. **Result-typed returns** — every method returns
|
|
113
|
+
* `Promise<Result<T, FileSystemError>>`. No `try`/`catch` at
|
|
114
|
+
* call sites, no `any` smuggling, no swallowed rejections.
|
|
115
|
+
* 3. **Testability** — the entire adapter can be replaced with a
|
|
116
|
+
* 17-method mock in tests; the rest of the library (factory,
|
|
117
|
+
* MetadataStore, server actions) never has to know whether it
|
|
118
|
+
* is running against the real AWS SDK or a stub.
|
|
119
|
+
* 4. **Stable wire shape** — the input/output types are pure data
|
|
120
|
+
* (no `Date` in the serialized form, no class instances), so
|
|
121
|
+
* they survive RSC serialization and the route-handler
|
|
122
|
+
* boundary.
|
|
123
|
+
*
|
|
124
|
+
* Method count is a deliberate design choice: see
|
|
125
|
+
* `sdd/file-next/design/decision/adapter-method-count`. The 13
|
|
126
|
+
* object-CRUD methods were the v0.1 surface; v0.2 adds 4 multipart
|
|
127
|
+
* primitives (`createMultipartUpload`, `uploadPart`,
|
|
128
|
+
* `completeMultipartUpload`, `abortMultipartUpload`) so providers
|
|
129
|
+
* that support chunked uploads expose them through the same
|
|
130
|
+
* Result-typed contract. The 13th method (`getPublicUrl`) is
|
|
131
|
+
* intentionally separate from the presigned-URL family so that
|
|
132
|
+
* providers which can't issue presigned URLs (public buckets,
|
|
133
|
+
* local dev backends) can still satisfy the shape by returning a
|
|
134
|
+
* plain URL.
|
|
135
|
+
*
|
|
136
|
+
* Conventions:
|
|
137
|
+
* - All methods are async (returning `Promise<Result<...>>`).
|
|
138
|
+
* - All methods take a single typed input object (no positional
|
|
139
|
+
* args beyond the first) so future fields are non-breaking.
|
|
140
|
+
* - All key-bearing inputs use the `S3Key` brand; all prefix-
|
|
141
|
+
* bearing inputs use the `Prefix` brand.
|
|
142
|
+
* - Errors are always `FileSystemError` (the 11-code catalog from
|
|
143
|
+
* `@/errors`). Provider-specific codes (S3 `NoSuchKey`, etc.)
|
|
144
|
+
* go on `cause.code`, not on the top-level `code`.
|
|
145
|
+
*/
|
|
146
|
+
|
|
147
|
+
interface ListInput {
|
|
148
|
+
/** S3 prefix to list under. Empty string is the bucket root. */
|
|
149
|
+
prefix?: Prefix;
|
|
150
|
+
/** Opaque continuation token from a prior `ListOutput`. */
|
|
151
|
+
continuationToken?: string;
|
|
152
|
+
/** Soft cap on the number of items returned. Providers may honor or ignore. */
|
|
153
|
+
limit?: number;
|
|
154
|
+
/**
|
|
155
|
+
* Character used to group keys (S3's "folder" emulation). Pass
|
|
156
|
+
* "/" to get back `prefixes` for each sub-folder, with `items`
|
|
157
|
+
* containing only the files at the current level. Omit for a
|
|
158
|
+
* flat recursive listing.
|
|
159
|
+
*/
|
|
160
|
+
delimiter?: string;
|
|
161
|
+
}
|
|
162
|
+
interface ReadInput {
|
|
163
|
+
key: S3Key;
|
|
164
|
+
/** Optional byte range (`bytes=0-1023`). Provider-specific semantics. */
|
|
165
|
+
range?: string;
|
|
166
|
+
}
|
|
167
|
+
interface WriteInput {
|
|
168
|
+
key: S3Key;
|
|
169
|
+
body: Uint8Array | ReadableStream<Uint8Array>;
|
|
170
|
+
contentType?: string;
|
|
171
|
+
/** Free-form user metadata stored alongside the object. */
|
|
172
|
+
metadata?: Record<string, string>;
|
|
173
|
+
}
|
|
174
|
+
interface DeleteInput {
|
|
175
|
+
key: S3Key;
|
|
176
|
+
}
|
|
177
|
+
interface MoveInput {
|
|
178
|
+
sourceKey: S3Key;
|
|
179
|
+
destinationKey: S3Key;
|
|
180
|
+
}
|
|
181
|
+
interface CopyInput {
|
|
182
|
+
sourceKey: S3Key;
|
|
183
|
+
destinationKey: S3Key;
|
|
184
|
+
}
|
|
185
|
+
interface StatInput {
|
|
186
|
+
key: S3Key;
|
|
187
|
+
}
|
|
188
|
+
interface ExistsInput {
|
|
189
|
+
key: S3Key;
|
|
190
|
+
}
|
|
191
|
+
interface GetMetadataInput {
|
|
192
|
+
key: S3Key;
|
|
193
|
+
}
|
|
194
|
+
interface SetMetadataInput {
|
|
195
|
+
key: S3Key;
|
|
196
|
+
metadata: Record<string, string>;
|
|
197
|
+
/**
|
|
198
|
+
* `false` (default) merges with existing user metadata; `true`
|
|
199
|
+
* replaces it entirely. Provider semantics may differ; adapters
|
|
200
|
+
* normalize.
|
|
201
|
+
*/
|
|
202
|
+
replace?: boolean;
|
|
203
|
+
}
|
|
204
|
+
interface PresignedUploadInput {
|
|
205
|
+
key: S3Key;
|
|
206
|
+
contentType?: string;
|
|
207
|
+
/** Expiry in seconds. Providers enforce a max (e.g. 7 days for S3). */
|
|
208
|
+
expiresIn?: number;
|
|
209
|
+
}
|
|
210
|
+
interface PresignedDownloadInput {
|
|
211
|
+
key: S3Key;
|
|
212
|
+
expiresIn?: number;
|
|
213
|
+
}
|
|
214
|
+
interface GetPublicUrlInput {
|
|
215
|
+
key: S3Key;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Multipart upload primitives (v0.2).
|
|
219
|
+
*
|
|
220
|
+
* S3's chunked upload protocol is a four-step dance:
|
|
221
|
+
* 1. `createMultipartUpload` returns an opaque `uploadId`.
|
|
222
|
+
* 2. `uploadPart` (one or more) returns the `etag` per chunk.
|
|
223
|
+
* 3. `completeMultipartUpload` finalises the object by listing
|
|
224
|
+
* the parts (in any order — S3 honours the caller's order).
|
|
225
|
+
* 4. On any failure, `abortMultipartUpload` cleans up the
|
|
226
|
+
* unfinished state on the server (parts are billed until
|
|
227
|
+
* either complete or abort runs).
|
|
228
|
+
*
|
|
229
|
+
* The four primitives are exposed individually so the higher-level
|
|
230
|
+
* `write` method can compose them, but they are also public so
|
|
231
|
+
* server actions or stream-multiplexers can drive the protocol
|
|
232
|
+
* themselves.
|
|
233
|
+
*/
|
|
234
|
+
interface CreateMultipartUploadInput {
|
|
235
|
+
key: S3Key;
|
|
236
|
+
contentType?: string;
|
|
237
|
+
metadata?: Record<string, string>;
|
|
238
|
+
}
|
|
239
|
+
interface UploadPartInput {
|
|
240
|
+
key: S3Key;
|
|
241
|
+
uploadId: string;
|
|
242
|
+
partNumber: number;
|
|
243
|
+
body: Uint8Array;
|
|
244
|
+
}
|
|
245
|
+
interface CompleteMultipartUploadInput {
|
|
246
|
+
key: S3Key;
|
|
247
|
+
uploadId: string;
|
|
248
|
+
/**
|
|
249
|
+
* Parts in the order the caller wants them assembled. S3
|
|
250
|
+
* honours this order (it is not required to be sorted by
|
|
251
|
+
* partNumber).
|
|
252
|
+
*/
|
|
253
|
+
parts: Array<{
|
|
254
|
+
partNumber: number;
|
|
255
|
+
etag: string;
|
|
256
|
+
}>;
|
|
257
|
+
}
|
|
258
|
+
interface AbortMultipartUploadInput {
|
|
259
|
+
key: S3Key;
|
|
260
|
+
uploadId: string;
|
|
261
|
+
}
|
|
262
|
+
interface ListOutput {
|
|
263
|
+
items: Array<{
|
|
264
|
+
key: S3Key;
|
|
265
|
+
size: number;
|
|
266
|
+
lastModified: Date;
|
|
267
|
+
}>;
|
|
268
|
+
/** Sub-prefixes discovered under `prefix` (S3 CommonPrefixes). */
|
|
269
|
+
prefixes: Array<Prefix>;
|
|
270
|
+
/** Opaque token; `undefined` means the listing is complete. */
|
|
271
|
+
nextContinuationToken?: string;
|
|
272
|
+
}
|
|
273
|
+
interface ReadOutput {
|
|
274
|
+
body: Uint8Array;
|
|
275
|
+
contentType?: string;
|
|
276
|
+
metadata?: Record<string, string>;
|
|
277
|
+
}
|
|
278
|
+
interface WriteOutput {
|
|
279
|
+
etag: string;
|
|
280
|
+
versionId?: string;
|
|
281
|
+
}
|
|
282
|
+
interface DeleteOutput {
|
|
283
|
+
[k: string]: unknown;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Side-effect outputs are intentionally empty `{}`. Providers MAY
|
|
287
|
+
* populate additional fields (etag, versionId, existed, ...) in
|
|
288
|
+
* practice, but the interface contract is the minimum guarantee.
|
|
289
|
+
* Callers needing provider-specific metadata should use `stat` or
|
|
290
|
+
* `getMetadata` instead.
|
|
291
|
+
*/
|
|
292
|
+
interface MoveOutput {
|
|
293
|
+
[k: string]: unknown;
|
|
294
|
+
}
|
|
295
|
+
interface CopyOutput {
|
|
296
|
+
[k: string]: unknown;
|
|
297
|
+
}
|
|
298
|
+
interface StatOutput {
|
|
299
|
+
key: S3Key;
|
|
300
|
+
size: number;
|
|
301
|
+
etag: string;
|
|
302
|
+
contentType: string;
|
|
303
|
+
lastModified: Date;
|
|
304
|
+
metadata: Record<string, string>;
|
|
305
|
+
}
|
|
306
|
+
interface ExistsOutput {
|
|
307
|
+
exists: boolean;
|
|
308
|
+
}
|
|
309
|
+
interface GetMetadataOutput {
|
|
310
|
+
[k: string]: unknown;
|
|
311
|
+
}
|
|
312
|
+
interface SetMetadataOutput {
|
|
313
|
+
[k: string]: unknown;
|
|
314
|
+
}
|
|
315
|
+
interface PresignedUploadOutput {
|
|
316
|
+
url: string;
|
|
317
|
+
method: "PUT" | "POST";
|
|
318
|
+
/** Required HTTP headers the client MUST send on the upload. */
|
|
319
|
+
requiredHeaders?: Record<string, string>;
|
|
320
|
+
}
|
|
321
|
+
interface PresignedDownloadOutput {
|
|
322
|
+
url: string;
|
|
323
|
+
}
|
|
324
|
+
interface GetPublicUrlOutput {
|
|
325
|
+
url: string;
|
|
326
|
+
}
|
|
327
|
+
interface CreateMultipartUploadOutput {
|
|
328
|
+
uploadId: string;
|
|
329
|
+
/** Echo of the input key, branded. Lets the caller chain without re-casting. */
|
|
330
|
+
key: S3Key;
|
|
331
|
+
}
|
|
332
|
+
interface UploadPartOutput {
|
|
333
|
+
etag: string;
|
|
334
|
+
/** Echo of the input part number for the caller's bookkeeping. */
|
|
335
|
+
partNumber: number;
|
|
336
|
+
}
|
|
337
|
+
interface CompleteMultipartUploadOutput {
|
|
338
|
+
etag?: string;
|
|
339
|
+
versionId?: string;
|
|
340
|
+
}
|
|
341
|
+
/** Side-effect outputs are intentionally empty (see note on DeleteOutput). */
|
|
342
|
+
interface AbortMultipartUploadOutput {
|
|
343
|
+
[k: string]: unknown;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* The 17-method contract every storage provider implements. The
|
|
347
|
+
* count is enforced by `packages/core/tests/storage/adapter.test.ts`
|
|
348
|
+
* (defensive — catches accidental additions/removals).
|
|
349
|
+
*/
|
|
350
|
+
interface S3CompatibleAdapter {
|
|
351
|
+
list(input: ListInput): Promise<Result<ListOutput, FileSystemError>>;
|
|
352
|
+
read(input: ReadInput): Promise<Result<ReadOutput, FileSystemError>>;
|
|
353
|
+
write(input: WriteInput): Promise<Result<WriteOutput, FileSystemError>>;
|
|
354
|
+
delete(input: DeleteInput): Promise<Result<DeleteOutput, FileSystemError>>;
|
|
355
|
+
move(input: MoveInput): Promise<Result<MoveOutput, FileSystemError>>;
|
|
356
|
+
copy(input: CopyInput): Promise<Result<CopyOutput, FileSystemError>>;
|
|
357
|
+
stat(input: StatInput): Promise<Result<StatOutput, FileSystemError>>;
|
|
358
|
+
exists(input: ExistsInput): Promise<Result<ExistsOutput, FileSystemError>>;
|
|
359
|
+
getMetadata(input: GetMetadataInput): Promise<Result<GetMetadataOutput, FileSystemError>>;
|
|
360
|
+
setMetadata(input: SetMetadataInput): Promise<Result<SetMetadataOutput, FileSystemError>>;
|
|
361
|
+
createPresignedUploadUrl(input: PresignedUploadInput): Promise<Result<PresignedUploadOutput, FileSystemError>>;
|
|
362
|
+
createPresignedDownloadUrl(input: PresignedDownloadInput): Promise<Result<PresignedDownloadOutput, FileSystemError>>;
|
|
363
|
+
getPublicUrl(input: GetPublicUrlInput): Promise<Result<GetPublicUrlOutput, FileSystemError>>;
|
|
364
|
+
createMultipartUpload(input: CreateMultipartUploadInput): Promise<Result<CreateMultipartUploadOutput, FileSystemError>>;
|
|
365
|
+
uploadPart(input: UploadPartInput): Promise<Result<UploadPartOutput, FileSystemError>>;
|
|
366
|
+
completeMultipartUpload(input: CompleteMultipartUploadInput): Promise<Result<CompleteMultipartUploadOutput, FileSystemError>>;
|
|
367
|
+
abortMultipartUpload(input: AbortMultipartUploadInput): Promise<Result<AbortMultipartUploadOutput, FileSystemError>>;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* `FileSystemConfig` — the discriminated union of provider configs
|
|
372
|
+
* and its Zod-driven parser.
|
|
373
|
+
*
|
|
374
|
+
* The Zod schema is the single source of truth for both the
|
|
375
|
+
* runtime shape AND the TypeScript type (`z.infer<...>`). Adding a
|
|
376
|
+
* new field is a one-line change in the schema; the inferred type
|
|
377
|
+
* updates automatically and every consumer gets the new field
|
|
378
|
+
* without a separate type definition to keep in sync.
|
|
379
|
+
*
|
|
380
|
+
* Provider-specific shape notes:
|
|
381
|
+
* - **S3**: `region` is required (AWS regional endpoints). An
|
|
382
|
+
* optional `endpoint` is allowed for S3-compatible providers
|
|
383
|
+
* (MinIO, LocalStack, etc.).
|
|
384
|
+
* - **R2**: `endpoint` is required (Cloudflare R2 has no region
|
|
385
|
+
* model — you talk to your account's endpoint). `forcePathStyle`
|
|
386
|
+
* is ALWAYS `true` (R2 requires path-style addressing; the
|
|
387
|
+
* literal type catches accidental overrides at parse time).
|
|
388
|
+
*
|
|
389
|
+
* `parseFileSystemConfig` is the trust boundary. Callers receive a
|
|
390
|
+
* `Result<FileSystemConfig, FileSystemError>` — a parse failure is
|
|
391
|
+
* an `InternalError` (retryable: false) with the original Zod
|
|
392
|
+
* issues preserved on `cause` for debugging.
|
|
393
|
+
*/
|
|
394
|
+
|
|
395
|
+
declare const CredentialsSchema: z.ZodObject<{
|
|
396
|
+
accessKeyId: z.ZodString;
|
|
397
|
+
secretAccessKey: z.ZodString;
|
|
398
|
+
}, "strip", z.ZodTypeAny, {
|
|
399
|
+
accessKeyId: string;
|
|
400
|
+
secretAccessKey: string;
|
|
401
|
+
}, {
|
|
402
|
+
accessKeyId: string;
|
|
403
|
+
secretAccessKey: string;
|
|
404
|
+
}>;
|
|
405
|
+
type Credentials = z.infer<typeof CredentialsSchema>;
|
|
406
|
+
declare const S3ConfigSchema: z.ZodObject<{
|
|
407
|
+
provider: z.ZodLiteral<"s3">;
|
|
408
|
+
bucket: z.ZodString;
|
|
409
|
+
region: z.ZodString;
|
|
410
|
+
/** Optional S3-compatible endpoint (MinIO, LocalStack, ...). */
|
|
411
|
+
endpoint: z.ZodOptional<z.ZodString>;
|
|
412
|
+
credentials: z.ZodObject<{
|
|
413
|
+
accessKeyId: z.ZodString;
|
|
414
|
+
secretAccessKey: z.ZodString;
|
|
415
|
+
}, "strip", z.ZodTypeAny, {
|
|
416
|
+
accessKeyId: string;
|
|
417
|
+
secretAccessKey: string;
|
|
418
|
+
}, {
|
|
419
|
+
accessKeyId: string;
|
|
420
|
+
secretAccessKey: string;
|
|
421
|
+
}>;
|
|
422
|
+
/** Default false; some S3-compatible providers need path-style. */
|
|
423
|
+
forcePathStyle: z.ZodDefault<z.ZodBoolean>;
|
|
424
|
+
}, "strip", z.ZodTypeAny, {
|
|
425
|
+
provider: "s3";
|
|
426
|
+
bucket: string;
|
|
427
|
+
region: string;
|
|
428
|
+
credentials: {
|
|
429
|
+
accessKeyId: string;
|
|
430
|
+
secretAccessKey: string;
|
|
431
|
+
};
|
|
432
|
+
forcePathStyle: boolean;
|
|
433
|
+
endpoint?: string | undefined;
|
|
434
|
+
}, {
|
|
435
|
+
provider: "s3";
|
|
436
|
+
bucket: string;
|
|
437
|
+
region: string;
|
|
438
|
+
credentials: {
|
|
439
|
+
accessKeyId: string;
|
|
440
|
+
secretAccessKey: string;
|
|
441
|
+
};
|
|
442
|
+
endpoint?: string | undefined;
|
|
443
|
+
forcePathStyle?: boolean | undefined;
|
|
444
|
+
}>;
|
|
445
|
+
declare const R2ConfigSchema: z.ZodObject<{
|
|
446
|
+
provider: z.ZodLiteral<"r2">;
|
|
447
|
+
bucket: z.ZodString;
|
|
448
|
+
endpoint: z.ZodString;
|
|
449
|
+
credentials: z.ZodObject<{
|
|
450
|
+
accessKeyId: z.ZodString;
|
|
451
|
+
secretAccessKey: z.ZodString;
|
|
452
|
+
}, "strip", z.ZodTypeAny, {
|
|
453
|
+
accessKeyId: string;
|
|
454
|
+
secretAccessKey: string;
|
|
455
|
+
}, {
|
|
456
|
+
accessKeyId: string;
|
|
457
|
+
secretAccessKey: string;
|
|
458
|
+
}>;
|
|
459
|
+
/** R2 ONLY supports path-style; the literal-true type makes that explicit. */
|
|
460
|
+
forcePathStyle: z.ZodLiteral<true>;
|
|
461
|
+
}, "strip", z.ZodTypeAny, {
|
|
462
|
+
provider: "r2";
|
|
463
|
+
bucket: string;
|
|
464
|
+
endpoint: string;
|
|
465
|
+
credentials: {
|
|
466
|
+
accessKeyId: string;
|
|
467
|
+
secretAccessKey: string;
|
|
468
|
+
};
|
|
469
|
+
forcePathStyle: true;
|
|
470
|
+
}, {
|
|
471
|
+
provider: "r2";
|
|
472
|
+
bucket: string;
|
|
473
|
+
endpoint: string;
|
|
474
|
+
credentials: {
|
|
475
|
+
accessKeyId: string;
|
|
476
|
+
secretAccessKey: string;
|
|
477
|
+
};
|
|
478
|
+
forcePathStyle: true;
|
|
479
|
+
}>;
|
|
480
|
+
declare const FileSystemConfigSchema: z.ZodDiscriminatedUnion<"provider", [z.ZodObject<{
|
|
481
|
+
provider: z.ZodLiteral<"s3">;
|
|
482
|
+
bucket: z.ZodString;
|
|
483
|
+
region: z.ZodString;
|
|
484
|
+
/** Optional S3-compatible endpoint (MinIO, LocalStack, ...). */
|
|
485
|
+
endpoint: z.ZodOptional<z.ZodString>;
|
|
486
|
+
credentials: z.ZodObject<{
|
|
487
|
+
accessKeyId: z.ZodString;
|
|
488
|
+
secretAccessKey: z.ZodString;
|
|
489
|
+
}, "strip", z.ZodTypeAny, {
|
|
490
|
+
accessKeyId: string;
|
|
491
|
+
secretAccessKey: string;
|
|
492
|
+
}, {
|
|
493
|
+
accessKeyId: string;
|
|
494
|
+
secretAccessKey: string;
|
|
495
|
+
}>;
|
|
496
|
+
/** Default false; some S3-compatible providers need path-style. */
|
|
497
|
+
forcePathStyle: z.ZodDefault<z.ZodBoolean>;
|
|
498
|
+
}, "strip", z.ZodTypeAny, {
|
|
499
|
+
provider: "s3";
|
|
500
|
+
bucket: string;
|
|
501
|
+
region: string;
|
|
502
|
+
credentials: {
|
|
503
|
+
accessKeyId: string;
|
|
504
|
+
secretAccessKey: string;
|
|
505
|
+
};
|
|
506
|
+
forcePathStyle: boolean;
|
|
507
|
+
endpoint?: string | undefined;
|
|
508
|
+
}, {
|
|
509
|
+
provider: "s3";
|
|
510
|
+
bucket: string;
|
|
511
|
+
region: string;
|
|
512
|
+
credentials: {
|
|
513
|
+
accessKeyId: string;
|
|
514
|
+
secretAccessKey: string;
|
|
515
|
+
};
|
|
516
|
+
endpoint?: string | undefined;
|
|
517
|
+
forcePathStyle?: boolean | undefined;
|
|
518
|
+
}>, z.ZodObject<{
|
|
519
|
+
provider: z.ZodLiteral<"r2">;
|
|
520
|
+
bucket: z.ZodString;
|
|
521
|
+
endpoint: z.ZodString;
|
|
522
|
+
credentials: z.ZodObject<{
|
|
523
|
+
accessKeyId: z.ZodString;
|
|
524
|
+
secretAccessKey: z.ZodString;
|
|
525
|
+
}, "strip", z.ZodTypeAny, {
|
|
526
|
+
accessKeyId: string;
|
|
527
|
+
secretAccessKey: string;
|
|
528
|
+
}, {
|
|
529
|
+
accessKeyId: string;
|
|
530
|
+
secretAccessKey: string;
|
|
531
|
+
}>;
|
|
532
|
+
/** R2 ONLY supports path-style; the literal-true type makes that explicit. */
|
|
533
|
+
forcePathStyle: z.ZodLiteral<true>;
|
|
534
|
+
}, "strip", z.ZodTypeAny, {
|
|
535
|
+
provider: "r2";
|
|
536
|
+
bucket: string;
|
|
537
|
+
endpoint: string;
|
|
538
|
+
credentials: {
|
|
539
|
+
accessKeyId: string;
|
|
540
|
+
secretAccessKey: string;
|
|
541
|
+
};
|
|
542
|
+
forcePathStyle: true;
|
|
543
|
+
}, {
|
|
544
|
+
provider: "r2";
|
|
545
|
+
bucket: string;
|
|
546
|
+
endpoint: string;
|
|
547
|
+
credentials: {
|
|
548
|
+
accessKeyId: string;
|
|
549
|
+
secretAccessKey: string;
|
|
550
|
+
};
|
|
551
|
+
forcePathStyle: true;
|
|
552
|
+
}>]>;
|
|
553
|
+
type S3Config = z.infer<typeof S3ConfigSchema>;
|
|
554
|
+
type R2Config = z.infer<typeof R2ConfigSchema>;
|
|
555
|
+
type FileSystemConfig = z.infer<typeof FileSystemConfigSchema>;
|
|
556
|
+
/**
|
|
557
|
+
* Parse an unknown value (typically the env object) into a typed
|
|
558
|
+
* `FileSystemConfig`. Returns a `Result` so the call site can
|
|
559
|
+
* branch on parse failure without try/catch.
|
|
560
|
+
*
|
|
561
|
+
* Mapping strategy:
|
|
562
|
+
* - On success: `ok(config)`.
|
|
563
|
+
* - On Zod failure: `err(FileSystemError(InternalError, retryable: false))`
|
|
564
|
+
* with the original issues preserved on `cause` (as a synthetic
|
|
565
|
+
* `{ code: "ZodError", message, issues }` shape so the
|
|
566
|
+
* FileSystemError contract is honored).
|
|
567
|
+
*/
|
|
568
|
+
declare const parseFileSystemConfig: (input: unknown) => Result<FileSystemConfig, FileSystemError>;
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* `MetadataStore` — the pure-TS interface every concrete store
|
|
572
|
+
* implements (memory, SQLite, Postgres).
|
|
573
|
+
*
|
|
574
|
+
* The store is the secondary metadata index. It mirrors the S3
|
|
575
|
+
* file tree so callers can list/search/sort WITHOUT a ListObjectsV2
|
|
576
|
+
* call (which is slow and expensive at scale). The S3 bucket
|
|
577
|
+
* remains the source of truth for object bytes; the store is
|
|
578
|
+
* the source of truth for tree structure, search indexes, and
|
|
579
|
+
* tenant scoping.
|
|
580
|
+
*
|
|
581
|
+
* Design references:
|
|
582
|
+
* - `sdd/file-next/design` §F (the 9-method contract)
|
|
583
|
+
* - `sdd/file-next/architecture/byodb-metadata-store` (hexagonal)
|
|
584
|
+
* - `sdd/file-next/decisions/error-codes-deviation` (C' catalog)
|
|
585
|
+
*
|
|
586
|
+
* Tenant isolation: every method takes an explicit \`tenantId\`
|
|
587
|
+
* parameter. The Postgres adapter enforces isolation at the DB
|
|
588
|
+
* level via \`SET LOCAL app.current_tenant\` + RLS; the SQLite
|
|
589
|
+
* and memory adapters filter by tenantId in app code (no
|
|
590
|
+
* structural isolation, but a single-tenant test DB can be used
|
|
591
|
+
* safely).
|
|
592
|
+
*/
|
|
593
|
+
|
|
594
|
+
/** The kind of node in the file tree. */
|
|
595
|
+
type NodeKind = "file" | "folder";
|
|
596
|
+
/**
|
|
597
|
+
* A single node in the metadata tree. The S3 object bytes are NOT
|
|
598
|
+
* here (they live in the bucket); this is the tree + application
|
|
599
|
+
* metadata that the consumer needs to render a file browser.
|
|
600
|
+
*/
|
|
601
|
+
interface FileNode {
|
|
602
|
+
/** Globally unique node id (UUID v4 in production). */
|
|
603
|
+
readonly id: string;
|
|
604
|
+
/** Owning tenant. */
|
|
605
|
+
readonly tenantId: TenantId;
|
|
606
|
+
/** Parent node id; null for the root. */
|
|
607
|
+
readonly parentId: string | null;
|
|
608
|
+
/** Display name (the last path segment). */
|
|
609
|
+
readonly name: string;
|
|
610
|
+
/** Full POSIX path from the root (materialized for fast lookups). */
|
|
611
|
+
readonly path: string;
|
|
612
|
+
/** File or folder. */
|
|
613
|
+
readonly kind: NodeKind;
|
|
614
|
+
/** Object size in bytes (0 for folders). */
|
|
615
|
+
readonly size: number;
|
|
616
|
+
/** MIME type (empty for folders). */
|
|
617
|
+
readonly mimeType: string;
|
|
618
|
+
/** The S3 key for the object (empty for folders). */
|
|
619
|
+
readonly s3Key: string;
|
|
620
|
+
/** Owning user (the creator; ownership is sticky unless explicitly transferred). */
|
|
621
|
+
readonly ownerId: UserId;
|
|
622
|
+
/** Application-defined user metadata. */
|
|
623
|
+
readonly metadata: Readonly<Record<string, string>>;
|
|
624
|
+
/** ISO timestamp. */
|
|
625
|
+
readonly createdAt: Date;
|
|
626
|
+
/** ISO timestamp. */
|
|
627
|
+
readonly updatedAt: Date;
|
|
628
|
+
/** Soft-delete tombstone. null = live. */
|
|
629
|
+
readonly deletedAt: Date | null;
|
|
630
|
+
}
|
|
631
|
+
interface CreateNodeInput {
|
|
632
|
+
readonly tenantId: TenantId;
|
|
633
|
+
readonly parentId: string | null;
|
|
634
|
+
readonly name: string;
|
|
635
|
+
readonly kind: NodeKind;
|
|
636
|
+
readonly size: number;
|
|
637
|
+
readonly mimeType: string;
|
|
638
|
+
readonly s3Key: string;
|
|
639
|
+
readonly ownerId: UserId;
|
|
640
|
+
readonly metadata?: Readonly<Record<string, string>>;
|
|
641
|
+
/** Optional stable id (UUID). Defaults to a generated UUID. */
|
|
642
|
+
readonly id?: string;
|
|
643
|
+
}
|
|
644
|
+
interface GetNodeInput {
|
|
645
|
+
readonly tenantId: TenantId;
|
|
646
|
+
readonly id: string;
|
|
647
|
+
}
|
|
648
|
+
interface ListChildrenInput {
|
|
649
|
+
readonly tenantId: TenantId;
|
|
650
|
+
readonly parentId: string | null;
|
|
651
|
+
/** Optional cursor for pagination; opaque to consumers. */
|
|
652
|
+
readonly cursor?: string;
|
|
653
|
+
/** Soft cap; the adapter may honor or ignore. */
|
|
654
|
+
readonly limit?: number;
|
|
655
|
+
}
|
|
656
|
+
interface MoveNodeInput {
|
|
657
|
+
readonly tenantId: TenantId;
|
|
658
|
+
readonly id: string;
|
|
659
|
+
/** New parent (null = root). */
|
|
660
|
+
readonly newParentId: string | null;
|
|
661
|
+
/** New display name (rename). If omitted, the name is preserved. */
|
|
662
|
+
readonly newName?: string;
|
|
663
|
+
}
|
|
664
|
+
interface DeleteNodeInput {
|
|
665
|
+
readonly tenantId: TenantId;
|
|
666
|
+
readonly id: string;
|
|
667
|
+
/**
|
|
668
|
+
* If true, recursively delete the subtree. If false (default),
|
|
669
|
+
* a non-empty folder returns Conflict.
|
|
670
|
+
*/
|
|
671
|
+
readonly recursive?: boolean;
|
|
672
|
+
}
|
|
673
|
+
interface UpdateMetadataInput {
|
|
674
|
+
readonly tenantId: TenantId;
|
|
675
|
+
readonly id: string;
|
|
676
|
+
/** User metadata to merge. */
|
|
677
|
+
readonly metadata: Readonly<Record<string, string>>;
|
|
678
|
+
/** true = replace the entire metadata map; false (default) = merge. */
|
|
679
|
+
readonly replace?: boolean;
|
|
680
|
+
}
|
|
681
|
+
interface SearchInput {
|
|
682
|
+
readonly tenantId: TenantId;
|
|
683
|
+
/** Case-insensitive substring matched against the node name. */
|
|
684
|
+
readonly query: string;
|
|
685
|
+
/** Optional scope: search only within a folder subtree. */
|
|
686
|
+
readonly parentId?: string;
|
|
687
|
+
readonly limit?: number;
|
|
688
|
+
}
|
|
689
|
+
interface GetPathInput {
|
|
690
|
+
readonly tenantId: TenantId;
|
|
691
|
+
readonly id: string;
|
|
692
|
+
}
|
|
693
|
+
interface ListChildrenOutput {
|
|
694
|
+
readonly items: ReadonlyArray<FileNode>;
|
|
695
|
+
/** Opaque cursor; undefined = listing is complete. */
|
|
696
|
+
readonly nextCursor?: string;
|
|
697
|
+
}
|
|
698
|
+
interface GetPathOutput {
|
|
699
|
+
/** The path from root to the target node, root-first. */
|
|
700
|
+
readonly segments: ReadonlyArray<FileNode>;
|
|
701
|
+
}
|
|
702
|
+
interface ReconcileResult {
|
|
703
|
+
/** Nodes in the store that had no matching S3 object. */
|
|
704
|
+
readonly orphansInStore: ReadonlyArray<string>;
|
|
705
|
+
/** S3 keys that had no matching store row. */
|
|
706
|
+
readonly orphansInS3: ReadonlyArray<string>;
|
|
707
|
+
/** Total nodes considered. */
|
|
708
|
+
readonly scanned: number;
|
|
709
|
+
}
|
|
710
|
+
interface MetadataStore {
|
|
711
|
+
/** Create a new node. Returns Conflict on a duplicate (parentId, name). */
|
|
712
|
+
createNode(input: CreateNodeInput): Promise<Result<FileNode, FileSystemError>>;
|
|
713
|
+
/** Get a node by id. Returns ok(null) when not found (NOT NotFound). */
|
|
714
|
+
getNode(input: GetNodeInput): Promise<Result<FileNode | null, FileSystemError>>;
|
|
715
|
+
/** List children of a folder, ordered by name. */
|
|
716
|
+
listChildren(input: ListChildrenInput): Promise<Result<ListChildrenOutput, FileSystemError>>;
|
|
717
|
+
/**
|
|
718
|
+
* Move a node to a new parent and/or rename it. Updates the
|
|
719
|
+
* materialized path for the node AND its descendants (cascades).
|
|
720
|
+
*/
|
|
721
|
+
moveNode(input: MoveNodeInput): Promise<Result<FileNode, FileSystemError>>;
|
|
722
|
+
/**
|
|
723
|
+
* Soft-delete a node (sets deletedAt). Recursive deletion
|
|
724
|
+
* tombstones the entire subtree in one call.
|
|
725
|
+
*/
|
|
726
|
+
deleteNode(input: DeleteNodeInput): Promise<Result<void, FileSystemError>>;
|
|
727
|
+
/** Update user metadata (merge or replace). */
|
|
728
|
+
updateMetadata(input: UpdateMetadataInput): Promise<Result<FileNode, FileSystemError>>;
|
|
729
|
+
/** Case-insensitive name search within an optional folder scope. */
|
|
730
|
+
search(input: SearchInput): Promise<Result<ListChildrenOutput, FileSystemError>>;
|
|
731
|
+
/**
|
|
732
|
+
* Walk the parent chain and return the path from root to the
|
|
733
|
+
* target node. The result includes the target itself as the
|
|
734
|
+
* last segment.
|
|
735
|
+
*/
|
|
736
|
+
getPath(input: GetPathInput): Promise<Result<GetPathOutput, FileSystemError>>;
|
|
737
|
+
/**
|
|
738
|
+
* Reconcile the store against an external source of truth
|
|
739
|
+
* (S3 bucket walk). Returns the drift set. The memory adapter
|
|
740
|
+
* is a no-op (no external source); SQLite/Postgres walk the
|
|
741
|
+
* bucket via the adapter and compare.
|
|
742
|
+
*/
|
|
743
|
+
reconcile(): Promise<Result<ReconcileResult, FileSystemError>>;
|
|
744
|
+
enqueueOrphan(input: {
|
|
745
|
+
tenantId: TenantId;
|
|
746
|
+
s3Key: S3Key;
|
|
747
|
+
metadataId?: string;
|
|
748
|
+
reason: string;
|
|
749
|
+
}): Promise<Result<{
|
|
750
|
+
id: string;
|
|
751
|
+
}, FileSystemError>>;
|
|
752
|
+
listPendingOrphans(input: {
|
|
753
|
+
tenantId: TenantId;
|
|
754
|
+
}): Promise<Result<Array<{
|
|
755
|
+
id: string;
|
|
756
|
+
tenantId: TenantId;
|
|
757
|
+
s3Key: S3Key;
|
|
758
|
+
metadataId: string | null;
|
|
759
|
+
reason: string;
|
|
760
|
+
createdAt: Date;
|
|
761
|
+
}>, FileSystemError>>;
|
|
762
|
+
deleteOrphan(input: {
|
|
763
|
+
tenantId: TenantId;
|
|
764
|
+
id: string;
|
|
765
|
+
}): Promise<Result<void, FileSystemError>>;
|
|
766
|
+
restoreNode(input: {
|
|
767
|
+
tenantId: TenantId;
|
|
768
|
+
id: string;
|
|
769
|
+
}): Promise<Result<FileNode, FileSystemError>>;
|
|
770
|
+
listTrash(input: {
|
|
771
|
+
tenantId: TenantId;
|
|
772
|
+
cursor?: string;
|
|
773
|
+
limit?: number;
|
|
774
|
+
}): Promise<Result<ListChildrenOutput, FileSystemError>>;
|
|
775
|
+
scanFileKeys(input: {
|
|
776
|
+
tenantId: TenantId;
|
|
777
|
+
}): Promise<Result<ReadonlyArray<{
|
|
778
|
+
id: string;
|
|
779
|
+
s3Key: string;
|
|
780
|
+
}>, FileSystemError>>;
|
|
781
|
+
sumSize(input: {
|
|
782
|
+
tenantId: TenantId;
|
|
783
|
+
}): Promise<Result<number, FileSystemError>>;
|
|
784
|
+
findByS3Key(input: {
|
|
785
|
+
tenantId: TenantId;
|
|
786
|
+
s3Key: string;
|
|
787
|
+
}): Promise<Result<FileNode | null, FileSystemError>>;
|
|
788
|
+
createShare(input: {
|
|
789
|
+
tenantId: TenantId;
|
|
790
|
+
nodeId: string;
|
|
791
|
+
expiresAt?: Date;
|
|
792
|
+
}): Promise<Result<{
|
|
793
|
+
token: string;
|
|
794
|
+
}, FileSystemError>>;
|
|
795
|
+
resolveShare(input: {
|
|
796
|
+
token: string;
|
|
797
|
+
}): Promise<Result<FileNode | null, FileSystemError>>;
|
|
798
|
+
revokeShare(input: {
|
|
799
|
+
tenantId: TenantId;
|
|
800
|
+
token: string;
|
|
801
|
+
}): Promise<Result<void, FileSystemError>>;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* The `FileSystem` container — adapter + config + optional metadata
|
|
806
|
+
* index + a `forTenant` chain that returns a namespaced view.
|
|
807
|
+
*/
|
|
808
|
+
|
|
809
|
+
interface FileSystem {
|
|
810
|
+
readonly adapter: S3CompatibleAdapter;
|
|
811
|
+
readonly config: FileSystemConfig;
|
|
812
|
+
readonly metadata: MetadataStore | undefined;
|
|
813
|
+
/** Set when this instance was produced by `forTenant`. */
|
|
814
|
+
readonly tenantId?: string;
|
|
815
|
+
/** Optional per-tenant byte cap enforced by write-through. */
|
|
816
|
+
readonly quotaBytes?: number;
|
|
817
|
+
forTenant(tenantId: string): FileSystem;
|
|
818
|
+
}
|
|
819
|
+
interface CreateFileSystemOptions {
|
|
820
|
+
readonly store?: MetadataStore;
|
|
821
|
+
readonly quotaBytes?: number;
|
|
822
|
+
/**
|
|
823
|
+
* When true (default), `forTenant(id)` prefixes object keys with
|
|
824
|
+
* `t/{id}/`. Set false for a single-tenant bucket or an R2 token
|
|
825
|
+
* scoped to the bucket root.
|
|
826
|
+
*/
|
|
827
|
+
readonly prefixTenantKeys?: boolean;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
export { andThen as $, type PresignedUploadOutput as A, type R2Config as B, type CreateFileSystemOptions as C, type DeleteInput as D, type ExistsInput as E, type FileNode as F, type GetMetadataInput as G, type ReadInput as H, type ReadOutput as I, type ReconcileResult as J, type S3Config as K, type ListChildrenInput as L, type MetadataStore as M, type NodeKind as N, type SearchInput as O, type Path as P, type SetMetadataInput as Q, type Result as R, type S3CompatibleAdapter as S, type TenantId as T, type UserId as U, type SetMetadataOutput as V, type StatInput as W, type StatOutput as X, type UpdateMetadataInput as Y, type WriteInput as Z, type WriteOutput as _, type FileSystem as a, asPath as a0, asPrefix as a1, asS3Key as a2, asTenantId as a3, asUserId as a4, assertPath as a5, assertPrefix as a6, assertS3Key as a7, assertTenantId as a8, assertUserId as a9, err as aa, map as ab, mapErr as ac, ok as ad, parseFileSystemConfig as ae, unwrap as af, unwrapOr as ag, type FileSystemConfig as b, type S3Key as c, type CopyInput as d, type CopyOutput as e, type CreateNodeInput as f, type Credentials as g, type DeleteNodeInput as h, type DeleteOutput as i, type ExistsOutput as j, type GetMetadataOutput as k, type GetNodeInput as l, type GetPathInput as m, type GetPathOutput as n, type GetPublicUrlInput as o, type GetPublicUrlOutput as p, type ListChildrenOutput as q, type ListInput as r, type ListOutput as s, type MoveInput as t, type MoveNodeInput as u, type MoveOutput as v, type Prefix as w, type PresignedDownloadInput as x, type PresignedDownloadOutput as y, type PresignedUploadInput as z };
|