alepha 0.20.5 → 0.20.7
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/AGENTS.md +0 -1
- package/CLAUDE.md +0 -1
- package/assets/agents-template.md +0 -1
- package/dist/api/audits/index.browser.js +1 -0
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.d.ts +701 -654
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +24 -1
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.browser.js +1 -0
- package/dist/api/files/index.browser.js.map +1 -1
- package/dist/api/files/index.d.ts +193 -166
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +52 -0
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +40 -14
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +639 -333
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +495 -162
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +222 -188
- package/dist/api/keys/index.d.ts.map +1 -1
- package/dist/api/keys/index.js +54 -0
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +265 -236
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +55 -13
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/organizations/index.d.ts +100 -97
- package/dist/api/organizations/index.d.ts.map +1 -1
- package/dist/api/organizations/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +332 -314
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +37 -0
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/payments/index.d.ts +431 -376
- package/dist/api/payments/index.d.ts.map +1 -1
- package/dist/api/payments/index.js +202 -87
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/subscriptions/index.d.ts +1695 -0
- package/dist/api/subscriptions/index.d.ts.map +1 -0
- package/dist/api/subscriptions/index.js +1919 -0
- package/dist/api/subscriptions/index.js.map +1 -0
- package/dist/api/users/index.d.ts +1001 -844
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +237 -28
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +123 -122
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts +21 -2
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +47 -0
- package/dist/bucket/index.js.map +1 -1
- package/dist/bucket/index.workerd.js +24 -0
- package/dist/bucket/index.workerd.js.map +1 -1
- package/dist/cache/core/index.d.ts +134 -7
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js +181 -15
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js +181 -15
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/cache/database/index.d.ts +156 -0
- package/dist/cache/database/index.d.ts.map +1 -0
- package/dist/cache/database/index.js +266 -0
- package/dist/cache/database/index.js.map +1 -0
- package/dist/cache/redis/index.d.ts +3 -2
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/captcha/index.js.map +1 -1
- package/dist/cli/config/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +142 -128
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +160 -13
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +3 -2
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +346 -290
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +106 -7
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +12 -11
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/command/index.d.ts +6 -5
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +119 -118
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +1 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +1 -1
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/crypto/index.browser.js.map +1 -1
- package/dist/crypto/index.d.ts +3 -2
- package/dist/crypto/index.d.ts.map +1 -1
- package/dist/crypto/index.js.map +1 -1
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/brevo/index.js.map +1 -1
- package/dist/email/core/index.d.ts +3 -2
- package/dist/email/core/index.d.ts.map +1 -1
- package/dist/email/core/index.js.map +1 -1
- package/dist/email/core/index.workerd.js.map +1 -1
- package/dist/email/smtp/index.d.ts +7 -6
- package/dist/email/smtp/index.d.ts.map +1 -1
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +5 -4
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.d.ts +10 -9
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +9 -8
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +9 -3
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +31 -10
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +33 -14
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +31 -10
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +6 -5
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +5 -4
- package/dist/queue/core/index.d.ts.map +1 -1
- package/dist/queue/core/index.js.map +1 -1
- package/dist/queue/core/index.workerd.js.map +1 -1
- package/dist/queue/redis/index.d.ts +3 -2
- package/dist/queue/redis/index.d.ts.map +1 -1
- package/dist/queue/redis/index.js.map +1 -1
- package/dist/react/auth/index.browser.js.map +1 -1
- package/dist/react/auth/index.js.map +1 -1
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/form/index.d.ts +5 -0
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js +8 -4
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/head/index.browser.js.map +1 -1
- package/dist/react/head/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +2 -1
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.js.map +1 -1
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +206 -205
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/testing/index.js.map +1 -1
- package/dist/react/ui/index.d.ts +11 -11
- package/dist/react/ui/index.d.ts.map +1 -1
- package/dist/react/ui/index.js.map +1 -1
- package/dist/redis/index.bun.js.map +1 -1
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.js.map +1 -1
- package/dist/router/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +25 -2
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +12 -0
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/index.workerd.js +12 -0
- package/dist/scheduler/index.workerd.js.map +1 -1
- package/dist/security/index.browser.js +29 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +82 -35
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +56 -3
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +163 -158
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +16 -4
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cookies/index.browser.js.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +35 -34
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +7 -6
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/cors/index.js.map +1 -1
- package/dist/server/etag/index.js.map +1 -1
- package/dist/server/health/index.d.ts +16 -15
- package/dist/server/health/index.d.ts.map +1 -1
- package/dist/server/health/index.js.map +1 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +51 -50
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.d.ts +6 -5
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +2 -1
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.js.map +1 -1
- package/dist/system/index.workerd.js.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/topic/redis/index.d.ts +3 -2
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/dist/topic/redis/index.js.map +1 -1
- package/package.json +33 -39
- package/src/api/audits/controllers/AdminAuditController.ts +29 -0
- package/src/api/audits/entities/audits.ts +1 -0
- package/src/api/files/controllers/FileController.ts +24 -0
- package/src/api/files/entities/files.ts +1 -0
- package/src/api/files/services/FileService.ts +41 -0
- package/src/api/jobs/__tests__/$job.spec.ts +501 -24
- package/src/api/jobs/entities/jobExecutionEntity.ts +4 -3
- package/src/api/jobs/index.ts +47 -10
- package/src/api/jobs/primitives/$job.ts +22 -9
- package/src/api/jobs/providers/DirectJobDispatcher.ts +71 -0
- package/src/api/jobs/providers/JobDispatcher.ts +49 -0
- package/src/api/jobs/providers/JobProvider.ts +385 -147
- package/src/api/jobs/providers/JobQueueProvider.ts +43 -18
- package/src/api/jobs/schemas/jobConfigAtom.ts +9 -3
- package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +11 -0
- package/src/api/jobs/schemas/jobRegistrationSchema.ts +4 -2
- package/src/api/jobs/services/JobService.ts +21 -11
- package/src/api/keys/controllers/AdminApiKeyController.ts +23 -0
- package/src/api/keys/entities/apiKeyEntity.ts +1 -0
- package/src/api/keys/services/ApiKeyService.ts +42 -0
- package/src/api/notifications/__tests__/AlephaApiNotifications.spec.ts +63 -0
- package/src/api/notifications/controllers/AdminNotificationController.ts +48 -1
- package/src/api/notifications/index.ts +13 -3
- package/src/api/notifications/jobs/NotificationJobs.ts +0 -6
- package/src/api/parameters/controllers/AdminParameterController.ts +26 -0
- package/src/api/parameters/services/ParameterProvider.ts +18 -0
- package/src/api/payments/controllers/MockCheckoutController.ts +146 -0
- package/src/api/payments/index.ts +3 -0
- package/src/api/payments/providers/MemoryPaymentProvider.ts +9 -4
- package/src/api/payments/providers/PaymentProvider.ts +25 -9
- package/src/api/payments/services/PaymentService.ts +3 -0
- package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
- package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
- package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
- package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
- package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
- package/src/api/subscriptions/entities/subscriptions.ts +68 -0
- package/src/api/subscriptions/index.ts +133 -0
- package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
- package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
- package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
- package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
- package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
- package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
- package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
- package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
- package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
- package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
- package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
- package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
- package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
- package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
- package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
- package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
- package/src/api/subscriptions/services/BillingService.ts +437 -0
- package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
- package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
- package/src/api/subscriptions/services/UsageService.ts +118 -0
- package/src/api/users/__tests__/Registration-emailMode.spec.ts +203 -0
- package/src/api/users/__tests__/UsernameSlugger.spec.ts +138 -0
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +41 -3
- package/src/api/users/controllers/AdminSessionController.ts +29 -0
- package/src/api/users/controllers/AdminUserController.ts +32 -0
- package/src/api/users/index.ts +3 -0
- package/src/api/users/services/CredentialService.ts +5 -0
- package/src/api/users/services/RegistrationService.ts +49 -1
- package/src/api/users/services/SessionCrudService.ts +16 -0
- package/src/api/users/services/SessionService.ts +17 -59
- package/src/api/users/services/UsernameSlugger.ts +195 -0
- package/src/bucket/primitives/$bucket.ts +21 -0
- package/src/bucket/providers/CloudflareR2Provider.ts +15 -0
- package/src/bucket/providers/FileStorageProvider.ts +9 -0
- package/src/bucket/providers/LocalFileStorageProvider.ts +14 -0
- package/src/bucket/providers/MemoryFileStorageProvider.ts +9 -0
- package/src/bucket/providers/NodeS3BucketProvider.ts +35 -0
- package/src/cache/core/__tests__/$cache.memory.spec.ts +450 -0
- package/src/cache/core/__tests__/$cache.swr.spec.ts +394 -0
- package/src/cache/core/index.ts +16 -0
- package/src/cache/core/primitives/$cache.ts +367 -24
- package/src/cache/database/__tests__/DatabaseCacheProvider.behavior.spec.ts +203 -0
- package/src/cache/database/__tests__/DatabaseCacheProvider.spec.ts +110 -0
- package/src/cache/database/entities/cacheEntries.ts +55 -0
- package/src/cache/database/index.ts +36 -0
- package/src/cache/database/providers/DatabaseCacheProvider.ts +348 -0
- package/src/cli/core/services/ProjectScaffolder.ts +0 -2
- package/src/cli/core/tasks/BuildCloudflareTask.ts +33 -3
- package/src/cli/core/tasks/BuildSitemapTask.ts +7 -0
- package/src/cli/core/tasks/BuildVercelTask.ts +82 -3
- package/src/cli/core/templates/agentMd.ts +39 -4
- package/src/cli/core/templates/biomeJson.ts +25 -1
- package/src/cli/core/templates/saasAdminLayoutTsx.ts +2 -2
- package/src/cli/platform/__tests__/CloudflareAdapter.spec.ts +117 -0
- package/src/cli/platform/__tests__/detectResources.spec.ts +96 -0
- package/src/cli/platform/adapters/CloudflareAdapter.ts +104 -7
- package/src/cli/platform/atoms/platformOptions.ts +13 -0
- package/src/cli/platform/commands/platform.ts +7 -1
- package/src/cli/platform/schemas/platform.ts +1 -0
- package/src/cli/platform/services/CloudflareApi.ts +61 -0
- package/src/cli/platform/services/PlatformOrchestrator.ts +9 -4
- package/src/core/__tests__/$module.spec.ts +2 -2
- package/src/core/primitives/$module.ts +4 -4
- package/src/mcp/providers/McpServerProvider.ts +1 -1
- package/src/orm/core/providers/DatabaseTypeProvider.ts +9 -3
- package/src/orm/core/providers/drivers/DatabaseProvider.ts +1 -1
- package/src/orm/core/schemas/insertSchema.ts +10 -2
- package/src/orm/core/services/Repository.ts +27 -7
- package/src/react/form/hooks/useFormState.ts +8 -1
- package/src/react/form/index.ts +10 -1
- package/src/react/form/services/FormModel.ts +9 -3
- package/src/scheduler/index.ts +14 -0
- package/src/scheduler/providers/CronProvider.ts +13 -0
- package/src/security/atoms/currentTenantAtom.ts +34 -0
- package/src/security/index.browser.ts +1 -0
- package/src/security/index.ts +12 -1
- package/src/security/primitives/$issuer.ts +17 -1
- package/src/security/providers/SecurityProvider.ts +37 -0
- package/src/server/auth/__tests__/validateRedirectUri.spec.ts +78 -0
- package/src/server/auth/providers/ServerAuthProvider.ts +21 -5
- package/tsconfig.base.json +2 -1
- package/dist/react/websocket/index.d.ts +0 -117
- package/dist/react/websocket/index.d.ts.map +0 -1
- package/dist/react/websocket/index.js +0 -108
- package/dist/react/websocket/index.js.map +0 -1
- package/dist/websocket/index.browser.js +0 -844
- package/dist/websocket/index.browser.js.map +0 -1
- package/dist/websocket/index.d.ts +0 -876
- package/dist/websocket/index.d.ts.map +0 -1
- package/dist/websocket/index.js +0 -1175
- package/dist/websocket/index.js.map +0 -1
- package/src/react/websocket/hooks/useRoom.tsx +0 -251
- package/src/react/websocket/index.ts +0 -7
- package/src/websocket/__tests__/$channel.spec.ts +0 -30
- package/src/websocket/__tests__/$websocket-new.spec.ts +0 -195
- package/src/websocket/__tests__/RoomManager.spec.ts +0 -146
- package/src/websocket/__tests__/websocket-integration.spec.ts +0 -951
- package/src/websocket/errors/WebSocketError.ts +0 -34
- package/src/websocket/index.browser.ts +0 -25
- package/src/websocket/index.shared.ts +0 -8
- package/src/websocket/index.ts +0 -85
- package/src/websocket/interfaces/WebSocketInterfaces.ts +0 -252
- package/src/websocket/primitives/$channel.ts +0 -131
- package/src/websocket/primitives/$websocket.ts +0 -107
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +0 -617
- package/src/websocket/providers/WebSocketServerProvider.ts +0 -56
- package/src/websocket/services/RoomManager.ts +0 -160
- package/src/websocket/services/WebSocketClient.ts +0 -642
- package/src/websocket/services/WebSocketTopicService.ts +0 -108
|
@@ -52,6 +52,9 @@ var MemoryFileStorageProvider = class {
|
|
|
52
52
|
if (!(fileKey in this.files)) throw new FileNotFoundError(`File with ID ${fileId} not found.`);
|
|
53
53
|
delete this.files[fileKey];
|
|
54
54
|
}
|
|
55
|
+
async deleteMany(bucketName, fileIds) {
|
|
56
|
+
for (const id of fileIds) delete this.files[`${bucketName}/${id}`];
|
|
57
|
+
}
|
|
55
58
|
createId() {
|
|
56
59
|
return randomUUID();
|
|
57
60
|
}
|
|
@@ -151,6 +154,20 @@ var BucketPrimitive = class extends Primitive {
|
|
|
151
154
|
});
|
|
152
155
|
}
|
|
153
156
|
/**
|
|
157
|
+
* Delete many files in one round-trip when the underlying provider supports
|
|
158
|
+
* batch (R2/S3 up to 1000 keys per call). Emits one `bucket:file:deleted`
|
|
159
|
+
* event per id unless `skipHook` is set.
|
|
160
|
+
*/
|
|
161
|
+
async deleteMany(fileIds, skipHook = false) {
|
|
162
|
+
if (fileIds.length === 0) return;
|
|
163
|
+
await this.provider.deleteMany(this.name, fileIds);
|
|
164
|
+
if (skipHook) return;
|
|
165
|
+
for (const id of fileIds) await this.alepha.events.emit("bucket:file:deleted", {
|
|
166
|
+
id,
|
|
167
|
+
bucket: this
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
154
171
|
* Checks if a file exists in the bucket.
|
|
155
172
|
*/
|
|
156
173
|
async exists(fileId) {
|
|
@@ -304,6 +321,13 @@ R2_BUCKET_NAME: t.string({ description: "R2 bucket name in Cloudflare" }) }));
|
|
|
304
321
|
if (!await this.exists(bucketName, fileId)) throw new FileNotFoundError(`File '${fileId}' not found in bucket '${bucketName}'.`);
|
|
305
322
|
await r2.delete(key);
|
|
306
323
|
}
|
|
324
|
+
async deleteMany(bucketName, fileIds) {
|
|
325
|
+
if (fileIds.length === 0) return;
|
|
326
|
+
const r2 = this.getR2();
|
|
327
|
+
const keys = fileIds.map((id) => this.key(bucketName, id));
|
|
328
|
+
this.log.trace(`Deleting ${keys.length} keys from '${bucketName}'`);
|
|
329
|
+
for (let i = 0; i < keys.length; i += 1e3) await r2.delete(keys.slice(i, i + 1e3));
|
|
330
|
+
}
|
|
307
331
|
/**
|
|
308
332
|
* Build the full R2 key: {prefix}/{bucketName}/{fileId}
|
|
309
333
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.workerd.js","names":[],"sources":["../../src/bucket/errors/InvalidFileError.ts","../../src/bucket/providers/FileStorageProvider.ts","../../src/bucket/errors/FileNotFoundError.ts","../../src/bucket/providers/MemoryFileStorageProvider.ts","../../src/bucket/primitives/$bucket.ts","../../src/bucket/providers/CloudflareR2Provider.ts","../../src/bucket/index.workerd.ts"],"sourcesContent":["import { AlephaError } from \"alepha\";\n\nexport class InvalidFileError extends AlephaError {\n public readonly status = 400;\n}\n","import type { FileLike } from \"alepha\";\n\nexport abstract class FileStorageProvider {\n /**\n * Uploads a file to the storage.\n *\n * @param bucketName - Container name\n * @param file - File to upload\n * @param fileId - Optional file identifier. If not provided, a unique ID will be generated.\n * @return The identifier of the uploaded file.\n */\n abstract upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string>;\n\n /**\n * Downloads a file from the storage.\n *\n * @param bucketName - Container name\n * @param fileId - Identifier of the file to download\n * @return The downloaded file as a FileLike object.\n */\n abstract download(bucketName: string, fileId: string): Promise<FileLike>;\n\n /**\n * Check if fileId exists in the storage bucket.\n *\n * @param bucketName - Container name\n * @param fileId - Identifier of the file to stream\n * @return True is the file exists, false otherwise.\n */\n abstract exists(bucketName: string, fileId: string): Promise<boolean>;\n\n /**\n * Delete permanently a file from the storage.\n *\n * @param bucketName - Container name\n * @param fileId - Identifier of the file to delete\n */\n abstract delete(bucketName: string, fileId: string): Promise<void>;\n}\n","import { AlephaError } from \"alepha\";\n\nexport class FileNotFoundError extends AlephaError {\n public readonly status = 404;\n}\n","import { randomUUID } from \"node:crypto\";\nimport { $inject, type FileLike } from \"alepha\";\nimport { FileDetector, FileSystemProvider } from \"alepha/system\";\nimport { FileNotFoundError } from \"../errors/FileNotFoundError.ts\";\nimport type { FileStorageProvider } from \"./FileStorageProvider.ts\";\n\ninterface StoredFile {\n buffer: Buffer;\n name: string;\n type: string;\n size: number;\n}\n\nexport class MemoryFileStorageProvider implements FileStorageProvider {\n public readonly files: Record<string, StoredFile> = {};\n protected readonly fileSystem = $inject(FileSystemProvider);\n protected readonly fileDetector = $inject(FileDetector);\n\n public async upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string> {\n fileId ??= this.createId();\n\n // Consume the stream and store as buffer so downloads are repeatable\n const chunks: Uint8Array[] = [];\n for await (const chunk of file.stream() as AsyncIterable<Uint8Array>) {\n chunks.push(chunk);\n }\n const buffer = Buffer.concat(chunks);\n\n this.files[`${bucketName}/${fileId}`] = {\n buffer,\n name: file.name,\n type: file.type,\n size: file.size,\n };\n\n return fileId;\n }\n\n public async download(bucketName: string, fileId: string): Promise<FileLike> {\n const fileKey = `${bucketName}/${fileId}`;\n const stored = this.files[fileKey];\n\n if (!stored) {\n throw new FileNotFoundError(`File with ID ${fileId} not found.`);\n }\n\n // Create a fresh FileLike with a new stream from the stored buffer\n return this.fileSystem.createFile({\n stream: new Blob([new Uint8Array(stored.buffer)]).stream(),\n name: stored.name,\n type: stored.type,\n size: stored.size,\n });\n }\n\n public async exists(bucketName: string, fileId: string): Promise<boolean> {\n return `${bucketName}/${fileId}` in this.files;\n }\n\n public async delete(bucketName: string, fileId: string): Promise<void> {\n const fileKey = `${bucketName}/${fileId}`;\n if (!(fileKey in this.files)) {\n throw new FileNotFoundError(`File with ID ${fileId} not found.`);\n }\n\n delete this.files[fileKey];\n }\n\n protected createId(): string {\n return randomUUID();\n }\n}\n","import {\n $inject,\n createPrimitive,\n type FileLike,\n KIND,\n Primitive,\n type Service,\n} from \"alepha\";\nimport { FileSystemProvider } from \"alepha/system\";\nimport { InvalidFileError } from \"../errors/InvalidFileError.ts\";\nimport { FileStorageProvider } from \"../providers/FileStorageProvider.ts\";\nimport { MemoryFileStorageProvider } from \"../providers/MemoryFileStorageProvider.ts\";\n\n/**\n * Creates a bucket primitive for file storage and management with configurable validation.\n *\n * Provides a comprehensive file storage system that handles uploads, downloads, validation,\n * and management across multiple storage backends with MIME type and size limit controls.\n *\n * **Key Features**\n * - Multi-provider support (filesystem, cloud storage, in-memory)\n * - Automatic MIME type and file size validation\n * - Event integration for file operations monitoring\n * - Flexible per-bucket and per-operation configuration\n * - Smart file type and size detection\n *\n * **Common Use Cases**\n * - User profile pictures and document uploads\n * - Product images and media management\n * - Document storage and retrieval systems\n *\n * @example\n * ```ts\n * class MediaService {\n * images = $bucket({\n * name: \"user-images\",\n * mimeTypes: [\"image/jpeg\", \"image/png\", \"image/gif\"],\n * maxSize: 5 // 5MB limit\n * });\n *\n * documents = $bucket({\n * name: \"documents\",\n * mimeTypes: [\"application/pdf\", \"text/plain\"],\n * maxSize: 50 // 50MB limit\n * });\n *\n * async uploadProfileImage(file: FileLike, userId: string): Promise<string> {\n * const fileId = await this.images.upload(file);\n * await this.userService.updateProfileImage(userId, fileId);\n * return fileId;\n * }\n *\n * async downloadDocument(documentId: string): Promise<FileLike> {\n * return await this.documents.download(documentId);\n * }\n *\n * async deleteDocument(documentId: string): Promise<void> {\n * await this.documents.delete(documentId);\n * }\n * }\n * ```\n */\nexport const $bucket = (options: BucketPrimitiveOptions) =>\n createPrimitive(BucketPrimitive, options);\n\nexport interface BucketPrimitiveOptions extends BucketFileOptions {\n /**\n * File storage provider configuration for the bucket.\n *\n * Options:\n * - **\"memory\"**: In-memory storage (default for development, lost on restart)\n * - **Service<FileStorageProvider>**: Custom provider class (e.g., S3FileStorageProvider, AzureBlobProvider)\n * - **undefined**: Uses the default file storage provider from dependency injection\n *\n * **Provider Selection Guidelines**:\n * - **Development**: Use \"memory\" for fast, simple testing without external dependencies\n * - **Production**: Use cloud providers (S3, Azure Blob, Google Cloud Storage) for scalability\n * - **Local deployment**: Use filesystem providers for on-premise installations\n * - **Hybrid**: Use different providers for different bucket types (temp files vs permanent storage)\n *\n * **Provider Capabilities**:\n * - File persistence and durability guarantees\n * - Scalability and performance characteristics\n * - Geographic distribution and CDN integration\n * - Cost implications for storage and bandwidth\n * - Backup and disaster recovery features\n *\n * @default Uses injected FileStorageProvider\n * @example \"memory\"\n * @example S3FileStorageProvider\n * @example AzureBlobStorageProvider\n */\n provider?: Service<FileStorageProvider> | \"memory\";\n\n /**\n * Unique name identifier for the bucket.\n *\n * This name is used for:\n * - Storage backend organization and partitioning\n * - File path generation and URL construction\n * - Logging, monitoring, and debugging\n * - Access control and permissions management\n * - Backup and replication configuration\n *\n * **Naming Conventions**:\n * - Use lowercase with hyphens for consistency\n * - Include purpose or content type in the name\n * - Avoid spaces and special characters\n * - Consider environment prefixes for deployment isolation\n *\n * If not provided, defaults to the property key where the bucket is declared.\n *\n * @example \"user-avatars\"\n * @example \"product-images\"\n * @example \"legal-documents\"\n * @example \"temp-processing-files\"\n */\n name?: string;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface BucketFileOptions {\n /**\n * Human-readable description of the bucket's purpose and contents.\n *\n * Used for:\n * - Documentation generation and API references\n * - Developer onboarding and system understanding\n * - Monitoring dashboards and admin interfaces\n * - Compliance and audit documentation\n *\n * **Description Best Practices**:\n * - Explain what types of files this bucket stores\n * - Mention any special handling or processing requirements\n * - Include information about retention policies if applicable\n * - Note any compliance or security considerations\n *\n * @example \"User profile pictures and avatar images\"\n * @example \"Product catalog images with automated thumbnail generation\"\n * @example \"Legal documents requiring long-term retention\"\n * @example \"Temporary files for data processing workflows\"\n */\n description?: string;\n\n /**\n * Array of allowed MIME types for files uploaded to this bucket.\n *\n * When specified, only files with these exact MIME types will be accepted.\n * Files with disallowed MIME types will be rejected with an InvalidFileError.\n *\n * **MIME Type Categories**:\n * - Images: \"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\", \"image/svg+xml\"\n * - Documents: \"application/pdf\", \"text/plain\", \"text/csv\"\n * - Office: \"application/msword\", \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\"\n * - Archives: \"application/zip\", \"application/x-tar\", \"application/gzip\"\n * - Media: \"video/mp4\", \"audio/mpeg\", \"audio/wav\"\n *\n * **Security Considerations**:\n * - Always validate MIME types for user uploads\n * - Be cautious with executable file types\n * - Consider using allow-lists rather than deny-lists\n * - Remember that MIME types can be spoofed by malicious users\n *\n * If not specified, all MIME types are allowed (not recommended for user uploads).\n *\n * @example [\"image/jpeg\", \"image/png\"] // Only JPEG and PNG images\n * @example [\"application/pdf\", \"text/plain\"] // Documents only\n * @example [\"video/mp4\", \"video/webm\"] // Video files\n */\n mimeTypes?: string[];\n\n /**\n * Maximum file size allowed in megabytes (MB).\n *\n * Files larger than this limit will be rejected with an InvalidFileError.\n * This helps prevent:\n * - Storage quota exhaustion\n * - Memory issues during file processing\n * - Long upload times and timeouts\n * - Abuse of storage resources\n *\n * **Size Guidelines by File Type**:\n * - Profile images: 1-5 MB\n * - Product photos: 5-10 MB\n * - Documents: 10-50 MB\n * - Video files: 50-500 MB\n * - Data files: 100-1000 MB\n *\n * **Considerations**:\n * - Consider your storage costs and limits\n * - Factor in network upload speeds for users\n * - Account for processing requirements (thumbnails, compression)\n * - Set reasonable limits based on actual use cases\n *\n * @default 10 MB\n *\n * @example 1 // 1MB for small images\n * @example 25 // 25MB for documents\n * @example 100 // 100MB for media files\n */\n maxSize?: number;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class BucketPrimitive extends Primitive<BucketPrimitiveOptions> {\n public readonly provider = this.$provider();\n protected readonly fileSystem = $inject(FileSystemProvider);\n\n public get name() {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n /**\n * Uploads a file to the bucket.\n */\n public async upload(\n file: FileLike,\n options?: BucketFileOptions,\n ): Promise<string> {\n if (file instanceof File) {\n // our createFile is smarter than the browser's File constructor\n // by doing this, we can guess the MIME type and size!\n file = this.fileSystem.createFile({ file });\n }\n\n options = {\n ...this.options,\n ...options,\n };\n\n const mimeTypes = options.mimeTypes ?? undefined;\n const maxSize = options.maxSize ?? 10; // Default to 10 MB if not specified\n\n if (mimeTypes) {\n const mimeType = file.type || \"application/octet-stream\";\n if (!mimeTypes.includes(mimeType)) {\n throw new InvalidFileError(\n `MIME type ${mimeType} is not allowed in bucket ${this.name}`,\n );\n }\n }\n\n // check size in bytes, convert MB to bytes\n if (file.size > maxSize * 1024 * 1024) {\n throw new InvalidFileError(\n `File size ${file.size} exceeds the maximum size of ${maxSize} MB in bucket ${this.name}`,\n );\n }\n\n const id = await this.provider.upload(this.name, file);\n\n await this.alepha.events.emit(\"bucket:file:uploaded\", {\n id,\n bucket: this,\n file,\n options,\n });\n\n return id;\n }\n\n /**\n * Delete permanently a file from the bucket.\n */\n public async delete(fileId: string, skipHook = false): Promise<void> {\n await this.provider.delete(this.name, fileId);\n\n if (skipHook) {\n return;\n }\n\n await this.alepha.events.emit(\"bucket:file:deleted\", {\n id: fileId,\n bucket: this,\n });\n }\n\n /**\n * Checks if a file exists in the bucket.\n */\n public async exists(fileId: string): Promise<boolean> {\n return this.provider.exists(this.name, fileId);\n }\n\n /**\n * Downloads a file from the bucket.\n */\n public async download(fileId: string): Promise<FileLike> {\n const file = await this.provider.download(this.name, fileId);\n\n await this.alepha.events.emit(\"bucket:file:downloaded\", {\n id: fileId,\n bucket: this,\n file,\n });\n\n return file;\n }\n\n protected $provider() {\n if (!this.options.provider) {\n return this.alepha.inject(FileStorageProvider);\n }\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryFileStorageProvider);\n }\n return this.alepha.inject(this.options.provider);\n }\n}\n\n$bucket[KIND] = BucketPrimitive;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface BucketFileOptions {\n /**\n * Optional description of the bucket.\n */\n description?: string;\n\n /**\n * Allowed MIME types.\n */\n mimeTypes?: string[];\n\n /**\n * Maximum size of the files in the bucket.\n *\n * @default 10\n */\n maxSize?: number;\n}\n","import type { R2Bucket } from \"@cloudflare/workers-types\";\nimport {\n $env,\n $hook,\n $inject,\n Alepha,\n AlephaError,\n type FileLike,\n t,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { FileNotFoundError } from \"../errors/FileNotFoundError.ts\";\nimport { $bucket } from \"../primitives/$bucket.ts\";\nimport type { FileStorageProvider } from \"./FileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Cloudflare R2 storage provider.\n *\n * Uses a single R2 bucket binding for all $bucket primitives.\n * Files are organized as: {APP_NAME}/{bucketName}/{fileId}\n *\n * **Required environment variables:**\n * - `R2_BUCKET_NAME` - The actual R2 bucket name in Cloudflare\n *\n * **Optional (uses core Alepha env):**\n * - `APP_NAME` - Prefix for all files (for multi-app setups sharing one R2 bucket)\n *\n * @example\n * ```bash\n * # .env\n * APP_NAME=myapp # optional, used as prefix\n * R2_BUCKET_NAME=storage\n * ```\n *\n * @example\n * ```toml\n * # wrangler.toml - automatically generated by alepha build\n * [[r2_buckets]]\n * binding = \"R2\"\n * bucket_name = \"storage\"\n * ```\n *\n * @example\n * ```ts\n * // Define buckets with validation rules\n * const avatars = $bucket({\n * name: \"avatars\",\n * maxFileSize: 5 * 1024 * 1024,\n * allowedMimeTypes: [\"image/*\"],\n * });\n *\n * const documents = $bucket({\n * name: \"documents\",\n * maxFileSize: 50 * 1024 * 1024,\n * allowedMimeTypes: [\"application/pdf\"],\n * });\n *\n * // Files stored at: myapp/avatars/uuid.png, myapp/documents/uuid.pdf\n * ```\n */\nexport class CloudflareR2Provider implements FileStorageProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly env = $env(\n t.object({\n /**\n * The actual R2 bucket name in Cloudflare.\n */\n R2_BUCKET_NAME: t.string({\n description: \"R2 bucket name in Cloudflare\",\n }),\n }),\n );\n\n protected r2?: R2Bucket;\n\n /**\n * Get the R2 bucket name from environment.\n */\n public get bucketName(): string {\n return this.env.R2_BUCKET_NAME;\n }\n\n /**\n * Get the optional prefix from APP_NAME environment variable.\n * Used for multi-app setups sharing the same R2 bucket.\n */\n public get prefix(): string | undefined {\n return this.alepha.env.APP_NAME;\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n const cloudflareEnv = this.alepha.get(\"cloudflare.env\") as\n | Record<string, unknown>\n | undefined;\n if (!cloudflareEnv) {\n throw new AlephaError(\n \"Cloudflare Workers environment not found in Alepha store under 'cloudflare.env'.\",\n );\n }\n\n const binding = cloudflareEnv[this.bucketName] as R2Bucket | undefined;\n if (!binding) {\n throw new AlephaError(\n `R2 binding '${this.bucketName}' not found in Cloudflare Workers environment.`,\n );\n }\n\n this.r2 = binding;\n\n const prefixStr = this.prefix ? `${this.prefix}/` : \"\";\n this.log.info(\n `R2 storage ready (bucket: ${this.bucketName}, prefix: ${prefixStr || \"(none)\"})`,\n );\n\n for (const bucket of this.alepha.primitives($bucket)) {\n if (bucket.provider !== this) {\n continue;\n }\n this.log.debug(\n `Bucket '${bucket.name}' -> ${prefixStr}${bucket.name}/`,\n );\n }\n },\n });\n\n public async upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string> {\n const r2 = this.getR2();\n fileId ??= this.createId(file.name);\n const key = this.key(bucketName, fileId);\n\n this.log.trace(`Uploading '${key}'`);\n\n const arrayBuffer = await file.arrayBuffer();\n\n await r2.put(key, arrayBuffer, {\n httpMetadata: {\n contentType: file.type,\n },\n customMetadata: {\n originalName: file.name,\n bucket: bucketName,\n },\n });\n\n return fileId;\n }\n\n public async download(bucketName: string, fileId: string): Promise<FileLike> {\n const r2 = this.getR2();\n const key = this.key(bucketName, fileId);\n\n this.log.trace(`Downloading '${key}'`);\n\n const object = await r2.get(key);\n if (!object) {\n throw new FileNotFoundError(\n `File '${fileId}' not found in bucket '${bucketName}'.`,\n );\n }\n\n const originalName = object.customMetadata?.originalName ?? fileId;\n const contentType =\n object.httpMetadata?.contentType ?? \"application/octet-stream\";\n\n return {\n name: originalName,\n type: contentType,\n size: object.size,\n lastModified: object.uploaded.getTime(),\n stream: () => object.body as unknown as ReadableStream,\n arrayBuffer: () => object.arrayBuffer(),\n text: () => object.text(),\n };\n }\n\n public async exists(bucketName: string, fileId: string): Promise<boolean> {\n const r2 = this.getR2();\n const key = this.key(bucketName, fileId);\n\n this.log.trace(`Checking '${key}'`);\n\n const object = await r2.head(key);\n return object !== null;\n }\n\n public async delete(bucketName: string, fileId: string): Promise<void> {\n const r2 = this.getR2();\n const key = this.key(bucketName, fileId);\n\n this.log.trace(`Deleting '${key}'`);\n\n // R2's delete doesn't throw if the file doesn't exist,\n // so we check existence first for consistency with other providers\n const exists = await this.exists(bucketName, fileId);\n if (!exists) {\n throw new FileNotFoundError(\n `File '${fileId}' not found in bucket '${bucketName}'.`,\n );\n }\n\n await r2.delete(key);\n }\n\n /**\n * Build the full R2 key: {prefix}/{bucketName}/{fileId}\n */\n protected key(bucketName: string, fileId: string): string {\n const parts = [bucketName, fileId];\n if (this.prefix) {\n parts.unshift(this.prefix);\n }\n return parts.join(\"/\");\n }\n\n protected getR2(): R2Bucket {\n if (!this.r2) {\n throw new AlephaError(\"R2 storage not initialized. Call start() first.\");\n }\n return this.r2;\n }\n\n protected createId(filename: string): string {\n const ext = filename.includes(\".\") ? filename.split(\".\").pop() : \"\";\n const id = crypto.randomUUID();\n return ext ? `${id}.${ext}` : id;\n }\n}\n","import { $module } from \"alepha\";\nimport { $bucket } from \"./primitives/$bucket.ts\";\nimport { CloudflareR2Provider } from \"./providers/CloudflareR2Provider.ts\";\nimport { FileStorageProvider } from \"./providers/FileStorageProvider.ts\";\nimport { MemoryFileStorageProvider } from \"./providers/MemoryFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./errors/FileNotFoundError.ts\";\nexport * from \"./primitives/$bucket.ts\";\nexport * from \"./providers/CloudflareR2Provider.ts\";\nexport * from \"./providers/FileStorageProvider.ts\";\nexport * from \"./providers/MemoryFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const AlephaBucket = $module({\n name: \"alepha.bucket\",\n primitives: [$bucket],\n services: [\n FileStorageProvider,\n MemoryFileStorageProvider,\n CloudflareR2Provider,\n ],\n register: (alepha) => {\n alepha.with({\n optional: true,\n provide: FileStorageProvider,\n use: alepha.isTest() ? MemoryFileStorageProvider : CloudflareR2Provider,\n });\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.workerd.js","names":[],"sources":["../../src/bucket/errors/InvalidFileError.ts","../../src/bucket/providers/FileStorageProvider.ts","../../src/bucket/errors/FileNotFoundError.ts","../../src/bucket/providers/MemoryFileStorageProvider.ts","../../src/bucket/primitives/$bucket.ts","../../src/bucket/providers/CloudflareR2Provider.ts","../../src/bucket/index.workerd.ts"],"sourcesContent":["import { AlephaError } from \"alepha\";\n\nexport class InvalidFileError extends AlephaError {\n public readonly status = 400;\n}\n","import type { FileLike } from \"alepha\";\n\nexport abstract class FileStorageProvider {\n /**\n * Uploads a file to the storage.\n *\n * @param bucketName - Container name\n * @param file - File to upload\n * @param fileId - Optional file identifier. If not provided, a unique ID will be generated.\n * @return The identifier of the uploaded file.\n */\n abstract upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string>;\n\n /**\n * Downloads a file from the storage.\n *\n * @param bucketName - Container name\n * @param fileId - Identifier of the file to download\n * @return The downloaded file as a FileLike object.\n */\n abstract download(bucketName: string, fileId: string): Promise<FileLike>;\n\n /**\n * Check if fileId exists in the storage bucket.\n *\n * @param bucketName - Container name\n * @param fileId - Identifier of the file to stream\n * @return True is the file exists, false otherwise.\n */\n abstract exists(bucketName: string, fileId: string): Promise<boolean>;\n\n /**\n * Delete permanently a file from the storage.\n *\n * @param bucketName - Container name\n * @param fileId - Identifier of the file to delete\n */\n abstract delete(bucketName: string, fileId: string): Promise<void>;\n\n /**\n * Delete multiple files in one round-trip where the provider supports\n * batch (R2/S3, up to 1000 per call). Memory/Local fall back to a loop.\n *\n * @param bucketName - Container name\n * @param fileIds - Identifiers of the files to delete\n */\n abstract deleteMany(bucketName: string, fileIds: string[]): Promise<void>;\n}\n","import { AlephaError } from \"alepha\";\n\nexport class FileNotFoundError extends AlephaError {\n public readonly status = 404;\n}\n","import { randomUUID } from \"node:crypto\";\nimport { $inject, type FileLike } from \"alepha\";\nimport { FileDetector, FileSystemProvider } from \"alepha/system\";\nimport { FileNotFoundError } from \"../errors/FileNotFoundError.ts\";\nimport type { FileStorageProvider } from \"./FileStorageProvider.ts\";\n\ninterface StoredFile {\n buffer: Buffer;\n name: string;\n type: string;\n size: number;\n}\n\nexport class MemoryFileStorageProvider implements FileStorageProvider {\n public readonly files: Record<string, StoredFile> = {};\n protected readonly fileSystem = $inject(FileSystemProvider);\n protected readonly fileDetector = $inject(FileDetector);\n\n public async upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string> {\n fileId ??= this.createId();\n\n // Consume the stream and store as buffer so downloads are repeatable\n const chunks: Uint8Array[] = [];\n for await (const chunk of file.stream() as AsyncIterable<Uint8Array>) {\n chunks.push(chunk);\n }\n const buffer = Buffer.concat(chunks);\n\n this.files[`${bucketName}/${fileId}`] = {\n buffer,\n name: file.name,\n type: file.type,\n size: file.size,\n };\n\n return fileId;\n }\n\n public async download(bucketName: string, fileId: string): Promise<FileLike> {\n const fileKey = `${bucketName}/${fileId}`;\n const stored = this.files[fileKey];\n\n if (!stored) {\n throw new FileNotFoundError(`File with ID ${fileId} not found.`);\n }\n\n // Create a fresh FileLike with a new stream from the stored buffer\n return this.fileSystem.createFile({\n stream: new Blob([new Uint8Array(stored.buffer)]).stream(),\n name: stored.name,\n type: stored.type,\n size: stored.size,\n });\n }\n\n public async exists(bucketName: string, fileId: string): Promise<boolean> {\n return `${bucketName}/${fileId}` in this.files;\n }\n\n public async delete(bucketName: string, fileId: string): Promise<void> {\n const fileKey = `${bucketName}/${fileId}`;\n if (!(fileKey in this.files)) {\n throw new FileNotFoundError(`File with ID ${fileId} not found.`);\n }\n\n delete this.files[fileKey];\n }\n\n public async deleteMany(\n bucketName: string,\n fileIds: string[],\n ): Promise<void> {\n for (const id of fileIds) {\n delete this.files[`${bucketName}/${id}`];\n }\n }\n\n protected createId(): string {\n return randomUUID();\n }\n}\n","import {\n $inject,\n createPrimitive,\n type FileLike,\n KIND,\n Primitive,\n type Service,\n} from \"alepha\";\nimport { FileSystemProvider } from \"alepha/system\";\nimport { InvalidFileError } from \"../errors/InvalidFileError.ts\";\nimport { FileStorageProvider } from \"../providers/FileStorageProvider.ts\";\nimport { MemoryFileStorageProvider } from \"../providers/MemoryFileStorageProvider.ts\";\n\n/**\n * Creates a bucket primitive for file storage and management with configurable validation.\n *\n * Provides a comprehensive file storage system that handles uploads, downloads, validation,\n * and management across multiple storage backends with MIME type and size limit controls.\n *\n * **Key Features**\n * - Multi-provider support (filesystem, cloud storage, in-memory)\n * - Automatic MIME type and file size validation\n * - Event integration for file operations monitoring\n * - Flexible per-bucket and per-operation configuration\n * - Smart file type and size detection\n *\n * **Common Use Cases**\n * - User profile pictures and document uploads\n * - Product images and media management\n * - Document storage and retrieval systems\n *\n * @example\n * ```ts\n * class MediaService {\n * images = $bucket({\n * name: \"user-images\",\n * mimeTypes: [\"image/jpeg\", \"image/png\", \"image/gif\"],\n * maxSize: 5 // 5MB limit\n * });\n *\n * documents = $bucket({\n * name: \"documents\",\n * mimeTypes: [\"application/pdf\", \"text/plain\"],\n * maxSize: 50 // 50MB limit\n * });\n *\n * async uploadProfileImage(file: FileLike, userId: string): Promise<string> {\n * const fileId = await this.images.upload(file);\n * await this.userService.updateProfileImage(userId, fileId);\n * return fileId;\n * }\n *\n * async downloadDocument(documentId: string): Promise<FileLike> {\n * return await this.documents.download(documentId);\n * }\n *\n * async deleteDocument(documentId: string): Promise<void> {\n * await this.documents.delete(documentId);\n * }\n * }\n * ```\n */\nexport const $bucket = (options: BucketPrimitiveOptions) =>\n createPrimitive(BucketPrimitive, options);\n\nexport interface BucketPrimitiveOptions extends BucketFileOptions {\n /**\n * File storage provider configuration for the bucket.\n *\n * Options:\n * - **\"memory\"**: In-memory storage (default for development, lost on restart)\n * - **Service<FileStorageProvider>**: Custom provider class (e.g., S3FileStorageProvider, AzureBlobProvider)\n * - **undefined**: Uses the default file storage provider from dependency injection\n *\n * **Provider Selection Guidelines**:\n * - **Development**: Use \"memory\" for fast, simple testing without external dependencies\n * - **Production**: Use cloud providers (S3, Azure Blob, Google Cloud Storage) for scalability\n * - **Local deployment**: Use filesystem providers for on-premise installations\n * - **Hybrid**: Use different providers for different bucket types (temp files vs permanent storage)\n *\n * **Provider Capabilities**:\n * - File persistence and durability guarantees\n * - Scalability and performance characteristics\n * - Geographic distribution and CDN integration\n * - Cost implications for storage and bandwidth\n * - Backup and disaster recovery features\n *\n * @default Uses injected FileStorageProvider\n * @example \"memory\"\n * @example S3FileStorageProvider\n * @example AzureBlobStorageProvider\n */\n provider?: Service<FileStorageProvider> | \"memory\";\n\n /**\n * Unique name identifier for the bucket.\n *\n * This name is used for:\n * - Storage backend organization and partitioning\n * - File path generation and URL construction\n * - Logging, monitoring, and debugging\n * - Access control and permissions management\n * - Backup and replication configuration\n *\n * **Naming Conventions**:\n * - Use lowercase with hyphens for consistency\n * - Include purpose or content type in the name\n * - Avoid spaces and special characters\n * - Consider environment prefixes for deployment isolation\n *\n * If not provided, defaults to the property key where the bucket is declared.\n *\n * @example \"user-avatars\"\n * @example \"product-images\"\n * @example \"legal-documents\"\n * @example \"temp-processing-files\"\n */\n name?: string;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface BucketFileOptions {\n /**\n * Human-readable description of the bucket's purpose and contents.\n *\n * Used for:\n * - Documentation generation and API references\n * - Developer onboarding and system understanding\n * - Monitoring dashboards and admin interfaces\n * - Compliance and audit documentation\n *\n * **Description Best Practices**:\n * - Explain what types of files this bucket stores\n * - Mention any special handling or processing requirements\n * - Include information about retention policies if applicable\n * - Note any compliance or security considerations\n *\n * @example \"User profile pictures and avatar images\"\n * @example \"Product catalog images with automated thumbnail generation\"\n * @example \"Legal documents requiring long-term retention\"\n * @example \"Temporary files for data processing workflows\"\n */\n description?: string;\n\n /**\n * Array of allowed MIME types for files uploaded to this bucket.\n *\n * When specified, only files with these exact MIME types will be accepted.\n * Files with disallowed MIME types will be rejected with an InvalidFileError.\n *\n * **MIME Type Categories**:\n * - Images: \"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\", \"image/svg+xml\"\n * - Documents: \"application/pdf\", \"text/plain\", \"text/csv\"\n * - Office: \"application/msword\", \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\"\n * - Archives: \"application/zip\", \"application/x-tar\", \"application/gzip\"\n * - Media: \"video/mp4\", \"audio/mpeg\", \"audio/wav\"\n *\n * **Security Considerations**:\n * - Always validate MIME types for user uploads\n * - Be cautious with executable file types\n * - Consider using allow-lists rather than deny-lists\n * - Remember that MIME types can be spoofed by malicious users\n *\n * If not specified, all MIME types are allowed (not recommended for user uploads).\n *\n * @example [\"image/jpeg\", \"image/png\"] // Only JPEG and PNG images\n * @example [\"application/pdf\", \"text/plain\"] // Documents only\n * @example [\"video/mp4\", \"video/webm\"] // Video files\n */\n mimeTypes?: string[];\n\n /**\n * Maximum file size allowed in megabytes (MB).\n *\n * Files larger than this limit will be rejected with an InvalidFileError.\n * This helps prevent:\n * - Storage quota exhaustion\n * - Memory issues during file processing\n * - Long upload times and timeouts\n * - Abuse of storage resources\n *\n * **Size Guidelines by File Type**:\n * - Profile images: 1-5 MB\n * - Product photos: 5-10 MB\n * - Documents: 10-50 MB\n * - Video files: 50-500 MB\n * - Data files: 100-1000 MB\n *\n * **Considerations**:\n * - Consider your storage costs and limits\n * - Factor in network upload speeds for users\n * - Account for processing requirements (thumbnails, compression)\n * - Set reasonable limits based on actual use cases\n *\n * @default 10 MB\n *\n * @example 1 // 1MB for small images\n * @example 25 // 25MB for documents\n * @example 100 // 100MB for media files\n */\n maxSize?: number;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class BucketPrimitive extends Primitive<BucketPrimitiveOptions> {\n public readonly provider = this.$provider();\n protected readonly fileSystem = $inject(FileSystemProvider);\n\n public get name() {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n /**\n * Uploads a file to the bucket.\n */\n public async upload(\n file: FileLike,\n options?: BucketFileOptions,\n ): Promise<string> {\n if (file instanceof File) {\n // our createFile is smarter than the browser's File constructor\n // by doing this, we can guess the MIME type and size!\n file = this.fileSystem.createFile({ file });\n }\n\n options = {\n ...this.options,\n ...options,\n };\n\n const mimeTypes = options.mimeTypes ?? undefined;\n const maxSize = options.maxSize ?? 10; // Default to 10 MB if not specified\n\n if (mimeTypes) {\n const mimeType = file.type || \"application/octet-stream\";\n if (!mimeTypes.includes(mimeType)) {\n throw new InvalidFileError(\n `MIME type ${mimeType} is not allowed in bucket ${this.name}`,\n );\n }\n }\n\n // check size in bytes, convert MB to bytes\n if (file.size > maxSize * 1024 * 1024) {\n throw new InvalidFileError(\n `File size ${file.size} exceeds the maximum size of ${maxSize} MB in bucket ${this.name}`,\n );\n }\n\n const id = await this.provider.upload(this.name, file);\n\n await this.alepha.events.emit(\"bucket:file:uploaded\", {\n id,\n bucket: this,\n file,\n options,\n });\n\n return id;\n }\n\n /**\n * Delete permanently a file from the bucket.\n */\n public async delete(fileId: string, skipHook = false): Promise<void> {\n await this.provider.delete(this.name, fileId);\n\n if (skipHook) {\n return;\n }\n\n await this.alepha.events.emit(\"bucket:file:deleted\", {\n id: fileId,\n bucket: this,\n });\n }\n\n /**\n * Delete many files in one round-trip when the underlying provider supports\n * batch (R2/S3 up to 1000 keys per call). Emits one `bucket:file:deleted`\n * event per id unless `skipHook` is set.\n */\n public async deleteMany(fileIds: string[], skipHook = false): Promise<void> {\n if (fileIds.length === 0) return;\n await this.provider.deleteMany(this.name, fileIds);\n\n if (skipHook) {\n return;\n }\n\n for (const id of fileIds) {\n await this.alepha.events.emit(\"bucket:file:deleted\", {\n id,\n bucket: this,\n });\n }\n }\n\n /**\n * Checks if a file exists in the bucket.\n */\n public async exists(fileId: string): Promise<boolean> {\n return this.provider.exists(this.name, fileId);\n }\n\n /**\n * Downloads a file from the bucket.\n */\n public async download(fileId: string): Promise<FileLike> {\n const file = await this.provider.download(this.name, fileId);\n\n await this.alepha.events.emit(\"bucket:file:downloaded\", {\n id: fileId,\n bucket: this,\n file,\n });\n\n return file;\n }\n\n protected $provider() {\n if (!this.options.provider) {\n return this.alepha.inject(FileStorageProvider);\n }\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryFileStorageProvider);\n }\n return this.alepha.inject(this.options.provider);\n }\n}\n\n$bucket[KIND] = BucketPrimitive;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface BucketFileOptions {\n /**\n * Optional description of the bucket.\n */\n description?: string;\n\n /**\n * Allowed MIME types.\n */\n mimeTypes?: string[];\n\n /**\n * Maximum size of the files in the bucket.\n *\n * @default 10\n */\n maxSize?: number;\n}\n","import type { R2Bucket } from \"@cloudflare/workers-types\";\nimport {\n $env,\n $hook,\n $inject,\n Alepha,\n AlephaError,\n type FileLike,\n t,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { FileNotFoundError } from \"../errors/FileNotFoundError.ts\";\nimport { $bucket } from \"../primitives/$bucket.ts\";\nimport type { FileStorageProvider } from \"./FileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Cloudflare R2 storage provider.\n *\n * Uses a single R2 bucket binding for all $bucket primitives.\n * Files are organized as: {APP_NAME}/{bucketName}/{fileId}\n *\n * **Required environment variables:**\n * - `R2_BUCKET_NAME` - The actual R2 bucket name in Cloudflare\n *\n * **Optional (uses core Alepha env):**\n * - `APP_NAME` - Prefix for all files (for multi-app setups sharing one R2 bucket)\n *\n * @example\n * ```bash\n * # .env\n * APP_NAME=myapp # optional, used as prefix\n * R2_BUCKET_NAME=storage\n * ```\n *\n * @example\n * ```toml\n * # wrangler.toml - automatically generated by alepha build\n * [[r2_buckets]]\n * binding = \"R2\"\n * bucket_name = \"storage\"\n * ```\n *\n * @example\n * ```ts\n * // Define buckets with validation rules\n * const avatars = $bucket({\n * name: \"avatars\",\n * maxFileSize: 5 * 1024 * 1024,\n * allowedMimeTypes: [\"image/*\"],\n * });\n *\n * const documents = $bucket({\n * name: \"documents\",\n * maxFileSize: 50 * 1024 * 1024,\n * allowedMimeTypes: [\"application/pdf\"],\n * });\n *\n * // Files stored at: myapp/avatars/uuid.png, myapp/documents/uuid.pdf\n * ```\n */\nexport class CloudflareR2Provider implements FileStorageProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly env = $env(\n t.object({\n /**\n * The actual R2 bucket name in Cloudflare.\n */\n R2_BUCKET_NAME: t.string({\n description: \"R2 bucket name in Cloudflare\",\n }),\n }),\n );\n\n protected r2?: R2Bucket;\n\n /**\n * Get the R2 bucket name from environment.\n */\n public get bucketName(): string {\n return this.env.R2_BUCKET_NAME;\n }\n\n /**\n * Get the optional prefix from APP_NAME environment variable.\n * Used for multi-app setups sharing the same R2 bucket.\n */\n public get prefix(): string | undefined {\n return this.alepha.env.APP_NAME;\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n const cloudflareEnv = this.alepha.get(\"cloudflare.env\") as\n | Record<string, unknown>\n | undefined;\n if (!cloudflareEnv) {\n throw new AlephaError(\n \"Cloudflare Workers environment not found in Alepha store under 'cloudflare.env'.\",\n );\n }\n\n const binding = cloudflareEnv[this.bucketName] as R2Bucket | undefined;\n if (!binding) {\n throw new AlephaError(\n `R2 binding '${this.bucketName}' not found in Cloudflare Workers environment.`,\n );\n }\n\n this.r2 = binding;\n\n const prefixStr = this.prefix ? `${this.prefix}/` : \"\";\n this.log.info(\n `R2 storage ready (bucket: ${this.bucketName}, prefix: ${prefixStr || \"(none)\"})`,\n );\n\n for (const bucket of this.alepha.primitives($bucket)) {\n if (bucket.provider !== this) {\n continue;\n }\n this.log.debug(\n `Bucket '${bucket.name}' -> ${prefixStr}${bucket.name}/`,\n );\n }\n },\n });\n\n public async upload(\n bucketName: string,\n file: FileLike,\n fileId?: string,\n ): Promise<string> {\n const r2 = this.getR2();\n fileId ??= this.createId(file.name);\n const key = this.key(bucketName, fileId);\n\n this.log.trace(`Uploading '${key}'`);\n\n const arrayBuffer = await file.arrayBuffer();\n\n await r2.put(key, arrayBuffer, {\n httpMetadata: {\n contentType: file.type,\n },\n customMetadata: {\n originalName: file.name,\n bucket: bucketName,\n },\n });\n\n return fileId;\n }\n\n public async download(bucketName: string, fileId: string): Promise<FileLike> {\n const r2 = this.getR2();\n const key = this.key(bucketName, fileId);\n\n this.log.trace(`Downloading '${key}'`);\n\n const object = await r2.get(key);\n if (!object) {\n throw new FileNotFoundError(\n `File '${fileId}' not found in bucket '${bucketName}'.`,\n );\n }\n\n const originalName = object.customMetadata?.originalName ?? fileId;\n const contentType =\n object.httpMetadata?.contentType ?? \"application/octet-stream\";\n\n return {\n name: originalName,\n type: contentType,\n size: object.size,\n lastModified: object.uploaded.getTime(),\n stream: () => object.body as unknown as ReadableStream,\n arrayBuffer: () => object.arrayBuffer(),\n text: () => object.text(),\n };\n }\n\n public async exists(bucketName: string, fileId: string): Promise<boolean> {\n const r2 = this.getR2();\n const key = this.key(bucketName, fileId);\n\n this.log.trace(`Checking '${key}'`);\n\n const object = await r2.head(key);\n return object !== null;\n }\n\n public async delete(bucketName: string, fileId: string): Promise<void> {\n const r2 = this.getR2();\n const key = this.key(bucketName, fileId);\n\n this.log.trace(`Deleting '${key}'`);\n\n // R2's delete doesn't throw if the file doesn't exist,\n // so we check existence first for consistency with other providers\n const exists = await this.exists(bucketName, fileId);\n if (!exists) {\n throw new FileNotFoundError(\n `File '${fileId}' not found in bucket '${bucketName}'.`,\n );\n }\n\n await r2.delete(key);\n }\n\n public async deleteMany(\n bucketName: string,\n fileIds: string[],\n ): Promise<void> {\n if (fileIds.length === 0) return;\n const r2 = this.getR2();\n const keys = fileIds.map((id) => this.key(bucketName, id));\n this.log.trace(`Deleting ${keys.length} keys from '${bucketName}'`);\n // R2 binding accepts a string[] and silently ignores missing keys.\n // Chunk at 1000 to stay within the documented batch limit.\n for (let i = 0; i < keys.length; i += 1000) {\n await r2.delete(keys.slice(i, i + 1000));\n }\n }\n\n /**\n * Build the full R2 key: {prefix}/{bucketName}/{fileId}\n */\n protected key(bucketName: string, fileId: string): string {\n const parts = [bucketName, fileId];\n if (this.prefix) {\n parts.unshift(this.prefix);\n }\n return parts.join(\"/\");\n }\n\n protected getR2(): R2Bucket {\n if (!this.r2) {\n throw new AlephaError(\"R2 storage not initialized. Call start() first.\");\n }\n return this.r2;\n }\n\n protected createId(filename: string): string {\n const ext = filename.includes(\".\") ? filename.split(\".\").pop() : \"\";\n const id = crypto.randomUUID();\n return ext ? `${id}.${ext}` : id;\n }\n}\n","import { $module } from \"alepha\";\nimport { $bucket } from \"./primitives/$bucket.ts\";\nimport { CloudflareR2Provider } from \"./providers/CloudflareR2Provider.ts\";\nimport { FileStorageProvider } from \"./providers/FileStorageProvider.ts\";\nimport { MemoryFileStorageProvider } from \"./providers/MemoryFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./errors/FileNotFoundError.ts\";\nexport * from \"./primitives/$bucket.ts\";\nexport * from \"./providers/CloudflareR2Provider.ts\";\nexport * from \"./providers/FileStorageProvider.ts\";\nexport * from \"./providers/MemoryFileStorageProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const AlephaBucket = $module({\n name: \"alepha.bucket\",\n primitives: [$bucket],\n services: [\n FileStorageProvider,\n MemoryFileStorageProvider,\n CloudflareR2Provider,\n ],\n register: (alepha) => {\n alepha.with({\n optional: true,\n provide: FileStorageProvider,\n use: alepha.isTest() ? MemoryFileStorageProvider : CloudflareR2Provider,\n });\n },\n});\n"],"mappings":";;;;;AAEA,IAAa,mBAAb,cAAsC,YAAY;CAChD,SAAyB;;;;ACD3B,IAAsB,sBAAtB,MAA0C;;;ACA1C,IAAa,oBAAb,cAAuC,YAAY;CACjD,SAAyB;;;;ACU3B,IAAa,4BAAb,MAAsE;CACpE,QAAoD,EAAE;CACtD,aAAgC,QAAQ,mBAAmB;CAC3D,eAAkC,QAAQ,aAAa;CAEvD,MAAa,OACX,YACA,MACA,QACiB;EACjB,WAAW,KAAK,UAAU;EAG1B,MAAM,SAAuB,EAAE;EAC/B,WAAW,MAAM,SAAS,KAAK,QAAQ,EACrC,OAAO,KAAK,MAAM;EAEpB,MAAM,SAAS,OAAO,OAAO,OAAO;EAEpC,KAAK,MAAM,GAAG,WAAW,GAAG,YAAY;GACtC;GACA,MAAM,KAAK;GACX,MAAM,KAAK;GACX,MAAM,KAAK;GACZ;EAED,OAAO;;CAGT,MAAa,SAAS,YAAoB,QAAmC;EAC3E,MAAM,UAAU,GAAG,WAAW,GAAG;EACjC,MAAM,SAAS,KAAK,MAAM;EAE1B,IAAI,CAAC,QACH,MAAM,IAAI,kBAAkB,gBAAgB,OAAO,aAAa;EAIlE,OAAO,KAAK,WAAW,WAAW;GAChC,QAAQ,IAAI,KAAK,CAAC,IAAI,WAAW,OAAO,OAAO,CAAC,CAAC,CAAC,QAAQ;GAC1D,MAAM,OAAO;GACb,MAAM,OAAO;GACb,MAAM,OAAO;GACd,CAAC;;CAGJ,MAAa,OAAO,YAAoB,QAAkC;EACxE,OAAO,GAAG,WAAW,GAAG,YAAY,KAAK;;CAG3C,MAAa,OAAO,YAAoB,QAA+B;EACrE,MAAM,UAAU,GAAG,WAAW,GAAG;EACjC,IAAI,EAAE,WAAW,KAAK,QACpB,MAAM,IAAI,kBAAkB,gBAAgB,OAAO,aAAa;EAGlE,OAAO,KAAK,MAAM;;CAGpB,MAAa,WACX,YACA,SACe;EACf,KAAK,MAAM,MAAM,SACf,OAAO,KAAK,MAAM,GAAG,WAAW,GAAG;;CAIvC,WAA6B;EAC3B,OAAO,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACpBvB,MAAa,WAAW,YACtB,gBAAgB,iBAAiB,QAAQ;AA+I3C,IAAa,kBAAb,cAAqC,UAAkC;CACrE,WAA2B,KAAK,WAAW;CAC3C,aAAgC,QAAQ,mBAAmB;CAE3D,IAAW,OAAO;EAChB,OAAO,KAAK,QAAQ,QAAQ,GAAG,KAAK,OAAO;;;;;CAM7C,MAAa,OACX,MACA,SACiB;EACjB,IAAI,gBAAgB,MAGlB,OAAO,KAAK,WAAW,WAAW,EAAE,MAAM,CAAC;EAG7C,UAAU;GACR,GAAG,KAAK;GACR,GAAG;GACJ;EAED,MAAM,YAAY,QAAQ,aAAa,KAAA;EACvC,MAAM,UAAU,QAAQ,WAAW;EAEnC,IAAI,WAAW;GACb,MAAM,WAAW,KAAK,QAAQ;GAC9B,IAAI,CAAC,UAAU,SAAS,SAAS,EAC/B,MAAM,IAAI,iBACR,aAAa,SAAS,4BAA4B,KAAK,OACxD;;EAKL,IAAI,KAAK,OAAO,UAAU,OAAO,MAC/B,MAAM,IAAI,iBACR,aAAa,KAAK,KAAK,+BAA+B,QAAQ,gBAAgB,KAAK,OACpF;EAGH,MAAM,KAAK,MAAM,KAAK,SAAS,OAAO,KAAK,MAAM,KAAK;EAEtD,MAAM,KAAK,OAAO,OAAO,KAAK,wBAAwB;GACpD;GACA,QAAQ;GACR;GACA;GACD,CAAC;EAEF,OAAO;;;;;CAMT,MAAa,OAAO,QAAgB,WAAW,OAAsB;EACnE,MAAM,KAAK,SAAS,OAAO,KAAK,MAAM,OAAO;EAE7C,IAAI,UACF;EAGF,MAAM,KAAK,OAAO,OAAO,KAAK,uBAAuB;GACnD,IAAI;GACJ,QAAQ;GACT,CAAC;;;;;;;CAQJ,MAAa,WAAW,SAAmB,WAAW,OAAsB;EAC1E,IAAI,QAAQ,WAAW,GAAG;EAC1B,MAAM,KAAK,SAAS,WAAW,KAAK,MAAM,QAAQ;EAElD,IAAI,UACF;EAGF,KAAK,MAAM,MAAM,SACf,MAAM,KAAK,OAAO,OAAO,KAAK,uBAAuB;GACnD;GACA,QAAQ;GACT,CAAC;;;;;CAON,MAAa,OAAO,QAAkC;EACpD,OAAO,KAAK,SAAS,OAAO,KAAK,MAAM,OAAO;;;;;CAMhD,MAAa,SAAS,QAAmC;EACvD,MAAM,OAAO,MAAM,KAAK,SAAS,SAAS,KAAK,MAAM,OAAO;EAE5D,MAAM,KAAK,OAAO,OAAO,KAAK,0BAA0B;GACtD,IAAI;GACJ,QAAQ;GACR;GACD,CAAC;EAEF,OAAO;;CAGT,YAAsB;EACpB,IAAI,CAAC,KAAK,QAAQ,UAChB,OAAO,KAAK,OAAO,OAAO,oBAAoB;EAEhD,IAAI,KAAK,QAAQ,aAAa,UAC5B,OAAO,KAAK,OAAO,OAAO,0BAA0B;EAEtD,OAAO,KAAK,OAAO,OAAO,KAAK,QAAQ,SAAS;;;AAIpD,QAAQ,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC/QhB,IAAa,uBAAb,MAAiE;CAC/D,SAA4B,QAAQ,OAAO;CAC3C,MAAyB,SAAS;CAClC,MAAyB,KACvB,EAAE,OAAO;;;;AAIP,gBAAgB,EAAE,OAAO,EACvB,aAAa,gCACd,CAAC,EACH,CAAC,CACH;CAED;;;;CAKA,IAAW,aAAqB;EAC9B,OAAO,KAAK,IAAI;;;;;;CAOlB,IAAW,SAA6B;EACtC,OAAO,KAAK,OAAO,IAAI;;CAGzB,UAA6B,MAAM;EACjC,IAAI;EACJ,SAAS,YAAY;GACnB,MAAM,gBAAgB,KAAK,OAAO,IAAI,iBAAiB;GAGvD,IAAI,CAAC,eACH,MAAM,IAAI,YACR,mFACD;GAGH,MAAM,UAAU,cAAc,KAAK;GACnC,IAAI,CAAC,SACH,MAAM,IAAI,YACR,eAAe,KAAK,WAAW,gDAChC;GAGH,KAAK,KAAK;GAEV,MAAM,YAAY,KAAK,SAAS,GAAG,KAAK,OAAO,KAAK;GACpD,KAAK,IAAI,KACP,6BAA6B,KAAK,WAAW,YAAY,aAAa,SAAS,GAChF;GAED,KAAK,MAAM,UAAU,KAAK,OAAO,WAAW,QAAQ,EAAE;IACpD,IAAI,OAAO,aAAa,MACtB;IAEF,KAAK,IAAI,MACP,WAAW,OAAO,KAAK,OAAO,YAAY,OAAO,KAAK,GACvD;;;EAGN,CAAC;CAEF,MAAa,OACX,YACA,MACA,QACiB;EACjB,MAAM,KAAK,KAAK,OAAO;EACvB,WAAW,KAAK,SAAS,KAAK,KAAK;EACnC,MAAM,MAAM,KAAK,IAAI,YAAY,OAAO;EAExC,KAAK,IAAI,MAAM,cAAc,IAAI,GAAG;EAEpC,MAAM,cAAc,MAAM,KAAK,aAAa;EAE5C,MAAM,GAAG,IAAI,KAAK,aAAa;GAC7B,cAAc,EACZ,aAAa,KAAK,MACnB;GACD,gBAAgB;IACd,cAAc,KAAK;IACnB,QAAQ;IACT;GACF,CAAC;EAEF,OAAO;;CAGT,MAAa,SAAS,YAAoB,QAAmC;EAC3E,MAAM,KAAK,KAAK,OAAO;EACvB,MAAM,MAAM,KAAK,IAAI,YAAY,OAAO;EAExC,KAAK,IAAI,MAAM,gBAAgB,IAAI,GAAG;EAEtC,MAAM,SAAS,MAAM,GAAG,IAAI,IAAI;EAChC,IAAI,CAAC,QACH,MAAM,IAAI,kBACR,SAAS,OAAO,yBAAyB,WAAW,IACrD;EAOH,OAAO;GACL,MALmB,OAAO,gBAAgB,gBAAgB;GAM1D,MAJA,OAAO,cAAc,eAAe;GAKpC,MAAM,OAAO;GACb,cAAc,OAAO,SAAS,SAAS;GACvC,cAAc,OAAO;GACrB,mBAAmB,OAAO,aAAa;GACvC,YAAY,OAAO,MAAM;GAC1B;;CAGH,MAAa,OAAO,YAAoB,QAAkC;EACxE,MAAM,KAAK,KAAK,OAAO;EACvB,MAAM,MAAM,KAAK,IAAI,YAAY,OAAO;EAExC,KAAK,IAAI,MAAM,aAAa,IAAI,GAAG;EAGnC,OAAO,MADc,GAAG,KAAK,IAAI,KACf;;CAGpB,MAAa,OAAO,YAAoB,QAA+B;EACrE,MAAM,KAAK,KAAK,OAAO;EACvB,MAAM,MAAM,KAAK,IAAI,YAAY,OAAO;EAExC,KAAK,IAAI,MAAM,aAAa,IAAI,GAAG;EAKnC,IAAI,CAAC,MADgB,KAAK,OAAO,YAAY,OAAO,EAElD,MAAM,IAAI,kBACR,SAAS,OAAO,yBAAyB,WAAW,IACrD;EAGH,MAAM,GAAG,OAAO,IAAI;;CAGtB,MAAa,WACX,YACA,SACe;EACf,IAAI,QAAQ,WAAW,GAAG;EAC1B,MAAM,KAAK,KAAK,OAAO;EACvB,MAAM,OAAO,QAAQ,KAAK,OAAO,KAAK,IAAI,YAAY,GAAG,CAAC;EAC1D,KAAK,IAAI,MAAM,YAAY,KAAK,OAAO,cAAc,WAAW,GAAG;EAGnE,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,KACpC,MAAM,GAAG,OAAO,KAAK,MAAM,GAAG,IAAI,IAAK,CAAC;;;;;CAO5C,IAAc,YAAoB,QAAwB;EACxD,MAAM,QAAQ,CAAC,YAAY,OAAO;EAClC,IAAI,KAAK,QACP,MAAM,QAAQ,KAAK,OAAO;EAE5B,OAAO,MAAM,KAAK,IAAI;;CAGxB,QAA4B;EAC1B,IAAI,CAAC,KAAK,IACR,MAAM,IAAI,YAAY,kDAAkD;EAE1E,OAAO,KAAK;;CAGd,SAAmB,UAA0B;EAC3C,MAAM,MAAM,SAAS,SAAS,IAAI,GAAG,SAAS,MAAM,IAAI,CAAC,KAAK,GAAG;EACjE,MAAM,KAAK,OAAO,YAAY;EAC9B,OAAO,MAAM,GAAG,GAAG,GAAG,QAAQ;;;;;ACxOlC,MAAa,eAAe,QAAQ;CAClC,MAAM;CACN,YAAY,CAAC,QAAQ;CACrB,UAAU;EACR;EACA;EACA;EACD;CACD,WAAW,WAAW;EACpB,OAAO,KAAK;GACV,UAAU;GACV,SAAS;GACT,KAAK,OAAO,QAAQ,GAAG,4BAA4B;GACpD,CAAC;;CAEL,CAAC"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as _$alepha from "alepha";
|
|
2
|
-
import { Alepha,
|
|
2
|
+
import { Alepha, MiddlewareMetadata, OPTIONS, Primitive, Service, Static } from "alepha";
|
|
3
3
|
import { DateTimeProvider, DurationLike, Timeout } from "alepha/datetime";
|
|
4
4
|
import * as _$alepha_logger0 from "alepha/logger";
|
|
5
|
+
import * as _$typebox from "typebox";
|
|
5
6
|
|
|
6
7
|
//#region ../../src/cache/core/providers/CacheProvider.d.ts
|
|
7
8
|
/**
|
|
@@ -136,6 +137,31 @@ declare function $cache<TReturn = string, TParameter extends any[] = any[]>(opti
|
|
|
136
137
|
}): CachePrimitiveFn<TReturn, TParameter>;
|
|
137
138
|
declare function $cache<TReturn = any, TParameter extends any[] = any[]>(options?: CachePrimitiveOptions<TReturn, TParameter>): CacheMiddlewareFn<TReturn>;
|
|
138
139
|
declare namespace $cache {}
|
|
140
|
+
/**
|
|
141
|
+
* Options for the in-memory L1 tier.
|
|
142
|
+
*/
|
|
143
|
+
interface CacheMemoryTierOptions {
|
|
144
|
+
/**
|
|
145
|
+
* TTL for the in-memory tier. Should be ≤ the remote `ttl`.
|
|
146
|
+
* Bounds the cross-isolate staleness window after invalidation.
|
|
147
|
+
*
|
|
148
|
+
* @default min(ttl, 30s)
|
|
149
|
+
*/
|
|
150
|
+
ttl?: DurationLike;
|
|
151
|
+
/**
|
|
152
|
+
* LRU bound — max entries kept in memory before eviction.
|
|
153
|
+
*
|
|
154
|
+
* @default 500
|
|
155
|
+
*/
|
|
156
|
+
max?: number;
|
|
157
|
+
/**
|
|
158
|
+
* Also cache provider misses (`undefined`) in memory for this duration.
|
|
159
|
+
* Prevents hammering the remote tier on cold/unknown keys.
|
|
160
|
+
*
|
|
161
|
+
* @default off
|
|
162
|
+
*/
|
|
163
|
+
negative?: DurationLike;
|
|
164
|
+
}
|
|
139
165
|
interface CachePrimitiveOptions<TReturn = any, TParameter extends any[] = any[]> {
|
|
140
166
|
/**
|
|
141
167
|
* The cache name. This is useful for invalidating multiple caches at once.
|
|
@@ -156,9 +182,26 @@ interface CachePrimitiveOptions<TReturn = any, TParameter extends any[] = any[]>
|
|
|
156
182
|
key?: (...args: TParameter) => string;
|
|
157
183
|
/**
|
|
158
184
|
* The store provider for the cache.
|
|
159
|
-
*
|
|
185
|
+
*
|
|
186
|
+
* Accepts:
|
|
187
|
+
* - `"memory"` — short-circuits to {@link MemoryCacheProvider} regardless
|
|
188
|
+
* of the default `CacheProvider` binding. Useful for caches that must
|
|
189
|
+
* stay process-local (e.g. ETag, HTTP client).
|
|
190
|
+
* - A {@link CacheProvider} class (concrete OR abstract) — resolved via
|
|
191
|
+
* `alepha.inject(...)` at primitive construction. Use this to opt a
|
|
192
|
+
* specific cache into a non-default backend (e.g.
|
|
193
|
+
* `provider: DatabaseCacheProvider` to keep one cache in SQL while the
|
|
194
|
+
* rest of the app uses Cloudflare KV).
|
|
195
|
+
* - `undefined` — falls back to whatever is bound to `CacheProvider` in
|
|
196
|
+
* the container. On Cloudflare workers this is
|
|
197
|
+
* {@link CloudflareKVProvider} by default; on Node it's
|
|
198
|
+
* {@link MemoryCacheProvider}.
|
|
199
|
+
*
|
|
200
|
+
* Note: passing an *abstract* class works because Alepha's DI resolves
|
|
201
|
+
* through substitutions, e.g. `alepha.with({ provide: CacheProvider, use:
|
|
202
|
+
* MyCacheProvider })`.
|
|
160
203
|
*/
|
|
161
|
-
provider?:
|
|
204
|
+
provider?: Service<CacheProvider> | "memory";
|
|
162
205
|
/**
|
|
163
206
|
* The time-to-live for the cache in seconds.
|
|
164
207
|
* Set 0 to skip expiration.
|
|
@@ -175,13 +218,40 @@ interface CachePrimitiveOptions<TReturn = any, TParameter extends any[] = any[]>
|
|
|
175
218
|
* Reduces storage size by 60-80% for JSON payloads at the cost of CPU.
|
|
176
219
|
*/
|
|
177
220
|
compress?: boolean;
|
|
221
|
+
/**
|
|
222
|
+
* Add an in-process L1 memory tier in front of `provider`.
|
|
223
|
+
*
|
|
224
|
+
* Reads check memory first, fall back to the provider on miss. Writes go
|
|
225
|
+
* to both tiers (write-through), so own-writes are immediately visible.
|
|
226
|
+
*
|
|
227
|
+
* Caveats:
|
|
228
|
+
* - Per-process only. Each Worker isolate / Node process has its own L1.
|
|
229
|
+
* `invalidate()` clears the local L1 + the remote provider; other
|
|
230
|
+
* processes keep their L1 until its TTL expires.
|
|
231
|
+
* - Use a short L1 TTL to bound the cross-isolate staleness window.
|
|
232
|
+
*
|
|
233
|
+
* @default off
|
|
234
|
+
*/
|
|
235
|
+
memory?: true | CacheMemoryTierOptions;
|
|
236
|
+
/**
|
|
237
|
+
* Stale-while-revalidate window. After `ttl` expires, the cached value
|
|
238
|
+
* remains servable for `stale` longer; reads in this window return the
|
|
239
|
+
* stale value immediately and trigger ONE background refresh
|
|
240
|
+
* (single-flight per key).
|
|
241
|
+
*
|
|
242
|
+
* Requires a `handler` (primitive mode) OR middleware mode wrapping a
|
|
243
|
+
* handler — the cache needs to know how to recompute.
|
|
244
|
+
*
|
|
245
|
+
* @default off
|
|
246
|
+
*/
|
|
247
|
+
stale?: DurationLike;
|
|
178
248
|
}
|
|
179
249
|
/**
|
|
180
250
|
* Cache configuration atom.
|
|
181
251
|
*/
|
|
182
|
-
declare const cacheOptions: _$alepha.Atom<_$
|
|
183
|
-
enabled: _$
|
|
184
|
-
defaultTtl: _$
|
|
252
|
+
declare const cacheOptions: _$alepha.Atom<_$typebox.TObject<{
|
|
253
|
+
enabled: _$typebox.TBoolean;
|
|
254
|
+
defaultTtl: _$typebox.TNumber;
|
|
185
255
|
}>, "alepha.cache.options">;
|
|
186
256
|
type CacheAtomOptions = Static<typeof cacheOptions.schema>;
|
|
187
257
|
declare module "alepha" {
|
|
@@ -189,6 +259,21 @@ declare module "alepha" {
|
|
|
189
259
|
[cacheOptions.key]: CacheAtomOptions;
|
|
190
260
|
}
|
|
191
261
|
}
|
|
262
|
+
declare const SWR_MARKER: "__swr";
|
|
263
|
+
type SwrEnvelope = {
|
|
264
|
+
[SWR_MARKER]: 1;
|
|
265
|
+
v: unknown;
|
|
266
|
+
f: number;
|
|
267
|
+
};
|
|
268
|
+
type L1Entry<T> = {
|
|
269
|
+
value: T | undefined;
|
|
270
|
+
expiresAt: number;
|
|
271
|
+
negative: boolean;
|
|
272
|
+
};
|
|
273
|
+
type ReadResult<T> = {
|
|
274
|
+
value: T | undefined;
|
|
275
|
+
stale: boolean;
|
|
276
|
+
};
|
|
192
277
|
declare class CachePrimitive<TReturn = any, TParameter extends any[] = any[]> extends Primitive<CachePrimitiveOptions<TReturn, TParameter>> {
|
|
193
278
|
protected readonly settings: Readonly<{
|
|
194
279
|
enabled: boolean;
|
|
@@ -196,6 +281,12 @@ declare class CachePrimitive<TReturn = any, TParameter extends any[] = any[]> ex
|
|
|
196
281
|
}>;
|
|
197
282
|
protected readonly dateTimeProvider: DateTimeProvider;
|
|
198
283
|
readonly provider: CacheProvider;
|
|
284
|
+
protected readonly memoryStore?: Map<string, L1Entry<TReturn>>;
|
|
285
|
+
protected readonly memoryMax: number;
|
|
286
|
+
protected readonly memoryTtlMs: number;
|
|
287
|
+
protected readonly negativeTtlMs: number;
|
|
288
|
+
protected readonly inflightRefreshes: Map<string, Promise<TReturn>>;
|
|
289
|
+
constructor(args: ConstructorParameters<typeof Primitive<CachePrimitiveOptions<TReturn, TParameter>>>[0]);
|
|
199
290
|
get container(): string;
|
|
200
291
|
run(...args: TParameter): Promise<TReturn>;
|
|
201
292
|
key(...args: TParameter): string;
|
|
@@ -203,7 +294,27 @@ declare class CachePrimitive<TReturn = any, TParameter extends any[] = any[]> ex
|
|
|
203
294
|
invalidate(...keys: string[]): Promise<void>;
|
|
204
295
|
set(key: string, value: TReturn, ttl?: DurationLike): Promise<void>;
|
|
205
296
|
get(key: string): Promise<TReturn | undefined>;
|
|
297
|
+
/**
|
|
298
|
+
* Internal read that also reports whether the value is stale (SWR
|
|
299
|
+
* grace window). Middleware and `run()` use this to decide whether to
|
|
300
|
+
* schedule a background refresh.
|
|
301
|
+
*/
|
|
302
|
+
read(key: string): Promise<ReadResult<TReturn>>;
|
|
303
|
+
/**
|
|
304
|
+
* Run a handler under single-flight: concurrent callers for the same
|
|
305
|
+
* key share one in-flight promise.
|
|
306
|
+
*/
|
|
307
|
+
runSingleFlight(key: string, handler: () => Promise<TReturn> | TReturn): Promise<TReturn>;
|
|
308
|
+
/**
|
|
309
|
+
* Schedule a background refresh for a stale key. At most one refresh
|
|
310
|
+
* per key is in-flight at any time; failures are swallowed (the stale
|
|
311
|
+
* value keeps being served until expiry).
|
|
312
|
+
*/
|
|
313
|
+
scheduleRefresh(key: string, handler: () => Promise<TReturn> | TReturn): void;
|
|
206
314
|
protected $provider(): CacheProvider;
|
|
315
|
+
protected isSwrEnvelope(value: unknown): value is SwrEnvelope;
|
|
316
|
+
protected setL1(key: string, entry: L1Entry<TReturn>): void;
|
|
317
|
+
protected delL1(key: string): void;
|
|
207
318
|
}
|
|
208
319
|
interface CachePrimitiveFn<TReturn = any, TParameter extends any[] = any[]> extends CachePrimitive<TReturn, TParameter> {
|
|
209
320
|
/**
|
|
@@ -495,6 +606,22 @@ declare module "alepha" {
|
|
|
495
606
|
key: string;
|
|
496
607
|
ttlMs?: number;
|
|
497
608
|
};
|
|
609
|
+
/**
|
|
610
|
+
* Fires when a stale value (SWR grace window) is served and a
|
|
611
|
+
* background refresh is scheduled.
|
|
612
|
+
*/
|
|
613
|
+
"cache:stale": {
|
|
614
|
+
container: string;
|
|
615
|
+
key: string;
|
|
616
|
+
};
|
|
617
|
+
/**
|
|
618
|
+
* Fires when a background SWR refresh completes successfully and
|
|
619
|
+
* the value has been written back to the cache.
|
|
620
|
+
*/
|
|
621
|
+
"cache:revalidate": {
|
|
622
|
+
container: string;
|
|
623
|
+
key: string;
|
|
624
|
+
};
|
|
498
625
|
}
|
|
499
626
|
}
|
|
500
627
|
/**
|
|
@@ -511,5 +638,5 @@ declare module "alepha" {
|
|
|
511
638
|
*/
|
|
512
639
|
declare const AlephaCache: _$alepha.Service<_$alepha.Module>;
|
|
513
640
|
//#endregion
|
|
514
|
-
export { $cache, AlephaCache, CacheAtomOptions, CacheMiddlewareFn, CachePrimitive, CachePrimitiveFn, CachePrimitiveOptions, CacheProvider, CloudflareKVProvider, KVListKey, KVListOptions, KVListResult, KVNamespace, KVPutOptions, KV_DEFAULT_BINDING, MemoryCacheCall, MemoryCacheDelCall, MemoryCacheProvider, MemoryCacheProviderOptions, MemoryCacheSetCall, MemoryCacheStats, cacheOptions };
|
|
641
|
+
export { $cache, AlephaCache, CacheAtomOptions, CacheMemoryTierOptions, CacheMiddlewareFn, CachePrimitive, CachePrimitiveFn, CachePrimitiveOptions, CacheProvider, CloudflareKVProvider, KVListKey, KVListOptions, KVListResult, KVNamespace, KVPutOptions, KV_DEFAULT_BINDING, MemoryCacheCall, MemoryCacheDelCall, MemoryCacheProvider, MemoryCacheProviderOptions, MemoryCacheSetCall, MemoryCacheStats, cacheOptions };
|
|
515
642
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/cache/core/providers/CacheProvider.ts","../../../src/cache/core/primitives/$cache.ts","../../../src/cache/core/providers/CloudflareKVProvider.ts","../../../src/cache/core/providers/MemoryCacheProvider.ts","../../../src/cache/core/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/cache/core/providers/CacheProvider.ts","../../../src/cache/core/primitives/$cache.ts","../../../src/cache/core/providers/CloudflareKVProvider.ts","../../../src/cache/core/providers/MemoryCacheProvider.ts","../../../src/cache/core/index.ts"],"mappings":"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"}
|