deepspace 0.19.5 → 0.20.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # deepspace
2
2
 
3
+ ## 0.20.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Harden and simplify app-file storage with race-safe quota summaries, fixed-layout recoverable multipart reservations, strict public/private scope isolation, bounded request bodies, and safe downloadable HTML/SVG/JavaScript attachments. Quota-enabled custom handlers must now provide a coordination key.
8
+
3
9
  ## 0.19.5
4
10
 
5
11
  ### Patch Changes
package/dist/cli.js CHANGED
@@ -3799,13 +3799,13 @@ var MAX_UPLOAD_REQUEST_BYTES = 25 * 1024 * 1024;
3799
3799
  var UPLOAD_PART_BYTES = 20 * 1024 * 1024;
3800
3800
  var MAX_UPLOAD_PARTS = Math.ceil(MAX_APP_FILE_BYTES / UPLOAD_PART_BYTES);
3801
3801
  var MAX_BASE64_UPLOAD_BYTES = Math.floor(MAX_UPLOAD_REQUEST_BYTES * 3 / 4);
3802
- function planUploadParts(totalBytes, partSize) {
3802
+ function planUploadParts(totalBytes) {
3803
3803
  const parts = [];
3804
- for (let start = 0; start < totalBytes; start += partSize) {
3804
+ for (let start = 0; start < totalBytes; start += UPLOAD_PART_BYTES) {
3805
3805
  parts.push({
3806
3806
  partNumber: parts.length + 1,
3807
3807
  start,
3808
- end: Math.min(start + partSize, totalBytes)
3808
+ end: Math.min(start + UPLOAD_PART_BYTES, totalBytes)
3809
3809
  });
3810
3810
  }
3811
3811
  return parts;
@@ -3820,20 +3820,6 @@ var ACCOUNT_STORAGE_LIMIT_BYTES = {
3820
3820
  };
3821
3821
  var REPO_STORE_LIMIT_BYTES = 256 * 1024 * 1024;
3822
3822
  var MAX_DEPLOY_ASSET_FILE_BYTES = 25 * 1024 * 1024;
3823
- var DANGEROUS_MIME_TYPES = /* @__PURE__ */ new Set([
3824
- "text/html",
3825
- "application/xhtml+xml",
3826
- "image/svg+xml",
3827
- "application/xml",
3828
- "text/xml",
3829
- "application/javascript",
3830
- "text/javascript",
3831
- "application/ecmascript",
3832
- "text/ecmascript"
3833
- ]);
3834
- function activeContentMessage(mimeType) {
3835
- 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.`;
3836
- }
3837
3823
  function formatBytes(bytes) {
3838
3824
  if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GiB`;
3839
3825
  if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
@@ -8491,9 +8477,6 @@ async function uploadAppFile(baseUrl, token, appId, localPath, key, onProgress)
8491
8477
  throw new ApiError(oversizeMessage(size), 0, "too_large", path);
8492
8478
  }
8493
8479
  const declaredType = contentTypeFor(localPath);
8494
- if (DANGEROUS_MIME_TYPES.has(declaredType)) {
8495
- throw new ApiError(activeContentMessage(declaredType), 0, "active_content", path);
8496
- }
8497
8480
  if (size > UPLOAD_PART_BYTES) {
8498
8481
  return uploadInParts(baseUrl, token, appId, localPath, key, size, onProgress);
8499
8482
  }
@@ -8514,24 +8497,35 @@ function isRetryable(err) {
8514
8497
  async function uploadInParts(baseUrl, token, appId, localPath, key, size, onProgress) {
8515
8498
  const contentType = contentTypeFor(localPath);
8516
8499
  const initPath = filesPath(appId, `/multipart?key=${encodeKeyPath(key)}`);
8517
- const session = await readJson(
8518
- await request(baseUrl, token, initPath, {
8519
- method: "POST",
8520
- headers: { "Content-Type": "application/json" },
8521
- body: JSON.stringify({ name: basename3(localPath), mimeType: contentType, size })
8522
- }),
8523
- initPath
8524
- );
8525
- const query = `uploadKey=${encodeKeyPath(session.uploadKey)}&uploadId=${encodeURIComponent(session.uploadId)}`;
8526
- if (!Number.isSafeInteger(session.partSize) || session.partSize < 1) {
8500
+ const requestId = randomUUID2();
8501
+ const initBody = JSON.stringify({
8502
+ requestId,
8503
+ name: basename3(localPath),
8504
+ mimeType: contentType,
8505
+ size
8506
+ });
8507
+ const sendInit = () => request(baseUrl, token, initPath, {
8508
+ method: "POST",
8509
+ headers: { "Content-Type": "application/json" },
8510
+ body: initBody
8511
+ }).then((response) => readJson(response, initPath));
8512
+ let session;
8513
+ try {
8514
+ session = await sendInit();
8515
+ } catch (error) {
8516
+ if (!isRetryable(error)) throw error;
8517
+ session = await sendInit();
8518
+ }
8519
+ const query = `uploadKey=${encodeKeyPath(session.uploadKey)}&uploadId=${encodeURIComponent(session.uploadId)}&reservationId=${encodeURIComponent(session.reservationId)}`;
8520
+ if (!session.uploadId || !session.uploadKey || !session.reservationId || session.partSize !== UPLOAD_PART_BYTES) {
8527
8521
  throw new ApiError(
8528
- `The platform advertised an unusable part size (${session.partSize}).`,
8522
+ `The platform returned an unusable part size or multipart session (${session.partSize}).`,
8529
8523
  0,
8530
8524
  "invalid_response",
8531
8525
  initPath
8532
8526
  );
8533
8527
  }
8534
- const plan = planUploadParts(size, session.partSize);
8528
+ const plan = planUploadParts(size);
8535
8529
  const file = await openAsBlob(localPath, { type: contentType });
8536
8530
  const uploaded = [];
8537
8531
  try {
@@ -13628,7 +13622,7 @@ import { join as join23, resolve as resolve20 } from "path";
13628
13622
  // package.json
13629
13623
  var package_default = {
13630
13624
  name: "deepspace",
13631
- version: "0.19.5",
13625
+ version: "0.20.0",
13632
13626
  engines: {
13633
13627
  node: ">=22.15.0 <23 || >=24 <25 || >=26 <27"
13634
13628
  },