alepha 0.14.2 → 0.14.4
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 +1 -1
- package/dist/api/audits/index.browser.js +5 -5
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.d.ts +706 -785
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +13 -13
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.browser.js +5 -5
- package/dist/api/files/index.browser.js.map +1 -1
- package/dist/api/files/index.d.ts +58 -137
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +71 -71
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +5 -5
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +29 -108
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +10 -10
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js +10 -10
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +504 -171
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +12 -12
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.browser.js +163 -10
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +277 -351
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +196 -91
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.browser.js +19 -19
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +787 -852
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +827 -596
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.browser.js +6 -6
- package/dist/api/verifications/index.browser.js.map +1 -1
- package/dist/api/verifications/index.d.ts +128 -128
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +6 -6
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/bin/index.d.ts +1 -2
- package/dist/bin/index.js +0 -1
- package/dist/bin/index.js.map +1 -1
- package/dist/cli/index.d.ts +252 -131
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +595 -395
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +46 -11
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +99 -19
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +40 -22
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +45 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +40 -22
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +40 -22
- package/dist/core/index.native.js.map +1 -1
- package/dist/fake/index.js +195 -168
- package/dist/fake/index.js.map +1 -1
- package/dist/file/index.d.ts +8 -0
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js +3 -0
- package/dist/file/index.js.map +1 -1
- package/dist/logger/index.d.ts +1 -1
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +12 -2
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.d.ts +59 -195
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +201 -430
- package/dist/orm/index.js.map +1 -1
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +171 -155
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +0 -1
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +12 -0
- package/dist/server/cache/index.d.ts.map +1 -1
- package/dist/server/cache/index.js +55 -2
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts +6 -0
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/compress/index.js +38 -1
- package/dist/server/compress/index.js.map +1 -1
- package/dist/server/core/index.browser.js +2 -2
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +10 -10
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +7 -4
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/links/index.browser.js +22 -6
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +46 -44
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +24 -41
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/static/index.js +4 -0
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +2 -1
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +9 -5
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/vite/index.d.ts +101 -106
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +574 -503
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts +7 -7
- package/package.json +7 -7
- package/src/api/audits/controllers/{AuditController.ts → AdminAuditController.ts} +5 -6
- package/src/api/audits/entities/audits.ts +5 -5
- package/src/api/audits/index.browser.ts +1 -1
- package/src/api/audits/index.ts +3 -3
- package/src/api/audits/primitives/$audit.spec.ts +276 -0
- package/src/api/audits/services/AuditService.spec.ts +495 -0
- package/src/api/files/__tests__/$bucket.spec.ts +91 -0
- package/src/api/files/controllers/AdminFileStatsController.spec.ts +166 -0
- package/src/api/files/controllers/{StorageStatsController.ts → AdminFileStatsController.ts} +2 -2
- package/src/api/files/controllers/FileController.spec.ts +558 -0
- package/src/api/files/controllers/FileController.ts +4 -5
- package/src/api/files/entities/files.ts +5 -5
- package/src/api/files/index.browser.ts +1 -1
- package/src/api/files/index.ts +4 -4
- package/src/api/files/jobs/FileJobs.spec.ts +52 -0
- package/src/api/files/services/FileService.spec.ts +109 -0
- package/src/api/jobs/__tests__/JobController.spec.ts +343 -0
- package/src/api/jobs/controllers/{JobController.ts → AdminJobController.ts} +2 -2
- package/src/api/jobs/entities/jobExecutions.ts +5 -5
- package/src/api/jobs/index.ts +3 -3
- package/src/api/jobs/primitives/$job.spec.ts +476 -0
- package/src/api/notifications/controllers/{NotificationController.ts → AdminNotificationController.ts} +4 -5
- package/src/api/notifications/entities/notifications.ts +5 -5
- package/src/api/notifications/index.browser.ts +1 -1
- package/src/api/notifications/index.ts +4 -4
- package/src/api/parameters/controllers/{ConfigController.ts → AdminConfigController.ts} +46 -107
- package/src/api/parameters/entities/parameters.ts +7 -17
- package/src/api/parameters/index.ts +3 -3
- package/src/api/parameters/primitives/$config.spec.ts +356 -0
- package/src/api/parameters/schemas/activateConfigBodySchema.ts +12 -0
- package/src/api/parameters/schemas/checkScheduledResponseSchema.ts +8 -0
- package/src/api/parameters/schemas/configCurrentResponseSchema.ts +13 -0
- package/src/api/parameters/schemas/configHistoryResponseSchema.ts +9 -0
- package/src/api/parameters/schemas/configNameParamSchema.ts +10 -0
- package/src/api/parameters/schemas/configNamesResponseSchema.ts +8 -0
- package/src/api/parameters/schemas/configTreeNodeSchema.ts +13 -0
- package/src/api/parameters/schemas/configVersionParamSchema.ts +9 -0
- package/src/api/parameters/schemas/configVersionResponseSchema.ts +9 -0
- package/src/api/parameters/schemas/configsByStatusResponseSchema.ts +9 -0
- package/src/api/parameters/schemas/createConfigVersionBodySchema.ts +24 -0
- package/src/api/parameters/schemas/index.ts +15 -0
- package/src/api/parameters/schemas/parameterResponseSchema.ts +26 -0
- package/src/api/parameters/schemas/parameterStatusSchema.ts +13 -0
- package/src/api/parameters/schemas/rollbackConfigBodySchema.ts +15 -0
- package/src/api/parameters/schemas/statusParamSchema.ts +9 -0
- package/src/api/users/__tests__/EmailVerification.spec.ts +369 -0
- package/src/api/users/__tests__/PasswordReset.spec.ts +550 -0
- package/src/api/users/controllers/AdminIdentityController.spec.ts +365 -0
- package/src/api/users/controllers/{IdentityController.ts → AdminIdentityController.ts} +3 -4
- package/src/api/users/controllers/AdminSessionController.spec.ts +274 -0
- package/src/api/users/controllers/{SessionController.ts → AdminSessionController.ts} +3 -4
- package/src/api/users/controllers/AdminUserController.spec.ts +372 -0
- package/src/api/users/controllers/AdminUserController.ts +116 -0
- package/src/api/users/controllers/UserController.ts +4 -107
- package/src/api/users/controllers/UserRealmController.ts +3 -0
- package/src/api/users/entities/identities.ts +6 -6
- package/src/api/users/entities/sessions.ts +6 -6
- package/src/api/users/entities/users.ts +9 -9
- package/src/api/users/index.ts +9 -6
- package/src/api/users/primitives/$userRealm.ts +13 -8
- package/src/api/users/services/CredentialService.spec.ts +509 -0
- package/src/api/users/services/CredentialService.ts +46 -0
- package/src/api/users/services/IdentityService.ts +15 -0
- package/src/api/users/services/RegistrationService.spec.ts +630 -0
- package/src/api/users/services/RegistrationService.ts +18 -0
- package/src/api/users/services/SessionService.spec.ts +301 -0
- package/src/api/users/services/SessionService.ts +110 -1
- package/src/api/users/services/UserService.ts +67 -2
- package/src/api/verifications/__tests__/CodeVerification.spec.ts +318 -0
- package/src/api/verifications/__tests__/LinkVerification.spec.ts +279 -0
- package/src/api/verifications/entities/verifications.ts +6 -6
- package/src/api/verifications/jobs/VerificationJobs.spec.ts +50 -0
- package/src/batch/__tests__/startup-buffering.spec.ts +458 -0
- package/src/batch/primitives/$batch.spec.ts +766 -0
- package/src/batch/providers/BatchProvider.spec.ts +786 -0
- package/src/bin/index.ts +0 -1
- package/src/bucket/__tests__/shared.ts +194 -0
- package/src/bucket/primitives/$bucket.spec.ts +104 -0
- package/src/bucket/providers/FileStorageProvider.spec.ts +13 -0
- package/src/bucket/providers/LocalFileStorageProvider.spec.ts +77 -0
- package/src/bucket/providers/MemoryFileStorageProvider.spec.ts +82 -0
- package/src/cache/core/__tests__/shared.ts +377 -0
- package/src/cache/core/primitives/$cache.spec.ts +111 -0
- package/src/cache/redis/__tests__/cache-redis.spec.ts +70 -0
- package/src/cli/apps/AlephaCli.ts +25 -6
- package/src/cli/atoms/buildOptions.ts +88 -0
- package/src/cli/commands/build.ts +32 -69
- package/src/cli/commands/db.ts +0 -4
- package/src/cli/commands/dev.ts +34 -10
- package/src/cli/commands/gen/changelog.spec.ts +315 -0
- package/src/cli/commands/{changelog.ts → gen/changelog.ts} +9 -9
- package/src/cli/commands/gen/env.ts +53 -0
- package/src/cli/commands/gen/openapi.ts +71 -0
- package/src/cli/commands/gen/resource.ts +15 -0
- package/src/cli/commands/gen.ts +24 -0
- package/src/cli/commands/init.ts +2 -1
- package/src/cli/commands/root.ts +12 -3
- package/src/cli/commands/test.ts +0 -1
- package/src/cli/commands/typecheck.ts +5 -0
- package/src/cli/commands/verify.ts +1 -1
- package/src/cli/defineConfig.ts +49 -7
- package/src/cli/index.ts +2 -2
- package/src/cli/services/AlephaCliUtils.ts +105 -55
- package/src/cli/services/GitMessageParser.ts +1 -1
- package/src/command/helpers/Asker.spec.ts +127 -0
- package/src/command/helpers/Runner.spec.ts +126 -0
- package/src/command/helpers/Runner.ts +1 -1
- package/src/command/primitives/$command.spec.ts +1588 -0
- package/src/command/primitives/$command.ts +0 -6
- package/src/command/providers/CliProvider.ts +75 -27
- package/src/core/Alepha.ts +87 -0
- package/src/core/__tests__/Alepha-emit.spec.ts +22 -0
- package/src/core/__tests__/Alepha-graph.spec.ts +93 -0
- package/src/core/__tests__/Alepha-has.spec.ts +41 -0
- package/src/core/__tests__/Alepha-inject.spec.ts +93 -0
- package/src/core/__tests__/Alepha-register.spec.ts +81 -0
- package/src/core/__tests__/Alepha-start.spec.ts +176 -0
- package/src/core/__tests__/Alepha-with.spec.ts +14 -0
- package/src/core/__tests__/TypeBox-usecases.spec.ts +35 -0
- package/src/core/__tests__/TypeBoxLocale.spec.ts +15 -0
- package/src/core/__tests__/descriptor.spec.ts +34 -0
- package/src/core/__tests__/fixtures/A.ts +5 -0
- package/src/core/__tests__/pagination.spec.ts +77 -0
- package/src/core/helpers/jsonSchemaToTypeBox.ts +2 -2
- package/src/core/primitives/$atom.spec.ts +43 -0
- package/src/core/primitives/$hook.spec.ts +130 -0
- package/src/core/primitives/$inject.spec.ts +175 -0
- package/src/core/primitives/$module.spec.ts +115 -0
- package/src/core/providers/CodecManager.spec.ts +740 -0
- package/src/core/providers/EventManager.spec.ts +762 -0
- package/src/core/providers/EventManager.ts +4 -0
- package/src/core/providers/StateManager.spec.ts +365 -0
- package/src/core/providers/TypeProvider.spec.ts +1607 -0
- package/src/core/providers/TypeProvider.ts +20 -26
- package/src/datetime/primitives/$interval.spec.ts +103 -0
- package/src/datetime/providers/DateTimeProvider.spec.ts +86 -0
- package/src/email/primitives/$email.spec.ts +175 -0
- package/src/email/providers/LocalEmailProvider.spec.ts +341 -0
- package/src/fake/__tests__/keyName.example.ts +40 -0
- package/src/fake/__tests__/keyName.spec.ts +152 -0
- package/src/fake/__tests__/module.example.ts +32 -0
- package/src/fake/providers/FakeProvider.spec.ts +438 -0
- package/src/file/providers/FileSystemProvider.ts +8 -0
- package/src/file/providers/NodeFileSystemProvider.spec.ts +418 -0
- package/src/file/providers/NodeFileSystemProvider.ts +5 -0
- package/src/file/services/FileDetector.spec.ts +591 -0
- package/src/lock/core/__tests__/shared.ts +190 -0
- package/src/lock/core/providers/MemoryLockProvider.spec.ts +25 -0
- package/src/lock/redis/providers/RedisLockProvider.spec.ts +25 -0
- package/src/logger/__tests__/SimpleFormatterProvider.spec.ts +109 -0
- package/src/logger/index.ts +15 -3
- package/src/logger/primitives/$logger.spec.ts +108 -0
- package/src/logger/services/Logger.spec.ts +295 -0
- package/src/mcp/__tests__/errors.spec.ts +175 -0
- package/src/mcp/__tests__/integration.spec.ts +450 -0
- package/src/mcp/helpers/jsonrpc.spec.ts +380 -0
- package/src/mcp/primitives/$prompt.spec.ts +468 -0
- package/src/mcp/primitives/$resource.spec.ts +390 -0
- package/src/mcp/primitives/$tool.spec.ts +406 -0
- package/src/mcp/providers/McpServerProvider.spec.ts +797 -0
- package/src/mcp/transports/StdioMcpTransport.ts +1 -1
- package/src/orm/__tests__/$repository-crud.spec.ts +276 -0
- package/src/orm/__tests__/$repository-hooks.spec.ts +325 -0
- package/src/orm/__tests__/$repository-orderBy.spec.ts +128 -0
- package/src/orm/__tests__/$repository-pagination-sort.spec.ts +149 -0
- package/src/orm/__tests__/$repository-save.spec.ts +37 -0
- package/src/orm/__tests__/ModelBuilder-integration.spec.ts +490 -0
- package/src/orm/__tests__/ModelBuilder-types.spec.ts +186 -0
- package/src/orm/__tests__/PostgresProvider.spec.ts +46 -0
- package/src/orm/__tests__/delete-returning.spec.ts +256 -0
- package/src/orm/__tests__/deletedAt.spec.ts +80 -0
- package/src/orm/__tests__/enums.spec.ts +315 -0
- package/src/orm/__tests__/execute.spec.ts +72 -0
- package/src/orm/__tests__/fixtures/bigEntitySchema.ts +65 -0
- package/src/orm/__tests__/fixtures/userEntitySchema.ts +27 -0
- package/src/orm/__tests__/joins.spec.ts +1114 -0
- package/src/orm/__tests__/page.spec.ts +287 -0
- package/src/orm/__tests__/primaryKey.spec.ts +87 -0
- package/src/orm/__tests__/query-date-encoding.spec.ts +402 -0
- package/src/orm/__tests__/ref-auto-onDelete.spec.ts +156 -0
- package/src/orm/__tests__/references.spec.ts +102 -0
- package/src/orm/__tests__/security.spec.ts +710 -0
- package/src/orm/__tests__/sqlite.spec.ts +111 -0
- package/src/orm/__tests__/string-operators.spec.ts +429 -0
- package/src/orm/__tests__/timestamps.spec.ts +388 -0
- package/src/orm/__tests__/validation.spec.ts +183 -0
- package/src/orm/__tests__/version.spec.ts +64 -0
- package/src/orm/helpers/parseQueryString.spec.ts +196 -0
- package/src/orm/index.ts +2 -8
- package/src/orm/primitives/$repository.spec.ts +137 -0
- package/src/orm/primitives/$sequence.spec.ts +29 -0
- package/src/orm/primitives/$transaction.spec.ts +82 -0
- package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -3
- package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +1 -1
- package/src/orm/providers/drivers/DatabaseProvider.ts +1 -1
- package/src/orm/providers/drivers/NodePostgresProvider.ts +3 -3
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +1 -1
- package/src/orm/providers/drivers/PglitePostgresProvider.ts +2 -2
- package/src/orm/services/ModelBuilder.spec.ts +575 -0
- package/src/orm/services/Repository.spec.ts +137 -0
- package/src/queue/core/__tests__/shared.ts +143 -0
- package/src/queue/core/providers/MemoryQueueProvider.spec.ts +23 -0
- package/src/queue/core/providers/WorkerProvider.spec.ts +394 -0
- package/src/queue/redis/providers/RedisQueueProvider.spec.ts +23 -0
- package/src/redis/__tests__/redis.spec.ts +58 -0
- package/src/retry/primitives/$retry.spec.ts +234 -0
- package/src/retry/providers/RetryProvider.spec.ts +438 -0
- package/src/router/__tests__/match.spec.ts +252 -0
- package/src/router/providers/RouterProvider.spec.ts +197 -0
- package/src/scheduler/__tests__/$scheduler-cron.spec.ts +25 -0
- package/src/scheduler/__tests__/$scheduler-interval.spec.ts +25 -0
- package/src/scheduler/__tests__/shared.ts +77 -0
- package/src/security/__tests__/bug-1-wildcard-after-start.spec.ts +229 -0
- package/src/security/__tests__/bug-2-password-validation.spec.ts +245 -0
- package/src/security/__tests__/bug-3-regex-vulnerability.spec.ts +407 -0
- package/src/security/__tests__/bug-4-oauth2-validation.spec.ts +439 -0
- package/src/security/__tests__/multi-layer-permissions.spec.ts +522 -0
- package/src/security/primitives/$permission.spec.ts +30 -0
- package/src/security/primitives/$permission.ts +2 -2
- package/src/security/primitives/$realm.spec.ts +101 -0
- package/src/security/primitives/$role.spec.ts +52 -0
- package/src/security/primitives/$serviceAccount.spec.ts +61 -0
- package/src/security/providers/SecurityProvider.spec.ts +350 -0
- package/src/server/auth/providers/ServerAuthProvider.ts +0 -2
- package/src/server/cache/providers/ServerCacheProvider.spec.ts +1125 -0
- package/src/server/cache/providers/ServerCacheProvider.ts +94 -9
- package/src/server/compress/providers/ServerCompressProvider.spec.ts +31 -0
- package/src/server/compress/providers/ServerCompressProvider.ts +63 -2
- package/src/server/cookies/providers/ServerCookiesProvider.spec.ts +253 -0
- package/src/server/core/__tests__/ServerRouterProvider-getRoutes.spec.ts +334 -0
- package/src/server/core/__tests__/ServerRouterProvider-requestId.spec.ts +129 -0
- package/src/server/core/helpers/ServerReply.ts +2 -2
- package/src/server/core/primitives/$action.spec.ts +191 -0
- package/src/server/core/primitives/$route.spec.ts +65 -0
- package/src/server/core/providers/ServerBodyParserProvider.spec.ts +93 -0
- package/src/server/core/providers/ServerLoggerProvider.spec.ts +100 -0
- package/src/server/core/providers/ServerProvider.ts +14 -2
- package/src/server/core/services/HttpClient.spec.ts +123 -0
- package/src/server/core/services/UserAgentParser.spec.ts +111 -0
- package/src/server/cors/providers/ServerCorsProvider.spec.ts +481 -0
- package/src/server/health/providers/ServerHealthProvider.spec.ts +22 -0
- package/src/server/helmet/providers/ServerHelmetProvider.spec.ts +105 -0
- package/src/server/links/__tests__/$action.spec.ts +238 -0
- package/src/server/links/__tests__/fixtures/CrudApp.ts +122 -0
- package/src/server/links/__tests__/requestId.spec.ts +120 -0
- package/src/server/links/primitives/$remote.spec.ts +228 -0
- package/src/server/links/providers/LinkProvider.spec.ts +54 -0
- package/src/server/links/providers/LinkProvider.ts +49 -3
- package/src/server/links/providers/ServerLinksProvider.ts +1 -53
- package/src/server/links/schemas/apiLinksResponseSchema.ts +7 -0
- package/src/server/metrics/providers/ServerMetricsProvider.spec.ts +25 -0
- package/src/server/multipart/providers/ServerMultipartProvider.spec.ts +528 -0
- package/src/server/proxy/primitives/$proxy.spec.ts +87 -0
- package/src/server/rate-limit/__tests__/ActionRateLimit.spec.ts +211 -0
- package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +344 -0
- package/src/server/security/__tests__/BasicAuth.spec.ts +684 -0
- package/src/server/security/__tests__/ServerSecurityProvider-realm.spec.ts +388 -0
- package/src/server/security/providers/ServerSecurityProvider.spec.ts +123 -0
- package/src/server/static/primitives/$serve.spec.ts +193 -0
- package/src/server/static/providers/ServerStaticProvider.ts +10 -0
- package/src/server/swagger/__tests__/ui.spec.ts +52 -0
- package/src/server/swagger/primitives/$swagger.spec.ts +193 -0
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +19 -12
- package/src/sms/primitives/$sms.spec.ts +165 -0
- package/src/sms/providers/LocalSmsProvider.spec.ts +224 -0
- package/src/sms/providers/MemorySmsProvider.spec.ts +193 -0
- package/src/thread/primitives/$thread.spec.ts +186 -0
- package/src/topic/core/__tests__/shared.ts +144 -0
- package/src/topic/core/providers/MemoryTopicProvider.spec.ts +23 -0
- package/src/topic/redis/providers/RedisTopicProvider.spec.ts +23 -0
- package/src/vite/helpers/importViteReact.ts +13 -0
- package/src/vite/index.ts +1 -21
- package/src/vite/plugins/viteAlephaDev.ts +32 -5
- package/src/vite/plugins/viteAlephaSsrPreload.ts +222 -0
- package/src/vite/tasks/buildClient.ts +11 -0
- package/src/vite/tasks/buildServer.ts +47 -3
- package/src/vite/tasks/devServer.ts +69 -0
- package/src/vite/tasks/index.ts +2 -1
- package/src/vite/tasks/runAlepha.ts +7 -1
- package/src/websocket/__tests__/$websocket-new.spec.ts +195 -0
- package/src/websocket/primitives/$channel.spec.ts +30 -0
- package/src/cli/assets/viteConfigTs.ts +0 -14
- package/src/cli/commands/run.ts +0 -24
- package/src/vite/plugins/viteAlepha.ts +0 -37
- package/src/vite/plugins/viteAlephaBuild.ts +0 -281
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Alepha } from "alepha";
|
|
3
|
+
import { $realm } from "alepha/security";
|
|
4
|
+
import { $action, $route, ForbiddenError, ServerProvider } from "alepha/server";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { AlephaServerSecurity } from "../index.ts";
|
|
7
|
+
|
|
8
|
+
describe("ServerSecurityProvider - Realm Protection", () => {
|
|
9
|
+
it("should allow access when user belongs to the required realm", async () => {
|
|
10
|
+
class TestApp {
|
|
11
|
+
realmA = $realm({
|
|
12
|
+
secret: "test-realm-a",
|
|
13
|
+
roles: [
|
|
14
|
+
{
|
|
15
|
+
name: "user",
|
|
16
|
+
permissions: [{ name: "*" }],
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
realmB = $realm({
|
|
22
|
+
secret: "test-realm-b",
|
|
23
|
+
roles: [
|
|
24
|
+
{
|
|
25
|
+
name: "user",
|
|
26
|
+
permissions: [{ name: "*" }],
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Action that requires realmA
|
|
32
|
+
actionA = $action({
|
|
33
|
+
secure: { realm: "realmA" },
|
|
34
|
+
handler: () => "REALM_A",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Route that requires realmB
|
|
38
|
+
routeB = $route({
|
|
39
|
+
method: "GET",
|
|
40
|
+
path: "/realm-b",
|
|
41
|
+
secure: { realm: "realmB" },
|
|
42
|
+
handler: () => "REALM_B",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const alepha = Alepha.create().with(AlephaServerSecurity);
|
|
47
|
+
const app = alepha.inject(TestApp);
|
|
48
|
+
await alepha.start();
|
|
49
|
+
|
|
50
|
+
const userA = {
|
|
51
|
+
id: randomUUID(),
|
|
52
|
+
roles: ["user"],
|
|
53
|
+
realm: "realmA",
|
|
54
|
+
name: "Test User A",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const userB = {
|
|
58
|
+
id: randomUUID(),
|
|
59
|
+
roles: ["user"],
|
|
60
|
+
realm: "realmB",
|
|
61
|
+
name: "Test User B",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// User from realmA should access actionA via .run()
|
|
65
|
+
expect(await app.actionA.run({}, { user: userA })).toBe("REALM_A");
|
|
66
|
+
|
|
67
|
+
// User from realmA should access actionA via .fetch()
|
|
68
|
+
expect(
|
|
69
|
+
await app.actionA.fetch({}, { user: userA }).then((it) => it.data),
|
|
70
|
+
).toBe("REALM_A");
|
|
71
|
+
|
|
72
|
+
// User from realmB should access routeB via .run()
|
|
73
|
+
const tokenB = await app.realmB.createToken(userB);
|
|
74
|
+
const responseB = await fetch(
|
|
75
|
+
`${alepha.inject(ServerProvider).hostname}/realm-b`,
|
|
76
|
+
{
|
|
77
|
+
headers: {
|
|
78
|
+
authorization: `Bearer ${tokenB.access_token}`,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
expect(await responseB.text()).toBe("REALM_B");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should deny access when user does not belong to the required realm", async () => {
|
|
87
|
+
class TestApp {
|
|
88
|
+
realmA = $realm({
|
|
89
|
+
secret: "test-realm-a",
|
|
90
|
+
roles: [
|
|
91
|
+
{
|
|
92
|
+
name: "user",
|
|
93
|
+
permissions: [{ name: "*" }],
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
realmB = $realm({
|
|
99
|
+
secret: "test-realm-b",
|
|
100
|
+
roles: [
|
|
101
|
+
{
|
|
102
|
+
name: "user",
|
|
103
|
+
permissions: [{ name: "*" }],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Action that requires realmA
|
|
109
|
+
actionA = $action({
|
|
110
|
+
secure: { realm: "realmA" },
|
|
111
|
+
handler: () => "REALM_A",
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Route that requires realmB
|
|
115
|
+
routeB = $route({
|
|
116
|
+
method: "GET",
|
|
117
|
+
path: "/realm-b",
|
|
118
|
+
secure: { realm: "realmB" },
|
|
119
|
+
handler: () => "REALM_B",
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const alepha = Alepha.create().with(AlephaServerSecurity);
|
|
124
|
+
const app = alepha.inject(TestApp);
|
|
125
|
+
await alepha.start();
|
|
126
|
+
|
|
127
|
+
const userA = {
|
|
128
|
+
id: randomUUID(),
|
|
129
|
+
roles: ["user"],
|
|
130
|
+
realm: "realmA",
|
|
131
|
+
name: "Test User A",
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const userB = {
|
|
135
|
+
id: randomUUID(),
|
|
136
|
+
roles: ["user"],
|
|
137
|
+
realm: "realmB",
|
|
138
|
+
name: "Test User B",
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// User from realmB should NOT access actionA via .run()
|
|
142
|
+
await expect(app.actionA.run({}, { user: userB })).rejects.toThrowError(
|
|
143
|
+
ForbiddenError,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// User from realmB should NOT access actionA via .fetch()
|
|
147
|
+
await expect(app.actionA.fetch({}, { user: userB })).rejects.toThrowError(
|
|
148
|
+
"User must belong to realm 'realmA' to access this route",
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// User from realmA should NOT access routeB via HTTP (requires realmB)
|
|
152
|
+
const tokenA = await app.realmA.createToken(userA);
|
|
153
|
+
|
|
154
|
+
const responseB = await fetch(
|
|
155
|
+
`${alepha.inject(ServerProvider).hostname}/realm-b`,
|
|
156
|
+
{
|
|
157
|
+
headers: {
|
|
158
|
+
authorization: `Bearer ${tokenA.access_token}`,
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(responseB.status).toBe(403);
|
|
164
|
+
const errorData = await responseB.json();
|
|
165
|
+
expect(errorData).toEqual({
|
|
166
|
+
error: "ForbiddenError",
|
|
167
|
+
message: "User must belong to realm 'realmB' to access this route",
|
|
168
|
+
status: 403,
|
|
169
|
+
requestId: expect.any(String),
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should work with actions when user has no realm attribute", async () => {
|
|
174
|
+
class TestApp {
|
|
175
|
+
realmA = $realm({
|
|
176
|
+
secret: "test-realm-a",
|
|
177
|
+
roles: [
|
|
178
|
+
{
|
|
179
|
+
name: "user",
|
|
180
|
+
permissions: [{ name: "*" }],
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
actionA = $action({
|
|
186
|
+
secure: { realm: "realmA" },
|
|
187
|
+
handler: () => "REALM_A",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const alepha = Alepha.create().with(AlephaServerSecurity);
|
|
192
|
+
const app = alepha.inject(TestApp);
|
|
193
|
+
await alepha.start();
|
|
194
|
+
|
|
195
|
+
const userWithoutRealm = {
|
|
196
|
+
id: randomUUID(),
|
|
197
|
+
roles: ["user"],
|
|
198
|
+
// no realm attribute
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// User without realm attribute should be denied via .run()
|
|
202
|
+
await expect(
|
|
203
|
+
app.actionA.run({}, { user: userWithoutRealm }),
|
|
204
|
+
).rejects.toThrowError(ForbiddenError);
|
|
205
|
+
|
|
206
|
+
// Note: .fetch() in test mode auto-assigns the first realm during token creation,
|
|
207
|
+
// so a user without a realm will succeed if the first realm matches the required realm.
|
|
208
|
+
// This is expected test helper behavior for convenience.
|
|
209
|
+
expect(
|
|
210
|
+
await app.actionA
|
|
211
|
+
.fetch({}, { user: userWithoutRealm })
|
|
212
|
+
.then((it) => it.data),
|
|
213
|
+
).toBe("REALM_A");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("should combine realm and permission checks", async () => {
|
|
217
|
+
class TestApp {
|
|
218
|
+
realmA = $realm({
|
|
219
|
+
secret: "test-realm-a",
|
|
220
|
+
roles: [
|
|
221
|
+
{
|
|
222
|
+
name: "admin",
|
|
223
|
+
permissions: [{ name: "*" }],
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: "user",
|
|
227
|
+
permissions: [{ name: "read:*" }],
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
realmB = $realm({
|
|
233
|
+
secret: "test-realm-b",
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Requires both realmA and admin permission
|
|
237
|
+
adminAction = $action({
|
|
238
|
+
group: "admin",
|
|
239
|
+
secure: { realm: "realmA" },
|
|
240
|
+
handler: () => "ADMIN",
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Requires both realmA and read permission
|
|
244
|
+
readAction = $action({
|
|
245
|
+
group: "read",
|
|
246
|
+
secure: { realm: "realmA" },
|
|
247
|
+
handler: () => "READ",
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const alepha = Alepha.create().with(AlephaServerSecurity);
|
|
252
|
+
const app = alepha.inject(TestApp);
|
|
253
|
+
await alepha.start();
|
|
254
|
+
|
|
255
|
+
const adminUserRealmA = {
|
|
256
|
+
id: randomUUID(),
|
|
257
|
+
roles: ["admin"],
|
|
258
|
+
realm: "realmA",
|
|
259
|
+
name: "Admin User",
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const regularUserRealmA = {
|
|
263
|
+
id: randomUUID(),
|
|
264
|
+
roles: ["user"],
|
|
265
|
+
realm: "realmA",
|
|
266
|
+
name: "Regular User",
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const adminUserRealmB = {
|
|
270
|
+
id: randomUUID(),
|
|
271
|
+
roles: ["admin"],
|
|
272
|
+
realm: "realmB",
|
|
273
|
+
name: "Admin User B",
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// Admin from realmA should access both actions via .run()
|
|
277
|
+
expect(await app.adminAction.run({}, { user: adminUserRealmA })).toBe(
|
|
278
|
+
"ADMIN",
|
|
279
|
+
);
|
|
280
|
+
expect(await app.readAction.run({}, { user: adminUserRealmA })).toBe(
|
|
281
|
+
"READ",
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// Admin from realmA should access both actions via .fetch()
|
|
285
|
+
expect(
|
|
286
|
+
await app.adminAction
|
|
287
|
+
.fetch({}, { user: adminUserRealmA })
|
|
288
|
+
.then((it) => it.data),
|
|
289
|
+
).toBe("ADMIN");
|
|
290
|
+
expect(
|
|
291
|
+
await app.readAction
|
|
292
|
+
.fetch({}, { user: adminUserRealmA })
|
|
293
|
+
.then((it) => it.data),
|
|
294
|
+
).toBe("READ");
|
|
295
|
+
|
|
296
|
+
// Regular user from realmA can access both actions via .run() (realm check only, no permission check)
|
|
297
|
+
// When secure: { realm: "..." } is used, permissions are not enforced
|
|
298
|
+
expect(await app.adminAction.run({}, { user: regularUserRealmA })).toBe(
|
|
299
|
+
"ADMIN",
|
|
300
|
+
);
|
|
301
|
+
expect(await app.readAction.run({}, { user: regularUserRealmA })).toBe(
|
|
302
|
+
"READ",
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Regular user from realmA can access both actions via .fetch()
|
|
306
|
+
expect(
|
|
307
|
+
await app.adminAction
|
|
308
|
+
.fetch({}, { user: regularUserRealmA })
|
|
309
|
+
.then((it) => it.data),
|
|
310
|
+
).toBe("ADMIN");
|
|
311
|
+
expect(
|
|
312
|
+
await app.readAction
|
|
313
|
+
.fetch({}, { user: regularUserRealmA })
|
|
314
|
+
.then((it) => it.data),
|
|
315
|
+
).toBe("READ");
|
|
316
|
+
|
|
317
|
+
// Admin from realmB should NOT access any action via .run() (wrong realm)
|
|
318
|
+
await expect(
|
|
319
|
+
app.adminAction.run({}, { user: adminUserRealmB }),
|
|
320
|
+
).rejects.toThrowError(ForbiddenError);
|
|
321
|
+
await expect(
|
|
322
|
+
app.readAction.run({}, { user: adminUserRealmB }),
|
|
323
|
+
).rejects.toThrowError(ForbiddenError);
|
|
324
|
+
|
|
325
|
+
await expect(
|
|
326
|
+
app.adminAction.fetch({}, { user: adminUserRealmB }),
|
|
327
|
+
).rejects.toThrowError(
|
|
328
|
+
"User must belong to realm 'realmA' to access this route",
|
|
329
|
+
);
|
|
330
|
+
await expect(
|
|
331
|
+
app.readAction.fetch({}, { user: adminUserRealmB }),
|
|
332
|
+
).rejects.toThrowError(
|
|
333
|
+
"User must belong to realm 'realmA' to access this route",
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("should work with fetch requests when realm is valid", async () => {
|
|
338
|
+
class TestApp {
|
|
339
|
+
realmA = $realm({
|
|
340
|
+
secret: "test-realm-a",
|
|
341
|
+
roles: [
|
|
342
|
+
{
|
|
343
|
+
name: "user",
|
|
344
|
+
permissions: [{ name: "*" }],
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
actionA = $action({
|
|
350
|
+
secure: { realm: "realmA" },
|
|
351
|
+
handler: () => "SUCCESS",
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const alepha = Alepha.create().with(AlephaServerSecurity);
|
|
356
|
+
const app = alepha.inject(TestApp);
|
|
357
|
+
await alepha.start();
|
|
358
|
+
|
|
359
|
+
const user = {
|
|
360
|
+
id: randomUUID(),
|
|
361
|
+
roles: ["user"],
|
|
362
|
+
realm: "realmA",
|
|
363
|
+
name: "Test User",
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// Should work via .run()
|
|
367
|
+
expect(await app.actionA.run({}, { user })).toBe("SUCCESS");
|
|
368
|
+
|
|
369
|
+
// Should work via .fetch()
|
|
370
|
+
expect(await app.actionA.fetch({}, { user }).then((it) => it.data)).toBe(
|
|
371
|
+
"SUCCESS",
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
// Should work via HTTP with token
|
|
375
|
+
const token = await app.realmA.createToken(user);
|
|
376
|
+
const response = await fetch(
|
|
377
|
+
`${alepha.inject(ServerProvider).hostname}${app.actionA.route.path}`,
|
|
378
|
+
{
|
|
379
|
+
headers: {
|
|
380
|
+
authorization: `Bearer ${token.access_token}`,
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
expect(response.status).toBe(200);
|
|
386
|
+
expect(await response.text()).toBe("SUCCESS");
|
|
387
|
+
});
|
|
388
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Alepha } from "alepha";
|
|
3
|
+
import { $realm } from "alepha/security";
|
|
4
|
+
import {
|
|
5
|
+
$action,
|
|
6
|
+
ForbiddenError,
|
|
7
|
+
HttpError,
|
|
8
|
+
ServerProvider,
|
|
9
|
+
UnauthorizedError,
|
|
10
|
+
} from "alepha/server";
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
import { AlephaServerSecurity } from "../index.ts";
|
|
13
|
+
|
|
14
|
+
describe("ServerSecurityProvider", () => {
|
|
15
|
+
it("should protect action from unauthorized users", async () => {
|
|
16
|
+
class TestApp {
|
|
17
|
+
ok = $action({
|
|
18
|
+
handler: () => "OK",
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const alepha = Alepha.create().with(AlephaServerSecurity);
|
|
23
|
+
const app = alepha.inject(TestApp);
|
|
24
|
+
await alepha.start();
|
|
25
|
+
|
|
26
|
+
// in testing environment, .run() a dummy user is created
|
|
27
|
+
expect(await app.ok.run({})).toBe("OK");
|
|
28
|
+
|
|
29
|
+
// but you can force empty user
|
|
30
|
+
await expect(app.ok.run({}, { user: undefined })).rejects.toThrowError(
|
|
31
|
+
UnauthorizedError,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// .fetch() will also generates a dummy user in testing environment
|
|
35
|
+
expect(await app.ok.fetch({}).then((it) => it.data)).toBe("OK");
|
|
36
|
+
|
|
37
|
+
// but you can also force empty user
|
|
38
|
+
await expect(app.ok.fetch({}, { user: undefined })).rejects.toThrowError(
|
|
39
|
+
HttpError,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// regular fetch does not trigger helpers
|
|
43
|
+
expect(
|
|
44
|
+
await fetch(
|
|
45
|
+
`${alepha.inject(ServerProvider).hostname}${app.ok.route.path}`,
|
|
46
|
+
).then((it) => it.json()),
|
|
47
|
+
).toEqual({
|
|
48
|
+
error: "UnauthorizedError",
|
|
49
|
+
message: "Invalid authorization header, maybe token is missing ?",
|
|
50
|
+
status: 401,
|
|
51
|
+
requestId: expect.any(String),
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should guard by permission", async () => {
|
|
56
|
+
class TestApp {
|
|
57
|
+
admin = $action({
|
|
58
|
+
group: "read",
|
|
59
|
+
handler: () => "ADMIN",
|
|
60
|
+
});
|
|
61
|
+
user = $action({
|
|
62
|
+
group: "read",
|
|
63
|
+
handler: () => "USER",
|
|
64
|
+
});
|
|
65
|
+
realm = $realm({
|
|
66
|
+
secret: "test",
|
|
67
|
+
roles: [
|
|
68
|
+
{
|
|
69
|
+
name: "admin",
|
|
70
|
+
permissions: [{ name: "*" }],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "user",
|
|
74
|
+
permissions: [{ name: "read:user" }],
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const alepha = Alepha.create().with(AlephaServerSecurity);
|
|
81
|
+
const app = alepha.inject(TestApp);
|
|
82
|
+
await alepha.start();
|
|
83
|
+
|
|
84
|
+
const user = {
|
|
85
|
+
id: randomUUID(),
|
|
86
|
+
roles: ["user"],
|
|
87
|
+
};
|
|
88
|
+
const admin = {
|
|
89
|
+
id: randomUUID(),
|
|
90
|
+
roles: ["admin"],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// as user, you can access user action
|
|
94
|
+
expect(await app.user.run({}, { user })).toBe("USER");
|
|
95
|
+
expect(await app.user.fetch({}, { user }).then((it) => it.data)).toBe(
|
|
96
|
+
"USER",
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// as admin, you can access user action too
|
|
100
|
+
expect(await app.user.run({}, { user: admin })).toBe("USER");
|
|
101
|
+
expect(
|
|
102
|
+
await app.user.fetch({}, { user: admin }).then((it) => it.data),
|
|
103
|
+
).toBe("USER");
|
|
104
|
+
|
|
105
|
+
// as user, you cannot access admin action
|
|
106
|
+
await expect(app.admin.run({}, { user })).rejects.toThrowError(
|
|
107
|
+
ForbiddenError,
|
|
108
|
+
);
|
|
109
|
+
await expect(app.admin.fetch({}, { user })).rejects.toThrowError(
|
|
110
|
+
new HttpError({
|
|
111
|
+
status: 403,
|
|
112
|
+
message: "User is not allowed to access 'read:admin'",
|
|
113
|
+
requestId: expect.any(String),
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// as admin, you can access admin action
|
|
118
|
+
expect(await app.admin.run({}, { user: admin })).toBe("ADMIN");
|
|
119
|
+
expect(
|
|
120
|
+
await app.admin.fetch({}, { user: admin }).then((it) => it.data),
|
|
121
|
+
).toBe("ADMIN");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { brotliCompressSync, gzipSync } from "node:zlib";
|
|
5
|
+
import { Alepha } from "alepha";
|
|
6
|
+
import { AlephaServer, ServerProvider } from "alepha/server";
|
|
7
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
8
|
+
import {
|
|
9
|
+
$serve,
|
|
10
|
+
AlephaServerStatic,
|
|
11
|
+
type ServePrimitiveOptions,
|
|
12
|
+
} from "../index.ts";
|
|
13
|
+
|
|
14
|
+
// --- Test Setup: Create a temporary directory for static files ---
|
|
15
|
+
|
|
16
|
+
const tempTestDir = join(tmpdir(), `alepha-static-test-${Date.now()}`);
|
|
17
|
+
const tempWeirdFileName = "weird file 02020&&&&&éééé";
|
|
18
|
+
|
|
19
|
+
beforeAll(async () => {
|
|
20
|
+
await mkdir(tempTestDir, { recursive: true });
|
|
21
|
+
|
|
22
|
+
// Create some test files
|
|
23
|
+
await writeFile(join(tempTestDir, "index.html"), "<h1>Hello World</h1>");
|
|
24
|
+
await writeFile(join(tempTestDir, "style.css"), "body { color: red; }");
|
|
25
|
+
await writeFile(join(tempTestDir, "script.js"), "console.log('test');");
|
|
26
|
+
await writeFile(join(tempTestDir, ".secret"), "should-not-be-served");
|
|
27
|
+
await writeFile(join(tempTestDir, tempWeirdFileName), "ok");
|
|
28
|
+
|
|
29
|
+
// Create pre-compressed versions
|
|
30
|
+
const cssContent = "body { color: blue; }";
|
|
31
|
+
await writeFile(join(tempTestDir, "compressed.css"), cssContent);
|
|
32
|
+
await writeFile(join(tempTestDir, "compressed.css.gz"), gzipSync(cssContent));
|
|
33
|
+
await writeFile(
|
|
34
|
+
join(tempTestDir, "compressed.css.br"),
|
|
35
|
+
brotliCompressSync(cssContent),
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterAll(async () => {
|
|
40
|
+
// Clean up the temporary directory
|
|
41
|
+
await rm(tempTestDir, { recursive: true, force: true });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// --- Test Suite ---
|
|
45
|
+
|
|
46
|
+
describe("alepha/server/static", () => {
|
|
47
|
+
const setupServer = async (serveOptions: ServePrimitiveOptions) => {
|
|
48
|
+
class TestApp {
|
|
49
|
+
staticContent = $serve({ root: tempTestDir, ...serveOptions });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const alepha = Alepha.create({ env: { LOG_LEVEL: "error" } })
|
|
53
|
+
.with(AlephaServer)
|
|
54
|
+
.with(AlephaServerStatic)
|
|
55
|
+
.with(TestApp);
|
|
56
|
+
|
|
57
|
+
await alepha.start();
|
|
58
|
+
const server = alepha.inject(ServerProvider);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
hostname: server.hostname,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
test("should serve a basic static file with correct content-type", async () => {
|
|
66
|
+
const { hostname } = await setupServer({});
|
|
67
|
+
|
|
68
|
+
const response = await fetch(`${hostname}/style.css`);
|
|
69
|
+
|
|
70
|
+
expect(response.status).toBe(200);
|
|
71
|
+
expect(response.headers.get("content-type")).toBe("text/css");
|
|
72
|
+
expect(await response.text()).toBe("body { color: red; }");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("should serve a file with invalid character", async () => {
|
|
76
|
+
const { hostname } = await setupServer({});
|
|
77
|
+
const r1 = await fetch(`${hostname}/${tempWeirdFileName}`);
|
|
78
|
+
expect(r1.status).toBe(200);
|
|
79
|
+
expect(await r1.text()).toBe("ok");
|
|
80
|
+
|
|
81
|
+
const r2 = await fetch(`${hostname}/${encodeURI(tempWeirdFileName)}`);
|
|
82
|
+
expect(r2.status).toBe(200);
|
|
83
|
+
expect(await r2.text()).toBe("ok");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("should serve index.html for root path", async () => {
|
|
87
|
+
const { hostname } = await setupServer({});
|
|
88
|
+
|
|
89
|
+
const response = await fetch(`${hostname}/`);
|
|
90
|
+
|
|
91
|
+
expect(response.status).toBe(200);
|
|
92
|
+
expect(response.headers.get("content-type")).toBe("text/html");
|
|
93
|
+
expect(await response.text()).toBe("<h1>Hello World</h1>");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("should handle ETag and Last-Modified headers for caching", async () => {
|
|
97
|
+
const { hostname } = await setupServer({});
|
|
98
|
+
|
|
99
|
+
const initialResponse = await fetch(`${hostname}/script.js`);
|
|
100
|
+
const etag = initialResponse.headers.get("etag");
|
|
101
|
+
const lastModified = initialResponse.headers.get("last-modified");
|
|
102
|
+
|
|
103
|
+
expect(etag).toBeDefined();
|
|
104
|
+
expect(lastModified).toBeDefined();
|
|
105
|
+
|
|
106
|
+
// Second request with caching headers
|
|
107
|
+
const cachedResponse = await fetch(`${hostname}/script.js`, {
|
|
108
|
+
headers: {
|
|
109
|
+
"if-none-match": etag!,
|
|
110
|
+
"if-modified-since": lastModified!,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(cachedResponse.status).toBe(304); // Not Modified
|
|
115
|
+
expect(await cachedResponse.text()).toBe(""); // Body should be empty
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("should serve pre-compressed .gz file if accepted", async () => {
|
|
119
|
+
const { hostname } = await setupServer({});
|
|
120
|
+
|
|
121
|
+
const response = await fetch(`${hostname}/compressed.css`, {
|
|
122
|
+
headers: { "Accept-Encoding": "gzip, deflate, br" },
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(response.status).toBe(200);
|
|
126
|
+
expect(response.headers.get("content-encoding")).toBe("br");
|
|
127
|
+
expect(response.headers.get("content-type")).toBe("text/css");
|
|
128
|
+
// The fetched content will be automatically decompressed by fetch
|
|
129
|
+
expect(await response.text()).toBe("body { color: blue; }");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("should serve pre-compressed .br file if accepted and preferred", async () => {
|
|
133
|
+
const { hostname } = await setupServer({});
|
|
134
|
+
|
|
135
|
+
// Brotli is generally preferred by servers if available
|
|
136
|
+
const response = await fetch(`${hostname}/compressed.css`, {
|
|
137
|
+
headers: { "Accept-Encoding": "br, gzip" },
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(response.status).toBe(200);
|
|
141
|
+
expect(response.headers.get("content-encoding")).toBe("br");
|
|
142
|
+
expect(await response.text()).toBe("body { color: blue; }");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("should not serve dotfiles by default", async () => {
|
|
146
|
+
const { hostname } = await setupServer({});
|
|
147
|
+
|
|
148
|
+
const response = await fetch(`${hostname}/.secret`);
|
|
149
|
+
expect(response.status).toBe(404);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("should serve dotfiles if ignoreDotEnvFiles is false", async () => {
|
|
153
|
+
const { hostname } = await setupServer({ ignoreDotEnvFiles: false });
|
|
154
|
+
|
|
155
|
+
const response = await fetch(`${hostname}/.secret`);
|
|
156
|
+
expect(response.status).toBe(200);
|
|
157
|
+
expect(await response.text()).toBe("should-not-be-served");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("should use historyApiFallback for SPA routing", async () => {
|
|
161
|
+
const { hostname } = await setupServer({ historyApiFallback: true });
|
|
162
|
+
|
|
163
|
+
// A path that doesn't correspond to a real file
|
|
164
|
+
const response = await fetch(`${hostname}/some/deep/spa/route`);
|
|
165
|
+
|
|
166
|
+
expect(response.status).toBe(200);
|
|
167
|
+
expect(response.headers.get("content-type")).toBe("text/html");
|
|
168
|
+
expect(await response.text()).toBe("<h1>Hello World</h1>");
|
|
169
|
+
|
|
170
|
+
// Should still not fallback for paths that look like files
|
|
171
|
+
const fileResponse = await fetch(`${hostname}/non-existent/style.css`);
|
|
172
|
+
expect(fileResponse.status).toBe(404);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("should apply Cache-Control headers for configured file types", async () => {
|
|
176
|
+
const { hostname } = await setupServer({
|
|
177
|
+
cacheControl: {
|
|
178
|
+
fileTypes: [".css"],
|
|
179
|
+
maxAge: [1, "day"],
|
|
180
|
+
immutable: true,
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const cssResponse = await fetch(`${hostname}/style.css`);
|
|
185
|
+
expect(cssResponse.headers.get("cache-control")).toBe(
|
|
186
|
+
"public, max-age=86400, immutable",
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
// JS file should not have the header
|
|
190
|
+
const jsResponse = await fetch(`${hostname}/script.js`);
|
|
191
|
+
expect(jsResponse.headers.get("cache-control")).toBeNull();
|
|
192
|
+
});
|
|
193
|
+
});
|