deepspace 0.13.0 → 0.14.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 +9 -0
- package/dist/cli.js +97 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +50 -5
- package/dist/server.js +95 -12
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +46 -1
- package/dist/worker.js +95 -12
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/dist/worker.d.ts
CHANGED
|
@@ -2947,6 +2947,33 @@ declare const UPLOAD_PART_BYTES: number;
|
|
|
2947
2947
|
* R2 allows 10,000 parts, so this is far inside what the API permits.
|
|
2948
2948
|
*/
|
|
2949
2949
|
declare const MAX_UPLOAD_PARTS: number;
|
|
2950
|
+
/**
|
|
2951
|
+
* Per-tier storage schedule, in bytes. Product semantics, not a deployment
|
|
2952
|
+
* dial — the one table both storage allocations admit against:
|
|
2953
|
+
*
|
|
2954
|
+
* - the repo store (Git packs + retained bundles + deploy assets), admitted
|
|
2955
|
+
* by the app's Repo DO (`platform/deploy-worker/src/vc/storage-quota.ts`)
|
|
2956
|
+
* - the app-files allocation, admitted by the shared files handler
|
|
2957
|
+
* (`server/utils/scoped-r2-files.ts`)
|
|
2958
|
+
*
|
|
2959
|
+
* Each allocation gets the full amount independently; they are different
|
|
2960
|
+
* buckets with different writers (docs/platform/r2-storage.md). The
|
|
2961
|
+
* customer-visible "storage" number is the app-files allocation — the repo
|
|
2962
|
+
* store is platform bookkeeping that degrades (drops rollback retention)
|
|
2963
|
+
* rather than surfacing here.
|
|
2964
|
+
*/
|
|
2965
|
+
declare const APP_STORAGE_LIMIT_BYTES: {
|
|
2966
|
+
readonly test: 0;
|
|
2967
|
+
readonly free: number;
|
|
2968
|
+
readonly starter: number;
|
|
2969
|
+
readonly premium: number;
|
|
2970
|
+
readonly admin: number;
|
|
2971
|
+
};
|
|
2972
|
+
/** The storage limit for a billing tier; unknown or missing tiers get free. */
|
|
2973
|
+
declare function storageLimitForTier(tier: string | null | undefined): number;
|
|
2974
|
+
/** The one storage-quota refusal sentence. `usedBytes` is net of any object
|
|
2975
|
+
* the upload replaces, so an upsert is charged only for its growth. */
|
|
2976
|
+
declare function storageQuotaMessage(incomingBytes: number, usedBytes: number, limitBytes: number): string;
|
|
2950
2977
|
/**
|
|
2951
2978
|
* The largest single file a deploy may ship as a static asset.
|
|
2952
2979
|
*
|
|
@@ -3019,8 +3046,26 @@ interface ScopedR2Config {
|
|
|
3019
3046
|
*/
|
|
3020
3047
|
requireAuthForMutations?: boolean;
|
|
3021
3048
|
}
|
|
3049
|
+
/**
|
|
3050
|
+
* The storage-quota contract for one allocation, supplied per request by the
|
|
3051
|
+
* mount. The handler is the enforcer; the mount only knows whose limit
|
|
3052
|
+
* applies.
|
|
3053
|
+
*/
|
|
3054
|
+
interface StorageAdmission {
|
|
3055
|
+
/** The WHOLE allocation the quota governs — the app prefix, not the
|
|
3056
|
+
* (possibly deeper) scope prefix a request resolved to. */
|
|
3057
|
+
prefix: string;
|
|
3058
|
+
/**
|
|
3059
|
+
* Resolve the owner's limit in bytes. `null` means the lookup failed;
|
|
3060
|
+
* writes then fail closed (503) rather than admitting unmetered storage,
|
|
3061
|
+
* mirroring the repo store's `StorageBillingUnavailableError`.
|
|
3062
|
+
*/
|
|
3063
|
+
limitBytes: () => Promise<number | null>;
|
|
3064
|
+
}
|
|
3022
3065
|
interface ScopedR2Auth {
|
|
3023
3066
|
userId: string | null;
|
|
3067
|
+
/** Absent = no quota on this mount (e.g. an app's own bucket). */
|
|
3068
|
+
storage?: StorageAdmission;
|
|
3024
3069
|
}
|
|
3025
3070
|
type ScopedR2Handler = (request: Request, url: URL, bucket: R2Bucket, auth: ScopedR2Auth) => Promise<Response>;
|
|
3026
3071
|
declare function isFilesVerbPath(method: string, subpath: string): boolean;
|
|
@@ -5237,4 +5282,4 @@ interface AppLogsResponse {
|
|
|
5237
5282
|
declare const APP_LOG_EVENT_KEYS: readonly ["id", "timestamp", "level", "eventType", "message", "source", "outcome", "request", "exception"];
|
|
5238
5283
|
type AppLogEventKey = (typeof APP_LOG_EVENT_KEYS)[number];
|
|
5239
5284
|
|
|
5240
|
-
export { AI_CHATS_SCHEMA, AI_MESSAGES_SCHEMA, ALLOWED_BINDING_TYPES, APP_ID_RE, APP_LOG_EVENT_KEYS, APP_NAME_RULES, AUTO_PROVISIONABLE_TYPES, AUTO_PROVISION_SENTINEL, type ActionContext, type ActionHandler, type ActionResult, type ActionTools, type ApiWorkerEnv, type AppLogEvent, type AppLogEventKey, type AppLogsResponse, type AppMigrationIdsValidation, type AppNameResolution, type AppNameValidation, type AuthWorkerEnv, Awareness, type AwarenessState, type AwarenessStates, BASE_USERS_SCHEMA, BUILT_IN_TOOLS, type BaseMessage, BaseRoom, CANONICAL_APP_IDENTITY_MIGRATION_ID, CHANNELS_SCHEMA, CHANNEL_INVITATIONS_SCHEMA, CHANNEL_MEMBERS_SCHEMA, CLIENT_LOG_MARKER, CONVERSATION_SCHEMAS, COST_RATES, CanvasRoom, type CanvasShape, type ChatContextConfig, type ChatMessageRow, type ChatRow, type ChatTurn, type ClientErrorKind, type ClientErrorReport, type ClientMessage, type CollectionPermissionSummary, type CollectionSchema, type ColumnDefinition, type ColumnInterpretation, type ComposioToolsOptions, type ConnectionAttachment, 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, DEEPSPACE_AGENT_PROFILES, DEEPSPACE_AI_DEFAULTS, DEEPSPACE_AI_MODELS, DEEPSPACE_MODEL_CATALOG_PROVENANCE, DEFAULT_CONTEXT_CONFIG, DEFAULT_DO_MANIFEST, DEFAULT_QUERY_LIMIT, DEPLOYED_APPS_DEFAULTS, DIRECTORY_SCHEMAS, type DOBindings, type DOManifest, type DOManifestEntry, type DeepSpaceAIEnv, type DeepSpaceAIModel, type DeepSpaceAIModelId, type DeepSpaceAIOptions, type DeepSpaceAIProvider, type DeepSpaceAgentDiagnosticContext, DeepSpaceAgentModelError, type DeepSpaceAgentProfile, DeepSpaceAgentProfileError, type DeepSpaceAgentProfileId, type DeepSpaceAgentStream, type DeepSpaceAgentStreamOptions, type DeepSpaceAgentSupport, type DeepSpaceAuth, type DeepSpaceAuthConfig, type DeepSpaceDocumentationRouteEnv, type DeepSpaceDocumentationRouteOptions, 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 HandlerContext, type Job, type JobContext, JobRoom, type JobRoomConfig, type JobStatus, type JwtClaims, type JwtVerifierConfig, LOG_LEVELS, type LogLevel, MAX_APP_FILE_BYTES, MAX_DEPLOY_ASSET_FILE_BYTES, MAX_UPLOAD_PARTS, MAX_UPLOAD_REQUEST_BYTES, MESSAGES_SCHEMA, MSG, MSG_AWARENESS, MSG_SYNC, MSG_SYNC_STEP1, MSG_SYNC_STEP2, MSG_SYNC_UPDATE, MSG_YJS_AWARENESS, MSG_YJS_SYNC, type MessageHandlers, type MsgType, type MutateActionData, type PermissionAnalysis, type PermissionContext, type PermissionLevel, type PermissionSource, type PlatformWorkerEnv, type Player, type PrefixResult, type PresencePeer, PresenceRoom, type ProtocolMessage, type QueryActionData, REACTIONS_SCHEMA, READ_RECEIPTS_SCHEMA, REGISTRY_INSTANCE, RESERVED_BINDING_NAMES, RESERVED_COLLECTION_NAMES, RESOURCE_ID_RE, ROLE_ADMIN, ROLE_ANONYMOUS, ROLE_DEFAULT, RecordRoom, type RecordRoomConfig, RegistryClientError, type ResolvedColumn, type ResolvedDeepSpaceAgentModel, type ResolvedPermission, type RolePermissions, type RunMigrationsResult, STRICT_APP_ID_RE, SYSTEM_COLLECTIONS, SYSTEM_MANAGED_COLUMNS, SchemaRegistry, type ScopeContext, type ScopedR2Auth, type ScopedR2Config, type ScopedR2Handler, type ScreenshotEnv, type ScreenshotResult, type ServerMessage, type SharedConnection, type StoredRecord, type Summarizer, type SyncResult, TEST_ACCOUNT_EMAIL_SUFFIX, type TokenDebugInfo, type ToolResult, type ToolSchema, UPLOAD_PART_BYTES, USERS_COLUMNS, type User, type UserAttachment, VOTING_SCHEMAS, type ValidationError, type VerifiedAuth, type VerifyOutcome, type VerifyResult, type Viewport, WORKSPACE_SCHEMAS, YjsRoom, analyzePermissions, apiWorkerFetch, appendMessage, applyAiToolDefaults, applySlidingWindow, authWorkerFetch, bindingManifestFromOutputConfig, buildCronContext, buildTableSelect, buildUiParts, callRegistry, canCreate, canDelete, canRead, canUpdate, capToolResultSize, captureScreenshot, checkFieldPermissions, clientBuild, coerceValue, collectionTableName, columnId, composioTools, computeDoMigration, computeHmacHex, createChat, createDecoder, createDeepSpaceAI, createDeepSpaceAuth, createEncoder, createScopedR2Handler, dataToColumnValues, decodeJwtPayload, deepSpaceAgentErrorSummary, deleteChatCascade, dispatch, encode, encodeAwarenessMessage, encodeSyncStep1, encodeSyncStep2, encodeUpdate, enqueueJob, formatBytes, getChat, getDeepSpaceAIModel, getGlobalDOSchemas, getGlobalDOType, getMessageType, getRolePermissions, handleAwarenessMessage, handleClientErrorReport, handleSyncMessage, hasContent, isAutoProvision, isFilesVerbPath, isOwner, isTestAccountClaims, isTestAccountEmail, isTestAccountTier, lintSchema, listDeepSpaceAgentModels, loadMessages, makeDefaultSummarizer, matchOrigin, meterAi, meterUsage, meterVectorize, noopPermissionContext, parseQuotaLimit, platformCorsPatterns, platformWorkerFetch, prepareMessagesWithCompaction, priceBindingUsageEvent, readVarUint, readVarUint8Array, registerClientErrorRoute, registerDeepSpaceDocumentation, registryErrorJson, registryStub, resolveAppName, resolveColumn, resolveDeepSpaceAgentModel, rowToData, runMigrations, serverBuild, streamDeepSpaceAgent, timingSafeEqualHex, timingSafeEqualStrings, toUint8Array, totalChars, truncateOldToolResults, turnsToCoreMessages, unwrapToolOutput, updateChat, validateAppMigrationIds, validateAppName, validateBindingManifest, validateDoManifest, verifyJwt, workspaceAccountsSchema, workspaceContentSharesSchema, workspaceEmailHandlesSchema, workspaceFormResponsesSchema, workspacePeopleSchema, workspaceProjectsSchema, workspaceTagsSchema, workspaceTasksSchema, workspaceTeamMembersSchema, workspaceTeamsSchema, workspaceTransactionsSchema, writeVarUint, writeVarUint8Array };
|
|
5285
|
+
export { AI_CHATS_SCHEMA, AI_MESSAGES_SCHEMA, ALLOWED_BINDING_TYPES, APP_ID_RE, APP_LOG_EVENT_KEYS, APP_NAME_RULES, APP_STORAGE_LIMIT_BYTES, AUTO_PROVISIONABLE_TYPES, AUTO_PROVISION_SENTINEL, type ActionContext, type ActionHandler, type ActionResult, type ActionTools, type ApiWorkerEnv, type AppLogEvent, type AppLogEventKey, type AppLogsResponse, type AppMigrationIdsValidation, type AppNameResolution, type AppNameValidation, type AuthWorkerEnv, Awareness, type AwarenessState, type AwarenessStates, BASE_USERS_SCHEMA, BUILT_IN_TOOLS, type BaseMessage, BaseRoom, CANONICAL_APP_IDENTITY_MIGRATION_ID, CHANNELS_SCHEMA, CHANNEL_INVITATIONS_SCHEMA, CHANNEL_MEMBERS_SCHEMA, CLIENT_LOG_MARKER, CONVERSATION_SCHEMAS, COST_RATES, CanvasRoom, type CanvasShape, type ChatContextConfig, type ChatMessageRow, type ChatRow, type ChatTurn, type ClientErrorKind, type ClientErrorReport, type ClientMessage, type CollectionPermissionSummary, type CollectionSchema, type ColumnDefinition, type ColumnInterpretation, type ComposioToolsOptions, type ConnectionAttachment, 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, DEEPSPACE_AGENT_PROFILES, DEEPSPACE_AI_DEFAULTS, DEEPSPACE_AI_MODELS, DEEPSPACE_MODEL_CATALOG_PROVENANCE, DEFAULT_CONTEXT_CONFIG, DEFAULT_DO_MANIFEST, DEFAULT_QUERY_LIMIT, DEPLOYED_APPS_DEFAULTS, DIRECTORY_SCHEMAS, type DOBindings, type DOManifest, type DOManifestEntry, type DeepSpaceAIEnv, type DeepSpaceAIModel, type DeepSpaceAIModelId, type DeepSpaceAIOptions, type DeepSpaceAIProvider, type DeepSpaceAgentDiagnosticContext, DeepSpaceAgentModelError, type DeepSpaceAgentProfile, DeepSpaceAgentProfileError, type DeepSpaceAgentProfileId, type DeepSpaceAgentStream, type DeepSpaceAgentStreamOptions, type DeepSpaceAgentSupport, type DeepSpaceAuth, type DeepSpaceAuthConfig, type DeepSpaceDocumentationRouteEnv, type DeepSpaceDocumentationRouteOptions, 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 HandlerContext, type Job, type JobContext, JobRoom, type JobRoomConfig, type JobStatus, type JwtClaims, type JwtVerifierConfig, LOG_LEVELS, type LogLevel, MAX_APP_FILE_BYTES, MAX_DEPLOY_ASSET_FILE_BYTES, MAX_UPLOAD_PARTS, MAX_UPLOAD_REQUEST_BYTES, MESSAGES_SCHEMA, MSG, MSG_AWARENESS, MSG_SYNC, MSG_SYNC_STEP1, MSG_SYNC_STEP2, MSG_SYNC_UPDATE, MSG_YJS_AWARENESS, MSG_YJS_SYNC, type MessageHandlers, type MsgType, type MutateActionData, type PermissionAnalysis, type PermissionContext, type PermissionLevel, type PermissionSource, type PlatformWorkerEnv, type Player, type PrefixResult, type PresencePeer, PresenceRoom, type ProtocolMessage, type QueryActionData, REACTIONS_SCHEMA, READ_RECEIPTS_SCHEMA, REGISTRY_INSTANCE, RESERVED_BINDING_NAMES, RESERVED_COLLECTION_NAMES, RESOURCE_ID_RE, ROLE_ADMIN, ROLE_ANONYMOUS, ROLE_DEFAULT, RecordRoom, type RecordRoomConfig, RegistryClientError, type ResolvedColumn, type ResolvedDeepSpaceAgentModel, type ResolvedPermission, type RolePermissions, type RunMigrationsResult, STRICT_APP_ID_RE, SYSTEM_COLLECTIONS, SYSTEM_MANAGED_COLUMNS, SchemaRegistry, type ScopeContext, type ScopedR2Auth, type ScopedR2Config, type ScopedR2Handler, type ScreenshotEnv, type ScreenshotResult, type ServerMessage, type SharedConnection, type StorageAdmission, type StoredRecord, type Summarizer, type SyncResult, TEST_ACCOUNT_EMAIL_SUFFIX, type TokenDebugInfo, type ToolResult, type ToolSchema, UPLOAD_PART_BYTES, USERS_COLUMNS, type User, type UserAttachment, VOTING_SCHEMAS, type ValidationError, type VerifiedAuth, type VerifyOutcome, type VerifyResult, type Viewport, WORKSPACE_SCHEMAS, YjsRoom, analyzePermissions, apiWorkerFetch, appendMessage, applyAiToolDefaults, applySlidingWindow, authWorkerFetch, bindingManifestFromOutputConfig, buildCronContext, buildTableSelect, buildUiParts, callRegistry, canCreate, canDelete, canRead, canUpdate, capToolResultSize, captureScreenshot, checkFieldPermissions, clientBuild, coerceValue, collectionTableName, columnId, composioTools, computeDoMigration, computeHmacHex, createChat, createDecoder, createDeepSpaceAI, createDeepSpaceAuth, createEncoder, createScopedR2Handler, dataToColumnValues, decodeJwtPayload, deepSpaceAgentErrorSummary, deleteChatCascade, dispatch, encode, encodeAwarenessMessage, encodeSyncStep1, encodeSyncStep2, encodeUpdate, enqueueJob, formatBytes, getChat, getDeepSpaceAIModel, getGlobalDOSchemas, getGlobalDOType, getMessageType, getRolePermissions, handleAwarenessMessage, handleClientErrorReport, handleSyncMessage, hasContent, isAutoProvision, isFilesVerbPath, isOwner, isTestAccountClaims, isTestAccountEmail, isTestAccountTier, lintSchema, listDeepSpaceAgentModels, loadMessages, makeDefaultSummarizer, matchOrigin, meterAi, meterUsage, meterVectorize, noopPermissionContext, parseQuotaLimit, platformCorsPatterns, platformWorkerFetch, prepareMessagesWithCompaction, priceBindingUsageEvent, readVarUint, readVarUint8Array, registerClientErrorRoute, registerDeepSpaceDocumentation, registryErrorJson, registryStub, resolveAppName, resolveColumn, resolveDeepSpaceAgentModel, rowToData, runMigrations, serverBuild, storageLimitForTier, storageQuotaMessage, streamDeepSpaceAgent, timingSafeEqualHex, timingSafeEqualStrings, toUint8Array, totalChars, truncateOldToolResults, turnsToCoreMessages, unwrapToolOutput, updateChat, validateAppMigrationIds, validateAppName, validateBindingManifest, validateDoManifest, verifyJwt, workspaceAccountsSchema, workspaceContentSharesSchema, workspaceEmailHandlesSchema, workspaceFormResponsesSchema, workspacePeopleSchema, workspaceProjectsSchema, workspaceTagsSchema, workspaceTasksSchema, workspaceTeamMembersSchema, workspaceTeamsSchema, workspaceTransactionsSchema, writeVarUint, writeVarUint8Array };
|
package/dist/worker.js
CHANGED
|
@@ -6246,6 +6246,23 @@ var MAX_UPLOAD_REQUEST_BYTES = 25 * 1024 * 1024;
|
|
|
6246
6246
|
var UPLOAD_PART_BYTES = 20 * 1024 * 1024;
|
|
6247
6247
|
var MAX_UPLOAD_PARTS = Math.ceil(MAX_APP_FILE_BYTES / UPLOAD_PART_BYTES);
|
|
6248
6248
|
var MAX_BASE64_UPLOAD_BYTES = Math.floor(MAX_UPLOAD_REQUEST_BYTES * 3 / 4);
|
|
6249
|
+
var APP_STORAGE_LIMIT_BYTES = {
|
|
6250
|
+
test: 0,
|
|
6251
|
+
free: 128 * 1024 * 1024,
|
|
6252
|
+
starter: 512 * 1024 * 1024,
|
|
6253
|
+
premium: 2 * 1024 * 1024 * 1024,
|
|
6254
|
+
admin: 10 * 1024 * 1024 * 1024
|
|
6255
|
+
};
|
|
6256
|
+
function storageLimitForTier(tier) {
|
|
6257
|
+
if (tier && Object.hasOwn(APP_STORAGE_LIMIT_BYTES, tier)) {
|
|
6258
|
+
return APP_STORAGE_LIMIT_BYTES[tier];
|
|
6259
|
+
}
|
|
6260
|
+
return APP_STORAGE_LIMIT_BYTES.free;
|
|
6261
|
+
}
|
|
6262
|
+
function storageQuotaMessage(incomingBytes, usedBytes, limitBytes) {
|
|
6263
|
+
const free = Math.max(0, limitBytes - usedBytes);
|
|
6264
|
+
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.`;
|
|
6265
|
+
}
|
|
6249
6266
|
var MAX_DEPLOY_ASSET_FILE_BYTES = 25 * 1024 * 1024;
|
|
6250
6267
|
var DANGEROUS_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
6251
6268
|
"text/html",
|
|
@@ -6332,6 +6349,52 @@ function declaredLength(request, what) {
|
|
|
6332
6349
|
function isKeyWithinPrefix(key, prefix) {
|
|
6333
6350
|
return key.startsWith(prefix);
|
|
6334
6351
|
}
|
|
6352
|
+
async function allocationUsage(bucket, prefix) {
|
|
6353
|
+
let total = 0;
|
|
6354
|
+
let cursor;
|
|
6355
|
+
do {
|
|
6356
|
+
const page = await bucket.list({ prefix, cursor, limit: 1e3 });
|
|
6357
|
+
for (const object of page.objects) total += object.size;
|
|
6358
|
+
cursor = page.truncated ? page.cursor : void 0;
|
|
6359
|
+
} while (cursor !== void 0);
|
|
6360
|
+
return total;
|
|
6361
|
+
}
|
|
6362
|
+
async function admitStorage(bucket, storage, incomingBytes, replacesKey) {
|
|
6363
|
+
if (!storage) return null;
|
|
6364
|
+
const limit = await storage.limitBytes();
|
|
6365
|
+
if (limit === null) {
|
|
6366
|
+
return fail(
|
|
6367
|
+
503,
|
|
6368
|
+
"The storage limit for this app could not be verified; nothing was uploaded. Retry shortly.",
|
|
6369
|
+
"storage_limit_unavailable"
|
|
6370
|
+
);
|
|
6371
|
+
}
|
|
6372
|
+
const stored = await allocationUsage(bucket, storage.prefix);
|
|
6373
|
+
const replaced = replacesKey === null ? 0 : (await bucket.head(replacesKey))?.size ?? 0;
|
|
6374
|
+
const used = stored - replaced;
|
|
6375
|
+
if (used + incomingBytes <= limit) return null;
|
|
6376
|
+
return fail(409, storageQuotaMessage(incomingBytes, used, limit), "storage_quota_exceeded", {
|
|
6377
|
+
usedBytes: used,
|
|
6378
|
+
limitBytes: limit
|
|
6379
|
+
});
|
|
6380
|
+
}
|
|
6381
|
+
async function admitAssembledObject(bucket, storage, key, objectBytes) {
|
|
6382
|
+
if (!storage) return null;
|
|
6383
|
+
const limit = await storage.limitBytes();
|
|
6384
|
+
if (limit === null) {
|
|
6385
|
+
console.error("[files:quota] limit unavailable at complete; keeping", key);
|
|
6386
|
+
return null;
|
|
6387
|
+
}
|
|
6388
|
+
const stored = await allocationUsage(bucket, storage.prefix);
|
|
6389
|
+
if (stored <= limit) return null;
|
|
6390
|
+
await bucket.delete(key).catch(() => void 0);
|
|
6391
|
+
return fail(
|
|
6392
|
+
409,
|
|
6393
|
+
`${storageQuotaMessage(objectBytes, stored - objectBytes, limit)} The assembled upload was not kept.`,
|
|
6394
|
+
"storage_quota_exceeded",
|
|
6395
|
+
{ usedBytes: stored - objectBytes, limitBytes: limit }
|
|
6396
|
+
);
|
|
6397
|
+
}
|
|
6335
6398
|
function anchorKey(raw, prefix) {
|
|
6336
6399
|
const safe = sanitizeSubpath(raw);
|
|
6337
6400
|
if (safe === null) return fail(400, "Invalid key: traversal not allowed", "invalid_key");
|
|
@@ -6421,10 +6484,10 @@ function createScopedR2Handler(config) {
|
|
|
6421
6484
|
await request.body?.cancel().catch(() => void 0);
|
|
6422
6485
|
return fail(401, "Authentication required", "unauthorized");
|
|
6423
6486
|
}
|
|
6424
|
-
return subpath === "upload" ? handleUpload(request, url, bucket, prefix, auth
|
|
6487
|
+
return subpath === "upload" ? handleUpload(request, url, bucket, prefix, auth, scope) : handleMultipart(request, url, bucket, prefix, auth, scope, subpath);
|
|
6425
6488
|
}
|
|
6426
6489
|
if (!subpath && request.method === "GET") {
|
|
6427
|
-
return handleList(bucket, prefix, url, scope);
|
|
6490
|
+
return handleList(bucket, prefix, url, scope, auth);
|
|
6428
6491
|
}
|
|
6429
6492
|
if (subpath && request.method === "GET") {
|
|
6430
6493
|
if (!isKeyWithinPrefix(subpath, prefix)) {
|
|
@@ -6458,7 +6521,7 @@ function createScopedR2Handler(config) {
|
|
|
6458
6521
|
return Response.json({ error: "Method not allowed" }, { status: 405, headers: CORS_HEADERS3 });
|
|
6459
6522
|
};
|
|
6460
6523
|
}
|
|
6461
|
-
async function handleUpload(request, url, bucket, prefix,
|
|
6524
|
+
async function handleUpload(request, url, bucket, prefix, auth, scope) {
|
|
6462
6525
|
try {
|
|
6463
6526
|
const declared = overRequestBound(Number(request.headers.get("content-length")), 8 * 1024);
|
|
6464
6527
|
if (declared) return declared;
|
|
@@ -6489,9 +6552,12 @@ async function handleUpload(request, url, bucket, prefix, userId, scope) {
|
|
|
6489
6552
|
if (mime instanceof Response) return mime;
|
|
6490
6553
|
const located = locateUpload(url, prefix, fileName);
|
|
6491
6554
|
if (located instanceof Response) return located;
|
|
6555
|
+
const replacesKey = url.searchParams.get("key") === null ? null : located.key;
|
|
6556
|
+
const overQuota = await admitStorage(bucket, auth.storage, fileData.byteLength, replacesKey);
|
|
6557
|
+
if (overQuota) return overQuota;
|
|
6492
6558
|
await bucket.put(located.key, fileData, {
|
|
6493
6559
|
httpMetadata: { contentType: mime.mimeType },
|
|
6494
|
-
customMetadata: fileMetadata(fileName, userId)
|
|
6560
|
+
customMetadata: fileMetadata(fileName, auth.userId)
|
|
6495
6561
|
});
|
|
6496
6562
|
return storedFileJson(located.key, fileName, url, scope);
|
|
6497
6563
|
} catch (err) {
|
|
@@ -6566,17 +6632,17 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
|
|
|
6566
6632
|
}
|
|
6567
6633
|
return { ok: true, part: uploaded.value };
|
|
6568
6634
|
}
|
|
6569
|
-
async function handleMultipart(request, url, bucket, prefix,
|
|
6635
|
+
async function handleMultipart(request, url, bucket, prefix, auth, scope, subpath) {
|
|
6570
6636
|
if (subpath === "multipart" && request.method === "POST") {
|
|
6571
|
-
return multipartInit(request, url, bucket, prefix,
|
|
6637
|
+
return multipartInit(request, url, bucket, prefix, auth);
|
|
6572
6638
|
}
|
|
6573
6639
|
if (subpath === "multipart/part") return multipartPart(request, url, bucket, prefix);
|
|
6574
6640
|
if (subpath === "multipart/complete") {
|
|
6575
|
-
return multipartComplete(request, url, bucket, prefix, scope);
|
|
6641
|
+
return multipartComplete(request, url, bucket, prefix, auth, scope);
|
|
6576
6642
|
}
|
|
6577
6643
|
return multipartAbort(url, bucket, prefix);
|
|
6578
6644
|
}
|
|
6579
|
-
async function multipartInit(request, url, bucket, prefix,
|
|
6645
|
+
async function multipartInit(request, url, bucket, prefix, auth) {
|
|
6580
6646
|
const body = await readControlJson(request);
|
|
6581
6647
|
if (body instanceof Response) return body;
|
|
6582
6648
|
const size = body.size;
|
|
@@ -6591,11 +6657,14 @@ async function multipartInit(request, url, bucket, prefix, userId) {
|
|
|
6591
6657
|
if (mime instanceof Response) return mime;
|
|
6592
6658
|
const located = locateUpload(url, prefix, name);
|
|
6593
6659
|
if (located instanceof Response) return located;
|
|
6660
|
+
const replacesKey = url.searchParams.get("key") === null ? null : located.key;
|
|
6661
|
+
const overQuota = await admitStorage(bucket, auth.storage, size, replacesKey);
|
|
6662
|
+
if (overQuota) return overQuota;
|
|
6594
6663
|
let upload;
|
|
6595
6664
|
try {
|
|
6596
6665
|
upload = await bucket.createMultipartUpload(located.key, {
|
|
6597
6666
|
httpMetadata: { contentType: mime.mimeType },
|
|
6598
|
-
customMetadata: fileMetadata(name, userId)
|
|
6667
|
+
customMetadata: fileMetadata(name, auth.userId)
|
|
6599
6668
|
});
|
|
6600
6669
|
} catch (error) {
|
|
6601
6670
|
return multipartFailure(error);
|
|
@@ -6645,7 +6714,7 @@ async function multipartPart(request, url, bucket, prefix) {
|
|
|
6645
6714
|
{ headers: CORS_HEADERS3 }
|
|
6646
6715
|
);
|
|
6647
6716
|
}
|
|
6648
|
-
async function multipartComplete(request, url, bucket, prefix, scope) {
|
|
6717
|
+
async function multipartComplete(request, url, bucket, prefix, auth, scope) {
|
|
6649
6718
|
const body = await readControlJson(request);
|
|
6650
6719
|
if (body instanceof Response) return body;
|
|
6651
6720
|
const session = resolveSession(url, prefix);
|
|
@@ -6679,6 +6748,8 @@ async function multipartComplete(request, url, bucket, prefix, scope) {
|
|
|
6679
6748
|
await bucket.delete(session.key).catch(() => void 0);
|
|
6680
6749
|
return tooBig;
|
|
6681
6750
|
}
|
|
6751
|
+
const overQuota = await admitAssembledObject(bucket, auth.storage, session.key, object.size);
|
|
6752
|
+
if (overQuota) return overQuota;
|
|
6682
6753
|
const name = object.customMetadata?.originalName ?? session.key.split("/").pop() ?? "file";
|
|
6683
6754
|
return storedFileJson(session.key, name, url, scope);
|
|
6684
6755
|
}
|
|
@@ -6693,7 +6764,7 @@ async function multipartAbort(url, bucket, prefix) {
|
|
|
6693
6764
|
}
|
|
6694
6765
|
return Response.json({ success: true, aborted: true }, { headers: CORS_HEADERS3 });
|
|
6695
6766
|
}
|
|
6696
|
-
async function handleList(bucket, prefix, url, scope) {
|
|
6767
|
+
async function handleList(bucket, prefix, url, scope, auth) {
|
|
6697
6768
|
const userPrefix = url.searchParams.get("prefix") || "";
|
|
6698
6769
|
const listPrefix = `${prefix}${userPrefix}`;
|
|
6699
6770
|
const limit = parseInt(url.searchParams.get("limit") || "100", 10);
|
|
@@ -6709,7 +6780,16 @@ async function handleList(bucket, prefix, url, scope) {
|
|
|
6709
6780
|
...obj.customMetadata
|
|
6710
6781
|
};
|
|
6711
6782
|
});
|
|
6712
|
-
|
|
6783
|
+
let storage;
|
|
6784
|
+
if (auth.storage) {
|
|
6785
|
+
storage = { usedBytes: await allocationUsage(bucket, auth.storage.prefix) };
|
|
6786
|
+
const limitBytes = await auth.storage.limitBytes();
|
|
6787
|
+
if (limitBytes !== null) storage.limitBytes = limitBytes;
|
|
6788
|
+
}
|
|
6789
|
+
return Response.json(
|
|
6790
|
+
{ files, truncated: listed.truncated, ...storage ? { storage } : {} },
|
|
6791
|
+
{ headers: CORS_HEADERS3 }
|
|
6792
|
+
);
|
|
6713
6793
|
}
|
|
6714
6794
|
async function handleDownload(bucket, key, scope) {
|
|
6715
6795
|
const object = await bucket.get(key);
|
|
@@ -9317,6 +9397,7 @@ export {
|
|
|
9317
9397
|
APP_ID_RE,
|
|
9318
9398
|
APP_LOG_EVENT_KEYS,
|
|
9319
9399
|
APP_NAME_RULES,
|
|
9400
|
+
APP_STORAGE_LIMIT_BYTES,
|
|
9320
9401
|
AUTO_PROVISIONABLE_TYPES,
|
|
9321
9402
|
AUTO_PROVISION_SENTINEL,
|
|
9322
9403
|
Awareness,
|
|
@@ -9468,6 +9549,8 @@ export {
|
|
|
9468
9549
|
rowToData,
|
|
9469
9550
|
runMigrations,
|
|
9470
9551
|
serverBuild,
|
|
9552
|
+
storageLimitForTier,
|
|
9553
|
+
storageQuotaMessage,
|
|
9471
9554
|
streamDeepSpaceAgent,
|
|
9472
9555
|
timingSafeEqualHex,
|
|
9473
9556
|
timingSafeEqualStrings,
|