alepha 0.15.1 → 0.15.3
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 +68 -80
- package/dist/api/audits/index.d.ts +10 -33
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +10 -33
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +10 -3
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +10 -3
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +162 -155
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +10 -3
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +413 -0
- package/dist/api/keys/index.d.ts.map +1 -0
- package/dist/api/keys/index.js +476 -0
- package/dist/api/keys/index.js.map +1 -0
- package/dist/api/notifications/index.d.ts +10 -4
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +10 -4
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +43 -50
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +30 -37
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.d.ts +1081 -760
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +2539 -218
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +138 -132
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +12 -4
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/batch/index.d.ts +20 -40
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +31 -44
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts +440 -8
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +1861 -12
- package/dist/bucket/index.js.map +1 -1
- package/dist/cache/core/index.d.ts +179 -7
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js +213 -7
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/redis/index.d.ts +1 -0
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cache/redis/index.js +4 -0
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/cli/index.d.ts +638 -5645
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2550 -368
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +203 -45
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +2060 -71
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +70 -40
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +34 -13
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +90 -40
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +70 -40
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts +15 -0
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js +15 -0
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/index.d.ts +323 -20
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +1857 -7
- package/dist/email/index.js.map +1 -1
- package/dist/fake/index.d.ts +90 -8
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js +91 -20
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +11 -4
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js +11 -4
- package/dist/lock/core/index.js.map +1 -1
- package/dist/logger/index.d.ts +17 -66
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +14 -63
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +10 -30
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +12 -35
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.browser.js +3 -3
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.bun.js +39 -20
- package/dist/orm/index.bun.js.map +1 -1
- package/dist/orm/index.d.ts +517 -540
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +58 -71
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +18 -10
- package/dist/queue/core/index.d.ts.map +1 -1
- package/dist/queue/core/index.js +14 -6
- package/dist/queue/core/index.js.map +1 -1
- package/dist/react/auth/index.browser.js +108 -0
- package/dist/react/auth/index.browser.js.map +1 -0
- package/dist/react/auth/index.d.ts +100 -0
- package/dist/react/auth/index.d.ts.map +1 -0
- package/dist/react/auth/index.js +145 -0
- package/dist/react/auth/index.js.map +1 -0
- package/dist/react/core/index.d.ts +469 -0
- package/dist/react/core/index.d.ts.map +1 -0
- package/dist/react/core/index.js +464 -0
- package/dist/react/core/index.js.map +1 -0
- package/dist/react/form/index.d.ts +232 -0
- package/dist/react/form/index.d.ts.map +1 -0
- package/dist/react/form/index.js +432 -0
- package/dist/react/form/index.js.map +1 -0
- package/dist/react/head/index.browser.js +423 -0
- package/dist/react/head/index.browser.js.map +1 -0
- package/dist/react/head/index.d.ts +288 -0
- package/dist/react/head/index.d.ts.map +1 -0
- package/dist/react/head/index.js +465 -0
- package/dist/react/head/index.js.map +1 -0
- package/dist/react/i18n/index.d.ts +175 -0
- package/dist/react/i18n/index.d.ts.map +1 -0
- package/dist/react/i18n/index.js +224 -0
- package/dist/react/i18n/index.js.map +1 -0
- package/dist/react/router/index.browser.js +1974 -0
- package/dist/react/router/index.browser.js.map +1 -0
- package/dist/react/router/index.d.ts +1956 -0
- package/dist/react/router/index.d.ts.map +1 -0
- package/dist/react/router/index.js +4722 -0
- package/dist/react/router/index.js.map +1 -0
- package/dist/react/websocket/index.d.ts +117 -0
- package/dist/react/websocket/index.d.ts.map +1 -0
- package/dist/react/websocket/index.js +107 -0
- package/dist/react/websocket/index.js.map +1 -0
- package/dist/redis/index.bun.js +4 -0
- package/dist/redis/index.bun.js.map +1 -1
- package/dist/redis/index.d.ts +41 -44
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js +16 -25
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts +11 -2
- package/dist/retry/index.d.ts.map +1 -1
- package/dist/retry/index.js +11 -2
- package/dist/retry/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +11 -2
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +11 -2
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +140 -49
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +164 -32
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +12 -7
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +12 -7
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +7 -22
- package/dist/server/cache/index.d.ts.map +1 -1
- package/dist/server/cache/index.js +7 -22
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts +10 -2
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/compress/index.js +10 -2
- package/dist/server/compress/index.js.map +1 -1
- package/dist/server/cookies/index.d.ts +40 -16
- package/dist/server/cookies/index.d.ts.map +1 -1
- package/dist/server/cookies/index.js +7 -5
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.d.ts +124 -23
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +231 -14
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +13 -23
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/cors/index.js +7 -21
- package/dist/server/cors/index.js.map +1 -1
- package/dist/server/health/index.d.ts +8 -2
- package/dist/server/health/index.d.ts.map +1 -1
- package/dist/server/health/index.js +8 -2
- package/dist/server/health/index.js.map +1 -1
- package/dist/server/helmet/index.d.ts +11 -3
- package/dist/server/helmet/index.d.ts.map +1 -1
- package/dist/server/helmet/index.js +11 -3
- package/dist/server/helmet/index.js.map +1 -1
- package/dist/server/links/index.d.ts +11 -6
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +11 -6
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +10 -3
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js +10 -3
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts +9 -3
- package/dist/server/multipart/index.d.ts.map +1 -1
- package/dist/server/multipart/index.js +9 -3
- package/dist/server/multipart/index.js.map +1 -1
- package/dist/server/proxy/index.d.ts +8 -2
- package/dist/server/proxy/index.d.ts.map +1 -1
- package/dist/server/proxy/index.js +8 -2
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.d.ts +30 -35
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.js +18 -55
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.d.ts +137 -4
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/static/index.js +1853 -5
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +309 -6
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +1854 -6
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +309 -7
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js +1856 -7
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js +1218 -0
- package/dist/system/index.browser.js.map +1 -0
- package/dist/{file → system}/index.d.ts +343 -16
- package/dist/system/index.d.ts.map +1 -0
- package/dist/{file → system}/index.js +419 -22
- package/dist/system/index.js.map +1 -0
- package/dist/thread/index.d.ts +11 -2
- package/dist/thread/index.d.ts.map +1 -1
- package/dist/thread/index.js +11 -2
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.d.ts +12 -5
- package/dist/topic/core/index.d.ts.map +1 -1
- package/dist/topic/core/index.js +12 -5
- package/dist/topic/core/index.js.map +1 -1
- package/dist/vite/index.d.ts +5 -6272
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +23 -10
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts +12 -8
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +12 -8
- package/dist/websocket/index.js.map +1 -1
- package/package.json +82 -11
- package/src/api/audits/index.ts +10 -33
- package/src/api/files/__tests__/$bucket.spec.ts +1 -1
- package/src/api/files/controllers/AdminFileStatsController.spec.ts +1 -1
- package/src/api/files/controllers/FileController.spec.ts +1 -1
- package/src/api/files/index.ts +10 -3
- package/src/api/files/jobs/FileJobs.spec.ts +1 -1
- package/src/api/files/services/FileService.spec.ts +1 -1
- package/src/api/jobs/index.ts +10 -3
- package/src/api/keys/controllers/AdminApiKeyController.ts +75 -0
- package/src/api/keys/controllers/ApiKeyController.ts +103 -0
- package/src/api/keys/entities/apiKeyEntity.ts +41 -0
- package/src/api/keys/index.ts +49 -0
- package/src/api/keys/schemas/adminApiKeyQuerySchema.ts +7 -0
- package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +17 -0
- package/src/api/keys/schemas/createApiKeyBodySchema.ts +7 -0
- package/src/api/keys/schemas/createApiKeyResponseSchema.ts +11 -0
- package/src/api/keys/schemas/listApiKeyResponseSchema.ts +15 -0
- package/src/api/keys/schemas/revokeApiKeyParamsSchema.ts +5 -0
- package/src/api/keys/schemas/revokeApiKeyResponseSchema.ts +5 -0
- package/src/api/keys/services/ApiKeyService.spec.ts +553 -0
- package/src/api/keys/services/ApiKeyService.ts +306 -0
- package/src/api/logs/TODO.md +55 -0
- package/src/api/notifications/index.ts +10 -4
- package/src/api/parameters/index.ts +9 -30
- package/src/api/parameters/primitives/$config.ts +12 -4
- package/src/api/parameters/services/ConfigStore.ts +9 -3
- package/src/api/users/__tests__/ApiKeys-integration.spec.ts +1035 -0
- package/src/api/users/__tests__/ApiKeys.spec.ts +401 -0
- package/src/api/users/index.ts +14 -3
- package/src/api/users/primitives/$realm.ts +33 -5
- package/src/api/users/providers/RealmProvider.ts +1 -12
- package/src/api/users/services/SessionService.ts +1 -1
- package/src/api/verifications/controllers/VerificationController.ts +2 -0
- package/src/api/verifications/index.ts +10 -4
- package/src/batch/index.ts +9 -36
- package/src/batch/primitives/$batch.ts +0 -8
- package/src/batch/providers/BatchProvider.ts +29 -2
- package/src/bucket/__tests__/shared.ts +1 -1
- package/src/bucket/index.ts +13 -6
- package/src/bucket/primitives/$bucket.ts +1 -1
- package/src/bucket/providers/LocalFileStorageProvider.ts +1 -1
- package/src/bucket/providers/MemoryFileStorageProvider.ts +1 -1
- package/src/cache/core/__tests__/shared.ts +30 -0
- package/src/cache/core/index.ts +11 -6
- package/src/cache/core/primitives/$cache.spec.ts +5 -0
- package/src/cache/core/providers/CacheProvider.ts +17 -0
- package/src/cache/core/providers/MemoryCacheProvider.ts +300 -1
- package/src/cache/redis/__tests__/cache-redis.spec.ts +5 -0
- package/src/cache/redis/providers/RedisCacheProvider.ts +9 -0
- package/src/cli/apps/AlephaCli.ts +1 -14
- package/src/cli/apps/AlephaPackageBuilderCli.ts +10 -1
- package/src/cli/atoms/buildOptions.ts +99 -9
- package/src/cli/commands/build.ts +150 -37
- package/src/cli/commands/db.ts +22 -18
- package/src/cli/commands/deploy.ts +1 -1
- package/src/cli/commands/dev.ts +1 -20
- package/src/cli/commands/gen/env.ts +5 -2
- package/src/cli/commands/gen/openapi.ts +5 -2
- package/src/cli/commands/init.spec.ts +588 -0
- package/src/cli/commands/init.ts +115 -58
- package/src/cli/commands/lint.ts +7 -1
- package/src/cli/commands/typecheck.ts +11 -0
- package/src/cli/providers/AppEntryProvider.ts +1 -1
- package/src/cli/providers/ViteBuildProvider.ts +8 -50
- package/src/cli/providers/ViteDevServerProvider.ts +35 -16
- package/src/cli/services/AlephaCliUtils.ts +52 -121
- package/src/cli/services/PackageManagerUtils.ts +129 -11
- package/src/cli/services/ProjectScaffolder.spec.ts +97 -0
- package/src/cli/services/ProjectScaffolder.ts +148 -81
- package/src/cli/services/ViteUtils.ts +82 -0
- package/src/cli/{assets/claudeMd.ts → templates/agentMd.ts} +37 -24
- package/src/cli/templates/apiAppSecurityTs.ts +11 -0
- package/src/cli/templates/apiIndexTs.ts +30 -0
- package/src/cli/templates/gitignore.ts +39 -0
- package/src/cli/{assets → templates}/mainCss.ts +11 -2
- package/src/cli/templates/mainServerTs.ts +33 -0
- package/src/cli/templates/webAppRouterTs.ts +74 -0
- package/src/cli/templates/webHelloComponentTsx.ts +30 -0
- package/src/command/helpers/Runner.spec.ts +139 -0
- package/src/command/helpers/Runner.ts +7 -22
- package/src/command/index.ts +12 -4
- package/src/command/providers/CliProvider.spec.ts +1392 -0
- package/src/command/providers/CliProvider.ts +320 -47
- package/src/core/Alepha.ts +34 -27
- package/src/core/__tests__/Alepha-start.spec.ts +4 -4
- package/src/core/helpers/jsonSchemaToTypeBox.spec.ts +771 -0
- package/src/core/helpers/jsonSchemaToTypeBox.ts +62 -10
- package/src/core/index.shared.ts +1 -0
- package/src/core/index.ts +20 -0
- package/src/core/providers/EventManager.spec.ts +0 -71
- package/src/core/providers/EventManager.ts +3 -15
- package/src/core/providers/Json.ts +2 -14
- package/src/datetime/index.ts +15 -0
- package/src/email/index.ts +10 -5
- package/src/email/providers/LocalEmailProvider.spec.ts +1 -1
- package/src/email/providers/LocalEmailProvider.ts +1 -1
- package/src/fake/__tests__/keyName.example.ts +1 -1
- package/src/fake/__tests__/keyName.spec.ts +5 -5
- package/src/fake/index.ts +9 -6
- package/src/fake/providers/FakeProvider.spec.ts +258 -40
- package/src/fake/providers/FakeProvider.ts +133 -19
- package/src/lock/core/index.ts +11 -4
- package/src/logger/index.ts +17 -66
- package/src/mcp/index.ts +10 -27
- package/src/mcp/transports/SseMcpTransport.ts +0 -11
- package/src/orm/__tests__/PostgresProvider.spec.ts +2 -2
- package/src/orm/index.browser.ts +2 -2
- package/src/orm/index.bun.ts +5 -3
- package/src/orm/index.ts +23 -53
- package/src/orm/providers/drivers/BunSqliteProvider.ts +5 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +57 -30
- package/src/orm/providers/drivers/DatabaseProvider.ts +9 -1
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +4 -1
- package/src/orm/services/Repository.ts +7 -3
- package/src/queue/core/index.ts +14 -6
- package/src/react/auth/__tests__/$auth.spec.ts +202 -0
- package/src/react/auth/hooks/useAuth.ts +32 -0
- package/src/react/auth/index.browser.ts +13 -0
- package/src/react/auth/index.shared.ts +2 -0
- package/src/react/auth/index.ts +48 -0
- package/src/react/auth/providers/ReactAuthProvider.ts +16 -0
- package/src/react/auth/services/ReactAuth.ts +135 -0
- package/src/react/core/__tests__/Router.spec.tsx +169 -0
- package/src/react/core/components/ClientOnly.tsx +49 -0
- package/src/react/core/components/ErrorBoundary.tsx +73 -0
- package/src/react/core/contexts/AlephaContext.ts +7 -0
- package/src/react/core/contexts/AlephaProvider.tsx +42 -0
- package/src/react/core/hooks/useAction.browser.spec.tsx +569 -0
- package/src/react/core/hooks/useAction.ts +480 -0
- package/src/react/core/hooks/useAlepha.ts +26 -0
- package/src/react/core/hooks/useClient.ts +17 -0
- package/src/react/core/hooks/useEvents.ts +51 -0
- package/src/react/core/hooks/useInject.ts +12 -0
- package/src/react/core/hooks/useStore.ts +52 -0
- package/src/react/core/index.ts +90 -0
- package/src/react/form/components/FormState.tsx +17 -0
- package/src/react/form/errors/FormValidationError.ts +18 -0
- package/src/react/form/hooks/useForm.browser.spec.tsx +366 -0
- package/src/react/form/hooks/useForm.ts +47 -0
- package/src/react/form/hooks/useFormState.ts +130 -0
- package/src/react/form/index.ts +44 -0
- package/src/react/form/services/FormModel.ts +614 -0
- package/src/react/head/helpers/SeoExpander.spec.ts +203 -0
- package/src/react/head/helpers/SeoExpander.ts +142 -0
- package/src/react/head/hooks/useHead.spec.tsx +288 -0
- package/src/react/head/hooks/useHead.ts +62 -0
- package/src/react/head/index.browser.ts +26 -0
- package/src/react/head/index.ts +44 -0
- package/src/react/head/interfaces/Head.ts +105 -0
- package/src/react/head/primitives/$head.ts +25 -0
- package/src/react/head/providers/BrowserHeadProvider.browser.spec.ts +196 -0
- package/src/react/head/providers/BrowserHeadProvider.ts +212 -0
- package/src/react/head/providers/HeadProvider.ts +168 -0
- package/src/react/head/providers/ServerHeadProvider.ts +31 -0
- package/src/react/i18n/__tests__/integration.spec.tsx +239 -0
- package/src/react/i18n/components/Localize.spec.tsx +357 -0
- package/src/react/i18n/components/Localize.tsx +35 -0
- package/src/react/i18n/hooks/useI18n.browser.spec.tsx +438 -0
- package/src/react/i18n/hooks/useI18n.ts +18 -0
- package/src/react/i18n/index.ts +41 -0
- package/src/react/i18n/primitives/$dictionary.ts +69 -0
- package/src/react/i18n/providers/I18nProvider.spec.ts +389 -0
- package/src/react/i18n/providers/I18nProvider.ts +278 -0
- package/src/react/router/__tests__/page-head-browser.browser.spec.ts +95 -0
- package/src/react/router/__tests__/page-head.spec.ts +48 -0
- package/src/react/router/__tests__/seo-head.spec.ts +125 -0
- package/src/react/router/atoms/ssrManifestAtom.ts +58 -0
- package/src/react/router/components/ErrorViewer.tsx +872 -0
- package/src/react/router/components/Link.tsx +23 -0
- package/src/react/router/components/NestedView.tsx +223 -0
- package/src/react/router/components/NotFound.tsx +30 -0
- package/src/react/router/constants/PAGE_PRELOAD_KEY.ts +6 -0
- package/src/react/router/contexts/RouterLayerContext.ts +12 -0
- package/src/react/router/errors/Redirection.ts +28 -0
- package/src/react/router/hooks/useActive.ts +52 -0
- package/src/react/router/hooks/useQueryParams.ts +63 -0
- package/src/react/router/hooks/useRouter.ts +20 -0
- package/src/react/router/hooks/useRouterState.ts +11 -0
- package/src/react/router/index.browser.ts +45 -0
- package/src/react/router/index.shared.ts +19 -0
- package/src/react/router/index.ts +146 -0
- package/src/react/router/primitives/$page.browser.spec.tsx +851 -0
- package/src/react/router/primitives/$page.spec.tsx +676 -0
- package/src/react/router/primitives/$page.ts +489 -0
- package/src/react/router/providers/ReactBrowserProvider.ts +312 -0
- package/src/react/router/providers/ReactBrowserRendererProvider.ts +25 -0
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +168 -0
- package/src/react/router/providers/ReactPageProvider.ts +726 -0
- package/src/react/router/providers/ReactPreloadProvider.spec.ts +142 -0
- package/src/react/router/providers/ReactPreloadProvider.ts +85 -0
- package/src/react/router/providers/ReactServerProvider.spec.tsx +316 -0
- package/src/react/router/providers/ReactServerProvider.ts +487 -0
- package/src/react/router/providers/ReactServerTemplateProvider.spec.ts +210 -0
- package/src/react/router/providers/ReactServerTemplateProvider.ts +542 -0
- package/src/react/router/providers/SSRManifestProvider.ts +334 -0
- package/src/react/router/services/ReactPageServerService.ts +48 -0
- package/src/react/router/services/ReactPageService.ts +27 -0
- package/src/react/router/services/ReactRouter.ts +262 -0
- package/src/react/websocket/hooks/useRoom.tsx +242 -0
- package/src/react/websocket/index.ts +7 -0
- package/src/redis/__tests__/redis.spec.ts +13 -0
- package/src/redis/index.ts +9 -25
- package/src/redis/providers/BunRedisProvider.ts +9 -0
- package/src/redis/providers/NodeRedisProvider.ts +8 -0
- package/src/redis/providers/RedisProvider.ts +16 -0
- package/src/retry/index.ts +11 -2
- package/src/router/index.ts +15 -0
- package/src/scheduler/index.ts +11 -2
- package/src/security/__tests__/BasicAuth.spec.ts +2 -0
- package/src/security/__tests__/ServerSecurityProvider.spec.ts +90 -5
- package/src/security/index.ts +15 -10
- package/src/security/interfaces/IssuerResolver.ts +27 -0
- package/src/security/primitives/$issuer.ts +55 -0
- package/src/security/providers/SecurityProvider.ts +179 -0
- package/src/security/providers/ServerBasicAuthProvider.ts +6 -2
- package/src/security/providers/ServerSecurityProvider.ts +63 -41
- package/src/server/auth/index.ts +12 -7
- package/src/server/cache/index.ts +7 -22
- package/src/server/compress/index.ts +10 -2
- package/src/server/cookies/index.ts +7 -5
- package/src/server/cookies/primitives/$cookie.ts +33 -11
- package/src/server/core/index.ts +16 -6
- package/src/server/core/interfaces/ServerRequest.ts +83 -1
- package/src/server/core/primitives/$action.spec.ts +1 -1
- package/src/server/core/primitives/$action.ts +8 -3
- package/src/server/core/providers/NodeHttpServerProvider.spec.ts +9 -3
- package/src/server/core/providers/NodeHttpServerProvider.ts +9 -3
- package/src/server/core/services/ServerRequestParser.spec.ts +520 -0
- package/src/server/core/services/ServerRequestParser.ts +306 -13
- package/src/server/cors/index.ts +7 -21
- package/src/server/cors/primitives/$cors.ts +6 -2
- package/src/server/health/index.ts +8 -2
- package/src/server/helmet/index.ts +11 -3
- package/src/server/links/index.ts +11 -6
- package/src/server/metrics/index.ts +10 -3
- package/src/server/multipart/index.ts +9 -3
- package/src/server/proxy/index.ts +8 -2
- package/src/server/rate-limit/index.ts +21 -25
- package/src/server/rate-limit/primitives/$rateLimit.ts +6 -2
- package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +38 -14
- package/src/server/rate-limit/providers/ServerRateLimitProvider.ts +22 -56
- package/src/server/static/index.ts +8 -2
- package/src/server/static/providers/ServerStaticProvider.ts +1 -1
- package/src/server/swagger/index.ts +9 -4
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +1 -1
- package/src/sms/index.ts +9 -5
- package/src/sms/providers/LocalSmsProvider.spec.ts +1 -1
- package/src/sms/providers/LocalSmsProvider.ts +1 -1
- package/src/system/index.browser.ts +36 -0
- package/src/system/index.ts +62 -0
- package/src/system/index.workerd.ts +1 -0
- package/src/{file → system}/providers/FileSystemProvider.ts +24 -0
- package/src/{file → system}/providers/MemoryFileSystemProvider.ts +116 -3
- package/src/system/providers/MemoryShellProvider.ts +164 -0
- package/src/{file → system}/providers/NodeFileSystemProvider.spec.ts +2 -2
- package/src/{file → system}/providers/NodeFileSystemProvider.ts +47 -2
- package/src/system/providers/NodeShellProvider.ts +184 -0
- package/src/system/providers/ShellProvider.ts +74 -0
- package/src/{file → system}/services/FileDetector.spec.ts +2 -2
- package/src/thread/index.ts +11 -2
- package/src/topic/core/index.ts +12 -5
- package/src/vite/tasks/buildClient.ts +2 -7
- package/src/vite/tasks/buildServer.ts +19 -13
- package/src/vite/tasks/generateCloudflare.ts +10 -7
- package/src/vite/tasks/generateDocker.ts +4 -0
- package/src/websocket/index.ts +12 -8
- package/dist/file/index.d.ts.map +0 -1
- package/dist/file/index.js.map +0 -1
- package/src/cli/assets/apiIndexTs.ts +0 -16
- package/src/cli/assets/mainServerTs.ts +0 -24
- package/src/cli/assets/webAppRouterTs.ts +0 -16
- package/src/cli/assets/webHelloComponentTsx.ts +0 -20
- package/src/cli/providers/ViteTemplateProvider.ts +0 -27
- package/src/file/index.ts +0 -43
- /package/src/cli/{assets → templates}/apiHelloControllerTs.ts +0 -0
- /package/src/cli/{assets → templates}/biomeJson.ts +0 -0
- /package/src/cli/{assets → templates}/dummySpecTs.ts +0 -0
- /package/src/cli/{assets → templates}/editorconfig.ts +0 -0
- /package/src/cli/{assets → templates}/mainBrowserTs.ts +0 -0
- /package/src/cli/{assets → templates}/tsconfigJson.ts +0 -0
- /package/src/cli/{assets → templates}/webIndexTs.ts +0 -0
- /package/src/{file → system}/errors/FileError.ts +0 -0
- /package/src/{file → system}/services/FileDetector.ts +0 -0
|
@@ -64,7 +64,7 @@ export class CloudflareD1Provider extends DatabaseProvider {
|
|
|
64
64
|
protected readonly env = $env(
|
|
65
65
|
t.object({
|
|
66
66
|
DATABASE_URL: t.string({
|
|
67
|
-
description: "Expect to be '
|
|
67
|
+
description: "Expect to be 'd1://name:id'",
|
|
68
68
|
}),
|
|
69
69
|
}),
|
|
70
70
|
);
|
|
@@ -104,41 +104,68 @@ export class CloudflareD1Provider extends DatabaseProvider {
|
|
|
104
104
|
protected readonly onStart = $hook({
|
|
105
105
|
on: "start",
|
|
106
106
|
handler: async () => {
|
|
107
|
-
|
|
108
|
-
"
|
|
109
|
-
|
|
110
|
-
).split(":");
|
|
111
|
-
const cloudflareEnv = this.alepha.store.get("cloudflare.env" as any);
|
|
112
|
-
if (!cloudflareEnv) {
|
|
113
|
-
throw new AlephaError(
|
|
114
|
-
"Cloudflare Workers environment not found in Alepha store under 'cloudflare.env'.",
|
|
107
|
+
try {
|
|
108
|
+
const [bindingName] = this.env.DATABASE_URL.replace("d1://", "").split(
|
|
109
|
+
":",
|
|
115
110
|
);
|
|
111
|
+
const cloudflareEnv = this.alepha.store.get("cloudflare.env" as any);
|
|
112
|
+
if (!cloudflareEnv) {
|
|
113
|
+
throw new AlephaError(
|
|
114
|
+
"Cloudflare Workers environment not found in Alepha store under 'cloudflare.env'.",
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const binding = cloudflareEnv[bindingName] as D1Database;
|
|
119
|
+
if (!binding) {
|
|
120
|
+
throw new AlephaError(
|
|
121
|
+
`D1 binding '${bindingName}' not found in Cloudflare Workers environment.`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this.d1 = binding;
|
|
126
|
+
|
|
127
|
+
// Dynamic import to avoid crashes when not on Cloudflare
|
|
128
|
+
const { drizzle } = await import("drizzle-orm/d1");
|
|
129
|
+
|
|
130
|
+
this.drizzleDb = drizzle(this.d1) as DrizzleD1Database;
|
|
131
|
+
|
|
132
|
+
// Never migrate in serverless mode - D1 migrations must be applied
|
|
133
|
+
// via `wrangler d1 migrations apply` before deployment
|
|
134
|
+
if (!this.alepha.isServerless()) {
|
|
135
|
+
await this.migrate();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.log.info("Using Cloudflare D1 database");
|
|
139
|
+
} catch (error) {
|
|
140
|
+
// Log the full error for debugging since Cloudflare Workers
|
|
141
|
+
// doesn't properly display error causes
|
|
142
|
+
const errorMessage =
|
|
143
|
+
error instanceof Error
|
|
144
|
+
? `${error.message}${error.stack ? `\n${error.stack}` : ""}`
|
|
145
|
+
: String(error);
|
|
146
|
+
this.log.error(`D1 initialization failed: ${errorMessage}`);
|
|
147
|
+
throw error;
|
|
116
148
|
}
|
|
117
|
-
|
|
118
|
-
const binding = cloudflareEnv[bindingName] as D1Database;
|
|
119
|
-
if (!binding) {
|
|
120
|
-
throw new AlephaError(
|
|
121
|
-
`D1 binding '${bindingName}' not found in Cloudflare Workers environment.`,
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
this.d1 = binding;
|
|
126
|
-
|
|
127
|
-
// Dynamic import to avoid crashes when not on Cloudflare
|
|
128
|
-
const { drizzle } = await import("drizzle-orm/d1");
|
|
129
|
-
|
|
130
|
-
this.drizzleDb = drizzle(this.d1) as DrizzleD1Database;
|
|
131
|
-
|
|
132
|
-
await this.migrate();
|
|
133
|
-
|
|
134
|
-
this.log.info("Using Cloudflare D1 database");
|
|
135
149
|
},
|
|
136
150
|
});
|
|
137
151
|
|
|
138
152
|
protected async executeMigrations(migrationsFolder: string): Promise<void> {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
153
|
+
this.log.debug(`Running D1 migrations from '${migrationsFolder}'...`);
|
|
154
|
+
try {
|
|
155
|
+
// Dynamic import for D1 migrator
|
|
156
|
+
const { migrate } = await import("drizzle-orm/d1/migrator");
|
|
157
|
+
await migrate(this.db as any, { migrationsFolder });
|
|
158
|
+
this.log.debug("D1 migrations completed successfully");
|
|
159
|
+
} catch (error) {
|
|
160
|
+
const errorMessage =
|
|
161
|
+
error instanceof Error
|
|
162
|
+
? `${error.name}: ${error.message}`
|
|
163
|
+
: String(error);
|
|
164
|
+
throw new AlephaError(
|
|
165
|
+
`D1 migration failed from '${migrationsFolder}': ${errorMessage}`,
|
|
166
|
+
{ cause: error },
|
|
167
|
+
);
|
|
168
|
+
}
|
|
142
169
|
}
|
|
143
170
|
|
|
144
171
|
/**
|
|
@@ -97,9 +97,17 @@ export abstract class DatabaseProvider {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
/**
|
|
100
|
-
* Base migration orchestration - handles environment logic
|
|
100
|
+
* Base migration orchestration - handles environment logic.
|
|
101
|
+
*
|
|
102
|
+
* Never runs in serverless mode - migrations should be applied during
|
|
103
|
+
* deployment, not at runtime (to avoid race conditions and timeouts).
|
|
101
104
|
*/
|
|
102
105
|
public async migrate(): Promise<void> {
|
|
106
|
+
// Never migrate in serverless mode - migrations should be applied during deployment
|
|
107
|
+
if (this.alepha.isServerless()) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
103
111
|
const migrationsFolder = this.getMigrationsFolder();
|
|
104
112
|
|
|
105
113
|
// Handle different environments
|
|
@@ -157,7 +157,10 @@ export class NodeSqliteProvider extends DatabaseProvider {
|
|
|
157
157
|
|
|
158
158
|
this.sqlite = new DatabaseSync(filepath);
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
// Never migrate in serverless mode - migrations should be applied during deployment
|
|
161
|
+
if (!this.alepha.isServerless()) {
|
|
162
|
+
await this.migrate();
|
|
163
|
+
}
|
|
161
164
|
|
|
162
165
|
this.log.info(`Using SQLite database at ${filepath}`);
|
|
163
166
|
},
|
|
@@ -616,7 +616,7 @@ export abstract class Repository<T extends TObject> {
|
|
|
616
616
|
*/
|
|
617
617
|
public async updateOne(
|
|
618
618
|
where: PgQueryWhereOrSQL<T>,
|
|
619
|
-
data:
|
|
619
|
+
data: WithSQL<Static<TObjectUpdate<T>>>,
|
|
620
620
|
opts: StatementOptions = {},
|
|
621
621
|
): Promise<Static<T>> {
|
|
622
622
|
await this.alepha.events.emit("repository:update:before", {
|
|
@@ -767,7 +767,7 @@ export abstract class Repository<T extends TObject> {
|
|
|
767
767
|
*/
|
|
768
768
|
public async updateById(
|
|
769
769
|
id: string | number,
|
|
770
|
-
data:
|
|
770
|
+
data: WithSQL<Static<TObjectUpdate<T>>>,
|
|
771
771
|
opts: StatementOptions = {},
|
|
772
772
|
): Promise<Static<T>> {
|
|
773
773
|
return await this.updateOne(this.getWhereId(id), data, opts);
|
|
@@ -778,7 +778,7 @@ export abstract class Repository<T extends TObject> {
|
|
|
778
778
|
*/
|
|
779
779
|
public async updateMany(
|
|
780
780
|
where: PgQueryWhereOrSQL<T>,
|
|
781
|
-
data:
|
|
781
|
+
data: WithSQL<Static<TObjectUpdate<T>>>,
|
|
782
782
|
opts: StatementOptions = {},
|
|
783
783
|
): Promise<Array<number | string>> {
|
|
784
784
|
await this.alepha.events.emit("repository:update:before", {
|
|
@@ -1177,3 +1177,7 @@ export interface StatementOptions {
|
|
|
1177
1177
|
*/
|
|
1178
1178
|
now?: DateTime | string;
|
|
1179
1179
|
}
|
|
1180
|
+
|
|
1181
|
+
type WithSQL<T> = {
|
|
1182
|
+
[P in keyof T]?: T[P] | SQL;
|
|
1183
|
+
};
|
package/src/queue/core/index.ts
CHANGED
|
@@ -16,14 +16,22 @@ export * from "./providers/WorkerProvider.ts";
|
|
|
16
16
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* | type | quality | stability |
|
|
20
|
+
* |------|---------|-----------|
|
|
21
|
+
* | backend | epic | stable |
|
|
20
22
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
23
|
+
* Asynchronous message processing with automatic worker management.
|
|
24
|
+
*
|
|
25
|
+
* **Features:**
|
|
26
|
+
* - Background job queues with type-safe payloads
|
|
27
|
+
* - Queue consumer handlers
|
|
28
|
+
* - Automatic worker threads for non-blocking processing
|
|
29
|
+
* - Retry mechanisms with exponential backoff
|
|
30
|
+
* - Dead letter queues for failed messages
|
|
31
|
+
* - Batch processing support
|
|
32
|
+
* - Configurable concurrency and worker pools
|
|
33
|
+
* - Providers: Memory (dev), Redis (production)
|
|
24
34
|
*
|
|
25
|
-
* @see {@link $queue}
|
|
26
|
-
* @see {@link $consumer}
|
|
27
35
|
* @module alepha.queue
|
|
28
36
|
*/
|
|
29
37
|
export const AlephaQueue = $module({
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Alepha } from "alepha";
|
|
3
|
+
import { DateTimeProvider } from "alepha/datetime";
|
|
4
|
+
import { $issuer, AlephaSecurity } from "alepha/security";
|
|
5
|
+
import { AlephaServer, HttpClient, ServerProvider } from "alepha/server";
|
|
6
|
+
import {
|
|
7
|
+
$auth,
|
|
8
|
+
alephaServerAuthRoutes,
|
|
9
|
+
type TokenResponse,
|
|
10
|
+
tokenResponseSchema,
|
|
11
|
+
tokensSchema,
|
|
12
|
+
} from "alepha/server/auth";
|
|
13
|
+
import { $client } from "alepha/server/links";
|
|
14
|
+
import { describe, test } from "vitest";
|
|
15
|
+
import { ReactAuth, type ReactAuthProvider } from "../index.ts";
|
|
16
|
+
|
|
17
|
+
describe("$auth", () => {
|
|
18
|
+
const user = {
|
|
19
|
+
id: randomUUID(),
|
|
20
|
+
name: "John Doe",
|
|
21
|
+
username: "john",
|
|
22
|
+
password: "***",
|
|
23
|
+
roles: ["admin"],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
class App {
|
|
27
|
+
issuer = $issuer({
|
|
28
|
+
secret: "my-secret-key",
|
|
29
|
+
roles: [
|
|
30
|
+
{
|
|
31
|
+
name: "admin",
|
|
32
|
+
permissions: [{ name: "*" }],
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
auth = $auth({
|
|
38
|
+
issuer: this.issuer,
|
|
39
|
+
credentials: {
|
|
40
|
+
account: () => user,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
api = $client<ReactAuthProvider>();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const userinfo = (alepha: Alepha, token?: string) =>
|
|
48
|
+
alepha
|
|
49
|
+
.inject(HttpClient)
|
|
50
|
+
.fetch(
|
|
51
|
+
`${alepha.inject(ServerProvider).hostname}${alephaServerAuthRoutes.userinfo}`,
|
|
52
|
+
{
|
|
53
|
+
method: "GET",
|
|
54
|
+
headers: {
|
|
55
|
+
authorization: `Bearer ${token}`,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
.then((it) => it.data);
|
|
60
|
+
|
|
61
|
+
const login = (alepha: Alepha) =>
|
|
62
|
+
alepha
|
|
63
|
+
.inject(HttpClient)
|
|
64
|
+
.fetch(
|
|
65
|
+
`${alepha.inject(ServerProvider).hostname}${alephaServerAuthRoutes.token}?provider=auth`,
|
|
66
|
+
{
|
|
67
|
+
method: "POST",
|
|
68
|
+
body: JSON.stringify({
|
|
69
|
+
username: user.username,
|
|
70
|
+
password: user.password,
|
|
71
|
+
}),
|
|
72
|
+
schema: {
|
|
73
|
+
response: tokenResponseSchema,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const refresh = (alepha: Alepha, tokens: TokenResponse) =>
|
|
79
|
+
alepha
|
|
80
|
+
.inject(HttpClient)
|
|
81
|
+
.fetch(
|
|
82
|
+
`${alepha.inject(ServerProvider).hostname}${alephaServerAuthRoutes.refresh}?provider=auth`,
|
|
83
|
+
{
|
|
84
|
+
method: "POST",
|
|
85
|
+
body: JSON.stringify({
|
|
86
|
+
refresh_token: tokens.refresh_token!,
|
|
87
|
+
access_token: tokens.access_token,
|
|
88
|
+
}),
|
|
89
|
+
schema: {
|
|
90
|
+
response: tokensSchema,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
test("should login with credentials", async ({ expect }) => {
|
|
96
|
+
const alepha = Alepha.create()
|
|
97
|
+
.with(AlephaServer)
|
|
98
|
+
.with(AlephaSecurity)
|
|
99
|
+
.with(App);
|
|
100
|
+
const auth = alepha.inject(ReactAuth);
|
|
101
|
+
await alepha.start();
|
|
102
|
+
|
|
103
|
+
expect(auth.user).toBeUndefined();
|
|
104
|
+
await auth.login("auth", {
|
|
105
|
+
username: user.username,
|
|
106
|
+
password: user.password,
|
|
107
|
+
hostname: alepha.inject(ServerProvider).hostname,
|
|
108
|
+
});
|
|
109
|
+
expect(auth.user).toEqual({
|
|
110
|
+
id: user.id,
|
|
111
|
+
name: user.name,
|
|
112
|
+
roles: user.roles,
|
|
113
|
+
username: user.username,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("should get userinfo", async ({ expect }) => {
|
|
118
|
+
const alepha = Alepha.create()
|
|
119
|
+
.with(AlephaServer)
|
|
120
|
+
.with(AlephaSecurity)
|
|
121
|
+
.with(App);
|
|
122
|
+
await alepha.start();
|
|
123
|
+
|
|
124
|
+
const { data: tokens } = await login(alepha);
|
|
125
|
+
|
|
126
|
+
expect(await userinfo(alepha, tokens.access_token)).toEqual({
|
|
127
|
+
user: {
|
|
128
|
+
id: user.id,
|
|
129
|
+
name: user.name,
|
|
130
|
+
roles: user.roles,
|
|
131
|
+
username: user.username,
|
|
132
|
+
sessionId: expect.any(String),
|
|
133
|
+
},
|
|
134
|
+
api: {
|
|
135
|
+
prefix: "/api",
|
|
136
|
+
links: [],
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("should reject expired token", async ({ expect }) => {
|
|
142
|
+
const alepha = Alepha.create()
|
|
143
|
+
.with(AlephaServer)
|
|
144
|
+
.with(AlephaSecurity)
|
|
145
|
+
.with(App);
|
|
146
|
+
await alepha.start();
|
|
147
|
+
|
|
148
|
+
const { data: tokens } = await login(alepha);
|
|
149
|
+
|
|
150
|
+
await alepha.inject(DateTimeProvider).travel(1, "hour");
|
|
151
|
+
|
|
152
|
+
expect(await userinfo(alepha, tokens.access_token)).toEqual({
|
|
153
|
+
api: {
|
|
154
|
+
prefix: "/api",
|
|
155
|
+
links: [],
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("should refresh expired token", async ({ expect }) => {
|
|
161
|
+
const alepha = Alepha.create()
|
|
162
|
+
.with(AlephaServer)
|
|
163
|
+
.with(AlephaSecurity)
|
|
164
|
+
.with(App);
|
|
165
|
+
await alepha.start();
|
|
166
|
+
|
|
167
|
+
const { data: tokens } = await login(alepha);
|
|
168
|
+
|
|
169
|
+
await alepha.inject(DateTimeProvider).travel(1, "hour");
|
|
170
|
+
|
|
171
|
+
const { data: tokens2 } = await refresh(alepha, tokens);
|
|
172
|
+
|
|
173
|
+
expect(await userinfo(alepha, tokens2.access_token)).toEqual({
|
|
174
|
+
user: {
|
|
175
|
+
id: user.id,
|
|
176
|
+
name: user.name,
|
|
177
|
+
roles: user.roles,
|
|
178
|
+
username: user.username,
|
|
179
|
+
},
|
|
180
|
+
api: {
|
|
181
|
+
prefix: "/api",
|
|
182
|
+
links: [],
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("should reject expired refresh token", async ({ expect }) => {
|
|
188
|
+
const alepha = Alepha.create()
|
|
189
|
+
.with(AlephaServer)
|
|
190
|
+
.with(AlephaSecurity)
|
|
191
|
+
.with(App);
|
|
192
|
+
await alepha.start();
|
|
193
|
+
|
|
194
|
+
const { data: tokens } = await login(alepha);
|
|
195
|
+
|
|
196
|
+
await alepha.inject(DateTimeProvider).travel(40, "days");
|
|
197
|
+
|
|
198
|
+
await expect(refresh(alepha, tokens)).rejects.toThrowError(
|
|
199
|
+
"Failed to refresh access token using the refresh token (issuer)",
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useAlepha, useStore } from "alepha/react";
|
|
2
|
+
import { type HttpVirtualClient, LinkProvider } from "alepha/server/links";
|
|
3
|
+
import { ReactAuth } from "../services/ReactAuth.ts";
|
|
4
|
+
|
|
5
|
+
export const useAuth = <T extends object = any>() => {
|
|
6
|
+
const alepha = useAlepha();
|
|
7
|
+
const [user] = useStore("alepha.server.request.user");
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
user,
|
|
11
|
+
logout: () => {
|
|
12
|
+
alepha.inject(ReactAuth).logout();
|
|
13
|
+
},
|
|
14
|
+
login: async (
|
|
15
|
+
provider: keyof T,
|
|
16
|
+
options: {
|
|
17
|
+
username?: string;
|
|
18
|
+
password?: string;
|
|
19
|
+
redirect?: string;
|
|
20
|
+
realm?: string;
|
|
21
|
+
[extra: string]: any;
|
|
22
|
+
} = {},
|
|
23
|
+
) => {
|
|
24
|
+
await alepha.inject(ReactAuth).login(provider as string, options);
|
|
25
|
+
},
|
|
26
|
+
can: <Api extends object = any>(
|
|
27
|
+
name: keyof HttpVirtualClient<Api>,
|
|
28
|
+
): boolean => {
|
|
29
|
+
return alepha.inject(LinkProvider).can(name as string);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { $module } from "alepha";
|
|
2
|
+
import { ReactAuth } from "./services/ReactAuth.ts";
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
5
|
+
|
|
6
|
+
export * from "./index.shared.ts";
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
export const AlephaReactAuth = $module({
|
|
11
|
+
name: "alepha.react.auth",
|
|
12
|
+
services: [ReactAuth],
|
|
13
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { $module } from "alepha";
|
|
2
|
+
import { AlephaReact } from "alepha/react";
|
|
3
|
+
import type { UserAccount } from "alepha/security";
|
|
4
|
+
import { $auth, AlephaServerAuth } from "alepha/server/auth";
|
|
5
|
+
import { AlephaServerLinks } from "alepha/server/links";
|
|
6
|
+
import { ReactAuthProvider } from "./providers/ReactAuthProvider.ts";
|
|
7
|
+
import { ReactAuth } from "./services/ReactAuth.ts";
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
export * from "./index.shared.ts";
|
|
12
|
+
export * from "./providers/ReactAuthProvider.ts";
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
declare module "alepha/react/router" {
|
|
17
|
+
interface ReactRouterState {
|
|
18
|
+
user?: UserAccount;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* | type | quality | stability |
|
|
26
|
+
* |------|---------|-----------|
|
|
27
|
+
* | frontend | rare | stable |
|
|
28
|
+
*
|
|
29
|
+
* Auth-related React components and hooks.
|
|
30
|
+
*
|
|
31
|
+
* **Features:**
|
|
32
|
+
* - Login/logout components
|
|
33
|
+
* - Protected route wrappers
|
|
34
|
+
* - Auth state hooks
|
|
35
|
+
*
|
|
36
|
+
* @module alepha.react.auth
|
|
37
|
+
*/
|
|
38
|
+
export const AlephaReactAuth = $module({
|
|
39
|
+
name: "alepha.react.auth",
|
|
40
|
+
primitives: [$auth],
|
|
41
|
+
services: [
|
|
42
|
+
AlephaReact,
|
|
43
|
+
AlephaServerLinks,
|
|
44
|
+
AlephaServerAuth,
|
|
45
|
+
ReactAuthProvider,
|
|
46
|
+
ReactAuth,
|
|
47
|
+
],
|
|
48
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { $hook, $inject, Alepha } from "alepha";
|
|
2
|
+
|
|
3
|
+
export class ReactAuthProvider {
|
|
4
|
+
protected readonly alepha = $inject(Alepha);
|
|
5
|
+
|
|
6
|
+
public readonly onRender = $hook({
|
|
7
|
+
on: "react:server:render:begin",
|
|
8
|
+
handler: async ({ request, state }) => {
|
|
9
|
+
if (request?.user) {
|
|
10
|
+
const { token, realm, ...user } = request.user; // do not send token and realm to the client
|
|
11
|
+
this.alepha.store.set("alepha.server.request.user", user); // for hydration, browser, etc...
|
|
12
|
+
state.user = user;
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { $hook, $inject, Alepha } from "alepha";
|
|
2
|
+
import { $logger } from "alepha/logger";
|
|
3
|
+
import { ReactBrowserProvider, Redirection } from "alepha/react/router";
|
|
4
|
+
import type { UserAccountToken } from "alepha/security";
|
|
5
|
+
import { HttpClient } from "alepha/server";
|
|
6
|
+
import {
|
|
7
|
+
alephaServerAuthRoutes,
|
|
8
|
+
type Tokens,
|
|
9
|
+
tokenResponseSchema,
|
|
10
|
+
userinfoResponseSchema,
|
|
11
|
+
} from "alepha/server/auth";
|
|
12
|
+
import { LinkProvider } from "alepha/server/links";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Browser, SSR friendly, service to handle authentication.
|
|
16
|
+
*/
|
|
17
|
+
export class ReactAuth {
|
|
18
|
+
protected readonly log = $logger();
|
|
19
|
+
protected readonly alepha = $inject(Alepha);
|
|
20
|
+
protected readonly httpClient = $inject(HttpClient);
|
|
21
|
+
protected readonly linkProvider = $inject(LinkProvider);
|
|
22
|
+
|
|
23
|
+
protected readonly onBeginTransition = $hook({
|
|
24
|
+
on: "react:transition:begin",
|
|
25
|
+
handler: async (event) => {
|
|
26
|
+
if (this.alepha.isBrowser()) {
|
|
27
|
+
Object.defineProperty(event.state, "user", {
|
|
28
|
+
get: () => this.user,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
protected readonly onFetchRequest = $hook({
|
|
35
|
+
on: "client:onRequest",
|
|
36
|
+
handler: async ({ request }) => {
|
|
37
|
+
if (this.alepha.isBrowser() && this.user) {
|
|
38
|
+
// ensure cookies are sent with requests and refresh-able
|
|
39
|
+
request.credentials ??= "include";
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the current authenticated user.
|
|
46
|
+
*
|
|
47
|
+
* Alias for `alepha.state.get("user")`
|
|
48
|
+
*/
|
|
49
|
+
public get user(): UserAccountToken | undefined {
|
|
50
|
+
return this.alepha.store.get("alepha.server.request.user");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public async ping() {
|
|
54
|
+
const { data } = await this.httpClient.fetch(
|
|
55
|
+
alephaServerAuthRoutes.userinfo,
|
|
56
|
+
{
|
|
57
|
+
schema: { response: userinfoResponseSchema },
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
this.alepha.store.set("alepha.server.request.apiLinks", data.api);
|
|
62
|
+
this.alepha.store.set("alepha.server.request.user", data.user);
|
|
63
|
+
|
|
64
|
+
return data.user;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public can(action: string): boolean {
|
|
68
|
+
if (!this.user) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return this.linkProvider.can(action);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public async login(
|
|
76
|
+
provider: string,
|
|
77
|
+
options: {
|
|
78
|
+
hostname?: string;
|
|
79
|
+
username?: string;
|
|
80
|
+
password?: string;
|
|
81
|
+
redirect?: string;
|
|
82
|
+
realm?: string;
|
|
83
|
+
[extra: string]: any;
|
|
84
|
+
},
|
|
85
|
+
): Promise<Tokens> {
|
|
86
|
+
const realmParam = options.realm
|
|
87
|
+
? `&realm=${encodeURIComponent(options.realm)}`
|
|
88
|
+
: "";
|
|
89
|
+
|
|
90
|
+
if (options.username || options.password) {
|
|
91
|
+
const { data } = await this.httpClient.fetch(
|
|
92
|
+
`${options.hostname || ""}${alephaServerAuthRoutes.token}?provider=${provider}${realmParam}`,
|
|
93
|
+
{
|
|
94
|
+
method: "POST",
|
|
95
|
+
body: JSON.stringify({
|
|
96
|
+
username: options.username,
|
|
97
|
+
password: options.password,
|
|
98
|
+
}),
|
|
99
|
+
schema: { response: tokenResponseSchema },
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
this.alepha.store.set("alepha.server.request.apiLinks", data.api);
|
|
104
|
+
this.alepha.store.set("alepha.server.request.user", data.user);
|
|
105
|
+
|
|
106
|
+
return data;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (this.alepha.isBrowser()) {
|
|
110
|
+
const browser = this.alepha.inject(ReactBrowserProvider);
|
|
111
|
+
const redirect =
|
|
112
|
+
options.redirect ||
|
|
113
|
+
(browser.transitioning
|
|
114
|
+
? window.location.origin + browser.transitioning.to
|
|
115
|
+
: window.location.href);
|
|
116
|
+
|
|
117
|
+
const href = `${window.location.origin}${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${encodeURIComponent(redirect)}`;
|
|
118
|
+
|
|
119
|
+
if (browser.transitioning) {
|
|
120
|
+
throw new Redirection(href);
|
|
121
|
+
} else {
|
|
122
|
+
window.location.href = href;
|
|
123
|
+
return {} as Tokens;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
throw new Redirection(
|
|
128
|
+
`${alephaServerAuthRoutes.login}?provider=${provider}${realmParam}&redirect_uri=${options.redirect || "/"}`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public logout() {
|
|
133
|
+
window.location.href = `${alephaServerAuthRoutes.logout}?post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}`;
|
|
134
|
+
}
|
|
135
|
+
}
|