@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,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
|