alepha 0.21.2 → 0.22.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/README.md +0 -1
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.d.ts +393 -403
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +25 -56
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.browser.js +31 -1
- package/dist/api/files/index.browser.js.map +1 -1
- package/dist/api/files/index.d.ts +313 -208
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +152 -42
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +2 -2
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +289 -292
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +39 -33
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +211 -216
- package/dist/api/keys/index.d.ts.map +1 -1
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +188 -195
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/oauth/index.d.ts +71 -76
- package/dist/api/oauth/index.d.ts.map +1 -1
- package/dist/api/oauth/index.js.map +1 -1
- package/dist/api/organizations/index.browser.js.map +1 -1
- package/dist/api/organizations/index.d.ts +104 -109
- package/dist/api/organizations/index.d.ts.map +1 -1
- package/dist/api/organizations/index.js.map +1 -1
- package/dist/api/parameters/index.browser.js +43 -16
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +488 -344
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +175 -35
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/payments/index.d.ts +396 -402
- package/dist/api/payments/index.d.ts.map +1 -1
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/subscriptions/index.d.ts +644 -652
- package/dist/api/subscriptions/index.d.ts.map +1 -1
- package/dist/api/subscriptions/index.js +1 -1
- package/dist/api/subscriptions/index.js.map +1 -1
- package/dist/api/users/index.browser.js +7 -0
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +1073 -1006
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +283 -61
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.browser.js.map +1 -1
- package/dist/api/verifications/index.d.ts +134 -140
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/background/index.d.ts +95 -0
- package/dist/background/index.d.ts.map +1 -0
- package/dist/background/index.js +121 -0
- package/dist/background/index.js.map +1 -0
- package/dist/background/index.workerd.js +110 -0
- package/dist/background/index.workerd.js.map +1 -0
- package/dist/batch/index.d.ts +5 -7
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/bin/index.js.map +1 -1
- package/dist/bucket/index.d.ts +76 -54
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +58 -11
- package/dist/bucket/index.js.map +1 -1
- package/dist/bucket/index.workerd.js +200 -5
- package/dist/bucket/index.workerd.js.map +1 -1
- package/dist/cache/core/index.d.ts +7 -10
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/cache/database/index.d.ts +22 -26
- package/dist/cache/database/index.d.ts.map +1 -1
- package/dist/cache/database/index.js.map +1 -1
- package/dist/cache/redis/index.d.ts +4 -7
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/captcha/index.d.ts +3 -6
- package/dist/captcha/index.d.ts.map +1 -1
- package/dist/captcha/index.js.map +1 -1
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/config/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +417 -214
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +325 -563
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +3 -5
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/i18n/index.d.ts +8 -12
- package/dist/cli/i18n/index.d.ts.map +1 -1
- package/dist/cli/i18n/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +126 -1342
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +136 -2374
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/platform-lib/index.d.ts +1446 -0
- package/dist/cli/platform-lib/index.d.ts.map +1 -0
- package/dist/cli/platform-lib/index.js +2597 -0
- package/dist/cli/platform-lib/index.js.map +1 -0
- package/dist/cli/vendor/index.d.ts +17 -21
- 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 +21 -20
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +39 -10
- package/dist/command/index.js.map +1 -1
- package/dist/{containers → container}/core/index.d.ts +13 -15
- package/dist/container/core/index.d.ts.map +1 -0
- package/dist/{containers → container}/core/index.js +23 -14
- package/dist/container/core/index.js.map +1 -0
- package/dist/{containers → container}/core/index.workerd.js +37 -22
- package/dist/container/core/index.workerd.js.map +1 -0
- package/dist/core/index.browser.js +27 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +48 -24
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +27 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +27 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +27 -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 +5 -8
- package/dist/crypto/index.d.ts.map +1 -1
- package/dist/crypto/index.js.map +1 -1
- package/dist/datetime/index.d.ts +3 -4
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/brevo/index.d.ts +2 -4
- package/dist/email/brevo/index.d.ts.map +1 -1
- package/dist/email/brevo/index.js.map +1 -1
- package/dist/email/cloudflare/index.d.ts +20 -7
- package/dist/email/cloudflare/index.d.ts.map +1 -1
- package/dist/email/cloudflare/index.js +46 -9
- package/dist/email/cloudflare/index.js.map +1 -1
- package/dist/email/core/index.d.ts +6 -9
- 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 +10 -13
- package/dist/email/smtp/index.d.ts.map +1 -1
- package/dist/email/smtp/index.js +107 -32
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/fake/index.d.ts +1 -2
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +9 -14
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js.map +1 -1
- package/dist/lock/redis/index.d.ts +2 -4
- package/dist/lock/redis/index.d.ts.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.d.ts +105 -76
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +196 -174
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +16 -20
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +19 -1
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +76 -62
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +20 -2
- 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 +28 -20
- 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 +12 -15
- 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 -5
- 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 +9 -2
- package/dist/react/auth/index.browser.js.map +1 -1
- package/dist/react/auth/index.d.ts +14 -9
- package/dist/react/auth/index.d.ts.map +1 -1
- package/dist/react/auth/index.js +9 -2
- package/dist/react/auth/index.js.map +1 -1
- package/dist/react/core/index.d.ts +7 -8
- package/dist/react/core/index.d.ts.map +1 -1
- package/dist/react/core/index.js +6 -3
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/form/index.d.ts +2 -4
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/head/index.browser.js.map +1 -1
- package/dist/react/head/index.d.ts +2 -4
- package/dist/react/head/index.d.ts.map +1 -1
- package/dist/react/head/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +47 -11
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/i18n/index.js +33 -1
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.d.ts +1 -2
- package/dist/react/intro/index.d.ts.map +1 -1
- package/dist/react/intro/index.js +2 -2
- package/dist/react/intro/index.js.map +1 -1
- package/dist/react/router/index.browser.js +65 -19
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +327 -222
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +65 -29
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/testing/index.d.ts +1 -2
- package/dist/react/testing/index.d.ts.map +1 -1
- package/dist/react/testing/index.js +16 -17
- package/dist/react/testing/index.js.map +1 -1
- package/dist/react/ui/index.d.ts +20 -25
- 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.d.ts +17 -19
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts +2 -4
- package/dist/retry/index.d.ts.map +1 -1
- package/dist/retry/index.js.map +1 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +10 -13
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/index.workerd.js.map +1 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +45 -48
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.browser.js.map +1 -1
- package/dist/server/auth/index.d.ts +167 -172
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +4 -8
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cookies/index.browser.js.map +1 -1
- package/dist/server/cookies/index.d.ts +5 -7
- package/dist/server/cookies/index.d.ts.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 +88 -73
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +19 -0
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +11 -14
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/cors/index.js.map +1 -1
- package/dist/server/etag/index.d.ts +6 -9
- package/dist/server/etag/index.d.ts.map +1 -1
- package/dist/server/etag/index.js.map +1 -1
- package/dist/server/health/index.d.ts +18 -21
- 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 +2 -0
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +63 -67
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +2 -0
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +5 -7
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/proxy/index.d.ts +3 -5
- package/dist/server/proxy/index.d.ts.map +1 -1
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.d.ts +10 -13
- 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.d.ts +3 -5
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +5 -8
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +3 -5
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.d.ts +2 -4
- package/dist/system/index.d.ts.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.d.ts +4 -6
- package/dist/topic/core/index.d.ts.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/topic/redis/index.d.ts +5 -8
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/dist/topic/redis/index.js.map +1 -1
- package/package.json +45 -22
- package/src/api/audits/__tests__/AuditService.spec.ts +18 -110
- package/src/api/audits/controllers/AdminAuditController.ts +14 -0
- package/src/api/audits/services/AuditService.ts +21 -88
- package/src/api/files/__tests__/FileService.spec.ts +207 -2
- package/src/api/files/index.ts +3 -0
- package/src/api/files/schemas/fileCreatorSummarySchema.ts +22 -0
- package/src/api/files/schemas/fileResourceSchema.ts +10 -1
- package/src/api/files/services/FileService.ts +170 -72
- package/src/api/jobs/__tests__/$job.spec.ts +24 -1
- package/src/api/jobs/index.ts +4 -3
- package/src/api/jobs/primitives/$job.ts +7 -3
- package/src/api/jobs/providers/DirectJobDispatcher.ts +17 -36
- package/src/api/jobs/providers/JobProvider.ts +53 -24
- package/src/api/jobs/schemas/jobConfigAtom.ts +1 -1
- package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +4 -1
- package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +3 -1
- package/src/api/parameters/__tests__/$parameter.spec.ts +19 -2
- package/src/api/parameters/audits/ParameterAudits.ts +17 -0
- package/src/api/parameters/controllers/AdminParameterController.ts +95 -19
- package/src/api/parameters/index.ts +3 -0
- package/src/api/parameters/schemas/activateParameterBodySchema.ts +3 -3
- package/src/api/parameters/schemas/createParameterVersionBodySchema.ts +3 -2
- package/src/api/parameters/schemas/parameterCreatorSummarySchema.ts +25 -0
- package/src/api/parameters/schemas/parameterResponseSchema.ts +5 -0
- package/src/api/parameters/schemas/rollbackParameterBodySchema.ts +4 -2
- package/src/api/parameters/services/ParameterProvider.ts +69 -6
- package/src/api/subscriptions/jobs/SubscriptionJobs.ts +1 -1
- package/src/api/users/__tests__/AdminSessionController.spec.ts +37 -0
- package/src/api/users/audits/SessionAudits.ts +33 -0
- package/src/api/users/audits/UserAudits.ts +19 -43
- package/src/api/users/controllers/AdminUserController.ts +66 -1
- package/src/api/users/entities/sessions.ts +6 -0
- package/src/api/users/entities/users.ts +2 -0
- package/src/api/users/index.ts +9 -1
- package/src/api/users/primitives/$realm.ts +3 -0
- package/src/api/users/schemas/sessionResourceSchema.ts +16 -0
- package/src/api/users/schemas/updateUserSchema.ts +1 -8
- package/src/api/users/schemas/userQuerySchema.ts +7 -0
- package/src/api/users/services/CredentialService.ts +15 -6
- package/src/api/users/services/IdentityService.ts +2 -1
- package/src/api/users/services/RegistrationService.ts +2 -1
- package/src/api/users/services/SessionCrudService.ts +19 -2
- package/src/api/users/services/SessionService.ts +39 -19
- package/src/api/users/services/UserService.ts +106 -8
- package/src/background/__tests__/BackgroundTaskProvider.spec.ts +96 -0
- package/src/background/index.ts +37 -0
- package/src/background/index.workerd.ts +28 -0
- package/src/background/providers/BackgroundTaskProvider.ts +70 -0
- package/src/background/providers/WorkerdBackgroundTaskProvider.ts +43 -0
- package/src/bucket/__tests__/$bucket.spec.ts +18 -0
- package/src/bucket/__tests__/LocalFileStorageProvider.spec.ts +5 -0
- package/src/bucket/__tests__/MemoryFileStorageProvider.spec.ts +5 -0
- package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +23 -4
- package/src/bucket/__tests__/shared.ts +30 -0
- package/src/bucket/index.ts +5 -5
- package/src/bucket/index.workerd.ts +11 -4
- package/src/bucket/primitives/$bucket.ts +27 -0
- package/src/bucket/providers/FileStorageProvider.ts +13 -0
- package/src/bucket/providers/LocalFileStorageProvider.ts +17 -1
- package/src/bucket/providers/MemoryFileStorageProvider.ts +7 -0
- package/src/bucket/providers/{CloudflareR2Provider.ts → R2FileStorageProvider.ts} +10 -1
- package/src/bucket/providers/{NodeS3BucketProvider.ts → S3FileStorageProvider.ts} +27 -5
- package/src/cli/core/__tests__/BuildDockerTask.spec.ts +25 -1
- package/src/cli/core/__tests__/init.spec.ts +0 -219
- package/src/cli/core/commands/__tests__/BuildCommand.spec.ts +43 -0
- package/src/cli/core/commands/build.ts +108 -30
- package/src/cli/core/commands/init.ts +0 -12
- package/src/cli/core/commands/pack.ts +133 -0
- package/src/cli/core/index.ts +3 -0
- package/src/cli/core/providers/ViteDevServerProvider.ts +40 -16
- package/src/cli/core/services/PackageManagerUtils.ts +0 -16
- package/src/cli/core/services/ProjectScaffolder.ts +29 -291
- package/src/cli/core/tasks/BuildCloudflareTask.ts +353 -47
- package/src/cli/core/tasks/BuildDockerTask.ts +33 -3
- package/src/cli/core/tasks/BuildTask.ts +34 -0
- package/src/cli/core/templates/apiIndexTs.ts +1 -22
- package/src/cli/core/templates/mainCss.ts +0 -1
- package/src/cli/core/templates/webAppRouterTs.ts +0 -99
- package/src/cli/core/templates/webIndexTs.ts +1 -22
- package/src/cli/platform/__tests__/SecretsCommand.spec.ts +5 -3
- package/src/cli/platform/commands/SecretsCommand.ts +8 -6
- package/src/cli/platform/commands/platform.ts +192 -46
- package/src/cli/platform/index.ts +12 -52
- package/src/cli/{platform → platform-lib}/__tests__/CloudflareAdapter.spec.ts +426 -169
- package/src/cli/{platform → platform-lib}/__tests__/NamingService.spec.ts +91 -4
- package/src/cli/{platform → platform-lib}/__tests__/VercelAdapter.spec.ts +56 -85
- package/src/cli/{platform → platform-lib}/adapters/CloudflareAdapter.ts +402 -165
- package/src/cli/{platform → platform-lib}/adapters/PlatformAdapter.ts +62 -35
- package/src/cli/{platform → platform-lib}/adapters/VercelAdapter.ts +6 -10
- package/src/cli/{platform → platform-lib}/atoms/platformOptions.ts +34 -1
- package/src/cli/platform-lib/index.ts +67 -0
- package/src/cli/platform-lib/services/NamingService.ts +136 -0
- package/src/cli/{platform → platform-lib}/services/PlatformInspector.ts +60 -13
- package/src/cli/{platform → platform-lib}/services/PlatformOrchestrator.ts +54 -43
- package/src/cli/{platform → platform-lib}/services/WranglerApi.ts +4 -2
- package/src/command/__tests__/Runner.spec.ts +20 -0
- package/src/command/helpers/EnvUtils.ts +19 -3
- package/src/command/helpers/Runner.ts +12 -2
- package/src/command/providers/CliProvider.ts +34 -1
- package/src/{containers → container}/core/__tests__/$container.spec.ts +5 -5
- package/src/{containers → container}/core/index.ts +4 -4
- package/src/{containers → container}/core/index.workerd.ts +19 -3
- package/src/{containers → container}/core/primitives/$container.ts +1 -1
- package/src/{containers → container}/core/providers/CloudflareContainerProvider.ts +17 -19
- package/src/{containers → container}/core/providers/ContainerProvider.ts +16 -2
- package/src/{containers → container}/core/providers/MockContainerProvider.ts +1 -1
- package/src/core/Alepha.ts +49 -1
- package/src/core/__tests__/$env.spec.ts +42 -0
- package/src/core/__tests__/dump.spec.ts +47 -0
- package/src/email/cloudflare/__tests__/CloudflareEmailProvider.spec.ts +42 -10
- package/src/email/cloudflare/index.ts +14 -5
- package/src/email/cloudflare/providers/CloudflareEmailProvider.ts +54 -9
- package/src/logger/__tests__/Logger.spec.ts +55 -0
- package/src/logger/index.ts +13 -0
- package/src/logger/services/Logger.ts +31 -1
- package/src/orm/__tests__/orm-showcase-tests.ts +27 -0
- package/src/orm/__tests__/orm-showcase.spec.ts +12 -0
- package/src/orm/core/interfaces/PgQuery.ts +4 -1
- package/src/orm/core/services/Repository.ts +27 -11
- package/src/react/auth/hooks/useAuth.ts +10 -5
- package/src/react/core/__tests__/useQuery.browser.spec.tsx +25 -0
- package/src/react/core/hooks/useAction.ts +14 -3
- package/src/react/core/hooks/useQuery.ts +24 -4
- package/src/react/i18n/components/Translate.tsx +47 -0
- package/src/react/i18n/index.ts +2 -0
- package/src/react/intro/components/GettingStartedAdminSlide.tsx +2 -2
- package/src/react/router/__tests__/$page.spec.tsx +3 -2
- package/src/react/router/__tests__/page-can.spec.ts +18 -13
- package/src/react/router/hooks/useQueryParams.ts +114 -14
- package/src/react/router/primitives/$page.ts +85 -4
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +3 -7
- package/src/react/router/providers/ReactServerProvider.ts +4 -13
- package/src/react/ui/services/SchemaControl.ts +3 -4
- package/src/server/core/providers/ServerMultipartProvider.ts +19 -0
- package/src/server/links/providers/LinkProvider.ts +10 -0
- package/dist/containers/core/index.d.ts.map +0 -1
- package/dist/containers/core/index.js.map +0 -1
- package/dist/containers/core/index.workerd.js.map +0 -1
- package/src/cli/core/templates/componentsJsonTs.ts +0 -39
- package/src/cli/core/templates/saasAdminLayoutTsx.ts +0 -77
- package/src/cli/core/templates/saasAdminPagesTsx.ts +0 -26
- package/src/cli/core/templates/saasAuthLayoutTsx.ts +0 -22
- package/src/cli/core/templates/saasAuthPagesTsx.ts +0 -62
- package/src/cli/core/templates/saasRealmProviderTs.ts +0 -52
- package/src/cli/platform/services/NamingService.ts +0 -54
- /package/dist/orm/core/{chunk-o8xxKEmq.js → chunk-B4FMCO8f.js} +0 -0
- /package/dist/react/testing/{chunk-6Ep1yQYe.js → chunk-BpyX8vjI.js} +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/GitHubSecretStore.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/PlatformCacheProvider.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/PlatformInspector.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/PlatformOrchestrator.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/SecretFilterService.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/detectResources.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/providers/GitHubSecretStore.ts +0 -0
- /package/src/cli/{platform → platform-lib}/providers/MemorySecretStore.ts +0 -0
- /package/src/cli/{platform → platform-lib}/providers/PlatformCacheProvider.ts +0 -0
- /package/src/cli/{platform → platform-lib}/providers/SecretStoreProvider.ts +0 -0
- /package/src/cli/{platform → platform-lib}/schemas/cloudflare.ts +0 -0
- /package/src/cli/{platform → platform-lib}/schemas/platform.ts +0 -0
- /package/src/cli/{platform → platform-lib}/schemas/vercel.ts +0 -0
- /package/src/cli/{platform → platform-lib}/services/CloudflareApi.ts +0 -0
- /package/src/cli/{platform → platform-lib}/services/SecretFilterService.ts +0 -0
- /package/src/cli/{platform → platform-lib}/services/VercelApi.ts +0 -0
- /package/src/cli/{platform → platform-lib}/services/VercelCli.ts +0 -0
- /package/src/{containers → container}/core/interfaces/ContainerOptions.ts +0 -0
- /package/src/{containers → container}/core/providers/NodeContainerProvider.ts +0 -0
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
import * as _$alepha from "alepha";
|
|
2
1
|
import { Middleware, Static } from "alepha";
|
|
3
|
-
import * as _$alepha_logger0 from "alepha/logger";
|
|
4
2
|
import { ServerRouterProvider } from "alepha/server";
|
|
5
|
-
import * as _$typebox from "typebox";
|
|
6
3
|
|
|
7
4
|
//#region ../../src/server/cors/providers/ServerCorsProvider.d.ts
|
|
8
5
|
/**
|
|
9
6
|
* CORS configuration atom (global defaults)
|
|
10
7
|
*/
|
|
11
|
-
declare const corsOptions:
|
|
12
|
-
origin:
|
|
13
|
-
methods:
|
|
14
|
-
headers:
|
|
15
|
-
credentials:
|
|
16
|
-
maxAge:
|
|
8
|
+
declare const corsOptions: import("alepha").Atom<import("typebox").TObject<{
|
|
9
|
+
origin: import("typebox").TOptional<import("typebox").TString>;
|
|
10
|
+
methods: import("typebox").TArray<import("typebox").TString>;
|
|
11
|
+
headers: import("typebox").TArray<import("typebox").TString>;
|
|
12
|
+
credentials: import("typebox").TOptional<import("typebox").TBoolean>;
|
|
13
|
+
maxAge: import("typebox").TOptional<import("typebox").TNumber>;
|
|
17
14
|
}>, "alepha.server.cors.options">;
|
|
18
15
|
type CorsOptions = Static<typeof corsOptions.schema>;
|
|
19
16
|
declare module "alepha" {
|
|
@@ -32,7 +29,7 @@ interface CorsRegistration extends Partial<CorsOptions> {
|
|
|
32
29
|
paths?: string[];
|
|
33
30
|
}
|
|
34
31
|
declare class ServerCorsProvider {
|
|
35
|
-
protected readonly log:
|
|
32
|
+
protected readonly log: import("alepha/logger").Logger;
|
|
36
33
|
protected readonly serverRouterProvider: ServerRouterProvider;
|
|
37
34
|
protected readonly globalOptions: Readonly<{
|
|
38
35
|
origin?: string | undefined;
|
|
@@ -49,7 +46,7 @@ declare class ServerCorsProvider {
|
|
|
49
46
|
* Register a CORS configuration (called by primitives)
|
|
50
47
|
*/
|
|
51
48
|
registerCors(config: CorsRegistration): void;
|
|
52
|
-
protected readonly onStart:
|
|
49
|
+
protected readonly onStart: import("alepha").HookPrimitive<"start">;
|
|
53
50
|
/**
|
|
54
51
|
* Build complete CORS options by merging with global defaults
|
|
55
52
|
*/
|
|
@@ -65,8 +62,8 @@ declare class ServerCorsProvider {
|
|
|
65
62
|
setHeader: (name: string, value: string) => void;
|
|
66
63
|
};
|
|
67
64
|
}, options: CorsOptions): void;
|
|
68
|
-
protected readonly configure:
|
|
69
|
-
protected readonly onRequest:
|
|
65
|
+
protected readonly configure: import("alepha").HookPrimitive<"start">;
|
|
66
|
+
protected readonly onRequest: import("alepha").HookPrimitive<"server:onRequest">;
|
|
70
67
|
isOriginAllowed(origin: string | undefined, allowed: CorsOptions["origin"]): boolean;
|
|
71
68
|
}
|
|
72
69
|
type ServerCorsProviderOptions = CorsOptions;
|
|
@@ -113,7 +110,7 @@ declare module "alepha/server" {
|
|
|
113
110
|
*
|
|
114
111
|
* @module alepha.server.cors
|
|
115
112
|
*/
|
|
116
|
-
declare const AlephaServerCors:
|
|
113
|
+
declare const AlephaServerCors: import("alepha").Service<import("alepha").Module>;
|
|
117
114
|
//#endregion
|
|
118
115
|
export { $cors, AlephaServerCors, CorsOptions, CorsRegistration, ServerCorsProvider, ServerCorsProviderOptions, corsOptions };
|
|
119
116
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/server/cors/providers/ServerCorsProvider.ts","../../../src/server/cors/primitives/$cors.ts","../../../src/server/cors/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/server/cors/providers/ServerCorsProvider.ts","../../../src/server/cors/primitives/$cors.ts","../../../src/server/cors/index.ts"],"mappings":";;;;;;AASA;cAAa,WAAA,mBAAW,IAAA,mBAAA,OAAA;;;;;;;KAsCZ,WAAA,GAAc,MAAM,QAAQ,WAAA,CAAY,MAAA;AAAA;EAAA,UAGxC,KAAA;IAAA,CACP,WAAA,CAAY,GAAG,GAAG,WAAA;EAAA;AAAA;AAAA,UAMN,gBAAA,SAAyB,OAAO,CAAC,WAAA;;;;EAIhD,IAAA;;;;EAIA,KAAA;AAAA;AAAA,cAGW,kBAAA;EAAA,mBACQ,GAAA,0BAAG,MAAA;EAAA,mBACH,oBAAA,EAAoB,oBAAA;EAAA,mBACpB,aAAA,EAAa,QAAA;;;;;;;;;;WAKhB,iBAAA,EAAmB,gBAAA;EAzBpB;;;EA8BR,YAAA,CAAa,MAAA,EAAQ,gBAAA;EAAA,mBAIT,OAAA,mBAAO,aAAA;EAlCM;AAMlC;;EAsDS,gBAAA,CAAiB,MAAA,EAAQ,OAAA,CAAQ,WAAA,IAAe,WAAA;EAtDR;;;EAmExC,gBAAA,CACL,OAAA;IACE,OAAA;MAAW,MAAA;IAAA;IACX,KAAA;MAAS,SAAA,GAAY,IAAA,UAAc,KAAA;IAAA;EAAA,GAErC,OAAA,EAAS,WAAA;EAAA,mBAqBQ,SAAA,mBAAS,aAAA;EAAA,mBAwBT,SAAA,mBAAS,aAAA;EASrB,eAAA,CACL,MAAA,sBACA,OAAA,EAAS,WAAA;AAAA;AAAA,KAWD,yBAAA,GAA4B,WAAW;;;;;AA3LnD;;;;;;;;;;;;;;;;;;;cCkBa,KAAA,GAAS,OAAA,GAAU,OAAA,CAAQ,WAAA,MAAe,UAAA;;;;YCb3C,WAAA;IF+BV;;;;IE1BE,IAAA,GAAO,WAAW;EAAA;AAAA;;;;;;;;;cAcT,gBAAA,mBAAgB,OAAA,kBAAA,MAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/server/cors/providers/ServerCorsProvider.ts","../../../src/server/cors/primitives/$cors.ts","../../../src/server/cors/index.ts"],"sourcesContent":["import { $atom, $hook, $inject, $state, type Static, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { ServerRouterProvider } from \"alepha/server\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * CORS configuration atom (global defaults)\n */\nexport const corsOptions = $atom({\n name: \"alepha.server.cors.options\",\n schema: t.object({\n origin: t.optional(\n t.string({\n description:\n \"Allowed origins (* for all, string for single, comma-separated for multiple)\",\n default: \"*\",\n }),\n ),\n methods: t.array(t.string(), {\n description: \"Allowed HTTP methods\",\n default: [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"],\n }),\n headers: t.array(t.string(), {\n description: \"Allowed headers\",\n default: [\"Content-Type\", \"Authorization\"],\n }),\n credentials: t.optional(\n t.boolean({\n description: \"Allow credentials\",\n default: false,\n }),\n ),\n maxAge: t.optional(\n t.number({\n description: \"Preflight cache duration in seconds\",\n }),\n ),\n }),\n default: {\n origin: \"*\",\n methods: [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"],\n headers: [\"Content-Type\", \"Authorization\"],\n credentials: false,\n },\n});\n\nexport type CorsOptions = Static<typeof corsOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [corsOptions.key]: CorsOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface CorsRegistration extends Partial<CorsOptions> {\n /**\n * Name identifier for this CORS config.\n */\n name?: string;\n /**\n * Path patterns to match (supports wildcards like /api/*).\n */\n paths?: string[];\n}\n\nexport class ServerCorsProvider {\n protected readonly log = $logger();\n protected readonly serverRouterProvider = $inject(ServerRouterProvider);\n protected readonly globalOptions = $state(corsOptions);\n\n /**\n * Registered CORS configurations with their path patterns\n */\n public readonly registeredConfigs: CorsRegistration[] = [];\n\n /**\n * Register a CORS configuration (called by primitives)\n */\n public registerCors(config: CorsRegistration): void {\n this.registeredConfigs.push(config);\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // Apply path-specific CORS configs to routes\n for (const config of this.registeredConfigs) {\n if (config.paths) {\n for (const pattern of config.paths) {\n const matchedRoutes = this.serverRouterProvider.getRoutes(pattern);\n for (const route of matchedRoutes) {\n route.cors = this.buildCorsOptions(config);\n }\n }\n }\n }\n\n if (this.registeredConfigs.length > 0) {\n this.log.info(\n `Initialized with ${this.registeredConfigs.length} registered CORS configurations.`,\n );\n }\n },\n });\n\n /**\n * Build complete CORS options by merging with global defaults\n */\n public buildCorsOptions(config: Partial<CorsOptions>): CorsOptions {\n return {\n origin: config.origin ?? this.globalOptions.origin,\n methods: config.methods ?? this.globalOptions.methods,\n headers: config.headers ?? this.globalOptions.headers,\n credentials: config.credentials ?? this.globalOptions.credentials,\n maxAge: config.maxAge ?? this.globalOptions.maxAge,\n };\n }\n\n /**\n * Apply CORS headers to the response\n */\n public applyCorsHeaders(\n request: {\n headers: { origin?: string };\n reply: { setHeader: (name: string, value: string) => void };\n },\n options: CorsOptions,\n ): void {\n const reqOrigin = request.headers.origin;\n const { origin, methods, headers, credentials, maxAge } = options;\n\n if (reqOrigin && this.isOriginAllowed(reqOrigin, origin)) {\n request.reply.setHeader(\"Access-Control-Allow-Origin\", reqOrigin);\n }\n\n if (credentials) {\n request.reply.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n }\n\n request.reply.setHeader(\"Access-Control-Allow-Methods\", methods.join(\", \"));\n request.reply.setHeader(\"Access-Control-Allow-Headers\", headers.join(\", \"));\n\n if (maxAge != null) {\n request.reply.setHeader(\"Access-Control-Max-Age\", String(maxAge));\n }\n }\n\n protected readonly configure = $hook({\n on: \"start\",\n handler: () => {\n const routes = this.serverRouterProvider.getRoutes();\n for (const route of routes) {\n if (\n !route.method ||\n route.method === \"GET\" ||\n route.method === \"OPTIONS\"\n ) {\n continue;\n }\n\n this.serverRouterProvider.createRoute({\n path: route.path,\n method: \"OPTIONS\",\n handler: ({ reply }) => {\n reply.setStatus(204);\n },\n });\n }\n },\n });\n\n protected readonly onRequest = $hook({\n on: \"server:onRequest\",\n handler: ({ route, request }) => {\n // Use route-specific CORS if defined, otherwise use global options\n const corsConfig = route.cors ?? this.globalOptions;\n this.applyCorsHeaders(request, corsConfig);\n },\n });\n\n public isOriginAllowed(\n origin: string | undefined,\n allowed: CorsOptions[\"origin\"],\n ): boolean {\n if (!allowed) return false;\n if (allowed === \"*\") return true;\n return allowed\n .split(\",\")\n .map((o) => o.trim())\n .includes(origin ?? \"\");\n }\n}\n\nexport type ServerCorsProviderOptions = CorsOptions;\n","import { AlephaError, createMiddleware, type Middleware } from \"alepha\";\nimport {\n type CorsOptions,\n ServerCorsProvider,\n} from \"../providers/ServerCorsProvider.ts\";\n\n/**\n * Middleware that applies CORS headers to the response and handles OPTIONS preflight.\n *\n * Reads the request from the ALS context and applies the configured\n * CORS headers via `ServerCorsProvider`. Options are merged with\n * global CORS defaults.\n *\n * For OPTIONS preflight requests, the middleware short-circuits with a 204 response\n * and skips the handler entirely.\n *\n * **Route middleware** — requires a request context (`$action`). Throws if used outside one.\n *\n * ```typescript\n * class ApiController {\n * getOrders = $action({\n * use: [$cors({ origin: \"https://app.example.com\", credentials: true })],\n * handler: async ({ query }) => { ... },\n * });\n * }\n * ```\n */\nexport const $cors = (options?: Partial<CorsOptions>): Middleware => {\n return createMiddleware({\n name: \"$cors\",\n options: options as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const corsProvider = alepha.inject(ServerCorsProvider);\n\n return async (...args) => {\n const request = alepha.get(\"alepha.http.request\");\n\n if (!request) {\n throw new AlephaError(\n \"$cors requires a request context (use inside $action)\",\n );\n }\n\n const corsConfig = corsProvider.buildCorsOptions(options ?? {});\n corsProvider.applyCorsHeaders(request, corsConfig);\n\n // OPTIONS preflight → respond immediately, skip handler\n if (request.method === \"OPTIONS\") {\n request.reply.setStatus(204);\n return;\n }\n\n return next(...args);\n };\n },\n });\n};\n","import { $module } from \"alepha\";\nimport {\n type CorsOptions,\n ServerCorsProvider,\n} from \"./providers/ServerCorsProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$cors.ts\";\nexport * from \"./providers/ServerCorsProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha/server\" {\n interface ServerRoute {\n /**\n * Route-specific CORS configuration.\n * If set, overrides the global CORS options for this route.\n */\n cors?: CorsOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Cross-Origin Resource Sharing configuration.\n *\n * **Features:**\n * - CORS policy definition\n *\n * @module alepha.server.cors\n */\nexport const AlephaServerCors = $module({\n name: \"alepha.server.cors\",\n services: [ServerCorsProvider],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/server/cors/providers/ServerCorsProvider.ts","../../../src/server/cors/primitives/$cors.ts","../../../src/server/cors/index.ts"],"sourcesContent":["import { $atom, $hook, $inject, $state, type Static, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { ServerRouterProvider } from \"alepha/server\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * CORS configuration atom (global defaults)\n */\nexport const corsOptions = $atom({\n name: \"alepha.server.cors.options\",\n schema: t.object({\n origin: t.optional(\n t.string({\n description:\n \"Allowed origins (* for all, string for single, comma-separated for multiple)\",\n default: \"*\",\n }),\n ),\n methods: t.array(t.string(), {\n description: \"Allowed HTTP methods\",\n default: [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"],\n }),\n headers: t.array(t.string(), {\n description: \"Allowed headers\",\n default: [\"Content-Type\", \"Authorization\"],\n }),\n credentials: t.optional(\n t.boolean({\n description: \"Allow credentials\",\n default: false,\n }),\n ),\n maxAge: t.optional(\n t.number({\n description: \"Preflight cache duration in seconds\",\n }),\n ),\n }),\n default: {\n origin: \"*\",\n methods: [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"],\n headers: [\"Content-Type\", \"Authorization\"],\n credentials: false,\n },\n});\n\nexport type CorsOptions = Static<typeof corsOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [corsOptions.key]: CorsOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface CorsRegistration extends Partial<CorsOptions> {\n /**\n * Name identifier for this CORS config.\n */\n name?: string;\n /**\n * Path patterns to match (supports wildcards like /api/*).\n */\n paths?: string[];\n}\n\nexport class ServerCorsProvider {\n protected readonly log = $logger();\n protected readonly serverRouterProvider = $inject(ServerRouterProvider);\n protected readonly globalOptions = $state(corsOptions);\n\n /**\n * Registered CORS configurations with their path patterns\n */\n public readonly registeredConfigs: CorsRegistration[] = [];\n\n /**\n * Register a CORS configuration (called by primitives)\n */\n public registerCors(config: CorsRegistration): void {\n this.registeredConfigs.push(config);\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // Apply path-specific CORS configs to routes\n for (const config of this.registeredConfigs) {\n if (config.paths) {\n for (const pattern of config.paths) {\n const matchedRoutes = this.serverRouterProvider.getRoutes(pattern);\n for (const route of matchedRoutes) {\n route.cors = this.buildCorsOptions(config);\n }\n }\n }\n }\n\n if (this.registeredConfigs.length > 0) {\n this.log.info(\n `Initialized with ${this.registeredConfigs.length} registered CORS configurations.`,\n );\n }\n },\n });\n\n /**\n * Build complete CORS options by merging with global defaults\n */\n public buildCorsOptions(config: Partial<CorsOptions>): CorsOptions {\n return {\n origin: config.origin ?? this.globalOptions.origin,\n methods: config.methods ?? this.globalOptions.methods,\n headers: config.headers ?? this.globalOptions.headers,\n credentials: config.credentials ?? this.globalOptions.credentials,\n maxAge: config.maxAge ?? this.globalOptions.maxAge,\n };\n }\n\n /**\n * Apply CORS headers to the response\n */\n public applyCorsHeaders(\n request: {\n headers: { origin?: string };\n reply: { setHeader: (name: string, value: string) => void };\n },\n options: CorsOptions,\n ): void {\n const reqOrigin = request.headers.origin;\n const { origin, methods, headers, credentials, maxAge } = options;\n\n if (reqOrigin && this.isOriginAllowed(reqOrigin, origin)) {\n request.reply.setHeader(\"Access-Control-Allow-Origin\", reqOrigin);\n }\n\n if (credentials) {\n request.reply.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n }\n\n request.reply.setHeader(\"Access-Control-Allow-Methods\", methods.join(\", \"));\n request.reply.setHeader(\"Access-Control-Allow-Headers\", headers.join(\", \"));\n\n if (maxAge != null) {\n request.reply.setHeader(\"Access-Control-Max-Age\", String(maxAge));\n }\n }\n\n protected readonly configure = $hook({\n on: \"start\",\n handler: () => {\n const routes = this.serverRouterProvider.getRoutes();\n for (const route of routes) {\n if (\n !route.method ||\n route.method === \"GET\" ||\n route.method === \"OPTIONS\"\n ) {\n continue;\n }\n\n this.serverRouterProvider.createRoute({\n path: route.path,\n method: \"OPTIONS\",\n handler: ({ reply }) => {\n reply.setStatus(204);\n },\n });\n }\n },\n });\n\n protected readonly onRequest = $hook({\n on: \"server:onRequest\",\n handler: ({ route, request }) => {\n // Use route-specific CORS if defined, otherwise use global options\n const corsConfig = route.cors ?? this.globalOptions;\n this.applyCorsHeaders(request, corsConfig);\n },\n });\n\n public isOriginAllowed(\n origin: string | undefined,\n allowed: CorsOptions[\"origin\"],\n ): boolean {\n if (!allowed) return false;\n if (allowed === \"*\") return true;\n return allowed\n .split(\",\")\n .map((o) => o.trim())\n .includes(origin ?? \"\");\n }\n}\n\nexport type ServerCorsProviderOptions = CorsOptions;\n","import { AlephaError, createMiddleware, type Middleware } from \"alepha\";\nimport {\n type CorsOptions,\n ServerCorsProvider,\n} from \"../providers/ServerCorsProvider.ts\";\n\n/**\n * Middleware that applies CORS headers to the response and handles OPTIONS preflight.\n *\n * Reads the request from the ALS context and applies the configured\n * CORS headers via `ServerCorsProvider`. Options are merged with\n * global CORS defaults.\n *\n * For OPTIONS preflight requests, the middleware short-circuits with a 204 response\n * and skips the handler entirely.\n *\n * **Route middleware** — requires a request context (`$action`). Throws if used outside one.\n *\n * ```typescript\n * class ApiController {\n * getOrders = $action({\n * use: [$cors({ origin: \"https://app.example.com\", credentials: true })],\n * handler: async ({ query }) => { ... },\n * });\n * }\n * ```\n */\nexport const $cors = (options?: Partial<CorsOptions>): Middleware => {\n return createMiddleware({\n name: \"$cors\",\n options: options as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const corsProvider = alepha.inject(ServerCorsProvider);\n\n return async (...args) => {\n const request = alepha.get(\"alepha.http.request\");\n\n if (!request) {\n throw new AlephaError(\n \"$cors requires a request context (use inside $action)\",\n );\n }\n\n const corsConfig = corsProvider.buildCorsOptions(options ?? {});\n corsProvider.applyCorsHeaders(request, corsConfig);\n\n // OPTIONS preflight → respond immediately, skip handler\n if (request.method === \"OPTIONS\") {\n request.reply.setStatus(204);\n return;\n }\n\n return next(...args);\n };\n },\n });\n};\n","import { $module } from \"alepha\";\nimport {\n type CorsOptions,\n ServerCorsProvider,\n} from \"./providers/ServerCorsProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$cors.ts\";\nexport * from \"./providers/ServerCorsProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha/server\" {\n interface ServerRoute {\n /**\n * Route-specific CORS configuration.\n * If set, overrides the global CORS options for this route.\n */\n cors?: CorsOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Cross-Origin Resource Sharing configuration.\n *\n * **Features:**\n * - CORS policy definition\n *\n * @module alepha.server.cors\n */\nexport const AlephaServerCors = $module({\n name: \"alepha.server.cors\",\n services: [ServerCorsProvider],\n});\n"],"mappings":"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"}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import * as _$alepha from "alepha";
|
|
2
1
|
import { Alepha, Middleware } from "alepha";
|
|
3
|
-
import * as _$alepha_cache0 from "alepha/cache";
|
|
4
2
|
import { CachePrimitiveOptions } from "alepha/cache";
|
|
5
3
|
import { CryptoProvider } from "alepha/crypto";
|
|
6
4
|
import { DateTimeProvider, DurationLike } from "alepha/datetime";
|
|
7
|
-
import * as _$alepha_logger0 from "alepha/logger";
|
|
8
5
|
import { ServerRequest, ServerRoute } from "alepha/server";
|
|
9
6
|
|
|
10
7
|
//#region ../../src/server/etag/primitives/$etag.d.ts
|
|
@@ -138,11 +135,11 @@ interface EtagMiddlewareOptionsResolved {
|
|
|
138
135
|
//#endregion
|
|
139
136
|
//#region ../../src/server/etag/providers/ServerEtagProvider.d.ts
|
|
140
137
|
declare class ServerEtagProvider {
|
|
141
|
-
protected readonly log:
|
|
138
|
+
protected readonly log: import("alepha/logger").Logger;
|
|
142
139
|
protected readonly alepha: Alepha;
|
|
143
140
|
protected readonly crypto: CryptoProvider;
|
|
144
141
|
protected readonly time: DateTimeProvider;
|
|
145
|
-
protected readonly cache:
|
|
142
|
+
protected readonly cache: import("alepha/cache").CacheMiddlewareFn<RouteCacheEntry>;
|
|
146
143
|
generateETag(content: string | Buffer): string;
|
|
147
144
|
invalidate(route: ServerRoute): Promise<void>;
|
|
148
145
|
/**
|
|
@@ -153,16 +150,16 @@ declare class ServerEtagProvider {
|
|
|
153
150
|
/**
|
|
154
151
|
* After an action response, store it in cache if store is enabled.
|
|
155
152
|
*/
|
|
156
|
-
protected readonly onActionResponse:
|
|
153
|
+
protected readonly onActionResponse: import("alepha").HookPrimitive<"action:onResponse">;
|
|
157
154
|
/**
|
|
158
155
|
* Before sending the response, check ETag for etag-only routes.
|
|
159
156
|
* This handles the case where etag is enabled but store is not.
|
|
160
157
|
*/
|
|
161
|
-
protected readonly onSend:
|
|
158
|
+
protected readonly onSend: import("alepha").HookPrimitive<"server:onSend">;
|
|
162
159
|
/**
|
|
163
160
|
* After the response is generated, store it and set ETag headers.
|
|
164
161
|
*/
|
|
165
|
-
protected readonly onResponse:
|
|
162
|
+
protected readonly onResponse: import("alepha").HookPrimitive<"server:onResponse">;
|
|
166
163
|
buildCacheControlHeader(options?: EtagMiddlewareOptionsResolved): string | undefined;
|
|
167
164
|
shouldStore(options?: EtagMiddlewareOptionsResolved): boolean;
|
|
168
165
|
shouldUseEtag(options?: EtagMiddlewareOptionsResolved): boolean;
|
|
@@ -194,7 +191,7 @@ interface RouteCacheEntry {
|
|
|
194
191
|
*
|
|
195
192
|
* @module alepha.server.etag
|
|
196
193
|
*/
|
|
197
|
-
declare const AlephaServerEtag:
|
|
194
|
+
declare const AlephaServerEtag: import("alepha").Service<import("alepha").Module>;
|
|
198
195
|
//#endregion
|
|
199
196
|
export { $etag, AlephaServerEtag, EtagMiddlewareOptions, EtagMiddlewareOptionsResolved, ServerEtagProvider, resolveEtagOptions };
|
|
200
197
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/server/etag/primitives/$etag.ts","../../../src/server/etag/providers/ServerEtagProvider.ts","../../../src/server/etag/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/server/etag/primitives/$etag.ts","../../../src/server/etag/providers/ServerEtagProvider.ts","../../../src/server/etag/index.ts"],"mappings":";;;;;;;;;;;AAyCA;;;;;;;;AAkCC;AAID;;;;;;;;AAEgC;AAchC;;;;;;;;;;;;cAtDa,KAAA,GAAS,OAAA,GAAU,qBAAA,KAAwB,UAkCvD;AAAA,iBAIe,kBAAA,CACd,OAAA,GAAU,qBAAA,GACT,6BAA6B;AAAA,KAcpB,qBAAA;;;;;;;;;EA+DA;;;;;;AAc4C;EA7DlD,KAAA,UAAe,YAAA,GAAe,qBAAA;EAiEU;;;;;EA1DxC,IAAA;EA0EkC;;;;;EAnElC,OAAA;IAqDJ;;;IA9CU,MAAA;IAqDJ;;;IAjDI,OAAA;IAoDJ;;;IAhDI,OAAA;IAmDJ;;;IA/CI,OAAA;IAgDwC;;;;IA3CxC,MAAA,YAAkB,YAAA;IC3IC;;;;IDgJnB,OAAA,YAAmB,YAAA;IC3IL;;;ID+Id,cAAA;IC5HC;;;IDgID,eAAA;IC4NmB;;;IDxNnB,SAAA;ICgP4C;;;;;ID1O5C,oBAAA,YAAgC,YAAA;EAAA;AAAA;AAAA,UAI3B,6BAAA;EACf,KAAA,UAAe,YAAA,GAAe,qBAAA;EAC9B,IAAA;EACA,OAAA;IAIM,MAAA;IACA,OAAA;IACA,OAAA;IACA,OAAA;IACA,MAAA,YAAkB,YAAA;IAClB,OAAA,YAAmB,YAAA;IACnB,cAAA;IACA,eAAA;IACA,SAAA;IACA,oBAAA,YAAgC,YAAA;EAAA;AAAA;;;cCtL3B,kBAAA;EAAA,mBACQ,GAAA,0BAAG,MAAA;EAAA,mBACH,MAAA,EAAM,MAAA;EAAA,mBACN,MAAA,EAAM,cAAA;EAAA,mBACN,IAAA,EAAI,gBAAA;EAAA,mBACJ,KAAA,yBAAK,iBAAA,CAAA,eAAA;EAKjB,YAAA,CAAa,OAAA,WAAkB,MAAA;EAKzB,UAAA,CAAW,KAAA,EAAO,WAAA,GAAW,OAAA;EDgBY;;AAkCvD;AAID;EC9Ce,UAAA,CACX,OAAA,EAAS,aAAA,EACT,OAAA,EAAS,6BAAA,GACR,OAAA;;;;qBA8EgB,gBAAA,mBAAgB,aAAA;EDjClC;;AAA6B;AAchC;EAdG,mBC0FkB,MAAA,mBAAM,aAAA;;;;qBA2CN,UAAA,mBAAU,aAAA;EA0GtB,uBAAA,CACL,OAAA,GAAU,6BAAA;EA6DL,WAAA,CAAY,OAAA,GAAU,6BAAA;EAMtB,aAAA,CAAc,OAAA,GAAU,6BAAA;EAAA,UAUrB,iBAAA,CAAkB,QAAA,WAAmB,YAAA;EAAA,UAQrC,cAAA,CAAe,KAAA,EAAO,WAAA,EAAa,MAAA,GAAS,aAAA;EDvSnC;;;;EAAA,UCuTH,qBAAA,CACd,MAAA,EAAQ,cAAA,CAAe,UAAA,GACvB,GAAA,UACA,MAAA,sBACA,WAAA,sBACA,YAAA,YACC,OAAA;AAAA;AAAA,UAwCK,eAAA;EACR,WAAA;EACA,IAAA;EACA,MAAA;EACA,YAAA;EACA,IAAA;AAAA;;;;;;;;ADhbF;;;;;;cElBa,gBAAA,mBAAgB,OAAA,kBAAA,MAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/server/etag/providers/ServerEtagProvider.ts","../../../src/server/etag/primitives/$etag.ts","../../../src/server/etag/index.ts"],"sourcesContent":["import { $hook, $inject, Alepha } from \"alepha\";\nimport { $cache } from \"alepha/cache\";\nimport { CryptoProvider } from \"alepha/crypto\";\nimport { DateTimeProvider, type DurationLike } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport type { ServerRequest, ServerRoute } from \"alepha/server\";\nimport type { EtagMiddlewareOptionsResolved } from \"../primitives/$etag.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class ServerEtagProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly crypto = $inject(CryptoProvider);\n protected readonly time = $inject(DateTimeProvider);\n protected readonly cache = $cache<RouteCacheEntry>({\n provider: \"memory\",\n name: \"http:server\",\n });\n\n public generateETag(content: string | Buffer): string {\n const data = typeof content === \"string\" ? content : content.toString();\n return `\"${this.crypto.hash(data, \"md5\")}\"`;\n }\n\n public async invalidate(route: ServerRoute) {\n await this.cache.invalidate(this.createCacheKey(route));\n }\n\n /**\n * Check cache for a stored response. Returns the cached body if found, undefined otherwise.\n * Called by the $etag() middleware before the handler runs.\n */\n public async checkCache(\n request: ServerRequest,\n options: EtagMiddlewareOptionsResolved,\n ): Promise<any> {\n if (!this.shouldStore(options)) {\n return undefined;\n }\n\n // Build key from route metadata (set by ServerRouterProvider before handler)\n // or from action request context (for .run() direct calls)\n const actionRequest = this.alepha.store.get(\"alepha.action.request\");\n\n const keySource = {\n method:\n request.metadata?.routeMethod ??\n actionRequest?.method ??\n request.method ??\n \"GET\",\n path:\n request.metadata?.routePath ??\n String(actionRequest?.url ?? request.url ?? \"\"),\n } as ServerRoute;\n\n const key = this.createCacheKey(keySource, actionRequest ?? request);\n const cached = await this.cache.get(key);\n\n if (!cached) {\n this.log.trace(\"Cache miss\", { key });\n return undefined;\n }\n\n this.log.trace(\"Cache hit\", { key });\n\n // Mark as cache hit in request metadata\n request.metadata ??= {} as any;\n request.metadata.etagHit = true;\n\n // For HTTP routes, set reply headers\n if (request.reply) {\n // Check if client has matching ETag - return 304\n if (\n request.headers?.[\"if-none-match\"] === cached.hash ||\n request.headers?.[\"if-modified-since\"] === cached.lastModified\n ) {\n request.reply.status = 304;\n request.reply.setHeader(\"etag\", cached.hash);\n request.reply.setHeader(\"last-modified\", cached.lastModified);\n this.log.trace(\"ETag match, returning 304\", {\n key,\n etag: cached.hash,\n });\n return request.reply.body;\n }\n\n request.reply.body = cached.body;\n request.reply.status = cached.status ?? 200;\n\n if (cached.contentType) {\n request.reply.setHeader(\"Content-Type\", cached.contentType);\n }\n\n request.reply.setHeader(\"etag\", cached.hash);\n request.reply.setHeader(\"last-modified\", cached.lastModified);\n }\n\n // For action direct invocations, return body\n const body =\n cached.contentType === \"application/json\"\n ? JSON.parse(cached.body)\n : cached.body;\n\n return body;\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n // Hooks (post-handler, read from request.metadata)\n // -------------------------------------------------------------------------------------------------------------------\n\n /**\n * After an action response, store it in cache if store is enabled.\n */\n protected readonly onActionResponse = $hook({\n on: \"action:onResponse\",\n handler: async ({ action, request, response }) => {\n const options = request.metadata\n ?.etagOptions as EtagMiddlewareOptionsResolved;\n if (!options || !this.shouldStore(options)) {\n return;\n }\n\n // Skip if this was a cache hit (don't re-store)\n if (request.metadata?.etagHit) {\n return;\n }\n\n // Don't cache error responses (status >= 400)\n if (request.reply.status && request.reply.status >= 400) {\n return;\n }\n\n if (!response) {\n return;\n }\n\n const contentType =\n typeof response === \"string\" ? \"text/plain\" : \"application/json\";\n const body =\n contentType === \"text/plain\" ? response : JSON.stringify(response);\n\n const generatedEtag = this.generateETag(body);\n const lastModified = this.time.toISOString();\n\n const key = this.createCacheKey(action.route, request);\n\n this.log.trace(\"Storing action response\", {\n key,\n action: action.name,\n });\n\n await this.cache.set(key, {\n body: body,\n lastModified,\n contentType: contentType,\n hash: generatedEtag,\n });\n\n // Set Cache-Control header if configured\n const cacheControl = this.buildCacheControlHeader(options);\n if (cacheControl) {\n request.reply.setHeader(\"cache-control\", cacheControl);\n }\n },\n });\n\n /**\n * Before sending the response, check ETag for etag-only routes.\n * This handles the case where etag is enabled but store is not.\n */\n protected readonly onSend = $hook({\n on: \"server:onSend\",\n handler: ({ request }) => {\n const options = request.metadata\n ?.etagOptions as EtagMiddlewareOptionsResolved;\n if (!options) {\n return;\n }\n\n const shouldStore = this.shouldStore(options);\n const shouldUseEtag = this.shouldUseEtag(options);\n\n if (request.reply.headers.etag) {\n // ETag already set, skip\n return;\n }\n\n if (\n !shouldStore &&\n shouldUseEtag &&\n request.reply.body != null &&\n (typeof request.reply.body === \"string\" ||\n Buffer.isBuffer(request.reply.body))\n ) {\n const generatedEtag = this.generateETag(request.reply.body);\n\n if (request.headers[\"if-none-match\"] === generatedEtag) {\n request.reply.status = 304;\n request.reply.body = undefined;\n request.reply.setHeader(\"etag\", generatedEtag);\n this.log.trace(\"ETag match on send, returning 304\", {\n route: request.url,\n etag: generatedEtag,\n });\n return;\n }\n }\n },\n });\n\n /**\n * After the response is generated, store it and set ETag headers.\n */\n protected readonly onResponse = $hook({\n on: \"server:onResponse\",\n priority: \"first\",\n handler: async ({ route, request, response }) => {\n const options = request.metadata\n ?.etagOptions as EtagMiddlewareOptionsResolved;\n if (!options) {\n return;\n }\n\n // Set Cache-Control header if configured\n const cacheControl = this.buildCacheControlHeader(options);\n if (cacheControl) {\n response.headers[\"cache-control\"] = cacheControl;\n }\n\n const shouldStore = this.shouldStore(options);\n const shouldUseEtag = this.shouldUseEtag(options);\n\n // Skip if neither cache nor etag is enabled\n if (!shouldStore && !shouldUseEtag) {\n return;\n }\n\n // Skip if this was a cache hit (don't re-store)\n if (request.metadata?.etagHit) {\n return;\n }\n\n // Don't cache error responses (status >= 400)\n if (response.status && response.status >= 400) {\n return;\n }\n\n // Initialize headers if not present\n response.headers ??= {};\n\n const key = this.createCacheKey(route, request);\n\n // Handle ReadableStream responses (e.g., SSR streaming)\n if (response.body instanceof ReadableStream && shouldStore) {\n // Tee the stream: one for client, one for cache collection\n const [clientStream, cacheStream] = (\n response.body as ReadableStream<Uint8Array>\n ).tee();\n\n // Replace response body with client stream (continues streaming to client)\n response.body = clientStream as typeof response.body;\n\n // Collect cache stream in background (non-blocking)\n this.collectStreamForCache(\n cacheStream,\n key,\n response.status,\n response.headers?.[\"content-type\"],\n shouldUseEtag,\n )\n .then((hash) => {\n if (shouldUseEtag && hash) {\n this.log.trace(\"Stream cached with hash\", { key, hash });\n }\n })\n .catch((err) => {\n this.log.warn(\"Failed to cache stream\", { key, error: err });\n });\n\n return;\n }\n\n // Only process string responses (text, html, json, etc.)\n if (typeof response.body !== \"string\") {\n return;\n }\n\n const generatedEtag = this.generateETag(response.body);\n const lastModified = this.time.toISOString();\n\n // Store response if storing is enabled\n if (shouldStore) {\n this.log.trace(\"Storing response\", {\n key,\n route: route.path,\n etag: shouldUseEtag,\n });\n\n await this.cache.set(key, {\n body: response.body,\n status: response.status,\n contentType: response.headers?.[\"content-type\"],\n lastModified,\n hash: generatedEtag,\n });\n }\n\n // Set ETag headers if etag is enabled\n if (shouldUseEtag) {\n response.headers.etag = generatedEtag;\n response.headers[\"last-modified\"] = lastModified;\n }\n },\n });\n\n // -------------------------------------------------------------------------------------------------------------------\n // Public helpers\n // -------------------------------------------------------------------------------------------------------------------\n\n public buildCacheControlHeader(\n options?: EtagMiddlewareOptionsResolved,\n ): string | undefined {\n if (!options) {\n return undefined;\n }\n\n const control = options.control;\n if (!control) {\n return undefined;\n }\n\n // If control is a string, return it directly\n if (typeof control === \"string\") {\n return control;\n }\n\n // If control is true, return default Cache-Control\n if (control === true) {\n return \"public, max-age=300\";\n }\n\n // Build Cache-Control from object directives\n const directives: string[] = [];\n\n if (control.public) {\n directives.push(\"public\");\n }\n if (control.private) {\n directives.push(\"private\");\n }\n if (control.noCache) {\n directives.push(\"no-cache\");\n }\n if (control.noStore) {\n directives.push(\"no-store\");\n }\n if (control.maxAge !== undefined) {\n const maxAgeSeconds = this.durationToSeconds(control.maxAge);\n directives.push(`max-age=${maxAgeSeconds}`);\n }\n if (control.sMaxAge !== undefined) {\n const sMaxAgeSeconds = this.durationToSeconds(control.sMaxAge);\n directives.push(`s-maxage=${sMaxAgeSeconds}`);\n }\n if (control.mustRevalidate) {\n directives.push(\"must-revalidate\");\n }\n if (control.proxyRevalidate) {\n directives.push(\"proxy-revalidate\");\n }\n if (control.immutable) {\n directives.push(\"immutable\");\n }\n if (control.staleWhileRevalidate !== undefined) {\n const seconds = this.durationToSeconds(control.staleWhileRevalidate);\n directives.push(`stale-while-revalidate=${seconds}`);\n }\n\n return directives.length > 0 ? directives.join(\", \") : undefined;\n }\n\n public shouldStore(options?: EtagMiddlewareOptionsResolved): boolean {\n if (!options) return false;\n if (options.store) return true;\n return false;\n }\n\n public shouldUseEtag(options?: EtagMiddlewareOptionsResolved): boolean {\n if (!options) return false;\n if (options.etag) return true;\n return false;\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n // Protected helpers\n // -------------------------------------------------------------------------------------------------------------------\n\n protected durationToSeconds(duration: number | DurationLike): number {\n if (typeof duration === \"number\") {\n return duration;\n }\n\n return this.time.duration(duration).asSeconds();\n }\n\n protected createCacheKey(route: ServerRoute, config?: ServerRequest): string {\n const params: string[] = [];\n for (const [key, value] of Object.entries(config?.params ?? {})) {\n params.push(`${key}=${value}`);\n }\n for (const [key, value] of Object.entries(config?.query ?? {})) {\n params.push(`${key}=${value}`);\n }\n\n return `${route.method}:${(route.path ?? \"\").replaceAll(\":\", \"\")}:${params.join(\",\").replaceAll(\":\", \"\")}`;\n }\n\n /**\n * Collect a ReadableStream into a string and store it in the cache.\n * This runs in the background while the original stream is sent to the client.\n */\n protected async collectStreamForCache(\n stream: ReadableStream<Uint8Array>,\n key: string,\n status: number | undefined,\n contentType: string | undefined,\n generateEtag: boolean,\n ): Promise<string | undefined> {\n const chunks: Uint8Array[] = [];\n const reader = stream.getReader();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n\n // Combine chunks into a single string\n const decoder = new TextDecoder();\n const body =\n chunks\n .map((chunk) => decoder.decode(chunk, { stream: true }))\n .join(\"\") + decoder.decode(); // Flush remaining\n\n const hash = this.generateETag(body);\n const lastModified = this.time.toISOString();\n\n this.log.trace(\"Storing streamed response\", { key });\n\n await this.cache.set(key, {\n body,\n status,\n contentType,\n lastModified,\n hash,\n });\n\n return generateEtag ? hash : undefined;\n } finally {\n reader.releaseLock();\n }\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ninterface RouteCacheEntry {\n contentType?: string;\n body: any;\n status?: number;\n lastModified: string;\n hash: string;\n}\n","import { createMiddleware, type Middleware } from \"alepha\";\nimport type { CachePrimitiveOptions } from \"alepha/cache\";\nimport type { DurationLike } from \"alepha/datetime\";\nimport { ServerEtagProvider } from \"../providers/ServerEtagProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Middleware that enables ETag-based response caching per-route.\n *\n * Sets per-request etag options in the ALS context.\n * The global `ServerEtagProvider` hooks read these\n * to generate ETags, handle 304s, and optionally store responses.\n *\n * When `store` is enabled, the middleware also checks the cache before\n * calling the handler, short-circuiting on cache hits.\n *\n * **Route middleware** — works inside `$action`, `$page`, or any pipeline.\n *\n * ```typescript\n * class UserController {\n * // ETag only (no response caching)\n * getUser = $action({\n * use: [$etag()],\n * handler: async ({ params }) => { ... },\n * });\n *\n * // ETag + response caching (store)\n * getProfile = $action({\n * use: [$etag(true)],\n * handler: async ({ params }) => { ... },\n * });\n *\n * // Fine-grained control\n * getStats = $action({\n * use: [$etag({ store: { ttl: [5, \"minutes\"] }, control: { public: true, maxAge: 300 } })],\n * handler: async ({ params }) => { ... },\n * });\n * }\n * ```\n */\nexport const $etag = (options?: EtagMiddlewareOptions): Middleware => {\n const resolved = resolveEtagOptions(options);\n\n return createMiddleware({\n name: \"$etag\",\n options: resolved as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const etagProvider = alepha.inject(ServerEtagProvider);\n\n return async (...args) => {\n const request = alepha.get(\"alepha.http.request\") ?? args[0];\n\n // Set etag options on request metadata for hooks to read\n if (request?.metadata) {\n request.metadata.etagOptions = resolved;\n }\n\n // If store is enabled, check cache before handler\n if (etagProvider.shouldStore(resolved)) {\n if (request) {\n const cached = await etagProvider.checkCache(request, resolved);\n\n // checkCache sets request.metadata.etagHit on cache hit\n // cached may be undefined for 304 responses (no body)\n if (cached !== undefined || request.metadata?.etagHit) {\n return cached;\n }\n }\n }\n\n return next(...args);\n };\n },\n });\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport function resolveEtagOptions(\n options?: EtagMiddlewareOptions,\n): EtagMiddlewareOptionsResolved {\n if (options === true) {\n return { store: true, etag: true };\n }\n\n if (!options) {\n return { etag: true };\n }\n\n return { etag: true, ...options };\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type EtagMiddlewareOptions =\n /**\n * If true, enables both store and etag.\n */\n | true\n /**\n * Object configuration for fine-grained control.\n */\n | {\n /**\n * If true, enables storing cached responses. (in-memory, Redis, @see alepha/cache for other providers)\n * If a DurationLike is provided, it will be used as the TTL for the cache.\n * If CachePrimitiveOptions is provided, it will be used to configure the cache storage.\n *\n * @default false\n */\n store?: true | DurationLike | CachePrimitiveOptions;\n\n /**\n * If true, enables ETag support for the cached responses.\n *\n * @default true (always true when using $etag)\n */\n etag?: true;\n\n /**\n * - If true, sets Cache-Control to \"public, max-age=300\" (5 minutes).\n * - If string, sets Cache-Control to the provided value directly.\n * - If object, configures Cache-Control directives.\n */\n control?:\n | true\n | string\n | {\n /**\n * Indicates that the response may be cached by any cache.\n */\n public?: boolean;\n /**\n * Indicates that the response is intended for a single user and must not be stored by a shared cache.\n */\n private?: boolean;\n /**\n * Forces caches to submit the request to the origin server for validation before releasing a cached copy.\n */\n noCache?: boolean;\n /**\n * Instructs caches not to store the response.\n */\n noStore?: boolean;\n /**\n * Maximum amount of time a resource is considered fresh.\n * Can be specified as a number (seconds) or as a DurationLike object.\n */\n maxAge?: number | DurationLike;\n /**\n * Overrides max-age for shared caches (e.g., CDNs).\n * Can be specified as a number (seconds) or as a DurationLike object.\n */\n sMaxAge?: number | DurationLike;\n /**\n * Indicates that once a resource becomes stale, caches must not use it without successful validation.\n */\n mustRevalidate?: boolean;\n /**\n * Similar to must-revalidate, but only for shared caches.\n */\n proxyRevalidate?: boolean;\n /**\n * Indicates that the response can be stored but must be revalidated before each use.\n */\n immutable?: boolean;\n /**\n * Time window (in seconds or DurationLike) during which a stale response may be served\n * while a fresh one is fetched in the background.\n * Supported by Cloudflare, modern browsers, and most CDNs.\n */\n staleWhileRevalidate?: number | DurationLike;\n };\n };\n\nexport interface EtagMiddlewareOptionsResolved {\n store?: true | DurationLike | CachePrimitiveOptions;\n etag?: true;\n control?:\n | true\n | string\n | {\n public?: boolean;\n private?: boolean;\n noCache?: boolean;\n noStore?: boolean;\n maxAge?: number | DurationLike;\n sMaxAge?: number | DurationLike;\n mustRevalidate?: boolean;\n proxyRevalidate?: boolean;\n immutable?: boolean;\n staleWhileRevalidate?: number | DurationLike;\n };\n}\n","import { $module } from \"alepha\";\nimport { AlephaCache } from \"alepha/cache\";\nimport { AlephaCrypto } from \"alepha/crypto\";\nimport { ServerEtagProvider } from \"./providers/ServerEtagProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$etag.ts\";\nexport * from \"./providers/ServerEtagProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * ETag-based response caching.\n *\n * **Features:**\n * - ETag generation and validation\n * - Conditional request handling (304 Not Modified)\n * - Optional response caching (store)\n * - Cache-Control header support\n *\n * @module alepha.server.etag\n */\nexport const AlephaServerEtag = $module({\n name: \"alepha.server.etag\",\n services: [AlephaCache, AlephaCrypto, ServerEtagProvider],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/server/etag/providers/ServerEtagProvider.ts","../../../src/server/etag/primitives/$etag.ts","../../../src/server/etag/index.ts"],"sourcesContent":["import { $hook, $inject, Alepha } from \"alepha\";\nimport { $cache } from \"alepha/cache\";\nimport { CryptoProvider } from \"alepha/crypto\";\nimport { DateTimeProvider, type DurationLike } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport type { ServerRequest, ServerRoute } from \"alepha/server\";\nimport type { EtagMiddlewareOptionsResolved } from \"../primitives/$etag.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class ServerEtagProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly crypto = $inject(CryptoProvider);\n protected readonly time = $inject(DateTimeProvider);\n protected readonly cache = $cache<RouteCacheEntry>({\n provider: \"memory\",\n name: \"http:server\",\n });\n\n public generateETag(content: string | Buffer): string {\n const data = typeof content === \"string\" ? content : content.toString();\n return `\"${this.crypto.hash(data, \"md5\")}\"`;\n }\n\n public async invalidate(route: ServerRoute) {\n await this.cache.invalidate(this.createCacheKey(route));\n }\n\n /**\n * Check cache for a stored response. Returns the cached body if found, undefined otherwise.\n * Called by the $etag() middleware before the handler runs.\n */\n public async checkCache(\n request: ServerRequest,\n options: EtagMiddlewareOptionsResolved,\n ): Promise<any> {\n if (!this.shouldStore(options)) {\n return undefined;\n }\n\n // Build key from route metadata (set by ServerRouterProvider before handler)\n // or from action request context (for .run() direct calls)\n const actionRequest = this.alepha.store.get(\"alepha.action.request\");\n\n const keySource = {\n method:\n request.metadata?.routeMethod ??\n actionRequest?.method ??\n request.method ??\n \"GET\",\n path:\n request.metadata?.routePath ??\n String(actionRequest?.url ?? request.url ?? \"\"),\n } as ServerRoute;\n\n const key = this.createCacheKey(keySource, actionRequest ?? request);\n const cached = await this.cache.get(key);\n\n if (!cached) {\n this.log.trace(\"Cache miss\", { key });\n return undefined;\n }\n\n this.log.trace(\"Cache hit\", { key });\n\n // Mark as cache hit in request metadata\n request.metadata ??= {} as any;\n request.metadata.etagHit = true;\n\n // For HTTP routes, set reply headers\n if (request.reply) {\n // Check if client has matching ETag - return 304\n if (\n request.headers?.[\"if-none-match\"] === cached.hash ||\n request.headers?.[\"if-modified-since\"] === cached.lastModified\n ) {\n request.reply.status = 304;\n request.reply.setHeader(\"etag\", cached.hash);\n request.reply.setHeader(\"last-modified\", cached.lastModified);\n this.log.trace(\"ETag match, returning 304\", {\n key,\n etag: cached.hash,\n });\n return request.reply.body;\n }\n\n request.reply.body = cached.body;\n request.reply.status = cached.status ?? 200;\n\n if (cached.contentType) {\n request.reply.setHeader(\"Content-Type\", cached.contentType);\n }\n\n request.reply.setHeader(\"etag\", cached.hash);\n request.reply.setHeader(\"last-modified\", cached.lastModified);\n }\n\n // For action direct invocations, return body\n const body =\n cached.contentType === \"application/json\"\n ? JSON.parse(cached.body)\n : cached.body;\n\n return body;\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n // Hooks (post-handler, read from request.metadata)\n // -------------------------------------------------------------------------------------------------------------------\n\n /**\n * After an action response, store it in cache if store is enabled.\n */\n protected readonly onActionResponse = $hook({\n on: \"action:onResponse\",\n handler: async ({ action, request, response }) => {\n const options = request.metadata\n ?.etagOptions as EtagMiddlewareOptionsResolved;\n if (!options || !this.shouldStore(options)) {\n return;\n }\n\n // Skip if this was a cache hit (don't re-store)\n if (request.metadata?.etagHit) {\n return;\n }\n\n // Don't cache error responses (status >= 400)\n if (request.reply.status && request.reply.status >= 400) {\n return;\n }\n\n if (!response) {\n return;\n }\n\n const contentType =\n typeof response === \"string\" ? \"text/plain\" : \"application/json\";\n const body =\n contentType === \"text/plain\" ? response : JSON.stringify(response);\n\n const generatedEtag = this.generateETag(body);\n const lastModified = this.time.toISOString();\n\n const key = this.createCacheKey(action.route, request);\n\n this.log.trace(\"Storing action response\", {\n key,\n action: action.name,\n });\n\n await this.cache.set(key, {\n body: body,\n lastModified,\n contentType: contentType,\n hash: generatedEtag,\n });\n\n // Set Cache-Control header if configured\n const cacheControl = this.buildCacheControlHeader(options);\n if (cacheControl) {\n request.reply.setHeader(\"cache-control\", cacheControl);\n }\n },\n });\n\n /**\n * Before sending the response, check ETag for etag-only routes.\n * This handles the case where etag is enabled but store is not.\n */\n protected readonly onSend = $hook({\n on: \"server:onSend\",\n handler: ({ request }) => {\n const options = request.metadata\n ?.etagOptions as EtagMiddlewareOptionsResolved;\n if (!options) {\n return;\n }\n\n const shouldStore = this.shouldStore(options);\n const shouldUseEtag = this.shouldUseEtag(options);\n\n if (request.reply.headers.etag) {\n // ETag already set, skip\n return;\n }\n\n if (\n !shouldStore &&\n shouldUseEtag &&\n request.reply.body != null &&\n (typeof request.reply.body === \"string\" ||\n Buffer.isBuffer(request.reply.body))\n ) {\n const generatedEtag = this.generateETag(request.reply.body);\n\n if (request.headers[\"if-none-match\"] === generatedEtag) {\n request.reply.status = 304;\n request.reply.body = undefined;\n request.reply.setHeader(\"etag\", generatedEtag);\n this.log.trace(\"ETag match on send, returning 304\", {\n route: request.url,\n etag: generatedEtag,\n });\n return;\n }\n }\n },\n });\n\n /**\n * After the response is generated, store it and set ETag headers.\n */\n protected readonly onResponse = $hook({\n on: \"server:onResponse\",\n priority: \"first\",\n handler: async ({ route, request, response }) => {\n const options = request.metadata\n ?.etagOptions as EtagMiddlewareOptionsResolved;\n if (!options) {\n return;\n }\n\n // Set Cache-Control header if configured\n const cacheControl = this.buildCacheControlHeader(options);\n if (cacheControl) {\n response.headers[\"cache-control\"] = cacheControl;\n }\n\n const shouldStore = this.shouldStore(options);\n const shouldUseEtag = this.shouldUseEtag(options);\n\n // Skip if neither cache nor etag is enabled\n if (!shouldStore && !shouldUseEtag) {\n return;\n }\n\n // Skip if this was a cache hit (don't re-store)\n if (request.metadata?.etagHit) {\n return;\n }\n\n // Don't cache error responses (status >= 400)\n if (response.status && response.status >= 400) {\n return;\n }\n\n // Initialize headers if not present\n response.headers ??= {};\n\n const key = this.createCacheKey(route, request);\n\n // Handle ReadableStream responses (e.g., SSR streaming)\n if (response.body instanceof ReadableStream && shouldStore) {\n // Tee the stream: one for client, one for cache collection\n const [clientStream, cacheStream] = (\n response.body as ReadableStream<Uint8Array>\n ).tee();\n\n // Replace response body with client stream (continues streaming to client)\n response.body = clientStream as typeof response.body;\n\n // Collect cache stream in background (non-blocking)\n this.collectStreamForCache(\n cacheStream,\n key,\n response.status,\n response.headers?.[\"content-type\"],\n shouldUseEtag,\n )\n .then((hash) => {\n if (shouldUseEtag && hash) {\n this.log.trace(\"Stream cached with hash\", { key, hash });\n }\n })\n .catch((err) => {\n this.log.warn(\"Failed to cache stream\", { key, error: err });\n });\n\n return;\n }\n\n // Only process string responses (text, html, json, etc.)\n if (typeof response.body !== \"string\") {\n return;\n }\n\n const generatedEtag = this.generateETag(response.body);\n const lastModified = this.time.toISOString();\n\n // Store response if storing is enabled\n if (shouldStore) {\n this.log.trace(\"Storing response\", {\n key,\n route: route.path,\n etag: shouldUseEtag,\n });\n\n await this.cache.set(key, {\n body: response.body,\n status: response.status,\n contentType: response.headers?.[\"content-type\"],\n lastModified,\n hash: generatedEtag,\n });\n }\n\n // Set ETag headers if etag is enabled\n if (shouldUseEtag) {\n response.headers.etag = generatedEtag;\n response.headers[\"last-modified\"] = lastModified;\n }\n },\n });\n\n // -------------------------------------------------------------------------------------------------------------------\n // Public helpers\n // -------------------------------------------------------------------------------------------------------------------\n\n public buildCacheControlHeader(\n options?: EtagMiddlewareOptionsResolved,\n ): string | undefined {\n if (!options) {\n return undefined;\n }\n\n const control = options.control;\n if (!control) {\n return undefined;\n }\n\n // If control is a string, return it directly\n if (typeof control === \"string\") {\n return control;\n }\n\n // If control is true, return default Cache-Control\n if (control === true) {\n return \"public, max-age=300\";\n }\n\n // Build Cache-Control from object directives\n const directives: string[] = [];\n\n if (control.public) {\n directives.push(\"public\");\n }\n if (control.private) {\n directives.push(\"private\");\n }\n if (control.noCache) {\n directives.push(\"no-cache\");\n }\n if (control.noStore) {\n directives.push(\"no-store\");\n }\n if (control.maxAge !== undefined) {\n const maxAgeSeconds = this.durationToSeconds(control.maxAge);\n directives.push(`max-age=${maxAgeSeconds}`);\n }\n if (control.sMaxAge !== undefined) {\n const sMaxAgeSeconds = this.durationToSeconds(control.sMaxAge);\n directives.push(`s-maxage=${sMaxAgeSeconds}`);\n }\n if (control.mustRevalidate) {\n directives.push(\"must-revalidate\");\n }\n if (control.proxyRevalidate) {\n directives.push(\"proxy-revalidate\");\n }\n if (control.immutable) {\n directives.push(\"immutable\");\n }\n if (control.staleWhileRevalidate !== undefined) {\n const seconds = this.durationToSeconds(control.staleWhileRevalidate);\n directives.push(`stale-while-revalidate=${seconds}`);\n }\n\n return directives.length > 0 ? directives.join(\", \") : undefined;\n }\n\n public shouldStore(options?: EtagMiddlewareOptionsResolved): boolean {\n if (!options) return false;\n if (options.store) return true;\n return false;\n }\n\n public shouldUseEtag(options?: EtagMiddlewareOptionsResolved): boolean {\n if (!options) return false;\n if (options.etag) return true;\n return false;\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n // Protected helpers\n // -------------------------------------------------------------------------------------------------------------------\n\n protected durationToSeconds(duration: number | DurationLike): number {\n if (typeof duration === \"number\") {\n return duration;\n }\n\n return this.time.duration(duration).asSeconds();\n }\n\n protected createCacheKey(route: ServerRoute, config?: ServerRequest): string {\n const params: string[] = [];\n for (const [key, value] of Object.entries(config?.params ?? {})) {\n params.push(`${key}=${value}`);\n }\n for (const [key, value] of Object.entries(config?.query ?? {})) {\n params.push(`${key}=${value}`);\n }\n\n return `${route.method}:${(route.path ?? \"\").replaceAll(\":\", \"\")}:${params.join(\",\").replaceAll(\":\", \"\")}`;\n }\n\n /**\n * Collect a ReadableStream into a string and store it in the cache.\n * This runs in the background while the original stream is sent to the client.\n */\n protected async collectStreamForCache(\n stream: ReadableStream<Uint8Array>,\n key: string,\n status: number | undefined,\n contentType: string | undefined,\n generateEtag: boolean,\n ): Promise<string | undefined> {\n const chunks: Uint8Array[] = [];\n const reader = stream.getReader();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n\n // Combine chunks into a single string\n const decoder = new TextDecoder();\n const body =\n chunks\n .map((chunk) => decoder.decode(chunk, { stream: true }))\n .join(\"\") + decoder.decode(); // Flush remaining\n\n const hash = this.generateETag(body);\n const lastModified = this.time.toISOString();\n\n this.log.trace(\"Storing streamed response\", { key });\n\n await this.cache.set(key, {\n body,\n status,\n contentType,\n lastModified,\n hash,\n });\n\n return generateEtag ? hash : undefined;\n } finally {\n reader.releaseLock();\n }\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ninterface RouteCacheEntry {\n contentType?: string;\n body: any;\n status?: number;\n lastModified: string;\n hash: string;\n}\n","import { createMiddleware, type Middleware } from \"alepha\";\nimport type { CachePrimitiveOptions } from \"alepha/cache\";\nimport type { DurationLike } from \"alepha/datetime\";\nimport { ServerEtagProvider } from \"../providers/ServerEtagProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Middleware that enables ETag-based response caching per-route.\n *\n * Sets per-request etag options in the ALS context.\n * The global `ServerEtagProvider` hooks read these\n * to generate ETags, handle 304s, and optionally store responses.\n *\n * When `store` is enabled, the middleware also checks the cache before\n * calling the handler, short-circuiting on cache hits.\n *\n * **Route middleware** — works inside `$action`, `$page`, or any pipeline.\n *\n * ```typescript\n * class UserController {\n * // ETag only (no response caching)\n * getUser = $action({\n * use: [$etag()],\n * handler: async ({ params }) => { ... },\n * });\n *\n * // ETag + response caching (store)\n * getProfile = $action({\n * use: [$etag(true)],\n * handler: async ({ params }) => { ... },\n * });\n *\n * // Fine-grained control\n * getStats = $action({\n * use: [$etag({ store: { ttl: [5, \"minutes\"] }, control: { public: true, maxAge: 300 } })],\n * handler: async ({ params }) => { ... },\n * });\n * }\n * ```\n */\nexport const $etag = (options?: EtagMiddlewareOptions): Middleware => {\n const resolved = resolveEtagOptions(options);\n\n return createMiddleware({\n name: \"$etag\",\n options: resolved as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const etagProvider = alepha.inject(ServerEtagProvider);\n\n return async (...args) => {\n const request = alepha.get(\"alepha.http.request\") ?? args[0];\n\n // Set etag options on request metadata for hooks to read\n if (request?.metadata) {\n request.metadata.etagOptions = resolved;\n }\n\n // If store is enabled, check cache before handler\n if (etagProvider.shouldStore(resolved)) {\n if (request) {\n const cached = await etagProvider.checkCache(request, resolved);\n\n // checkCache sets request.metadata.etagHit on cache hit\n // cached may be undefined for 304 responses (no body)\n if (cached !== undefined || request.metadata?.etagHit) {\n return cached;\n }\n }\n }\n\n return next(...args);\n };\n },\n });\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport function resolveEtagOptions(\n options?: EtagMiddlewareOptions,\n): EtagMiddlewareOptionsResolved {\n if (options === true) {\n return { store: true, etag: true };\n }\n\n if (!options) {\n return { etag: true };\n }\n\n return { etag: true, ...options };\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type EtagMiddlewareOptions =\n /**\n * If true, enables both store and etag.\n */\n | true\n /**\n * Object configuration for fine-grained control.\n */\n | {\n /**\n * If true, enables storing cached responses. (in-memory, Redis, @see alepha/cache for other providers)\n * If a DurationLike is provided, it will be used as the TTL for the cache.\n * If CachePrimitiveOptions is provided, it will be used to configure the cache storage.\n *\n * @default false\n */\n store?: true | DurationLike | CachePrimitiveOptions;\n\n /**\n * If true, enables ETag support for the cached responses.\n *\n * @default true (always true when using $etag)\n */\n etag?: true;\n\n /**\n * - If true, sets Cache-Control to \"public, max-age=300\" (5 minutes).\n * - If string, sets Cache-Control to the provided value directly.\n * - If object, configures Cache-Control directives.\n */\n control?:\n | true\n | string\n | {\n /**\n * Indicates that the response may be cached by any cache.\n */\n public?: boolean;\n /**\n * Indicates that the response is intended for a single user and must not be stored by a shared cache.\n */\n private?: boolean;\n /**\n * Forces caches to submit the request to the origin server for validation before releasing a cached copy.\n */\n noCache?: boolean;\n /**\n * Instructs caches not to store the response.\n */\n noStore?: boolean;\n /**\n * Maximum amount of time a resource is considered fresh.\n * Can be specified as a number (seconds) or as a DurationLike object.\n */\n maxAge?: number | DurationLike;\n /**\n * Overrides max-age for shared caches (e.g., CDNs).\n * Can be specified as a number (seconds) or as a DurationLike object.\n */\n sMaxAge?: number | DurationLike;\n /**\n * Indicates that once a resource becomes stale, caches must not use it without successful validation.\n */\n mustRevalidate?: boolean;\n /**\n * Similar to must-revalidate, but only for shared caches.\n */\n proxyRevalidate?: boolean;\n /**\n * Indicates that the response can be stored but must be revalidated before each use.\n */\n immutable?: boolean;\n /**\n * Time window (in seconds or DurationLike) during which a stale response may be served\n * while a fresh one is fetched in the background.\n * Supported by Cloudflare, modern browsers, and most CDNs.\n */\n staleWhileRevalidate?: number | DurationLike;\n };\n };\n\nexport interface EtagMiddlewareOptionsResolved {\n store?: true | DurationLike | CachePrimitiveOptions;\n etag?: true;\n control?:\n | true\n | string\n | {\n public?: boolean;\n private?: boolean;\n noCache?: boolean;\n noStore?: boolean;\n maxAge?: number | DurationLike;\n sMaxAge?: number | DurationLike;\n mustRevalidate?: boolean;\n proxyRevalidate?: boolean;\n immutable?: boolean;\n staleWhileRevalidate?: number | DurationLike;\n };\n}\n","import { $module } from \"alepha\";\nimport { AlephaCache } from \"alepha/cache\";\nimport { AlephaCrypto } from \"alepha/crypto\";\nimport { ServerEtagProvider } from \"./providers/ServerEtagProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$etag.ts\";\nexport * from \"./providers/ServerEtagProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * ETag-based response caching.\n *\n * **Features:**\n * - ETag generation and validation\n * - Conditional request handling (304 Not Modified)\n * - Optional response caching (store)\n * - Cache-Control header support\n *\n * @module alepha.server.etag\n */\nexport const AlephaServerEtag = $module({\n name: \"alepha.server.etag\",\n services: [AlephaCache, AlephaCrypto, ServerEtagProvider],\n});\n"],"mappings":"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"}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import * as _$alepha from "alepha";
|
|
2
1
|
import { Alepha } from "alepha";
|
|
3
|
-
import * as _$alepha_server0 from "alepha/server";
|
|
4
2
|
import { DateTimeProvider } from "alepha/datetime";
|
|
5
|
-
import * as _$typebox from "typebox";
|
|
6
3
|
|
|
7
4
|
//#region ../../src/server/health/providers/ServerHealthProvider.d.ts
|
|
8
5
|
/**
|
|
@@ -13,20 +10,20 @@ import * as _$typebox from "typebox";
|
|
|
13
10
|
declare class ServerHealthProvider {
|
|
14
11
|
protected readonly time: DateTimeProvider;
|
|
15
12
|
protected readonly alepha: Alepha;
|
|
16
|
-
readonly health:
|
|
17
|
-
response:
|
|
18
|
-
message:
|
|
19
|
-
uptime:
|
|
20
|
-
date:
|
|
21
|
-
ready:
|
|
13
|
+
readonly health: import("alepha/server").RoutePrimitive<{
|
|
14
|
+
response: import("typebox").TObject<{
|
|
15
|
+
message: import("typebox").TString;
|
|
16
|
+
uptime: import("typebox").TNumber;
|
|
17
|
+
date: import("typebox").TString;
|
|
18
|
+
ready: import("typebox").TBoolean;
|
|
22
19
|
}>;
|
|
23
20
|
}>;
|
|
24
|
-
readonly healthz:
|
|
25
|
-
response:
|
|
26
|
-
message:
|
|
27
|
-
uptime:
|
|
28
|
-
date:
|
|
29
|
-
ready:
|
|
21
|
+
readonly healthz: import("alepha/server").RoutePrimitive<{
|
|
22
|
+
response: import("typebox").TObject<{
|
|
23
|
+
message: import("typebox").TString;
|
|
24
|
+
uptime: import("typebox").TNumber;
|
|
25
|
+
date: import("typebox").TString;
|
|
26
|
+
ready: import("typebox").TBoolean;
|
|
30
27
|
}>;
|
|
31
28
|
}>;
|
|
32
29
|
protected healthCheck(): {
|
|
@@ -38,11 +35,11 @@ declare class ServerHealthProvider {
|
|
|
38
35
|
}
|
|
39
36
|
//#endregion
|
|
40
37
|
//#region ../../src/server/health/schemas/healthSchema.d.ts
|
|
41
|
-
declare const healthSchema:
|
|
42
|
-
message:
|
|
43
|
-
uptime:
|
|
44
|
-
date:
|
|
45
|
-
ready:
|
|
38
|
+
declare const healthSchema: import("typebox").TObject<{
|
|
39
|
+
message: import("typebox").TString;
|
|
40
|
+
uptime: import("typebox").TNumber;
|
|
41
|
+
date: import("typebox").TString;
|
|
42
|
+
ready: import("typebox").TBoolean;
|
|
46
43
|
}>;
|
|
47
44
|
//#endregion
|
|
48
45
|
//#region ../../src/server/health/index.d.ts
|
|
@@ -54,7 +51,7 @@ declare const healthSchema: _$typebox.TObject<{
|
|
|
54
51
|
*
|
|
55
52
|
* @module alepha.server.health
|
|
56
53
|
*/
|
|
57
|
-
declare const AlephaServerHealth:
|
|
54
|
+
declare const AlephaServerHealth: import("alepha").Service<import("alepha").Module>;
|
|
58
55
|
//#endregion
|
|
59
56
|
export { AlephaServerHealth, ServerHealthProvider, healthSchema };
|
|
60
57
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/server/health/providers/ServerHealthProvider.ts","../../../src/server/health/schemas/healthSchema.ts","../../../src/server/health/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/server/health/providers/ServerHealthProvider.ts","../../../src/server/health/schemas/healthSchema.ts","../../../src/server/health/index.ts"],"mappings":";;;;;;AAUA;;;cAAa,oBAAA;EAAA,mBACQ,IAAA,EAAM,gBAAA;EAAA,mBACN,MAAA,EAAM,MAAA;EAAA,SAET,MAAA,0BAAM,cAAA;;;;;;;;WASN,OAAA,0BAAO,cAAA;;;;;;;;YASb,WAAA;;;;;;;;;cC9BC,YAAA,oBAAY,OAAA;;;;;;;;;;ADQzB;;;;;;cESa,kBAAA,mBAAkB,OAAA,kBAAA,MAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/server/health/schemas/healthSchema.ts","../../../src/server/health/providers/ServerHealthProvider.ts","../../../src/server/health/index.ts"],"sourcesContent":["import { t } from \"alepha\";\n\nexport const healthSchema = t.object({\n message: t.text(),\n uptime: t.number(),\n date: t.datetime(),\n ready: t.boolean(),\n});\n","import { $inject, Alepha } from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $route } from \"alepha/server\";\nimport { healthSchema } from \"../schemas/healthSchema.ts\";\n\n/**\n * Register `/health` & `/healthz` endpoint.\n *\n * - Provides basic health information about the server.\n */\nexport class ServerHealthProvider {\n protected readonly time: DateTimeProvider = $inject(DateTimeProvider);\n protected readonly alepha = $inject(Alepha);\n\n public readonly health = $route({\n path: \"/health\",\n schema: {\n response: healthSchema,\n },\n silent: true,\n handler: () => this.healthCheck(),\n });\n\n public readonly healthz = $route({\n path: \"/healthz\",\n schema: {\n response: healthSchema,\n },\n silent: true,\n handler: () => this.healthCheck(),\n });\n\n protected healthCheck() {\n return {\n message: \"OK\",\n uptime: Math.floor(process.uptime()),\n date: this.time.nowISOString(),\n ready: this.alepha.isReady(),\n };\n }\n}\n","import { $module } from \"alepha\";\nimport { AlephaServer } from \"alepha/server\";\nimport { ServerHealthProvider } from \"./providers/ServerHealthProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./providers/ServerHealthProvider.ts\";\nexport * from \"./schemas/healthSchema.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Application health monitoring endpoints.\n *\n * **Features:**\n * - `GET /health` endpoint\n *\n * @module alepha.server.health\n */\nexport const AlephaServerHealth = $module({\n name: \"alepha.server.health\",\n services: [AlephaServer, ServerHealthProvider],\n});\n"],"mappings":";;;;AAEA,MAAa,eAAe,EAAE,OAAO;CACnC,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/server/health/schemas/healthSchema.ts","../../../src/server/health/providers/ServerHealthProvider.ts","../../../src/server/health/index.ts"],"sourcesContent":["import { t } from \"alepha\";\n\nexport const healthSchema = t.object({\n message: t.text(),\n uptime: t.number(),\n date: t.datetime(),\n ready: t.boolean(),\n});\n","import { $inject, Alepha } from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $route } from \"alepha/server\";\nimport { healthSchema } from \"../schemas/healthSchema.ts\";\n\n/**\n * Register `/health` & `/healthz` endpoint.\n *\n * - Provides basic health information about the server.\n */\nexport class ServerHealthProvider {\n protected readonly time: DateTimeProvider = $inject(DateTimeProvider);\n protected readonly alepha = $inject(Alepha);\n\n public readonly health = $route({\n path: \"/health\",\n schema: {\n response: healthSchema,\n },\n silent: true,\n handler: () => this.healthCheck(),\n });\n\n public readonly healthz = $route({\n path: \"/healthz\",\n schema: {\n response: healthSchema,\n },\n silent: true,\n handler: () => this.healthCheck(),\n });\n\n protected healthCheck() {\n return {\n message: \"OK\",\n uptime: Math.floor(process.uptime()),\n date: this.time.nowISOString(),\n ready: this.alepha.isReady(),\n };\n }\n}\n","import { $module } from \"alepha\";\nimport { AlephaServer } from \"alepha/server\";\nimport { ServerHealthProvider } from \"./providers/ServerHealthProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./providers/ServerHealthProvider.ts\";\nexport * from \"./schemas/healthSchema.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Application health monitoring endpoints.\n *\n * **Features:**\n * - `GET /health` endpoint\n *\n * @module alepha.server.health\n */\nexport const AlephaServerHealth = $module({\n name: \"alepha.server.health\",\n services: [AlephaServer, ServerHealthProvider],\n});\n"],"mappings":";;;;AAEA,MAAa,eAAe,EAAE,OAAO;CACnC,SAAS,EAAE,KAAK;CAChB,QAAQ,EAAE,OAAO;CACjB,MAAM,EAAE,SAAS;CACjB,OAAO,EAAE,QAAQ;AACnB,CAAC;;;;;;;;ACGD,IAAa,uBAAb,MAAkC;CAChC,OAA4C,QAAQ,gBAAgB;CACpE,SAA4B,QAAQ,MAAM;CAE1C,SAAyB,OAAO;EAC9B,MAAM;EACN,QAAQ,EACN,UAAU,aACZ;EACA,QAAQ;EACR,eAAe,KAAK,YAAY;CAClC,CAAC;CAED,UAA0B,OAAO;EAC/B,MAAM;EACN,QAAQ,EACN,UAAU,aACZ;EACA,QAAQ;EACR,eAAe,KAAK,YAAY;CAClC,CAAC;CAED,cAAwB;EACtB,OAAO;GACL,SAAS;GACT,QAAQ,KAAK,MAAM,QAAQ,OAAO,CAAC;GACnC,MAAM,KAAK,KAAK,aAAa;GAC7B,OAAO,KAAK,OAAO,QAAQ;EAC7B;CACF;AACF;;;;;;;;;;;ACrBA,MAAa,qBAAqB,QAAQ;CACxC,MAAM;CACN,UAAU,CAAC,cAAc,oBAAoB;AAC/C,CAAC"}
|
|
@@ -265,6 +265,8 @@ var LinkProvider = class LinkProvider {
|
|
|
265
265
|
* - `can("admin:user:read")` → O(1) set lookup
|
|
266
266
|
*/
|
|
267
267
|
can(name) {
|
|
268
|
+
const registry = this.alepha.store.get("alepha.server.request.apiLinks");
|
|
269
|
+
if (registry && registry !== this.lastLoadedRegistry) this.loadRegistry(registry);
|
|
268
270
|
if (this.actionMap.size > 0) {
|
|
269
271
|
if (this.actionMap.has(name)) return true;
|
|
270
272
|
} else {
|