deepspace 0.13.0 → 0.15.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/dist/server.d.ts CHANGED
@@ -218,6 +218,33 @@ declare const UPLOAD_PART_BYTES: number;
218
218
  * R2 allows 10,000 parts, so this is far inside what the API permits.
219
219
  */
220
220
  declare const MAX_UPLOAD_PARTS: number;
221
+ /**
222
+ * Per-tier storage schedule, in bytes. Product semantics, not a deployment
223
+ * dial — the one table both storage allocations admit against:
224
+ *
225
+ * - the repo store (Git packs + retained bundles + deploy assets), admitted
226
+ * by the app's Repo DO (`platform/deploy-worker/src/vc/storage-quota.ts`)
227
+ * - the app-files allocation, admitted by the shared files handler
228
+ * (`server/utils/scoped-r2-files.ts`)
229
+ *
230
+ * Each allocation gets the full amount independently; they are different
231
+ * buckets with different writers (docs/platform/r2-storage.md). The
232
+ * customer-visible "storage" number is the app-files allocation — the repo
233
+ * store is platform bookkeeping that degrades (drops rollback retention)
234
+ * rather than surfacing here.
235
+ */
236
+ declare const APP_STORAGE_LIMIT_BYTES: {
237
+ readonly test: 0;
238
+ readonly free: number;
239
+ readonly starter: number;
240
+ readonly premium: number;
241
+ readonly admin: number;
242
+ };
243
+ /** The storage limit for a billing tier; unknown or missing tiers get free. */
244
+ declare function storageLimitForTier(tier: string | null | undefined): number;
245
+ /** The one storage-quota refusal sentence. `usedBytes` is net of any object
246
+ * the upload replaces, so an upsert is charged only for its growth. */
247
+ declare function storageQuotaMessage(incomingBytes: number, usedBytes: number, limitBytes: number): string;
221
248
  /**
222
249
  * The largest single file a deploy may ship as a static asset.
223
250
  *
@@ -290,8 +317,26 @@ interface ScopedR2Config {
290
317
  */
291
318
  requireAuthForMutations?: boolean;
292
319
  }
320
+ /**
321
+ * The storage-quota contract for one allocation, supplied per request by the
322
+ * mount. The handler is the enforcer; the mount only knows whose limit
323
+ * applies.
324
+ */
325
+ interface StorageAdmission {
326
+ /** The WHOLE allocation the quota governs — the app prefix, not the
327
+ * (possibly deeper) scope prefix a request resolved to. */
328
+ prefix: string;
329
+ /**
330
+ * Resolve the owner's limit in bytes. `null` means the lookup failed;
331
+ * writes then fail closed (503) rather than admitting unmetered storage,
332
+ * mirroring the repo store's `StorageBillingUnavailableError`.
333
+ */
334
+ limitBytes: () => Promise<number | null>;
335
+ }
293
336
  interface ScopedR2Auth {
294
337
  userId: string | null;
338
+ /** Absent = no quota on this mount (e.g. an app's own bucket). */
339
+ storage?: StorageAdmission;
295
340
  }
296
341
  type ScopedR2Handler = (request: Request, url: URL, bucket: R2Bucket, auth: ScopedR2Auth) => Promise<Response>;
297
342
  declare function isFilesVerbPath(method: string, subpath: string): boolean;
@@ -3631,7 +3676,7 @@ declare function createDeepSpaceAuth(config: DeepSpaceAuthConfig): better_auth.A
3631
3676
  id: string;
3632
3677
  organizationId: string;
3633
3678
  email: string;
3634
- role: "member" | "admin" | "owner";
3679
+ role: "admin" | "member" | "owner";
3635
3680
  status: better_auth_plugins.InvitationStatus;
3636
3681
  inviterId: string;
3637
3682
  expiresAt: Date;
@@ -3641,7 +3686,7 @@ declare function createDeepSpaceAuth(config: DeepSpaceAuthConfig): better_auth.A
3641
3686
  Member: {
3642
3687
  id: string;
3643
3688
  organizationId: string;
3644
- role: "member" | "admin" | "owner";
3689
+ role: "admin" | "member" | "owner";
3645
3690
  createdAt: Date;
3646
3691
  userId: string;
3647
3692
  teamId?: string | undefined | undefined;
@@ -3669,7 +3714,7 @@ declare function createDeepSpaceAuth(config: DeepSpaceAuthConfig): better_auth.A
3669
3714
  members: {
3670
3715
  id: string;
3671
3716
  organizationId: string;
3672
- role: "member" | "admin" | "owner";
3717
+ role: "admin" | "member" | "owner";
3673
3718
  createdAt: Date;
3674
3719
  userId: string;
3675
3720
  teamId?: string | undefined | undefined;
@@ -3684,7 +3729,7 @@ declare function createDeepSpaceAuth(config: DeepSpaceAuthConfig): better_auth.A
3684
3729
  id: string;
3685
3730
  organizationId: string;
3686
3731
  email: string;
3687
- role: "member" | "admin" | "owner";
3732
+ role: "admin" | "member" | "owner";
3688
3733
  status: better_auth_plugins.InvitationStatus;
3689
3734
  inviterId: string;
3690
3735
  expiresAt: Date;
@@ -4614,4 +4659,4 @@ declare class RefundError extends Error {
4614
4659
  constructor(message: string, status: number);
4615
4660
  }
4616
4661
 
4617
- export { AI_CHATS_SCHEMA, AI_MESSAGES_SCHEMA, ALLOWED_BINDING_TYPES, APP_ID_RE, APP_NAME_RULES, AUTO_PROVISIONABLE_TYPES, AUTO_PROVISION_SENTINEL, type ActionContext, type ActionHandler, type ActionResult, type ActionTools, type ApiWorkerEnv, type AppNameResolution, type AppNameValidation, type AuthWorkerEnv, BASE_USERS_SCHEMA, BUILT_IN_TOOLS, BaseRoom, CHANNELS_SCHEMA, CHANNEL_INVITATIONS_SCHEMA, CHANNEL_MEMBERS_SCHEMA, CONVERSATION_SCHEMAS, COST_RATES, CancelSubscriptionError, type CancelSubscriptionOpts, type CancelSubscriptionResult, CanvasRoom, type CanvasShape, type ChatContextConfig, type ChatMessageRow, type ChatRow, type ChatTurn, type CollectionPermissionSummary, type CollectionSchema, type ColumnDefinition, type ColumnInterpretation, type ComposioToolsOptions, type ConvMemberData, type ConvMessageData, type ConvReactionData, type ConvReadCursorData, type ConvVoteData, type ConversationStateData, type CronContext, type CronExecution, CronRoom, type CronRoomConfig, type CronTask, type CustomBinding, type CustomBindingManifest, DEFAULT_CONTEXT_CONFIG, DEFAULT_DO_MANIFEST, DEFAULT_QUERY_LIMIT, DEPLOYED_APPS_DEFAULTS, DIRECTORY_SCHEMAS, type DOBindings, type DOManifest, type DOManifestEntry, type DebugApiContext, type DeepSpaceAIEnv, type DeepSpaceAIOptions, type DeepSpaceAgentDiagnosticContext, DeepSpaceAgentModelError, DeepSpaceAgentProfileError, type DeepSpaceAgentStream, type DeepSpaceAgentStreamOptions, type DeepSpaceAuth, type DeepSpaceAuthConfig, type DirectoryCommunityData, type DirectoryConversationData, type DirectoryMembershipData, type DirectoryPostData, type DoMigrationDirective, type DoMigrationPlan, type ExistingDOBinding, GLOBAL_DO_TYPES, GLOBAL_DO_TYPE_NAMES, type GameInput, GameRoom, type GameRoomConfig, type GetActionData, type GlobalDOType, type Job, type JobContext, JobRoom, type JobRoomConfig, type JobStatus, type JwtClaims, type JwtVerifierConfig, MAX_APP_FILE_BYTES, MAX_DEPLOY_ASSET_FILE_BYTES, MAX_UPLOAD_PARTS, MAX_UPLOAD_REQUEST_BYTES, MESSAGES_SCHEMA, type MutateActionData, type PermissionAnalysis, type PermissionContext, type PermissionLevel, type PermissionSource, type PlanInfo, type PlatformWorkerEnv, type Player, type PrefixResult, type PresencePeer, PresenceRoom, type QueryActionData, REACTIONS_SCHEMA, READ_RECEIPTS_SCHEMA, REGISTRY_INSTANCE, RESERVED_BINDING_NAMES, RESERVED_COLLECTION_NAMES, RESOURCE_ID_RE, type RecordContext, RecordRoom, type RecordRoomConfig, RefundError, type RefundOpts, type RefundResult, RegistryClientError, type ResolvedColumn, type ResolvedPermission, type RolePermissions, type RunMigrationsResult, STRICT_APP_ID_RE, SYSTEM_COLLECTION_SCHEMAS, SYSTEM_MANAGED_COLUMNS, SchemaRegistry, type ScopeContext, type ScopedR2Auth, type ScopedR2Config, type ScopedR2Handler, type ScreenshotEnv, type ScreenshotResult, type SharedConnection, type StoredRecord, SubscriptionAuthError, type SubscriptionContext, type SubscriptionRead, SubscriptionRequiredError, type SubscriptionStatus, type Summarizer, TEST_ACCOUNT_EMAIL_SUFFIX, type TokenDebugInfo, type ToolResult, type ToolSchema, type ToolsApiContext, UPLOAD_PART_BYTES, USERS_COLUMNS, type User, type UserAttachment, type UserContext, VOTING_SCHEMAS, type ValidationError, type VerifiedAuth, type VerifyOutcome, type VerifyResult, type Viewport, WORKSPACE_SCHEMAS, type YjsContext, YjsRoom, analyzePermissions, apiWorkerFetch, appendMessage, applyAiToolDefaults, applySlidingWindow, authWorkerFetch, bindingManifestFromOutputConfig, broadcastChange, broadcastYjsUpdate, buildCronContext, buildTableSelect, buildUiParts, callRegistry, canCreate, canDelete, canRead, canUpdate, cancelSubscription, capToolResultSize, captureScreenshot, checkFieldPermissions, coerceValue, collectionTableName, columnId, composioTools, computeDoMigration, computeHmacHex, createChat, createDeepSpaceAI, createDeepSpaceAuth, createScopedR2Handler, dataToColumnValues, decodeJwtPayload, deepSpaceAgentErrorSummary, deleteChatCascade, deleteRecord, enqueueJob, executeQuery, formatBytes, getAllUsers, getChat, getGlobalDOSchemas, getGlobalDOType, getOrCreateYjsDoc, getRecord, getRolePermissions, getSubscription, getUser, getYjsDocKey, handleApiRequest, handleDelete, handlePut, handleSetRole, handleSubscribe, handleToolsRequest, handleUnsubscribe, handleUserList, handleUserUpdate, handleYjsBinaryMessage, handleYjsJoin, handleYjsLeave, isAutoProvision, isFilesVerbPath, isOwner, isTestAccountClaims, isTestAccountEmail, isTestAccountTier, lintSchema, loadMessages, makeDefaultSummarizer, matchOrigin, meterAi, meterUsage, meterVectorize, noopPermissionContext, parseQuotaLimit, platformCorsPatterns, platformWorkerFetch, prepareMessagesWithCompaction, priceBindingUsageEvent, putRecord, readRecord, recordMatchesSubscription, refundInvoice, registerUser, registryErrorJson, registryStub, requireSubscription, resolveAppName, resolveColumn, rowToData, runMigrations, saveYjsDoc, streamDeepSpaceAgent, timingSafeEqualHex, timingSafeEqualStrings, totalChars, truncateOldToolResults, turnsToCoreMessages, unwrapToolOutput, updateChat, validateAppName, validateBindingManifest, validateDoManifest, verifyJwt, workspaceAccountsSchema, workspaceContentSharesSchema, workspaceEmailHandlesSchema, workspaceFormResponsesSchema, workspacePeopleSchema, workspaceProjectsSchema, workspaceTagsSchema, workspaceTasksSchema, workspaceTeamMembersSchema, workspaceTeamsSchema, workspaceTransactionsSchema };
4662
+ export { AI_CHATS_SCHEMA, AI_MESSAGES_SCHEMA, ALLOWED_BINDING_TYPES, APP_ID_RE, APP_NAME_RULES, APP_STORAGE_LIMIT_BYTES, AUTO_PROVISIONABLE_TYPES, AUTO_PROVISION_SENTINEL, type ActionContext, type ActionHandler, type ActionResult, type ActionTools, type ApiWorkerEnv, type AppNameResolution, type AppNameValidation, type AuthWorkerEnv, BASE_USERS_SCHEMA, BUILT_IN_TOOLS, BaseRoom, CHANNELS_SCHEMA, CHANNEL_INVITATIONS_SCHEMA, CHANNEL_MEMBERS_SCHEMA, CONVERSATION_SCHEMAS, COST_RATES, CancelSubscriptionError, type CancelSubscriptionOpts, type CancelSubscriptionResult, CanvasRoom, type CanvasShape, type ChatContextConfig, type ChatMessageRow, type ChatRow, type ChatTurn, type CollectionPermissionSummary, type CollectionSchema, type ColumnDefinition, type ColumnInterpretation, type ComposioToolsOptions, type ConvMemberData, type ConvMessageData, type ConvReactionData, type ConvReadCursorData, type ConvVoteData, type ConversationStateData, type CronContext, type CronExecution, CronRoom, type CronRoomConfig, type CronTask, type CustomBinding, type CustomBindingManifest, DEFAULT_CONTEXT_CONFIG, DEFAULT_DO_MANIFEST, DEFAULT_QUERY_LIMIT, DEPLOYED_APPS_DEFAULTS, DIRECTORY_SCHEMAS, type DOBindings, type DOManifest, type DOManifestEntry, type DebugApiContext, type DeepSpaceAIEnv, type DeepSpaceAIOptions, type DeepSpaceAgentDiagnosticContext, DeepSpaceAgentModelError, DeepSpaceAgentProfileError, type DeepSpaceAgentStream, type DeepSpaceAgentStreamOptions, type DeepSpaceAuth, type DeepSpaceAuthConfig, type DirectoryCommunityData, type DirectoryConversationData, type DirectoryMembershipData, type DirectoryPostData, type DoMigrationDirective, type DoMigrationPlan, type ExistingDOBinding, GLOBAL_DO_TYPES, GLOBAL_DO_TYPE_NAMES, type GameInput, GameRoom, type GameRoomConfig, type GetActionData, type GlobalDOType, type Job, type JobContext, JobRoom, type JobRoomConfig, type JobStatus, type JwtClaims, type JwtVerifierConfig, MAX_APP_FILE_BYTES, MAX_DEPLOY_ASSET_FILE_BYTES, MAX_UPLOAD_PARTS, MAX_UPLOAD_REQUEST_BYTES, MESSAGES_SCHEMA, type MutateActionData, type PermissionAnalysis, type PermissionContext, type PermissionLevel, type PermissionSource, type PlanInfo, type PlatformWorkerEnv, type Player, type PrefixResult, type PresencePeer, PresenceRoom, type QueryActionData, REACTIONS_SCHEMA, READ_RECEIPTS_SCHEMA, REGISTRY_INSTANCE, RESERVED_BINDING_NAMES, RESERVED_COLLECTION_NAMES, RESOURCE_ID_RE, type RecordContext, RecordRoom, type RecordRoomConfig, RefundError, type RefundOpts, type RefundResult, RegistryClientError, type ResolvedColumn, type ResolvedPermission, type RolePermissions, type RunMigrationsResult, STRICT_APP_ID_RE, SYSTEM_COLLECTION_SCHEMAS, SYSTEM_MANAGED_COLUMNS, SchemaRegistry, type ScopeContext, type ScopedR2Auth, type ScopedR2Config, type ScopedR2Handler, type ScreenshotEnv, type ScreenshotResult, type SharedConnection, type StorageAdmission, type StoredRecord, SubscriptionAuthError, type SubscriptionContext, type SubscriptionRead, SubscriptionRequiredError, type SubscriptionStatus, type Summarizer, TEST_ACCOUNT_EMAIL_SUFFIX, type TokenDebugInfo, type ToolResult, type ToolSchema, type ToolsApiContext, UPLOAD_PART_BYTES, USERS_COLUMNS, type User, type UserAttachment, type UserContext, VOTING_SCHEMAS, type ValidationError, type VerifiedAuth, type VerifyOutcome, type VerifyResult, type Viewport, WORKSPACE_SCHEMAS, type YjsContext, YjsRoom, analyzePermissions, apiWorkerFetch, appendMessage, applyAiToolDefaults, applySlidingWindow, authWorkerFetch, bindingManifestFromOutputConfig, broadcastChange, broadcastYjsUpdate, buildCronContext, buildTableSelect, buildUiParts, callRegistry, canCreate, canDelete, canRead, canUpdate, cancelSubscription, capToolResultSize, captureScreenshot, checkFieldPermissions, coerceValue, collectionTableName, columnId, composioTools, computeDoMigration, computeHmacHex, createChat, createDeepSpaceAI, createDeepSpaceAuth, createScopedR2Handler, dataToColumnValues, decodeJwtPayload, deepSpaceAgentErrorSummary, deleteChatCascade, deleteRecord, enqueueJob, executeQuery, formatBytes, getAllUsers, getChat, getGlobalDOSchemas, getGlobalDOType, getOrCreateYjsDoc, getRecord, getRolePermissions, getSubscription, getUser, getYjsDocKey, handleApiRequest, handleDelete, handlePut, handleSetRole, handleSubscribe, handleToolsRequest, handleUnsubscribe, handleUserList, handleUserUpdate, handleYjsBinaryMessage, handleYjsJoin, handleYjsLeave, isAutoProvision, isFilesVerbPath, isOwner, isTestAccountClaims, isTestAccountEmail, isTestAccountTier, lintSchema, loadMessages, makeDefaultSummarizer, matchOrigin, meterAi, meterUsage, meterVectorize, noopPermissionContext, parseQuotaLimit, platformCorsPatterns, platformWorkerFetch, prepareMessagesWithCompaction, priceBindingUsageEvent, putRecord, readRecord, recordMatchesSubscription, refundInvoice, registerUser, registryErrorJson, registryStub, requireSubscription, resolveAppName, resolveColumn, rowToData, runMigrations, saveYjsDoc, storageLimitForTier, storageQuotaMessage, streamDeepSpaceAgent, timingSafeEqualHex, timingSafeEqualStrings, totalChars, truncateOldToolResults, turnsToCoreMessages, unwrapToolOutput, updateChat, validateAppName, validateBindingManifest, validateDoManifest, verifyJwt, workspaceAccountsSchema, workspaceContentSharesSchema, workspaceEmailHandlesSchema, workspaceFormResponsesSchema, workspacePeopleSchema, workspaceProjectsSchema, workspaceTagsSchema, workspaceTasksSchema, workspaceTeamMembersSchema, workspaceTeamsSchema, workspaceTransactionsSchema };
package/dist/server.js CHANGED
@@ -180,6 +180,23 @@ var MAX_UPLOAD_REQUEST_BYTES = 25 * 1024 * 1024;
180
180
  var UPLOAD_PART_BYTES = 20 * 1024 * 1024;
181
181
  var MAX_UPLOAD_PARTS = Math.ceil(MAX_APP_FILE_BYTES / UPLOAD_PART_BYTES);
182
182
  var MAX_BASE64_UPLOAD_BYTES = Math.floor(MAX_UPLOAD_REQUEST_BYTES * 3 / 4);
183
+ var APP_STORAGE_LIMIT_BYTES = {
184
+ test: 0,
185
+ free: 128 * 1024 * 1024,
186
+ starter: 512 * 1024 * 1024,
187
+ premium: 2 * 1024 * 1024 * 1024,
188
+ admin: 10 * 1024 * 1024 * 1024
189
+ };
190
+ function storageLimitForTier(tier) {
191
+ if (tier && Object.hasOwn(APP_STORAGE_LIMIT_BYTES, tier)) {
192
+ return APP_STORAGE_LIMIT_BYTES[tier];
193
+ }
194
+ return APP_STORAGE_LIMIT_BYTES.free;
195
+ }
196
+ function storageQuotaMessage(incomingBytes, usedBytes, limitBytes) {
197
+ const free = Math.max(0, limitBytes - usedBytes);
198
+ return `This upload needs ${formatBytes(incomingBytes)} but the app's file storage has ${formatBytes(free)} free (${formatBytes(usedBytes)} of ${formatBytes(limitBytes)} used). Delete files with \`deepspace app files rm <key>\`, or upgrade the owner's plan.`;
199
+ }
183
200
  var MAX_DEPLOY_ASSET_FILE_BYTES = 25 * 1024 * 1024;
184
201
  var DANGEROUS_MIME_TYPES = /* @__PURE__ */ new Set([
185
202
  "text/html",
@@ -266,6 +283,52 @@ function declaredLength(request, what) {
266
283
  function isKeyWithinPrefix(key, prefix) {
267
284
  return key.startsWith(prefix);
268
285
  }
286
+ async function allocationUsage(bucket, prefix) {
287
+ let total = 0;
288
+ let cursor;
289
+ do {
290
+ const page = await bucket.list({ prefix, cursor, limit: 1e3 });
291
+ for (const object of page.objects) total += object.size;
292
+ cursor = page.truncated ? page.cursor : void 0;
293
+ } while (cursor !== void 0);
294
+ return total;
295
+ }
296
+ async function admitStorage(bucket, storage, incomingBytes, replacesKey) {
297
+ if (!storage) return null;
298
+ const limit = await storage.limitBytes();
299
+ if (limit === null) {
300
+ return fail(
301
+ 503,
302
+ "The storage limit for this app could not be verified; nothing was uploaded. Retry shortly.",
303
+ "storage_limit_unavailable"
304
+ );
305
+ }
306
+ const stored = await allocationUsage(bucket, storage.prefix);
307
+ const replaced = replacesKey === null ? 0 : (await bucket.head(replacesKey))?.size ?? 0;
308
+ const used = stored - replaced;
309
+ if (used + incomingBytes <= limit) return null;
310
+ return fail(409, storageQuotaMessage(incomingBytes, used, limit), "storage_quota_exceeded", {
311
+ usedBytes: used,
312
+ limitBytes: limit
313
+ });
314
+ }
315
+ async function admitAssembledObject(bucket, storage, key, objectBytes) {
316
+ if (!storage) return null;
317
+ const limit = await storage.limitBytes();
318
+ if (limit === null) {
319
+ console.error("[files:quota] limit unavailable at complete; keeping", key);
320
+ return null;
321
+ }
322
+ const stored = await allocationUsage(bucket, storage.prefix);
323
+ if (stored <= limit) return null;
324
+ await bucket.delete(key).catch(() => void 0);
325
+ return fail(
326
+ 409,
327
+ `${storageQuotaMessage(objectBytes, stored - objectBytes, limit)} The assembled upload was not kept.`,
328
+ "storage_quota_exceeded",
329
+ { usedBytes: stored - objectBytes, limitBytes: limit }
330
+ );
331
+ }
269
332
  function anchorKey(raw, prefix) {
270
333
  const safe = sanitizeSubpath(raw);
271
334
  if (safe === null) return fail(400, "Invalid key: traversal not allowed", "invalid_key");
@@ -355,10 +418,10 @@ function createScopedR2Handler(config) {
355
418
  await request.body?.cancel().catch(() => void 0);
356
419
  return fail(401, "Authentication required", "unauthorized");
357
420
  }
358
- return subpath === "upload" ? handleUpload(request, url, bucket, prefix, auth.userId, scope) : handleMultipart(request, url, bucket, prefix, auth.userId, scope, subpath);
421
+ return subpath === "upload" ? handleUpload(request, url, bucket, prefix, auth, scope) : handleMultipart(request, url, bucket, prefix, auth, scope, subpath);
359
422
  }
360
423
  if (!subpath && request.method === "GET") {
361
- return handleList(bucket, prefix, url, scope);
424
+ return handleList(bucket, prefix, url, scope, auth);
362
425
  }
363
426
  if (subpath && request.method === "GET") {
364
427
  if (!isKeyWithinPrefix(subpath, prefix)) {
@@ -392,7 +455,7 @@ function createScopedR2Handler(config) {
392
455
  return Response.json({ error: "Method not allowed" }, { status: 405, headers: CORS_HEADERS });
393
456
  };
394
457
  }
395
- async function handleUpload(request, url, bucket, prefix, userId, scope) {
458
+ async function handleUpload(request, url, bucket, prefix, auth, scope) {
396
459
  try {
397
460
  const declared = overRequestBound(Number(request.headers.get("content-length")), 8 * 1024);
398
461
  if (declared) return declared;
@@ -423,9 +486,12 @@ async function handleUpload(request, url, bucket, prefix, userId, scope) {
423
486
  if (mime instanceof Response) return mime;
424
487
  const located = locateUpload(url, prefix, fileName);
425
488
  if (located instanceof Response) return located;
489
+ const replacesKey = url.searchParams.get("key") === null ? null : located.key;
490
+ const overQuota = await admitStorage(bucket, auth.storage, fileData.byteLength, replacesKey);
491
+ if (overQuota) return overQuota;
426
492
  await bucket.put(located.key, fileData, {
427
493
  httpMetadata: { contentType: mime.mimeType },
428
- customMetadata: fileMetadata(fileName, userId)
494
+ customMetadata: fileMetadata(fileName, auth.userId)
429
495
  });
430
496
  return storedFileJson(located.key, fileName, url, scope);
431
497
  } catch (err) {
@@ -500,17 +566,17 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
500
566
  }
501
567
  return { ok: true, part: uploaded.value };
502
568
  }
503
- async function handleMultipart(request, url, bucket, prefix, userId, scope, subpath) {
569
+ async function handleMultipart(request, url, bucket, prefix, auth, scope, subpath) {
504
570
  if (subpath === "multipart" && request.method === "POST") {
505
- return multipartInit(request, url, bucket, prefix, userId);
571
+ return multipartInit(request, url, bucket, prefix, auth);
506
572
  }
507
573
  if (subpath === "multipart/part") return multipartPart(request, url, bucket, prefix);
508
574
  if (subpath === "multipart/complete") {
509
- return multipartComplete(request, url, bucket, prefix, scope);
575
+ return multipartComplete(request, url, bucket, prefix, auth, scope);
510
576
  }
511
577
  return multipartAbort(url, bucket, prefix);
512
578
  }
513
- async function multipartInit(request, url, bucket, prefix, userId) {
579
+ async function multipartInit(request, url, bucket, prefix, auth) {
514
580
  const body = await readControlJson(request);
515
581
  if (body instanceof Response) return body;
516
582
  const size = body.size;
@@ -525,11 +591,14 @@ async function multipartInit(request, url, bucket, prefix, userId) {
525
591
  if (mime instanceof Response) return mime;
526
592
  const located = locateUpload(url, prefix, name);
527
593
  if (located instanceof Response) return located;
594
+ const replacesKey = url.searchParams.get("key") === null ? null : located.key;
595
+ const overQuota = await admitStorage(bucket, auth.storage, size, replacesKey);
596
+ if (overQuota) return overQuota;
528
597
  let upload;
529
598
  try {
530
599
  upload = await bucket.createMultipartUpload(located.key, {
531
600
  httpMetadata: { contentType: mime.mimeType },
532
- customMetadata: fileMetadata(name, userId)
601
+ customMetadata: fileMetadata(name, auth.userId)
533
602
  });
534
603
  } catch (error) {
535
604
  return multipartFailure(error);
@@ -579,7 +648,7 @@ async function multipartPart(request, url, bucket, prefix) {
579
648
  { headers: CORS_HEADERS }
580
649
  );
581
650
  }
582
- async function multipartComplete(request, url, bucket, prefix, scope) {
651
+ async function multipartComplete(request, url, bucket, prefix, auth, scope) {
583
652
  const body = await readControlJson(request);
584
653
  if (body instanceof Response) return body;
585
654
  const session = resolveSession(url, prefix);
@@ -613,6 +682,8 @@ async function multipartComplete(request, url, bucket, prefix, scope) {
613
682
  await bucket.delete(session.key).catch(() => void 0);
614
683
  return tooBig;
615
684
  }
685
+ const overQuota = await admitAssembledObject(bucket, auth.storage, session.key, object.size);
686
+ if (overQuota) return overQuota;
616
687
  const name = object.customMetadata?.originalName ?? session.key.split("/").pop() ?? "file";
617
688
  return storedFileJson(session.key, name, url, scope);
618
689
  }
@@ -627,7 +698,7 @@ async function multipartAbort(url, bucket, prefix) {
627
698
  }
628
699
  return Response.json({ success: true, aborted: true }, { headers: CORS_HEADERS });
629
700
  }
630
- async function handleList(bucket, prefix, url, scope) {
701
+ async function handleList(bucket, prefix, url, scope, auth) {
631
702
  const userPrefix = url.searchParams.get("prefix") || "";
632
703
  const listPrefix = `${prefix}${userPrefix}`;
633
704
  const limit = parseInt(url.searchParams.get("limit") || "100", 10);
@@ -643,7 +714,16 @@ async function handleList(bucket, prefix, url, scope) {
643
714
  ...obj.customMetadata
644
715
  };
645
716
  });
646
- return Response.json({ files, truncated: listed.truncated }, { headers: CORS_HEADERS });
717
+ let storage;
718
+ if (auth.storage) {
719
+ storage = { usedBytes: await allocationUsage(bucket, auth.storage.prefix) };
720
+ const limitBytes = await auth.storage.limitBytes();
721
+ if (limitBytes !== null) storage.limitBytes = limitBytes;
722
+ }
723
+ return Response.json(
724
+ { files, truncated: listed.truncated, ...storage ? { storage } : {} },
725
+ { headers: CORS_HEADERS }
726
+ );
647
727
  }
648
728
  async function handleDownload(bucket, key, scope) {
649
729
  const object = await bucket.get(key);
@@ -7909,6 +7989,7 @@ export {
7909
7989
  ALLOWED_BINDING_TYPES,
7910
7990
  APP_ID_RE,
7911
7991
  APP_NAME_RULES,
7992
+ APP_STORAGE_LIMIT_BYTES,
7912
7993
  AUTO_PROVISIONABLE_TYPES,
7913
7994
  AUTO_PROVISION_SENTINEL,
7914
7995
  BASE_USERS_SCHEMA,
@@ -8053,6 +8134,8 @@ export {
8053
8134
  rowToData,
8054
8135
  runMigrations,
8055
8136
  saveYjsDoc,
8137
+ storageLimitForTier,
8138
+ storageQuotaMessage,
8056
8139
  streamDeepSpaceAgent,
8057
8140
  timingSafeEqualHex,
8058
8141
  timingSafeEqualStrings,