alepha 0.23.0 → 0.24.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/assets/swagger-ui/swagger-ui-bundle.js +1 -1
- package/assets/swagger-ui/swagger-ui-standalone-preset.js +1 -1
- package/dist/api/audits/index.browser.js +49 -52
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.d.ts +629 -565
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +206 -98
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.browser.js +44 -44
- package/dist/api/files/index.browser.js.map +1 -1
- package/dist/api/files/index.d.ts +292 -293
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +61 -64
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +61 -59
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +546 -347
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +165 -86
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +247 -235
- package/dist/api/keys/index.d.ts.map +1 -1
- package/dist/api/keys/index.js +63 -66
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js +40 -38
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +339 -209
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +108 -60
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/oauth/index.d.ts +190 -78
- package/dist/api/oauth/index.d.ts.map +1 -1
- package/dist/api/oauth/index.js +290 -68
- package/dist/api/oauth/index.js.map +1 -1
- package/dist/api/organizations/index.browser.js +14 -14
- package/dist/api/organizations/index.browser.js.map +1 -1
- package/dist/api/organizations/index.d.ts +158 -134
- package/dist/api/organizations/index.d.ts.map +1 -1
- package/dist/api/organizations/index.js +18 -18
- package/dist/api/organizations/index.js.map +1 -1
- package/dist/api/parameters/index.browser.js +74 -53
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +607 -513
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +205 -127
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/payments/index.d.ts +652 -449
- package/dist/api/payments/index.d.ts.map +1 -1
- package/dist/api/payments/index.js +159 -93
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/subscriptions/index.d.ts +1032 -762
- package/dist/api/subscriptions/index.d.ts.map +1 -1
- package/dist/api/subscriptions/index.js +166 -185
- package/dist/api/subscriptions/index.js.map +1 -1
- package/dist/api/users/index.browser.js +48 -71
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +1297 -1143
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +456 -392
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.browser.js +17 -17
- package/dist/api/verifications/index.browser.js.map +1 -1
- package/dist/api/verifications/index.d.ts +167 -155
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +47 -95
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/background/index.d.ts +0 -1
- package/dist/background/index.d.ts.map +1 -1
- package/dist/background/index.js.map +1 -1
- package/dist/background/index.workerd.js.map +1 -1
- package/dist/batch/index.d.ts +0 -1
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/bin/index.js +1 -1
- package/dist/bin/index.js.map +1 -1
- package/dist/bucket/index.d.ts +27 -32
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +56 -26
- package/dist/bucket/index.js.map +1 -1
- package/dist/bucket/index.workerd.js +52 -24
- package/dist/bucket/index.workerd.js.map +1 -1
- package/dist/cache/core/index.d.ts +4 -6
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js +4 -10
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js +4 -10
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/cache/database/index.d.ts +22 -23
- package/dist/cache/database/index.d.ts.map +1 -1
- package/dist/cache/database/index.js +11 -20
- package/dist/cache/database/index.js.map +1 -1
- package/dist/cache/redis/index.d.ts +3 -4
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cache/redis/index.js +2 -2
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/captcha/index.d.ts.map +1 -1
- package/dist/captcha/index.js +4 -4
- package/dist/captcha/index.js.map +1 -1
- package/dist/cli/config/index.d.ts +0 -1
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/core/index.d.ts +205 -442
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +259 -284
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +3 -9
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/devtools/index.js +3 -3
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/i18n/index.d.ts +8 -34
- package/dist/cli/i18n/index.d.ts.map +1 -1
- package/dist/cli/i18n/index.js +6 -6
- package/dist/cli/i18n/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +89 -82
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +27 -33
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/platform-lib/index.d.ts +351 -404
- package/dist/cli/platform-lib/index.d.ts.map +1 -1
- package/dist/cli/platform-lib/index.js +218 -239
- package/dist/cli/platform-lib/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +17 -41
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js +10 -13
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/command/index.d.ts +83 -195
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +160 -563
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +845 -859
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +787 -527
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +845 -859
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +845 -859
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +845 -859
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/crypto/index.browser.js +1 -1
- package/dist/crypto/index.browser.js.map +1 -1
- package/dist/crypto/index.d.ts +3 -4
- package/dist/crypto/index.d.ts.map +1 -1
- package/dist/crypto/index.js +6 -3
- package/dist/crypto/index.js.map +1 -1
- package/dist/datetime/index.d.ts +12 -2
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js +6 -1
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/brevo/index.d.ts +0 -1
- package/dist/email/brevo/index.d.ts.map +1 -1
- package/dist/email/brevo/index.js +4 -4
- package/dist/email/brevo/index.js.map +1 -1
- package/dist/email/cloudflare/index.d.ts +0 -1
- package/dist/email/cloudflare/index.d.ts.map +1 -1
- package/dist/email/cloudflare/index.js +2 -2
- package/dist/email/cloudflare/index.js.map +1 -1
- package/dist/email/core/index.d.ts +15 -7
- package/dist/email/core/index.d.ts.map +1 -1
- package/dist/email/core/index.js +14 -5
- package/dist/email/core/index.js.map +1 -1
- package/dist/email/core/index.workerd.js +5 -1
- package/dist/email/core/index.workerd.js.map +1 -1
- package/dist/email/smtp/index.d.ts +8 -9
- package/dist/email/smtp/index.d.ts.map +1 -1
- package/dist/email/smtp/index.js +702 -479
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/fake/index.d.ts +1580 -1238
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js +11728 -32131
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +13 -9
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js +5 -5
- package/dist/lock/core/index.js.map +1 -1
- package/dist/lock/redis/index.d.ts +0 -1
- 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 +81 -69
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +123 -56
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +27 -90
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +42 -70
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +54 -40
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +140 -106
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +145 -150
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +144 -109
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js +64 -54
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +36 -76
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js +67 -57
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +62 -75
- package/dist/queue/core/index.d.ts.map +1 -1
- package/dist/queue/core/index.js +48 -71
- package/dist/queue/core/index.js.map +1 -1
- package/dist/queue/core/index.workerd.js +48 -71
- package/dist/queue/core/index.workerd.js.map +1 -1
- package/dist/queue/redis/index.d.ts +3 -4
- package/dist/queue/redis/index.d.ts.map +1 -1
- package/dist/queue/redis/index.js +2 -2
- package/dist/queue/redis/index.js.map +1 -1
- package/dist/react/auth/index.browser.js.map +1 -1
- package/dist/react/auth/index.d.ts +7 -4
- package/dist/react/auth/index.d.ts.map +1 -1
- package/dist/react/auth/index.js.map +1 -1
- package/dist/react/core/index.d.ts +87 -5
- package/dist/react/core/index.d.ts.map +1 -1
- package/dist/react/core/index.js +170 -23
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/form/index.d.ts +4 -5
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js +91 -56
- 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 +0 -1
- 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 +4 -5
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/i18n/index.js +2 -2
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.d.ts +1 -4
- package/dist/react/intro/index.d.ts.map +1 -1
- package/dist/react/intro/index.js.map +1 -1
- package/dist/react/router/index.browser.js +60 -24
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +378 -377
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +77 -41
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/sitemap/index.d.ts +0 -1
- package/dist/react/sitemap/index.d.ts.map +1 -1
- package/dist/react/sitemap/index.js.map +1 -1
- package/dist/react/testing/index.d.ts +4 -5
- package/dist/react/testing/index.d.ts.map +1 -1
- package/dist/react/testing/index.js +41 -15
- package/dist/react/testing/index.js.map +1 -1
- package/dist/react/testing/{chunk-BpyX8vjI.js → rolldown-runtime-Bl3dcgcQ.js} +1 -1
- package/dist/react/ui/index.d.ts +35 -34
- package/dist/react/ui/index.d.ts.map +1 -1
- package/dist/react/ui/index.js +12 -17
- package/dist/react/ui/index.js.map +1 -1
- package/dist/redis/index.bun.js +5 -5
- package/dist/redis/index.bun.js.map +1 -1
- package/dist/redis/index.d.ts +6 -7
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js +6 -6
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts +0 -1
- 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 +4 -12
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +9 -4
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/index.workerd.js +9 -4
- package/dist/scheduler/index.workerd.js.map +1 -1
- package/dist/security/index.browser.js +30 -28
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +113 -44
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +452 -37
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.browser.js +19 -19
- package/dist/server/auth/index.browser.js.map +1 -1
- package/dist/server/auth/index.d.ts +198 -274
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +83 -621
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cookies/index.browser.js +103 -5
- package/dist/server/cookies/index.browser.js.map +1 -1
- package/dist/server/cookies/index.d.ts +72 -5
- package/dist/server/cookies/index.d.ts.map +1 -1
- package/dist/server/cookies/index.js +103 -9
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js +22 -27
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +116 -85
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +105 -118
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +9 -10
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/cors/index.js +14 -26
- package/dist/server/cors/index.js.map +1 -1
- package/dist/server/etag/index.d.ts +2 -3
- 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 -19
- package/dist/server/health/index.d.ts.map +1 -1
- package/dist/server/health/index.js +6 -6
- package/dist/server/health/index.js.map +1 -1
- package/dist/server/links/index.browser.js +15 -18
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +66 -67
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +29 -32
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +0 -1
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js +10 -3
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/proxy/index.d.ts +0 -1
- 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 +8 -9
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.js +6 -12
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.d.ts +0 -1
- 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 +4 -4
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +37 -25
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +0 -1
- 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 +0 -1
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js +2 -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 +19 -20
- package/dist/topic/core/index.d.ts.map +1 -1
- package/dist/topic/core/index.js +8 -8
- package/dist/topic/core/index.js.map +1 -1
- package/dist/topic/redis/index.d.ts +3 -4
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/dist/topic/redis/index.js +2 -2
- package/dist/topic/redis/index.js.map +1 -1
- package/package.json +22 -35
- package/src/api/audits/__tests__/AuditJobs.spec.ts +83 -0
- package/src/api/audits/__tests__/AuditService.spec.ts +139 -1
- package/src/api/audits/controllers/AdminAuditController.ts +41 -41
- package/src/api/audits/entities/audits.ts +22 -22
- package/src/api/audits/index.ts +6 -1
- package/src/api/audits/jobs/AuditJobs.ts +29 -0
- package/src/api/audits/parameters/AuditParameters.ts +51 -0
- package/src/api/audits/primitives/$audit.ts +24 -0
- package/src/api/audits/schemas/auditQuerySchema.ts +13 -13
- package/src/api/audits/schemas/createAuditSchema.ts +18 -18
- package/src/api/audits/services/AuditService.ts +79 -7
- package/src/api/files/__tests__/FileController.spec.ts +5 -5
- package/src/api/files/__tests__/FileJobs.spec.ts +6 -0
- package/src/api/files/controllers/FileController.ts +25 -25
- package/src/api/files/entities/files.ts +15 -15
- package/src/api/files/schemas/fileCreatorSummarySchema.ts +7 -7
- package/src/api/files/schemas/fileQuerySchema.ts +9 -9
- package/src/api/files/schemas/fileResourceSchema.ts +7 -9
- package/src/api/files/schemas/storageStatsSchema.ts +13 -13
- package/src/api/jobs/__tests__/$job-hardening.spec.ts +289 -0
- package/src/api/jobs/__tests__/$job.spec.ts +18 -18
- package/src/api/jobs/controllers/AdminJobController.ts +8 -8
- package/src/api/jobs/entities/jobExecutionEntity.ts +29 -19
- package/src/api/jobs/providers/JobProvider.ts +128 -12
- package/src/api/jobs/providers/JobQueueProvider.ts +2 -2
- package/src/api/jobs/schemas/jobConfigAtom.ts +22 -22
- package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +6 -6
- package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +11 -15
- package/src/api/jobs/schemas/jobRegistrationSchema.ts +20 -19
- package/src/api/jobs/schemas/triggerJobSchema.ts +3 -3
- package/src/api/jobs/services/JobService.ts +6 -6
- package/src/api/keys/__tests__/ApiKeyService.spec.ts +2 -2
- package/src/api/keys/controllers/AdminApiKeyController.ts +10 -10
- package/src/api/keys/entities/apiKeyEntity.ts +15 -15
- package/src/api/keys/schemas/adminApiKeyQuerySchema.ts +4 -4
- package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +15 -15
- package/src/api/keys/schemas/createApiKeyBodySchema.ts +5 -5
- package/src/api/keys/schemas/createApiKeyResponseSchema.ts +9 -9
- package/src/api/keys/schemas/listApiKeyResponseSchema.ts +13 -13
- package/src/api/keys/schemas/revokeApiKeyParamsSchema.ts +3 -3
- package/src/api/keys/schemas/revokeApiKeyResponseSchema.ts +3 -3
- package/src/api/notifications/__tests__/AlephaApiNotifications.spec.ts +2 -2
- package/src/api/notifications/controllers/AdminNotificationController.ts +38 -14
- package/src/api/notifications/jobs/NotificationJobs.ts +7 -6
- package/src/api/notifications/primitives/$notification.ts +43 -4
- package/src/api/notifications/schemas/notificationContactPreferencesSchema.ts +4 -4
- package/src/api/notifications/schemas/notificationContactSchema.ts +7 -7
- package/src/api/notifications/schemas/notificationDetailResourceSchema.ts +9 -11
- package/src/api/notifications/schemas/notificationPayloadSchema.ts +11 -9
- package/src/api/notifications/schemas/notificationQuerySchema.ts +6 -6
- package/src/api/notifications/schemas/notificationResourceSchema.ts +14 -14
- package/src/api/notifications/services/NotificationSenderService.ts +38 -2
- package/src/api/oauth/__tests__/OAuthClientService.spec.ts +42 -0
- package/src/api/oauth/__tests__/OAuthController.spec.ts +128 -0
- package/src/api/oauth/controllers/OAuthController.ts +132 -12
- package/src/api/oauth/entities/oauthClientEntity.ts +29 -12
- package/src/api/oauth/helpers/oauthMetadata.ts +1 -0
- package/src/api/oauth/helpers/oidcMetadata.ts +19 -0
- package/src/api/oauth/index.ts +1 -0
- package/src/api/oauth/schemas/authorizeDecisionBodySchema.ts +12 -11
- package/src/api/oauth/schemas/authorizeQuerySchema.ts +12 -10
- package/src/api/oauth/schemas/registerClientBodySchema.ts +10 -11
- package/src/api/oauth/schemas/tokenRequestBodySchema.ts +9 -8
- package/src/api/oauth/services/OAuthClientService.ts +228 -15
- package/src/api/organizations/controllers/AdminOrganizationController.ts +8 -8
- package/src/api/organizations/entities/organizations.ts +6 -6
- package/src/api/organizations/schemas/createOrganizationSchema.ts +5 -5
- package/src/api/organizations/schemas/organizationQuerySchema.ts +4 -4
- package/src/api/organizations/schemas/updateOrganizationSchema.ts +1 -2
- package/src/api/parameters/__tests__/$parameter.spec.ts +229 -50
- package/src/api/parameters/controllers/AdminParameterController.ts +9 -12
- package/src/api/parameters/entities/parameters.ts +29 -16
- package/src/api/parameters/primitives/$parameter.ts +3 -3
- package/src/api/parameters/schemas/activateParameterBodySchema.ts +4 -4
- package/src/api/parameters/schemas/createParameterVersionBodySchema.ts +15 -15
- package/src/api/parameters/schemas/parameterCreatorSummarySchema.ts +7 -7
- package/src/api/parameters/schemas/parameterCurrentResponseSchema.ts +7 -7
- package/src/api/parameters/schemas/parameterHistoryResponseSchema.ts +3 -3
- package/src/api/parameters/schemas/parameterNameParamSchema.ts +2 -3
- package/src/api/parameters/schemas/parameterNamesResponseSchema.ts +3 -3
- package/src/api/parameters/schemas/parameterResponseSchema.ts +3 -3
- package/src/api/parameters/schemas/parameterStatusSchema.ts +2 -2
- package/src/api/parameters/schemas/parameterTreeNodeSchema.ts +6 -6
- package/src/api/parameters/schemas/parameterVersionParamSchema.ts +5 -6
- package/src/api/parameters/schemas/parameterVersionResponseSchema.ts +3 -3
- package/src/api/parameters/schemas/rollbackParameterBodySchema.ts +6 -9
- package/src/api/parameters/services/ParameterProvider.ts +152 -65
- package/src/api/payments/controllers/AdminPaymentController.ts +7 -7
- package/src/api/payments/controllers/MockCheckoutController.ts +79 -28
- package/src/api/payments/controllers/PaymentController.ts +4 -4
- package/src/api/payments/entities/paymentIntents.ts +11 -11
- package/src/api/payments/entities/paymentMethods.ts +11 -11
- package/src/api/payments/entities/refunds.ts +9 -9
- package/src/api/payments/providers/PaymentProvider.ts +18 -0
- package/src/api/payments/schemas/intentSchemas.ts +25 -25
- package/src/api/payments/schemas/paymentMethodSchemas.ts +3 -3
- package/src/api/payments/services/PaymentService.ts +60 -3
- package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +14 -0
- package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +11 -11
- package/src/api/subscriptions/controllers/SubscriptionController.ts +5 -4
- package/src/api/subscriptions/entities/subscriptionEvents.ts +15 -15
- package/src/api/subscriptions/entities/subscriptions.ts +22 -22
- package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +22 -22
- package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +4 -4
- package/src/api/subscriptions/schemas/changePlanSchema.ts +5 -5
- package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +14 -7
- package/src/api/subscriptions/schemas/entitlementsSchema.ts +10 -10
- package/src/api/subscriptions/schemas/mrrSchema.ts +9 -9
- package/src/api/subscriptions/schemas/planDefinitionSchema.ts +21 -21
- package/src/api/subscriptions/schemas/planResourceSchema.ts +19 -19
- package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +8 -8
- package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +7 -7
- package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +17 -17
- package/src/api/subscriptions/services/SubscriptionConfig.ts +2 -2
- package/src/api/users/__tests__/ApiKeys-integration.spec.ts +11 -11
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +111 -119
- package/src/api/users/controllers/AdminIdentityController.ts +29 -19
- package/src/api/users/controllers/AdminSessionController.ts +37 -27
- package/src/api/users/controllers/AdminUserController.ts +75 -53
- package/src/api/users/controllers/MySessionController.ts +125 -0
- package/src/api/users/controllers/RealmController.ts +9 -10
- package/src/api/users/controllers/UserController.ts +55 -55
- package/src/api/users/entities/identities.ts +8 -8
- package/src/api/users/entities/sessions.ts +17 -17
- package/src/api/users/entities/users.ts +17 -17
- package/src/api/users/index.ts +3 -0
- package/src/api/users/notifications/UserNotifications.ts +76 -24
- package/src/api/users/primitives/$realm.ts +12 -26
- package/src/api/users/providers/RealmProvider.ts +0 -15
- package/src/api/users/schemas/completePasswordResetRequestSchema.ts +8 -12
- package/src/api/users/schemas/completeRegistrationRequestSchema.ts +17 -20
- package/src/api/users/schemas/createUserSchema.ts +2 -2
- package/src/api/users/schemas/identityQuerySchema.ts +4 -4
- package/src/api/users/schemas/identityResourceSchema.ts +3 -2
- package/src/api/users/schemas/loginSchema.ts +4 -4
- package/src/api/users/schemas/passwordResetIntentResponseSchema.ts +6 -8
- package/src/api/users/schemas/realmConfigSchema.ts +10 -24
- package/src/api/users/schemas/registerQuerySchema.ts +6 -6
- package/src/api/users/schemas/registerRequestSchema.ts +21 -43
- package/src/api/users/schemas/registerResponseSchema.ts +2 -2
- package/src/api/users/schemas/registerSchema.ts +13 -31
- package/src/api/users/schemas/registrationIntentResponseSchema.ts +19 -23
- package/src/api/users/schemas/resetPasswordSchema.ts +10 -17
- package/src/api/users/schemas/sessionQuerySchema.ts +3 -3
- package/src/api/users/schemas/sessionResourceSchema.ts +25 -25
- package/src/api/users/schemas/updateUserSchema.ts +3 -4
- package/src/api/users/schemas/userQuerySchema.ts +7 -7
- package/src/api/users/services/RegistrationService.ts +25 -11
- package/src/api/users/services/UserService.ts +12 -6
- package/src/api/verifications/__tests__/CodeVerification.spec.ts +63 -101
- package/src/api/verifications/__tests__/LinkVerification.spec.ts +23 -92
- package/src/api/verifications/controllers/VerificationController.ts +13 -27
- package/src/api/verifications/entities/verifications.ts +13 -14
- package/src/api/verifications/schemas/requestVerificationCodeResponseSchema.ts +17 -14
- package/src/api/verifications/schemas/validateVerificationCodeResponseSchema.ts +9 -10
- package/src/api/verifications/schemas/verificationSettingsSchema.ts +65 -66
- package/src/api/verifications/schemas/verificationTypeEnumSchema.ts +2 -2
- package/src/api/verifications/services/VerificationService.ts +12 -0
- package/src/background/__tests__/BackgroundTaskProvider.spec.ts +36 -0
- package/src/batch/__tests__/$batch.spec.ts +22 -22
- package/src/batch/__tests__/startup-buffering.spec.ts +9 -9
- package/src/bin/index.ts +1 -1
- package/src/bucket/__tests__/TenantScopedBucket.spec.ts +60 -0
- package/src/bucket/providers/LocalFileStorageProvider.ts +14 -6
- package/src/bucket/providers/MemoryFileStorageProvider.ts +18 -7
- package/src/bucket/providers/R2FileStorageProvider.ts +14 -6
- package/src/bucket/providers/S3FileStorageProvider.ts +31 -15
- package/src/cache/core/primitives/$cache.ts +7 -10
- package/src/cache/database/entities/cacheEntries.ts +16 -27
- package/src/cache/database/providers/DatabaseCacheProvider.ts +15 -13
- package/src/cache/redis/providers/RedisCacheProvider.ts +6 -6
- package/src/captcha/providers/TurnstileCaptchaProvider.ts +4 -4
- package/src/cli/core/__tests__/BuildCloudflareTask.spec.ts +178 -0
- package/src/cli/core/__tests__/BuildDockerTask.spec.ts +0 -2
- package/src/cli/core/__tests__/DbCommand.spec.ts +108 -0
- package/src/cli/core/atoms/appEntryOptions.ts +5 -5
- package/src/cli/core/atoms/buildOptions.ts +76 -76
- package/src/cli/core/atoms/changelogOptions.ts +3 -3
- package/src/cli/core/atoms/devOptions.ts +3 -3
- package/src/cli/core/commands/build.ts +36 -39
- package/src/cli/core/commands/db.ts +171 -83
- package/src/cli/core/commands/dev.ts +8 -8
- package/src/cli/core/commands/gen/changelog.ts +12 -14
- package/src/cli/core/commands/gen/env.ts +6 -6
- package/src/cli/core/commands/gen/openapi.ts +6 -6
- package/src/cli/core/commands/init.ts +28 -33
- package/src/cli/core/commands/pack.ts +10 -10
- package/src/cli/core/commands/root.ts +7 -8
- package/src/cli/core/commands/test.ts +17 -20
- package/src/cli/core/tasks/BuildCloudflareTask.ts +103 -230
- package/src/cli/core/templates/apiHelloResponseSchemaTs.ts +4 -4
- package/src/cli/devtools/atoms/devtoolsOptions.ts +6 -6
- package/src/cli/i18n/atoms/i18nOptions.ts +8 -8
- package/src/cli/platform/commands/SecretsCommand.ts +24 -25
- package/src/cli/platform/commands/platform.ts +40 -46
- package/src/cli/platform/index.ts +1 -1
- package/src/cli/platform-lib/__tests__/CloudflareAdapter.spec.ts +0 -4
- package/src/cli/platform-lib/__tests__/PlatformInspector.spec.ts +10 -3
- package/src/cli/platform-lib/__tests__/VercelAdapter.spec.ts +0 -2
- package/src/cli/platform-lib/adapters/CloudflareAdapter.ts +13 -14
- package/src/cli/platform-lib/adapters/VercelAdapter.ts +6 -11
- package/src/cli/platform-lib/atoms/platformOptions.ts +46 -25
- package/src/cli/platform-lib/schemas/cloudflare.ts +87 -87
- package/src/cli/platform-lib/schemas/platform.ts +51 -51
- package/src/cli/platform-lib/schemas/vercel.ts +28 -28
- package/src/cli/platform-lib/services/CloudflareApi.ts +5 -5
- package/src/cli/platform-lib/services/VercelApi.ts +4 -4
- package/src/cli/platform-lib/services/VercelCli.ts +6 -15
- package/src/cli/platform-lib/services/WranglerApi.ts +6 -15
- package/src/cli/vendor/atoms/vendorOptions.ts +9 -9
- package/src/cli/vendor/commands/VendorCommand.ts +11 -12
- package/src/command/__tests__/$command.spec.ts +82 -83
- package/src/command/__tests__/Asker.spec.ts +6 -13
- package/src/command/__tests__/CliProvider.spec.ts +136 -149
- package/src/command/__tests__/Runner.spec.ts +86 -110
- package/src/command/helpers/Asker.ts +23 -58
- package/src/command/helpers/Runner.ts +34 -110
- package/src/command/index.ts +4 -9
- package/src/command/primitives/$command.ts +10 -10
- package/src/command/providers/CliProvider.ts +183 -161
- package/src/core/Alepha.ts +62 -13
- package/src/core/__tests__/$atom.spec.ts +52 -3
- package/src/core/__tests__/$computed.spec.ts +129 -0
- package/src/core/__tests__/$env.spec.ts +13 -13
- package/src/core/__tests__/$inject.spec.ts +4 -4
- package/src/core/__tests__/$module.spec.ts +1 -1
- package/src/core/__tests__/Alepha-graph.spec.ts +1 -1
- package/src/core/__tests__/Alepha-parseEnv.spec.ts +32 -32
- package/src/core/__tests__/Alepha-start.spec.ts +3 -3
- package/src/core/__tests__/CodecManager.spec.ts +113 -94
- package/src/core/__tests__/KeylessJsonSchemaCodec.spec.ts +138 -138
- package/src/core/__tests__/SchemaValidator.spec.ts +43 -44
- package/src/core/__tests__/StateManager.persist.browser.spec.ts +87 -0
- package/src/core/__tests__/StateManager.spec.ts +423 -0
- package/src/core/__tests__/TypeBox-usecases.spec.ts +8 -8
- package/src/core/__tests__/TypeProvider.spec.ts +314 -309
- package/src/core/__tests__/dump.spec.ts +6 -6
- package/src/core/__tests__/pagination.spec.ts +4 -4
- package/src/core/__tests__/scoped-after-start.spec.ts +43 -0
- package/src/core/errors/TypeBoxError.ts +10 -4
- package/src/core/helpers/FileLike.ts +5 -5
- package/src/core/helpers/coerceStrings.ts +70 -0
- package/src/core/helpers/jsonSchemaToZod.ts +112 -0
- package/src/core/index.shared.ts +4 -1
- package/src/core/primitives/$atom.ts +95 -27
- package/src/core/primitives/$computed.ts +148 -0
- package/src/core/primitives/$env.ts +5 -5
- package/src/core/primitives/$state.ts +1 -1
- package/src/core/providers/AlsProvider.ts +26 -0
- package/src/core/providers/CodecManager.ts +1 -2
- package/src/core/providers/JsonSchemaCodec.ts +1 -2
- package/src/core/providers/KeylessJsonSchemaCodec.ts +82 -57
- package/src/core/providers/SchemaValidator.ts +48 -158
- package/src/core/providers/StateManager.ts +383 -9
- package/src/core/providers/TypeProvider.ts +80 -734
- package/src/core/providers/ZodProvider.ts +380 -0
- package/src/core/providers/zodAugment.ts +69 -0
- package/src/core/schemas/pageQuerySchema.ts +19 -21
- package/src/core/schemas/pageSchema.ts +47 -57
- package/src/crypto/__tests__/SecretProvider.spec.ts +31 -0
- package/src/crypto/providers/SecretProvider.ts +16 -10
- package/src/datetime/primitives/$throttle.ts +6 -1
- package/src/email/brevo/providers/BrevoEmailProvider.ts +4 -4
- package/src/email/cloudflare/providers/CloudflareEmailProvider.ts +6 -6
- package/src/email/core/index.ts +7 -2
- package/src/email/core/primitives/$email.ts +5 -1
- package/src/email/core/providers/LocalEmailProvider.ts +5 -5
- package/src/email/smtp/providers/NodemailerEmailProvider.ts +41 -52
- package/src/fake/__tests__/FakeProvider.spec.ts +110 -110
- package/src/fake/__tests__/keyName.example.ts +21 -21
- package/src/fake/__tests__/keyName.spec.ts +41 -41
- package/src/fake/__tests__/module.example.ts +8 -8
- package/src/fake/providers/FakeProvider.ts +122 -121
- package/src/lock/core/__tests__/lock-mutual-exclusion.spec.ts +44 -0
- package/src/lock/core/index.ts +1 -0
- package/src/lock/core/primitives/$lock.ts +13 -6
- package/src/logger/__tests__/CliFormatterProvider.spec.ts +76 -0
- package/src/logger/__tests__/Logger.spec.ts +49 -0
- package/src/logger/index.ts +25 -18
- package/src/logger/providers/CliFormatterProvider.ts +75 -0
- package/src/logger/schemas/logEntrySchema.ts +10 -10
- package/src/logger/services/Logger.ts +18 -0
- package/src/mcp/__tests__/$prompt.spec.ts +21 -23
- package/src/mcp/__tests__/$tool.spec.ts +22 -22
- package/src/mcp/__tests__/McpServerProvider.spec.ts +11 -11
- package/src/mcp/__tests__/StreamableHttpMcpTransport.spec.ts +2 -2
- package/src/mcp/__tests__/integration.spec.ts +33 -33
- package/src/mcp/primitives/$prompt.ts +11 -9
- package/src/mcp/primitives/$tool.ts +27 -86
- package/src/mcp/transports/StreamableHttpMcpTransport.ts +7 -7
- package/src/orm/__tests__/$repository-tests.ts +133 -27
- package/src/orm/__tests__/$repository.spec.ts +23 -0
- package/src/orm/__tests__/$transactional-tests.ts +4 -4
- package/src/orm/__tests__/ModelBuilder-tests.ts +28 -28
- package/src/orm/__tests__/ModelBuilder.spec.ts +124 -106
- package/src/orm/__tests__/QueryManager.spec.ts +5 -5
- package/src/orm/__tests__/delete-returning-tests.ts +11 -11
- package/src/orm/__tests__/deletedAt-tests.ts +3 -3
- package/src/orm/__tests__/enums.spec.ts +29 -25
- package/src/orm/__tests__/execute-tests.ts +8 -8
- package/src/orm/__tests__/fixtures/bigEntitySchema.ts +48 -48
- package/src/orm/__tests__/fixtures/userEntitySchema.ts +6 -6
- package/src/orm/__tests__/joins-tests.ts +30 -30
- package/src/orm/__tests__/organization-tests.ts +105 -3
- package/src/orm/__tests__/organization.spec.ts +46 -0
- package/src/orm/__tests__/orm-errors.spec.ts +10 -10
- package/src/orm/__tests__/orm-next-tests.ts +29 -29
- package/src/orm/__tests__/orm-showcase-tests.ts +9 -9
- package/src/orm/__tests__/primaryKey.spec.ts +4 -4
- package/src/orm/__tests__/query-date-encoding-tests.ts +17 -17
- package/src/orm/__tests__/ref-auto-onDelete.spec.ts +14 -14
- package/src/orm/__tests__/references.spec.ts +7 -7
- package/src/orm/__tests__/security.spec.ts +17 -17
- package/src/orm/__tests__/string-operators-tests.ts +5 -5
- package/src/orm/__tests__/timestamps-tests.ts +14 -14
- package/src/orm/__tests__/validation-tests.ts +5 -5
- package/src/orm/core/__tests__/BunSqliteProvider.bun.spec.ts +8 -8
- package/src/orm/core/constants/PG_SYMBOLS.ts +13 -1
- package/src/orm/core/entities/alephaSequences.ts +8 -8
- package/src/orm/core/helpers/pgAttr.ts +4 -4
- package/src/orm/core/primitives/$entity.ts +10 -6
- package/src/orm/core/providers/DatabaseTypeProvider.ts +46 -53
- package/src/orm/core/providers/drivers/BunSqliteProvider.ts +8 -8
- package/src/orm/core/providers/drivers/CloudflareD1Provider.ts +3 -5
- package/src/orm/core/providers/drivers/NodeSqliteProvider.ts +8 -8
- package/src/orm/core/schemas/databaseEnvSchema.ts +4 -4
- package/src/orm/core/schemas/insertSchema.ts +14 -18
- package/src/orm/core/schemas/legacyIdSchema.ts +2 -2
- package/src/orm/core/schemas/updateSchema.ts +10 -16
- package/src/orm/core/services/PgRelationManager.ts +2 -2
- package/src/orm/core/services/QueryManager.ts +13 -2
- package/src/orm/core/services/Repository.ts +88 -33
- package/src/orm/core/services/SqliteModelBuilder.ts +38 -45
- package/src/orm/postgres/__tests__/BunPostgresProvider.bun.spec.ts +4 -4
- package/src/orm/postgres/providers/CloudflareHyperdriveProvider.ts +4 -6
- package/src/orm/postgres/schemas/postgresEnvSchema.ts +6 -6
- package/src/orm/postgres/services/PostgresModelBuilder.ts +127 -114
- package/src/queue/core/__tests__/$consumer.spec.ts +4 -4
- package/src/queue/core/__tests__/WorkerProvider.spec.ts +4 -4
- package/src/queue/core/__tests__/shared.ts +6 -6
- package/src/queue/core/primitives/$consumer.ts +5 -5
- package/src/queue/core/primitives/$queue.ts +44 -61
- package/src/queue/core/providers/CloudflareQueueProvider.ts +6 -0
- package/src/queue/core/providers/WorkerProvider.ts +17 -14
- package/src/queue/redis/providers/RedisQueueProvider.ts +3 -3
- package/src/react/core/__tests__/ErrorBoundary.browser.spec.tsx +92 -0
- package/src/react/core/__tests__/Router.spec.tsx +4 -4
- package/src/react/core/__tests__/useComputed.browser.spec.tsx +192 -0
- package/src/react/core/__tests__/useQuery.browser.spec.tsx +48 -0
- package/src/react/core/__tests__/useSelector.browser.spec.tsx +105 -0
- package/src/react/core/__tests__/useStore.browser.spec.tsx +113 -0
- package/src/react/core/components/ErrorBoundary.tsx +42 -0
- package/src/react/core/hooks/useAction.ts +56 -19
- package/src/react/core/hooks/useComputed.ts +91 -0
- package/src/react/core/hooks/useSelector.ts +67 -0
- package/src/react/core/hooks/useStore.ts +36 -17
- package/src/react/core/index.ts +3 -0
- package/src/react/core/utils/shallowEqual.ts +36 -0
- package/src/react/form/__tests__/FormModel-submit-loading.spec.ts +2 -2
- package/src/react/form/__tests__/form-submitting-reactive.browser.spec.tsx +5 -5
- package/src/react/form/__tests__/useForm.browser.spec.tsx +54 -54
- package/src/react/form/errors/FormValidationError.ts +0 -2
- package/src/react/form/hooks/useForm.ts +19 -4
- package/src/react/form/hooks/useFormQuerySync.ts +0 -0
- package/src/react/form/services/FormModel.ts +67 -47
- package/src/react/form/services/parseField.ts +66 -27
- package/src/react/i18n/__tests__/Localize.spec.tsx +1 -1
- package/src/react/i18n/providers/I18nProvider.ts +2 -2
- package/src/react/router/__tests__/$page.browser.spec.tsx +19 -19
- package/src/react/router/__tests__/$page.spec.tsx +21 -21
- package/src/react/router/__tests__/ReactBrowserProvider.browser.spec.ts +87 -1
- package/src/react/router/atoms/ssrManifestAtom.ts +16 -16
- package/src/react/router/components/NestedView.tsx +11 -3
- package/src/react/router/hooks/useQueryParams.ts +1 -1
- package/src/react/router/primitives/$page.ts +2 -2
- package/src/react/router/providers/ReactBrowserProvider.ts +64 -13
- package/src/react/router/providers/ReactPageProvider.ts +26 -12
- package/src/react/router/providers/ReactServerProvider.ts +13 -15
- package/src/react/sitemap/__tests__/$sitemap.spec.ts +2 -2
- package/src/react/ui/atoms/uiAtom.ts +6 -6
- package/src/react/ui/atoms/uiThemeListAtom.ts +7 -7
- package/src/react/ui/hooks/useColorMode.ts +11 -4
- package/src/react/ui/services/SchemaControl.ts +4 -4
- package/src/redis/providers/BunRedisProvider.ts +19 -11
- package/src/redis/providers/NodeRedisProvider.ts +3 -3
- package/src/scheduler/primitives/$scheduler.ts +21 -8
- package/src/security/__tests__/assertRealmScope.spec.ts +29 -0
- package/src/security/atoms/currentTenantAtom.ts +6 -6
- package/src/security/atoms/currentUserAtom.ts +2 -2
- package/src/security/primitives/$issuer.ts +24 -2
- package/src/security/providers/JwtProvider.ts +100 -0
- package/src/security/providers/SecurityProvider.ts +43 -2
- package/src/security/schemas/permissionSchema.ts +19 -19
- package/src/security/schemas/roleSchema.ts +22 -28
- package/src/security/schemas/userAccountInfoSchema.ts +47 -37
- package/src/server/auth/index.ts +0 -3
- package/src/server/auth/primitives/$auth.ts +6 -6
- package/src/server/auth/primitives/$authApple.ts +10 -10
- package/src/server/auth/primitives/$authFacebook.ts +10 -10
- package/src/server/auth/primitives/$authFranceConnect.ts +10 -10
- package/src/server/auth/primitives/$authGithub.ts +10 -10
- package/src/server/auth/primitives/$authGoogle.ts +10 -10
- package/src/server/auth/primitives/$authMicrosoft.ts +14 -14
- package/src/server/auth/providers/ServerAuthProvider.ts +49 -48
- package/src/server/auth/schemas/authenticationProviderSchema.ts +9 -12
- package/src/server/auth/schemas/tokenResponseSchema.ts +2 -2
- package/src/server/auth/schemas/tokensSchema.ts +16 -16
- package/src/server/auth/schemas/userinfoResponseSchema.ts +3 -3
- package/src/server/cookies/__tests__/AtomCookieBinding.spec.ts +80 -0
- package/src/server/cookies/__tests__/AtomCookiePersistence.browser.spec.ts +169 -0
- package/src/server/cookies/__tests__/AtomCookiePersistence.spec.ts +294 -0
- package/src/server/cookies/__tests__/ServerCookiesProvider.spec.ts +14 -14
- package/src/server/cookies/index.browser.ts +4 -1
- package/src/server/cookies/index.ts +8 -1
- package/src/server/cookies/primitives/$cookie.browser.ts +1 -1
- package/src/server/cookies/primitives/$cookie.ts +16 -0
- package/src/server/cookies/providers/AtomCookiePersistence.browser.ts +133 -0
- package/src/server/cookies/providers/AtomCookiePersistence.ts +148 -0
- package/src/server/cookies/providers/ServerCookiesProvider.ts +13 -5
- package/src/server/cookies/services/CookieParser.ts +6 -1
- package/src/server/core/__tests__/$action.spec.ts +13 -13
- package/src/server/core/__tests__/$middleware.spec.ts +10 -10
- package/src/server/core/__tests__/$route.spec.ts +9 -9
- package/src/server/core/__tests__/$sse.spec.ts +26 -26
- package/src/server/core/__tests__/BunHttpServerProvider.bun.spec.ts +6 -6
- package/src/server/core/__tests__/HttpClient.spec.ts +2 -2
- package/src/server/core/__tests__/NodeHttpServerProvider.spec.ts +15 -3
- package/src/server/core/__tests__/ServerBodyParserProvider.spec.ts +14 -14
- package/src/server/core/__tests__/ServerMultipartProvider.spec.ts +33 -33
- package/src/server/core/__tests__/ServerRouterProvider-errorHandler.spec.ts +5 -1
- package/src/server/core/__tests__/ServerRouterProvider-getRoutes.spec.ts +25 -25
- package/src/server/core/__tests__/ServerRouterProvider-serializationError.spec.ts +9 -5
- package/src/server/core/__tests__/ServerRouterProvider-validationError.spec.ts +56 -24
- package/src/server/core/errors/ValidationError.ts +6 -3
- package/src/server/core/helpers/isMultipart.ts +2 -1
- package/src/server/core/index.ts +5 -2
- package/src/server/core/interfaces/ServerRequest.ts +6 -14
- package/src/server/core/primitives/$action.ts +26 -36
- package/src/server/core/primitives/$sse.ts +3 -3
- package/src/server/core/providers/BunHttpServerProvider.ts +8 -9
- package/src/server/core/providers/NodeHttpServerProvider.ts +8 -9
- package/src/server/core/providers/ServerBodyParserProvider.ts +20 -12
- package/src/server/core/providers/ServerCompressProvider.ts +9 -10
- package/src/server/core/providers/ServerHelmetProvider.ts +26 -27
- package/src/server/core/providers/ServerMultipartProvider.ts +19 -19
- package/src/server/core/providers/ServerRouterProvider.ts +44 -15
- package/src/server/core/schemas/errorSchema.ts +20 -25
- package/src/server/core/schemas/okSchema.ts +9 -14
- package/src/server/core/services/HttpClient.ts +13 -1
- package/src/server/core/services/ServerRequestParser.ts +6 -6
- package/src/server/cors/providers/ServerCorsProvider.ts +26 -28
- package/src/server/health/schemas/healthSchema.ts +6 -6
- package/src/server/links/__tests__/$remote.spec.ts +3 -3
- package/src/server/links/__tests__/BatchEndpoint.spec.ts +9 -9
- package/src/server/links/__tests__/LinkProvider.spec.ts +9 -9
- package/src/server/links/__tests__/ServerLinksProvider.spec.ts +23 -23
- package/src/server/links/__tests__/fixtures/CrudApp.ts +21 -21
- package/src/server/links/atoms/apiLinksAtom.ts +2 -2
- package/src/server/links/atoms/linkOptionsAtom.ts +6 -6
- package/src/server/links/primitives/$remote.ts +3 -3
- package/src/server/links/providers/LinkProvider.ts +3 -3
- package/src/server/links/providers/ServerLinksProvider.ts +20 -20
- package/src/server/links/schemas/apiLinksResponseSchema.ts +23 -23
- package/src/server/proxy/__tests__/$proxy.spec.ts +9 -9
- package/src/server/proxy/providers/ServerProxyProvider.ts +5 -0
- package/src/server/rate-limit/providers/ServerRateLimitProvider.ts +18 -20
- package/src/server/swagger/__tests__/$swagger.spec.ts +91 -94
- package/src/server/swagger/__tests__/ui.spec.ts +3 -3
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +66 -43
- package/src/system/__tests__/NodeShellProvider.spec.ts +46 -0
- package/src/system/providers/NodeShellProvider.ts +4 -1
- package/src/topic/core/__tests__/$subscriber.spec.ts +4 -4
- package/src/topic/core/__tests__/shared.ts +8 -8
- package/src/topic/core/primitives/$subscriber.ts +5 -5
- package/src/topic/core/primitives/$topic.ts +14 -14
- package/src/topic/redis/providers/RedisTopicProvider.ts +3 -3
- package/dist/container/core/index.d.ts +0 -236
- package/dist/container/core/index.d.ts.map +0 -1
- package/dist/container/core/index.js +0 -231
- package/dist/container/core/index.js.map +0 -1
- package/dist/container/core/index.workerd.js +0 -192
- package/dist/container/core/index.workerd.js.map +0 -1
- package/src/command/helpers/PrettyAsker.ts +0 -256
- package/src/command/helpers/PrettyPrint.ts +0 -332
- package/src/container/core/__tests__/$container.spec.ts +0 -83
- package/src/container/core/index.ts +0 -50
- package/src/container/core/index.workerd.ts +0 -53
- package/src/container/core/interfaces/ContainerOptions.ts +0 -69
- package/src/container/core/primitives/$container.ts +0 -100
- package/src/container/core/providers/CloudflareContainerProvider.ts +0 -70
- package/src/container/core/providers/ContainerProvider.ts +0 -92
- package/src/container/core/providers/MockContainerProvider.ts +0 -62
- package/src/container/core/providers/NodeContainerProvider.ts +0 -53
- package/src/core/__tests__/jsonSchemaToTypeBox.spec.ts +0 -771
- package/src/core/helpers/jsonSchemaToTypeBox.ts +0 -359
- package/src/server/auth/__tests__/authFederationClient.spec.ts +0 -40
- package/src/server/auth/__tests__/federationAssertion.spec.ts +0 -146
- package/src/server/auth/helpers/federationAssertion.ts +0 -74
- package/src/server/auth/primitives/$authFederationBroker.ts +0 -273
- package/src/server/auth/primitives/$authFederationClient.ts +0 -89
- /package/dist/orm/core/{chunk-B4FMCO8f.js → rolldown-runtime-B4FMCO8f.js} +0 -0
|
@@ -71,7 +71,7 @@ var BrowserCryptoProvider = class BrowserCryptoProvider {
|
|
|
71
71
|
}
|
|
72
72
|
randomCode(length) {
|
|
73
73
|
const max = 10 ** length;
|
|
74
|
-
const array = new Uint32Array(1);
|
|
74
|
+
const array = /* @__PURE__ */ new Uint32Array(1);
|
|
75
75
|
const limit = Math.floor(4294967296 / max) * max;
|
|
76
76
|
let value;
|
|
77
77
|
do {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.js","names":[],"sources":["../../src/crypto/providers/BrowserCryptoProvider.ts","../../src/crypto/index.browser.ts"],"sourcesContent":["import { AlephaError } from \"alepha\";\n\nexport class BrowserCryptoProvider {\n protected static readonly AES_ALGORITHM = \"AES-GCM\";\n protected static readonly AES_IV_LENGTH = 12;\n public hashPassword(): never {\n throw new AlephaError(\"hashPassword is not supported in the browser\");\n }\n\n public verifyPassword(): never {\n throw new AlephaError(\"verifyPassword is not supported in the browser\");\n }\n\n public async hash(data: string, algorithm = \"SHA-256\"): Promise<string> {\n const encoded = new TextEncoder().encode(data);\n const digest = await crypto.subtle.digest(algorithm, encoded);\n return this.toHex(digest);\n }\n\n public async hmac(\n data: string,\n secret: string,\n algorithm = \"SHA-256\",\n ): Promise<string> {\n const key = await crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(secret),\n { name: \"HMAC\", hash: algorithm },\n false,\n [\"sign\"],\n );\n const signature = await crypto.subtle.sign(\n \"HMAC\",\n key,\n new TextEncoder().encode(data),\n );\n return this.toHex(signature);\n }\n\n public async verifyHmac(\n data: string,\n signature: string,\n secret: string,\n algorithm = \"SHA-256\",\n ): Promise<boolean> {\n const expected = await this.hmac(data, secret, algorithm);\n return this.equals(expected, signature);\n }\n\n public async encrypt(plaintext: string, key: string): Promise<string> {\n const iv = crypto.getRandomValues(\n new Uint8Array(BrowserCryptoProvider.AES_IV_LENGTH),\n );\n const cryptoKey = await this.deriveAesKey(key);\n const encoded = new TextEncoder().encode(plaintext);\n const encrypted = await crypto.subtle.encrypt(\n {\n name: BrowserCryptoProvider.AES_ALGORITHM,\n iv: iv.buffer as ArrayBuffer,\n },\n cryptoKey,\n encoded,\n );\n // Web Crypto appends the auth tag to the ciphertext\n const encryptedBytes = new Uint8Array(encrypted);\n const ciphertext = encryptedBytes.slice(0, -16);\n const tag = encryptedBytes.slice(-16);\n return `${this.toHex(iv)}:${this.toHex(tag)}:${this.toHex(ciphertext)}`;\n }\n\n public async decrypt(ciphertext: string, key: string): Promise<string> {\n const parts = ciphertext.split(\":\");\n if (parts.length !== 3) {\n throw new AlephaError(\"Invalid ciphertext format\");\n }\n\n const [ivHex, tagHex, encryptedHex] = parts;\n const iv = this.fromHex(ivHex!);\n const tag = this.fromHex(tagHex!);\n const encrypted = this.fromHex(encryptedHex!);\n // Web Crypto expects tag appended to ciphertext\n const combined = new Uint8Array(encrypted.length + tag.length);\n combined.set(encrypted);\n combined.set(tag, encrypted.length);\n const cryptoKey = await this.deriveAesKey(key);\n const decrypted = await crypto.subtle.decrypt(\n {\n name: BrowserCryptoProvider.AES_ALGORITHM,\n iv: iv.buffer as ArrayBuffer,\n },\n cryptoKey,\n combined.buffer as ArrayBuffer,\n );\n return new TextDecoder().decode(decrypted);\n }\n\n public equals(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n let result = 0;\n for (let i = 0; i < a.length; i++) {\n result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return result === 0;\n }\n\n public randomUUID(): string {\n return crypto.randomUUID();\n }\n\n public randomText(length: number): string {\n const bytes = crypto.getRandomValues(new Uint8Array(length));\n return this.toBase64Url(bytes.buffer as ArrayBuffer).slice(0, length);\n }\n\n public randomCode(length: number): string {\n const max = 10 ** length;\n const array = new Uint32Array(1);\n // Rejection sampling to avoid modulo bias\n const limit = Math.floor(0x100000000 / max) * max;\n let value: number;\n do {\n crypto.getRandomValues(array);\n value = array[0]!;\n } while (value >= limit);\n const code = value % max;\n return String(code).padStart(length, \"0\");\n }\n\n protected toHex(buffer: ArrayBuffer | Uint8Array): string {\n const bytes =\n buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n }\n\n protected fromHex(hex: string): Uint8Array {\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n bytes[i / 2] = Number.parseInt(hex.substring(i, i + 2), 16);\n }\n return bytes;\n }\n\n protected toBase64Url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n }\n\n protected async deriveAesKey(key: string): Promise<CryptoKey> {\n const encoded = new TextEncoder().encode(key);\n const hash = await crypto.subtle.digest(\"SHA-256\", encoded);\n return crypto.subtle.importKey(\n \"raw\",\n hash,\n { name: BrowserCryptoProvider.AES_ALGORITHM },\n false,\n [\"encrypt\", \"decrypt\"],\n );\n }\n\n /**\n * Derive an AES-GCM key from a low-entropy user passphrase using\n * PBKDF2-SHA-256. Use this in place of {@link deriveAesKey} whenever the\n * key material is a human-chosen string — a single SHA-256 over a\n * passphrase is brute-forceable by anyone with the ciphertext.\n *\n * OWASP 2023 recommends 600k iterations of PBKDF2-SHA-256 for password\n * storage; we use the same budget for client-side at-rest content keys.\n * Each protected blob carries its own random salt so the same passphrase\n * derives a different key per blob.\n */\n public async deriveKeyFromPassphrase(\n passphrase: string,\n saltHex: string,\n iterations = 600_000,\n ): Promise<CryptoKey> {\n const baseKey = await crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(passphrase),\n { name: \"PBKDF2\" },\n false,\n [\"deriveKey\"],\n );\n return crypto.subtle.deriveKey(\n {\n name: \"PBKDF2\",\n salt: this.fromHex(saltHex).buffer as ArrayBuffer,\n iterations,\n hash: \"SHA-256\",\n },\n baseKey,\n { name: BrowserCryptoProvider.AES_ALGORITHM, length: 256 },\n false,\n [\"encrypt\", \"decrypt\"],\n );\n }\n\n /**\n * Encrypt `plaintext` with a pre-derived passphrase key, returning a\n * self-contained envelope (versioned for forward compatibility):\n *\n * { v: 1, salt, iv, ciphertext, kdf }\n *\n * `salt` + `kdf` are echoed back so {@link decryptWithPassphrase} can\n * reproduce the exact derivation. Each call generates a fresh IV — pass\n * a stable salt across saves of the same protected blob so the\n * passphrase only has to be derived once per session.\n */\n public async encryptWithPassphrase(\n plaintext: string,\n key: CryptoKey,\n saltHex: string,\n iterations = 600_000,\n ): Promise<string> {\n const iv = crypto.getRandomValues(\n new Uint8Array(BrowserCryptoProvider.AES_IV_LENGTH),\n );\n const encrypted = await crypto.subtle.encrypt(\n {\n name: BrowserCryptoProvider.AES_ALGORITHM,\n iv: iv.buffer as ArrayBuffer,\n },\n key,\n new TextEncoder().encode(plaintext),\n );\n return JSON.stringify({\n v: 1,\n salt: saltHex,\n iv: this.toHex(iv),\n ciphertext: this.toHex(new Uint8Array(encrypted)),\n kdf: { name: \"PBKDF2\", iterations, hash: \"SHA-256\" },\n });\n }\n\n /**\n * Reverse of {@link encryptWithPassphrase}. Throws when the passphrase\n * is wrong or the envelope is corrupt — both surface as the same\n * `OperationError` from Web Crypto, which the UI presents as a generic\n * \"wrong passphrase\" without revealing which.\n */\n public async decryptWithPassphrase(\n envelope: string,\n passphrase: string,\n ): Promise<string> {\n let parsed: {\n v?: number;\n salt?: string;\n iv?: string;\n ciphertext?: string;\n kdf?: { iterations?: number };\n };\n try {\n parsed = JSON.parse(envelope);\n } catch {\n throw new AlephaError(\"Invalid protected folio envelope\");\n }\n if (!parsed.salt || !parsed.iv || !parsed.ciphertext) {\n throw new AlephaError(\"Invalid protected folio envelope\");\n }\n const key = await this.deriveKeyFromPassphrase(\n passphrase,\n parsed.salt,\n parsed.kdf?.iterations ?? 600_000,\n );\n const decrypted = await crypto.subtle.decrypt(\n {\n name: BrowserCryptoProvider.AES_ALGORITHM,\n iv: this.fromHex(parsed.iv).buffer as ArrayBuffer,\n },\n key,\n this.fromHex(parsed.ciphertext).buffer as ArrayBuffer,\n );\n return new TextDecoder().decode(decrypted);\n }\n}\n","import { $module } from \"alepha\";\nimport { BrowserCryptoProvider } from \"./providers/BrowserCryptoProvider.ts\";\n\nexport { BrowserCryptoProvider as CryptoProvider } from \"./providers/BrowserCryptoProvider.ts\";\n\n/**\n * Cryptographic utilities: hashing, HMAC, AES-256-GCM encryption, password hashing, and secure random generation.\n *\n * @module alepha.crypto\n */\nexport const AlephaCrypto = $module({\n name: \"alepha.crypto\",\n services: [BrowserCryptoProvider],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.browser.js","names":[],"sources":["../../src/crypto/providers/BrowserCryptoProvider.ts","../../src/crypto/index.browser.ts"],"sourcesContent":["import { AlephaError } from \"alepha\";\n\nexport class BrowserCryptoProvider {\n protected static readonly AES_ALGORITHM = \"AES-GCM\";\n protected static readonly AES_IV_LENGTH = 12;\n public hashPassword(): never {\n throw new AlephaError(\"hashPassword is not supported in the browser\");\n }\n\n public verifyPassword(): never {\n throw new AlephaError(\"verifyPassword is not supported in the browser\");\n }\n\n public async hash(data: string, algorithm = \"SHA-256\"): Promise<string> {\n const encoded = new TextEncoder().encode(data);\n const digest = await crypto.subtle.digest(algorithm, encoded);\n return this.toHex(digest);\n }\n\n public async hmac(\n data: string,\n secret: string,\n algorithm = \"SHA-256\",\n ): Promise<string> {\n const key = await crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(secret),\n { name: \"HMAC\", hash: algorithm },\n false,\n [\"sign\"],\n );\n const signature = await crypto.subtle.sign(\n \"HMAC\",\n key,\n new TextEncoder().encode(data),\n );\n return this.toHex(signature);\n }\n\n public async verifyHmac(\n data: string,\n signature: string,\n secret: string,\n algorithm = \"SHA-256\",\n ): Promise<boolean> {\n const expected = await this.hmac(data, secret, algorithm);\n return this.equals(expected, signature);\n }\n\n public async encrypt(plaintext: string, key: string): Promise<string> {\n const iv = crypto.getRandomValues(\n new Uint8Array(BrowserCryptoProvider.AES_IV_LENGTH),\n );\n const cryptoKey = await this.deriveAesKey(key);\n const encoded = new TextEncoder().encode(plaintext);\n const encrypted = await crypto.subtle.encrypt(\n {\n name: BrowserCryptoProvider.AES_ALGORITHM,\n iv: iv.buffer as ArrayBuffer,\n },\n cryptoKey,\n encoded,\n );\n // Web Crypto appends the auth tag to the ciphertext\n const encryptedBytes = new Uint8Array(encrypted);\n const ciphertext = encryptedBytes.slice(0, -16);\n const tag = encryptedBytes.slice(-16);\n return `${this.toHex(iv)}:${this.toHex(tag)}:${this.toHex(ciphertext)}`;\n }\n\n public async decrypt(ciphertext: string, key: string): Promise<string> {\n const parts = ciphertext.split(\":\");\n if (parts.length !== 3) {\n throw new AlephaError(\"Invalid ciphertext format\");\n }\n\n const [ivHex, tagHex, encryptedHex] = parts;\n const iv = this.fromHex(ivHex!);\n const tag = this.fromHex(tagHex!);\n const encrypted = this.fromHex(encryptedHex!);\n // Web Crypto expects tag appended to ciphertext\n const combined = new Uint8Array(encrypted.length + tag.length);\n combined.set(encrypted);\n combined.set(tag, encrypted.length);\n const cryptoKey = await this.deriveAesKey(key);\n const decrypted = await crypto.subtle.decrypt(\n {\n name: BrowserCryptoProvider.AES_ALGORITHM,\n iv: iv.buffer as ArrayBuffer,\n },\n cryptoKey,\n combined.buffer as ArrayBuffer,\n );\n return new TextDecoder().decode(decrypted);\n }\n\n public equals(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n let result = 0;\n for (let i = 0; i < a.length; i++) {\n result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return result === 0;\n }\n\n public randomUUID(): string {\n return crypto.randomUUID();\n }\n\n public randomText(length: number): string {\n const bytes = crypto.getRandomValues(new Uint8Array(length));\n return this.toBase64Url(bytes.buffer as ArrayBuffer).slice(0, length);\n }\n\n public randomCode(length: number): string {\n const max = 10 ** length;\n const array = new Uint32Array(1);\n // Rejection sampling to avoid modulo bias\n const limit = Math.floor(0x100000000 / max) * max;\n let value: number;\n do {\n crypto.getRandomValues(array);\n value = array[0]!;\n } while (value >= limit);\n const code = value % max;\n return String(code).padStart(length, \"0\");\n }\n\n protected toHex(buffer: ArrayBuffer | Uint8Array): string {\n const bytes =\n buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n }\n\n protected fromHex(hex: string): Uint8Array {\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n bytes[i / 2] = Number.parseInt(hex.substring(i, i + 2), 16);\n }\n return bytes;\n }\n\n protected toBase64Url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n }\n\n protected async deriveAesKey(key: string): Promise<CryptoKey> {\n const encoded = new TextEncoder().encode(key);\n const hash = await crypto.subtle.digest(\"SHA-256\", encoded);\n return crypto.subtle.importKey(\n \"raw\",\n hash,\n { name: BrowserCryptoProvider.AES_ALGORITHM },\n false,\n [\"encrypt\", \"decrypt\"],\n );\n }\n\n /**\n * Derive an AES-GCM key from a low-entropy user passphrase using\n * PBKDF2-SHA-256. Use this in place of {@link deriveAesKey} whenever the\n * key material is a human-chosen string — a single SHA-256 over a\n * passphrase is brute-forceable by anyone with the ciphertext.\n *\n * OWASP 2023 recommends 600k iterations of PBKDF2-SHA-256 for password\n * storage; we use the same budget for client-side at-rest content keys.\n * Each protected blob carries its own random salt so the same passphrase\n * derives a different key per blob.\n */\n public async deriveKeyFromPassphrase(\n passphrase: string,\n saltHex: string,\n iterations = 600_000,\n ): Promise<CryptoKey> {\n const baseKey = await crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(passphrase),\n { name: \"PBKDF2\" },\n false,\n [\"deriveKey\"],\n );\n return crypto.subtle.deriveKey(\n {\n name: \"PBKDF2\",\n salt: this.fromHex(saltHex).buffer as ArrayBuffer,\n iterations,\n hash: \"SHA-256\",\n },\n baseKey,\n { name: BrowserCryptoProvider.AES_ALGORITHM, length: 256 },\n false,\n [\"encrypt\", \"decrypt\"],\n );\n }\n\n /**\n * Encrypt `plaintext` with a pre-derived passphrase key, returning a\n * self-contained envelope (versioned for forward compatibility):\n *\n * { v: 1, salt, iv, ciphertext, kdf }\n *\n * `salt` + `kdf` are echoed back so {@link decryptWithPassphrase} can\n * reproduce the exact derivation. Each call generates a fresh IV — pass\n * a stable salt across saves of the same protected blob so the\n * passphrase only has to be derived once per session.\n */\n public async encryptWithPassphrase(\n plaintext: string,\n key: CryptoKey,\n saltHex: string,\n iterations = 600_000,\n ): Promise<string> {\n const iv = crypto.getRandomValues(\n new Uint8Array(BrowserCryptoProvider.AES_IV_LENGTH),\n );\n const encrypted = await crypto.subtle.encrypt(\n {\n name: BrowserCryptoProvider.AES_ALGORITHM,\n iv: iv.buffer as ArrayBuffer,\n },\n key,\n new TextEncoder().encode(plaintext),\n );\n return JSON.stringify({\n v: 1,\n salt: saltHex,\n iv: this.toHex(iv),\n ciphertext: this.toHex(new Uint8Array(encrypted)),\n kdf: { name: \"PBKDF2\", iterations, hash: \"SHA-256\" },\n });\n }\n\n /**\n * Reverse of {@link encryptWithPassphrase}. Throws when the passphrase\n * is wrong or the envelope is corrupt — both surface as the same\n * `OperationError` from Web Crypto, which the UI presents as a generic\n * \"wrong passphrase\" without revealing which.\n */\n public async decryptWithPassphrase(\n envelope: string,\n passphrase: string,\n ): Promise<string> {\n let parsed: {\n v?: number;\n salt?: string;\n iv?: string;\n ciphertext?: string;\n kdf?: { iterations?: number };\n };\n try {\n parsed = JSON.parse(envelope);\n } catch {\n throw new AlephaError(\"Invalid protected folio envelope\");\n }\n if (!parsed.salt || !parsed.iv || !parsed.ciphertext) {\n throw new AlephaError(\"Invalid protected folio envelope\");\n }\n const key = await this.deriveKeyFromPassphrase(\n passphrase,\n parsed.salt,\n parsed.kdf?.iterations ?? 600_000,\n );\n const decrypted = await crypto.subtle.decrypt(\n {\n name: BrowserCryptoProvider.AES_ALGORITHM,\n iv: this.fromHex(parsed.iv).buffer as ArrayBuffer,\n },\n key,\n this.fromHex(parsed.ciphertext).buffer as ArrayBuffer,\n );\n return new TextDecoder().decode(decrypted);\n }\n}\n","import { $module } from \"alepha\";\nimport { BrowserCryptoProvider } from \"./providers/BrowserCryptoProvider.ts\";\n\nexport { BrowserCryptoProvider as CryptoProvider } from \"./providers/BrowserCryptoProvider.ts\";\n\n/**\n * Cryptographic utilities: hashing, HMAC, AES-256-GCM encryption, password hashing, and secure random generation.\n *\n * @module alepha.crypto\n */\nexport const AlephaCrypto = $module({\n name: \"alepha.crypto\",\n services: [BrowserCryptoProvider],\n});\n"],"mappings":"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"}
|
package/dist/crypto/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Alepha } from "alepha";
|
|
2
2
|
import { ScryptOptions } from "node:crypto";
|
|
3
|
-
|
|
4
3
|
//#region ../../src/crypto/providers/CryptoProvider.d.ts
|
|
5
4
|
declare class CryptoProvider {
|
|
6
5
|
protected static readonly SCRYPT_OPTIONS: ScryptOptions;
|
|
@@ -37,9 +36,9 @@ declare class CryptoProvider {
|
|
|
37
36
|
//#endregion
|
|
38
37
|
//#region ../../src/crypto/providers/SecretProvider.d.ts
|
|
39
38
|
declare const DEFAULT_SECRET_KEY_VALUE = "change-me-in-production";
|
|
40
|
-
declare const alephaSecretEnvSchema: import("
|
|
41
|
-
APP_SECRET: import("
|
|
42
|
-
}>;
|
|
39
|
+
declare const alephaSecretEnvSchema: import("zod").ZodObject<{
|
|
40
|
+
APP_SECRET: import("zod").ZodString;
|
|
41
|
+
}, import("zod/v4/core").$strip>;
|
|
43
42
|
declare class SecretProvider {
|
|
44
43
|
protected readonly log: import("alepha/logger").Logger;
|
|
45
44
|
protected readonly alepha: Alepha;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/crypto/providers/CryptoProvider.ts","../../src/crypto/providers/SecretProvider.ts","../../src/crypto/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/crypto/providers/CryptoProvider.ts","../../src/crypto/providers/SecretProvider.ts","../../src/crypto/index.ts"],"mappings":";;;cAca;4BACe,gBAAgB;4BAKhB;4BACA;4BACA;4BACA;4BACA;4BACA;EAEb,aAAa,mBAAmB;EAWhC,eACX,kBACA,iBACC;EAuCI,KAAK,cAAc;EAInB,KAAK,cAAc,gBAAgB;EAInC,WACL,cACA,mBACA,gBACA;EAMK,QAAQ,mBAAmB;EAc3B,QAAQ,oBAAoB;EAoB5B,OAAO,WAAW;EAWlB;EAIA,WAAW;EAIX,WAAW;YAMR,YACR,kBACA,cACA,gBACA,SAAS,gBACR,QAAQ;YASD,aAAa,cAAc;;;;;;;;EAcxB,wBACX,oBACA,iBACA,sBACC,QAAQ;EAwBE,sBACX,mBACA,KAAK,WACL,iBACA,sBACC;EAkBU,sBACX,kBACA,qBACC;;;;cC5OQ;cAEA,qCAAqB;;;cAQrB;qBACQ,6BAAG;qBACH,QAAM;qBACN;;;MAER;qBAIQ,4BAAS;;;;;;;;;cCVjB,+BAAY,yBAAA"}
|
package/dist/crypto/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $env, $hook, $inject, $module, Alepha, AlephaError,
|
|
1
|
+
import { $env, $hook, $inject, $module, Alepha, AlephaError, z } from "alepha";
|
|
2
2
|
import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, randomInt, randomUUID, scrypt, timingSafeEqual } from "node:crypto";
|
|
3
3
|
import { $logger } from "alepha/logger";
|
|
4
4
|
//#region ../../src/crypto/providers/CryptoProvider.ts
|
|
@@ -155,7 +155,7 @@ const hexToBytes = (hex) => {
|
|
|
155
155
|
//#endregion
|
|
156
156
|
//#region ../../src/crypto/providers/SecretProvider.ts
|
|
157
157
|
const DEFAULT_SECRET_KEY_VALUE = "change-me-in-production";
|
|
158
|
-
const alephaSecretEnvSchema =
|
|
158
|
+
const alephaSecretEnvSchema = z.object({ APP_SECRET: z.text({
|
|
159
159
|
default: DEFAULT_SECRET_KEY_VALUE,
|
|
160
160
|
description: "The secret key used for signing JWTs, encrypting cookies, and other security features."
|
|
161
161
|
}) });
|
|
@@ -169,7 +169,10 @@ var SecretProvider = class {
|
|
|
169
169
|
configure = $hook({
|
|
170
170
|
on: "configure",
|
|
171
171
|
handler: async () => {
|
|
172
|
-
if (this.secretKey === "change-me-in-production"
|
|
172
|
+
if (this.secretKey === "change-me-in-production") {
|
|
173
|
+
if (this.alepha.isProduction()) throw new AlephaError("APP_SECRET is unset in production (using the built-in default). Set a strong, unique APP_SECRET environment variable — the default is public and lets anyone forge authentication tokens.");
|
|
174
|
+
this.log.warn("Using the default APP_SECRET. This is fine for local development but MUST be set to a strong, unique value before deploying to production.");
|
|
175
|
+
}
|
|
173
176
|
}
|
|
174
177
|
});
|
|
175
178
|
};
|
package/dist/crypto/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/crypto/providers/CryptoProvider.ts","../../src/crypto/providers/SecretProvider.ts","../../src/crypto/index.ts"],"sourcesContent":["import type { ScryptOptions } from \"node:crypto\";\nimport {\n createCipheriv,\n createDecipheriv,\n createHash,\n createHmac,\n randomBytes,\n randomInt,\n randomUUID,\n scrypt,\n timingSafeEqual,\n} from \"node:crypto\";\nimport { AlephaError } from \"alepha\";\n\nexport class CryptoProvider {\n protected static readonly SCRYPT_OPTIONS: ScryptOptions = {\n N: 16384,\n r: 8,\n p: 1,\n };\n protected static readonly SCRYPT_KEY_LENGTH = 64;\n protected static readonly SALT_LENGTH = 16;\n protected static readonly AES_ALGORITHM = \"aes-256-gcm\";\n protected static readonly AES_IV_LENGTH = 12;\n protected static readonly AES_TAG_LENGTH = 16;\n protected static readonly AES_KEY_LENGTH = 32;\n\n public async hashPassword(password: string): Promise<string> {\n const salt = randomBytes(CryptoProvider.SALT_LENGTH).toString(\"hex\");\n const derivedKey = (await this.scryptAsync(\n password,\n salt,\n CryptoProvider.SCRYPT_KEY_LENGTH,\n CryptoProvider.SCRYPT_OPTIONS,\n )) as Buffer;\n return `${salt}:${derivedKey.toString(\"hex\")}`;\n }\n\n public async verifyPassword(\n password: string,\n stored: string,\n ): Promise<boolean> {\n if (!stored || typeof stored !== \"string\") {\n return false;\n }\n\n const parts = stored.split(\":\");\n if (parts.length !== 2) {\n return false;\n }\n\n const [salt, originalHex] = parts;\n\n if (!salt || !originalHex) {\n return false;\n }\n\n if (originalHex.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(originalHex)) {\n return false;\n }\n\n try {\n const derivedKey = (await this.scryptAsync(\n password,\n salt,\n CryptoProvider.SCRYPT_KEY_LENGTH,\n CryptoProvider.SCRYPT_OPTIONS,\n )) as Buffer;\n const originalKey = Buffer.from(originalHex, \"hex\");\n\n if (derivedKey.length !== originalKey.length) {\n return false;\n }\n\n return timingSafeEqual(derivedKey, originalKey);\n } catch {\n return false;\n }\n }\n\n public hash(data: string, algorithm = \"sha256\"): string {\n return createHash(algorithm).update(data).digest(\"hex\");\n }\n\n public hmac(data: string, secret: string, algorithm = \"sha256\"): string {\n return createHmac(algorithm, secret).update(data).digest(\"hex\");\n }\n\n public verifyHmac(\n data: string,\n signature: string,\n secret: string,\n algorithm = \"sha256\",\n ): boolean {\n const expected = this.hmac(data, secret, algorithm);\n return this.equals(expected, signature);\n }\n\n public encrypt(plaintext: string, key: string): string {\n const keyBuffer = this.deriveAesKey(key);\n const iv = randomBytes(CryptoProvider.AES_IV_LENGTH);\n const cipher = createCipheriv(CryptoProvider.AES_ALGORITHM, keyBuffer, iv, {\n authTagLength: CryptoProvider.AES_TAG_LENGTH,\n });\n const encrypted = Buffer.concat([\n cipher.update(plaintext, \"utf8\"),\n cipher.final(),\n ]);\n const tag = cipher.getAuthTag();\n return `${iv.toString(\"hex\")}:${tag.toString(\"hex\")}:${encrypted.toString(\"hex\")}`;\n }\n\n public decrypt(ciphertext: string, key: string): string {\n const parts = ciphertext.split(\":\");\n if (parts.length !== 3) {\n throw new AlephaError(\"Invalid ciphertext format\");\n }\n\n const [ivHex, tagHex, encryptedHex] = parts;\n const keyBuffer = this.deriveAesKey(key);\n const decipher = createDecipheriv(\n CryptoProvider.AES_ALGORITHM,\n keyBuffer,\n Buffer.from(ivHex!, \"hex\"),\n { authTagLength: CryptoProvider.AES_TAG_LENGTH },\n );\n decipher.setAuthTag(Buffer.from(tagHex!, \"hex\"));\n return (\n decipher.update(encryptedHex!, \"hex\", \"utf8\") + decipher.final(\"utf8\")\n );\n }\n\n public equals(a: string, b: string): boolean {\n const bufA = Buffer.from(a);\n const bufB = Buffer.from(b);\n if (bufA.length !== bufB.length) {\n // Constant-time compare against self to avoid timing leak on length mismatch\n timingSafeEqual(bufA, bufA);\n return false;\n }\n return timingSafeEqual(bufA, bufB);\n }\n\n public randomUUID(): string {\n return randomUUID();\n }\n\n public randomText(length: number): string {\n return randomBytes(length).toString(\"base64url\").slice(0, length);\n }\n\n public randomCode(length: number): string {\n const max = 10 ** length;\n const code = randomInt(max);\n return String(code).padStart(length, \"0\");\n }\n\n protected scryptAsync(\n password: string,\n salt: string,\n keylen: number,\n options: ScryptOptions,\n ): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n scrypt(password, salt, keylen, options, (err, derivedKey) => {\n if (err) reject(err);\n else resolve(derivedKey);\n });\n });\n }\n\n protected deriveAesKey(key: string): Buffer {\n return createHash(\"sha256\")\n .update(key)\n .digest()\n .subarray(0, CryptoProvider.AES_KEY_LENGTH);\n }\n\n /**\n * Web Crypto API parity with `BrowserCryptoProvider`. Node 18+ exposes\n * `globalThis.crypto.subtle`, so the implementations are the same on\n * both runtimes. Server-side use is unusual — passphrase-derived keys\n * are a browser-only flow in Alepha — but these stubs exist so the\n * type surface matches and shared call sites compile.\n */\n public async deriveKeyFromPassphrase(\n passphrase: string,\n saltHex: string,\n iterations = 600_000,\n ): Promise<CryptoKey> {\n const subtle = (globalThis as { crypto: { subtle: SubtleCrypto } }).crypto\n .subtle;\n const baseKey = await subtle.importKey(\n \"raw\",\n new TextEncoder().encode(passphrase),\n { name: \"PBKDF2\" },\n false,\n [\"deriveKey\"],\n );\n return subtle.deriveKey(\n {\n name: \"PBKDF2\",\n salt: hexToBytes(saltHex).buffer as ArrayBuffer,\n iterations,\n hash: \"SHA-256\",\n },\n baseKey,\n { name: \"AES-GCM\", length: 256 },\n false,\n [\"encrypt\", \"decrypt\"],\n );\n }\n\n public async encryptWithPassphrase(\n plaintext: string,\n key: CryptoKey,\n saltHex: string,\n iterations = 600_000,\n ): Promise<string> {\n const subtle = (globalThis as { crypto: { subtle: SubtleCrypto } }).crypto\n .subtle;\n const iv = randomBytes(CryptoProvider.AES_IV_LENGTH);\n const encrypted = await subtle.encrypt(\n { name: \"AES-GCM\", iv: iv.buffer as ArrayBuffer },\n key,\n new TextEncoder().encode(plaintext),\n );\n return JSON.stringify({\n v: 1,\n salt: saltHex,\n iv: iv.toString(\"hex\"),\n ciphertext: Buffer.from(encrypted).toString(\"hex\"),\n kdf: { name: \"PBKDF2\", iterations, hash: \"SHA-256\" },\n });\n }\n\n public async decryptWithPassphrase(\n envelope: string,\n passphrase: string,\n ): Promise<string> {\n const subtle = (globalThis as { crypto: { subtle: SubtleCrypto } }).crypto\n .subtle;\n let parsed: {\n salt?: string;\n iv?: string;\n ciphertext?: string;\n kdf?: { iterations?: number };\n };\n try {\n parsed = JSON.parse(envelope);\n } catch {\n throw new AlephaError(\"Invalid protected envelope\");\n }\n if (!parsed.salt || !parsed.iv || !parsed.ciphertext) {\n throw new AlephaError(\"Invalid protected envelope\");\n }\n const key = await this.deriveKeyFromPassphrase(\n passphrase,\n parsed.salt,\n parsed.kdf?.iterations ?? 600_000,\n );\n const decrypted = await subtle.decrypt(\n { name: \"AES-GCM\", iv: hexToBytes(parsed.iv).buffer as ArrayBuffer },\n key,\n hexToBytes(parsed.ciphertext).buffer as ArrayBuffer,\n );\n return new TextDecoder().decode(decrypted);\n }\n}\n\nconst hexToBytes = (hex: string): Uint8Array => {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n out[i / 2] = Number.parseInt(hex.substring(i, i + 2), 16);\n }\n return out;\n};\n","import { $env, $hook, $inject, Alepha, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\n\nexport const DEFAULT_SECRET_KEY_VALUE = \"change-me-in-production\";\n\nexport const alephaSecretEnvSchema = t.object({\n APP_SECRET: t.text({\n default: DEFAULT_SECRET_KEY_VALUE,\n description:\n \"The secret key used for signing JWTs, encrypting cookies, and other security features.\",\n }),\n});\n\nexport class SecretProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly env = $env(alephaSecretEnvSchema);\n\n public get secretKey(): string {\n return this.env.APP_SECRET;\n }\n\n protected readonly configure = $hook({\n on: \"configure\",\n handler: async () => {\n // It's not ideal from a security pov but it's the best for convenience,\n // it can be changed to a hard error in a future release.\n if (\n this.secretKey === DEFAULT_SECRET_KEY_VALUE &&\n this.alepha.isProduction()\n ) {\n this.log.warn(\n \"Using default secret key. Please set a secure APP_SECRET environment variable.\",\n );\n }\n },\n });\n}\n","import { $module } from \"alepha\";\nimport { CryptoProvider } from \"./providers/CryptoProvider.ts\";\nimport { SecretProvider } from \"./providers/SecretProvider.ts\";\n\nexport * from \"./providers/CryptoProvider.ts\";\nexport * from \"./providers/SecretProvider.ts\";\n\n/**\n * Cryptographic utilities: hashing, HMAC, AES-256-GCM encryption, password hashing, and secure random generation.\n *\n * @module alepha.crypto\n */\nexport const AlephaCrypto = $module({\n name: \"alepha.crypto\",\n services: [CryptoProvider, SecretProvider],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/crypto/providers/CryptoProvider.ts","../../src/crypto/providers/SecretProvider.ts","../../src/crypto/index.ts"],"sourcesContent":["import type { ScryptOptions } from \"node:crypto\";\nimport {\n createCipheriv,\n createDecipheriv,\n createHash,\n createHmac,\n randomBytes,\n randomInt,\n randomUUID,\n scrypt,\n timingSafeEqual,\n} from \"node:crypto\";\nimport { AlephaError } from \"alepha\";\n\nexport class CryptoProvider {\n protected static readonly SCRYPT_OPTIONS: ScryptOptions = {\n N: 16384,\n r: 8,\n p: 1,\n };\n protected static readonly SCRYPT_KEY_LENGTH = 64;\n protected static readonly SALT_LENGTH = 16;\n protected static readonly AES_ALGORITHM = \"aes-256-gcm\";\n protected static readonly AES_IV_LENGTH = 12;\n protected static readonly AES_TAG_LENGTH = 16;\n protected static readonly AES_KEY_LENGTH = 32;\n\n public async hashPassword(password: string): Promise<string> {\n const salt = randomBytes(CryptoProvider.SALT_LENGTH).toString(\"hex\");\n const derivedKey = (await this.scryptAsync(\n password,\n salt,\n CryptoProvider.SCRYPT_KEY_LENGTH,\n CryptoProvider.SCRYPT_OPTIONS,\n )) as Buffer;\n return `${salt}:${derivedKey.toString(\"hex\")}`;\n }\n\n public async verifyPassword(\n password: string,\n stored: string,\n ): Promise<boolean> {\n if (!stored || typeof stored !== \"string\") {\n return false;\n }\n\n const parts = stored.split(\":\");\n if (parts.length !== 2) {\n return false;\n }\n\n const [salt, originalHex] = parts;\n\n if (!salt || !originalHex) {\n return false;\n }\n\n if (originalHex.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(originalHex)) {\n return false;\n }\n\n try {\n const derivedKey = (await this.scryptAsync(\n password,\n salt,\n CryptoProvider.SCRYPT_KEY_LENGTH,\n CryptoProvider.SCRYPT_OPTIONS,\n )) as Buffer;\n const originalKey = Buffer.from(originalHex, \"hex\");\n\n if (derivedKey.length !== originalKey.length) {\n return false;\n }\n\n return timingSafeEqual(derivedKey, originalKey);\n } catch {\n return false;\n }\n }\n\n public hash(data: string, algorithm = \"sha256\"): string {\n return createHash(algorithm).update(data).digest(\"hex\");\n }\n\n public hmac(data: string, secret: string, algorithm = \"sha256\"): string {\n return createHmac(algorithm, secret).update(data).digest(\"hex\");\n }\n\n public verifyHmac(\n data: string,\n signature: string,\n secret: string,\n algorithm = \"sha256\",\n ): boolean {\n const expected = this.hmac(data, secret, algorithm);\n return this.equals(expected, signature);\n }\n\n public encrypt(plaintext: string, key: string): string {\n const keyBuffer = this.deriveAesKey(key);\n const iv = randomBytes(CryptoProvider.AES_IV_LENGTH);\n const cipher = createCipheriv(CryptoProvider.AES_ALGORITHM, keyBuffer, iv, {\n authTagLength: CryptoProvider.AES_TAG_LENGTH,\n });\n const encrypted = Buffer.concat([\n cipher.update(plaintext, \"utf8\"),\n cipher.final(),\n ]);\n const tag = cipher.getAuthTag();\n return `${iv.toString(\"hex\")}:${tag.toString(\"hex\")}:${encrypted.toString(\"hex\")}`;\n }\n\n public decrypt(ciphertext: string, key: string): string {\n const parts = ciphertext.split(\":\");\n if (parts.length !== 3) {\n throw new AlephaError(\"Invalid ciphertext format\");\n }\n\n const [ivHex, tagHex, encryptedHex] = parts;\n const keyBuffer = this.deriveAesKey(key);\n const decipher = createDecipheriv(\n CryptoProvider.AES_ALGORITHM,\n keyBuffer,\n Buffer.from(ivHex!, \"hex\"),\n { authTagLength: CryptoProvider.AES_TAG_LENGTH },\n );\n decipher.setAuthTag(Buffer.from(tagHex!, \"hex\"));\n return (\n decipher.update(encryptedHex!, \"hex\", \"utf8\") + decipher.final(\"utf8\")\n );\n }\n\n public equals(a: string, b: string): boolean {\n const bufA = Buffer.from(a);\n const bufB = Buffer.from(b);\n if (bufA.length !== bufB.length) {\n // Constant-time compare against self to avoid timing leak on length mismatch\n timingSafeEqual(bufA, bufA);\n return false;\n }\n return timingSafeEqual(bufA, bufB);\n }\n\n public randomUUID(): string {\n return randomUUID();\n }\n\n public randomText(length: number): string {\n return randomBytes(length).toString(\"base64url\").slice(0, length);\n }\n\n public randomCode(length: number): string {\n const max = 10 ** length;\n const code = randomInt(max);\n return String(code).padStart(length, \"0\");\n }\n\n protected scryptAsync(\n password: string,\n salt: string,\n keylen: number,\n options: ScryptOptions,\n ): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n scrypt(password, salt, keylen, options, (err, derivedKey) => {\n if (err) reject(err);\n else resolve(derivedKey);\n });\n });\n }\n\n protected deriveAesKey(key: string): Buffer {\n return createHash(\"sha256\")\n .update(key)\n .digest()\n .subarray(0, CryptoProvider.AES_KEY_LENGTH);\n }\n\n /**\n * Web Crypto API parity with `BrowserCryptoProvider`. Node 18+ exposes\n * `globalThis.crypto.subtle`, so the implementations are the same on\n * both runtimes. Server-side use is unusual — passphrase-derived keys\n * are a browser-only flow in Alepha — but these stubs exist so the\n * type surface matches and shared call sites compile.\n */\n public async deriveKeyFromPassphrase(\n passphrase: string,\n saltHex: string,\n iterations = 600_000,\n ): Promise<CryptoKey> {\n const subtle = (globalThis as { crypto: { subtle: SubtleCrypto } }).crypto\n .subtle;\n const baseKey = await subtle.importKey(\n \"raw\",\n new TextEncoder().encode(passphrase),\n { name: \"PBKDF2\" },\n false,\n [\"deriveKey\"],\n );\n return subtle.deriveKey(\n {\n name: \"PBKDF2\",\n salt: hexToBytes(saltHex).buffer as ArrayBuffer,\n iterations,\n hash: \"SHA-256\",\n },\n baseKey,\n { name: \"AES-GCM\", length: 256 },\n false,\n [\"encrypt\", \"decrypt\"],\n );\n }\n\n public async encryptWithPassphrase(\n plaintext: string,\n key: CryptoKey,\n saltHex: string,\n iterations = 600_000,\n ): Promise<string> {\n const subtle = (globalThis as { crypto: { subtle: SubtleCrypto } }).crypto\n .subtle;\n const iv = randomBytes(CryptoProvider.AES_IV_LENGTH);\n const encrypted = await subtle.encrypt(\n { name: \"AES-GCM\", iv: iv.buffer as ArrayBuffer },\n key,\n new TextEncoder().encode(plaintext),\n );\n return JSON.stringify({\n v: 1,\n salt: saltHex,\n iv: iv.toString(\"hex\"),\n ciphertext: Buffer.from(encrypted).toString(\"hex\"),\n kdf: { name: \"PBKDF2\", iterations, hash: \"SHA-256\" },\n });\n }\n\n public async decryptWithPassphrase(\n envelope: string,\n passphrase: string,\n ): Promise<string> {\n const subtle = (globalThis as { crypto: { subtle: SubtleCrypto } }).crypto\n .subtle;\n let parsed: {\n salt?: string;\n iv?: string;\n ciphertext?: string;\n kdf?: { iterations?: number };\n };\n try {\n parsed = JSON.parse(envelope);\n } catch {\n throw new AlephaError(\"Invalid protected envelope\");\n }\n if (!parsed.salt || !parsed.iv || !parsed.ciphertext) {\n throw new AlephaError(\"Invalid protected envelope\");\n }\n const key = await this.deriveKeyFromPassphrase(\n passphrase,\n parsed.salt,\n parsed.kdf?.iterations ?? 600_000,\n );\n const decrypted = await subtle.decrypt(\n { name: \"AES-GCM\", iv: hexToBytes(parsed.iv).buffer as ArrayBuffer },\n key,\n hexToBytes(parsed.ciphertext).buffer as ArrayBuffer,\n );\n return new TextDecoder().decode(decrypted);\n }\n}\n\nconst hexToBytes = (hex: string): Uint8Array => {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n out[i / 2] = Number.parseInt(hex.substring(i, i + 2), 16);\n }\n return out;\n};\n","import { $env, $hook, $inject, Alepha, AlephaError, z } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\n\nexport const DEFAULT_SECRET_KEY_VALUE = \"change-me-in-production\";\n\nexport const alephaSecretEnvSchema = z.object({\n APP_SECRET: z.text({\n default: DEFAULT_SECRET_KEY_VALUE,\n description:\n \"The secret key used for signing JWTs, encrypting cookies, and other security features.\",\n }),\n});\n\nexport class SecretProvider {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly env = $env(alephaSecretEnvSchema);\n\n public get secretKey(): string {\n return this.env.APP_SECRET;\n }\n\n protected readonly configure = $hook({\n on: \"configure\",\n handler: async () => {\n if (this.secretKey === DEFAULT_SECRET_KEY_VALUE) {\n // In production the default secret is a full token-forgery bypass:\n // JWTs would be signed with a public, well-known constant. Fail closed.\n if (this.alepha.isProduction()) {\n throw new AlephaError(\n \"APP_SECRET is unset in production (using the built-in default). \" +\n \"Set a strong, unique APP_SECRET environment variable — the default \" +\n \"is public and lets anyone forge authentication tokens.\",\n );\n }\n // Outside production, keep the convenience default but make the risk loud.\n this.log.warn(\n \"Using the default APP_SECRET. This is fine for local development but \" +\n \"MUST be set to a strong, unique value before deploying to production.\",\n );\n }\n },\n });\n}\n","import { $module } from \"alepha\";\nimport { CryptoProvider } from \"./providers/CryptoProvider.ts\";\nimport { SecretProvider } from \"./providers/SecretProvider.ts\";\n\nexport * from \"./providers/CryptoProvider.ts\";\nexport * from \"./providers/SecretProvider.ts\";\n\n/**\n * Cryptographic utilities: hashing, HMAC, AES-256-GCM encryption, password hashing, and secure random generation.\n *\n * @module alepha.crypto\n */\nexport const AlephaCrypto = $module({\n name: \"alepha.crypto\",\n services: [CryptoProvider, SecretProvider],\n});\n"],"mappings":"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"}
|
package/dist/datetime/index.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { Alepha, KIND, Middleware, Primitive } from "alepha";
|
|
2
|
+
import "dayjs/plugin/relativeTime.js";
|
|
2
3
|
import dayjsDuration, { DurationUnitType } from "dayjs/plugin/duration.js";
|
|
4
|
+
import "dayjs/plugin/utc.js";
|
|
5
|
+
import "dayjs/plugin/timezone.js";
|
|
6
|
+
import "dayjs/plugin/localizedFormat.js";
|
|
7
|
+
import "dayjs/locale/ar.js";
|
|
8
|
+
import "dayjs/locale/fr.js";
|
|
3
9
|
import { Dayjs, ManipulateType, OpUnitType, PluginFunc, QUnitType } from "dayjs";
|
|
4
|
-
|
|
5
10
|
//#region ../../src/datetime/providers/DateTimeProvider.d.ts
|
|
6
11
|
type DateTimeInput = string | number | Date | DateTime | Dayjs;
|
|
7
12
|
type DurationLike = number | Duration | [number, ManipulateType];
|
|
@@ -242,7 +247,12 @@ interface ThrottleOptions {
|
|
|
242
247
|
* Middleware that rate-controls handler execution using a token bucket.
|
|
243
248
|
*
|
|
244
249
|
* Excess calls are **delayed** until capacity is available — never rejected.
|
|
245
|
-
* Process-local (not distributed)
|
|
250
|
+
* Process-local (not distributed) — it cannot rate-limit across instances.
|
|
251
|
+
*
|
|
252
|
+
* **Limitation**: the token refill is time-based and re-checked only when a
|
|
253
|
+
* waiter wakes, so a burst of *concurrent* calls can wake in the same window
|
|
254
|
+
* and briefly exceed `rate`. Treat it as traffic smoothing, not a hard cap —
|
|
255
|
+
* do not rely on it to enforce a strict quota.
|
|
246
256
|
*
|
|
247
257
|
* **Use case**: protect an external API from your own traffic.
|
|
248
258
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/datetime/providers/DateTimeProvider.ts","../../src/datetime/primitives/$debounce.ts","../../src/datetime/primitives/$interval.ts","../../src/datetime/primitives/$throttle.ts","../../src/datetime/primitives/$timeout.ts","../../src/datetime/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/datetime/providers/DateTimeProvider.ts","../../src/datetime/primitives/$debounce.ts","../../src/datetime/primitives/$interval.ts","../../src/datetime/primitives/$throttle.ts","../../src/datetime/primitives/$timeout.ts","../../src/datetime/index.ts"],"mappings":";;;;;;;;;;KAuBY,kCAAkC,OAAO,WAAW;KAEpD,wBAAwB,oBAAoB;;;;;;;cAQ3C;qBACQ,OAAO;EAE1B,YAAY,OAAO;;;;EAOnB,IAAI,gBAAgB,OAAO,iBAAiB;EAC5C,IAAI,UAAU,WAAW;;;;EAWzB,SAAS,gBAAgB,OAAO,iBAAiB;EACjD,SAAS,UAAU,WAAW;EAQ9B,QAAQ,MAAM,aAAa;EAI3B,MAAM,MAAM,aAAa;EAIzB,QAAQ,OAAO;EAIf,SAAS,OAAO;EAIhB,OAAO,OAAO,eAAe,OAAO;EAIpC,KAAK,OAAO,eAAe,OAAO,YAAY;EAI9C,GAAG,mBAAmB;EAItB,OAAO,eAAe;EAItB,OAAO;EAIP,QAAQ;EAIR;EAIA,UAAU;EAIV;EAIA;EAIA;EAIA;;;;;;;EAUA,WAAW;;;;;cAQA;qBACQ,OAAO,cAAc;EAExC,YAAY,OAAO,cAAc;EAIjC;EAIA;EAIA;EAIA;EAIA;EAIA,GAAG,MAAM;EAIT;;;;EAOA,WAAW,cAAc;;cAKd,aAAU,mBAAqB,SAAS;cAWxC;SACG,SAAS,MAAM;YAQnB,QAAM;YACN,KAAK;qBACI,UAAU;qBACV,WAAW;EAE9B;qBAMmB,0BAAO;qBAgBP,yBAAM;EAelB,UAAU;EAIV,WAAW,iBAAiB,SAAS;;;;EAOrC,IAAI,MAAM,mCAAmC;;;;EAO7C,GAAG,MAAM,mCAAmC;;;;EAU5C,YAAY,OAAM;;;;EAOlB,OAAO;;;;;;EASP;;;;;;EAYA;;;;;;YAYG,kBAAkB;;;;EAWrB,WAAQ,UACH,cAAY,OACf,mBACN;EAgBI,eAAe,iBAAiB,SAAS;;;;;EAkBnC,QAAQ;;;;;;;EAUd,KACL,UAAU,cACV;IACE,SAAS;IACT;MAED;EA2BI,eACL,oBACA,UAAU,cACV,kBACC;;;;EAkBI,cACL,sBACA,UAAU,cACV,eACC;EAkCI,aAAa,SAAS;EAUtB,cAAc,UAAU;;;;EASlB,SAAS,GACpB,KAAK,QAAQ,gBAAgB,QAAQ,IACrC,UAAU,eACT,QAAQ;;;;EAiBE,OACX,UAAU,cACV,OAAO,iBACN;;;;EAwDI,SAAS;;;;EAQT;;UAYQ;EACf;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;;;;UC7kBe;;;;EAIf,OAAO;;;;;EAMP,UAAU;;;;;;;;;;;;;;;;;;;;cAqBC,YAAS,SAAa,oBAAkB;;;;;;;;cC1BxC;YAAsB,2BAAwB;;;UAK1C;;;;EAIf;;;;EAKA,UAAU;;cAKC,0BAA0B,UAAU;qBAC5B,kBAAgB;EAE5B;YAEG;;;;UC7BK;;;;EAIf;;;;EAKA,KAAK;;;;;;;;;;;;;;;;;;;;;;;;cAyBM,YAAS,SAAa,oBAAkB;;;;;;;;;;;;;;;;;;;;;cChBxC,WAAQ,UAAc,iBAAe;;;;;;;;;;;;;;cCDrC,iCAAc,yBAAA"}
|
package/dist/datetime/index.js
CHANGED
|
@@ -458,7 +458,12 @@ const $debounce = (options) => {
|
|
|
458
458
|
* Middleware that rate-controls handler execution using a token bucket.
|
|
459
459
|
*
|
|
460
460
|
* Excess calls are **delayed** until capacity is available — never rejected.
|
|
461
|
-
* Process-local (not distributed)
|
|
461
|
+
* Process-local (not distributed) — it cannot rate-limit across instances.
|
|
462
|
+
*
|
|
463
|
+
* **Limitation**: the token refill is time-based and re-checked only when a
|
|
464
|
+
* waiter wakes, so a burst of *concurrent* calls can wake in the same window
|
|
465
|
+
* and briefly exceed `rate`. Treat it as traffic smoothing, not a hard cap —
|
|
466
|
+
* do not rely on it to enforce a strict quota.
|
|
462
467
|
*
|
|
463
468
|
* **Use case**: protect an external API from your own traffic.
|
|
464
469
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/datetime/providers/DateTimeProvider.ts","../../src/datetime/primitives/$interval.ts","../../src/datetime/primitives/$debounce.ts","../../src/datetime/primitives/$throttle.ts","../../src/datetime/primitives/$timeout.ts","../../src/datetime/index.ts"],"sourcesContent":["import \"dayjs/plugin/relativeTime.js\";\nimport \"dayjs/plugin/duration.js\";\nimport \"dayjs/plugin/utc.js\";\nimport \"dayjs/plugin/timezone.js\";\nimport \"dayjs/plugin/localizedFormat.js\";\nimport \"dayjs/locale/ar.js\";\nimport \"dayjs/locale/fr.js\";\nimport { $hook, $inject, Alepha } from \"alepha\";\nimport DayjsApi, {\n type Dayjs,\n type ManipulateType,\n type OpUnitType,\n type PluginFunc,\n type QUnitType,\n} from \"dayjs\";\nimport dayjsDuration, { type DurationUnitType } from \"dayjs/plugin/duration.js\";\nimport dayjsLocalizedFormat from \"dayjs/plugin/localizedFormat.js\";\nimport dayjsRelativeTime from \"dayjs/plugin/relativeTime.js\";\nimport dayjsTimezone from \"dayjs/plugin/timezone.js\";\nimport dayjsUtc from \"dayjs/plugin/utc.js\";\n\nexport type { DurationUnitType, ManipulateType, OpUnitType, QUnitType };\n\nexport type DateTimeInput = string | number | Date | DateTime | Dayjs;\n\nexport type DurationLike = number | Duration | [number, ManipulateType];\n\n/**\n * Immutable wrapper around the underlying date-time engine.\n *\n * Designed to isolate consumers from the engine in use (currently dayjs).\n * Methods that produce a new value return a new `DateTime` instance.\n */\nexport class DateTime {\n protected readonly inner: Dayjs;\n\n constructor(inner: Dayjs) {\n this.inner = inner;\n }\n\n /**\n * Add a duration to this date-time.\n */\n add(amount: number, unit?: ManipulateType): DateTime;\n add(duration: Duration): DateTime;\n add(amount: number | Duration, unit?: ManipulateType): DateTime {\n if (amount instanceof Duration) {\n return new DateTime(this.inner.add(amount.toDayjs()));\n }\n return new DateTime(this.inner.add(amount, unit));\n }\n\n /**\n * Subtract a duration from this date-time.\n */\n subtract(amount: number, unit?: ManipulateType): DateTime;\n subtract(duration: Duration): DateTime;\n subtract(amount: number | Duration, unit?: ManipulateType): DateTime {\n if (amount instanceof Duration) {\n return new DateTime(this.inner.subtract(amount.toDayjs()));\n }\n return new DateTime(this.inner.subtract(amount, unit));\n }\n\n startOf(unit: OpUnitType): DateTime {\n return new DateTime(this.inner.startOf(unit));\n }\n\n endOf(unit: OpUnitType): DateTime {\n return new DateTime(this.inner.endOf(unit));\n }\n\n isAfter(other: DateTimeInput): boolean {\n return this.inner.isAfter(toDayjs(other));\n }\n\n isBefore(other: DateTimeInput): boolean {\n return this.inner.isBefore(toDayjs(other));\n }\n\n isSame(other: DateTimeInput, unit?: OpUnitType): boolean {\n return this.inner.isSame(toDayjs(other), unit);\n }\n\n diff(other: DateTimeInput, unit?: QUnitType | OpUnitType): number {\n return this.inner.diff(toDayjs(other), unit);\n }\n\n tz(timezone: string): DateTime {\n return new DateTime(this.inner.tz(timezone));\n }\n\n locale(lang: string): DateTime {\n return new DateTime(this.inner.locale(lang));\n }\n\n format(template?: string): string {\n return this.inner.format(template);\n }\n\n fromNow(withoutSuffix?: boolean): string {\n return this.inner.fromNow(withoutSuffix);\n }\n\n toISOString(): string {\n return this.inner.toISOString();\n }\n\n toDate(): Date {\n return this.inner.toDate();\n }\n\n valueOf(): number {\n return this.inner.valueOf();\n }\n\n unix(): number {\n return this.inner.unix();\n }\n\n toJSON(): string {\n return this.inner.toISOString();\n }\n\n toString(): string {\n return this.inner.toISOString();\n }\n\n /**\n * Escape hatch for the underlying dayjs instance.\n *\n * Use sparingly — anything calling this becomes coupled to dayjs and\n * will need to migrate when the engine is replaced.\n */\n toDayjs(): Dayjs {\n return this.inner;\n }\n}\n\n/**\n * Immutable wrapper around the underlying duration engine.\n */\nexport class Duration {\n protected readonly inner: dayjsDuration.Duration;\n\n constructor(inner: dayjsDuration.Duration) {\n this.inner = inner;\n }\n\n asMilliseconds(): number {\n return this.inner.asMilliseconds();\n }\n\n asSeconds(): number {\n return this.inner.asSeconds();\n }\n\n asMinutes(): number {\n return this.inner.asMinutes();\n }\n\n asHours(): number {\n return this.inner.asHours();\n }\n\n asDays(): number {\n return this.inner.asDays();\n }\n\n as(unit: DurationUnitType): number {\n return this.inner.as(unit);\n }\n\n toISOString(): string {\n return this.inner.toISOString();\n }\n\n /**\n * Escape hatch for the underlying dayjs duration.\n */\n toDayjs(): dayjsDuration.Duration {\n return this.inner;\n }\n}\n\nexport const isDateTime = (value: unknown): value is DateTime => {\n return value instanceof DateTime;\n};\n\nconst toDayjs = (value: DateTimeInput): Dayjs => {\n if (value instanceof DateTime) {\n return value.toDayjs();\n }\n return DayjsApi(value as any);\n};\n\nexport class DateTimeProvider {\n public static PLUGINS: Array<PluginFunc<any>> = [\n dayjsDuration,\n dayjsRelativeTime,\n dayjsUtc,\n dayjsTimezone,\n dayjsLocalizedFormat,\n ];\n\n protected alepha = $inject(Alepha);\n protected ref: DateTime | null = null;\n protected readonly timeouts: Timeout[] = [];\n protected readonly intervals: Interval[] = [];\n\n constructor() {\n for (const plugin of DateTimeProvider.PLUGINS) {\n DayjsApi.extend(plugin);\n }\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // we start intervals now but first tick will be rejected as App is not ready yet\n await Promise.all(\n this.intervals.map(async (interval) => {\n if (interval.timer != null) {\n return;\n }\n await interval.run();\n interval.timer = setInterval(interval.run, interval.duration);\n }),\n );\n },\n });\n\n protected readonly onStop = $hook({\n on: \"stop\",\n handler: () => {\n for (const timeout of [...this.timeouts]) {\n this.clearTimeout(timeout);\n }\n\n for (const interval of this.intervals) {\n clearInterval(interval.timer);\n interval.duration = 0;\n interval.timer = null;\n }\n },\n });\n\n public setLocale(locale: string): void {\n DayjsApi.locale(locale);\n }\n\n public isDateTime(value: unknown): value is DateTime {\n return value instanceof DateTime;\n }\n\n /**\n * Create a new UTC DateTime instance.\n */\n public utc(date: DateTimeInput | null | undefined): DateTime {\n return new DateTime(DayjsApi.utc(unwrap(date)));\n }\n\n /**\n * Create a new DateTime instance.\n */\n public of(date: DateTimeInput | null | undefined): DateTime {\n if (date instanceof DateTime) {\n return date;\n }\n return new DateTime(DayjsApi(date as any));\n }\n\n /**\n * Get the current date as a string.\n */\n public toISOString(date: DateTimeInput = this.now()): string {\n return this.of(date).toISOString();\n }\n\n /**\n * Get the current date.\n */\n public now(): DateTime {\n return this.getCurrentDate();\n }\n\n /**\n * Get the current date as a string.\n *\n * This is much faster than `DateTimeProvider.now().toISOString()` as it avoids creating a DateTime instance.\n */\n public nowISOString(): string {\n if (this.ref) {\n return this.ref.toISOString();\n }\n return new Date().toISOString();\n }\n\n /**\n * Get the current date as milliseconds since epoch.\n *\n * This is much faster than `DateTimeProvider.now().valueOf()` as it avoids creating a DateTime instance.\n */\n public nowMillis(): number {\n if (this.ref) {\n return this.ref.valueOf();\n }\n return Date.now();\n }\n\n /**\n * Get the current date as a string.\n *\n * @protected\n */\n protected getCurrentDate(): DateTime {\n if (this.ref) {\n return this.ref;\n }\n\n return new DateTime(DayjsApi());\n }\n\n /**\n * Create a new Duration instance.\n */\n public duration = (\n duration: DurationLike,\n unit?: ManipulateType,\n ): Duration => {\n if (duration instanceof Duration) {\n return duration;\n }\n\n if (Array.isArray(duration)) {\n return new Duration(DayjsApi.duration(duration[0], duration[1]));\n }\n\n if (typeof duration === \"number\") {\n return new Duration(DayjsApi.duration(duration, unit || \"milliseconds\"));\n }\n\n return duration;\n };\n\n public isDurationLike(value: unknown): value is DurationLike {\n try {\n return DayjsApi.isDuration(\n this.duration(value as DurationLike).toDayjs(),\n );\n } catch {\n return false;\n }\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Timer Management\n\n /**\n * Return a promise that resolves after the next tick.\n * It uses `setTimeout` with 0 ms delay.\n */\n public async tick(): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n\n /**\n * Wait for a certain duration.\n *\n * You can clear the timeout by using the `AbortSignal` API.\n * Aborted signal will resolve the promise immediately, it does not reject it.\n */\n public wait(\n duration: DurationLike,\n options: {\n signal?: AbortSignal;\n now?: number;\n } = {},\n ): Promise<void> {\n return new Promise((resolve) => {\n let clearTimeout: any;\n let callback: any;\n\n const timeout = this.createTimeout(\n () => {\n if (options.signal && clearTimeout) {\n options.signal.removeEventListener(\"abort\", callback);\n }\n resolve();\n },\n duration,\n options.now,\n );\n\n if (options.signal) {\n clearTimeout = () => this.clearTimeout(timeout);\n callback = () => {\n clearTimeout();\n resolve();\n };\n options.signal.addEventListener(\"abort\", callback);\n }\n });\n }\n\n public createInterval(\n run: () => unknown,\n duration: DurationLike,\n start = false,\n ): Interval {\n const interval: Interval = {\n run,\n duration: this.duration(duration).asMilliseconds(),\n };\n\n this.intervals.push(interval);\n\n if (start) {\n interval.timer = setInterval(interval.run, interval.duration);\n }\n\n return interval;\n }\n\n /**\n * Run a callback after a certain duration.\n */\n public createTimeout(\n callback: () => void,\n duration: DurationLike,\n now?: number,\n ): Timeout {\n if (this.ref && now) {\n const next = this.of(now).add(this.duration(duration));\n if (next.valueOf() < this.now().valueOf()) {\n callback();\n }\n return {\n now,\n duration: 0,\n callback: () => {},\n clear: () => {},\n };\n }\n\n const timeout: Timeout = {\n now: now ?? this.now().valueOf(),\n duration: this.duration(duration).asMilliseconds(),\n callback,\n clear: () => this.clearTimeout(timeout),\n };\n\n timeout.timer = setTimeout(() => {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n }, timeout.duration);\n\n this.timeouts.push(timeout);\n\n return timeout;\n }\n\n public clearTimeout(timeout: Timeout): void {\n clearTimeout(timeout.timer);\n timeout.duration = 0;\n timeout.timer = null;\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n }\n\n public clearInterval(interval: Interval): void {\n clearInterval(interval.timer);\n interval.duration = 0;\n interval.timer = null;\n }\n\n /**\n * Run a function with a deadline.\n */\n public async deadline<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n duration: DurationLike,\n ): Promise<T> {\n const abort = new AbortController();\n const timeout = this.createTimeout(() => abort.abort(), duration);\n try {\n return await fn(abort.signal);\n } finally {\n this.clearTimeout(timeout);\n }\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Testing\n\n /**\n * Add time to the current date.\n */\n public async travel(\n duration: DurationLike,\n unit?: ManipulateType,\n ): Promise<void> {\n this.ref = this.ref || this.now();\n const ms = this.duration(duration, unit).asMilliseconds();\n const now = this.nowMillis();\n this.ref = this.ref.add(this.duration(duration, unit));\n\n for (const timeout of [...this.timeouts]) {\n if (!timeout.timer) {\n continue;\n }\n\n clearTimeout(timeout.timer);\n timeout.timer = null;\n\n const spent = now - timeout.now;\n timeout.duration = timeout.duration - spent - ms;\n\n if (timeout.duration <= 0) {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n } else {\n timeout.timer = setTimeout(() => {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n }, timeout.duration);\n }\n }\n\n for (const interval of this.intervals) {\n if (!interval.timer) {\n continue;\n }\n\n clearInterval(interval.timer);\n\n const repeat = Math.floor(ms / interval.duration);\n for (let i = 0; i < repeat; i++) {\n await interval.run();\n }\n\n // Keep intervals suspended — they only fire during travel() calls\n interval.timer = null;\n }\n\n await this.tick();\n }\n\n /**\n * Stop the time.\n */\n public pause(): DateTime {\n this.ref = this.ref || this.now();\n return this.ref;\n }\n\n /**\n * Reset the reference date.\n */\n public reset(): void {\n this.ref = null;\n }\n}\n\nconst unwrap = (value: DateTimeInput | null | undefined): any => {\n if (value instanceof DateTime) {\n return value.toDayjs();\n }\n return value;\n};\n\nexport interface Interval {\n timer?: any;\n duration: number;\n run: () => unknown;\n}\n\nexport interface Timeout {\n now: number;\n timer?: any;\n duration: number;\n callback: () => void;\n clear: () => void;\n}\n","import { $inject, createPrimitive, KIND, Primitive } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\n/**\n * Run a function periodically.\n * It uses the `setInterval` internally.\n * It starts by default when the context starts and stops when the context stops.\n */\nexport const $interval = (options: IntervalPrimitiveOptions) =>\n createPrimitive(IntervalPrimitive, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface IntervalPrimitiveOptions {\n /**\n * The interval handler.\n */\n handler: () => unknown;\n\n /**\n * The interval duration.\n */\n duration: DurationLike;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class IntervalPrimitive extends Primitive<IntervalPrimitiveOptions> {\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n\n public called = 0;\n\n protected onInit() {\n this.dateTimeProvider.createInterval(async () => {\n await this.options.handler();\n this.called += 1;\n }, this.options.duration);\n }\n}\n\n$interval[KIND] = IntervalPrimitive;\n","import { createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\nexport interface DebounceOptions {\n /**\n * Coalescing window. Concurrent calls within this window share one execution.\n */\n delay: DurationLike;\n\n /**\n * Key function to group calls. Calls with the same key are coalesced.\n * Defaults to `JSON.stringify(args)`.\n */\n key?: (...args: any[]) => string;\n}\n\n/**\n * Middleware that coalesces concurrent calls with the same key into a single handler execution.\n *\n * All callers within the delay window receive the same result. No storage —\n * once the handler finishes, the next call starts fresh. Process-local.\n *\n * **Use case**: thundering herd protection — cache expires, 100 requests\n * hit the same endpoint, debounce ensures one rebuild.\n *\n * ```typescript\n * class SearchController {\n * search = $action({\n * use: [$debounce({ delay: [200, \"ms\"], key: (req) => req.query.q })],\n * handler: async ({ query }) => this.searchService.search(query.q),\n * });\n * }\n * ```\n */\nexport const $debounce = (options: DebounceOptions): Middleware => {\n return createMiddleware({\n name: \"$debounce\",\n options: options as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n const pending = new Map<string, Promise<any>>();\n\n return async (...args) => {\n const key = options.key?.(...args) ?? JSON.stringify(args);\n\n const existing = pending.get(key);\n if (existing) {\n return existing;\n }\n\n let resolve: (value: any) => void;\n let reject: (error: any) => void;\n const promise = new Promise<any>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n dateTimeProvider.createTimeout(async () => {\n try {\n const result = await next(...args);\n resolve!(result);\n } catch (error) {\n reject!(error);\n } finally {\n pending.delete(key);\n }\n }, options.delay);\n\n pending.set(key, promise);\n return promise;\n };\n },\n });\n};\n","import { createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\nexport interface ThrottleOptions {\n /**\n * Max calls per window.\n */\n rate: number;\n\n /**\n * Window duration.\n */\n per: DurationLike;\n}\n\n/**\n * Middleware that rate-controls handler execution using a token bucket.\n *\n * Excess calls are **delayed** until capacity is available — never rejected.\n * Process-local (not distributed). Use `$rateLimit` for distributed rate limiting.\n *\n * **Use case**: protect an external API from your own traffic.\n *\n * ```typescript\n * class PaymentController {\n * charge = $action({\n * use: [$throttle({ rate: 80, per: [1, \"second\"] })],\n * handler: async ({ body }) => this.stripe.charges.create(body),\n * });\n * }\n * ```\n */\nexport const $throttle = (options: ThrottleOptions): Middleware => {\n return createMiddleware({\n name: \"$throttle\",\n options: options as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n const intervalMs = dateTimeProvider\n .duration(options.per)\n .asMilliseconds();\n\n let tokens = options.rate;\n let lastRefill = dateTimeProvider.nowMillis();\n\n return async (...args) => {\n const now = dateTimeProvider.nowMillis();\n const elapsed = now - lastRefill;\n const refill = Math.floor(elapsed / intervalMs) * options.rate;\n\n if (refill > 0) {\n tokens = Math.min(options.rate, tokens + refill);\n lastRefill += Math.floor(elapsed / intervalMs) * intervalMs;\n }\n\n if (tokens <= 0) {\n const waitMs = intervalMs - (now - lastRefill);\n await dateTimeProvider.wait(waitMs);\n tokens = options.rate;\n lastRefill = dateTimeProvider.nowMillis();\n }\n\n tokens--;\n return next(...args);\n };\n },\n });\n};\n","import { AlephaError, createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\n/**\n * Middleware that aborts handler execution if it exceeds a duration limit.\n *\n * Uses `Promise.race` with a managed timeout from `DateTimeProvider` —\n * if the handler doesn't resolve before the deadline, the promise rejects.\n * Uses managed timeouts so it works with `DateTimeProvider.travel()` in tests.\n *\n * ```typescript\n * class OrderService {\n * processOrder = $pipeline({\n * use: [$timeout([30, \"seconds\"])],\n * handler: async (orderId: string) => {\n * return await this.orders.updateById(orderId, { status: \"paid\" });\n * },\n * });\n * }\n * ```\n */\nexport const $timeout = (duration: DurationLike): Middleware => {\n return createMiddleware({\n name: \"$timeout\",\n options: { duration },\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n\n return async (...args) => {\n let rejectTimeout: (reason: Error) => void;\n const timeoutPromise = new Promise<never>((_, reject) => {\n rejectTimeout = reject;\n });\n\n const timer = dateTimeProvider.createTimeout(() => {\n rejectTimeout(new AlephaError(\"$timeout: handler exceeded deadline\"));\n }, duration);\n\n try {\n return await Promise.race([next(...args), timeoutPromise]);\n } finally {\n dateTimeProvider.clearTimeout(timer);\n }\n };\n },\n });\n};\n","import { $module } from \"alepha\";\nimport { $interval } from \"./primitives/$interval.ts\";\nimport { DateTimeProvider } from \"./providers/DateTimeProvider.ts\";\n\nexport * from \"./primitives/$debounce.ts\";\nexport * from \"./primitives/$interval.ts\";\nexport * from \"./primitives/$throttle.ts\";\nexport * from \"./primitives/$timeout.ts\";\nexport * from \"./providers/DateTimeProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Date and time operations.\n *\n * **Features:**\n * - Recurring interval definitions\n * - Duration parsing (ISO 8601, human-readable)\n * - Timezone support\n * - Dayjs integration\n *\n * @module alepha.datetime\n */\nexport const AlephaDateTime = $module({\n name: \"alepha.datetime\",\n primitives: [$interval],\n services: [DateTimeProvider],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/datetime/providers/DateTimeProvider.ts","../../src/datetime/primitives/$interval.ts","../../src/datetime/primitives/$debounce.ts","../../src/datetime/primitives/$throttle.ts","../../src/datetime/primitives/$timeout.ts","../../src/datetime/index.ts"],"sourcesContent":["import \"dayjs/plugin/relativeTime.js\";\nimport \"dayjs/plugin/duration.js\";\nimport \"dayjs/plugin/utc.js\";\nimport \"dayjs/plugin/timezone.js\";\nimport \"dayjs/plugin/localizedFormat.js\";\nimport \"dayjs/locale/ar.js\";\nimport \"dayjs/locale/fr.js\";\nimport { $hook, $inject, Alepha } from \"alepha\";\nimport DayjsApi, {\n type Dayjs,\n type ManipulateType,\n type OpUnitType,\n type PluginFunc,\n type QUnitType,\n} from \"dayjs\";\nimport dayjsDuration, { type DurationUnitType } from \"dayjs/plugin/duration.js\";\nimport dayjsLocalizedFormat from \"dayjs/plugin/localizedFormat.js\";\nimport dayjsRelativeTime from \"dayjs/plugin/relativeTime.js\";\nimport dayjsTimezone from \"dayjs/plugin/timezone.js\";\nimport dayjsUtc from \"dayjs/plugin/utc.js\";\n\nexport type { DurationUnitType, ManipulateType, OpUnitType, QUnitType };\n\nexport type DateTimeInput = string | number | Date | DateTime | Dayjs;\n\nexport type DurationLike = number | Duration | [number, ManipulateType];\n\n/**\n * Immutable wrapper around the underlying date-time engine.\n *\n * Designed to isolate consumers from the engine in use (currently dayjs).\n * Methods that produce a new value return a new `DateTime` instance.\n */\nexport class DateTime {\n protected readonly inner: Dayjs;\n\n constructor(inner: Dayjs) {\n this.inner = inner;\n }\n\n /**\n * Add a duration to this date-time.\n */\n add(amount: number, unit?: ManipulateType): DateTime;\n add(duration: Duration): DateTime;\n add(amount: number | Duration, unit?: ManipulateType): DateTime {\n if (amount instanceof Duration) {\n return new DateTime(this.inner.add(amount.toDayjs()));\n }\n return new DateTime(this.inner.add(amount, unit));\n }\n\n /**\n * Subtract a duration from this date-time.\n */\n subtract(amount: number, unit?: ManipulateType): DateTime;\n subtract(duration: Duration): DateTime;\n subtract(amount: number | Duration, unit?: ManipulateType): DateTime {\n if (amount instanceof Duration) {\n return new DateTime(this.inner.subtract(amount.toDayjs()));\n }\n return new DateTime(this.inner.subtract(amount, unit));\n }\n\n startOf(unit: OpUnitType): DateTime {\n return new DateTime(this.inner.startOf(unit));\n }\n\n endOf(unit: OpUnitType): DateTime {\n return new DateTime(this.inner.endOf(unit));\n }\n\n isAfter(other: DateTimeInput): boolean {\n return this.inner.isAfter(toDayjs(other));\n }\n\n isBefore(other: DateTimeInput): boolean {\n return this.inner.isBefore(toDayjs(other));\n }\n\n isSame(other: DateTimeInput, unit?: OpUnitType): boolean {\n return this.inner.isSame(toDayjs(other), unit);\n }\n\n diff(other: DateTimeInput, unit?: QUnitType | OpUnitType): number {\n return this.inner.diff(toDayjs(other), unit);\n }\n\n tz(timezone: string): DateTime {\n return new DateTime(this.inner.tz(timezone));\n }\n\n locale(lang: string): DateTime {\n return new DateTime(this.inner.locale(lang));\n }\n\n format(template?: string): string {\n return this.inner.format(template);\n }\n\n fromNow(withoutSuffix?: boolean): string {\n return this.inner.fromNow(withoutSuffix);\n }\n\n toISOString(): string {\n return this.inner.toISOString();\n }\n\n toDate(): Date {\n return this.inner.toDate();\n }\n\n valueOf(): number {\n return this.inner.valueOf();\n }\n\n unix(): number {\n return this.inner.unix();\n }\n\n toJSON(): string {\n return this.inner.toISOString();\n }\n\n toString(): string {\n return this.inner.toISOString();\n }\n\n /**\n * Escape hatch for the underlying dayjs instance.\n *\n * Use sparingly — anything calling this becomes coupled to dayjs and\n * will need to migrate when the engine is replaced.\n */\n toDayjs(): Dayjs {\n return this.inner;\n }\n}\n\n/**\n * Immutable wrapper around the underlying duration engine.\n */\nexport class Duration {\n protected readonly inner: dayjsDuration.Duration;\n\n constructor(inner: dayjsDuration.Duration) {\n this.inner = inner;\n }\n\n asMilliseconds(): number {\n return this.inner.asMilliseconds();\n }\n\n asSeconds(): number {\n return this.inner.asSeconds();\n }\n\n asMinutes(): number {\n return this.inner.asMinutes();\n }\n\n asHours(): number {\n return this.inner.asHours();\n }\n\n asDays(): number {\n return this.inner.asDays();\n }\n\n as(unit: DurationUnitType): number {\n return this.inner.as(unit);\n }\n\n toISOString(): string {\n return this.inner.toISOString();\n }\n\n /**\n * Escape hatch for the underlying dayjs duration.\n */\n toDayjs(): dayjsDuration.Duration {\n return this.inner;\n }\n}\n\nexport const isDateTime = (value: unknown): value is DateTime => {\n return value instanceof DateTime;\n};\n\nconst toDayjs = (value: DateTimeInput): Dayjs => {\n if (value instanceof DateTime) {\n return value.toDayjs();\n }\n return DayjsApi(value as any);\n};\n\nexport class DateTimeProvider {\n public static PLUGINS: Array<PluginFunc<any>> = [\n dayjsDuration,\n dayjsRelativeTime,\n dayjsUtc,\n dayjsTimezone,\n dayjsLocalizedFormat,\n ];\n\n protected alepha = $inject(Alepha);\n protected ref: DateTime | null = null;\n protected readonly timeouts: Timeout[] = [];\n protected readonly intervals: Interval[] = [];\n\n constructor() {\n for (const plugin of DateTimeProvider.PLUGINS) {\n DayjsApi.extend(plugin);\n }\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // we start intervals now but first tick will be rejected as App is not ready yet\n await Promise.all(\n this.intervals.map(async (interval) => {\n if (interval.timer != null) {\n return;\n }\n await interval.run();\n interval.timer = setInterval(interval.run, interval.duration);\n }),\n );\n },\n });\n\n protected readonly onStop = $hook({\n on: \"stop\",\n handler: () => {\n for (const timeout of [...this.timeouts]) {\n this.clearTimeout(timeout);\n }\n\n for (const interval of this.intervals) {\n clearInterval(interval.timer);\n interval.duration = 0;\n interval.timer = null;\n }\n },\n });\n\n public setLocale(locale: string): void {\n DayjsApi.locale(locale);\n }\n\n public isDateTime(value: unknown): value is DateTime {\n return value instanceof DateTime;\n }\n\n /**\n * Create a new UTC DateTime instance.\n */\n public utc(date: DateTimeInput | null | undefined): DateTime {\n return new DateTime(DayjsApi.utc(unwrap(date)));\n }\n\n /**\n * Create a new DateTime instance.\n */\n public of(date: DateTimeInput | null | undefined): DateTime {\n if (date instanceof DateTime) {\n return date;\n }\n return new DateTime(DayjsApi(date as any));\n }\n\n /**\n * Get the current date as a string.\n */\n public toISOString(date: DateTimeInput = this.now()): string {\n return this.of(date).toISOString();\n }\n\n /**\n * Get the current date.\n */\n public now(): DateTime {\n return this.getCurrentDate();\n }\n\n /**\n * Get the current date as a string.\n *\n * This is much faster than `DateTimeProvider.now().toISOString()` as it avoids creating a DateTime instance.\n */\n public nowISOString(): string {\n if (this.ref) {\n return this.ref.toISOString();\n }\n return new Date().toISOString();\n }\n\n /**\n * Get the current date as milliseconds since epoch.\n *\n * This is much faster than `DateTimeProvider.now().valueOf()` as it avoids creating a DateTime instance.\n */\n public nowMillis(): number {\n if (this.ref) {\n return this.ref.valueOf();\n }\n return Date.now();\n }\n\n /**\n * Get the current date as a string.\n *\n * @protected\n */\n protected getCurrentDate(): DateTime {\n if (this.ref) {\n return this.ref;\n }\n\n return new DateTime(DayjsApi());\n }\n\n /**\n * Create a new Duration instance.\n */\n public duration = (\n duration: DurationLike,\n unit?: ManipulateType,\n ): Duration => {\n if (duration instanceof Duration) {\n return duration;\n }\n\n if (Array.isArray(duration)) {\n return new Duration(DayjsApi.duration(duration[0], duration[1]));\n }\n\n if (typeof duration === \"number\") {\n return new Duration(DayjsApi.duration(duration, unit || \"milliseconds\"));\n }\n\n return duration;\n };\n\n public isDurationLike(value: unknown): value is DurationLike {\n try {\n return DayjsApi.isDuration(\n this.duration(value as DurationLike).toDayjs(),\n );\n } catch {\n return false;\n }\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Timer Management\n\n /**\n * Return a promise that resolves after the next tick.\n * It uses `setTimeout` with 0 ms delay.\n */\n public async tick(): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n\n /**\n * Wait for a certain duration.\n *\n * You can clear the timeout by using the `AbortSignal` API.\n * Aborted signal will resolve the promise immediately, it does not reject it.\n */\n public wait(\n duration: DurationLike,\n options: {\n signal?: AbortSignal;\n now?: number;\n } = {},\n ): Promise<void> {\n return new Promise((resolve) => {\n let clearTimeout: any;\n let callback: any;\n\n const timeout = this.createTimeout(\n () => {\n if (options.signal && clearTimeout) {\n options.signal.removeEventListener(\"abort\", callback);\n }\n resolve();\n },\n duration,\n options.now,\n );\n\n if (options.signal) {\n clearTimeout = () => this.clearTimeout(timeout);\n callback = () => {\n clearTimeout();\n resolve();\n };\n options.signal.addEventListener(\"abort\", callback);\n }\n });\n }\n\n public createInterval(\n run: () => unknown,\n duration: DurationLike,\n start = false,\n ): Interval {\n const interval: Interval = {\n run,\n duration: this.duration(duration).asMilliseconds(),\n };\n\n this.intervals.push(interval);\n\n if (start) {\n interval.timer = setInterval(interval.run, interval.duration);\n }\n\n return interval;\n }\n\n /**\n * Run a callback after a certain duration.\n */\n public createTimeout(\n callback: () => void,\n duration: DurationLike,\n now?: number,\n ): Timeout {\n if (this.ref && now) {\n const next = this.of(now).add(this.duration(duration));\n if (next.valueOf() < this.now().valueOf()) {\n callback();\n }\n return {\n now,\n duration: 0,\n callback: () => {},\n clear: () => {},\n };\n }\n\n const timeout: Timeout = {\n now: now ?? this.now().valueOf(),\n duration: this.duration(duration).asMilliseconds(),\n callback,\n clear: () => this.clearTimeout(timeout),\n };\n\n timeout.timer = setTimeout(() => {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n }, timeout.duration);\n\n this.timeouts.push(timeout);\n\n return timeout;\n }\n\n public clearTimeout(timeout: Timeout): void {\n clearTimeout(timeout.timer);\n timeout.duration = 0;\n timeout.timer = null;\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n }\n\n public clearInterval(interval: Interval): void {\n clearInterval(interval.timer);\n interval.duration = 0;\n interval.timer = null;\n }\n\n /**\n * Run a function with a deadline.\n */\n public async deadline<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n duration: DurationLike,\n ): Promise<T> {\n const abort = new AbortController();\n const timeout = this.createTimeout(() => abort.abort(), duration);\n try {\n return await fn(abort.signal);\n } finally {\n this.clearTimeout(timeout);\n }\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Testing\n\n /**\n * Add time to the current date.\n */\n public async travel(\n duration: DurationLike,\n unit?: ManipulateType,\n ): Promise<void> {\n this.ref = this.ref || this.now();\n const ms = this.duration(duration, unit).asMilliseconds();\n const now = this.nowMillis();\n this.ref = this.ref.add(this.duration(duration, unit));\n\n for (const timeout of [...this.timeouts]) {\n if (!timeout.timer) {\n continue;\n }\n\n clearTimeout(timeout.timer);\n timeout.timer = null;\n\n const spent = now - timeout.now;\n timeout.duration = timeout.duration - spent - ms;\n\n if (timeout.duration <= 0) {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n } else {\n timeout.timer = setTimeout(() => {\n const index = this.timeouts.indexOf(timeout);\n if (index !== -1) {\n this.timeouts.splice(index, 1);\n }\n timeout.callback();\n }, timeout.duration);\n }\n }\n\n for (const interval of this.intervals) {\n if (!interval.timer) {\n continue;\n }\n\n clearInterval(interval.timer);\n\n const repeat = Math.floor(ms / interval.duration);\n for (let i = 0; i < repeat; i++) {\n await interval.run();\n }\n\n // Keep intervals suspended — they only fire during travel() calls\n interval.timer = null;\n }\n\n await this.tick();\n }\n\n /**\n * Stop the time.\n */\n public pause(): DateTime {\n this.ref = this.ref || this.now();\n return this.ref;\n }\n\n /**\n * Reset the reference date.\n */\n public reset(): void {\n this.ref = null;\n }\n}\n\nconst unwrap = (value: DateTimeInput | null | undefined): any => {\n if (value instanceof DateTime) {\n return value.toDayjs();\n }\n return value;\n};\n\nexport interface Interval {\n timer?: any;\n duration: number;\n run: () => unknown;\n}\n\nexport interface Timeout {\n now: number;\n timer?: any;\n duration: number;\n callback: () => void;\n clear: () => void;\n}\n","import { $inject, createPrimitive, KIND, Primitive } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\n/**\n * Run a function periodically.\n * It uses the `setInterval` internally.\n * It starts by default when the context starts and stops when the context stops.\n */\nexport const $interval = (options: IntervalPrimitiveOptions) =>\n createPrimitive(IntervalPrimitive, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface IntervalPrimitiveOptions {\n /**\n * The interval handler.\n */\n handler: () => unknown;\n\n /**\n * The interval duration.\n */\n duration: DurationLike;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class IntervalPrimitive extends Primitive<IntervalPrimitiveOptions> {\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n\n public called = 0;\n\n protected onInit() {\n this.dateTimeProvider.createInterval(async () => {\n await this.options.handler();\n this.called += 1;\n }, this.options.duration);\n }\n}\n\n$interval[KIND] = IntervalPrimitive;\n","import { createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\nexport interface DebounceOptions {\n /**\n * Coalescing window. Concurrent calls within this window share one execution.\n */\n delay: DurationLike;\n\n /**\n * Key function to group calls. Calls with the same key are coalesced.\n * Defaults to `JSON.stringify(args)`.\n */\n key?: (...args: any[]) => string;\n}\n\n/**\n * Middleware that coalesces concurrent calls with the same key into a single handler execution.\n *\n * All callers within the delay window receive the same result. No storage —\n * once the handler finishes, the next call starts fresh. Process-local.\n *\n * **Use case**: thundering herd protection — cache expires, 100 requests\n * hit the same endpoint, debounce ensures one rebuild.\n *\n * ```typescript\n * class SearchController {\n * search = $action({\n * use: [$debounce({ delay: [200, \"ms\"], key: (req) => req.query.q })],\n * handler: async ({ query }) => this.searchService.search(query.q),\n * });\n * }\n * ```\n */\nexport const $debounce = (options: DebounceOptions): Middleware => {\n return createMiddleware({\n name: \"$debounce\",\n options: options as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n const pending = new Map<string, Promise<any>>();\n\n return async (...args) => {\n const key = options.key?.(...args) ?? JSON.stringify(args);\n\n const existing = pending.get(key);\n if (existing) {\n return existing;\n }\n\n let resolve: (value: any) => void;\n let reject: (error: any) => void;\n const promise = new Promise<any>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n dateTimeProvider.createTimeout(async () => {\n try {\n const result = await next(...args);\n resolve!(result);\n } catch (error) {\n reject!(error);\n } finally {\n pending.delete(key);\n }\n }, options.delay);\n\n pending.set(key, promise);\n return promise;\n };\n },\n });\n};\n","import { createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\nexport interface ThrottleOptions {\n /**\n * Max calls per window.\n */\n rate: number;\n\n /**\n * Window duration.\n */\n per: DurationLike;\n}\n\n/**\n * Middleware that rate-controls handler execution using a token bucket.\n *\n * Excess calls are **delayed** until capacity is available — never rejected.\n * Process-local (not distributed) — it cannot rate-limit across instances.\n *\n * **Limitation**: the token refill is time-based and re-checked only when a\n * waiter wakes, so a burst of *concurrent* calls can wake in the same window\n * and briefly exceed `rate`. Treat it as traffic smoothing, not a hard cap —\n * do not rely on it to enforce a strict quota.\n *\n * **Use case**: protect an external API from your own traffic.\n *\n * ```typescript\n * class PaymentController {\n * charge = $action({\n * use: [$throttle({ rate: 80, per: [1, \"second\"] })],\n * handler: async ({ body }) => this.stripe.charges.create(body),\n * });\n * }\n * ```\n */\nexport const $throttle = (options: ThrottleOptions): Middleware => {\n return createMiddleware({\n name: \"$throttle\",\n options: options as unknown as Record<string, unknown>,\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n const intervalMs = dateTimeProvider\n .duration(options.per)\n .asMilliseconds();\n\n let tokens = options.rate;\n let lastRefill = dateTimeProvider.nowMillis();\n\n return async (...args) => {\n const now = dateTimeProvider.nowMillis();\n const elapsed = now - lastRefill;\n const refill = Math.floor(elapsed / intervalMs) * options.rate;\n\n if (refill > 0) {\n tokens = Math.min(options.rate, tokens + refill);\n lastRefill += Math.floor(elapsed / intervalMs) * intervalMs;\n }\n\n if (tokens <= 0) {\n const waitMs = intervalMs - (now - lastRefill);\n await dateTimeProvider.wait(waitMs);\n tokens = options.rate;\n lastRefill = dateTimeProvider.nowMillis();\n }\n\n tokens--;\n return next(...args);\n };\n },\n });\n};\n","import { AlephaError, createMiddleware, type Middleware } from \"alepha\";\nimport {\n DateTimeProvider,\n type DurationLike,\n} from \"../providers/DateTimeProvider.ts\";\n\n/**\n * Middleware that aborts handler execution if it exceeds a duration limit.\n *\n * Uses `Promise.race` with a managed timeout from `DateTimeProvider` —\n * if the handler doesn't resolve before the deadline, the promise rejects.\n * Uses managed timeouts so it works with `DateTimeProvider.travel()` in tests.\n *\n * ```typescript\n * class OrderService {\n * processOrder = $pipeline({\n * use: [$timeout([30, \"seconds\"])],\n * handler: async (orderId: string) => {\n * return await this.orders.updateById(orderId, { status: \"paid\" });\n * },\n * });\n * }\n * ```\n */\nexport const $timeout = (duration: DurationLike): Middleware => {\n return createMiddleware({\n name: \"$timeout\",\n options: { duration },\n handler: ({ alepha, next }) => {\n const dateTimeProvider = alepha.inject(DateTimeProvider);\n\n return async (...args) => {\n let rejectTimeout: (reason: Error) => void;\n const timeoutPromise = new Promise<never>((_, reject) => {\n rejectTimeout = reject;\n });\n\n const timer = dateTimeProvider.createTimeout(() => {\n rejectTimeout(new AlephaError(\"$timeout: handler exceeded deadline\"));\n }, duration);\n\n try {\n return await Promise.race([next(...args), timeoutPromise]);\n } finally {\n dateTimeProvider.clearTimeout(timer);\n }\n };\n },\n });\n};\n","import { $module } from \"alepha\";\nimport { $interval } from \"./primitives/$interval.ts\";\nimport { DateTimeProvider } from \"./providers/DateTimeProvider.ts\";\n\nexport * from \"./primitives/$debounce.ts\";\nexport * from \"./primitives/$interval.ts\";\nexport * from \"./primitives/$throttle.ts\";\nexport * from \"./primitives/$timeout.ts\";\nexport * from \"./providers/DateTimeProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Date and time operations.\n *\n * **Features:**\n * - Recurring interval definitions\n * - Duration parsing (ISO 8601, human-readable)\n * - Timezone support\n * - Dayjs integration\n *\n * @module alepha.datetime\n */\nexport const AlephaDateTime = $module({\n name: \"alepha.datetime\",\n primitives: [$interval],\n services: [DateTimeProvider],\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/email/brevo/providers/BrevoEmailProvider.ts","../../../src/email/brevo/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/email/brevo/providers/BrevoEmailProvider.ts","../../../src/email/brevo/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;cAyCa,8BAA8B;qBACtB;;;;qBACA,6BAAG;EAET,KAAK,SAAS,mBAAmB;;;;;;;;;;cChCnC,mCAAgB,yBAAA"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { $env, $module,
|
|
1
|
+
import { $env, $module, z } from "alepha";
|
|
2
2
|
import { AlephaEmail, EmailError, EmailProvider } from "alepha/email";
|
|
3
3
|
import { $logger } from "alepha/logger";
|
|
4
4
|
//#region ../../src/email/brevo/providers/BrevoEmailProvider.ts
|
|
5
5
|
/**
|
|
6
6
|
* Environment variables for Brevo configuration.
|
|
7
7
|
*/
|
|
8
|
-
const envSchema =
|
|
9
|
-
BREVO_API_KEY:
|
|
10
|
-
EMAIL_FROM:
|
|
8
|
+
const envSchema = z.object({
|
|
9
|
+
BREVO_API_KEY: z.text({ description: "Brevo API key for transactional email" }),
|
|
10
|
+
EMAIL_FROM: z.text({ description: "Default sender email address" })
|
|
11
11
|
});
|
|
12
12
|
/**
|
|
13
13
|
* Email provider using Brevo (formerly Sendinblue) transactional email API.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/email/brevo/providers/BrevoEmailProvider.ts","../../../src/email/brevo/index.ts"],"sourcesContent":["import { $env,
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/email/brevo/providers/BrevoEmailProvider.ts","../../../src/email/brevo/index.ts"],"sourcesContent":["import { $env, z } from \"alepha\";\nimport {\n EmailError,\n type EmailProvider,\n type EmailSendOptions,\n} from \"alepha/email\";\nimport { $logger } from \"alepha/logger\";\n\n/**\n * Environment variables for Brevo configuration.\n */\nconst envSchema = z.object({\n BREVO_API_KEY: z.text({\n description: \"Brevo API key for transactional email\",\n }),\n EMAIL_FROM: z.text({\n description: \"Default sender email address\",\n }),\n});\n\n/**\n * Email provider using Brevo (formerly Sendinblue) transactional email API.\n *\n * Sends emails via `POST https://api.brevo.com/v3/smtp/email`.\n *\n * Configuration is provided via environment variables:\n * - `BREVO_API_KEY`: Brevo API key\n * - `EMAIL_FROM`: Default sender email address\n *\n * @example\n * ```typescript\n * // .env\n * // BREVO_API_KEY=xkeysib-xxx\n * // EMAIL_FROM=noreply@example.com\n *\n * // app.ts\n * import { AlephaEmailBrevo } from \"alepha/email/brevo\";\n *\n * const app = Alepha.create().with(AlephaEmailBrevo);\n * ```\n */\nexport class BrevoEmailProvider implements EmailProvider {\n protected readonly env = $env(envSchema);\n protected readonly log = $logger();\n\n public async send(options: EmailSendOptions): Promise<void> {\n const { to, subject, body } = options;\n this.log.info(\"Sending email via Brevo\", { to, subject });\n\n const recipients = Array.isArray(to) ? to : [to];\n\n try {\n const response = await fetch(\"https://api.brevo.com/v3/smtp/email\", {\n method: \"POST\",\n headers: {\n \"api-key\": this.env.BREVO_API_KEY,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify({\n sender: { email: this.env.EMAIL_FROM },\n to: recipients.map((email) => ({ email })),\n subject,\n htmlContent: body,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new EmailError(`Brevo API returned ${response.status}: ${text}`);\n }\n\n this.log.info(\"Email sent successfully via Brevo\", {\n to,\n subject,\n });\n } catch (error) {\n if (error instanceof EmailError) {\n throw error;\n }\n const message = `Failed to send email via Brevo: ${error instanceof Error ? error.message : String(error)}`;\n this.log.error(message, { to, subject });\n throw new EmailError(message, error instanceof Error ? error : undefined);\n }\n }\n}\n","import { $module, type Alepha } from \"alepha\";\nimport { AlephaEmail, EmailProvider } from \"alepha/email\";\nimport { BrevoEmailProvider } from \"./providers/BrevoEmailProvider.ts\";\n\n// Exports\nexport * from \"./providers/BrevoEmailProvider.ts\";\n\n/**\n * Plugin for Alepha Email that provides Brevo transactional email capabilities.\n *\n * @see {@link BrevoEmailProvider}\n * @module alepha.email.brevo\n */\nexport const AlephaEmailBrevo = $module({\n name: \"alepha.email.brevo\",\n services: [BrevoEmailProvider],\n register: (alepha: Alepha) =>\n alepha\n .with({\n optional: true,\n provide: EmailProvider,\n use: BrevoEmailProvider,\n })\n .with(AlephaEmail),\n});\n"],"mappings":";;;;;;;AAWA,MAAM,YAAY,EAAE,OAAO;CACzB,eAAe,EAAE,KAAK,EACpB,aAAa,wCACf,CAAC;CACD,YAAY,EAAE,KAAK,EACjB,aAAa,+BACf,CAAC;AACH,CAAC;;;;;;;;;;;;;;;;;;;;;;AAuBD,IAAa,qBAAb,MAAyD;CACvD,MAAyB,KAAK,SAAS;CACvC,MAAyB,QAAQ;CAEjC,MAAa,KAAK,SAA0C;EAC1D,MAAM,EAAE,IAAI,SAAS,SAAS;EAC9B,KAAK,IAAI,KAAK,2BAA2B;GAAE;GAAI;EAAQ,CAAC;EAExD,MAAM,aAAa,MAAM,QAAQ,EAAE,IAAI,KAAK,CAAC,EAAE;EAE/C,IAAI;GACF,MAAM,WAAW,MAAM,MAAM,uCAAuC;IAClE,QAAQ;IACR,SAAS;KACP,WAAW,KAAK,IAAI;KACpB,gBAAgB;KAChB,QAAQ;IACV;IACA,MAAM,KAAK,UAAU;KACnB,QAAQ,EAAE,OAAO,KAAK,IAAI,WAAW;KACrC,IAAI,WAAW,KAAK,WAAW,EAAE,MAAM,EAAE;KACzC;KACA,aAAa;IACf,CAAC;GACH,CAAC;GAED,IAAI,CAAC,SAAS,IAAI;IAChB,MAAM,OAAO,MAAM,SAAS,KAAK;IACjC,MAAM,IAAI,WAAW,sBAAsB,SAAS,OAAO,IAAI,MAAM;GACvE;GAEA,KAAK,IAAI,KAAK,qCAAqC;IACjD;IACA;GACF,CAAC;EACH,SAAS,OAAO;GACd,IAAI,iBAAiB,YACnB,MAAM;GAER,MAAM,UAAU,mCAAmC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GACxG,KAAK,IAAI,MAAM,SAAS;IAAE;IAAI;GAAQ,CAAC;GACvC,MAAM,IAAI,WAAW,SAAS,iBAAiB,QAAQ,QAAQ,KAAA,CAAS;EAC1E;CACF;AACF;;;;;;;;;ACxEA,MAAa,mBAAmB,QAAQ;CACtC,MAAM;CACN,UAAU,CAAC,kBAAkB;CAC7B,WAAW,WACT,OACG,KAAK;EACJ,UAAU;EACV,SAAS;EACT,KAAK;CACP,CAAC,CAAC,CACD,KAAK,WAAW;AACvB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/email/cloudflare/providers/CloudflareEmailProvider.ts","../../../src/email/cloudflare/index.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/email/cloudflare/providers/CloudflareEmailProvider.ts","../../../src/email/cloudflare/index.ts"],"mappings":";;;;;;;;;cAca;;;;;;UAyBI;EACf,KAAK,SAAS,6BAA6B,QAAQ;;UAGpC;EACf;EACA;IAAiB;IAAe;;EAChC;EACA;EACA;EACA;EACA;EACA;EACA,UAAU;;UAGK;EACf;EACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA8BW,mCAAmC;qBAC3B,QAAM;qBACN;;;qBACA,6BAAG;YAEZ,UAAU;qBAED,0BAAO;EAkCb,KAAK,SAAS,mBAAmB;;;;;;;;;;;;YAwDpC,YACR;IACY;IAAe;;YAUnB,cAAc;;;;;;;;;;;cCtLb,wCAAqB,yBAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $env, $hook, $inject, $module, Alepha, AlephaError,
|
|
1
|
+
import { $env, $hook, $inject, $module, Alepha, AlephaError, z } from "alepha";
|
|
2
2
|
import { AlephaEmail, EmailError, EmailProvider } from "alepha/email";
|
|
3
3
|
import { $logger } from "alepha/logger";
|
|
4
4
|
//#region ../../src/email/cloudflare/providers/CloudflareEmailProvider.ts
|
|
@@ -18,7 +18,7 @@ const SEND_EMAIL_DEFAULT_BINDING = "SEND_EMAIL";
|
|
|
18
18
|
* set it. `send()` re-checks at call time and throws a clear error if
|
|
19
19
|
* it's missing then.
|
|
20
20
|
*/
|
|
21
|
-
const envSchema =
|
|
21
|
+
const envSchema = z.object({ EMAIL_FROM: z.text({ description: "Default sender (a verified sender address). Accepts a bare address or an RFC 5322 display-name form, e.g. `Lore <noreply@lore.alepha.dev>`." }).optional() });
|
|
22
22
|
/**
|
|
23
23
|
* Email provider using Cloudflare's Email Sending API via a Workers binding.
|
|
24
24
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/email/cloudflare/providers/CloudflareEmailProvider.ts","../../../src/email/cloudflare/index.ts"],"sourcesContent":["import { $env, $hook, $inject, Alepha, AlephaError, t } from \"alepha\";\nimport {\n EmailError,\n type EmailProvider,\n type EmailSendOptions,\n} from \"alepha/email\";\nimport { $logger } from \"alepha/logger\";\n\n/**\n * Default Cloudflare Email Sending binding name.\n *\n * Matches the convention used by other Alepha Cloudflare providers\n * (e.g. `KV_CACHE` for {@link CloudflareKVProvider}).\n */\nexport const SEND_EMAIL_DEFAULT_BINDING = \"SEND_EMAIL\";\n\n/**\n * Environment variables for Cloudflare email configuration.\n *\n * `EMAIL_FROM` is `optional` at the schema level so the provider can be\n * registered (and constructed) in non-Workers contexts — Node `yarn\n * start`, build-time introspection, etc. — without forcing every dev to\n * set it. `send()` re-checks at call time and throws a clear error if\n * it's missing then.\n */\nconst envSchema = t.object({\n EMAIL_FROM: t.optional(\n t.text({\n description:\n \"Default sender (a verified sender address). Accepts a bare address or an RFC 5322 display-name form, e.g. `Lore <noreply@lore.alepha.dev>`.\",\n }),\n ),\n});\n\n/**\n * Shape of the Cloudflare Email Sending binding (public beta, 2026-04-16).\n *\n * @see https://developers.cloudflare.com/email-service/\n */\nexport interface CloudflareEmailBinding {\n send(message: CloudflareEmailSendMessage): Promise<CloudflareEmailSendResult>;\n}\n\nexport interface CloudflareEmailSendMessage {\n to: string | string[];\n from: string | { email: string; name?: string };\n subject: string;\n html?: string;\n text?: string;\n cc?: string | string[];\n bcc?: string | string[];\n reply_to?: string | string[];\n headers?: Record<string, string>;\n}\n\nexport interface CloudflareEmailSendResult {\n id?: string;\n status?: \"queued\" | \"sent\" | \"bounced\" | string;\n}\n\n/**\n * Email provider using Cloudflare's Email Sending API via a Workers binding.\n *\n * Requires the Workers Paid plan and a verified sender address on the\n * `EMAIL_FROM` domain.\n *\n * **Required Cloudflare binding:**\n * - `SEND_EMAIL` — an Email Sending binding in wrangler configuration\n *\n * Configuration is provided via environment variables:\n * - `EMAIL_FROM`: Default sender email address\n *\n * @example\n * ```toml\n * # wrangler.toml\n * [[send_email]]\n * binding = \"SEND_EMAIL\"\n * ```\n *\n * @example\n * ```typescript\n * // app.ts\n * import { AlephaEmailCloudflare } from \"alepha/email/cloudflare\";\n *\n * const app = Alepha.create().with(AlephaEmailCloudflare);\n * ```\n */\nexport class CloudflareEmailProvider implements EmailProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly env = $env(envSchema);\n protected readonly log = $logger();\n\n protected binding?: CloudflareEmailBinding;\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: () => {\n // Tolerate boot off-Workers. The provider has to be registered\n // unconditionally so the CF build task can see it and emit the\n // `send_email` binding into wrangler.jsonc — but actually starting\n // on Node (yarn start / yarn dev) would crash because no binding\n // is wired. Treat that as \"inert provider\": warn, don't throw.\n // `send()` later will surface the real error if it's ever called.\n const cloudflareEnv = this.alepha.get(\"cloudflare.env\") as\n | Record<string, unknown>\n | undefined;\n if (!cloudflareEnv) {\n this.log.warn(\n \"Cloudflare Email Sending inert: 'cloudflare.env' not set (not running on Workers).\",\n );\n return;\n }\n\n const binding = cloudflareEnv[SEND_EMAIL_DEFAULT_BINDING] as\n | CloudflareEmailBinding\n | undefined;\n if (!binding) {\n this.log.warn(\n `Cloudflare Email Sending inert: binding '${SEND_EMAIL_DEFAULT_BINDING}' not found in Workers environment.`,\n );\n return;\n }\n\n this.binding = binding;\n this.log.info(\"Cloudflare Email Sending OK\");\n },\n });\n\n public async send(options: EmailSendOptions): Promise<void> {\n const { to, subject, body } = options;\n if (!this.env.EMAIL_FROM) {\n throw new EmailError(\n \"Cannot send email via Cloudflare: EMAIL_FROM env var is not set.\",\n );\n }\n this.log.info(\"Sending email via Cloudflare\", { to, subject });\n\n const message: CloudflareEmailSendMessage = {\n to: Array.isArray(to) ? to : [to],\n from: this.resolveFrom(this.env.EMAIL_FROM),\n subject,\n html: body,\n };\n\n try {\n const result = await this.getBinding().send(message);\n\n if (result?.status === \"bounced\") {\n throw new EmailError(\n `Cloudflare email bounced (id=${result.id ?? \"unknown\"})`,\n );\n }\n\n this.log.info(\"Email sent successfully via Cloudflare\", {\n to,\n subject,\n id: result?.id,\n status: result?.status,\n });\n } catch (error) {\n if (error instanceof EmailError) {\n throw error;\n }\n const status = (error as { status?: number })?.status;\n const message =\n status === 429\n ? `Cloudflare email rate limit hit (429): ${error instanceof Error ? error.message : String(error)}`\n : `Failed to send email via Cloudflare: ${error instanceof Error ? error.message : String(error)}`;\n this.log.error(message, { to, subject });\n throw new EmailError(message, error instanceof Error ? error : undefined);\n }\n }\n\n /**\n * Map the configured `EMAIL_FROM` to Cloudflare's `from` shape. An RFC 5322\n * display-name form — `Name <addr@host>` (the name may be quoted) — is split\n * into `{ email, name }` so Cloudflare renders the sender's display name\n * (e.g. `Lore <noreply@lore.alepha.dev>` → \"Lore\"). A bare address is passed\n * through unchanged.\n *\n * The object key is `email` (not `address`) — that is the field the\n * Cloudflare Email Service Workers API expects. Sending `{ address, name }`\n * makes the API reject the message with an opaque \"internal error\".\n */\n protected resolveFrom(\n value: string,\n ): string | { email: string; name?: string } {\n const match = value.match(/^\\s*(.*?)\\s*<([^>]+)>\\s*$/);\n if (!match) {\n return value.trim();\n }\n const name = match[1].replace(/^\"|\"$/g, \"\").trim();\n const email = match[2].trim();\n return name ? { email, name } : { email };\n }\n\n protected getBinding(): CloudflareEmailBinding {\n if (!this.binding) {\n throw new AlephaError(\n \"Cloudflare Email binding not initialized. Call start() first.\",\n );\n }\n return this.binding;\n }\n}\n","import { $module, type Alepha } from \"alepha\";\nimport { AlephaEmail, EmailProvider } from \"alepha/email\";\nimport { CloudflareEmailProvider } from \"./providers/CloudflareEmailProvider.ts\";\n\n// Exports\nexport * from \"./providers/CloudflareEmailProvider.ts\";\n\n/**\n * Plugin for Alepha Email that sends through Cloudflare's Email Sending API\n * via a Workers binding.\n *\n * @see {@link CloudflareEmailProvider}\n * @module alepha.email.cloudflare\n */\nexport const AlephaEmailCloudflare = $module({\n name: \"alepha.email.cloudflare\",\n services: [CloudflareEmailProvider],\n // Gate the substitution on `isServerless()` so apps can register this\n // module unconditionally — the build path needs to see the service to\n // emit the wrangler `send_email` binding, but the runtime substitution\n // must only kick in on Workers. Off-Workers we fall through to\n // AlephaEmail's defaults (LocalEmailProvider for dev, MemoryEmailProvider\n // for tests), so `yarn start` / `yarn dev` / e2e keep working without\n // a real binding.\n register: (alepha: Alepha) => {\n if (alepha.isServerless()) {\n alepha.with({\n optional: true,\n provide: EmailProvider,\n use: CloudflareEmailProvider,\n });\n }\n return alepha.with(AlephaEmail);\n },\n});\n"],"mappings":";;;;;;;;;;AAcA,MAAa,6BAA6B;;;;;;;;;;AAW1C,MAAM,YAAY,EAAE,OAAO,EACzB,YAAY,EAAE,SACZ,EAAE,KAAK,EACL,aACE,8IACJ,CAAC,CACH,EACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuDD,IAAa,0BAAb,MAA8D;CAC5D,SAA4B,QAAQ,MAAM;CAC1C,MAAyB,KAAK,SAAS;CACvC,MAAyB,QAAQ;CAEjC;CAEA,UAA6B,MAAM;EACjC,IAAI;EACJ,eAAe;GAOb,MAAM,gBAAgB,KAAK,OAAO,IAAI,gBAAgB;GAGtD,IAAI,CAAC,eAAe;IAClB,KAAK,IAAI,KACP,oFACF;IACA;GACF;GAEA,MAAM,UAAU,cAAc;GAG9B,IAAI,CAAC,SAAS;IACZ,KAAK,IAAI,KACP,4CAA4C,2BAA2B,oCACzE;IACA;GACF;GAEA,KAAK,UAAU;GACf,KAAK,IAAI,KAAK,6BAA6B;EAC7C;CACF,CAAC;CAED,MAAa,KAAK,SAA0C;EAC1D,MAAM,EAAE,IAAI,SAAS,SAAS;EAC9B,IAAI,CAAC,KAAK,IAAI,YACZ,MAAM,IAAI,WACR,kEACF;EAEF,KAAK,IAAI,KAAK,gCAAgC;GAAE;GAAI;EAAQ,CAAC;EAE7D,MAAM,UAAsC;GAC1C,IAAI,MAAM,QAAQ,EAAE,IAAI,KAAK,CAAC,EAAE;GAChC,MAAM,KAAK,YAAY,KAAK,IAAI,UAAU;GAC1C;GACA,MAAM;EACR;EAEA,IAAI;GACF,MAAM,SAAS,MAAM,KAAK,WAAW,EAAE,KAAK,OAAO;GAEnD,IAAI,QAAQ,WAAW,WACrB,MAAM,IAAI,WACR,gCAAgC,OAAO,MAAM,UAAU,EACzD;GAGF,KAAK,IAAI,KAAK,0CAA0C;IACtD;IACA;IACA,IAAI,QAAQ;IACZ,QAAQ,QAAQ;GAClB,CAAC;EACH,SAAS,OAAO;GACd,IAAI,iBAAiB,YACnB,MAAM;GAGR,MAAM,UADU,OAA+B,WAElC,MACP,0CAA0C,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,MAC/F,wCAAwC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GACnG,KAAK,IAAI,MAAM,SAAS;IAAE;IAAI;GAAQ,CAAC;GACvC,MAAM,IAAI,WAAW,SAAS,iBAAiB,QAAQ,QAAQ,KAAA,CAAS;EAC1E;CACF;;;;;;;;;;;;CAaA,YACE,OAC2C;EAC3C,MAAM,QAAQ,MAAM,MAAM,2BAA2B;EACrD,IAAI,CAAC,OACH,OAAO,MAAM,KAAK;EAEpB,MAAM,OAAO,MAAM,GAAG,QAAQ,UAAU,EAAE,EAAE,KAAK;EACjD,MAAM,QAAQ,MAAM,GAAG,KAAK;EAC5B,OAAO,OAAO;GAAE;GAAO;EAAK,IAAI,EAAE,MAAM;CAC1C;CAEA,aAA+C;EAC7C,IAAI,CAAC,KAAK,SACR,MAAM,IAAI,YACR,+DACF;EAEF,OAAO,KAAK;CACd;AACF;;;;;;;;;;AC9LA,MAAa,wBAAwB,QAAQ;CAC3C,MAAM;CACN,UAAU,CAAC,uBAAuB;CAQlC,WAAW,WAAmB;EAC5B,IAAI,OAAO,aAAa,GACtB,OAAO,KAAK;GACV,UAAU;GACV,SAAS;GACT,KAAK;EACP,CAAC;EAEH,OAAO,OAAO,KAAK,WAAW;CAChC;AACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/email/cloudflare/providers/CloudflareEmailProvider.ts","../../../src/email/cloudflare/index.ts"],"sourcesContent":["import { $env, $hook, $inject, Alepha, AlephaError, z } from \"alepha\";\nimport {\n EmailError,\n type EmailProvider,\n type EmailSendOptions,\n} from \"alepha/email\";\nimport { $logger } from \"alepha/logger\";\n\n/**\n * Default Cloudflare Email Sending binding name.\n *\n * Matches the convention used by other Alepha Cloudflare providers\n * (e.g. `KV_CACHE` for {@link CloudflareKVProvider}).\n */\nexport const SEND_EMAIL_DEFAULT_BINDING = \"SEND_EMAIL\";\n\n/**\n * Environment variables for Cloudflare email configuration.\n *\n * `EMAIL_FROM` is `optional` at the schema level so the provider can be\n * registered (and constructed) in non-Workers contexts — Node `yarn\n * start`, build-time introspection, etc. — without forcing every dev to\n * set it. `send()` re-checks at call time and throws a clear error if\n * it's missing then.\n */\nconst envSchema = z.object({\n EMAIL_FROM: z\n .text({\n description:\n \"Default sender (a verified sender address). Accepts a bare address or an RFC 5322 display-name form, e.g. `Lore <noreply@lore.alepha.dev>`.\",\n })\n .optional(),\n});\n\n/**\n * Shape of the Cloudflare Email Sending binding (public beta, 2026-04-16).\n *\n * @see https://developers.cloudflare.com/email-service/\n */\nexport interface CloudflareEmailBinding {\n send(message: CloudflareEmailSendMessage): Promise<CloudflareEmailSendResult>;\n}\n\nexport interface CloudflareEmailSendMessage {\n to: string | string[];\n from: string | { email: string; name?: string };\n subject: string;\n html?: string;\n text?: string;\n cc?: string | string[];\n bcc?: string | string[];\n reply_to?: string | string[];\n headers?: Record<string, string>;\n}\n\nexport interface CloudflareEmailSendResult {\n id?: string;\n status?: \"queued\" | \"sent\" | \"bounced\" | string;\n}\n\n/**\n * Email provider using Cloudflare's Email Sending API via a Workers binding.\n *\n * Requires the Workers Paid plan and a verified sender address on the\n * `EMAIL_FROM` domain.\n *\n * **Required Cloudflare binding:**\n * - `SEND_EMAIL` — an Email Sending binding in wrangler configuration\n *\n * Configuration is provided via environment variables:\n * - `EMAIL_FROM`: Default sender email address\n *\n * @example\n * ```toml\n * # wrangler.toml\n * [[send_email]]\n * binding = \"SEND_EMAIL\"\n * ```\n *\n * @example\n * ```typescript\n * // app.ts\n * import { AlephaEmailCloudflare } from \"alepha/email/cloudflare\";\n *\n * const app = Alepha.create().with(AlephaEmailCloudflare);\n * ```\n */\nexport class CloudflareEmailProvider implements EmailProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly env = $env(envSchema);\n protected readonly log = $logger();\n\n protected binding?: CloudflareEmailBinding;\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: () => {\n // Tolerate boot off-Workers. The provider has to be registered\n // unconditionally so the CF build task can see it and emit the\n // `send_email` binding into wrangler.jsonc — but actually starting\n // on Node (yarn start / yarn dev) would crash because no binding\n // is wired. Treat that as \"inert provider\": warn, don't throw.\n // `send()` later will surface the real error if it's ever called.\n const cloudflareEnv = this.alepha.get(\"cloudflare.env\") as\n | Record<string, unknown>\n | undefined;\n if (!cloudflareEnv) {\n this.log.warn(\n \"Cloudflare Email Sending inert: 'cloudflare.env' not set (not running on Workers).\",\n );\n return;\n }\n\n const binding = cloudflareEnv[SEND_EMAIL_DEFAULT_BINDING] as\n | CloudflareEmailBinding\n | undefined;\n if (!binding) {\n this.log.warn(\n `Cloudflare Email Sending inert: binding '${SEND_EMAIL_DEFAULT_BINDING}' not found in Workers environment.`,\n );\n return;\n }\n\n this.binding = binding;\n this.log.info(\"Cloudflare Email Sending OK\");\n },\n });\n\n public async send(options: EmailSendOptions): Promise<void> {\n const { to, subject, body } = options;\n if (!this.env.EMAIL_FROM) {\n throw new EmailError(\n \"Cannot send email via Cloudflare: EMAIL_FROM env var is not set.\",\n );\n }\n this.log.info(\"Sending email via Cloudflare\", { to, subject });\n\n const message: CloudflareEmailSendMessage = {\n to: Array.isArray(to) ? to : [to],\n from: this.resolveFrom(this.env.EMAIL_FROM),\n subject,\n html: body,\n };\n\n try {\n const result = await this.getBinding().send(message);\n\n if (result?.status === \"bounced\") {\n throw new EmailError(\n `Cloudflare email bounced (id=${result.id ?? \"unknown\"})`,\n );\n }\n\n this.log.info(\"Email sent successfully via Cloudflare\", {\n to,\n subject,\n id: result?.id,\n status: result?.status,\n });\n } catch (error) {\n if (error instanceof EmailError) {\n throw error;\n }\n const status = (error as { status?: number })?.status;\n const message =\n status === 429\n ? `Cloudflare email rate limit hit (429): ${error instanceof Error ? error.message : String(error)}`\n : `Failed to send email via Cloudflare: ${error instanceof Error ? error.message : String(error)}`;\n this.log.error(message, { to, subject });\n throw new EmailError(message, error instanceof Error ? error : undefined);\n }\n }\n\n /**\n * Map the configured `EMAIL_FROM` to Cloudflare's `from` shape. An RFC 5322\n * display-name form — `Name <addr@host>` (the name may be quoted) — is split\n * into `{ email, name }` so Cloudflare renders the sender's display name\n * (e.g. `Lore <noreply@lore.alepha.dev>` → \"Lore\"). A bare address is passed\n * through unchanged.\n *\n * The object key is `email` (not `address`) — that is the field the\n * Cloudflare Email Service Workers API expects. Sending `{ address, name }`\n * makes the API reject the message with an opaque \"internal error\".\n */\n protected resolveFrom(\n value: string,\n ): string | { email: string; name?: string } {\n const match = value.match(/^\\s*(.*?)\\s*<([^>]+)>\\s*$/);\n if (!match) {\n return value.trim();\n }\n const name = match[1].replace(/^\"|\"$/g, \"\").trim();\n const email = match[2].trim();\n return name ? { email, name } : { email };\n }\n\n protected getBinding(): CloudflareEmailBinding {\n if (!this.binding) {\n throw new AlephaError(\n \"Cloudflare Email binding not initialized. Call start() first.\",\n );\n }\n return this.binding;\n }\n}\n","import { $module, type Alepha } from \"alepha\";\nimport { AlephaEmail, EmailProvider } from \"alepha/email\";\nimport { CloudflareEmailProvider } from \"./providers/CloudflareEmailProvider.ts\";\n\n// Exports\nexport * from \"./providers/CloudflareEmailProvider.ts\";\n\n/**\n * Plugin for Alepha Email that sends through Cloudflare's Email Sending API\n * via a Workers binding.\n *\n * @see {@link CloudflareEmailProvider}\n * @module alepha.email.cloudflare\n */\nexport const AlephaEmailCloudflare = $module({\n name: \"alepha.email.cloudflare\",\n services: [CloudflareEmailProvider],\n // Gate the substitution on `isServerless()` so apps can register this\n // module unconditionally — the build path needs to see the service to\n // emit the wrangler `send_email` binding, but the runtime substitution\n // must only kick in on Workers. Off-Workers we fall through to\n // AlephaEmail's defaults (LocalEmailProvider for dev, MemoryEmailProvider\n // for tests), so `yarn start` / `yarn dev` / e2e keep working without\n // a real binding.\n register: (alepha: Alepha) => {\n if (alepha.isServerless()) {\n alepha.with({\n optional: true,\n provide: EmailProvider,\n use: CloudflareEmailProvider,\n });\n }\n return alepha.with(AlephaEmail);\n },\n});\n"],"mappings":";;;;;;;;;;AAcA,MAAa,6BAA6B;;;;;;;;;;AAW1C,MAAM,YAAY,EAAE,OAAO,EACzB,YAAY,EACT,KAAK,EACJ,aACE,8IACJ,CAAC,CAAC,CACD,SAAS,EACd,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuDD,IAAa,0BAAb,MAA8D;CAC5D,SAA4B,QAAQ,MAAM;CAC1C,MAAyB,KAAK,SAAS;CACvC,MAAyB,QAAQ;CAEjC;CAEA,UAA6B,MAAM;EACjC,IAAI;EACJ,eAAe;GAOb,MAAM,gBAAgB,KAAK,OAAO,IAAI,gBAAgB;GAGtD,IAAI,CAAC,eAAe;IAClB,KAAK,IAAI,KACP,oFACF;IACA;GACF;GAEA,MAAM,UAAU,cAAc;GAG9B,IAAI,CAAC,SAAS;IACZ,KAAK,IAAI,KACP,4CAA4C,2BAA2B,oCACzE;IACA;GACF;GAEA,KAAK,UAAU;GACf,KAAK,IAAI,KAAK,6BAA6B;EAC7C;CACF,CAAC;CAED,MAAa,KAAK,SAA0C;EAC1D,MAAM,EAAE,IAAI,SAAS,SAAS;EAC9B,IAAI,CAAC,KAAK,IAAI,YACZ,MAAM,IAAI,WACR,kEACF;EAEF,KAAK,IAAI,KAAK,gCAAgC;GAAE;GAAI;EAAQ,CAAC;EAE7D,MAAM,UAAsC;GAC1C,IAAI,MAAM,QAAQ,EAAE,IAAI,KAAK,CAAC,EAAE;GAChC,MAAM,KAAK,YAAY,KAAK,IAAI,UAAU;GAC1C;GACA,MAAM;EACR;EAEA,IAAI;GACF,MAAM,SAAS,MAAM,KAAK,WAAW,CAAC,CAAC,KAAK,OAAO;GAEnD,IAAI,QAAQ,WAAW,WACrB,MAAM,IAAI,WACR,gCAAgC,OAAO,MAAM,UAAU,EACzD;GAGF,KAAK,IAAI,KAAK,0CAA0C;IACtD;IACA;IACA,IAAI,QAAQ;IACZ,QAAQ,QAAQ;GAClB,CAAC;EACH,SAAS,OAAO;GACd,IAAI,iBAAiB,YACnB,MAAM;GAGR,MAAM,UADU,OAA+B,WAElC,MACP,0CAA0C,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,MAC/F,wCAAwC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GACnG,KAAK,IAAI,MAAM,SAAS;IAAE;IAAI;GAAQ,CAAC;GACvC,MAAM,IAAI,WAAW,SAAS,iBAAiB,QAAQ,QAAQ,KAAA,CAAS;EAC1E;CACF;;;;;;;;;;;;CAaA,YACE,OAC2C;EAC3C,MAAM,QAAQ,MAAM,MAAM,2BAA2B;EACrD,IAAI,CAAC,OACH,OAAO,MAAM,KAAK;EAEpB,MAAM,OAAO,MAAM,EAAE,CAAC,QAAQ,UAAU,EAAE,CAAC,CAAC,KAAK;EACjD,MAAM,QAAQ,MAAM,EAAE,CAAC,KAAK;EAC5B,OAAO,OAAO;GAAE;GAAO;EAAK,IAAI,EAAE,MAAM;CAC1C;CAEA,aAA+C;EAC7C,IAAI,CAAC,KAAK,SACR,MAAM,IAAI,YACR,+DACF;EAEF,OAAO,KAAK;CACd;AACF;;;;;;;;;;AC9LA,MAAa,wBAAwB,QAAQ;CAC3C,MAAM;CACN,UAAU,CAAC,uBAAuB;CAQlC,WAAW,WAAmB;EAC5B,IAAI,OAAO,aAAa,GACtB,OAAO,KAAK;GACV,UAAU;GACV,SAAS;GACT,KAAK;EACP,CAAC;EAEH,OAAO,OAAO,KAAK,WAAW;CAChC;AACF,CAAC"}
|