deepspace 0.11.0 → 0.13.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/CHANGELOG.md +198 -0
- package/dist/{chunk-ZJ43EK3N.js → chunk-233GQU2H.js} +492 -365
- package/dist/chunk-233GQU2H.js.map +1 -0
- package/dist/cli.js +872 -256
- package/dist/cli.js.map +1 -1
- package/dist/documentation-client-core.d.ts +2 -2
- package/dist/documentation-client-core.js +1 -1
- package/dist/documentation-default-renderer.cjs +34 -34
- package/dist/documentation-react.d.ts +1 -1
- package/dist/documentation-react.js +1 -1
- package/dist/documentation-runtime.js +10 -10
- package/dist/documentation-server-core.d.ts +2 -2
- package/dist/documentation-server-core.js +1 -1
- package/dist/documentation.js +2 -2
- package/dist/documentation.js.map +1 -1
- package/dist/index.d.ts +28 -3
- package/dist/index.js +131 -5
- package/dist/index.js.map +1 -1
- package/dist/{public-BGRX_Cjd.d.ts → public-BtArwbVM.d.ts} +8 -0
- package/dist/server.d.ts +104 -5
- package/dist/server.js +355 -77
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +104 -5
- package/dist/worker.js +360 -77
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-ZJ43EK3N.js.map +0 -1
package/dist/worker.js
CHANGED
|
@@ -6240,8 +6240,54 @@ function registryErrorJson(c, err) {
|
|
|
6240
6240
|
throw err;
|
|
6241
6241
|
}
|
|
6242
6242
|
|
|
6243
|
+
// src/shared/app-files.ts
|
|
6244
|
+
var MAX_APP_FILE_BYTES = 1024 * 1024 * 1024;
|
|
6245
|
+
var MAX_UPLOAD_REQUEST_BYTES = 25 * 1024 * 1024;
|
|
6246
|
+
var UPLOAD_PART_BYTES = 20 * 1024 * 1024;
|
|
6247
|
+
var MAX_UPLOAD_PARTS = Math.ceil(MAX_APP_FILE_BYTES / UPLOAD_PART_BYTES);
|
|
6248
|
+
var MAX_BASE64_UPLOAD_BYTES = Math.floor(MAX_UPLOAD_REQUEST_BYTES * 3 / 4);
|
|
6249
|
+
var MAX_DEPLOY_ASSET_FILE_BYTES = 25 * 1024 * 1024;
|
|
6250
|
+
var DANGEROUS_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
6251
|
+
"text/html",
|
|
6252
|
+
"application/xhtml+xml",
|
|
6253
|
+
"image/svg+xml",
|
|
6254
|
+
"application/xml",
|
|
6255
|
+
"text/xml",
|
|
6256
|
+
"application/javascript",
|
|
6257
|
+
"text/javascript",
|
|
6258
|
+
"application/ecmascript",
|
|
6259
|
+
"text/ecmascript"
|
|
6260
|
+
]);
|
|
6261
|
+
function activeContentMessage(mimeType) {
|
|
6262
|
+
return `${mimeType} is not accepted: the files API serves your app's own origin, so stored HTML/SVG/JavaScript would execute there with your users' sessions. Ship it as a build asset under \`public/\` instead, where it is served as part of your app rather than as user-uploaded content.`;
|
|
6263
|
+
}
|
|
6264
|
+
function formatBytes(bytes) {
|
|
6265
|
+
if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GiB`;
|
|
6266
|
+
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
6267
|
+
if (bytes >= 1024) return `${Math.round(bytes / 1024)} KiB`;
|
|
6268
|
+
return `${bytes} B`;
|
|
6269
|
+
}
|
|
6270
|
+
function encodeKeyPath(key) {
|
|
6271
|
+
return key.split("/").map(encodeURIComponent).join("/");
|
|
6272
|
+
}
|
|
6273
|
+
function oversizeMessage(bytes, limit = MAX_APP_FILE_BYTES) {
|
|
6274
|
+
return `That file is ${formatBytes(bytes)}; the limit is ${formatBytes(limit)} per file.`;
|
|
6275
|
+
}
|
|
6276
|
+
function oversizeRequestMessage(bytes) {
|
|
6277
|
+
return `This request carries ${formatBytes(bytes)}; one request may carry ${formatBytes(MAX_UPLOAD_REQUEST_BYTES)}. Files above ${formatBytes(UPLOAD_PART_BYTES)} upload in parts.`;
|
|
6278
|
+
}
|
|
6279
|
+
function oversizePartMessage(bytes) {
|
|
6280
|
+
return `This part carries ${formatBytes(bytes)}; a part may carry ${formatBytes(UPLOAD_PART_BYTES)} \u2014 the size the upload advertised.`;
|
|
6281
|
+
}
|
|
6282
|
+
|
|
6243
6283
|
// src/server/utils/scoped-r2-files.ts
|
|
6244
6284
|
var CORS_HEADERS3 = { "Access-Control-Allow-Origin": "*" };
|
|
6285
|
+
function fail(status, error, code, extra) {
|
|
6286
|
+
return Response.json(
|
|
6287
|
+
{ error, ...code ? { code } : {}, ...extra },
|
|
6288
|
+
{ status, headers: CORS_HEADERS3 }
|
|
6289
|
+
);
|
|
6290
|
+
}
|
|
6245
6291
|
function contentDisposition(originalName) {
|
|
6246
6292
|
const safe = originalName.replace(/[^\x20-\x7e]|["\\]/g, "_") || "download";
|
|
6247
6293
|
const encoded = encodeURIComponent(originalName).replace(
|
|
@@ -6256,25 +6302,89 @@ function sanitizeSubpath(raw) {
|
|
|
6256
6302
|
if (segments.some((s) => s === ".." || s === ".")) return null;
|
|
6257
6303
|
return raw;
|
|
6258
6304
|
}
|
|
6259
|
-
function
|
|
6260
|
-
|
|
6305
|
+
function overRequestBound(bytes, slack = 0) {
|
|
6306
|
+
if (bytes <= MAX_UPLOAD_REQUEST_BYTES + slack) return null;
|
|
6307
|
+
return fail(413, oversizeRequestMessage(bytes), "too_large", {
|
|
6308
|
+
maxBytes: MAX_UPLOAD_REQUEST_BYTES,
|
|
6309
|
+
partSize: UPLOAD_PART_BYTES
|
|
6310
|
+
});
|
|
6311
|
+
}
|
|
6312
|
+
function overCeiling(bytes) {
|
|
6313
|
+
if (bytes <= MAX_APP_FILE_BYTES) return null;
|
|
6314
|
+
return fail(413, oversizeMessage(bytes), "too_large", { maxBytes: MAX_APP_FILE_BYTES });
|
|
6315
|
+
}
|
|
6316
|
+
function overPartBound(bytes) {
|
|
6317
|
+
if (bytes <= UPLOAD_PART_BYTES) return null;
|
|
6318
|
+
return fail(413, oversizePartMessage(bytes), "too_large", { maxBytes: UPLOAD_PART_BYTES });
|
|
6319
|
+
}
|
|
6320
|
+
function declaredLength(request, what) {
|
|
6321
|
+
const raw = request.headers.get("content-length");
|
|
6322
|
+
const declared = raw === null ? NaN : Number(raw);
|
|
6323
|
+
if (!Number.isSafeInteger(declared) || declared < 0) {
|
|
6324
|
+
return fail(
|
|
6325
|
+
411,
|
|
6326
|
+
`${what} must declare a Content-Length. A chunked or length-less body is refused: R2 cannot size a write it has no length for.`,
|
|
6327
|
+
"length_required"
|
|
6328
|
+
);
|
|
6329
|
+
}
|
|
6330
|
+
return declared;
|
|
6261
6331
|
}
|
|
6262
6332
|
function isKeyWithinPrefix(key, prefix) {
|
|
6263
6333
|
return key.startsWith(prefix);
|
|
6264
6334
|
}
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
"
|
|
6268
|
-
"
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
"
|
|
6273
|
-
"
|
|
6274
|
-
"
|
|
6335
|
+
function anchorKey(raw, prefix) {
|
|
6336
|
+
const safe = sanitizeSubpath(raw);
|
|
6337
|
+
if (safe === null) return fail(400, "Invalid key: traversal not allowed", "invalid_key");
|
|
6338
|
+
if (!safe) return fail(400, "Invalid key: empty", "invalid_key");
|
|
6339
|
+
return { key: `${prefix}${safe}` };
|
|
6340
|
+
}
|
|
6341
|
+
var VERB_ROUTES = /* @__PURE__ */ new Set([
|
|
6342
|
+
"POST upload",
|
|
6343
|
+
"POST multipart",
|
|
6344
|
+
"DELETE multipart",
|
|
6345
|
+
"PUT multipart/part",
|
|
6346
|
+
"POST multipart/complete"
|
|
6275
6347
|
]);
|
|
6276
|
-
function
|
|
6277
|
-
return
|
|
6348
|
+
function isFilesVerbPath(method, subpath) {
|
|
6349
|
+
return VERB_ROUTES.has(`${method} ${subpath}`);
|
|
6350
|
+
}
|
|
6351
|
+
function resolveMimeType(raw) {
|
|
6352
|
+
const mimeType = raw.split(";")[0].trim().toLowerCase() || "application/octet-stream";
|
|
6353
|
+
return DANGEROUS_MIME_TYPES.has(mimeType) ? fail(415, activeContentMessage(mimeType), "active_content") : { mimeType };
|
|
6354
|
+
}
|
|
6355
|
+
function r2ErrorCode(error) {
|
|
6356
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6357
|
+
const match = /\((\d{4,6})\)\s*$/.exec(message.trim());
|
|
6358
|
+
return match ? Number(match[1]) : null;
|
|
6359
|
+
}
|
|
6360
|
+
function multipartFailure(error) {
|
|
6361
|
+
switch (r2ErrorCode(error)) {
|
|
6362
|
+
case 10024:
|
|
6363
|
+
return fail(
|
|
6364
|
+
404,
|
|
6365
|
+
"That upload does not exist \u2014 it was completed, abandoned, or expired. Start a new one.",
|
|
6366
|
+
"unknown_upload"
|
|
6367
|
+
);
|
|
6368
|
+
case 10011:
|
|
6369
|
+
// EntityTooSmall
|
|
6370
|
+
case 10025:
|
|
6371
|
+
// InvalidPart — a named part was never uploaded
|
|
6372
|
+
case 10048:
|
|
6373
|
+
return fail(
|
|
6374
|
+
400,
|
|
6375
|
+
"The parts do not assemble: every part except the last must be exactly the size the upload advertised, and each must be uploaded before it is named.",
|
|
6376
|
+
"bad_parts"
|
|
6377
|
+
);
|
|
6378
|
+
case 10012:
|
|
6379
|
+
// MetadataTooLarge
|
|
6380
|
+
case 10020:
|
|
6381
|
+
return fail(400, "The upload names a key or metadata R2 will not accept.", "invalid_key");
|
|
6382
|
+
default: {
|
|
6383
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6384
|
+
console.error("[files:multipart] R2 error:", message);
|
|
6385
|
+
return fail(503, "The files service could not complete that upload. Retry shortly.", "r2_error");
|
|
6386
|
+
}
|
|
6387
|
+
}
|
|
6278
6388
|
}
|
|
6279
6389
|
function createScopedR2Handler(config) {
|
|
6280
6390
|
const requireAuthForMutations = config.requireAuthForMutations ?? true;
|
|
@@ -6306,14 +6416,12 @@ function createScopedR2Handler(config) {
|
|
|
6306
6416
|
return Response.json({ error: errorMsg }, { status, headers: CORS_HEADERS3 });
|
|
6307
6417
|
}
|
|
6308
6418
|
const prefix = result.prefix;
|
|
6309
|
-
if (
|
|
6419
|
+
if (isFilesVerbPath(request.method, subpath)) {
|
|
6310
6420
|
if (requireAuthForMutations && !auth.userId) {
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
{ status: 401, headers: CORS_HEADERS3 }
|
|
6314
|
-
);
|
|
6421
|
+
await request.body?.cancel().catch(() => void 0);
|
|
6422
|
+
return fail(401, "Authentication required", "unauthorized");
|
|
6315
6423
|
}
|
|
6316
|
-
return handleUpload(request, url, bucket, prefix, auth.userId, scope);
|
|
6424
|
+
return subpath === "upload" ? handleUpload(request, url, bucket, prefix, auth.userId, scope) : handleMultipart(request, url, bucket, prefix, auth.userId, scope, subpath);
|
|
6317
6425
|
}
|
|
6318
6426
|
if (!subpath && request.method === "GET") {
|
|
6319
6427
|
return handleList(bucket, prefix, url, scope);
|
|
@@ -6340,87 +6448,250 @@ function createScopedR2Handler(config) {
|
|
|
6340
6448
|
{ status: 403, headers: CORS_HEADERS3 }
|
|
6341
6449
|
);
|
|
6342
6450
|
}
|
|
6343
|
-
await bucket.
|
|
6344
|
-
|
|
6451
|
+
const existed = await bucket.head(subpath) !== null;
|
|
6452
|
+
if (existed) await bucket.delete(subpath);
|
|
6453
|
+
return Response.json(
|
|
6454
|
+
{ success: true, deleted: existed ? subpath : null, existed },
|
|
6455
|
+
{ headers: CORS_HEADERS3 }
|
|
6456
|
+
);
|
|
6345
6457
|
}
|
|
6346
6458
|
return Response.json({ error: "Method not allowed" }, { status: 405, headers: CORS_HEADERS3 });
|
|
6347
6459
|
};
|
|
6348
6460
|
}
|
|
6349
6461
|
async function handleUpload(request, url, bucket, prefix, userId, scope) {
|
|
6350
6462
|
try {
|
|
6463
|
+
const declared = overRequestBound(Number(request.headers.get("content-length")), 8 * 1024);
|
|
6464
|
+
if (declared) return declared;
|
|
6351
6465
|
const contentType = request.headers.get("content-type") || "";
|
|
6352
6466
|
let fileData;
|
|
6353
6467
|
let fileName;
|
|
6354
|
-
let
|
|
6468
|
+
let rawMimeType;
|
|
6355
6469
|
if (contentType.includes("multipart/form-data")) {
|
|
6356
6470
|
const formData = await request.formData();
|
|
6357
6471
|
const file = formData.get("file");
|
|
6358
|
-
if (!file)
|
|
6359
|
-
|
|
6360
|
-
|
|
6472
|
+
if (!file) return fail(400, "No file provided", "bad_request");
|
|
6473
|
+
const tooBig = overRequestBound(file.size);
|
|
6474
|
+
if (tooBig) return tooBig;
|
|
6361
6475
|
fileData = await file.arrayBuffer();
|
|
6362
6476
|
fileName = formData.get("name")?.toString() || file.name;
|
|
6363
|
-
|
|
6477
|
+
rawMimeType = file.type || "application/octet-stream";
|
|
6364
6478
|
} else {
|
|
6365
6479
|
const body = await request.json();
|
|
6366
|
-
if (!body.data || !body.name)
|
|
6367
|
-
return Response.json(
|
|
6368
|
-
{ error: "Missing data or name" },
|
|
6369
|
-
{ status: 400, headers: CORS_HEADERS3 }
|
|
6370
|
-
);
|
|
6371
|
-
}
|
|
6480
|
+
if (!body.data || !body.name) return fail(400, "Missing data or name", "bad_request");
|
|
6372
6481
|
const base64Data = body.data.replace(/^data:[^;]+;base64,/, "");
|
|
6482
|
+
const tooBig = overRequestBound(Math.floor(base64Data.length * 3 / 4));
|
|
6483
|
+
if (tooBig) return tooBig;
|
|
6373
6484
|
fileData = Uint8Array.from(atob(base64Data), (c) => c.charCodeAt(0)).buffer;
|
|
6374
6485
|
fileName = body.name;
|
|
6375
|
-
|
|
6376
|
-
}
|
|
6377
|
-
|
|
6378
|
-
if (
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
const requestedKey = url.searchParams.get("key");
|
|
6385
|
-
let key;
|
|
6386
|
-
if (requestedKey !== null) {
|
|
6387
|
-
const safe = sanitizeSubpath(requestedKey);
|
|
6388
|
-
if (safe === null) {
|
|
6389
|
-
return Response.json(
|
|
6390
|
-
{ error: "Invalid key: traversal not allowed" },
|
|
6391
|
-
{ status: 400, headers: CORS_HEADERS3 }
|
|
6392
|
-
);
|
|
6393
|
-
}
|
|
6394
|
-
if (!safe) {
|
|
6395
|
-
return Response.json(
|
|
6396
|
-
{ error: "Invalid key: empty" },
|
|
6397
|
-
{ status: 400, headers: CORS_HEADERS3 }
|
|
6398
|
-
);
|
|
6399
|
-
}
|
|
6400
|
-
key = `${prefix}${safe}`;
|
|
6401
|
-
} else {
|
|
6402
|
-
const fileKey = `${Date.now()}-${Math.random().toString(36).slice(2, 11)}-${fileName}`;
|
|
6403
|
-
key = `${prefix}${fileKey}`;
|
|
6404
|
-
}
|
|
6405
|
-
await bucket.put(key, fileData, {
|
|
6406
|
-
httpMetadata: { contentType: mimeType },
|
|
6407
|
-
customMetadata: {
|
|
6408
|
-
originalName: fileName,
|
|
6409
|
-
...userId ? { uploadedBy: userId } : {},
|
|
6410
|
-
uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6411
|
-
}
|
|
6486
|
+
rawMimeType = body.mimeType || "application/octet-stream";
|
|
6487
|
+
}
|
|
6488
|
+
const mime = resolveMimeType(rawMimeType);
|
|
6489
|
+
if (mime instanceof Response) return mime;
|
|
6490
|
+
const located = locateUpload(url, prefix, fileName);
|
|
6491
|
+
if (located instanceof Response) return located;
|
|
6492
|
+
await bucket.put(located.key, fileData, {
|
|
6493
|
+
httpMetadata: { contentType: mime.mimeType },
|
|
6494
|
+
customMetadata: fileMetadata(fileName, userId)
|
|
6412
6495
|
});
|
|
6413
|
-
|
|
6414
|
-
fileUrl.searchParams.set("scope", scope);
|
|
6415
|
-
return Response.json(
|
|
6416
|
-
{ success: true, key, url: fileUrl.toString(), name: fileName },
|
|
6417
|
-
{ headers: CORS_HEADERS3 }
|
|
6418
|
-
);
|
|
6496
|
+
return storedFileJson(located.key, fileName, url, scope);
|
|
6419
6497
|
} catch (err) {
|
|
6420
6498
|
const msg = err instanceof Error ? err.message : "Upload failed";
|
|
6421
6499
|
console.error("[handleUpload] Error:", msg, err instanceof Error ? err.stack : "");
|
|
6422
|
-
return
|
|
6500
|
+
return fail(500, msg, "upload_failed");
|
|
6501
|
+
}
|
|
6502
|
+
}
|
|
6503
|
+
function locateUpload(url, prefix, fileName) {
|
|
6504
|
+
const requested = url.searchParams.get("key");
|
|
6505
|
+
const subkey = requested ?? `${Date.now()}-${Math.random().toString(36).slice(2, 11)}-${fileName}`;
|
|
6506
|
+
const anchored = anchorKey(subkey, prefix);
|
|
6507
|
+
return anchored instanceof Response ? anchored : { key: anchored.key, subkey };
|
|
6508
|
+
}
|
|
6509
|
+
function fileMetadata(fileName, userId) {
|
|
6510
|
+
return {
|
|
6511
|
+
originalName: fileName,
|
|
6512
|
+
...userId ? { uploadedBy: userId } : {},
|
|
6513
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6514
|
+
};
|
|
6515
|
+
}
|
|
6516
|
+
function storedFileJson(key, name, url, scope) {
|
|
6517
|
+
const fileUrl = new URL(`/api/files/${encodeKeyPath(key)}`, url.origin);
|
|
6518
|
+
fileUrl.searchParams.set("scope", scope);
|
|
6519
|
+
return Response.json(
|
|
6520
|
+
{ success: true, key, url: fileUrl.toString(), name },
|
|
6521
|
+
{ headers: CORS_HEADERS3 }
|
|
6522
|
+
);
|
|
6523
|
+
}
|
|
6524
|
+
var MAX_CONTROL_BODY_BYTES = 64 * 1024;
|
|
6525
|
+
async function readControlJson(request) {
|
|
6526
|
+
const declared = declaredLength(request, "This request");
|
|
6527
|
+
if (declared instanceof Response) return declared;
|
|
6528
|
+
if (declared > MAX_CONTROL_BODY_BYTES) {
|
|
6529
|
+
return fail(413, `Request body exceeds ${MAX_CONTROL_BODY_BYTES} bytes`, "too_large");
|
|
6530
|
+
}
|
|
6531
|
+
let parsed;
|
|
6532
|
+
try {
|
|
6533
|
+
parsed = await request.json();
|
|
6534
|
+
} catch {
|
|
6535
|
+
return fail(400, "Body must be JSON", "bad_request");
|
|
6536
|
+
}
|
|
6537
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : fail(400, "Body must be a JSON object", "bad_request");
|
|
6538
|
+
}
|
|
6539
|
+
function resolveSession(url, prefix) {
|
|
6540
|
+
const uploadKey = url.searchParams.get("uploadKey");
|
|
6541
|
+
const uploadId = url.searchParams.get("uploadId");
|
|
6542
|
+
if (!uploadKey || !uploadId) {
|
|
6543
|
+
return fail(400, "uploadKey and uploadId are required", "bad_request");
|
|
6544
|
+
}
|
|
6545
|
+
const anchored = anchorKey(uploadKey, prefix);
|
|
6546
|
+
return anchored instanceof Response ? anchored : { key: anchored.key, uploadId };
|
|
6547
|
+
}
|
|
6548
|
+
async function streamPart(upload, partNumber, body, expectedBytes) {
|
|
6549
|
+
let observedBytes = 0;
|
|
6550
|
+
const sized = new FixedLengthStream(expectedBytes);
|
|
6551
|
+
const pumped = body.pipeThrough(
|
|
6552
|
+
new TransformStream({
|
|
6553
|
+
transform(chunk, controller) {
|
|
6554
|
+
observedBytes += chunk.byteLength;
|
|
6555
|
+
controller.enqueue(chunk);
|
|
6556
|
+
}
|
|
6557
|
+
})
|
|
6558
|
+
).pipeTo(sized.writable);
|
|
6559
|
+
const [uploaded, drained] = await Promise.allSettled([
|
|
6560
|
+
upload.uploadPart(partNumber, sized.readable),
|
|
6561
|
+
pumped
|
|
6562
|
+
]);
|
|
6563
|
+
if (uploaded.status === "rejected" || drained.status === "rejected") {
|
|
6564
|
+
const reason = uploaded.status === "rejected" ? uploaded.reason : drained.reason;
|
|
6565
|
+
return { ok: false, lengthMismatch: observedBytes !== expectedBytes, error: reason };
|
|
6566
|
+
}
|
|
6567
|
+
return { ok: true, part: uploaded.value };
|
|
6568
|
+
}
|
|
6569
|
+
async function handleMultipart(request, url, bucket, prefix, userId, scope, subpath) {
|
|
6570
|
+
if (subpath === "multipart" && request.method === "POST") {
|
|
6571
|
+
return multipartInit(request, url, bucket, prefix, userId);
|
|
6572
|
+
}
|
|
6573
|
+
if (subpath === "multipart/part") return multipartPart(request, url, bucket, prefix);
|
|
6574
|
+
if (subpath === "multipart/complete") {
|
|
6575
|
+
return multipartComplete(request, url, bucket, prefix, scope);
|
|
6576
|
+
}
|
|
6577
|
+
return multipartAbort(url, bucket, prefix);
|
|
6578
|
+
}
|
|
6579
|
+
async function multipartInit(request, url, bucket, prefix, userId) {
|
|
6580
|
+
const body = await readControlJson(request);
|
|
6581
|
+
if (body instanceof Response) return body;
|
|
6582
|
+
const size = body.size;
|
|
6583
|
+
if (!Number.isSafeInteger(size) || size < 0) {
|
|
6584
|
+
return fail(400, "size must be a non-negative integer number of bytes", "bad_request");
|
|
6585
|
+
}
|
|
6586
|
+
const tooBig = overCeiling(size);
|
|
6587
|
+
if (tooBig) return tooBig;
|
|
6588
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
6589
|
+
if (!name) return fail(400, "name is required", "bad_request");
|
|
6590
|
+
const mime = resolveMimeType(typeof body.mimeType === "string" ? body.mimeType : "");
|
|
6591
|
+
if (mime instanceof Response) return mime;
|
|
6592
|
+
const located = locateUpload(url, prefix, name);
|
|
6593
|
+
if (located instanceof Response) return located;
|
|
6594
|
+
let upload;
|
|
6595
|
+
try {
|
|
6596
|
+
upload = await bucket.createMultipartUpload(located.key, {
|
|
6597
|
+
httpMetadata: { contentType: mime.mimeType },
|
|
6598
|
+
customMetadata: fileMetadata(name, userId)
|
|
6599
|
+
});
|
|
6600
|
+
} catch (error) {
|
|
6601
|
+
return multipartFailure(error);
|
|
6602
|
+
}
|
|
6603
|
+
return Response.json(
|
|
6604
|
+
{
|
|
6605
|
+
uploadId: upload.uploadId,
|
|
6606
|
+
// Echoed back on every later call. Relative to the app, exactly like
|
|
6607
|
+
// `?key=`, so the mount that anchors keys anchors this one too.
|
|
6608
|
+
uploadKey: located.subkey,
|
|
6609
|
+
partSize: UPLOAD_PART_BYTES,
|
|
6610
|
+
maxParts: MAX_UPLOAD_PARTS,
|
|
6611
|
+
maxBytes: MAX_APP_FILE_BYTES
|
|
6612
|
+
},
|
|
6613
|
+
{ headers: CORS_HEADERS3 }
|
|
6614
|
+
);
|
|
6615
|
+
}
|
|
6616
|
+
async function multipartPart(request, url, bucket, prefix) {
|
|
6617
|
+
const refuse = async (response) => {
|
|
6618
|
+
await request.body?.cancel().catch(() => void 0);
|
|
6619
|
+
return response;
|
|
6620
|
+
};
|
|
6621
|
+
const declaredBytes = declaredLength(request, "Part uploads");
|
|
6622
|
+
if (declaredBytes instanceof Response) return refuse(declaredBytes);
|
|
6623
|
+
const tooBig = overPartBound(declaredBytes);
|
|
6624
|
+
if (tooBig) return refuse(tooBig);
|
|
6625
|
+
const partNumber = Number(url.searchParams.get("partNumber"));
|
|
6626
|
+
if (!Number.isSafeInteger(partNumber) || partNumber < 1 || partNumber > MAX_UPLOAD_PARTS) {
|
|
6627
|
+
return refuse(
|
|
6628
|
+
fail(400, `partNumber must be a whole number from 1 to ${MAX_UPLOAD_PARTS}`, "bad_request")
|
|
6629
|
+
);
|
|
6630
|
+
}
|
|
6631
|
+
const session = resolveSession(url, prefix);
|
|
6632
|
+
if (session instanceof Response) return refuse(session);
|
|
6633
|
+
if (!request.body) return fail(400, "A part body is required", "bad_request");
|
|
6634
|
+
const upload = bucket.resumeMultipartUpload(session.key, session.uploadId);
|
|
6635
|
+
const stored = await streamPart(upload, partNumber, request.body, declaredBytes);
|
|
6636
|
+
if (!stored.ok) {
|
|
6637
|
+
return stored.lengthMismatch ? fail(
|
|
6638
|
+
400,
|
|
6639
|
+
`Part ${partNumber} declared ${declaredBytes} bytes but its body did not carry them`,
|
|
6640
|
+
"bad_request"
|
|
6641
|
+
) : multipartFailure(stored.error);
|
|
6642
|
+
}
|
|
6643
|
+
return Response.json(
|
|
6644
|
+
{ partNumber: stored.part.partNumber, etag: stored.part.etag },
|
|
6645
|
+
{ headers: CORS_HEADERS3 }
|
|
6646
|
+
);
|
|
6647
|
+
}
|
|
6648
|
+
async function multipartComplete(request, url, bucket, prefix, scope) {
|
|
6649
|
+
const body = await readControlJson(request);
|
|
6650
|
+
if (body instanceof Response) return body;
|
|
6651
|
+
const session = resolveSession(url, prefix);
|
|
6652
|
+
if (session instanceof Response) return session;
|
|
6653
|
+
const raw = body.parts;
|
|
6654
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
6655
|
+
return fail(400, "parts must be a non-empty array", "bad_request");
|
|
6656
|
+
}
|
|
6657
|
+
if (raw.length > MAX_UPLOAD_PARTS) {
|
|
6658
|
+
return fail(400, `An upload may have at most ${MAX_UPLOAD_PARTS} parts`, "bad_request");
|
|
6659
|
+
}
|
|
6660
|
+
const parts = [];
|
|
6661
|
+
for (let index = 0; index < raw.length; index++) {
|
|
6662
|
+
const { partNumber, etag } = raw[index] ?? {};
|
|
6663
|
+
if (!Number.isSafeInteger(partNumber) || partNumber < 1) {
|
|
6664
|
+
return fail(400, `parts[${index}].partNumber must be a positive integer`, "bad_request");
|
|
6665
|
+
}
|
|
6666
|
+
if (typeof etag !== "string" || !etag) {
|
|
6667
|
+
return fail(400, `parts[${index}].etag must be the string uploadPart returned`, "bad_request");
|
|
6668
|
+
}
|
|
6669
|
+
parts.push({ partNumber, etag });
|
|
6670
|
+
}
|
|
6671
|
+
let object;
|
|
6672
|
+
try {
|
|
6673
|
+
object = await bucket.resumeMultipartUpload(session.key, session.uploadId).complete(parts);
|
|
6674
|
+
} catch (error) {
|
|
6675
|
+
return multipartFailure(error);
|
|
6676
|
+
}
|
|
6677
|
+
const tooBig = overCeiling(object.size);
|
|
6678
|
+
if (tooBig) {
|
|
6679
|
+
await bucket.delete(session.key).catch(() => void 0);
|
|
6680
|
+
return tooBig;
|
|
6681
|
+
}
|
|
6682
|
+
const name = object.customMetadata?.originalName ?? session.key.split("/").pop() ?? "file";
|
|
6683
|
+
return storedFileJson(session.key, name, url, scope);
|
|
6684
|
+
}
|
|
6685
|
+
async function multipartAbort(url, bucket, prefix) {
|
|
6686
|
+
const session = resolveSession(url, prefix);
|
|
6687
|
+
if (session instanceof Response) return session;
|
|
6688
|
+
try {
|
|
6689
|
+
await bucket.resumeMultipartUpload(session.key, session.uploadId).abort();
|
|
6690
|
+
} catch (error) {
|
|
6691
|
+
console.error("[files:multipart] abort:", error instanceof Error ? error.message : error);
|
|
6692
|
+
return Response.json({ success: true, aborted: false }, { headers: CORS_HEADERS3 });
|
|
6423
6693
|
}
|
|
6694
|
+
return Response.json({ success: true, aborted: true }, { headers: CORS_HEADERS3 });
|
|
6424
6695
|
}
|
|
6425
6696
|
async function handleList(bucket, prefix, url, scope) {
|
|
6426
6697
|
const userPrefix = url.searchParams.get("prefix") || "";
|
|
@@ -8559,7 +8830,12 @@ function serverCard(request, manifest, publicBasePath) {
|
|
|
8559
8830
|
name: manifest.name,
|
|
8560
8831
|
description: `Search and read the published ${documentationSubject(manifest.name)}.`,
|
|
8561
8832
|
version: MCP_SERVER_VERSION,
|
|
8833
|
+
// Preferred version, then everything the handshake will actually accept.
|
|
8834
|
+
// Advertising only the preferred one made the card look like it disagreed
|
|
8835
|
+
// with `initialize`, which per spec MUST echo a supported version the
|
|
8836
|
+
// client asked for rather than force the newest.
|
|
8562
8837
|
protocolVersion: CURRENT_PROTOCOL_VERSION,
|
|
8838
|
+
protocolVersions: [...SUPPORTED_PROTOCOL_VERSIONS],
|
|
8563
8839
|
transport: {
|
|
8564
8840
|
type: "streamable-http",
|
|
8565
8841
|
url: new URL(`${publicBasePath}/mcp`, request.url).toString()
|
|
@@ -9072,6 +9348,10 @@ export {
|
|
|
9072
9348
|
GameRoom,
|
|
9073
9349
|
JobRoom,
|
|
9074
9350
|
LOG_LEVELS,
|
|
9351
|
+
MAX_APP_FILE_BYTES,
|
|
9352
|
+
MAX_DEPLOY_ASSET_FILE_BYTES,
|
|
9353
|
+
MAX_UPLOAD_PARTS,
|
|
9354
|
+
MAX_UPLOAD_REQUEST_BYTES,
|
|
9075
9355
|
MESSAGES_SCHEMA,
|
|
9076
9356
|
MSG,
|
|
9077
9357
|
MSG_AWARENESS,
|
|
@@ -9098,6 +9378,7 @@ export {
|
|
|
9098
9378
|
SYSTEM_MANAGED_COLUMNS,
|
|
9099
9379
|
SchemaRegistry,
|
|
9100
9380
|
TEST_ACCOUNT_EMAIL_SUFFIX,
|
|
9381
|
+
UPLOAD_PART_BYTES,
|
|
9101
9382
|
USERS_COLUMNS,
|
|
9102
9383
|
VOTING_SCHEMAS,
|
|
9103
9384
|
WORKSPACE_SCHEMAS,
|
|
@@ -9144,6 +9425,7 @@ export {
|
|
|
9144
9425
|
encodeSyncStep2,
|
|
9145
9426
|
encodeUpdate,
|
|
9146
9427
|
enqueueJob,
|
|
9428
|
+
formatBytes,
|
|
9147
9429
|
getChat,
|
|
9148
9430
|
getDeepSpaceAIModel,
|
|
9149
9431
|
getGlobalDOSchemas,
|
|
@@ -9155,6 +9437,7 @@ export {
|
|
|
9155
9437
|
handleSyncMessage,
|
|
9156
9438
|
hasContent,
|
|
9157
9439
|
isAutoProvision,
|
|
9440
|
+
isFilesVerbPath,
|
|
9158
9441
|
isOwner,
|
|
9159
9442
|
isTestAccountClaims,
|
|
9160
9443
|
isTestAccountEmail,
|