alepha 0.21.2 → 0.23.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 +282 -285
- 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 +217 -222
- 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 +1106 -1005
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +307 -64
- 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 +137 -143
- 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 +458 -249
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +372 -660
- 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 +20 -17
- package/dist/cli/i18n/index.d.ts.map +1 -1
- package/dist/cli/i18n/index.js +45 -11
- 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 +1472 -0
- package/dist/cli/platform-lib/index.d.ts.map +1 -0
- package/dist/cli/platform-lib/index.js +2660 -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 +20 -19
- 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 +25 -20
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +23 -0
- 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 -5
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js +16 -15
- 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 +90 -11
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/i18n/index.js +147 -11
- 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 +193 -24
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +434 -222
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +249 -35
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/sitemap/index.browser.js +35 -0
- package/dist/react/sitemap/index.browser.js.map +1 -0
- package/dist/react/sitemap/index.d.ts +92 -0
- package/dist/react/sitemap/index.d.ts.map +1 -0
- package/dist/react/sitemap/index.js +131 -0
- package/dist/react/sitemap/index.js.map +1 -0
- 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 +272 -173
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +1608 -15
- 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 +20 -7
- package/dist/server/cookies/index.d.ts.map +1 -1
- package/dist/server/cookies/index.js +22 -3
- 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 +106 -73
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +44 -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 +59 -23
- 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/controllers/RealmController.ts +1 -0
- 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 +29 -0
- package/src/api/users/providers/RealmProvider.ts +15 -0
- package/src/api/users/schemas/realmConfigSchema.ts +14 -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/atoms/buildOptions.ts +0 -12
- package/src/cli/core/commands/__tests__/BuildCommand.spec.ts +43 -0
- package/src/cli/core/commands/build.ts +105 -37
- 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 -3
- 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 +382 -56
- package/src/cli/core/tasks/BuildDockerTask.ts +33 -3
- package/src/cli/core/tasks/BuildPrerenderTask.ts +44 -7
- 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/i18n/__tests__/I18nCheckService.spec.ts +48 -0
- package/src/cli/i18n/services/I18nCheckService.ts +65 -11
- 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 +519 -190
- 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/mcp/__tests__/McpServerProvider.spec.ts +71 -0
- package/src/mcp/providers/McpServerProvider.ts +55 -0
- 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/form/__tests__/FormModel-submit-loading.spec.ts +71 -0
- package/src/react/form/__tests__/form-submitting-reactive.browser.spec.tsx +96 -0
- package/src/react/form/services/FormModel.ts +57 -39
- package/src/react/i18n/__tests__/I18nProvider.spec.ts +89 -0
- package/src/react/i18n/__tests__/locale-routing.spec.ts +107 -0
- package/src/react/i18n/components/Translate.tsx +47 -0
- package/src/react/i18n/index.ts +2 -0
- package/src/react/i18n/providers/I18nProvider.ts +171 -12
- package/src/react/intro/components/GettingStartedAdminSlide.tsx +2 -2
- package/src/react/router/__tests__/$page.spec.tsx +3 -2
- package/src/react/router/__tests__/RouterLocaleProvider.spec.ts +127 -0
- package/src/react/router/__tests__/page-can.spec.ts +18 -13
- package/src/react/router/hooks/useQueryParams.ts +114 -14
- package/src/react/router/index.browser.ts +4 -0
- package/src/react/router/index.shared.ts +1 -0
- package/src/react/router/index.ts +9 -0
- package/src/react/router/primitives/$page.ts +85 -4
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +18 -8
- package/src/react/router/providers/ReactPageProvider.ts +12 -1
- package/src/react/router/providers/ReactServerProvider.ts +96 -14
- package/src/react/router/providers/RootComponentsProvider.ts +13 -0
- package/src/react/router/providers/RouterLocaleProvider.ts +125 -0
- package/src/react/router/providers/__tests__/RootComponentsProvider.spec.ts +15 -0
- package/src/react/router/providers/__tests__/rootComponents.ssr.browser.spec.tsx +67 -0
- package/src/react/sitemap/__tests__/$sitemap.spec.ts +131 -0
- package/src/react/sitemap/index.browser.ts +21 -0
- package/src/react/sitemap/index.ts +25 -0
- package/src/react/sitemap/primitives/$sitemap.browser.ts +26 -0
- package/src/react/sitemap/primitives/$sitemap.ts +196 -0
- package/src/react/ui/services/SchemaControl.ts +3 -4
- package/src/server/auth/__tests__/appleClientSecret.spec.ts +34 -0
- package/src/server/auth/__tests__/authFederationClient.spec.ts +40 -0
- package/src/server/auth/__tests__/federationAssertion.spec.ts +146 -0
- package/src/server/auth/__tests__/federationRedirectReplay.spec.ts +44 -0
- package/src/server/auth/helpers/appleClientSecret.ts +24 -0
- package/src/server/auth/helpers/federationAssertion.ts +74 -0
- package/src/server/auth/helpers/jtiReplayGuard.ts +41 -0
- package/src/server/auth/helpers/safeRedirectPath.ts +19 -0
- package/src/server/auth/index.ts +4 -0
- package/src/server/auth/primitives/$authFederationBroker.ts +273 -0
- package/src/server/auth/primitives/$authFederationClient.ts +89 -0
- package/src/server/auth/providers/ServerAuthProvider.ts +18 -4
- package/src/server/cookies/__tests__/ServerCookiesProvider.spec.ts +70 -0
- package/src/server/cookies/providers/ServerCookiesProvider.ts +23 -3
- package/src/server/core/interfaces/ServerRequest.ts +8 -0
- package/src/server/core/primitives/$route.ts +27 -0
- 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/tasks/BuildSitemapTask.ts +0 -130
- 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
|
@@ -67,9 +67,12 @@ export type PgRelation<Base extends TObject> = {
|
|
|
67
67
|
schema: TObject;
|
|
68
68
|
name: string;
|
|
69
69
|
};
|
|
70
|
+
// `readonly` so a literal tuple written with `as const` (the natural
|
|
71
|
+
// shape for `["userId", users.cols.id]`) satisfies the type without an
|
|
72
|
+
// explicit cast. The relation manager only reads the indices.
|
|
70
73
|
on:
|
|
71
74
|
| SQLWrapper
|
|
72
|
-
| [
|
|
75
|
+
| readonly [
|
|
73
76
|
keyof Static<Base>,
|
|
74
77
|
{
|
|
75
78
|
name: string;
|
|
@@ -597,27 +597,43 @@ export abstract class Repository<T extends TObject> {
|
|
|
597
597
|
|
|
598
598
|
/**
|
|
599
599
|
* Find an entity by ID. Returns `undefined` if not found.
|
|
600
|
+
*
|
|
601
|
+
* Pass `with` to eager-load relations on the result — same `with` map
|
|
602
|
+
* shape as `findOne` / `paginate`. Without `with`, returns the plain
|
|
603
|
+
* row.
|
|
604
|
+
*
|
|
605
|
+
* @example
|
|
606
|
+
* ```ts
|
|
607
|
+
* const session = await sessions.findById(id, {
|
|
608
|
+
* with: { user: { join: users, on: ["userId", users.cols.id] as const } },
|
|
609
|
+
* });
|
|
610
|
+
* session?.user?.email;
|
|
611
|
+
* ```
|
|
600
612
|
*/
|
|
601
|
-
public async findById(
|
|
613
|
+
public async findById<R extends PgRelationMap<T>>(
|
|
602
614
|
id: string | number,
|
|
603
|
-
opts: StatementOptions = {},
|
|
604
|
-
): Promise<
|
|
605
|
-
|
|
615
|
+
opts: StatementOptions & { with?: R } = {},
|
|
616
|
+
): Promise<PgStatic<T, R> | undefined> {
|
|
617
|
+
const { with: withRelations, ...rest } = opts;
|
|
618
|
+
return (await this.findOne<R>(
|
|
606
619
|
{
|
|
607
620
|
where: this.getWhereId(id),
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
621
|
+
...(withRelations ? { with: withRelations } : {}),
|
|
622
|
+
} as Pick<PgQueryRelations<T, R>, "with" | "where">,
|
|
623
|
+
rest,
|
|
624
|
+
)) as PgStatic<T, R> | undefined;
|
|
611
625
|
}
|
|
612
626
|
|
|
613
627
|
/**
|
|
614
628
|
* Find an entity by ID. Throws `DbEntityNotFoundError` if not found.
|
|
629
|
+
*
|
|
630
|
+
* Pass `with` to eager-load relations — see {@link findById}.
|
|
615
631
|
*/
|
|
616
|
-
public async getById(
|
|
632
|
+
public async getById<R extends PgRelationMap<T>>(
|
|
617
633
|
id: string | number,
|
|
618
|
-
opts: StatementOptions = {},
|
|
619
|
-
): Promise<
|
|
620
|
-
const entity = await this.findById(id, opts);
|
|
634
|
+
opts: StatementOptions & { with?: R } = {},
|
|
635
|
+
): Promise<PgStatic<T, R>> {
|
|
636
|
+
const entity = await this.findById<R>(id, opts);
|
|
621
637
|
|
|
622
638
|
if (!entity) {
|
|
623
639
|
throw new DbEntityNotFoundError(this.tableName);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useAlepha, useStore } from "alepha/react";
|
|
2
2
|
import { currentUserAtom } from "alepha/security";
|
|
3
|
-
import {
|
|
3
|
+
import { LinkProvider } from "alepha/server/links";
|
|
4
4
|
import { ReactAuth } from "../services/ReactAuth.ts";
|
|
5
5
|
|
|
6
6
|
export const useAuth = <T extends object = any>() => {
|
|
@@ -24,10 +24,15 @@ export const useAuth = <T extends object = any>() => {
|
|
|
24
24
|
) => {
|
|
25
25
|
await alepha.inject(ReactAuth).login(provider as string, options);
|
|
26
26
|
},
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
/**
|
|
28
|
+
* UI permission check — does the current user hold this permission?
|
|
29
|
+
* Supports exact and wildcard names (e.g. `"admin:*"`, `"admin:user:read"`).
|
|
30
|
+
*
|
|
31
|
+
* UI affordance only (show/hide/disable). Real access control is enforced
|
|
32
|
+
* server-side via `$secure` on the route/action.
|
|
33
|
+
*/
|
|
34
|
+
has: (permission: string): boolean => {
|
|
35
|
+
return alepha.inject(LinkProvider).can(permission);
|
|
31
36
|
},
|
|
32
37
|
};
|
|
33
38
|
};
|
|
@@ -41,6 +41,31 @@ describe("useQuery", () => {
|
|
|
41
41
|
expect(handler).toHaveBeenCalledTimes(2);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
+
test("reports loading on the first render, before the fetch settles", async ({
|
|
45
|
+
expect,
|
|
46
|
+
}) => {
|
|
47
|
+
const alepha = Alepha.create().with(AlephaDateTime);
|
|
48
|
+
await alepha.start();
|
|
49
|
+
|
|
50
|
+
const handler = vi.fn(async () => "value");
|
|
51
|
+
|
|
52
|
+
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
53
|
+
<AlephaContext.Provider value={alepha}>{children}</AlephaContext.Provider>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const { result } = renderHook(() => useQuery({ handler }, []), { wrapper });
|
|
57
|
+
|
|
58
|
+
// No data yet, but loading must already be true so callers can render a
|
|
59
|
+
// skeleton instead of an empty/not-found flash.
|
|
60
|
+
expect(result.current.data).toBe(undefined);
|
|
61
|
+
expect(result.current.loading).toBe(true);
|
|
62
|
+
|
|
63
|
+
await waitFor(() => {
|
|
64
|
+
expect(result.current.data).toBe("value");
|
|
65
|
+
});
|
|
66
|
+
expect(result.current.loading).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
44
69
|
test("does not run when enabled is false", async ({ expect }) => {
|
|
45
70
|
const alepha = Alepha.create().with(AlephaDateTime);
|
|
46
71
|
await alepha.start();
|
|
@@ -158,6 +158,15 @@ export function useAction<Args extends any[], Result = void>(
|
|
|
158
158
|
abortControllerRef.current.abort();
|
|
159
159
|
abortControllerRef.current = undefined;
|
|
160
160
|
}
|
|
161
|
+
|
|
162
|
+
// Release the concurrency guard: we've just abandoned the in-flight run
|
|
163
|
+
// (its result is discarded by the aborted-signal check), so the *next*
|
|
164
|
+
// lifecycle must be free to start. Without this, a remount that reuses
|
|
165
|
+
// the same refs — React StrictMode's double-invoke, or a Suspense/router
|
|
166
|
+
// remount on client-side navigation — would hit `if (isExecutingRef
|
|
167
|
+
// .current) return` and never re-run, leaving a `useQuery` stuck on its
|
|
168
|
+
// loading skeleton forever (the original request 200s but is dropped).
|
|
169
|
+
isExecutingRef.current = false;
|
|
161
170
|
};
|
|
162
171
|
}, []);
|
|
163
172
|
|
|
@@ -230,10 +239,12 @@ export function useAction<Args extends any[], Result = void>(
|
|
|
230
239
|
|
|
231
240
|
if (options.onError) {
|
|
232
241
|
await options.onError(error);
|
|
233
|
-
} else {
|
|
234
|
-
// Re-throw if no custom error handler
|
|
235
|
-
throw error;
|
|
236
242
|
}
|
|
243
|
+
// Without a custom `onError`, the error is NOT re-thrown: it is captured
|
|
244
|
+
// in `error` state and emitted as `react:action:error` (a mounted
|
|
245
|
+
// <ActionErrorToaster /> surfaces it as a toast). This keeps
|
|
246
|
+
// fire-and-forget `action.run()` calls from producing unhandled
|
|
247
|
+
// promise rejections.
|
|
237
248
|
} finally {
|
|
238
249
|
isExecutingRef.current = false;
|
|
239
250
|
if (isMountedRef.current) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Async } from "alepha";
|
|
2
2
|
import type { DurationLike } from "alepha/datetime";
|
|
3
|
-
import { type DependencyList, useCallback, useState } from "react";
|
|
3
|
+
import { type DependencyList, useCallback, useRef, useState } from "react";
|
|
4
4
|
import type { ActionContext } from "./useAction.ts";
|
|
5
5
|
import { useAction } from "./useAction.ts";
|
|
6
6
|
|
|
@@ -47,17 +47,23 @@ export function useQuery<Result>(
|
|
|
47
47
|
options: UseQueryOptions<Result>,
|
|
48
48
|
deps: DependencyList,
|
|
49
49
|
): UseQueryReturn<Result> {
|
|
50
|
+
const enabled = options.enabled !== false;
|
|
50
51
|
const [data, setData] = useState<Result | undefined>(options.initialData);
|
|
52
|
+
// Tracks whether the auto-run has completed at least once, so we can keep
|
|
53
|
+
// `loading` true on the very first render — before `runOnInit`'s effect
|
|
54
|
+
// fires — instead of briefly reporting `loading: false` with no data.
|
|
55
|
+
const settledRef = useRef(false);
|
|
51
56
|
|
|
52
57
|
const action = useAction<[], Result>(
|
|
53
58
|
{
|
|
54
59
|
id: options.id,
|
|
55
60
|
handler: options.handler,
|
|
56
|
-
runOnInit:
|
|
61
|
+
runOnInit: enabled,
|
|
57
62
|
runEvery: options.runEvery,
|
|
58
63
|
debounce: options.debounce,
|
|
59
64
|
onError: options.onError,
|
|
60
65
|
onSuccess: async (result) => {
|
|
66
|
+
settledRef.current = true;
|
|
61
67
|
setData(result);
|
|
62
68
|
if (options.onSuccess) {
|
|
63
69
|
await options.onSuccess(result);
|
|
@@ -69,9 +75,21 @@ export function useQuery<Result>(
|
|
|
69
75
|
|
|
70
76
|
const refetch = useCallback(() => action.run(), [action.run]);
|
|
71
77
|
|
|
78
|
+
// `loading` is true while a fetch is in flight, AND during the initial gap
|
|
79
|
+
// between first render and the auto-run effect — so consumers can render a
|
|
80
|
+
// skeleton immediately instead of flashing an empty/not-found state. Only
|
|
81
|
+
// applies to enabled queries with no `initialData` seed; an error settles
|
|
82
|
+
// it (the error path keeps the caller's `onError` re-throw semantics).
|
|
83
|
+
const loading =
|
|
84
|
+
action.loading ||
|
|
85
|
+
(enabled &&
|
|
86
|
+
options.initialData === undefined &&
|
|
87
|
+
!settledRef.current &&
|
|
88
|
+
action.error === undefined);
|
|
89
|
+
|
|
72
90
|
return {
|
|
73
91
|
data,
|
|
74
|
-
loading
|
|
92
|
+
loading,
|
|
75
93
|
error: action.error,
|
|
76
94
|
refetch,
|
|
77
95
|
cancel: action.cancel,
|
|
@@ -132,7 +150,9 @@ export interface UseQueryReturn<Result> {
|
|
|
132
150
|
data: Result | undefined;
|
|
133
151
|
|
|
134
152
|
/**
|
|
135
|
-
* Loading state — `true` while a fetch is in flight
|
|
153
|
+
* Loading state — `true` while a fetch is in flight, and also from the
|
|
154
|
+
* first render until the initial auto-run settles (for enabled queries
|
|
155
|
+
* with no `initialData`), so a skeleton can render without a flash.
|
|
136
156
|
*/
|
|
137
157
|
loading: boolean;
|
|
138
158
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Alepha, t } from "alepha";
|
|
2
|
+
import { AlephaLogger } from "alepha/logger";
|
|
3
|
+
import { describe, it } from "vitest";
|
|
4
|
+
import { FormModel } from "../services/FormModel.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Repro for: a form whose submit fails TypeBox validation (missing field)
|
|
8
|
+
* leaves the submit button stuck in its loading state forever.
|
|
9
|
+
*
|
|
10
|
+
* The button's loading is driven by the `form:submit:begin` / `form:submit:end`
|
|
11
|
+
* event pair (see useFormState). So the invariant that matters is: every
|
|
12
|
+
* `form:submit:begin` is followed by a `form:submit:end`, even when validation
|
|
13
|
+
* throws AND even when a downstream error listener (e.g. the action-error
|
|
14
|
+
* toaster) itself throws.
|
|
15
|
+
*/
|
|
16
|
+
describe("FormModel.submit loading pairing", () => {
|
|
17
|
+
const makeForm = (alepha: Alepha) =>
|
|
18
|
+
alepha.inject(FormModel as any, {
|
|
19
|
+
lifetime: "transient",
|
|
20
|
+
args: [
|
|
21
|
+
"f1",
|
|
22
|
+
{
|
|
23
|
+
id: "f1",
|
|
24
|
+
schema: t.object({ email: t.text(), password: t.text() }),
|
|
25
|
+
handler: () => {},
|
|
26
|
+
initialValues: {}, // both required fields missing → decode throws
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
}) as FormModel<any>;
|
|
30
|
+
|
|
31
|
+
it("emits form:submit:end on a validation error (intrinsic)", async ({
|
|
32
|
+
expect,
|
|
33
|
+
}) => {
|
|
34
|
+
const alepha = Alepha.create().with(AlephaLogger);
|
|
35
|
+
await alepha.start();
|
|
36
|
+
const form = makeForm(alepha);
|
|
37
|
+
let begun = false;
|
|
38
|
+
let ended = false;
|
|
39
|
+
alepha.events.on("form:submit:begin", () => {
|
|
40
|
+
begun = true;
|
|
41
|
+
});
|
|
42
|
+
alepha.events.on("form:submit:end", () => {
|
|
43
|
+
ended = true;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await form.submit().catch(() => {});
|
|
47
|
+
|
|
48
|
+
expect(begun).toBe(true);
|
|
49
|
+
expect(ended).toBe(true); // pairing must hold
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("emits form:submit:end even when a react:action:error listener throws", async ({
|
|
53
|
+
expect,
|
|
54
|
+
}) => {
|
|
55
|
+
const alepha = Alepha.create().with(AlephaLogger);
|
|
56
|
+
await alepha.start();
|
|
57
|
+
const form = makeForm(alepha);
|
|
58
|
+
let ended = false;
|
|
59
|
+
alepha.events.on("form:submit:end", () => {
|
|
60
|
+
ended = true;
|
|
61
|
+
});
|
|
62
|
+
// Simulates a globally-mounted listener (e.g. ActionErrorToaster) throwing.
|
|
63
|
+
alepha.events.on("react:action:error", () => {
|
|
64
|
+
throw new Error("toaster boom");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await form.submit().catch(() => {});
|
|
68
|
+
|
|
69
|
+
expect(ended).toBe(true); // currently FAILS: end is skipped → button stuck
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { fireEvent, render, waitFor } from "@testing-library/react";
|
|
2
|
+
import { Alepha, t } from "alepha";
|
|
3
|
+
import { AlephaLogger } from "alepha/logger";
|
|
4
|
+
import { AlephaContext } from "alepha/react";
|
|
5
|
+
import { describe, it } from "vitest";
|
|
6
|
+
import { useForm, useFormState } from "../index.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Submit buttons bind their loading state to `useFormState(form, ["loading"])`
|
|
10
|
+
* (the reactive form-state API; the non-reactive `form.submitting` getter was
|
|
11
|
+
* removed). This loading must:
|
|
12
|
+
* - turn ON while an async submit is in flight, and
|
|
13
|
+
* - turn OFF again afterwards — including after a TypeBox validation error.
|
|
14
|
+
*
|
|
15
|
+
* The OFF guarantee relies on `FormModel.submit` always emitting
|
|
16
|
+
* `form:submit:end` (see FormModel-submit-loading.spec.ts).
|
|
17
|
+
*/
|
|
18
|
+
describe("useFormState loading drives the submit button", () => {
|
|
19
|
+
const mount = (alepha: Alepha, ui: React.ReactNode) =>
|
|
20
|
+
render(
|
|
21
|
+
<AlephaContext.Provider value={alepha}>{ui}</AlephaContext.Provider>,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
it("turns loading on during an async submit and off after", async ({
|
|
25
|
+
expect,
|
|
26
|
+
}) => {
|
|
27
|
+
const alepha = Alepha.create().with(AlephaLogger);
|
|
28
|
+
let release!: () => void;
|
|
29
|
+
|
|
30
|
+
const Form = () => {
|
|
31
|
+
const form = useForm({
|
|
32
|
+
id: "async",
|
|
33
|
+
schema: t.object({ name: t.string({ minLength: 1 }) }),
|
|
34
|
+
initialValues: { name: "ok" },
|
|
35
|
+
handler: () => new Promise<void>((r) => (release = r)),
|
|
36
|
+
});
|
|
37
|
+
const { loading } = useFormState(form, ["loading"]);
|
|
38
|
+
return (
|
|
39
|
+
<form {...form.props} data-testid="form">
|
|
40
|
+
<button
|
|
41
|
+
type="submit"
|
|
42
|
+
data-testid="submit"
|
|
43
|
+
data-loading={loading ? "true" : "false"}
|
|
44
|
+
>
|
|
45
|
+
go
|
|
46
|
+
</button>
|
|
47
|
+
</form>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const { getByTestId } = mount(alepha, <Form />);
|
|
52
|
+
fireEvent.submit(getByTestId("form"));
|
|
53
|
+
|
|
54
|
+
await waitFor(() =>
|
|
55
|
+
expect(getByTestId("submit").getAttribute("data-loading")).toBe("true"),
|
|
56
|
+
);
|
|
57
|
+
release();
|
|
58
|
+
await waitFor(() =>
|
|
59
|
+
expect(getByTestId("submit").getAttribute("data-loading")).toBe("false"),
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("clears loading after a TypeBox validation error", async ({ expect }) => {
|
|
64
|
+
const alepha = Alepha.create().with(AlephaLogger);
|
|
65
|
+
|
|
66
|
+
const Form = () => {
|
|
67
|
+
const form = useForm({
|
|
68
|
+
id: "login",
|
|
69
|
+
schema: t.object({
|
|
70
|
+
identifier: t.string({ minLength: 1 }),
|
|
71
|
+
password: t.string({ minLength: 6 }),
|
|
72
|
+
}),
|
|
73
|
+
handler: async () => {},
|
|
74
|
+
});
|
|
75
|
+
const { loading } = useFormState(form, ["loading"]);
|
|
76
|
+
return (
|
|
77
|
+
<form {...form.props} data-testid="form">
|
|
78
|
+
<button
|
|
79
|
+
type="submit"
|
|
80
|
+
data-testid="submit"
|
|
81
|
+
data-loading={loading ? "true" : "false"}
|
|
82
|
+
>
|
|
83
|
+
Sign in
|
|
84
|
+
</button>
|
|
85
|
+
</form>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const { getByTestId } = mount(alepha, <Form />);
|
|
90
|
+
fireEvent.submit(getByTestId("form")); // empty → validation error
|
|
91
|
+
|
|
92
|
+
await waitFor(() =>
|
|
93
|
+
expect(getByTestId("submit").getAttribute("data-loading")).toBe("false"),
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -27,10 +27,6 @@ export class FormModel<T extends TObject> {
|
|
|
27
27
|
|
|
28
28
|
public input: SchemaToInput<T>;
|
|
29
29
|
|
|
30
|
-
public get submitting(): boolean {
|
|
31
|
-
return this.submitInProgress;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
30
|
constructor(
|
|
35
31
|
public readonly id: string,
|
|
36
32
|
public readonly options: FormCtrlOptions<T>,
|
|
@@ -185,14 +181,22 @@ export class FormModel<T extends TObject> {
|
|
|
185
181
|
return;
|
|
186
182
|
}
|
|
187
183
|
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
184
|
+
// Lifecycle events are best-effort NOTIFICATIONS (loading spinners, toasts,
|
|
185
|
+
// analytics). A misbehaving listener must never break form state or reject
|
|
186
|
+
// submit() — hence `{ catch: true }` on every emit below. Without it, a
|
|
187
|
+
// throwing `react:action:error`/`form:submit:error` listener would skip the
|
|
188
|
+
// `form:submit:end` "loading off" signal and leave submit buttons stuck in
|
|
189
|
+
// their loading state forever.
|
|
190
|
+
await this.alepha.events.emit(
|
|
191
|
+
"react:action:begin",
|
|
192
|
+
{ type: "form", id: this.id },
|
|
193
|
+
{ catch: true },
|
|
194
|
+
);
|
|
195
|
+
await this.alepha.events.emit(
|
|
196
|
+
"form:submit:begin",
|
|
197
|
+
{ id: this.id },
|
|
198
|
+
{ catch: true },
|
|
199
|
+
);
|
|
196
200
|
|
|
197
201
|
this.submitInProgress = true;
|
|
198
202
|
|
|
@@ -210,39 +214,53 @@ export class FormModel<T extends TObject> {
|
|
|
210
214
|
|
|
211
215
|
await options.handler(values as any);
|
|
212
216
|
|
|
213
|
-
await this.alepha.events.emit(
|
|
214
|
-
|
|
215
|
-
id: this.id,
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
217
|
+
await this.alepha.events.emit(
|
|
218
|
+
"react:action:success",
|
|
219
|
+
{ type: "form", id: this.id },
|
|
220
|
+
{ catch: true },
|
|
221
|
+
);
|
|
222
|
+
await this.alepha.events.emit(
|
|
223
|
+
"form:submit:success",
|
|
224
|
+
{ id: this.id, values },
|
|
225
|
+
{ catch: true },
|
|
226
|
+
);
|
|
221
227
|
} catch (error) {
|
|
222
228
|
this.log.error("Form submission error:", error);
|
|
223
229
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
await this.alepha.events.emit(
|
|
232
|
-
|
|
233
|
-
id: this.id,
|
|
234
|
-
|
|
230
|
+
// A throwing onError callback must not abort the lifecycle either.
|
|
231
|
+
try {
|
|
232
|
+
options.onError?.(error as Error);
|
|
233
|
+
} catch (handlerError) {
|
|
234
|
+
this.log.error("Form onError handler threw:", handlerError);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
await this.alepha.events.emit(
|
|
238
|
+
"react:action:error",
|
|
239
|
+
{ type: "form", id: this.id, error: error as Error },
|
|
240
|
+
{ catch: true },
|
|
241
|
+
);
|
|
242
|
+
await this.alepha.events.emit(
|
|
243
|
+
"form:submit:error",
|
|
244
|
+
{ error: error as Error, id: this.id },
|
|
245
|
+
{ catch: true },
|
|
246
|
+
);
|
|
235
247
|
} finally {
|
|
236
248
|
this.submitInProgress = false;
|
|
237
|
-
}
|
|
238
249
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
250
|
+
// The "loading off" signals live in `finally` so they ALWAYS fire,
|
|
251
|
+
// even if something above threw — guaranteeing the begin/end pairing
|
|
252
|
+
// that drives submit-button loading state.
|
|
253
|
+
await this.alepha.events.emit(
|
|
254
|
+
"react:action:end",
|
|
255
|
+
{ type: "form", id: this.id },
|
|
256
|
+
{ catch: true },
|
|
257
|
+
);
|
|
258
|
+
await this.alepha.events.emit(
|
|
259
|
+
"form:submit:end",
|
|
260
|
+
{ id: this.id },
|
|
261
|
+
{ catch: true },
|
|
262
|
+
);
|
|
263
|
+
}
|
|
246
264
|
};
|
|
247
265
|
|
|
248
266
|
/**
|
|
@@ -449,6 +449,95 @@ describe("I18nProvider", () => {
|
|
|
449
449
|
});
|
|
450
450
|
});
|
|
451
451
|
|
|
452
|
+
describe("server-side language autodetect (Accept-Language)", () => {
|
|
453
|
+
/**
|
|
454
|
+
* Injects a real, dictionary-populated `I18nProvider` and exposes the
|
|
455
|
+
* protected resolution used by the `server:onRequest` hook, so the priority
|
|
456
|
+
* (cookie → header → fallback) can be unit-tested against a true registry.
|
|
457
|
+
*/
|
|
458
|
+
const setup = (AppClass: new () => unknown) => {
|
|
459
|
+
const alepha = Alepha.create().with(AlephaReactI18n);
|
|
460
|
+
alepha.inject(AppClass as any);
|
|
461
|
+
const i18n = alepha.inject(I18nProvider);
|
|
462
|
+
return {
|
|
463
|
+
i18n,
|
|
464
|
+
resolve: (cookieLang?: string, headerLang?: string): string =>
|
|
465
|
+
(i18n as any).resolveRequestLang(cookieLang, headerLang),
|
|
466
|
+
};
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
class App {
|
|
470
|
+
en = $dictionary({
|
|
471
|
+
lazy: async () => ({ default: { hello: "Hello" } }),
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
fr = $dictionary({
|
|
475
|
+
lazy: async () => ({ default: { hello: "Bonjour" } }),
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
test("uses the Accept-Language header when no cookie is set", ({
|
|
480
|
+
expect,
|
|
481
|
+
}) => {
|
|
482
|
+
expect(setup(App).resolve(undefined, "fr")).toBe("fr");
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
test("lets a manually-selected cookie win over the header", ({
|
|
486
|
+
expect,
|
|
487
|
+
}) => {
|
|
488
|
+
expect(setup(App).resolve("en", "fr")).toBe("en");
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test("falls back when the header language is not registered", ({
|
|
492
|
+
expect,
|
|
493
|
+
}) => {
|
|
494
|
+
expect(setup(App).resolve(undefined, "de")).toBe("en");
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
test("falls back when neither cookie nor header is present", ({
|
|
498
|
+
expect,
|
|
499
|
+
}) => {
|
|
500
|
+
expect(setup(App).resolve(undefined, undefined)).toBe("en");
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
test("normalizes a region-qualified header to a registered base language", ({
|
|
504
|
+
expect,
|
|
505
|
+
}) => {
|
|
506
|
+
expect(setup(App).resolve(undefined, "fr-FR")).toBe("fr");
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test("prefers an exact region match when one is registered", ({
|
|
510
|
+
expect,
|
|
511
|
+
}) => {
|
|
512
|
+
class RegionApp {
|
|
513
|
+
enUS = $dictionary({
|
|
514
|
+
lang: "en-US",
|
|
515
|
+
lazy: async () => ({ default: { color: "color" } }),
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
fr = $dictionary({
|
|
519
|
+
lazy: async () => ({ default: { color: "couleur" } }),
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
expect(setup(RegionApp).resolve(undefined, "en-US")).toBe("en-US");
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
test("ignores the header when autoDetect is disabled", ({ expect }) => {
|
|
527
|
+
const { i18n, resolve } = setup(App);
|
|
528
|
+
i18n.options.autoDetect = false;
|
|
529
|
+
expect(resolve(undefined, "fr")).toBe("en");
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test("still honours a manual cookie when autoDetect is disabled", ({
|
|
533
|
+
expect,
|
|
534
|
+
}) => {
|
|
535
|
+
const { i18n, resolve } = setup(App);
|
|
536
|
+
i18n.options.autoDetect = false;
|
|
537
|
+
expect(resolve("fr", "en")).toBe("fr");
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
452
541
|
test("should handle partial interpolation args", async ({ expect }) => {
|
|
453
542
|
class App {
|
|
454
543
|
en = $dictionary({
|