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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as alepha1 from "alepha";
|
|
2
2
|
import { KIND, Primitive, Static } from "alepha";
|
|
3
3
|
import { ServerRequest, ServerRouterProvider } from "alepha/server";
|
|
4
|
-
import
|
|
4
|
+
import { CacheProvider } from "alepha/cache";
|
|
5
5
|
import * as alepha_logger0 from "alepha/logger";
|
|
6
6
|
|
|
7
7
|
//#region ../../src/server/rate-limit/providers/ServerRateLimitProvider.d.ts
|
|
@@ -34,40 +34,35 @@ declare class ServerRateLimitProvider {
|
|
|
34
34
|
RATE_LIMIT_WINDOW_MS: number;
|
|
35
35
|
RATE_LIMIT_MAX_REQUESTS: number;
|
|
36
36
|
};
|
|
37
|
-
protected readonly
|
|
37
|
+
protected readonly cacheProvider: CacheProvider;
|
|
38
38
|
protected readonly globalOptions: Readonly<{
|
|
39
39
|
windowMs?: number | undefined;
|
|
40
40
|
max?: number | undefined;
|
|
41
41
|
skipFailedRequests?: boolean | undefined;
|
|
42
42
|
skipSuccessfulRequests?: boolean | undefined;
|
|
43
43
|
}>;
|
|
44
|
+
protected static readonly CACHE_NAME = "rate-limit";
|
|
44
45
|
/**
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
* Registered rate limit configurations with their path patterns
|
|
47
|
+
*/
|
|
47
48
|
readonly registeredConfigs: RateLimitPrimitiveOptions[];
|
|
48
49
|
/**
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
* Register a rate limit configuration (called by primitives)
|
|
51
|
+
*/
|
|
51
52
|
registerRateLimit(config: RateLimitPrimitiveOptions): void;
|
|
52
53
|
protected readonly onStart: alepha1.HookPrimitive<"start">;
|
|
53
54
|
readonly onRequest: alepha1.HookPrimitive<"server:onRequest">;
|
|
54
55
|
readonly onActionRequest: alepha1.HookPrimitive<"action:onRequest">;
|
|
55
56
|
/**
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
* Build complete rate limit options by merging with global defaults
|
|
58
|
+
*/
|
|
58
59
|
protected buildRateLimitOptions(config: RateLimitPrimitiveOptions): RateLimitOptions;
|
|
59
60
|
/**
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
* Set rate limit headers on the response
|
|
62
|
+
*/
|
|
62
63
|
protected setRateLimitHeaders(request: ServerRequest, result: RateLimitResult): void;
|
|
63
64
|
checkLimit(req: ServerRequest, options?: RateLimitOptions): Promise<RateLimitResult>;
|
|
64
65
|
protected generateKey(req: ServerRequest): string;
|
|
65
|
-
protected getClientIP(req: ServerRequest): string;
|
|
66
|
-
}
|
|
67
|
-
interface RateLimitData {
|
|
68
|
-
count: number;
|
|
69
|
-
windowStart: number;
|
|
70
|
-
hits: number[];
|
|
71
66
|
}
|
|
72
67
|
//#endregion
|
|
73
68
|
//#region ../../src/server/rate-limit/primitives/$rateLimit.d.ts
|
|
@@ -102,9 +97,13 @@ declare const $rateLimit: {
|
|
|
102
97
|
[KIND]: typeof RateLimitPrimitive;
|
|
103
98
|
};
|
|
104
99
|
interface RateLimitPrimitiveOptions extends RateLimitOptions {
|
|
105
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* Name identifier for this rate limit (default: property key).
|
|
102
|
+
*/
|
|
106
103
|
name?: string;
|
|
107
|
-
/**
|
|
104
|
+
/**
|
|
105
|
+
* Path patterns to match (supports wildcards like /api/*).
|
|
106
|
+
*/
|
|
108
107
|
paths?: string[];
|
|
109
108
|
}
|
|
110
109
|
interface AbstractRateLimitPrimitive {
|
|
@@ -117,8 +116,8 @@ declare class RateLimitPrimitive extends Primitive<RateLimitPrimitiveOptions> im
|
|
|
117
116
|
get name(): string;
|
|
118
117
|
protected onInit(): void;
|
|
119
118
|
/**
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
* Checks rate limit for the given request using this primitive's configuration.
|
|
120
|
+
*/
|
|
122
121
|
check(request: ServerRequest, options?: RateLimitOptions): Promise<RateLimitResult>;
|
|
123
122
|
}
|
|
124
123
|
//#endregion
|
|
@@ -126,55 +125,51 @@ declare class RateLimitPrimitive extends Primitive<RateLimitPrimitiveOptions> im
|
|
|
126
125
|
declare module "alepha/server" {
|
|
127
126
|
interface ActionPrimitiveOptions<TConfig> {
|
|
128
127
|
/**
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
* Rate limiting configuration for this action.
|
|
129
|
+
* When specified, the action will be rate limited according to these settings.
|
|
130
|
+
*/
|
|
132
131
|
rateLimit?: RateLimitOptions;
|
|
133
132
|
}
|
|
134
133
|
interface ServerRoute {
|
|
135
134
|
/**
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
* Route-specific rate limit configuration.
|
|
136
|
+
* If set, overrides the global rate limit options for this route.
|
|
137
|
+
*/
|
|
139
138
|
rateLimit?: RateLimitOptions;
|
|
140
139
|
}
|
|
141
140
|
}
|
|
142
141
|
interface RateLimitOptions {
|
|
143
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* Maximum number of requests per window (default: 100).
|
|
144
|
+
*/
|
|
144
145
|
max?: number;
|
|
145
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* Window duration in milliseconds (default: 15 minutes).
|
|
148
|
+
*/
|
|
146
149
|
windowMs?: number;
|
|
147
|
-
/**
|
|
150
|
+
/**
|
|
151
|
+
* Custom key generator function.
|
|
152
|
+
*/
|
|
148
153
|
keyGenerator?: (req: any) => string;
|
|
149
|
-
/**
|
|
154
|
+
/**
|
|
155
|
+
* Skip rate limiting for failed requests.
|
|
156
|
+
*/
|
|
150
157
|
skipFailedRequests?: boolean;
|
|
151
|
-
/**
|
|
158
|
+
/**
|
|
159
|
+
* Skip rate limiting for successful requests.
|
|
160
|
+
*/
|
|
152
161
|
skipSuccessfulRequests?: boolean;
|
|
153
162
|
}
|
|
154
163
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* - The `$rateLimit` primitive with `paths` option for path-based rate limiting
|
|
159
|
-
* - The `rateLimit` option in action primitives for action-specific limiting
|
|
160
|
-
*
|
|
161
|
-
* It offers sliding window rate limiting, custom key generation, and seamless integration with server routes.
|
|
164
|
+
* | type | quality | stability |
|
|
165
|
+
* |------|---------|-----------|
|
|
166
|
+
* | backend | standard | stable |
|
|
162
167
|
*
|
|
163
|
-
*
|
|
164
|
-
* ```ts
|
|
165
|
-
* import { $rateLimit, AlephaServerRateLimit } from "alepha/server/rate-limit";
|
|
168
|
+
* Request rate limiting on actions.
|
|
166
169
|
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* apiRateLimit = $rateLimit({
|
|
170
|
-
* paths: ["/api/*"],
|
|
171
|
-
* max: 100,
|
|
172
|
-
* windowMs: 15 * 60 * 1000, // 15 minutes
|
|
173
|
-
* });
|
|
174
|
-
* }
|
|
175
|
-
* ```
|
|
170
|
+
* **Features:**
|
|
171
|
+
* - Rate limit configuration per action
|
|
176
172
|
*
|
|
177
|
-
* @see {@link $rateLimit}
|
|
178
173
|
* @module alepha.server.rate-limit
|
|
179
174
|
*/
|
|
180
175
|
declare const AlephaServerRateLimit: alepha1.Service<alepha1.Module>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/server/rate-limit/providers/ServerRateLimitProvider.ts","../../../src/server/rate-limit/primitives/$rateLimit.ts","../../../src/server/rate-limit/index.ts"],"mappings":";;;;;;;UAaiB,eAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/server/rate-limit/providers/ServerRateLimitProvider.ts","../../../src/server/rate-limit/primitives/$rateLimit.ts","../../../src/server/rate-limit/index.ts"],"mappings":";;;;;;;UAaiB,eAAA;EACf,OAAA;EACA,KAAA;EACA,SAAA;EACA,SAAA;EACA,UAAA;AAAA;;;;cAMW,gBAAA,EAAgB,OAAA,CAAA,IAAA,SAAA,OAAA;8BAyB3B,OAAA,CAAA,OAAA;;;;;KAEU,oBAAA,GAAuB,MAAA,QAAc,gBAAA,CAAiB,MAAA;AAAA;EAAA,UAGtD,KAAA;IAAA,CACP,gBAAA,CAAiB,GAAA,GAAM,oBAAA;EAAA;AAAA;AAAA,cAiBf,uBAAA;EAAA,mBACQ,GAAA,EADe,cAAA,CACZ,MAAA;EAAA,mBACH,oBAAA,EAAoB,oBAAA;EAAA,mBACpB,GAAA;;;;qBACA,aAAA,EAAa,aAAA;EAAA,mBACb,aAAA,EAAa,QAAA;;;;;;4BAEN,UAAA;;;;WAKV,iBAAA,EAAmB,yBAAA;;;;EAK5B,iBAAA,CAAkB,MAAA,EAAQ,yBAAA;EAAA,mBAId,OAAA,EAJuC,OAAA,CAIhC,aAAA;EAAA,SAuBV,SAAA,EAvBU,OAAA,CAuBD,aAAA;EAAA,SAuBT,eAAA,EAvBS,OAAA,CAuBM,aAAA;EAxFE;;;EAAA,UAmHvB,qBAAA,CACR,MAAA,EAAQ,yBAAA,GACP,gBAAA;EArHmE;AAAE;;EAAF,UAqI5D,mBAAA,CACR,OAAA,EAAS,aAAA,EACT,MAAA,EAAQ,eAAA;EAiBG,UAAA,CACX,GAAA,EAAK,aAAA,EACL,OAAA,GAAS,gBAAA,GACR,OAAA,CAAQ,eAAA;EAAA,UAqCD,WAAA,CAAY,GAAA,EAAK,aAAA;AAAA;;;;;;AAtO7B;;;;;;;;;;;AAWA;;;;;;;;;;;;cCUa,UAAA;EAAA,WACF,yBAAA,GACR,0BAAA;EAAA;;UAMc,yBAAA,SAAkC,gBAAA;;;;EAIjD,IAAA;;;;EAIA,KAAA;AAAA;AAAA,UAGe,0BAAA;EAAA,SACN,IAAA;EAAA,SACA,OAAA,EAAS,yBAAA;EAClB,KAAA,CACE,OAAA,EAAS,aAAA,EACT,OAAA,GAAU,gBAAA,GACT,OAAA,CAAQ,eAAA;AAAA;AAAA,cAGA,kBAAA,SACH,SAAA,CAAU,yBAAA,aACP,0BAAA;EAAA,mBAEQ,uBAAA,EAAuB,uBAAA;EAAA,IAE/B,IAAA,CAAA;EAAA,UAID,MAAA,CAAA;EDrBuB;;;EC6BpB,KAAA,CACX,OAAA,EAAS,aAAA,EACT,OAAA,GAAU,gBAAA,GACT,OAAA,CAAQ,eAAA;AAAA;;;;YCtED,sBAAA;;;;AFAZ;IEKI,SAAA,GAAY,gBAAA;EAAA;EAAA,UAGJ,WAAA;IFPV;;;;IEYE,SAAA,GAAY,gBAAA;EAAA;AAAA;AAAA,UAMC,gBAAA;EFiBf;;;EEbA,GAAA;;;;EAIA,QAAA;;;;EAIA,YAAA,IAAgB,GAAA;EFpBW;;;EEwB3B,kBAAA;;;;EAIA,sBAAA;AAAA;;;;;;;;;;AFDF;;;cEgBa,qBAAA,EAAqB,OAAA,CAAA,OAAA,CAIhC,OAAA,CAJgC,MAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $atom, $env, $hook, $inject, $module, $use, KIND, Primitive, createPrimitive, t } from "alepha";
|
|
2
2
|
import { AlephaServer, HttpError, ServerRouterProvider } from "alepha/server";
|
|
3
|
-
import {
|
|
3
|
+
import { CacheProvider } from "alepha/cache";
|
|
4
4
|
import { $logger } from "alepha/logger";
|
|
5
5
|
|
|
6
6
|
//#region ../../src/server/rate-limit/providers/ServerRateLimitProvider.ts
|
|
@@ -27,15 +27,13 @@ const envSchema = t.object({
|
|
|
27
27
|
description: "Maximum requests per window"
|
|
28
28
|
})
|
|
29
29
|
});
|
|
30
|
-
var ServerRateLimitProvider = class {
|
|
30
|
+
var ServerRateLimitProvider = class ServerRateLimitProvider {
|
|
31
31
|
log = $logger();
|
|
32
32
|
serverRouterProvider = $inject(ServerRouterProvider);
|
|
33
33
|
env = $env(envSchema);
|
|
34
|
-
|
|
35
|
-
name: "server-rate-limit",
|
|
36
|
-
ttl: [this.env.RATE_LIMIT_WINDOW_MS, "milliseconds"]
|
|
37
|
-
});
|
|
34
|
+
cacheProvider = $inject(CacheProvider);
|
|
38
35
|
globalOptions = $use(rateLimitOptions);
|
|
36
|
+
static CACHE_NAME = "rate-limit";
|
|
39
37
|
/**
|
|
40
38
|
* Registered rate limit configurations with their path patterns
|
|
41
39
|
*/
|
|
@@ -104,45 +102,24 @@ var ServerRateLimitProvider = class {
|
|
|
104
102
|
async checkLimit(req, options = {}) {
|
|
105
103
|
const windowMs = options.windowMs ?? this.env.RATE_LIMIT_WINDOW_MS;
|
|
106
104
|
const max = options.max ?? this.env.RATE_LIMIT_MAX_REQUESTS;
|
|
107
|
-
const
|
|
105
|
+
const baseKey = this.generateKey(req);
|
|
108
106
|
const now = Date.now();
|
|
109
|
-
const windowStart = now
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
};
|
|
115
|
-
const validHits = currentData.hits.filter((hit) => hit >= windowStart);
|
|
116
|
-
const allowed = validHits.length < max;
|
|
117
|
-
const remaining = Math.max(0, max - validHits.length);
|
|
118
|
-
const resetTime = Math.max(...validHits, windowStart) + windowMs;
|
|
119
|
-
if (allowed) {
|
|
120
|
-
validHits.push(now);
|
|
121
|
-
await this.cache.set(key, {
|
|
122
|
-
count: validHits.length,
|
|
123
|
-
windowStart: Math.min(currentData.windowStart, windowStart),
|
|
124
|
-
hits: validHits
|
|
125
|
-
});
|
|
126
|
-
}
|
|
107
|
+
const windowStart = Math.floor(now / windowMs) * windowMs;
|
|
108
|
+
const resetTime = windowStart + windowMs;
|
|
109
|
+
const key = `${baseKey}:${windowStart}`;
|
|
110
|
+
const count = await this.cacheProvider.incr(ServerRateLimitProvider.CACHE_NAME, key, 1);
|
|
111
|
+
const allowed = count <= max;
|
|
127
112
|
const result = {
|
|
128
113
|
allowed,
|
|
129
114
|
limit: max,
|
|
130
|
-
remaining:
|
|
115
|
+
remaining: Math.max(0, max - count),
|
|
131
116
|
resetTime
|
|
132
117
|
};
|
|
133
118
|
if (!allowed) result.retryAfter = Math.ceil((resetTime - now) / 1e3);
|
|
134
119
|
return result;
|
|
135
120
|
}
|
|
136
121
|
generateKey(req) {
|
|
137
|
-
return `ip:${
|
|
138
|
-
}
|
|
139
|
-
getClientIP(req) {
|
|
140
|
-
const forwarded = req.headers?.["x-forwarded-for"];
|
|
141
|
-
if (forwarded) {
|
|
142
|
-
const firstIp = forwarded.split(",")[0].trim();
|
|
143
|
-
if (firstIp) return firstIp;
|
|
144
|
-
}
|
|
145
|
-
return req.ip || "unknown";
|
|
122
|
+
return `ip:${req.ip || "unknown"}`;
|
|
146
123
|
}
|
|
147
124
|
};
|
|
148
125
|
|
|
@@ -201,29 +178,15 @@ $rateLimit[KIND] = RateLimitPrimitive;
|
|
|
201
178
|
//#endregion
|
|
202
179
|
//#region ../../src/server/rate-limit/index.ts
|
|
203
180
|
/**
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* - The `$rateLimit` primitive with `paths` option for path-based rate limiting
|
|
208
|
-
* - The `rateLimit` option in action primitives for action-specific limiting
|
|
181
|
+
* | type | quality | stability |
|
|
182
|
+
* |------|---------|-----------|
|
|
183
|
+
* | backend | standard | stable |
|
|
209
184
|
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* @example
|
|
213
|
-
* ```ts
|
|
214
|
-
* import { $rateLimit, AlephaServerRateLimit } from "alepha/server/rate-limit";
|
|
185
|
+
* Request rate limiting on actions.
|
|
215
186
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* apiRateLimit = $rateLimit({
|
|
219
|
-
* paths: ["/api/*"],
|
|
220
|
-
* max: 100,
|
|
221
|
-
* windowMs: 15 * 60 * 1000, // 15 minutes
|
|
222
|
-
* });
|
|
223
|
-
* }
|
|
224
|
-
* ```
|
|
187
|
+
* **Features:**
|
|
188
|
+
* - Rate limit configuration per action
|
|
225
189
|
*
|
|
226
|
-
* @see {@link $rateLimit}
|
|
227
190
|
* @module alepha.server.rate-limit
|
|
228
191
|
*/
|
|
229
192
|
const AlephaServerRateLimit = $module({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/server/rate-limit/providers/ServerRateLimitProvider.ts","../../../src/server/rate-limit/primitives/$rateLimit.ts","../../../src/server/rate-limit/index.ts"],"sourcesContent":["import { $atom, $env, $hook, $inject, $use, type Static, t } from \"alepha\";\nimport { $cache } from \"alepha/cache\";\nimport { $logger } from \"alepha/logger\";\nimport {\n HttpError,\n type ServerRequest,\n ServerRouterProvider,\n} from \"alepha/server\";\nimport type { RateLimitOptions } from \"../index.ts\";\nimport type { RateLimitPrimitiveOptions } from \"../primitives/$rateLimit.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface RateLimitResult {\n allowed: boolean;\n limit: number;\n remaining: number;\n resetTime: number;\n retryAfter?: number;\n}\n\n/**\n * Rate limit configuration atom (global defaults)\n */\nexport const rateLimitOptions = $atom({\n name: \"alepha.server.rate-limit.options\",\n schema: t.object({\n windowMs: t.optional(\n t.number({\n description: \"Window duration in milliseconds\",\n }),\n ),\n max: t.optional(\n t.number({\n description: \"Maximum number of requests per window\",\n }),\n ),\n skipFailedRequests: t.optional(\n t.boolean({\n description: \"Skip rate limiting for failed requests\",\n }),\n ),\n skipSuccessfulRequests: t.optional(\n t.boolean({\n description: \"Skip rate limiting for successful requests\",\n }),\n ),\n }),\n default: {},\n});\n\nexport type RateLimitAtomOptions = Static<typeof rateLimitOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [rateLimitOptions.key]: RateLimitAtomOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nconst envSchema = t.object({\n RATE_LIMIT_WINDOW_MS: t.number({\n default: 15 * 60 * 1000, // 15 minutes\n description: \"Rate limit window in milliseconds\",\n }),\n RATE_LIMIT_MAX_REQUESTS: t.number({\n default: 100,\n description: \"Maximum requests per window\",\n }),\n});\n\nexport class ServerRateLimitProvider {\n protected readonly log = $logger();\n protected readonly serverRouterProvider = $inject(ServerRouterProvider);\n protected readonly env = $env(envSchema);\n\n protected readonly cache = $cache<RateLimitData>({\n name: \"server-rate-limit\",\n ttl: [this.env.RATE_LIMIT_WINDOW_MS, \"milliseconds\"],\n });\n\n protected readonly globalOptions = $use(rateLimitOptions);\n\n /**\n * Registered rate limit configurations with their path patterns\n */\n public readonly registeredConfigs: RateLimitPrimitiveOptions[] = [];\n\n /**\n * Register a rate limit configuration (called by primitives)\n */\n public registerRateLimit(config: RateLimitPrimitiveOptions): void {\n this.registeredConfigs.push(config);\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // Apply path-specific rate limit configs to routes\n for (const config of this.registeredConfigs) {\n if (config.paths) {\n for (const pattern of config.paths) {\n const matchedRoutes = this.serverRouterProvider.getRoutes(pattern);\n for (const route of matchedRoutes) {\n route.rateLimit = this.buildRateLimitOptions(config);\n }\n }\n }\n }\n\n if (this.registeredConfigs.length > 0) {\n this.log.info(\n `Initialized with ${this.registeredConfigs.length} registered rate-limit configurations.`,\n );\n }\n },\n });\n\n public readonly onRequest = $hook({\n on: \"server:onRequest\",\n handler: async ({ route, request }) => {\n // Use route-specific rate limit if defined, otherwise use global options\n const rateLimitConfig = route.rateLimit ?? this.globalOptions;\n\n // Skip if no rate limiting configured\n if (!rateLimitConfig.max && !rateLimitConfig.windowMs) {\n return;\n }\n\n const result = await this.checkLimit(request, rateLimitConfig);\n this.setRateLimitHeaders(request, result);\n\n if (!result.allowed) {\n throw new HttpError({\n status: 429,\n message: \"Too Many Requests\",\n });\n }\n },\n });\n\n public readonly onActionRequest = $hook({\n on: \"action:onRequest\",\n handler: async ({ action, request }) => {\n // Check if this action has rate limiting enabled\n const rateLimit = action.options?.rateLimit;\n if (!rateLimit) {\n return; // No rate limiting for this action\n }\n\n const result = await this.checkLimit(request, rateLimit);\n\n if (!result.allowed) {\n // Actions are internal - don't set HTTP headers\n // Only throw error to prevent action execution\n throw new HttpError({\n status: 429,\n message: \"Too Many Requests\",\n });\n }\n\n // Action allowed - no headers to set since actions are internal\n },\n });\n\n /**\n * Build complete rate limit options by merging with global defaults\n */\n protected buildRateLimitOptions(\n config: RateLimitPrimitiveOptions,\n ): RateLimitOptions {\n return {\n max: config.max ?? this.globalOptions.max,\n windowMs: config.windowMs ?? this.globalOptions.windowMs,\n keyGenerator: config.keyGenerator,\n skipFailedRequests:\n config.skipFailedRequests ?? this.globalOptions.skipFailedRequests,\n skipSuccessfulRequests:\n config.skipSuccessfulRequests ??\n this.globalOptions.skipSuccessfulRequests,\n };\n }\n\n /**\n * Set rate limit headers on the response\n */\n protected setRateLimitHeaders(\n request: ServerRequest,\n result: RateLimitResult,\n ): void {\n request.reply.setHeader(\"X-RateLimit-Limit\", result.limit.toString());\n request.reply.setHeader(\n \"X-RateLimit-Remaining\",\n result.remaining.toString(),\n );\n request.reply.setHeader(\n \"X-RateLimit-Reset\",\n Math.ceil(result.resetTime / 1000).toString(),\n );\n\n if (!result.allowed && result.retryAfter) {\n request.reply.setHeader(\"Retry-After\", result.retryAfter.toString());\n }\n }\n\n public async checkLimit(\n req: ServerRequest,\n options: RateLimitOptions = {},\n ): Promise<RateLimitResult> {\n const windowMs = options.windowMs ?? this.env.RATE_LIMIT_WINDOW_MS;\n const max = options.max ?? this.env.RATE_LIMIT_MAX_REQUESTS;\n const key = this.generateKey(req);\n\n const now = Date.now();\n const windowStart = now - windowMs;\n\n // Get current rate limit data\n const currentData = (await this.cache.get(key)) || {\n count: 0,\n windowStart: now,\n hits: [],\n };\n\n // Clean old hits outside the current window\n const validHits = currentData.hits.filter(\n (hit: number) => hit >= windowStart,\n );\n\n // Check if limit exceeded\n const allowed = validHits.length < max;\n const remaining = Math.max(0, max - validHits.length);\n const resetTime = Math.max(...validHits, windowStart) + windowMs;\n\n // If allowed, record this request\n if (allowed) {\n validHits.push(now);\n await this.cache.set(key, {\n count: validHits.length,\n windowStart: Math.min(currentData.windowStart, windowStart),\n hits: validHits,\n });\n }\n\n const result: RateLimitResult = {\n allowed,\n limit: max,\n remaining: allowed ? remaining - 1 : remaining,\n resetTime,\n };\n\n if (!allowed) {\n result.retryAfter = Math.ceil((resetTime - now) / 1000);\n }\n\n return result;\n }\n\n protected generateKey(req: ServerRequest): string {\n // Default to IP-based rate limiting\n const ip = this.getClientIP(req);\n return `ip:${ip}`;\n }\n\n protected getClientIP(req: ServerRequest): string {\n // Check x-forwarded-for header first (for proxies/load balancers)\n const forwarded = req.headers?.[\"x-forwarded-for\"];\n if (forwarded) {\n // x-forwarded-for can contain multiple IPs, get the first one (original client)\n const firstIp = forwarded.split(\",\")[0].trim();\n if (firstIp) return firstIp;\n }\n\n return req.ip || \"unknown\";\n }\n}\n\ninterface RateLimitData {\n count: number;\n windowStart: number;\n hits: number[];\n}\n","import { $inject, createPrimitive, KIND, Primitive } from \"alepha\";\nimport type { ServerRequest } from \"alepha/server\";\nimport type { RateLimitOptions } from \"../index.ts\";\nimport {\n type RateLimitResult,\n ServerRateLimitProvider,\n} from \"../providers/ServerRateLimitProvider.ts\";\n\n/**\n * Declares rate limiting for server routes or custom usage.\n * This primitive provides methods to check rate limits and configure behavior\n * within the server request/response cycle.\n *\n * @example\n * ```ts\n * class ApiService {\n * // Apply rate limiting to specific paths\n * apiRateLimit = $rateLimit({\n * paths: [\"/api/*\"],\n * max: 100,\n * windowMs: 15 * 60 * 1000, // 15 minutes\n * });\n *\n * // Or use check() method for manual rate limiting\n * customAction = $action({\n * handler: async (req) => {\n * const result = await this.apiRateLimit.check(req);\n * if (!result.allowed) throw new Error(\"Rate limited\");\n * return \"ok\";\n * },\n * });\n * }\n * ```\n */\nexport const $rateLimit = (\n options: RateLimitPrimitiveOptions = {},\n): AbstractRateLimitPrimitive => {\n return createPrimitive(RateLimitPrimitive, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface RateLimitPrimitiveOptions extends RateLimitOptions {\n /** Name identifier for this rate limit (default: property key) */\n name?: string;\n /** Path patterns to match (supports wildcards like /api/*) */\n paths?: string[];\n}\n\nexport interface AbstractRateLimitPrimitive {\n readonly name: string;\n readonly options: RateLimitPrimitiveOptions;\n check(\n request: ServerRequest,\n options?: RateLimitOptions,\n ): Promise<RateLimitResult>;\n}\n\nexport class RateLimitPrimitive\n extends Primitive<RateLimitPrimitiveOptions>\n implements AbstractRateLimitPrimitive\n{\n protected readonly serverRateLimitProvider = $inject(ServerRateLimitProvider);\n\n public get name(): string {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n protected onInit() {\n // Register this rate limit configuration with the provider\n this.serverRateLimitProvider.registerRateLimit(this.options);\n }\n\n /**\n * Checks rate limit for the given request using this primitive's configuration.\n */\n public async check(\n request: ServerRequest,\n options?: RateLimitOptions,\n ): Promise<RateLimitResult> {\n const mergedOptions = { ...this.options, ...options };\n return this.serverRateLimitProvider.checkLimit(request, mergedOptions);\n }\n}\n\n$rateLimit[KIND] = RateLimitPrimitive;\n","import { $module } from \"alepha\";\nimport { AlephaServer } from \"alepha/server\";\nimport { $rateLimit } from \"./primitives/$rateLimit.ts\";\nimport { ServerRateLimitProvider } from \"./providers/ServerRateLimitProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$rateLimit.ts\";\nexport * from \"./providers/ServerRateLimitProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha/server\" {\n interface ActionPrimitiveOptions<TConfig> {\n /**\n * Rate limiting configuration for this action.\n * When specified, the action will be rate limited according to these settings.\n */\n rateLimit?: RateLimitOptions;\n }\n\n interface ServerRoute {\n /**\n * Route-specific rate limit configuration.\n * If set, overrides the global rate limit options for this route.\n */\n rateLimit?: RateLimitOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface RateLimitOptions {\n /** Maximum number of requests per window (default: 100) */\n max?: number;\n /** Window duration in milliseconds (default: 15 minutes) */\n windowMs?: number;\n /** Custom key generator function */\n keyGenerator?: (req: any) => string;\n /** Skip rate limiting for failed requests */\n skipFailedRequests?: boolean;\n /** Skip rate limiting for successful requests */\n skipSuccessfulRequests?: boolean;\n}\n\n/**\n * Provides rate limiting capabilities for server routes and actions with configurable limits and windows.\n *\n * The server-rate-limit module enables per-route and per-action rate limiting using either:\n * - The `$rateLimit` primitive with `paths` option for path-based rate limiting\n * - The `rateLimit` option in action primitives for action-specific limiting\n *\n * It offers sliding window rate limiting, custom key generation, and seamless integration with server routes.\n *\n * @example\n * ```ts\n * import { $rateLimit, AlephaServerRateLimit } from \"alepha/server/rate-limit\";\n *\n * class ApiService {\n * // Path-specific rate limiting\n * apiRateLimit = $rateLimit({\n * paths: [\"/api/*\"],\n * max: 100,\n * windowMs: 15 * 60 * 1000, // 15 minutes\n * });\n * }\n * ```\n *\n * @see {@link $rateLimit}\n * @module alepha.server.rate-limit\n */\nexport const AlephaServerRateLimit = $module({\n name: \"alepha.server.rate-limit\",\n primitives: [$rateLimit],\n services: [AlephaServer, ServerRateLimitProvider],\n});\n"],"mappings":";;;;;;;;;AAwBA,MAAa,mBAAmB,MAAM;CACpC,MAAM;CACN,QAAQ,EAAE,OAAO;EACf,UAAU,EAAE,SACV,EAAE,OAAO,EACP,aAAa,mCACd,CAAC,CACH;EACD,KAAK,EAAE,SACL,EAAE,OAAO,EACP,aAAa,yCACd,CAAC,CACH;EACD,oBAAoB,EAAE,SACpB,EAAE,QAAQ,EACR,aAAa,0CACd,CAAC,CACH;EACD,wBAAwB,EAAE,SACxB,EAAE,QAAQ,EACR,aAAa,8CACd,CAAC,CACH;EACF,CAAC;CACF,SAAS,EAAE;CACZ,CAAC;AAYF,MAAM,YAAY,EAAE,OAAO;CACzB,sBAAsB,EAAE,OAAO;EAC7B,SAAS,MAAU;EACnB,aAAa;EACd,CAAC;CACF,yBAAyB,EAAE,OAAO;EAChC,SAAS;EACT,aAAa;EACd,CAAC;CACH,CAAC;AAEF,IAAa,0BAAb,MAAqC;CACnC,AAAmB,MAAM,SAAS;CAClC,AAAmB,uBAAuB,QAAQ,qBAAqB;CACvE,AAAmB,MAAM,KAAK,UAAU;CAExC,AAAmB,QAAQ,OAAsB;EAC/C,MAAM;EACN,KAAK,CAAC,KAAK,IAAI,sBAAsB,eAAe;EACrD,CAAC;CAEF,AAAmB,gBAAgB,KAAK,iBAAiB;;;;CAKzD,AAAgB,oBAAiD,EAAE;;;;CAKnE,AAAO,kBAAkB,QAAyC;AAChE,OAAK,kBAAkB,KAAK,OAAO;;CAGrC,AAAmB,UAAU,MAAM;EACjC,IAAI;EACJ,SAAS,YAAY;AAEnB,QAAK,MAAM,UAAU,KAAK,kBACxB,KAAI,OAAO,MACT,MAAK,MAAM,WAAW,OAAO,OAAO;IAClC,MAAM,gBAAgB,KAAK,qBAAqB,UAAU,QAAQ;AAClE,SAAK,MAAM,SAAS,cAClB,OAAM,YAAY,KAAK,sBAAsB,OAAO;;AAM5D,OAAI,KAAK,kBAAkB,SAAS,EAClC,MAAK,IAAI,KACP,oBAAoB,KAAK,kBAAkB,OAAO,wCACnD;;EAGN,CAAC;CAEF,AAAgB,YAAY,MAAM;EAChC,IAAI;EACJ,SAAS,OAAO,EAAE,OAAO,cAAc;GAErC,MAAM,kBAAkB,MAAM,aAAa,KAAK;AAGhD,OAAI,CAAC,gBAAgB,OAAO,CAAC,gBAAgB,SAC3C;GAGF,MAAM,SAAS,MAAM,KAAK,WAAW,SAAS,gBAAgB;AAC9D,QAAK,oBAAoB,SAAS,OAAO;AAEzC,OAAI,CAAC,OAAO,QACV,OAAM,IAAI,UAAU;IAClB,QAAQ;IACR,SAAS;IACV,CAAC;;EAGP,CAAC;CAEF,AAAgB,kBAAkB,MAAM;EACtC,IAAI;EACJ,SAAS,OAAO,EAAE,QAAQ,cAAc;GAEtC,MAAM,YAAY,OAAO,SAAS;AAClC,OAAI,CAAC,UACH;AAKF,OAAI,EAFW,MAAM,KAAK,WAAW,SAAS,UAAU,EAE5C,QAGV,OAAM,IAAI,UAAU;IAClB,QAAQ;IACR,SAAS;IACV,CAAC;;EAKP,CAAC;;;;CAKF,AAAU,sBACR,QACkB;AAClB,SAAO;GACL,KAAK,OAAO,OAAO,KAAK,cAAc;GACtC,UAAU,OAAO,YAAY,KAAK,cAAc;GAChD,cAAc,OAAO;GACrB,oBACE,OAAO,sBAAsB,KAAK,cAAc;GAClD,wBACE,OAAO,0BACP,KAAK,cAAc;GACtB;;;;;CAMH,AAAU,oBACR,SACA,QACM;AACN,UAAQ,MAAM,UAAU,qBAAqB,OAAO,MAAM,UAAU,CAAC;AACrE,UAAQ,MAAM,UACZ,yBACA,OAAO,UAAU,UAAU,CAC5B;AACD,UAAQ,MAAM,UACZ,qBACA,KAAK,KAAK,OAAO,YAAY,IAAK,CAAC,UAAU,CAC9C;AAED,MAAI,CAAC,OAAO,WAAW,OAAO,WAC5B,SAAQ,MAAM,UAAU,eAAe,OAAO,WAAW,UAAU,CAAC;;CAIxE,MAAa,WACX,KACA,UAA4B,EAAE,EACJ;EAC1B,MAAM,WAAW,QAAQ,YAAY,KAAK,IAAI;EAC9C,MAAM,MAAM,QAAQ,OAAO,KAAK,IAAI;EACpC,MAAM,MAAM,KAAK,YAAY,IAAI;EAEjC,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,cAAc,MAAM;EAG1B,MAAM,cAAe,MAAM,KAAK,MAAM,IAAI,IAAI,IAAK;GACjD,OAAO;GACP,aAAa;GACb,MAAM,EAAE;GACT;EAGD,MAAM,YAAY,YAAY,KAAK,QAChC,QAAgB,OAAO,YACzB;EAGD,MAAM,UAAU,UAAU,SAAS;EACnC,MAAM,YAAY,KAAK,IAAI,GAAG,MAAM,UAAU,OAAO;EACrD,MAAM,YAAY,KAAK,IAAI,GAAG,WAAW,YAAY,GAAG;AAGxD,MAAI,SAAS;AACX,aAAU,KAAK,IAAI;AACnB,SAAM,KAAK,MAAM,IAAI,KAAK;IACxB,OAAO,UAAU;IACjB,aAAa,KAAK,IAAI,YAAY,aAAa,YAAY;IAC3D,MAAM;IACP,CAAC;;EAGJ,MAAM,SAA0B;GAC9B;GACA,OAAO;GACP,WAAW,UAAU,YAAY,IAAI;GACrC;GACD;AAED,MAAI,CAAC,QACH,QAAO,aAAa,KAAK,MAAM,YAAY,OAAO,IAAK;AAGzD,SAAO;;CAGT,AAAU,YAAY,KAA4B;AAGhD,SAAO,MADI,KAAK,YAAY,IAAI;;CAIlC,AAAU,YAAY,KAA4B;EAEhD,MAAM,YAAY,IAAI,UAAU;AAChC,MAAI,WAAW;GAEb,MAAM,UAAU,UAAU,MAAM,IAAI,CAAC,GAAG,MAAM;AAC9C,OAAI,QAAS,QAAO;;AAGtB,SAAO,IAAI,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC/OrB,MAAa,cACX,UAAqC,EAAE,KACR;AAC/B,QAAO,gBAAgB,oBAAoB,QAAQ;;AAqBrD,IAAa,qBAAb,cACU,UAEV;CACE,AAAmB,0BAA0B,QAAQ,wBAAwB;CAE7E,IAAW,OAAe;AACxB,SAAO,KAAK,QAAQ,QAAQ,GAAG,KAAK,OAAO;;CAG7C,AAAU,SAAS;AAEjB,OAAK,wBAAwB,kBAAkB,KAAK,QAAQ;;;;;CAM9D,MAAa,MACX,SACA,SAC0B;EAC1B,MAAM,gBAAgB;GAAE,GAAG,KAAK;GAAS,GAAG;GAAS;AACrD,SAAO,KAAK,wBAAwB,WAAW,SAAS,cAAc;;;AAI1E,WAAW,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACdnB,MAAa,wBAAwB,QAAQ;CAC3C,MAAM;CACN,YAAY,CAAC,WAAW;CACxB,UAAU,CAAC,cAAc,wBAAwB;CAClD,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/server/rate-limit/providers/ServerRateLimitProvider.ts","../../../src/server/rate-limit/primitives/$rateLimit.ts","../../../src/server/rate-limit/index.ts"],"sourcesContent":["import { $atom, $env, $hook, $inject, $use, type Static, t } from \"alepha\";\nimport { CacheProvider } from \"alepha/cache\";\nimport { $logger } from \"alepha/logger\";\nimport {\n HttpError,\n type ServerRequest,\n ServerRouterProvider,\n} from \"alepha/server\";\nimport type { RateLimitOptions } from \"../index.ts\";\nimport type { RateLimitPrimitiveOptions } from \"../primitives/$rateLimit.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface RateLimitResult {\n allowed: boolean;\n limit: number;\n remaining: number;\n resetTime: number;\n retryAfter?: number;\n}\n\n/**\n * Rate limit configuration atom (global defaults)\n */\nexport const rateLimitOptions = $atom({\n name: \"alepha.server.rate-limit.options\",\n schema: t.object({\n windowMs: t.optional(\n t.number({\n description: \"Window duration in milliseconds\",\n }),\n ),\n max: t.optional(\n t.number({\n description: \"Maximum number of requests per window\",\n }),\n ),\n skipFailedRequests: t.optional(\n t.boolean({\n description: \"Skip rate limiting for failed requests\",\n }),\n ),\n skipSuccessfulRequests: t.optional(\n t.boolean({\n description: \"Skip rate limiting for successful requests\",\n }),\n ),\n }),\n default: {},\n});\n\nexport type RateLimitAtomOptions = Static<typeof rateLimitOptions.schema>;\n\ndeclare module \"alepha\" {\n interface State {\n [rateLimitOptions.key]: RateLimitAtomOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nconst envSchema = t.object({\n RATE_LIMIT_WINDOW_MS: t.number({\n default: 15 * 60 * 1000, // 15 minutes\n description: \"Rate limit window in milliseconds\",\n }),\n RATE_LIMIT_MAX_REQUESTS: t.number({\n default: 100,\n description: \"Maximum requests per window\",\n }),\n});\n\nexport class ServerRateLimitProvider {\n protected readonly log = $logger();\n protected readonly serverRouterProvider = $inject(ServerRouterProvider);\n protected readonly env = $env(envSchema);\n protected readonly cacheProvider = $inject(CacheProvider);\n protected readonly globalOptions = $use(rateLimitOptions);\n\n protected static readonly CACHE_NAME = \"rate-limit\";\n\n /**\n * Registered rate limit configurations with their path patterns\n */\n public readonly registeredConfigs: RateLimitPrimitiveOptions[] = [];\n\n /**\n * Register a rate limit configuration (called by primitives)\n */\n public registerRateLimit(config: RateLimitPrimitiveOptions): void {\n this.registeredConfigs.push(config);\n }\n\n protected readonly onStart = $hook({\n on: \"start\",\n handler: async () => {\n // Apply path-specific rate limit configs to routes\n for (const config of this.registeredConfigs) {\n if (config.paths) {\n for (const pattern of config.paths) {\n const matchedRoutes = this.serverRouterProvider.getRoutes(pattern);\n for (const route of matchedRoutes) {\n route.rateLimit = this.buildRateLimitOptions(config);\n }\n }\n }\n }\n\n if (this.registeredConfigs.length > 0) {\n this.log.info(\n `Initialized with ${this.registeredConfigs.length} registered rate-limit configurations.`,\n );\n }\n },\n });\n\n public readonly onRequest = $hook({\n on: \"server:onRequest\",\n handler: async ({ route, request }) => {\n // Use route-specific rate limit if defined, otherwise use global options\n const rateLimitConfig = route.rateLimit ?? this.globalOptions;\n\n // Skip if no rate limiting configured\n if (!rateLimitConfig.max && !rateLimitConfig.windowMs) {\n return;\n }\n\n const result = await this.checkLimit(request, rateLimitConfig);\n this.setRateLimitHeaders(request, result);\n\n if (!result.allowed) {\n throw new HttpError({\n status: 429,\n message: \"Too Many Requests\",\n });\n }\n },\n });\n\n public readonly onActionRequest = $hook({\n on: \"action:onRequest\",\n handler: async ({ action, request }) => {\n // Check if this action has rate limiting enabled\n const rateLimit = action.options?.rateLimit;\n if (!rateLimit) {\n return; // No rate limiting for this action\n }\n\n const result = await this.checkLimit(request, rateLimit);\n\n if (!result.allowed) {\n // Actions are internal - don't set HTTP headers\n // Only throw error to prevent action execution\n throw new HttpError({\n status: 429,\n message: \"Too Many Requests\",\n });\n }\n\n // Action allowed - no headers to set since actions are internal\n },\n });\n\n /**\n * Build complete rate limit options by merging with global defaults\n */\n protected buildRateLimitOptions(\n config: RateLimitPrimitiveOptions,\n ): RateLimitOptions {\n return {\n max: config.max ?? this.globalOptions.max,\n windowMs: config.windowMs ?? this.globalOptions.windowMs,\n keyGenerator: config.keyGenerator,\n skipFailedRequests:\n config.skipFailedRequests ?? this.globalOptions.skipFailedRequests,\n skipSuccessfulRequests:\n config.skipSuccessfulRequests ??\n this.globalOptions.skipSuccessfulRequests,\n };\n }\n\n /**\n * Set rate limit headers on the response\n */\n protected setRateLimitHeaders(\n request: ServerRequest,\n result: RateLimitResult,\n ): void {\n request.reply.setHeader(\"X-RateLimit-Limit\", result.limit.toString());\n request.reply.setHeader(\n \"X-RateLimit-Remaining\",\n result.remaining.toString(),\n );\n request.reply.setHeader(\n \"X-RateLimit-Reset\",\n Math.ceil(result.resetTime / 1000).toString(),\n );\n\n if (!result.allowed && result.retryAfter) {\n request.reply.setHeader(\"Retry-After\", result.retryAfter.toString());\n }\n }\n\n public async checkLimit(\n req: ServerRequest,\n options: RateLimitOptions = {},\n ): Promise<RateLimitResult> {\n const windowMs = options.windowMs ?? this.env.RATE_LIMIT_WINDOW_MS;\n const max = options.max ?? this.env.RATE_LIMIT_MAX_REQUESTS;\n const baseKey = this.generateKey(req);\n\n const now = Date.now();\n // Fixed window: round down to nearest window boundary\n const windowStart = Math.floor(now / windowMs) * windowMs;\n const resetTime = windowStart + windowMs;\n\n // Include window timestamp in key for automatic expiration of old windows\n const key = `${baseKey}:${windowStart}`;\n\n // Atomic increment - returns the new count after incrementing\n const count = await this.cacheProvider.incr(\n ServerRateLimitProvider.CACHE_NAME,\n key,\n 1,\n );\n\n const allowed = count <= max;\n const remaining = Math.max(0, max - count);\n\n const result: RateLimitResult = {\n allowed,\n limit: max,\n remaining,\n resetTime,\n };\n\n if (!allowed) {\n result.retryAfter = Math.ceil((resetTime - now) / 1000);\n }\n\n return result;\n }\n\n protected generateKey(req: ServerRequest): string {\n // Use req.ip which is resolved by ServerRequestParser with proper trust proxy handling\n return `ip:${req.ip || \"unknown\"}`;\n }\n}\n","import { $inject, createPrimitive, KIND, Primitive } from \"alepha\";\nimport type { ServerRequest } from \"alepha/server\";\nimport type { RateLimitOptions } from \"../index.ts\";\nimport {\n type RateLimitResult,\n ServerRateLimitProvider,\n} from \"../providers/ServerRateLimitProvider.ts\";\n\n/**\n * Declares rate limiting for server routes or custom usage.\n * This primitive provides methods to check rate limits and configure behavior\n * within the server request/response cycle.\n *\n * @example\n * ```ts\n * class ApiService {\n * // Apply rate limiting to specific paths\n * apiRateLimit = $rateLimit({\n * paths: [\"/api/*\"],\n * max: 100,\n * windowMs: 15 * 60 * 1000, // 15 minutes\n * });\n *\n * // Or use check() method for manual rate limiting\n * customAction = $action({\n * handler: async (req) => {\n * const result = await this.apiRateLimit.check(req);\n * if (!result.allowed) throw new Error(\"Rate limited\");\n * return \"ok\";\n * },\n * });\n * }\n * ```\n */\nexport const $rateLimit = (\n options: RateLimitPrimitiveOptions = {},\n): AbstractRateLimitPrimitive => {\n return createPrimitive(RateLimitPrimitive, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface RateLimitPrimitiveOptions extends RateLimitOptions {\n /**\n * Name identifier for this rate limit (default: property key).\n */\n name?: string;\n /**\n * Path patterns to match (supports wildcards like /api/*).\n */\n paths?: string[];\n}\n\nexport interface AbstractRateLimitPrimitive {\n readonly name: string;\n readonly options: RateLimitPrimitiveOptions;\n check(\n request: ServerRequest,\n options?: RateLimitOptions,\n ): Promise<RateLimitResult>;\n}\n\nexport class RateLimitPrimitive\n extends Primitive<RateLimitPrimitiveOptions>\n implements AbstractRateLimitPrimitive\n{\n protected readonly serverRateLimitProvider = $inject(ServerRateLimitProvider);\n\n public get name(): string {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n protected onInit() {\n // Register this rate limit configuration with the provider\n this.serverRateLimitProvider.registerRateLimit(this.options);\n }\n\n /**\n * Checks rate limit for the given request using this primitive's configuration.\n */\n public async check(\n request: ServerRequest,\n options?: RateLimitOptions,\n ): Promise<RateLimitResult> {\n const mergedOptions = { ...this.options, ...options };\n return this.serverRateLimitProvider.checkLimit(request, mergedOptions);\n }\n}\n\n$rateLimit[KIND] = RateLimitPrimitive;\n","import { $module } from \"alepha\";\nimport { AlephaServer } from \"alepha/server\";\nimport { $rateLimit } from \"./primitives/$rateLimit.ts\";\nimport { ServerRateLimitProvider } from \"./providers/ServerRateLimitProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$rateLimit.ts\";\nexport * from \"./providers/ServerRateLimitProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha/server\" {\n interface ActionPrimitiveOptions<TConfig> {\n /**\n * Rate limiting configuration for this action.\n * When specified, the action will be rate limited according to these settings.\n */\n rateLimit?: RateLimitOptions;\n }\n\n interface ServerRoute {\n /**\n * Route-specific rate limit configuration.\n * If set, overrides the global rate limit options for this route.\n */\n rateLimit?: RateLimitOptions;\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface RateLimitOptions {\n /**\n * Maximum number of requests per window (default: 100).\n */\n max?: number;\n /**\n * Window duration in milliseconds (default: 15 minutes).\n */\n windowMs?: number;\n /**\n * Custom key generator function.\n */\n keyGenerator?: (req: any) => string;\n /**\n * Skip rate limiting for failed requests.\n */\n skipFailedRequests?: boolean;\n /**\n * Skip rate limiting for successful requests.\n */\n skipSuccessfulRequests?: boolean;\n}\n\n/**\n * | type | quality | stability |\n * |------|---------|-----------|\n * | backend | standard | stable |\n *\n * Request rate limiting on actions.\n *\n * **Features:**\n * - Rate limit configuration per action\n *\n * @module alepha.server.rate-limit\n */\nexport const AlephaServerRateLimit = $module({\n name: \"alepha.server.rate-limit\",\n primitives: [$rateLimit],\n services: [AlephaServer, ServerRateLimitProvider],\n});\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as alepha1 from "alepha";
|
|
2
|
-
import { Alepha, KIND, Primitive } from "alepha";
|
|
2
|
+
import { Alepha, Json, KIND, Primitive } from "alepha";
|
|
3
3
|
import { ServerHandler, ServerRouterProvider } from "alepha/server";
|
|
4
4
|
import { DateTimeProvider, DurationLike } from "alepha/datetime";
|
|
5
|
-
import { FileDetector } from "alepha/file";
|
|
6
5
|
import * as alepha_logger0 from "alepha/logger";
|
|
6
|
+
import { Readable } from "node:stream";
|
|
7
7
|
|
|
8
8
|
//#region ../../src/server/static/primitives/$serve.d.ts
|
|
9
9
|
/**
|
|
@@ -15,76 +15,203 @@ declare const $serve: {
|
|
|
15
15
|
};
|
|
16
16
|
interface ServePrimitiveOptions {
|
|
17
17
|
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
* Prefix for the served path.
|
|
19
|
+
*
|
|
20
|
+
* @default "/"
|
|
21
|
+
*/
|
|
22
22
|
path?: string;
|
|
23
23
|
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
* Path to the directory to serve.
|
|
25
|
+
*
|
|
26
|
+
* @default process.cwd()
|
|
27
|
+
*/
|
|
28
28
|
root?: string;
|
|
29
29
|
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
* If true, primitive will be ignored.
|
|
31
|
+
*
|
|
32
|
+
* @default false
|
|
33
|
+
*/
|
|
34
34
|
disabled?: boolean;
|
|
35
35
|
/**
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
* Whether to keep dot files (e.g. `.gitignore`, `.env`) in the served directory.
|
|
37
|
+
*
|
|
38
|
+
* @default true
|
|
39
|
+
*/
|
|
40
40
|
ignoreDotEnvFiles?: boolean;
|
|
41
41
|
/**
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
* Whether to use the index.html file when the path is a directory.
|
|
43
|
+
*
|
|
44
|
+
* @default true
|
|
45
|
+
*/
|
|
46
46
|
indexFallback?: boolean;
|
|
47
47
|
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
* Force all requests "not found" to be served with the index.html file.
|
|
49
|
+
* This is useful for single-page applications (SPAs) that use client-side only routing.
|
|
50
|
+
*/
|
|
51
51
|
historyApiFallback?: boolean;
|
|
52
52
|
/**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
* Optional name of the primitive.
|
|
54
|
+
* This is used for logging and debugging purposes.
|
|
55
|
+
*
|
|
56
|
+
* @default Key name.
|
|
57
|
+
*/
|
|
58
58
|
name?: string;
|
|
59
59
|
/**
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
* Whether to use cache control headers.
|
|
61
|
+
*
|
|
62
|
+
* @default {}
|
|
63
|
+
*/
|
|
64
64
|
cacheControl?: Partial<CacheControlOptions> | false;
|
|
65
65
|
}
|
|
66
66
|
interface CacheControlOptions {
|
|
67
67
|
/**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
* Whether to use cache control headers.
|
|
69
|
+
*
|
|
70
|
+
* @default [.js, .css]
|
|
71
|
+
*/
|
|
72
72
|
fileTypes: string[];
|
|
73
73
|
/**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
* The maximum age of the cache in seconds.
|
|
75
|
+
*
|
|
76
|
+
* @default 60 * 60 * 24 * 2 // 2 days
|
|
77
|
+
*/
|
|
78
78
|
maxAge: DurationLike;
|
|
79
79
|
/**
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
* Whether to use immutable cache control headers.
|
|
81
|
+
*
|
|
82
|
+
* @default true
|
|
83
|
+
*/
|
|
84
84
|
immutable: boolean;
|
|
85
85
|
}
|
|
86
86
|
declare class ServePrimitive extends Primitive<ServePrimitiveOptions> {}
|
|
87
87
|
//#endregion
|
|
88
|
+
//#region ../../src/system/services/FileDetector.d.ts
|
|
89
|
+
interface FileTypeResult {
|
|
90
|
+
/**
|
|
91
|
+
* The detected MIME type
|
|
92
|
+
*/
|
|
93
|
+
mimeType: string;
|
|
94
|
+
/**
|
|
95
|
+
* The detected file extension
|
|
96
|
+
*/
|
|
97
|
+
extension: string;
|
|
98
|
+
/**
|
|
99
|
+
* Whether the file type was verified by magic bytes
|
|
100
|
+
*/
|
|
101
|
+
verified: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* The stream (potentially wrapped to allow re-reading)
|
|
104
|
+
*/
|
|
105
|
+
stream: Readable;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Service for detecting file types and getting content types.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* const detector = alepha.inject(FileDetector);
|
|
113
|
+
*
|
|
114
|
+
* // Get content type from filename
|
|
115
|
+
* const mimeType = detector.getContentType("image.png"); // "image/png"
|
|
116
|
+
*
|
|
117
|
+
* // Detect file type by magic bytes
|
|
118
|
+
* const stream = createReadStream('image.png');
|
|
119
|
+
* const result = await detector.detectFileType(stream, 'image.png');
|
|
120
|
+
* console.log(result.mimeType); // 'image/png'
|
|
121
|
+
* console.log(result.verified); // true if magic bytes match
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
declare class FileDetector {
|
|
125
|
+
/**
|
|
126
|
+
* Magic byte signatures for common file formats.
|
|
127
|
+
* Each signature is represented as an array of bytes or null (wildcard).
|
|
128
|
+
*/
|
|
129
|
+
protected static readonly MAGIC_BYTES: Record<string, {
|
|
130
|
+
signature: (number | null)[];
|
|
131
|
+
mimeType: string;
|
|
132
|
+
}[]>;
|
|
133
|
+
/**
|
|
134
|
+
* All possible format signatures for checking against actual file content
|
|
135
|
+
*/
|
|
136
|
+
protected static readonly ALL_SIGNATURES: {
|
|
137
|
+
signature: (number | null)[];
|
|
138
|
+
mimeType: string;
|
|
139
|
+
ext: string;
|
|
140
|
+
}[];
|
|
141
|
+
/**
|
|
142
|
+
* MIME type map for file extensions.
|
|
143
|
+
*
|
|
144
|
+
* Can be used to get the content type of file based on its extension.
|
|
145
|
+
* Feel free to add more mime types in your project!
|
|
146
|
+
*/
|
|
147
|
+
static readonly mimeMap: Record<string, string>;
|
|
148
|
+
/**
|
|
149
|
+
* Reverse MIME type map for looking up extensions from MIME types.
|
|
150
|
+
* Prefers shorter, more common extensions when multiple exist.
|
|
151
|
+
*/
|
|
152
|
+
protected static readonly reverseMimeMap: Record<string, string>;
|
|
153
|
+
/**
|
|
154
|
+
* Returns the file extension for a given MIME type.
|
|
155
|
+
*
|
|
156
|
+
* @param mimeType - The MIME type to look up
|
|
157
|
+
* @returns The file extension (without dot), or "bin" if not found
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* const detector = alepha.inject(FileDetector);
|
|
162
|
+
* const ext = detector.getExtensionFromMimeType("image/png"); // "png"
|
|
163
|
+
* const ext2 = detector.getExtensionFromMimeType("application/octet-stream"); // "bin"
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
getExtensionFromMimeType(mimeType: string): string;
|
|
167
|
+
/**
|
|
168
|
+
* Returns the content type of file based on its filename.
|
|
169
|
+
*
|
|
170
|
+
* @param filename - The filename to check
|
|
171
|
+
* @returns The MIME type
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* const detector = alepha.inject(FileDetector);
|
|
176
|
+
* const mimeType = detector.getContentType("image.png"); // "image/png"
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
getContentType(filename: string): string;
|
|
180
|
+
/**
|
|
181
|
+
* Detects the file type by checking magic bytes against the stream content.
|
|
182
|
+
*
|
|
183
|
+
* @param stream - The readable stream to check
|
|
184
|
+
* @param filename - The filename (used to get the extension)
|
|
185
|
+
* @returns File type information including MIME type, extension, and verification status
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```typescript
|
|
189
|
+
* const detector = alepha.inject(FileDetector);
|
|
190
|
+
* const stream = createReadStream('image.png');
|
|
191
|
+
* const result = await detector.detectFileType(stream, 'image.png');
|
|
192
|
+
* console.log(result.mimeType); // 'image/png'
|
|
193
|
+
* console.log(result.verified); // true if magic bytes match
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
detectFileType(stream: Readable, filename: string): Promise<FileTypeResult>;
|
|
197
|
+
/**
|
|
198
|
+
* Reads all bytes from a stream and returns the first N bytes along with a new stream containing all data.
|
|
199
|
+
* This approach reads the entire stream upfront to avoid complex async handling issues.
|
|
200
|
+
*
|
|
201
|
+
* @protected
|
|
202
|
+
*/
|
|
203
|
+
protected peekBytes(stream: Readable, numBytes: number): Promise<{
|
|
204
|
+
buffer: Buffer;
|
|
205
|
+
stream: Readable;
|
|
206
|
+
}>;
|
|
207
|
+
/**
|
|
208
|
+
* Checks if a buffer matches a magic byte signature.
|
|
209
|
+
*
|
|
210
|
+
* @protected
|
|
211
|
+
*/
|
|
212
|
+
protected matchesSignature(buffer: Buffer, signature: (number | null)[]): boolean;
|
|
213
|
+
}
|
|
214
|
+
//#endregion
|
|
88
215
|
//#region ../../src/server/static/providers/ServerStaticProvider.d.ts
|
|
89
216
|
declare class ServerStaticProvider {
|
|
90
217
|
protected readonly alepha: Alepha;
|
|
@@ -110,9 +237,15 @@ interface ServeDirectory {
|
|
|
110
237
|
//#endregion
|
|
111
238
|
//#region ../../src/server/static/index.d.ts
|
|
112
239
|
/**
|
|
113
|
-
*
|
|
240
|
+
* | type | quality | stability |
|
|
241
|
+
* |------|---------|-----------|
|
|
242
|
+
* | backend | standard | stable |
|
|
243
|
+
*
|
|
244
|
+
* Static file serving.
|
|
245
|
+
*
|
|
246
|
+
* **Features:**
|
|
247
|
+
* - Serve static files from directory
|
|
114
248
|
*
|
|
115
|
-
* @see {@link ServerStaticProvider}
|
|
116
249
|
* @module alepha.server.static
|
|
117
250
|
*/
|
|
118
251
|
declare const AlephaServerStatic: alepha1.Service<alepha1.Module>;
|