alepha 0.21.2 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.d.ts +393 -403
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +25 -56
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.browser.js +31 -1
- package/dist/api/files/index.browser.js.map +1 -1
- package/dist/api/files/index.d.ts +313 -208
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +152 -42
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +2 -2
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +289 -292
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +39 -33
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +211 -216
- package/dist/api/keys/index.d.ts.map +1 -1
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +188 -195
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/oauth/index.d.ts +71 -76
- package/dist/api/oauth/index.d.ts.map +1 -1
- package/dist/api/oauth/index.js.map +1 -1
- package/dist/api/organizations/index.browser.js.map +1 -1
- package/dist/api/organizations/index.d.ts +104 -109
- package/dist/api/organizations/index.d.ts.map +1 -1
- package/dist/api/organizations/index.js.map +1 -1
- package/dist/api/parameters/index.browser.js +43 -16
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +488 -344
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +175 -35
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/payments/index.d.ts +396 -402
- package/dist/api/payments/index.d.ts.map +1 -1
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/subscriptions/index.d.ts +644 -652
- package/dist/api/subscriptions/index.d.ts.map +1 -1
- package/dist/api/subscriptions/index.js +1 -1
- package/dist/api/subscriptions/index.js.map +1 -1
- package/dist/api/users/index.browser.js +7 -0
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +1073 -1006
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +283 -61
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.browser.js.map +1 -1
- package/dist/api/verifications/index.d.ts +134 -140
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/background/index.d.ts +95 -0
- package/dist/background/index.d.ts.map +1 -0
- package/dist/background/index.js +121 -0
- package/dist/background/index.js.map +1 -0
- package/dist/background/index.workerd.js +110 -0
- package/dist/background/index.workerd.js.map +1 -0
- package/dist/batch/index.d.ts +5 -7
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/bin/index.js.map +1 -1
- package/dist/bucket/index.d.ts +76 -54
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +58 -11
- package/dist/bucket/index.js.map +1 -1
- package/dist/bucket/index.workerd.js +200 -5
- package/dist/bucket/index.workerd.js.map +1 -1
- package/dist/cache/core/index.d.ts +7 -10
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/cache/database/index.d.ts +22 -26
- package/dist/cache/database/index.d.ts.map +1 -1
- package/dist/cache/database/index.js.map +1 -1
- package/dist/cache/redis/index.d.ts +4 -7
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/captcha/index.d.ts +3 -6
- package/dist/captcha/index.d.ts.map +1 -1
- package/dist/captcha/index.js.map +1 -1
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/config/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +417 -214
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +325 -563
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +3 -5
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/i18n/index.d.ts +8 -12
- package/dist/cli/i18n/index.d.ts.map +1 -1
- package/dist/cli/i18n/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +126 -1342
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +136 -2374
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/platform-lib/index.d.ts +1446 -0
- package/dist/cli/platform-lib/index.d.ts.map +1 -0
- package/dist/cli/platform-lib/index.js +2597 -0
- package/dist/cli/platform-lib/index.js.map +1 -0
- package/dist/cli/vendor/index.d.ts +17 -21
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/command/index.d.ts +21 -20
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +39 -10
- package/dist/command/index.js.map +1 -1
- package/dist/{containers → container}/core/index.d.ts +13 -15
- package/dist/container/core/index.d.ts.map +1 -0
- package/dist/{containers → container}/core/index.js +23 -14
- package/dist/container/core/index.js.map +1 -0
- package/dist/{containers → container}/core/index.workerd.js +37 -22
- package/dist/container/core/index.workerd.js.map +1 -0
- package/dist/core/index.browser.js +27 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +48 -24
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +27 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +27 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +27 -1
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/crypto/index.browser.js.map +1 -1
- package/dist/crypto/index.d.ts +5 -8
- package/dist/crypto/index.d.ts.map +1 -1
- package/dist/crypto/index.js.map +1 -1
- package/dist/datetime/index.d.ts +3 -4
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/brevo/index.d.ts +2 -4
- package/dist/email/brevo/index.d.ts.map +1 -1
- package/dist/email/brevo/index.js.map +1 -1
- package/dist/email/cloudflare/index.d.ts +20 -7
- package/dist/email/cloudflare/index.d.ts.map +1 -1
- package/dist/email/cloudflare/index.js +46 -9
- package/dist/email/cloudflare/index.js.map +1 -1
- package/dist/email/core/index.d.ts +6 -9
- package/dist/email/core/index.d.ts.map +1 -1
- package/dist/email/core/index.js.map +1 -1
- package/dist/email/core/index.workerd.js.map +1 -1
- package/dist/email/smtp/index.d.ts +10 -13
- package/dist/email/smtp/index.d.ts.map +1 -1
- package/dist/email/smtp/index.js +107 -32
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/fake/index.d.ts +1 -2
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +9 -14
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js.map +1 -1
- package/dist/lock/redis/index.d.ts +2 -4
- package/dist/lock/redis/index.d.ts.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.d.ts +105 -76
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +196 -174
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +16 -20
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +19 -1
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +76 -62
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +20 -2
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +28 -20
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +12 -15
- package/dist/queue/core/index.d.ts.map +1 -1
- package/dist/queue/core/index.js.map +1 -1
- package/dist/queue/core/index.workerd.js.map +1 -1
- package/dist/queue/redis/index.d.ts +3 -5
- package/dist/queue/redis/index.d.ts.map +1 -1
- package/dist/queue/redis/index.js.map +1 -1
- package/dist/react/auth/index.browser.js +9 -2
- package/dist/react/auth/index.browser.js.map +1 -1
- package/dist/react/auth/index.d.ts +14 -9
- package/dist/react/auth/index.d.ts.map +1 -1
- package/dist/react/auth/index.js +9 -2
- package/dist/react/auth/index.js.map +1 -1
- package/dist/react/core/index.d.ts +7 -8
- package/dist/react/core/index.d.ts.map +1 -1
- package/dist/react/core/index.js +6 -3
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/form/index.d.ts +2 -4
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/head/index.browser.js.map +1 -1
- package/dist/react/head/index.d.ts +2 -4
- package/dist/react/head/index.d.ts.map +1 -1
- package/dist/react/head/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +47 -11
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/i18n/index.js +33 -1
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.d.ts +1 -2
- package/dist/react/intro/index.d.ts.map +1 -1
- package/dist/react/intro/index.js +2 -2
- package/dist/react/intro/index.js.map +1 -1
- package/dist/react/router/index.browser.js +65 -19
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +327 -222
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +65 -29
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/testing/index.d.ts +1 -2
- package/dist/react/testing/index.d.ts.map +1 -1
- package/dist/react/testing/index.js +16 -17
- package/dist/react/testing/index.js.map +1 -1
- package/dist/react/ui/index.d.ts +20 -25
- package/dist/react/ui/index.d.ts.map +1 -1
- package/dist/react/ui/index.js.map +1 -1
- package/dist/redis/index.bun.js.map +1 -1
- package/dist/redis/index.d.ts +17 -19
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts +2 -4
- package/dist/retry/index.d.ts.map +1 -1
- package/dist/retry/index.js.map +1 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +10 -13
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/index.workerd.js.map +1 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +45 -48
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.browser.js.map +1 -1
- package/dist/server/auth/index.d.ts +167 -172
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +4 -8
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cookies/index.browser.js.map +1 -1
- package/dist/server/cookies/index.d.ts +5 -7
- package/dist/server/cookies/index.d.ts.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +88 -73
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +19 -0
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +11 -14
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/cors/index.js.map +1 -1
- package/dist/server/etag/index.d.ts +6 -9
- package/dist/server/etag/index.d.ts.map +1 -1
- package/dist/server/etag/index.js.map +1 -1
- package/dist/server/health/index.d.ts +18 -21
- package/dist/server/health/index.d.ts.map +1 -1
- package/dist/server/health/index.js.map +1 -1
- package/dist/server/links/index.browser.js +2 -0
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +63 -67
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +2 -0
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +5 -7
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/proxy/index.d.ts +3 -5
- package/dist/server/proxy/index.d.ts.map +1 -1
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.d.ts +10 -13
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.d.ts +3 -5
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +5 -8
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +3 -5
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.d.ts +2 -4
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js.map +1 -1
- package/dist/system/index.workerd.js.map +1 -1
- package/dist/topic/core/index.d.ts +4 -6
- package/dist/topic/core/index.d.ts.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/topic/redis/index.d.ts +5 -8
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/dist/topic/redis/index.js.map +1 -1
- package/package.json +45 -22
- package/src/api/audits/__tests__/AuditService.spec.ts +18 -110
- package/src/api/audits/controllers/AdminAuditController.ts +14 -0
- package/src/api/audits/services/AuditService.ts +21 -88
- package/src/api/files/__tests__/FileService.spec.ts +207 -2
- package/src/api/files/index.ts +3 -0
- package/src/api/files/schemas/fileCreatorSummarySchema.ts +22 -0
- package/src/api/files/schemas/fileResourceSchema.ts +10 -1
- package/src/api/files/services/FileService.ts +170 -72
- package/src/api/jobs/__tests__/$job.spec.ts +24 -1
- package/src/api/jobs/index.ts +4 -3
- package/src/api/jobs/primitives/$job.ts +7 -3
- package/src/api/jobs/providers/DirectJobDispatcher.ts +17 -36
- package/src/api/jobs/providers/JobProvider.ts +53 -24
- package/src/api/jobs/schemas/jobConfigAtom.ts +1 -1
- package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +4 -1
- package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +3 -1
- package/src/api/parameters/__tests__/$parameter.spec.ts +19 -2
- package/src/api/parameters/audits/ParameterAudits.ts +17 -0
- package/src/api/parameters/controllers/AdminParameterController.ts +95 -19
- package/src/api/parameters/index.ts +3 -0
- package/src/api/parameters/schemas/activateParameterBodySchema.ts +3 -3
- package/src/api/parameters/schemas/createParameterVersionBodySchema.ts +3 -2
- package/src/api/parameters/schemas/parameterCreatorSummarySchema.ts +25 -0
- package/src/api/parameters/schemas/parameterResponseSchema.ts +5 -0
- package/src/api/parameters/schemas/rollbackParameterBodySchema.ts +4 -2
- package/src/api/parameters/services/ParameterProvider.ts +69 -6
- package/src/api/subscriptions/jobs/SubscriptionJobs.ts +1 -1
- package/src/api/users/__tests__/AdminSessionController.spec.ts +37 -0
- package/src/api/users/audits/SessionAudits.ts +33 -0
- package/src/api/users/audits/UserAudits.ts +19 -43
- package/src/api/users/controllers/AdminUserController.ts +66 -1
- package/src/api/users/entities/sessions.ts +6 -0
- package/src/api/users/entities/users.ts +2 -0
- package/src/api/users/index.ts +9 -1
- package/src/api/users/primitives/$realm.ts +3 -0
- package/src/api/users/schemas/sessionResourceSchema.ts +16 -0
- package/src/api/users/schemas/updateUserSchema.ts +1 -8
- package/src/api/users/schemas/userQuerySchema.ts +7 -0
- package/src/api/users/services/CredentialService.ts +15 -6
- package/src/api/users/services/IdentityService.ts +2 -1
- package/src/api/users/services/RegistrationService.ts +2 -1
- package/src/api/users/services/SessionCrudService.ts +19 -2
- package/src/api/users/services/SessionService.ts +39 -19
- package/src/api/users/services/UserService.ts +106 -8
- package/src/background/__tests__/BackgroundTaskProvider.spec.ts +96 -0
- package/src/background/index.ts +37 -0
- package/src/background/index.workerd.ts +28 -0
- package/src/background/providers/BackgroundTaskProvider.ts +70 -0
- package/src/background/providers/WorkerdBackgroundTaskProvider.ts +43 -0
- package/src/bucket/__tests__/$bucket.spec.ts +18 -0
- package/src/bucket/__tests__/LocalFileStorageProvider.spec.ts +5 -0
- package/src/bucket/__tests__/MemoryFileStorageProvider.spec.ts +5 -0
- package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +23 -4
- package/src/bucket/__tests__/shared.ts +30 -0
- package/src/bucket/index.ts +5 -5
- package/src/bucket/index.workerd.ts +11 -4
- package/src/bucket/primitives/$bucket.ts +27 -0
- package/src/bucket/providers/FileStorageProvider.ts +13 -0
- package/src/bucket/providers/LocalFileStorageProvider.ts +17 -1
- package/src/bucket/providers/MemoryFileStorageProvider.ts +7 -0
- package/src/bucket/providers/{CloudflareR2Provider.ts → R2FileStorageProvider.ts} +10 -1
- package/src/bucket/providers/{NodeS3BucketProvider.ts → S3FileStorageProvider.ts} +27 -5
- package/src/cli/core/__tests__/BuildDockerTask.spec.ts +25 -1
- package/src/cli/core/__tests__/init.spec.ts +0 -219
- package/src/cli/core/commands/__tests__/BuildCommand.spec.ts +43 -0
- package/src/cli/core/commands/build.ts +108 -30
- package/src/cli/core/commands/init.ts +0 -12
- package/src/cli/core/commands/pack.ts +133 -0
- package/src/cli/core/index.ts +3 -0
- package/src/cli/core/providers/ViteDevServerProvider.ts +40 -16
- package/src/cli/core/services/PackageManagerUtils.ts +0 -16
- package/src/cli/core/services/ProjectScaffolder.ts +29 -291
- package/src/cli/core/tasks/BuildCloudflareTask.ts +353 -47
- package/src/cli/core/tasks/BuildDockerTask.ts +33 -3
- package/src/cli/core/tasks/BuildTask.ts +34 -0
- package/src/cli/core/templates/apiIndexTs.ts +1 -22
- package/src/cli/core/templates/mainCss.ts +0 -1
- package/src/cli/core/templates/webAppRouterTs.ts +0 -99
- package/src/cli/core/templates/webIndexTs.ts +1 -22
- package/src/cli/platform/__tests__/SecretsCommand.spec.ts +5 -3
- package/src/cli/platform/commands/SecretsCommand.ts +8 -6
- package/src/cli/platform/commands/platform.ts +192 -46
- package/src/cli/platform/index.ts +12 -52
- package/src/cli/{platform → platform-lib}/__tests__/CloudflareAdapter.spec.ts +426 -169
- package/src/cli/{platform → platform-lib}/__tests__/NamingService.spec.ts +91 -4
- package/src/cli/{platform → platform-lib}/__tests__/VercelAdapter.spec.ts +56 -85
- package/src/cli/{platform → platform-lib}/adapters/CloudflareAdapter.ts +402 -165
- package/src/cli/{platform → platform-lib}/adapters/PlatformAdapter.ts +62 -35
- package/src/cli/{platform → platform-lib}/adapters/VercelAdapter.ts +6 -10
- package/src/cli/{platform → platform-lib}/atoms/platformOptions.ts +34 -1
- package/src/cli/platform-lib/index.ts +67 -0
- package/src/cli/platform-lib/services/NamingService.ts +136 -0
- package/src/cli/{platform → platform-lib}/services/PlatformInspector.ts +60 -13
- package/src/cli/{platform → platform-lib}/services/PlatformOrchestrator.ts +54 -43
- package/src/cli/{platform → platform-lib}/services/WranglerApi.ts +4 -2
- package/src/command/__tests__/Runner.spec.ts +20 -0
- package/src/command/helpers/EnvUtils.ts +19 -3
- package/src/command/helpers/Runner.ts +12 -2
- package/src/command/providers/CliProvider.ts +34 -1
- package/src/{containers → container}/core/__tests__/$container.spec.ts +5 -5
- package/src/{containers → container}/core/index.ts +4 -4
- package/src/{containers → container}/core/index.workerd.ts +19 -3
- package/src/{containers → container}/core/primitives/$container.ts +1 -1
- package/src/{containers → container}/core/providers/CloudflareContainerProvider.ts +17 -19
- package/src/{containers → container}/core/providers/ContainerProvider.ts +16 -2
- package/src/{containers → container}/core/providers/MockContainerProvider.ts +1 -1
- package/src/core/Alepha.ts +49 -1
- package/src/core/__tests__/$env.spec.ts +42 -0
- package/src/core/__tests__/dump.spec.ts +47 -0
- package/src/email/cloudflare/__tests__/CloudflareEmailProvider.spec.ts +42 -10
- package/src/email/cloudflare/index.ts +14 -5
- package/src/email/cloudflare/providers/CloudflareEmailProvider.ts +54 -9
- package/src/logger/__tests__/Logger.spec.ts +55 -0
- package/src/logger/index.ts +13 -0
- package/src/logger/services/Logger.ts +31 -1
- package/src/orm/__tests__/orm-showcase-tests.ts +27 -0
- package/src/orm/__tests__/orm-showcase.spec.ts +12 -0
- package/src/orm/core/interfaces/PgQuery.ts +4 -1
- package/src/orm/core/services/Repository.ts +27 -11
- package/src/react/auth/hooks/useAuth.ts +10 -5
- package/src/react/core/__tests__/useQuery.browser.spec.tsx +25 -0
- package/src/react/core/hooks/useAction.ts +14 -3
- package/src/react/core/hooks/useQuery.ts +24 -4
- package/src/react/i18n/components/Translate.tsx +47 -0
- package/src/react/i18n/index.ts +2 -0
- package/src/react/intro/components/GettingStartedAdminSlide.tsx +2 -2
- package/src/react/router/__tests__/$page.spec.tsx +3 -2
- package/src/react/router/__tests__/page-can.spec.ts +18 -13
- package/src/react/router/hooks/useQueryParams.ts +114 -14
- package/src/react/router/primitives/$page.ts +85 -4
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +3 -7
- package/src/react/router/providers/ReactServerProvider.ts +4 -13
- package/src/react/ui/services/SchemaControl.ts +3 -4
- package/src/server/core/providers/ServerMultipartProvider.ts +19 -0
- package/src/server/links/providers/LinkProvider.ts +10 -0
- package/dist/containers/core/index.d.ts.map +0 -1
- package/dist/containers/core/index.js.map +0 -1
- package/dist/containers/core/index.workerd.js.map +0 -1
- package/src/cli/core/templates/componentsJsonTs.ts +0 -39
- package/src/cli/core/templates/saasAdminLayoutTsx.ts +0 -77
- package/src/cli/core/templates/saasAdminPagesTsx.ts +0 -26
- package/src/cli/core/templates/saasAuthLayoutTsx.ts +0 -22
- package/src/cli/core/templates/saasAuthPagesTsx.ts +0 -62
- package/src/cli/core/templates/saasRealmProviderTs.ts +0 -52
- package/src/cli/platform/services/NamingService.ts +0 -54
- /package/dist/orm/core/{chunk-o8xxKEmq.js → chunk-B4FMCO8f.js} +0 -0
- /package/dist/react/testing/{chunk-6Ep1yQYe.js → chunk-BpyX8vjI.js} +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/GitHubSecretStore.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/PlatformCacheProvider.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/PlatformInspector.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/PlatformOrchestrator.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/SecretFilterService.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/detectResources.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/providers/GitHubSecretStore.ts +0 -0
- /package/src/cli/{platform → platform-lib}/providers/MemorySecretStore.ts +0 -0
- /package/src/cli/{platform → platform-lib}/providers/PlatformCacheProvider.ts +0 -0
- /package/src/cli/{platform → platform-lib}/providers/SecretStoreProvider.ts +0 -0
- /package/src/cli/{platform → platform-lib}/schemas/cloudflare.ts +0 -0
- /package/src/cli/{platform → platform-lib}/schemas/platform.ts +0 -0
- /package/src/cli/{platform → platform-lib}/schemas/vercel.ts +0 -0
- /package/src/cli/{platform → platform-lib}/services/CloudflareApi.ts +0 -0
- /package/src/cli/{platform → platform-lib}/services/SecretFilterService.ts +0 -0
- /package/src/cli/{platform → platform-lib}/services/VercelApi.ts +0 -0
- /package/src/cli/{platform → platform-lib}/services/VercelCli.ts +0 -0
- /package/src/{containers → container}/core/interfaces/ContainerOptions.ts +0 -0
- /package/src/{containers → container}/core/providers/NodeContainerProvider.ts +0 -0
|
@@ -11,10 +11,11 @@ import {
|
|
|
11
11
|
type DurationLike,
|
|
12
12
|
} from "alepha/datetime";
|
|
13
13
|
import { $logger } from "alepha/logger";
|
|
14
|
-
import { $repository, type Page } from "alepha/orm";
|
|
14
|
+
import { $repository, type Page, RepositoryProvider } from "alepha/orm";
|
|
15
15
|
import type { UserAccountToken } from "alepha/security";
|
|
16
16
|
import type { Ok } from "alepha/server";
|
|
17
17
|
import { NotFoundError } from "alepha/server";
|
|
18
|
+
import { FileSystemProvider } from "alepha/system";
|
|
18
19
|
import { type FileEntity, files } from "../entities/files.ts";
|
|
19
20
|
import type { FileQuery } from "../schemas/fileQuerySchema.ts";
|
|
20
21
|
import type { FileResource } from "../schemas/fileResourceSchema.ts";
|
|
@@ -24,9 +25,39 @@ export class FileService {
|
|
|
24
25
|
protected readonly alepha = $inject(Alepha);
|
|
25
26
|
protected readonly log = $logger();
|
|
26
27
|
protected readonly dateTimeProvider = $inject(DateTimeProvider);
|
|
28
|
+
protected readonly repositoryProvider = $inject(RepositoryProvider);
|
|
29
|
+
protected readonly fileSystem = $inject(FileSystemProvider);
|
|
27
30
|
protected readonly defaultBucket = $bucket({ name: "default" });
|
|
28
31
|
public readonly fileRepository = $repository(files);
|
|
29
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Best-effort left join embedding the uploader on every file row, so the
|
|
35
|
+
* admin UI can render `user.email` instead of the bare `creator` UUID.
|
|
36
|
+
* Joins `files.creator` → `users.id`. Only applied when the `users` table is
|
|
37
|
+
* actually registered in the app (see `findFiles`) — the files module stays
|
|
38
|
+
* usable standalone, without `alepha/api/users`. Targets the default `users`
|
|
39
|
+
* table, so creators in non-default realms come back unmatched (`user`
|
|
40
|
+
* undefined), which the UI handles by falling back to `creatorName`.
|
|
41
|
+
*
|
|
42
|
+
* The `users` entity is resolved from the repository registry at runtime
|
|
43
|
+
* rather than imported: the users module already depends on the files module
|
|
44
|
+
* (avatars), so a compile-time import here would form a circular dependency.
|
|
45
|
+
*/
|
|
46
|
+
protected resolveCreatorJoin() {
|
|
47
|
+
const usersEntity = this.repositoryProvider
|
|
48
|
+
.getRepositories()
|
|
49
|
+
.find((repo) => repo.entity.name === "users")?.entity;
|
|
50
|
+
if (!usersEntity) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
user: {
|
|
55
|
+
join: usersEntity,
|
|
56
|
+
on: ["creator", usersEntity.cols.id] as ["creator", { name: string }],
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
30
61
|
protected onUploadFile = $hook({
|
|
31
62
|
on: "bucket:file:uploaded",
|
|
32
63
|
handler: async ({ file, bucket, options, id }) => {
|
|
@@ -36,18 +67,20 @@ export class FileService {
|
|
|
36
67
|
|
|
37
68
|
const checksum = await this.calculateChecksum(file);
|
|
38
69
|
|
|
39
|
-
await this.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
70
|
+
await this.persistBlobMetadata(bucket, id, () =>
|
|
71
|
+
this.fileRepository.create({
|
|
72
|
+
blobId: id,
|
|
73
|
+
mimeType: file.type,
|
|
74
|
+
name: file.name,
|
|
75
|
+
originalName: file.name,
|
|
76
|
+
size: file.size,
|
|
77
|
+
creator: options.user?.id,
|
|
78
|
+
creatorRealm: options.user?.realm,
|
|
79
|
+
expirationDate: this.getExpirationDate(options.ttl),
|
|
80
|
+
bucket: bucket.name,
|
|
81
|
+
checksum,
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
51
84
|
},
|
|
52
85
|
});
|
|
53
86
|
|
|
@@ -66,15 +99,28 @@ export class FileService {
|
|
|
66
99
|
/**
|
|
67
100
|
* Calculates SHA-256 checksum of a file.
|
|
68
101
|
*
|
|
102
|
+
* Reads the whole file into memory. Callers that already hold the bytes
|
|
103
|
+
* should use {@link hashBuffer} instead to avoid re-reading — re-reading a
|
|
104
|
+
* one-shot stream either yields the wrong hash or drains a stream another
|
|
105
|
+
* step still needs.
|
|
106
|
+
*
|
|
69
107
|
* @param file - The file to calculate checksum for
|
|
70
108
|
* @returns Hexadecimal string representation of the SHA-256 hash
|
|
71
109
|
* @protected
|
|
72
110
|
*/
|
|
73
111
|
protected async calculateChecksum(file: FileLike): Promise<string> {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
112
|
+
return this.hashBuffer(await file.arrayBuffer());
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Calculates the SHA-256 checksum of an already-read body.
|
|
117
|
+
*
|
|
118
|
+
* @param data - The file bytes
|
|
119
|
+
* @returns Hexadecimal string representation of the SHA-256 hash
|
|
120
|
+
* @protected
|
|
121
|
+
*/
|
|
122
|
+
protected hashBuffer(data: ArrayBuffer): string {
|
|
123
|
+
return createHash("sha256").update(Buffer.from(data)).digest("hex");
|
|
78
124
|
}
|
|
79
125
|
|
|
80
126
|
/**
|
|
@@ -141,8 +187,17 @@ export class FileService {
|
|
|
141
187
|
where.createdAt = { lte: q.createdBefore };
|
|
142
188
|
}
|
|
143
189
|
|
|
190
|
+
// The creator join requires the `users` table. Only opt in when a users
|
|
191
|
+
// repository is registered (i.e. `alepha/api/users` is loaded) so the
|
|
192
|
+
// files module — and its standalone tests — keep working without it.
|
|
193
|
+
const withCreator = this.resolveCreatorJoin();
|
|
194
|
+
|
|
144
195
|
return await this.fileRepository
|
|
145
|
-
.paginate(
|
|
196
|
+
.paginate(
|
|
197
|
+
q,
|
|
198
|
+
{ where, ...(withCreator ? { with: withCreator } : {}) },
|
|
199
|
+
{ count: true },
|
|
200
|
+
)
|
|
146
201
|
.then((page) => {
|
|
147
202
|
return {
|
|
148
203
|
...page,
|
|
@@ -206,7 +261,20 @@ export class FileService {
|
|
|
206
261
|
): Promise<FileEntity> {
|
|
207
262
|
const bucket = this.bucket(options.bucket);
|
|
208
263
|
|
|
209
|
-
|
|
264
|
+
// Read the source exactly once. The checksum and the stored bytes are
|
|
265
|
+
// both derived from this single buffer, so a one-shot stream is never
|
|
266
|
+
// read twice — the previous code checksummed the file and then let
|
|
267
|
+
// bucket.upload read it again, which drained the stream and stored an
|
|
268
|
+
// empty blob. Uploads are size-capped (bucket maxSize / multipart
|
|
269
|
+
// limits), so buffering here is intentional and bounded.
|
|
270
|
+
const data = await file.arrayBuffer();
|
|
271
|
+
const checksum = this.hashBuffer(data);
|
|
272
|
+
file = this.fileSystem.createFile({
|
|
273
|
+
arrayBuffer: data,
|
|
274
|
+
name: file.name,
|
|
275
|
+
type: file.type,
|
|
276
|
+
});
|
|
277
|
+
|
|
210
278
|
const blobId = await bucket.upload(file, { persist: false });
|
|
211
279
|
|
|
212
280
|
let expirationDate: string | undefined;
|
|
@@ -218,20 +286,58 @@ export class FileService {
|
|
|
218
286
|
expirationDate = this.getExpirationDate(bucket.options.ttl);
|
|
219
287
|
}
|
|
220
288
|
|
|
221
|
-
return await this.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
289
|
+
return await this.persistBlobMetadata(bucket, blobId, () =>
|
|
290
|
+
this.fileRepository.create({
|
|
291
|
+
blobId: blobId,
|
|
292
|
+
mimeType: file.type,
|
|
293
|
+
name: file.name,
|
|
294
|
+
originalName: file.name,
|
|
295
|
+
size: file.size,
|
|
296
|
+
creator: options.user?.id,
|
|
297
|
+
creatorRealm: options.user?.realm,
|
|
298
|
+
creatorName: options.user?.name,
|
|
299
|
+
expirationDate,
|
|
300
|
+
bucket: bucket.name,
|
|
301
|
+
tags: options.tags,
|
|
302
|
+
checksum,
|
|
303
|
+
}),
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Persists the metadata row for an already-uploaded blob, deleting the
|
|
309
|
+
* blob if the insert fails. Uploads are not atomic: the blob is written to
|
|
310
|
+
* storage first (the row needs the returned `blobId`), so a failed insert
|
|
311
|
+
* would otherwise leak the blob — an orphaned blob with no DB row. This
|
|
312
|
+
* compensates by removing the blob, favouring the recoverable failure
|
|
313
|
+
* (a missing blob) over the worse one (a row pointing at nothing).
|
|
314
|
+
*
|
|
315
|
+
* Best-effort: cleanup runs with `skipHook` so it neither re-emits
|
|
316
|
+
* `bucket:file:deleted` nor touches the (non-existent) DB row, and a failed
|
|
317
|
+
* cleanup is logged rather than thrown. The original write error is always
|
|
318
|
+
* rethrown so callers still see the real failure.
|
|
319
|
+
*
|
|
320
|
+
* @param bucket - The bucket the blob was uploaded to
|
|
321
|
+
* @param blobId - The id returned by `bucket.upload`
|
|
322
|
+
* @param insert - Thunk performing the metadata insert
|
|
323
|
+
* @returns The created file entity
|
|
324
|
+
*/
|
|
325
|
+
protected async persistBlobMetadata(
|
|
326
|
+
bucket: BucketPrimitive,
|
|
327
|
+
blobId: string,
|
|
328
|
+
insert: () => Promise<FileEntity>,
|
|
329
|
+
): Promise<FileEntity> {
|
|
330
|
+
try {
|
|
331
|
+
return await insert();
|
|
332
|
+
} catch (error) {
|
|
333
|
+
await bucket.delete(blobId, true).catch((cleanupError) => {
|
|
334
|
+
this.log.warn(
|
|
335
|
+
`Failed to remove orphaned blob ${blobId} from bucket ${bucket.name} after a metadata write failure`,
|
|
336
|
+
cleanupError,
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
235
341
|
}
|
|
236
342
|
|
|
237
343
|
/**
|
|
@@ -388,50 +494,42 @@ export class FileService {
|
|
|
388
494
|
/**
|
|
389
495
|
* Gets storage statistics including total size, file count, and breakdowns by bucket and MIME type.
|
|
390
496
|
*
|
|
497
|
+
* Aggregated in SQL (`SUM`/`COUNT` + `GROUP BY`) rather than by loading
|
|
498
|
+
* every row into memory — the table can hold millions of files, and this
|
|
499
|
+
* endpoint must stay O(groups), not O(rows). Totals are derived from the
|
|
500
|
+
* per-bucket groups (every row has exactly one bucket), so no extra query
|
|
501
|
+
* is needed.
|
|
502
|
+
*
|
|
391
503
|
* @returns Storage statistics with aggregated data
|
|
392
504
|
*/
|
|
393
505
|
public async getStorageStats(): Promise<StorageStats> {
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const mimeTypeMap = new Map<string, number>();
|
|
416
|
-
for (const file of allFiles) {
|
|
417
|
-
const existing = mimeTypeMap.get(file.mimeType) || 0;
|
|
418
|
-
mimeTypeMap.set(file.mimeType, existing + 1);
|
|
419
|
-
}
|
|
506
|
+
const [byBucketRows, byMimeTypeRows] = await Promise.all([
|
|
507
|
+
this.fileRepository.aggregate({
|
|
508
|
+
select: { bucket: true, size: { sum: true, count: true } },
|
|
509
|
+
groupBy: ["bucket"],
|
|
510
|
+
}),
|
|
511
|
+
this.fileRepository.aggregate({
|
|
512
|
+
select: { mimeType: true, size: { count: true } },
|
|
513
|
+
groupBy: ["mimeType"],
|
|
514
|
+
}),
|
|
515
|
+
]);
|
|
516
|
+
|
|
517
|
+
const byBucket = byBucketRows.map((row) => ({
|
|
518
|
+
bucket: row.bucket,
|
|
519
|
+
totalSize: row.size.sum,
|
|
520
|
+
fileCount: row.size.count,
|
|
521
|
+
}));
|
|
522
|
+
|
|
523
|
+
const byMimeType = byMimeTypeRows.map((row) => ({
|
|
524
|
+
mimeType: row.mimeType,
|
|
525
|
+
fileCount: row.size.count,
|
|
526
|
+
}));
|
|
420
527
|
|
|
421
528
|
return {
|
|
422
|
-
totalSize,
|
|
423
|
-
totalFiles,
|
|
424
|
-
byBucket
|
|
425
|
-
|
|
426
|
-
totalSize: stats.totalSize,
|
|
427
|
-
fileCount: stats.fileCount,
|
|
428
|
-
})),
|
|
429
|
-
byMimeType: Array.from(mimeTypeMap.entries()).map(
|
|
430
|
-
([mimeType, fileCount]) => ({
|
|
431
|
-
mimeType,
|
|
432
|
-
fileCount,
|
|
433
|
-
}),
|
|
434
|
-
),
|
|
529
|
+
totalSize: byBucket.reduce((sum, b) => sum + b.totalSize, 0),
|
|
530
|
+
totalFiles: byBucket.reduce((sum, b) => sum + b.fileCount, 0),
|
|
531
|
+
byBucket,
|
|
532
|
+
byMimeType,
|
|
435
533
|
};
|
|
436
534
|
}
|
|
437
535
|
|
|
@@ -71,7 +71,7 @@ describe("$job — registration validation", () => {
|
|
|
71
71
|
// ---------------------------------------------------------------------------
|
|
72
72
|
|
|
73
73
|
describe("$job — cron mode", () => {
|
|
74
|
-
it("runs handler inline on trigger
|
|
74
|
+
it("runs handler inline on trigger and keeps the last successful run", async ({
|
|
75
75
|
expect,
|
|
76
76
|
}) => {
|
|
77
77
|
const alepha = makeApp();
|
|
@@ -89,6 +89,29 @@ describe("$job — cron mode", () => {
|
|
|
89
89
|
await alepha.start();
|
|
90
90
|
await app.tick.trigger();
|
|
91
91
|
expect(calls).toBe(1);
|
|
92
|
+
// Cron jobs keep their last successful run by default so "Last run" works.
|
|
93
|
+
const rows = await app.executions.findMany({
|
|
94
|
+
where: { jobName: { eq: "App.tick" } },
|
|
95
|
+
});
|
|
96
|
+
expect(rows).toHaveLength(1);
|
|
97
|
+
expect(rows[0].status).toBe("ok");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("records no row on success when record is 'error' (opt-out)", async ({
|
|
101
|
+
expect,
|
|
102
|
+
}) => {
|
|
103
|
+
const alepha = makeApp();
|
|
104
|
+
class App {
|
|
105
|
+
executions = $repository(jobExecutionEntity);
|
|
106
|
+
tick = $job({
|
|
107
|
+
cron: "0 0 * * *",
|
|
108
|
+
record: "error",
|
|
109
|
+
handler: async () => {},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const app = alepha.inject(App);
|
|
113
|
+
await alepha.start();
|
|
114
|
+
await app.tick.trigger();
|
|
92
115
|
const rows = await app.executions.findMany({
|
|
93
116
|
where: { jobName: { eq: "App.tick" } },
|
|
94
117
|
});
|
package/src/api/jobs/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { $module } from "alepha";
|
|
2
|
+
import { AlephaBackground } from "alepha/background";
|
|
2
3
|
import type { DateTime } from "alepha/datetime";
|
|
3
4
|
import { AlephaLock } from "alepha/lock";
|
|
4
5
|
import { AlephaQueue } from "alepha/queue";
|
|
@@ -62,8 +63,8 @@ declare module "alepha" {
|
|
|
62
63
|
* is overkill.
|
|
63
64
|
*
|
|
64
65
|
* **Retries** are sweep-driven across all modes (no exponential backoff).
|
|
65
|
-
* Granularity is bounded by `sweepCron` (default
|
|
66
|
-
* may land anywhere from a few seconds to ~
|
|
66
|
+
* Granularity is bounded by `sweepCron` (default 15 min). The first retry
|
|
67
|
+
* may land anywhere from a few seconds to ~15 min later depending on when
|
|
67
68
|
* the next sweep tick fires. Cron jobs that declare `retry` go through
|
|
68
69
|
* the same sweep path — a transient failure no longer means waiting for
|
|
69
70
|
* the next cron tick (useful for once-daily jobs).
|
|
@@ -86,7 +87,7 @@ declare module "alepha" {
|
|
|
86
87
|
export const AlephaApiJobs = $module({
|
|
87
88
|
name: "alepha.api.jobs",
|
|
88
89
|
primitives: [$job],
|
|
89
|
-
imports: [AlephaScheduler, AlephaLock],
|
|
90
|
+
imports: [AlephaScheduler, AlephaLock, AlephaBackground],
|
|
90
91
|
services: [JobProvider, JobService, AdminJobController, DirectJobDispatcher],
|
|
91
92
|
});
|
|
92
93
|
|
|
@@ -77,8 +77,8 @@ export interface JobPrimitiveOptions<T extends TSchema = TSchema>
|
|
|
77
77
|
* Cron-mode jobs do not retry — the next tick re-runs.
|
|
78
78
|
*
|
|
79
79
|
* Retries are picked up by the reconciliation sweep, so retry granularity
|
|
80
|
-
* is bounded by `sweepCron` (default
|
|
81
|
-
* earlier than
|
|
80
|
+
* is bounded by `sweepCron` (default 15 minutes). The first retry may run
|
|
81
|
+
* earlier than 15 minutes if the sweep tick happens sooner.
|
|
82
82
|
*/
|
|
83
83
|
retry?: JobRetryOptions;
|
|
84
84
|
|
|
@@ -115,10 +115,14 @@ export interface JobPrimitiveOptions<T extends TSchema = TSchema>
|
|
|
115
115
|
/**
|
|
116
116
|
* Whether to record successful executions.
|
|
117
117
|
*
|
|
118
|
-
* - `"error"` (default for
|
|
118
|
+
* - `"error"` (default for queue): only error/cancelled rows kept
|
|
119
119
|
* - `"all"`: keep success rows too (bounded by `keepLastSuccess`)
|
|
120
120
|
* - `"none"`: fire-and-forget, no row even on error
|
|
121
121
|
*
|
|
122
|
+
* **Cron jobs default to keeping their last successful run** (`record: "all"`
|
|
123
|
+
* with `keep.ok = 1`) so the admin "Last run" is accurate — set
|
|
124
|
+
* `record: "error"` to opt out (e.g. for very high-frequency crons).
|
|
125
|
+
*
|
|
122
126
|
* Note: queue-mode jobs always write a `pending` row at push time (outbox).
|
|
123
127
|
* This setting controls whether that row is kept on success.
|
|
124
128
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { $inject, Alepha } from "alepha";
|
|
2
|
+
import { BackgroundTaskProvider } from "alepha/background";
|
|
2
3
|
import { $logger } from "alepha/logger";
|
|
3
4
|
import { JobDispatcher } from "./JobDispatcher.ts";
|
|
4
5
|
import { JobProvider } from "./JobProvider.ts";
|
|
@@ -12,20 +13,17 @@ import { JobProvider } from "./JobProvider.ts";
|
|
|
12
13
|
* if the process dies before the handler finishes, the next sweep tick
|
|
13
14
|
* picks the row up and re-dispatches.
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* (every 5 min by default) would re-dispatch.
|
|
21
|
-
*
|
|
22
|
-
* **Vercel / single-Node** — on long-running runtimes the event loop
|
|
23
|
-
* keeps the promise alive naturally; no special wiring is required.
|
|
16
|
+
* Keeping the isolate alive past the HTTP response (Cloudflare Workers) vs.
|
|
17
|
+
* relying on the event loop (Node/Vercel) is delegated to
|
|
18
|
+
* {@link BackgroundTaskProvider.defer} — this dispatcher stays
|
|
19
|
+
* platform-agnostic. The DB outbox row remains the durability guarantee: if
|
|
20
|
+
* the process dies mid-handler, the next sweep re-dispatches.
|
|
24
21
|
*/
|
|
25
22
|
export class DirectJobDispatcher extends JobDispatcher {
|
|
26
23
|
public readonly kind = "direct" as const;
|
|
27
24
|
|
|
28
25
|
protected readonly alepha = $inject(Alepha);
|
|
26
|
+
protected readonly background = $inject(BackgroundTaskProvider);
|
|
29
27
|
protected readonly log = $logger();
|
|
30
28
|
|
|
31
29
|
// Lazy: resolved on first dispatch to break the JobProvider ↔ Dispatcher
|
|
@@ -40,32 +38,15 @@ export class DirectJobDispatcher extends JobDispatcher {
|
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
public async dispatch(jobName: string, executionId: string): Promise<void> {
|
|
43
|
-
|
|
44
|
-
.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// the handler actually finishes. Outside CF this read returns undefined
|
|
54
|
-
// and we fall through to plain fire-and-track.
|
|
55
|
-
const waitUntil = this.alepha.store.get("cloudflare.waitUntil") as
|
|
56
|
-
| ((p: Promise<unknown>) => void)
|
|
57
|
-
| undefined;
|
|
58
|
-
if (typeof waitUntil === "function") {
|
|
59
|
-
try {
|
|
60
|
-
waitUntil(promise);
|
|
61
|
-
} catch (e) {
|
|
62
|
-
// The runtime may reject waitUntil if called outside a request scope.
|
|
63
|
-
// Promise still runs; just log.
|
|
64
|
-
this.log.debug(
|
|
65
|
-
"waitUntil rejected — falling back to fire-and-track",
|
|
66
|
-
e,
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
41
|
+
this.background.defer(() =>
|
|
42
|
+
this.getJobProvider()
|
|
43
|
+
.processExecution(jobName, executionId)
|
|
44
|
+
.catch((err) => {
|
|
45
|
+
this.log.warn(
|
|
46
|
+
`Direct execution failed for '${jobName}' (sweep will retry)`,
|
|
47
|
+
err,
|
|
48
|
+
);
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
70
51
|
}
|
|
71
52
|
}
|
|
@@ -152,6 +152,38 @@ export class JobProvider {
|
|
|
152
152
|
protected readonly perExecutionLogs = new Map<string, LogEntry[]>();
|
|
153
153
|
protected stopping = false;
|
|
154
154
|
|
|
155
|
+
constructor() {
|
|
156
|
+
// Register the sweep + trim crons eagerly so the wrangler build
|
|
157
|
+
// step (which reads `CronProvider.getCronJobs()` without running
|
|
158
|
+
// any lifecycle hooks) sees them and emits the matching cron
|
|
159
|
+
// triggers into wrangler.jsonc. CronProvider's `start` hook will
|
|
160
|
+
// boot whatever is in `cronJobs` at runtime — registering here
|
|
161
|
+
// (constructor, runs at inject time) is equivalent to registering
|
|
162
|
+
// in `onStart` from CronProvider's POV but visible to build-time
|
|
163
|
+
// introspection.
|
|
164
|
+
this.cronProvider.createCronJob(
|
|
165
|
+
"api:jobs:sweep",
|
|
166
|
+
this.config.sweepCron,
|
|
167
|
+
async () => {
|
|
168
|
+
await this.sweep();
|
|
169
|
+
},
|
|
170
|
+
true,
|
|
171
|
+
);
|
|
172
|
+
this.cronProvider.createCronJob(
|
|
173
|
+
"api:jobs:trim",
|
|
174
|
+
this.config.trimCron,
|
|
175
|
+
async () => {
|
|
176
|
+
if (this.stopping) return;
|
|
177
|
+
try {
|
|
178
|
+
await this.trimRingBuffers();
|
|
179
|
+
} catch (e) {
|
|
180
|
+
this.log.error("Trim failed", { error: e });
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
true,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
155
187
|
// --- Registration -----------------------------------------------------------------------------------------------
|
|
156
188
|
|
|
157
189
|
public registerJob(name: string, options: JobPrimitiveOptions): void {
|
|
@@ -170,6 +202,21 @@ export class JobProvider {
|
|
|
170
202
|
}
|
|
171
203
|
|
|
172
204
|
const kind: "cron" | "queue" = options.cron ? "cron" : "queue";
|
|
205
|
+
|
|
206
|
+
// Cron jobs keep their last successful run by default, so the admin
|
|
207
|
+
// "Last run" reflects reality — a routine cron success is otherwise
|
|
208
|
+
// unrecorded (only failures are), making every healthy cron look like it
|
|
209
|
+
// "never" ran. Bounded to the latest row (keep.ok=1) to avoid unbounded
|
|
210
|
+
// growth from recurring ticks. Opt out of recording successes entirely
|
|
211
|
+
// with `record: "error"` (recommended for very high-frequency crons).
|
|
212
|
+
if (kind === "cron" && options.record === undefined) {
|
|
213
|
+
options = {
|
|
214
|
+
...options,
|
|
215
|
+
record: "all",
|
|
216
|
+
keep: { ...options.keep, ok: options.keep?.ok ?? 1 },
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
173
220
|
this.jobs.set(name, { name, options, kind });
|
|
174
221
|
this.log.debug(`Registered ${kind} job '${name}'`, {
|
|
175
222
|
cron: options.cron,
|
|
@@ -996,8 +1043,8 @@ export class JobProvider {
|
|
|
996
1043
|
if (canRetry) {
|
|
997
1044
|
// Retries are sweep-driven: write the row as `scheduled` with
|
|
998
1045
|
// `scheduledAt = now`. The next sweep tick (every `sweepCron`,
|
|
999
|
-
// default
|
|
1000
|
-
// can land anywhere from a few seconds to ~
|
|
1046
|
+
// default 15 minutes) re-dispatches it. This means the first retry
|
|
1047
|
+
// can land anywhere from a few seconds to ~15 minutes later — the
|
|
1001
1048
|
// exact moment depends on when the next sweep tick fires.
|
|
1002
1049
|
//
|
|
1003
1050
|
// We deliberately do NOT use exponential backoff or a local timer.
|
|
@@ -1246,28 +1293,10 @@ export class JobProvider {
|
|
|
1246
1293
|
await this.sweep();
|
|
1247
1294
|
}
|
|
1248
1295
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
await this.sweep();
|
|
1254
|
-
},
|
|
1255
|
-
true,
|
|
1256
|
-
);
|
|
1257
|
-
|
|
1258
|
-
this.cronProvider.createCronJob(
|
|
1259
|
-
"api:jobs:trim",
|
|
1260
|
-
this.config.trimCron,
|
|
1261
|
-
async () => {
|
|
1262
|
-
if (this.stopping) return;
|
|
1263
|
-
try {
|
|
1264
|
-
await this.trimRingBuffers();
|
|
1265
|
-
} catch (e) {
|
|
1266
|
-
this.log.error("Trim failed", { error: e });
|
|
1267
|
-
}
|
|
1268
|
-
},
|
|
1269
|
-
true,
|
|
1270
|
-
);
|
|
1296
|
+
// Sweep + trim cron registrations live in the constructor — see
|
|
1297
|
+
// the note there. CronProvider's `start` hook (which runs before
|
|
1298
|
+
// ours via DI order) has already booted them by the time we get
|
|
1299
|
+
// here.
|
|
1271
1300
|
},
|
|
1272
1301
|
});
|
|
1273
1302
|
|
|
@@ -12,7 +12,10 @@ import { jobExecutionEntity } from "../entities/jobExecutionEntity.ts";
|
|
|
12
12
|
* - `can` derives the available admin actions from the row's status.
|
|
13
13
|
*/
|
|
14
14
|
export const jobExecutionResourceSchema = t.extend(
|
|
15
|
-
|
|
15
|
+
// `t.extend` composes (interface-extends), it does not override: the base
|
|
16
|
+
// `priority` (integer) would still be enforced alongside the enum below and
|
|
17
|
+
// reject the int→string transform. Drop it from the base first.
|
|
18
|
+
t.omit(jobExecutionEntity.schema, ["priority"]),
|
|
16
19
|
{
|
|
17
20
|
priority: t.enum(["critical", "high", "normal", "low"]),
|
|
18
21
|
can: t.object({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t } from "alepha";
|
|
1
|
+
import { type Static, t } from "alepha";
|
|
2
2
|
|
|
3
3
|
export const adminApiKeyResourceSchema = t.object({
|
|
4
4
|
id: t.uuid(),
|
|
@@ -15,3 +15,5 @@ export const adminApiKeyResourceSchema = t.object({
|
|
|
15
15
|
revokedAt: t.optional(t.datetime()),
|
|
16
16
|
usageCount: t.integer(),
|
|
17
17
|
});
|
|
18
|
+
|
|
19
|
+
export type AdminApiKeyResource = Static<typeof adminApiKeyResourceSchema>;
|
|
@@ -1321,7 +1321,7 @@ describe("getCurrentWithDefault", () => {
|
|
|
1321
1321
|
expect(result.schema).toBeNull();
|
|
1322
1322
|
});
|
|
1323
1323
|
|
|
1324
|
-
it("should
|
|
1324
|
+
it("should lazy-seed v1 from defaults when primitive registered but no DB value", async () => {
|
|
1325
1325
|
class AppConfig {
|
|
1326
1326
|
features = $parameter({
|
|
1327
1327
|
name: "gwd.default.only",
|
|
@@ -1338,7 +1338,19 @@ describe("getCurrentWithDefault", () => {
|
|
|
1338
1338
|
const provider = alepha.inject(ParameterProvider);
|
|
1339
1339
|
const result = await provider.getCurrentWithDefault("gwd.default.only");
|
|
1340
1340
|
|
|
1341
|
-
|
|
1341
|
+
// Auto-seed: getCurrentWithDefault materializes v1 from the compiled
|
|
1342
|
+
// defaults when nothing is in the DB yet, so the admin UI always has a
|
|
1343
|
+
// concrete current row to edit / roll back / compare against.
|
|
1344
|
+
expect(result.current).not.toBeNull();
|
|
1345
|
+
expect(result.current!.version).toBe(1);
|
|
1346
|
+
expect(result.current!.status).toBe("current");
|
|
1347
|
+
expect(result.current!.content).toEqual({
|
|
1348
|
+
enableBeta: false,
|
|
1349
|
+
maxUploadSize: 10485760,
|
|
1350
|
+
});
|
|
1351
|
+
expect(result.current!.changeDescription).toBe(
|
|
1352
|
+
"Auto-seeded from compiled defaults",
|
|
1353
|
+
);
|
|
1342
1354
|
expect(result.next).toBeNull();
|
|
1343
1355
|
expect(result.defaultValue).toEqual({
|
|
1344
1356
|
enableBeta: false,
|
|
@@ -1349,6 +1361,11 @@ describe("getCurrentWithDefault", () => {
|
|
|
1349
1361
|
maxUploadSize: 10485760,
|
|
1350
1362
|
});
|
|
1351
1363
|
expect(result.schema).not.toBeNull();
|
|
1364
|
+
|
|
1365
|
+
// Idempotent: a second call returns the same row without re-creating.
|
|
1366
|
+
const second = await provider.getCurrentWithDefault("gwd.default.only");
|
|
1367
|
+
expect(second.current!.id).toBe(result.current!.id);
|
|
1368
|
+
expect(second.current!.version).toBe(1);
|
|
1352
1369
|
});
|
|
1353
1370
|
|
|
1354
1371
|
it("should return current, next, and defaults when all exist", async () => {
|