@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.
@@ -0,0 +1,628 @@
1
+ 'use strict';
2
+
3
+ require('server-only');
4
+ var zod = require('zod');
5
+
6
+ // src/server/entry.ts
7
+
8
+ // src/types/result.ts
9
+ var ok = (value) => ({ ok: true, value });
10
+ var err = (error) => ({ ok: false, error });
11
+
12
+ // src/errors/mappers.ts
13
+ var HTTP_STATUS_TO_CODE = {
14
+ 401: { code: "Unauthorized", retryable: false },
15
+ 403: { code: "Forbidden", retryable: false },
16
+ 404: { code: "NotFound", retryable: false },
17
+ 408: { code: "NetworkError", retryable: true },
18
+ 409: { code: "Conflict", retryable: false },
19
+ 413: { code: "PayloadTooLarge", retryable: false },
20
+ 415: { code: "UnsupportedMediaType", retryable: false },
21
+ 429: { code: "RateLimited", retryable: true }
22
+ };
23
+ var AWS_NAME_TO_CODE = {
24
+ NoSuchKey: { code: "NotFound", retryable: false },
25
+ NoSuchBucket: { code: "NotFound", retryable: false },
26
+ // v0.2 multipart: unknown/aborted/expired uploadId.
27
+ NoSuchUpload: { code: "NotFound", retryable: false },
28
+ AccessDenied: { code: "Forbidden", retryable: false },
29
+ SlowDown: { code: "QuotaExceeded", retryable: true },
30
+ BadDigest: { code: "ChecksumMismatch", retryable: true },
31
+ XAmzContentSHA256Mismatch: { code: "ChecksumMismatch", retryable: true },
32
+ NetworkingError: { code: "NetworkError", retryable: true },
33
+ TimeoutError: { code: "NetworkError", retryable: true }
34
+ };
35
+ var PG_SQLSTATE_TO_CODE = {
36
+ "23505": { code: "Conflict", retryable: false }
37
+ };
38
+ var SQLITE_CODE_TO_CODE = {
39
+ SQLITE_CONSTRAINT_UNIQUE: { code: "Conflict", retryable: false },
40
+ SQLITE_CONSTRAINT_PRIMARYKEY: { code: "Conflict", retryable: false }
41
+ };
42
+ var toCause = (code, message, extra) => ({
43
+ code,
44
+ message,
45
+ ...extra ?? {}
46
+ });
47
+ var fallback = (err2) => new FileSystemError({
48
+ code: "InternalError",
49
+ message: err2 instanceof Error ? err2.message : String(err2),
50
+ retryable: true,
51
+ cause: toCause(
52
+ "Unknown",
53
+ err2 instanceof Error ? err2.message : String(err2)
54
+ )
55
+ });
56
+ var fromAws = (err2) => {
57
+ if (err2 instanceof FileSystemError) return err2;
58
+ if (typeof err2 !== "object" || err2 === null) return fallback(err2);
59
+ const awsErr = err2;
60
+ const name = typeof awsErr.name === "string" ? awsErr.name : void 0;
61
+ const message = typeof awsErr.message === "string" ? awsErr.message : `AWS error: ${name ?? "unknown"}`;
62
+ const status = typeof awsErr.$metadata?.httpStatusCode === "number" ? awsErr.$metadata.httpStatusCode : void 0;
63
+ const cause = toCause(
64
+ name ?? (status !== void 0 ? String(status) : "Unknown"),
65
+ message,
66
+ status !== void 0 ? { httpStatusCode: status } : void 0
67
+ );
68
+ if (name && name in AWS_NAME_TO_CODE) {
69
+ const m = AWS_NAME_TO_CODE[name];
70
+ return new FileSystemError({ code: m.code, message, retryable: m.retryable, cause });
71
+ }
72
+ if (status !== void 0 && status in HTTP_STATUS_TO_CODE) {
73
+ const m = HTTP_STATUS_TO_CODE[status];
74
+ return new FileSystemError({ code: m.code, message, retryable: m.retryable, cause });
75
+ }
76
+ if (status !== void 0 && status >= 500 && status < 600) {
77
+ return new FileSystemError({ code: "InternalError", message, retryable: true, cause });
78
+ }
79
+ if (status !== void 0 && status >= 400 && status < 500) {
80
+ return new FileSystemError({ code: "Forbidden", message, retryable: false, cause });
81
+ }
82
+ return new FileSystemError({ code: "InternalError", message, retryable: true, cause });
83
+ };
84
+ var fromPg = (err2) => {
85
+ if (err2 instanceof FileSystemError) return err2;
86
+ if (typeof err2 !== "object" || err2 === null) return fallback(err2);
87
+ const pgErr = err2;
88
+ const sqlstate = typeof pgErr.code === "string" ? pgErr.code : void 0;
89
+ const message = typeof pgErr.message === "string" ? pgErr.message : `Postgres error: ${sqlstate ?? "unknown"}`;
90
+ const cause = toCause(sqlstate ?? "Unknown", message);
91
+ if (sqlstate && sqlstate in PG_SQLSTATE_TO_CODE) {
92
+ const m = PG_SQLSTATE_TO_CODE[sqlstate];
93
+ return new FileSystemError({ code: m.code, message, retryable: m.retryable, cause });
94
+ }
95
+ return new FileSystemError({ code: "InternalError", message, retryable: true, cause });
96
+ };
97
+ var fromSqlite = (err2) => {
98
+ if (err2 instanceof FileSystemError) return err2;
99
+ if (typeof err2 !== "object" || err2 === null) return fallback(err2);
100
+ const sqlErr = err2;
101
+ const code = typeof sqlErr.code === "string" ? sqlErr.code : void 0;
102
+ const message = typeof sqlErr.message === "string" ? sqlErr.message : `SQLite error: ${code ?? "unknown"}`;
103
+ const cause = toCause(code ?? "Unknown", message);
104
+ if (code && code in SQLITE_CODE_TO_CODE) {
105
+ const m = SQLITE_CODE_TO_CODE[code];
106
+ return new FileSystemError({ code: m.code, message, retryable: m.retryable, cause });
107
+ }
108
+ return new FileSystemError({ code: "InternalError", message, retryable: true, cause });
109
+ };
110
+
111
+ // src/errors/index.ts
112
+ var FileSystemError = class extends Error {
113
+ code;
114
+ retryable;
115
+ cause;
116
+ constructor(opts) {
117
+ super(opts.message);
118
+ this.name = "FileSystemError";
119
+ this.code = opts.code;
120
+ this.retryable = opts.retryable;
121
+ if (opts.cause !== void 0) {
122
+ this.cause = opts.cause;
123
+ }
124
+ }
125
+ /**
126
+ * Stable JSON shape for RSC pass-through. Error.prototype.message
127
+ * is non-enumerable, so plain `JSON.stringify(err)` would emit `{}`
128
+ * and lose the message + code + retryable flag. `cause` is omitted
129
+ * entirely (not set to undefined) when absent so the shape stays
130
+ * stable for hash-based caches.
131
+ */
132
+ toJSON() {
133
+ return {
134
+ name: this.name,
135
+ code: this.code,
136
+ message: this.message,
137
+ retryable: this.retryable,
138
+ ...this.cause !== void 0 ? { cause: this.cause } : {}
139
+ };
140
+ }
141
+ /**
142
+ * Mappers from upstream error shapes (AWS SDK v3, node-postgres,
143
+ * better-sqlite3) to FileSystemError. See `./mappers.ts` for the
144
+ * mapping tables. Re-exposed as static methods so callers can pick
145
+ * the import style that fits; the free functions are the canonical
146
+ * implementation.
147
+ */
148
+ static fromAws(err2) {
149
+ return fromAws(err2);
150
+ }
151
+ static fromPg(err2) {
152
+ return fromPg(err2);
153
+ }
154
+ static fromSqlite(err2) {
155
+ return fromSqlite(err2);
156
+ }
157
+ };
158
+
159
+ // src/types/branded.ts
160
+ var asS3Key = (s) => s;
161
+ var asTenantId = (s) => s;
162
+
163
+ // src/server/actions.ts
164
+ var NodeIdSchema = zod.z.string().min(1, "id is required");
165
+ var PathSchema = zod.z.string().nullable();
166
+ var ListFilesInputSchema = zod.z.object({
167
+ parentId: PathSchema,
168
+ limit: zod.z.number().int().positive().optional(),
169
+ cursor: zod.z.string().optional()
170
+ });
171
+ var DeleteFileInputSchema = zod.z.object({
172
+ id: NodeIdSchema,
173
+ recursive: zod.z.boolean().optional()
174
+ });
175
+ var MoveFileInputSchema = zod.z.object({
176
+ id: NodeIdSchema,
177
+ newParentId: PathSchema,
178
+ newName: zod.z.string().min(1).max(255).optional()
179
+ });
180
+ var CopyFileInputSchema = zod.z.object({
181
+ id: NodeIdSchema,
182
+ newParentId: PathSchema,
183
+ newName: zod.z.string().min(1).max(255).optional()
184
+ });
185
+ var SetMetadataInputSchema = zod.z.object({
186
+ id: NodeIdSchema,
187
+ metadata: zod.z.record(zod.z.string()),
188
+ replace: zod.z.boolean().optional()
189
+ });
190
+ var CreateFolderInputSchema = zod.z.object({
191
+ parentId: PathSchema,
192
+ name: zod.z.string().min(1).max(255)
193
+ });
194
+ var PrepareUploadInputSchema = zod.z.object({
195
+ parentId: PathSchema,
196
+ name: zod.z.string().min(1).max(255),
197
+ contentType: zod.z.string().min(1),
198
+ contentLength: zod.z.number().int().nonnegative()
199
+ });
200
+ var ConfirmUploadInputSchema = zod.z.object({
201
+ id: NodeIdSchema,
202
+ parentId: PathSchema,
203
+ name: zod.z.string().min(1).max(255),
204
+ contentType: zod.z.string().optional(),
205
+ size: zod.z.number().int().nonnegative().optional()
206
+ });
207
+ var SearchFilesInputSchema = zod.z.object({
208
+ query: zod.z.string(),
209
+ parentId: zod.z.string().optional(),
210
+ limit: zod.z.number().int().positive().optional()
211
+ });
212
+ var RestoreNodeInputSchema = zod.z.object({
213
+ id: NodeIdSchema
214
+ });
215
+ var CreateShareInputSchema = zod.z.object({
216
+ id: NodeIdSchema
217
+ });
218
+ var ResolveShareInputSchema = zod.z.object({
219
+ token: zod.z.string().min(1)
220
+ });
221
+ var RevokeShareInputSchema = zod.z.object({
222
+ token: zod.z.string().min(1)
223
+ });
224
+ var zodErr = (message, parsed) => err(
225
+ new FileSystemError({
226
+ code: "InternalError",
227
+ message,
228
+ retryable: false,
229
+ cause: { code: "ZodError", message: parsed.error.message, issues: parsed.error.issues }
230
+ })
231
+ );
232
+ var createServerActions = (deps) => {
233
+ const { store, writeThrough, fs, getAuth } = deps;
234
+ const wrap = (e, code, message) => {
235
+ if (e instanceof FileSystemError) return e;
236
+ return new FileSystemError({
237
+ code,
238
+ message: `${message}: ${e instanceof Error ? e.message : String(e)}`,
239
+ retryable: true
240
+ });
241
+ };
242
+ const authTenant = async () => getAuth();
243
+ const listFiles = async (input) => {
244
+ const parsed = ListFilesInputSchema.safeParse(input);
245
+ if (!parsed.success) return zodErr("Invalid listFiles input", parsed);
246
+ try {
247
+ const auth = await authTenant();
248
+ return store.listChildren({
249
+ tenantId: asTenantId(auth.tenantId),
250
+ parentId: parsed.data.parentId,
251
+ limit: parsed.data.limit,
252
+ cursor: parsed.data.cursor
253
+ });
254
+ } catch (e) {
255
+ return err(wrap(e, "InternalError", "listFiles failed"));
256
+ }
257
+ };
258
+ const deleteFile = async (input) => {
259
+ const parsed = DeleteFileInputSchema.safeParse(input);
260
+ if (!parsed.success) return zodErr("Invalid deleteFile input", parsed);
261
+ try {
262
+ const auth = await authTenant();
263
+ return writeThrough.deleteThroughFile({
264
+ tenantId: auth.tenantId,
265
+ id: parsed.data.id,
266
+ recursive: parsed.data.recursive
267
+ });
268
+ } catch (e) {
269
+ return err(wrap(e, "InternalError", "deleteFile failed"));
270
+ }
271
+ };
272
+ const moveFile = async (input) => {
273
+ const parsed = MoveFileInputSchema.safeParse(input);
274
+ if (!parsed.success) return zodErr("Invalid moveFile input", parsed);
275
+ try {
276
+ const auth = await authTenant();
277
+ return store.moveNode({
278
+ tenantId: asTenantId(auth.tenantId),
279
+ id: parsed.data.id,
280
+ newParentId: parsed.data.newParentId,
281
+ newName: parsed.data.newName
282
+ });
283
+ } catch (e) {
284
+ return err(wrap(e, "InternalError", "moveFile failed"));
285
+ }
286
+ };
287
+ const copyFile = async (input) => {
288
+ const parsed = CopyFileInputSchema.safeParse(input);
289
+ if (!parsed.success) return zodErr("Invalid copyFile input", parsed);
290
+ try {
291
+ const auth = await authTenant();
292
+ return writeThrough.copyThroughFile({
293
+ tenantId: auth.tenantId,
294
+ id: parsed.data.id,
295
+ newParentId: parsed.data.newParentId,
296
+ newName: parsed.data.newName
297
+ });
298
+ } catch (e) {
299
+ return err(wrap(e, "InternalError", "copyFile failed"));
300
+ }
301
+ };
302
+ const setMetadata = async (input) => {
303
+ const parsed = SetMetadataInputSchema.safeParse(input);
304
+ if (!parsed.success) return zodErr("Invalid setMetadata input", parsed);
305
+ try {
306
+ const auth = await authTenant();
307
+ return store.updateMetadata({
308
+ tenantId: asTenantId(auth.tenantId),
309
+ id: parsed.data.id,
310
+ metadata: parsed.data.metadata,
311
+ replace: parsed.data.replace
312
+ });
313
+ } catch (e) {
314
+ return err(wrap(e, "InternalError", "setMetadata failed"));
315
+ }
316
+ };
317
+ const createFolder = async (input) => {
318
+ const parsed = CreateFolderInputSchema.safeParse(input);
319
+ if (!parsed.success) return zodErr("Invalid createFolder input", parsed);
320
+ try {
321
+ const auth = await authTenant();
322
+ return store.createNode({
323
+ tenantId: asTenantId(auth.tenantId),
324
+ parentId: parsed.data.parentId,
325
+ name: parsed.data.name,
326
+ kind: "folder",
327
+ size: 0,
328
+ mimeType: "",
329
+ s3Key: "",
330
+ ownerId: auth.userId
331
+ });
332
+ } catch (e) {
333
+ return err(wrap(e, "InternalError", "createFolder failed"));
334
+ }
335
+ };
336
+ const prepareUpload = async (input) => {
337
+ const parsed = PrepareUploadInputSchema.safeParse(input);
338
+ if (!parsed.success) return zodErr("Invalid prepareUpload input", parsed);
339
+ try {
340
+ const auth = await authTenant();
341
+ const id = typeof globalThis.crypto?.randomUUID === "function" ? globalThis.crypto.randomUUID() : `upl-${Date.now()}`;
342
+ const signed = await fs.forTenant(auth.tenantId).adapter.createPresignedUploadUrl({
343
+ key: asS3Key(id),
344
+ contentType: parsed.data.contentType,
345
+ expiresIn: 900
346
+ });
347
+ if (!signed.ok) return signed;
348
+ return ok({
349
+ id,
350
+ key: id,
351
+ url: signed.value.url,
352
+ method: signed.value.method,
353
+ headers: signed.value.requiredHeaders ?? {},
354
+ expiresAt: new Date(Date.now() + 9e5).toISOString()
355
+ });
356
+ } catch (e) {
357
+ return err(wrap(e, "InternalError", "prepareUpload failed"));
358
+ }
359
+ };
360
+ const confirmUpload = async (input) => {
361
+ const parsed = ConfirmUploadInputSchema.safeParse(input);
362
+ if (!parsed.success) return zodErr("Invalid confirmUpload input", parsed);
363
+ try {
364
+ const auth = await authTenant();
365
+ return writeThrough.confirmUpload({
366
+ tenantId: auth.tenantId,
367
+ id: parsed.data.id,
368
+ parentId: parsed.data.parentId,
369
+ name: parsed.data.name,
370
+ contentType: parsed.data.contentType,
371
+ size: parsed.data.size,
372
+ ownerId: auth.userId
373
+ });
374
+ } catch (e) {
375
+ return err(wrap(e, "InternalError", "confirmUpload failed"));
376
+ }
377
+ };
378
+ const searchFiles = async (input) => {
379
+ const parsed = SearchFilesInputSchema.safeParse(input);
380
+ if (!parsed.success) return zodErr("Invalid searchFiles input", parsed);
381
+ try {
382
+ const auth = await authTenant();
383
+ return store.search({
384
+ tenantId: asTenantId(auth.tenantId),
385
+ query: parsed.data.query,
386
+ parentId: parsed.data.parentId,
387
+ limit: parsed.data.limit
388
+ });
389
+ } catch (e) {
390
+ return err(wrap(e, "InternalError", "searchFiles failed"));
391
+ }
392
+ };
393
+ const listTrash = async (input = {}) => {
394
+ try {
395
+ const auth = await authTenant();
396
+ return store.listTrash({
397
+ tenantId: asTenantId(auth.tenantId),
398
+ limit: input.limit,
399
+ cursor: input.cursor
400
+ });
401
+ } catch (e) {
402
+ return err(wrap(e, "InternalError", "listTrash failed"));
403
+ }
404
+ };
405
+ const restoreNode = async (input) => {
406
+ const parsed = RestoreNodeInputSchema.safeParse(input);
407
+ if (!parsed.success) return zodErr("Invalid restoreNode input", parsed);
408
+ try {
409
+ const auth = await authTenant();
410
+ return store.restoreNode({
411
+ tenantId: asTenantId(auth.tenantId),
412
+ id: parsed.data.id
413
+ });
414
+ } catch (e) {
415
+ return err(wrap(e, "InternalError", "restoreNode failed"));
416
+ }
417
+ };
418
+ const createShare = async (input) => {
419
+ const parsed = CreateShareInputSchema.safeParse(input);
420
+ if (!parsed.success) return zodErr("Invalid createShare input", parsed);
421
+ try {
422
+ const auth = await authTenant();
423
+ return store.createShare({
424
+ tenantId: asTenantId(auth.tenantId),
425
+ nodeId: parsed.data.id
426
+ });
427
+ } catch (e) {
428
+ return err(wrap(e, "InternalError", "createShare failed"));
429
+ }
430
+ };
431
+ const resolveShare = async (input) => {
432
+ const parsed = ResolveShareInputSchema.safeParse(input);
433
+ if (!parsed.success) return zodErr("Invalid resolveShare input", parsed);
434
+ try {
435
+ return store.resolveShare({ token: parsed.data.token });
436
+ } catch (e) {
437
+ return err(wrap(e, "InternalError", "resolveShare failed"));
438
+ }
439
+ };
440
+ const revokeShare = async (input) => {
441
+ const parsed = RevokeShareInputSchema.safeParse(input);
442
+ if (!parsed.success) return zodErr("Invalid revokeShare input", parsed);
443
+ try {
444
+ const auth = await authTenant();
445
+ return store.revokeShare({
446
+ tenantId: asTenantId(auth.tenantId),
447
+ token: parsed.data.token
448
+ });
449
+ } catch (e) {
450
+ return err(wrap(e, "InternalError", "revokeShare failed"));
451
+ }
452
+ };
453
+ return {
454
+ listFiles,
455
+ deleteFile,
456
+ moveFile,
457
+ copyFile,
458
+ setMetadata,
459
+ createFolder,
460
+ prepareUpload,
461
+ confirmUpload,
462
+ searchFiles,
463
+ listTrash,
464
+ restoreNode,
465
+ createShare,
466
+ resolveShare,
467
+ revokeShare
468
+ };
469
+ };
470
+
471
+ // src/server/route-handlers/upload.ts
472
+ var MAX_EXPIRES_IN_SECONDS = 7 * 24 * 60 * 60;
473
+ var DEFAULT_EXPIRES_IN_SECONDS = 900;
474
+ var matchesContentType = (pattern, actual) => {
475
+ if (pattern === actual) return true;
476
+ if (pattern.endsWith("/*")) {
477
+ const prefix = pattern.slice(0, -1);
478
+ return actual.startsWith(prefix);
479
+ }
480
+ return false;
481
+ };
482
+ var assertExpiresIn = (expiresIn) => {
483
+ if (!Number.isFinite(expiresIn) || expiresIn < 1 || expiresIn > MAX_EXPIRES_IN_SECONDS) {
484
+ throw new FileSystemError({
485
+ code: "InternalError",
486
+ message: `expiresIn must be between 1 and ${MAX_EXPIRES_IN_SECONDS} seconds (S3 SigV4 limit)`,
487
+ retryable: false,
488
+ cause: { code: "InvalidArgument", message: `expiresIn=${expiresIn}` }
489
+ });
490
+ }
491
+ };
492
+ var createUploadRouteHandler = (opts) => {
493
+ const expiresIn = opts.expiresIn ?? DEFAULT_EXPIRES_IN_SECONDS;
494
+ assertExpiresIn(expiresIn);
495
+ return async (req) => {
496
+ let body;
497
+ try {
498
+ body = await req.json();
499
+ } catch {
500
+ return Response.json(
501
+ { ok: false, error: { code: "InternalError", message: "Request body must be valid JSON" } },
502
+ { status: 400 }
503
+ );
504
+ }
505
+ if (typeof opts.maxBytes === "number" && body.contentLength > opts.maxBytes) {
506
+ return Response.json(
507
+ {
508
+ ok: false,
509
+ error: {
510
+ code: "PayloadTooLarge",
511
+ message: `contentLength ${body.contentLength} exceeds maxBytes ${opts.maxBytes}`
512
+ }
513
+ },
514
+ { status: 413 }
515
+ );
516
+ }
517
+ if (opts.allowedContentTypes && opts.allowedContentTypes.length > 0) {
518
+ const allowed = opts.allowedContentTypes.some((p) => matchesContentType(p, body.contentType));
519
+ if (!allowed) {
520
+ return Response.json(
521
+ {
522
+ ok: false,
523
+ error: {
524
+ code: "UnsupportedMediaType",
525
+ message: `contentType "${body.contentType}" is not in the allowed list`
526
+ }
527
+ },
528
+ { status: 415 }
529
+ );
530
+ }
531
+ }
532
+ let fs = opts.fs;
533
+ if (opts.getAuth) {
534
+ const auth = await opts.getAuth(req);
535
+ if (!auth) {
536
+ return Response.json(
537
+ { ok: false, error: { code: "Unauthorized", message: "Not authenticated" } },
538
+ { status: 401 }
539
+ );
540
+ }
541
+ fs = opts.fs.forTenant(auth.tenantId);
542
+ }
543
+ const id = typeof globalThis.crypto?.randomUUID === "function" ? globalThis.crypto.randomUUID() : `upl-${Date.now()}`;
544
+ const r = await fs.adapter.createPresignedUploadUrl({
545
+ key: asS3Key(id),
546
+ contentType: body.contentType,
547
+ expiresIn
548
+ });
549
+ if (!r.ok) {
550
+ return Response.json(
551
+ { ok: false, error: { code: r.error.code, message: r.error.message } },
552
+ { status: 500 }
553
+ );
554
+ }
555
+ const expiresAt = new Date(Date.now() + expiresIn * 1e3).toISOString();
556
+ return Response.json({
557
+ ok: true,
558
+ value: {
559
+ url: r.value.url,
560
+ key: id,
561
+ id,
562
+ method: r.value.method,
563
+ headers: r.value.requiredHeaders ?? {},
564
+ expiresAt
565
+ }
566
+ });
567
+ };
568
+ };
569
+
570
+ // src/server/route-handlers/download.ts
571
+ var MAX_EXPIRES_IN_SECONDS2 = 7 * 24 * 60 * 60;
572
+ var DEFAULT_EXPIRES_IN_SECONDS2 = 900;
573
+ var assertExpiresIn2 = (expiresIn) => {
574
+ if (!Number.isFinite(expiresIn) || expiresIn < 1 || expiresIn > MAX_EXPIRES_IN_SECONDS2) {
575
+ throw new FileSystemError({
576
+ code: "InternalError",
577
+ message: `expiresIn must be between 1 and ${MAX_EXPIRES_IN_SECONDS2} seconds (S3 SigV4 limit)`,
578
+ retryable: false,
579
+ cause: { code: "InvalidArgument", message: `expiresIn=${expiresIn}` }
580
+ });
581
+ }
582
+ };
583
+ var createDownloadRouteHandler = (opts) => {
584
+ const expiresIn = opts.expiresIn ?? DEFAULT_EXPIRES_IN_SECONDS2;
585
+ assertExpiresIn2(expiresIn);
586
+ return async (req) => {
587
+ const url = new URL(req.url);
588
+ const key = url.searchParams.get("key");
589
+ if (!key) {
590
+ return Response.json(
591
+ { ok: false, error: { code: "InternalError", message: "Missing required query param: key" } },
592
+ { status: 400 }
593
+ );
594
+ }
595
+ let fs = opts.fs;
596
+ if (opts.getAuth) {
597
+ const auth = await opts.getAuth(req);
598
+ if (!auth) {
599
+ return Response.json(
600
+ { ok: false, error: { code: "Unauthorized", message: "Not authenticated" } },
601
+ { status: 401 }
602
+ );
603
+ }
604
+ fs = opts.fs.forTenant(auth.tenantId);
605
+ }
606
+ const r = await fs.adapter.createPresignedDownloadUrl({
607
+ key: asS3Key(key),
608
+ expiresIn
609
+ });
610
+ if (!r.ok) {
611
+ return Response.json(
612
+ { ok: false, error: { code: r.error.code, message: r.error.message } },
613
+ { status: 500 }
614
+ );
615
+ }
616
+ const expiresAt = new Date(Date.now() + expiresIn * 1e3).toISOString();
617
+ return Response.json({
618
+ ok: true,
619
+ value: { url: r.value.url, expiresAt }
620
+ });
621
+ };
622
+ };
623
+
624
+ exports.createDownloadRouteHandler = createDownloadRouteHandler;
625
+ exports.createServerActions = createServerActions;
626
+ exports.createUploadRouteHandler = createUploadRouteHandler;
627
+ //# sourceMappingURL=index.cjs.map
628
+ //# sourceMappingURL=index.cjs.map