alepha 0.15.0 → 0.15.2
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 +43 -98
- package/dist/api/audits/index.d.ts +630 -653
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +12 -35
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +365 -358
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +12 -5
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +255 -248
- 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.browser.js +4 -4
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +84 -78
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +14 -8
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +528 -535
- 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 +1221 -910
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +2556 -248
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +142 -136
- 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 +142 -162
- 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 +595 -171
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +1856 -12
- package/dist/bucket/index.js.map +1 -1
- package/dist/cache/core/index.d.ts +225 -53
- 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 +6 -2
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/cli/index.d.ts +834 -226
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2872 -417
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +458 -310
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +2011 -76
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +309 -97
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +796 -701
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +329 -97
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +309 -97
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts +59 -44
- 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 +314 -19
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +1852 -7
- package/dist/email/index.js.map +1 -1
- package/dist/fake/index.d.ts +5500 -5418
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js +113 -42
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +219 -212
- 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/lock/redis/index.d.ts.map +1 -1
- package/dist/logger/index.d.ts +41 -90
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +15 -68
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +228 -230
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +32 -31
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.browser.js +12 -12
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.bun.js +90 -80
- package/dist/orm/index.bun.js.map +1 -1
- package/dist/orm/index.d.ts +1434 -1459
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +112 -130
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +262 -254
- 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/queue/redis/index.d.ts.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 +1980 -0
- package/dist/react/router/index.browser.js.map +1 -0
- package/dist/react/router/index.d.ts +2068 -0
- package/dist/react/router/index.d.ts.map +1 -0
- package/dist/react/router/index.js +4932 -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 +127 -130
- 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 +80 -71
- 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/router/index.d.ts +6 -6
- package/dist/router/index.d.ts.map +1 -1
- package/dist/scheduler/index.d.ts +119 -28
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +404 -3
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +642 -228
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +1579 -37
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +1141 -111
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +1261 -25
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +63 -78
- 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 +13 -5
- 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 +46 -22
- 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 +307 -196
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +271 -38
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +24 -34
- 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 +25 -19
- 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 +13 -5
- 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.browser.js +9 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +133 -128
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +24 -11
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +524 -4
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js +4472 -7
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts +15 -9
- 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 +110 -104
- 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 +46 -51
- 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 +181 -48
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/static/index.js +1848 -5
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +348 -53
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +1849 -6
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +312 -18
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js +1854 -10
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js +496 -0
- package/dist/system/index.browser.js.map +1 -0
- package/dist/system/index.d.ts +1158 -0
- package/dist/system/index.d.ts.map +1 -0
- package/dist/{file → system}/index.js +412 -20
- package/dist/system/index.js.map +1 -0
- package/dist/thread/index.d.ts +82 -73
- package/dist/thread/index.d.ts.map +1 -1
- package/dist/thread/index.js +13 -4
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.d.ts +330 -323
- 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/topic/redis/index.d.ts +6 -6
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/dist/vite/index.d.ts +163 -5825
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +130 -477
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js +3 -3
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +287 -283
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +15 -11
- package/dist/websocket/index.js.map +1 -1
- package/package.json +86 -17
- 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 +52 -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 -11
- 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 +3 -16
- package/src/cli/apps/AlephaPackageBuilderCli.ts +10 -2
- package/src/cli/atoms/appEntryOptions.ts +13 -0
- package/src/cli/atoms/buildOptions.ts +1 -1
- package/src/cli/atoms/changelogOptions.ts +1 -1
- package/src/cli/commands/build.ts +64 -52
- package/src/cli/commands/db.ts +17 -11
- package/src/cli/commands/deploy.ts +1 -1
- package/src/cli/commands/dev.ts +13 -49
- package/src/cli/commands/gen/env.ts +6 -3
- package/src/cli/commands/gen/openapi.ts +5 -2
- package/src/cli/commands/init.spec.ts +544 -0
- package/src/cli/commands/init.ts +101 -58
- package/src/cli/commands/lint.ts +8 -2
- package/src/cli/commands/typecheck.ts +11 -0
- package/src/cli/defineConfig.ts +9 -0
- package/src/cli/index.ts +2 -1
- package/src/cli/providers/AppEntryProvider.ts +131 -0
- package/src/cli/providers/ViteBuildProvider.ts +40 -0
- package/src/cli/providers/ViteDevServerProvider.ts +378 -0
- package/src/cli/services/AlephaCliUtils.ts +39 -93
- package/src/cli/services/PackageManagerUtils.ts +140 -17
- package/src/cli/services/ProjectScaffolder.ts +169 -101
- package/src/cli/services/ViteUtils.ts +82 -0
- package/src/cli/{assets/claudeMd.ts → templates/agentMd.ts} +41 -28
- package/src/cli/{assets → templates}/apiHelloControllerTs.ts +2 -1
- package/src/cli/{assets → templates}/biomeJson.ts +2 -1
- package/src/cli/{assets → templates}/dummySpecTs.ts +2 -1
- package/src/cli/{assets → templates}/editorconfig.ts +2 -1
- package/src/cli/templates/gitignore.ts +39 -0
- package/src/cli/{assets → templates}/mainBrowserTs.ts +2 -1
- package/src/cli/templates/mainCss.ts +33 -0
- package/src/cli/templates/mainServerTs.ts +33 -0
- package/src/cli/{assets → templates}/tsconfigJson.ts +2 -1
- package/src/cli/templates/webAppRouterTs.ts +50 -0
- package/src/cli/templates/webHelloComponentTsx.ts +20 -0
- package/src/command/helpers/Runner.spec.ts +4 -0
- package/src/command/helpers/Runner.ts +3 -21
- package/src/command/index.ts +12 -4
- package/src/command/providers/CliProvider.spec.ts +1067 -0
- package/src/command/providers/CliProvider.ts +203 -40
- package/src/core/Alepha.ts +3 -9
- 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/primitives/$module.ts +12 -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/core/providers/KeylessJsonSchemaCodec.spec.ts +257 -0
- package/src/core/providers/KeylessJsonSchemaCodec.ts +396 -14
- package/src/core/providers/SchemaValidator.spec.ts +236 -0
- 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/logger/providers/PrettyFormatterProvider.ts +0 -9
- package/src/mcp/errors/McpError.ts +30 -0
- package/src/mcp/index.ts +13 -27
- package/src/mcp/transports/SseMcpTransport.ts +6 -7
- package/src/orm/__tests__/PostgresProvider.spec.ts +2 -2
- package/src/orm/index.browser.ts +2 -2
- package/src/orm/index.bun.ts +4 -2
- package/src/orm/index.ts +21 -47
- package/src/orm/providers/DrizzleKitProvider.ts +3 -5
- package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -0
- package/src/orm/services/Repository.ts +18 -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 +142 -0
- package/src/react/router/primitives/$page.browser.spec.tsx +851 -0
- package/src/react/router/primitives/$page.spec.tsx +708 -0
- package/src/react/router/primitives/$page.ts +497 -0
- package/src/react/router/providers/ReactBrowserProvider.ts +309 -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/ReactServerProvider.spec.tsx +316 -0
- package/src/react/router/providers/ReactServerProvider.ts +558 -0
- package/src/react/router/providers/ReactServerTemplateProvider.ts +979 -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 +13 -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 +36 -22
- 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 +17 -7
- 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/BunHttpServerProvider.ts +1 -1
- package/src/server/core/providers/NodeHttpServerProvider.spec.ts +125 -0
- package/src/server/core/providers/NodeHttpServerProvider.ts +77 -22
- package/src/server/core/providers/ServerLoggerProvider.ts +2 -2
- package/src/server/core/providers/ServerProvider.ts +9 -12
- 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/atoms/apiLinksAtom.ts +7 -0
- package/src/server/links/index.browser.ts +2 -0
- package/src/server/links/index.ts +13 -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 +11 -0
- package/src/system/index.ts +62 -0
- package/src/{file → system}/providers/FileSystemProvider.ts +16 -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 +36 -0
- 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/index.ts +3 -2
- package/src/vite/tasks/buildClient.ts +2 -8
- package/src/vite/tasks/buildServer.ts +84 -21
- package/src/vite/tasks/copyAssets.ts +5 -4
- package/src/vite/tasks/generateSitemap.ts +64 -23
- package/src/vite/tasks/index.ts +0 -2
- package/src/vite/tasks/prerenderPages.ts +49 -24
- package/src/websocket/index.ts +12 -8
- package/dist/file/index.d.ts +0 -839
- package/dist/file/index.d.ts.map +0 -1
- package/dist/file/index.js.map +0 -1
- package/src/cli/assets/indexHtml.ts +0 -15
- package/src/cli/assets/mainServerTs.ts +0 -24
- package/src/cli/assets/webAppRouterTs.ts +0 -15
- package/src/cli/assets/webHelloComponentTsx.ts +0 -16
- package/src/cli/commands/format.ts +0 -23
- package/src/file/index.ts +0 -43
- package/src/vite/helpers/boot.ts +0 -117
- package/src/vite/plugins/viteAlephaDev.ts +0 -177
- package/src/vite/tasks/devServer.ts +0 -71
- package/src/vite/tasks/runAlepha.ts +0 -270
- /package/dist/orm/{chunk-DtkW-qnP.js → chunk-DH6iiROE.js} +0 -0
- /package/src/cli/{assets → templates}/apiIndexTs.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
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
$atom,
|
|
4
|
+
$env,
|
|
5
|
+
$hook,
|
|
6
|
+
$inject,
|
|
7
|
+
$use,
|
|
8
|
+
Alepha,
|
|
9
|
+
AlephaError,
|
|
10
|
+
type Static,
|
|
11
|
+
t,
|
|
12
|
+
} from "alepha";
|
|
13
|
+
import { $logger } from "alepha/logger";
|
|
14
|
+
import { ServerHeadProvider } from "alepha/react/head";
|
|
15
|
+
import { type ServerHandler, ServerRouterProvider } from "alepha/server";
|
|
16
|
+
import { ServerLinksProvider } from "alepha/server/links";
|
|
17
|
+
import { ServerStaticProvider } from "alepha/server/static";
|
|
18
|
+
import { FileSystemProvider } from "alepha/system";
|
|
19
|
+
import { renderToReadableStream } from "react-dom/server";
|
|
20
|
+
import { Redirection } from "../errors/Redirection.ts";
|
|
21
|
+
import {
|
|
22
|
+
$page,
|
|
23
|
+
type PagePrimitiveRenderOptions,
|
|
24
|
+
type PagePrimitiveRenderResult,
|
|
25
|
+
} from "../primitives/$page.ts";
|
|
26
|
+
import {
|
|
27
|
+
type PageRoute,
|
|
28
|
+
ReactPageProvider,
|
|
29
|
+
type ReactRouterState,
|
|
30
|
+
} from "./ReactPageProvider.ts";
|
|
31
|
+
import { ReactServerTemplateProvider } from "./ReactServerTemplateProvider.ts";
|
|
32
|
+
import { SSRManifestProvider } from "./SSRManifestProvider.ts";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* React server provider responsible for SSR and static file serving.
|
|
36
|
+
*
|
|
37
|
+
* Coordinates between:
|
|
38
|
+
* - ReactPageProvider: Page routing and layer resolution
|
|
39
|
+
* - ReactServerTemplateProvider: HTML template parsing and streaming
|
|
40
|
+
* - ServerHeadProvider: Head content management
|
|
41
|
+
* - SSRManifestProvider: Module preload link collection
|
|
42
|
+
*
|
|
43
|
+
* Uses `react-dom/server` under the hood.
|
|
44
|
+
*/
|
|
45
|
+
export class ReactServerProvider {
|
|
46
|
+
/**
|
|
47
|
+
* SSR response headers - pre-allocated to avoid object creation per request.
|
|
48
|
+
*/
|
|
49
|
+
protected readonly SSR_HEADERS = {
|
|
50
|
+
"content-type": "text/html",
|
|
51
|
+
"cache-control": "no-store, no-cache, must-revalidate, proxy-revalidate",
|
|
52
|
+
pragma: "no-cache",
|
|
53
|
+
expires: "0",
|
|
54
|
+
} as const;
|
|
55
|
+
|
|
56
|
+
protected readonly fs = $inject(FileSystemProvider);
|
|
57
|
+
protected readonly log = $logger();
|
|
58
|
+
protected readonly alepha = $inject(Alepha);
|
|
59
|
+
protected readonly env = $env(envSchema);
|
|
60
|
+
protected readonly pageApi = $inject(ReactPageProvider);
|
|
61
|
+
protected readonly templateProvider = $inject(ReactServerTemplateProvider);
|
|
62
|
+
protected readonly serverHeadProvider = $inject(ServerHeadProvider);
|
|
63
|
+
protected readonly serverStaticProvider = $inject(ServerStaticProvider);
|
|
64
|
+
protected readonly serverRouterProvider = $inject(ServerRouterProvider);
|
|
65
|
+
protected readonly ssrManifestProvider = $inject(SSRManifestProvider);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Cached check for ServerLinksProvider - avoids has() lookup per request.
|
|
69
|
+
*/
|
|
70
|
+
protected hasServerLinksProvider = false;
|
|
71
|
+
|
|
72
|
+
protected readonly options = $use(reactServerOptions);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Configure the React server provider.
|
|
76
|
+
*/
|
|
77
|
+
public readonly onConfigure = $hook({
|
|
78
|
+
on: "configure",
|
|
79
|
+
handler: async () => {
|
|
80
|
+
const pages = this.alepha.primitives($page);
|
|
81
|
+
|
|
82
|
+
const ssrEnabled =
|
|
83
|
+
pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
|
|
84
|
+
|
|
85
|
+
this.alepha.store.set("alepha.react.server.ssr", ssrEnabled);
|
|
86
|
+
|
|
87
|
+
// production mode
|
|
88
|
+
let root = "";
|
|
89
|
+
|
|
90
|
+
// non-serverless mode only -> serve static files
|
|
91
|
+
if (!this.alepha.isServerless() && !this.alepha.isViteDev()) {
|
|
92
|
+
root = await this.getPublicDirectory();
|
|
93
|
+
if (!root) {
|
|
94
|
+
this.log.warn(
|
|
95
|
+
"Missing static files, static file server will be disabled",
|
|
96
|
+
);
|
|
97
|
+
} else {
|
|
98
|
+
this.log.debug(`Using static files from: ${root}`);
|
|
99
|
+
await this.configureStaticServer(root);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (ssrEnabled) {
|
|
104
|
+
await this.registerPages(async () => this.template);
|
|
105
|
+
this.log.info("SSR OK");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// no SSR enabled, serve index.html for all unmatched routes
|
|
110
|
+
this.log.info("SSR is disabled, use History API fallback");
|
|
111
|
+
this.serverRouterProvider.createRoute({
|
|
112
|
+
path: "*",
|
|
113
|
+
handler: async ({ url, reply }) => {
|
|
114
|
+
if (url.pathname.includes(".")) {
|
|
115
|
+
// If the request is for a file (e.g., /style.css), do not fallback
|
|
116
|
+
reply.headers["content-type"] = "text/plain";
|
|
117
|
+
reply.body = "Not Found";
|
|
118
|
+
reply.status = 404;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
reply.headers["content-type"] = "text/html";
|
|
123
|
+
|
|
124
|
+
// serve index.html for all unmatched routes
|
|
125
|
+
return this.template;
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get the current HTML template.
|
|
133
|
+
*/
|
|
134
|
+
public get template() {
|
|
135
|
+
return (
|
|
136
|
+
this.alepha.store.get("alepha.react.server.template") ??
|
|
137
|
+
"<!DOCTYPE html><html lang='en'><head></head><body><div id='root'></div></body></html>"
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Register all pages as server routes.
|
|
143
|
+
*/
|
|
144
|
+
protected async registerPages(templateLoader: TemplateLoader) {
|
|
145
|
+
// Parse template once at startup
|
|
146
|
+
const template = await templateLoader();
|
|
147
|
+
if (template) {
|
|
148
|
+
this.templateProvider.parseTemplate(template);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Set up early head content (entry assets preloads)
|
|
152
|
+
this.setupEarlyHeadContent();
|
|
153
|
+
|
|
154
|
+
// Cache ServerLinksProvider check at startup
|
|
155
|
+
this.hasServerLinksProvider = this.alepha.has(ServerLinksProvider);
|
|
156
|
+
|
|
157
|
+
for (const page of this.pageApi.getPages()) {
|
|
158
|
+
if (page.component || page.lazy) {
|
|
159
|
+
this.log.debug(`+ ${page.match} -> ${page.name}`);
|
|
160
|
+
|
|
161
|
+
this.serverRouterProvider.createRoute({
|
|
162
|
+
...page,
|
|
163
|
+
schema: undefined, // schema is handled by the page primitive provider
|
|
164
|
+
method: "GET",
|
|
165
|
+
path: page.match,
|
|
166
|
+
handler: this.createHandler(page, templateLoader),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Set up early head content with entry assets.
|
|
174
|
+
*
|
|
175
|
+
* This content is sent immediately when streaming starts, before page loaders run,
|
|
176
|
+
* allowing the browser to start downloading entry.js and CSS files early.
|
|
177
|
+
*
|
|
178
|
+
* Uses <script type="module"> instead of <link rel="modulepreload"> for JS
|
|
179
|
+
* because the script needs to execute anyway - this way the browser starts
|
|
180
|
+
* downloading, parsing, AND will execute as soon as ready.
|
|
181
|
+
*
|
|
182
|
+
* Also injects critical meta tags (charset, viewport) if not specified in $head,
|
|
183
|
+
* and strips these assets from the original template head to avoid duplicates.
|
|
184
|
+
*/
|
|
185
|
+
protected setupEarlyHeadContent(): void {
|
|
186
|
+
const assets = this.ssrManifestProvider.getEntryAssets();
|
|
187
|
+
const globalHead = this.serverHeadProvider.resolveGlobalHead();
|
|
188
|
+
|
|
189
|
+
const parts: string[] = [];
|
|
190
|
+
|
|
191
|
+
if (assets) {
|
|
192
|
+
// Add CSS stylesheets (critical for rendering)
|
|
193
|
+
for (const css of assets.css) {
|
|
194
|
+
parts.push(`<link rel="stylesheet" href="${css}" crossorigin="">`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Add entry JS as script module (not just modulepreload)
|
|
198
|
+
// This starts download, parse, AND execution immediately
|
|
199
|
+
if (assets.js) {
|
|
200
|
+
parts.push(
|
|
201
|
+
`<script type="module" crossorigin="" src="${assets.js}"></script>`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Pass global head so critical meta tags can be injected if missing
|
|
207
|
+
this.templateProvider.setEarlyHeadContent(
|
|
208
|
+
parts.length > 0 ? `${parts.join("\n")}\n` : "",
|
|
209
|
+
globalHead,
|
|
210
|
+
assets ?? undefined,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
this.log.debug("Early head content set", {
|
|
214
|
+
css: assets?.css.length ?? 0,
|
|
215
|
+
js: assets?.js ? 1 : 0,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get the public directory path where static files are located.
|
|
221
|
+
*/
|
|
222
|
+
protected async getPublicDirectory(): Promise<string> {
|
|
223
|
+
const maybe = [
|
|
224
|
+
join(process.cwd(), `dist/${this.options.publicDir}`),
|
|
225
|
+
join(process.cwd(), this.options.publicDir),
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
for (const it of maybe) {
|
|
229
|
+
if (await this.fs.exists(it)) {
|
|
230
|
+
return it;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return "";
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Configure the static file server to serve files from the given root directory.
|
|
239
|
+
*/
|
|
240
|
+
protected async configureStaticServer(root: string) {
|
|
241
|
+
await this.serverStaticProvider.createStaticServer({
|
|
242
|
+
root,
|
|
243
|
+
cacheControl: {
|
|
244
|
+
maxAge: 3600,
|
|
245
|
+
immutable: true,
|
|
246
|
+
},
|
|
247
|
+
...this.options.staticServer,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Create the request handler for a page route.
|
|
253
|
+
*/
|
|
254
|
+
protected createHandler(
|
|
255
|
+
route: PageRoute,
|
|
256
|
+
templateLoader: TemplateLoader,
|
|
257
|
+
): ServerHandler {
|
|
258
|
+
return async (serverRequest) => {
|
|
259
|
+
const { url, reply, query, params } = serverRequest;
|
|
260
|
+
|
|
261
|
+
// Ensure template is parsed (handles dev mode where template may change)
|
|
262
|
+
if (!this.templateProvider.isReady()) {
|
|
263
|
+
const template = await templateLoader();
|
|
264
|
+
if (!template) {
|
|
265
|
+
throw new AlephaError("Missing template for SSR rendering");
|
|
266
|
+
}
|
|
267
|
+
this.templateProvider.parseTemplate(template);
|
|
268
|
+
this.setupEarlyHeadContent();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this.log.trace("Rendering page", { name: route.name });
|
|
272
|
+
|
|
273
|
+
// Initialize router state
|
|
274
|
+
const state: ReactRouterState = {
|
|
275
|
+
url,
|
|
276
|
+
params,
|
|
277
|
+
query,
|
|
278
|
+
name: route.name,
|
|
279
|
+
onError: () => null,
|
|
280
|
+
layers: [],
|
|
281
|
+
meta: {},
|
|
282
|
+
head: {},
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// Set up API links if available
|
|
286
|
+
if (this.hasServerLinksProvider) {
|
|
287
|
+
this.alepha.store.set(
|
|
288
|
+
"alepha.server.request.apiLinks",
|
|
289
|
+
await this.alepha.inject(ServerLinksProvider).getUserApiLinks({
|
|
290
|
+
user: (serverRequest as any).user, // TODO: fix type
|
|
291
|
+
authorization: serverRequest.headers.authorization,
|
|
292
|
+
}),
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Check access permissions
|
|
297
|
+
let target: PageRoute | undefined = route;
|
|
298
|
+
while (target) {
|
|
299
|
+
if (route.can && !route.can()) {
|
|
300
|
+
this.log.warn(
|
|
301
|
+
`Access to page '${route.name}' is forbidden by can() check`,
|
|
302
|
+
);
|
|
303
|
+
reply.status = 403;
|
|
304
|
+
reply.headers["content-type"] = "text/plain";
|
|
305
|
+
return "Forbidden";
|
|
306
|
+
}
|
|
307
|
+
target = target.parent;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
await this.alepha.events.emit("react:server:render:begin", {
|
|
311
|
+
request: serverRequest,
|
|
312
|
+
state,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Apply SSR headers early
|
|
316
|
+
Object.assign(reply.headers, this.SSR_HEADERS);
|
|
317
|
+
|
|
318
|
+
// Resolve global head for early streaming (htmlAttributes only)
|
|
319
|
+
const globalHead = this.serverHeadProvider.resolveGlobalHead();
|
|
320
|
+
|
|
321
|
+
// Create optimized HTML stream with early head
|
|
322
|
+
const htmlStream = this.templateProvider.createEarlyHtmlStream(
|
|
323
|
+
globalHead,
|
|
324
|
+
async () => {
|
|
325
|
+
// === ASYNC WORK (runs while early head is being sent) ===
|
|
326
|
+
const result = await this.renderPage(route, state);
|
|
327
|
+
|
|
328
|
+
if (result.redirect) {
|
|
329
|
+
// Return redirect URL - template provider will inject meta refresh
|
|
330
|
+
// since HTTP headers have already been sent
|
|
331
|
+
return { redirect: result.redirect };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return { state, reactStream: result.reactStream! };
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
hydration: true,
|
|
338
|
+
onError: (error) => {
|
|
339
|
+
if (error instanceof Redirection) {
|
|
340
|
+
this.log.debug("Streaming resulted in redirection", {
|
|
341
|
+
redirect: error.redirect,
|
|
342
|
+
});
|
|
343
|
+
// Can't do redirect after streaming started - already handled above
|
|
344
|
+
} else {
|
|
345
|
+
// disable logging here, it's noisy and duplicate
|
|
346
|
+
// this.log.error("HTML stream error", error);
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
this.log.trace("Page streaming started (early head optimization)");
|
|
353
|
+
route.onServerResponse?.(serverRequest);
|
|
354
|
+
reply.body = htmlStream;
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ---------------------------------------------------------------------------
|
|
359
|
+
// Core rendering logic - shared between SSR handler and static prerendering
|
|
360
|
+
// ---------------------------------------------------------------------------
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Core page rendering logic shared between SSR handler and static prerendering.
|
|
364
|
+
*
|
|
365
|
+
* Handles:
|
|
366
|
+
* - Layer resolution (loaders)
|
|
367
|
+
* - Redirect detection
|
|
368
|
+
* - Head content filling
|
|
369
|
+
* - Preload link collection
|
|
370
|
+
* - React stream rendering
|
|
371
|
+
*
|
|
372
|
+
* @param route - The page route to render
|
|
373
|
+
* @param state - The router state
|
|
374
|
+
* @returns Render result with redirect or React stream
|
|
375
|
+
*/
|
|
376
|
+
protected async renderPage(
|
|
377
|
+
route: PageRoute,
|
|
378
|
+
state: ReactRouterState,
|
|
379
|
+
): Promise<{ redirect?: string; reactStream?: ReadableStream<Uint8Array> }> {
|
|
380
|
+
// Resolve page layers (loaders)
|
|
381
|
+
const { redirect } = await this.pageApi.createLayers(route, state);
|
|
382
|
+
if (redirect) {
|
|
383
|
+
this.log.debug("Resolver resulted in redirection", { redirect });
|
|
384
|
+
return { redirect };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Fill head from route config
|
|
388
|
+
this.serverHeadProvider.fillHead(state);
|
|
389
|
+
|
|
390
|
+
// Collect and inject modulepreload links for page-specific chunks
|
|
391
|
+
const preloadLinks = this.ssrManifestProvider.collectPreloadLinks(route);
|
|
392
|
+
if (preloadLinks.length > 0) {
|
|
393
|
+
state.head ??= {};
|
|
394
|
+
state.head.link = [...(state.head.link ?? []), ...preloadLinks];
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Render React to stream
|
|
398
|
+
|
|
399
|
+
const element = this.pageApi.root(state);
|
|
400
|
+
this.alepha.store.set("alepha.react.router.state", state);
|
|
401
|
+
|
|
402
|
+
const reactStream = await renderToReadableStream(element, {
|
|
403
|
+
onError: (error: unknown) => {
|
|
404
|
+
if (error instanceof Redirection) {
|
|
405
|
+
this.log.warn("Redirect during streaming ignored", {
|
|
406
|
+
redirect: error.redirect,
|
|
407
|
+
});
|
|
408
|
+
} else {
|
|
409
|
+
// disable logging here, it's noisy and duplicate
|
|
410
|
+
// this.log.error("Streaming render error", error);
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
return { reactStream };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ---------------------------------------------------------------------------
|
|
419
|
+
// Testing utilities - kept for backwards compatibility with tests
|
|
420
|
+
// ---------------------------------------------------------------------------
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* For testing purposes, renders a page to HTML string.
|
|
424
|
+
* Uses the same streaming code path as production, then collects to string.
|
|
425
|
+
*
|
|
426
|
+
* @param name - Page name to render
|
|
427
|
+
* @param options - Render options (params, query, html, hydration)
|
|
428
|
+
*/
|
|
429
|
+
public async render(
|
|
430
|
+
name: string,
|
|
431
|
+
options: PagePrimitiveRenderOptions = {},
|
|
432
|
+
): Promise<PagePrimitiveRenderResult> {
|
|
433
|
+
const page = this.pageApi.page(name);
|
|
434
|
+
const url = new URL(this.pageApi.url(name, options));
|
|
435
|
+
const state: ReactRouterState = {
|
|
436
|
+
url,
|
|
437
|
+
params: options.params ?? {},
|
|
438
|
+
query: options.query ?? {},
|
|
439
|
+
onError: () => null,
|
|
440
|
+
layers: [],
|
|
441
|
+
meta: {},
|
|
442
|
+
head: {},
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
this.log.trace("Rendering", { url });
|
|
446
|
+
|
|
447
|
+
await this.alepha.events.emit("react:server:render:begin", { state });
|
|
448
|
+
|
|
449
|
+
// Ensure template is parsed with early head content (entry.js, CSS)
|
|
450
|
+
if (!this.templateProvider.isReady()) {
|
|
451
|
+
this.templateProvider.parseTemplate(this.template);
|
|
452
|
+
this.setupEarlyHeadContent();
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Use shared rendering logic
|
|
456
|
+
const result = await this.renderPage(page, state);
|
|
457
|
+
|
|
458
|
+
if (result.redirect) {
|
|
459
|
+
return { state, html: "", redirect: result.redirect };
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const reactStream = result.reactStream!;
|
|
463
|
+
|
|
464
|
+
// If full HTML page not requested, collect just the React content
|
|
465
|
+
if (!options.html) {
|
|
466
|
+
const html = await this.streamToString(reactStream);
|
|
467
|
+
return { state, html };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Create full HTML stream and collect to string
|
|
471
|
+
const htmlStream = this.templateProvider.createHtmlStream(
|
|
472
|
+
reactStream,
|
|
473
|
+
state,
|
|
474
|
+
{ hydration: options.hydration ?? true },
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
const html = await this.streamToString(htmlStream);
|
|
478
|
+
|
|
479
|
+
await this.alepha.events.emit("react:server:render:end", { state, html });
|
|
480
|
+
|
|
481
|
+
return { state, html };
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Collect a ReadableStream into a string.
|
|
486
|
+
*/
|
|
487
|
+
protected async streamToString(
|
|
488
|
+
stream: ReadableStream<Uint8Array>,
|
|
489
|
+
): Promise<string> {
|
|
490
|
+
const reader = stream.getReader();
|
|
491
|
+
const decoder = new TextDecoder();
|
|
492
|
+
const chunks: string[] = [];
|
|
493
|
+
|
|
494
|
+
try {
|
|
495
|
+
while (true) {
|
|
496
|
+
const { done, value } = await reader.read();
|
|
497
|
+
if (done) break;
|
|
498
|
+
chunks.push(decoder.decode(value, { stream: true }));
|
|
499
|
+
}
|
|
500
|
+
chunks.push(decoder.decode()); // Flush remaining
|
|
501
|
+
} finally {
|
|
502
|
+
reader.releaseLock();
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return chunks.join("");
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
510
|
+
|
|
511
|
+
type TemplateLoader = () => Promise<string | undefined>;
|
|
512
|
+
|
|
513
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
514
|
+
|
|
515
|
+
const envSchema = t.object({
|
|
516
|
+
REACT_SSR_ENABLED: t.optional(t.boolean()),
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
declare module "alepha" {
|
|
520
|
+
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
521
|
+
interface State {
|
|
522
|
+
"alepha.react.server.ssr"?: boolean;
|
|
523
|
+
"alepha.react.server.template"?: string;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* React server provider configuration atom
|
|
529
|
+
*/
|
|
530
|
+
export const reactServerOptions = $atom({
|
|
531
|
+
name: "alepha.react.server.options",
|
|
532
|
+
schema: t.object({
|
|
533
|
+
publicDir: t.string(),
|
|
534
|
+
staticServer: t.object({
|
|
535
|
+
disabled: t.boolean(),
|
|
536
|
+
path: t.string({
|
|
537
|
+
description: "URL path where static files will be served.",
|
|
538
|
+
}),
|
|
539
|
+
}),
|
|
540
|
+
}),
|
|
541
|
+
default: {
|
|
542
|
+
publicDir: "public",
|
|
543
|
+
staticServer: {
|
|
544
|
+
disabled: false,
|
|
545
|
+
path: "/",
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
export type ReactServerProviderOptions = Static<
|
|
551
|
+
typeof reactServerOptions.schema
|
|
552
|
+
>;
|
|
553
|
+
|
|
554
|
+
declare module "alepha" {
|
|
555
|
+
interface State {
|
|
556
|
+
[reactServerOptions.key]: ReactServerProviderOptions;
|
|
557
|
+
}
|
|
558
|
+
}
|