alepha 0.14.1 → 0.14.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- 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 +784 -784
- 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 +57 -57
- 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 +165 -165
- 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 +583 -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 +281 -276
- 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 +778 -764
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +831 -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 +125 -125
- 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/batch/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/cache/core/index.js.map +1 -1
- package/dist/cli/index.d.ts +249 -218
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +951 -821
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +40 -0
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +97 -17
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +14 -18
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +29 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +21 -24
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +21 -24
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.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/lock/redis/index.js.map +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.browser.js +26 -5
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.d.ts +146 -121
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +49 -24
- package/dist/orm/index.js.map +1 -1
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.js.map +1 -1
- package/dist/router/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +6 -6
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +29 -29
- 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.js.map +1 -1
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/compress/index.js +2 -0
- package/dist/server/compress/index.js.map +1 -1
- package/dist/server/cookies/index.browser.js.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +1 -1
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/health/index.d.ts +17 -17
- package/dist/server/helmet/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/multipart/index.js.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/security/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 +8 -3
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +12 -4
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.js.map +1 -1
- 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 +13 -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 +54 -16
- package/src/cli/apps/AlephaPackageBuilderCli.ts +2 -1
- package/src/cli/assets/appRouterTs.ts +1 -1
- package/src/cli/commands/{ViteCommands.ts → build.ts} +2 -105
- package/src/cli/commands/clean.ts +14 -0
- package/src/cli/commands/{DrizzleCommands.ts → db.ts} +10 -117
- package/src/cli/commands/{DeployCommands.ts → deploy.ts} +1 -1
- package/src/cli/commands/dev.ts +69 -0
- package/src/cli/commands/format.ts +17 -0
- package/src/cli/commands/gen/changelog.spec.ts +315 -0
- package/src/cli/commands/{ChangelogCommands.ts → gen/changelog.ts} +16 -31
- package/src/cli/commands/gen/openapi.ts +71 -0
- package/src/cli/commands/gen.ts +18 -0
- package/src/cli/commands/{CoreCommands.ts → init.ts} +4 -40
- package/src/cli/commands/lint.ts +17 -0
- package/src/cli/commands/root.ts +41 -0
- package/src/cli/commands/run.ts +24 -0
- package/src/cli/commands/test.ts +42 -0
- package/src/cli/commands/typecheck.ts +24 -0
- package/src/cli/commands/{VerifyCommands.ts → verify.ts} +1 -13
- package/src/cli/defineConfig.ts +10 -1
- package/src/cli/index.ts +17 -7
- package/src/cli/services/AlephaCliUtils.ts +71 -32
- 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/primitives/$command.spec.ts +1588 -0
- package/src/command/providers/CliProvider.ts +74 -24
- package/src/core/Alepha.ts +52 -4
- 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/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/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.browser.ts +1 -1
- package/src/orm/index.ts +10 -6
- 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/{PostgresTypeProvider.ts → DatabaseTypeProvider.ts} +25 -3
- 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 +378 -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 +942 -0
- package/src/server/compress/providers/ServerCompressProvider.spec.ts +31 -0
- package/src/server/compress/providers/ServerCompressProvider.ts +2 -0
- 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/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 +3 -1
- 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/swagger/__tests__/ui.spec.ts +52 -0
- package/src/server/swagger/primitives/$swagger.spec.ts +193 -0
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +18 -8
- 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/plugins/viteAlephaDev.ts +16 -4
- 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/commands/BiomeCommands.ts +0 -29
|
@@ -0,0 +1,942 @@
|
|
|
1
|
+
import { Alepha } from "alepha";
|
|
2
|
+
import { $action } from "alepha/server";
|
|
3
|
+
import { afterEach, beforeEach, describe, test } from "vitest";
|
|
4
|
+
import { AlephaServerCache, ServerCacheProvider } from "../index.ts";
|
|
5
|
+
|
|
6
|
+
class TestApp {
|
|
7
|
+
counter = 0;
|
|
8
|
+
private resetCounter = 0;
|
|
9
|
+
|
|
10
|
+
cachedAction = $action({
|
|
11
|
+
cache: true,
|
|
12
|
+
handler: () => `cached-${this.counter++}`,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
uncachedAction = $action({
|
|
16
|
+
cache: false,
|
|
17
|
+
handler: () => `uncached-${this.counter++}`,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
cachedWithCustomTtl = $action({
|
|
21
|
+
cache: { store: { ttl: 1000 } },
|
|
22
|
+
handler: () => `ttl-cached-${this.counter++}`,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
asyncCachedAction = $action({
|
|
26
|
+
cache: true,
|
|
27
|
+
handler: async () => {
|
|
28
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
29
|
+
return `async-cached-${this.counter++}`;
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
etagOnlyAction = $action({
|
|
34
|
+
cache: { etag: true },
|
|
35
|
+
handler: () => `etag-only-${this.counter++}`,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
etagAndCacheAction = $action({
|
|
39
|
+
cache: true,
|
|
40
|
+
handler: () => `etag-and-cache-${this.counter++}`,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
dynamicContentAction = $action({
|
|
44
|
+
cache: { etag: true },
|
|
45
|
+
handler: () => `dynamic-${Date.now()}`,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
cacheWithControlTrue = $action({
|
|
49
|
+
cache: { store: true, control: true },
|
|
50
|
+
handler: () => `control-true-${this.counter++}`,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
cacheWithControlString = $action({
|
|
54
|
+
cache: { store: true, control: "public, max-age=600, immutable" },
|
|
55
|
+
handler: () => `control-string-${this.counter++}`,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
cacheWithControlObject = $action({
|
|
59
|
+
cache: {
|
|
60
|
+
store: true,
|
|
61
|
+
control: {
|
|
62
|
+
public: true,
|
|
63
|
+
maxAge: 3600,
|
|
64
|
+
mustRevalidate: true,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
handler: () => `control-object-${this.counter++}`,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
cacheWithComplexControl = $action({
|
|
71
|
+
cache: {
|
|
72
|
+
store: {
|
|
73
|
+
ttl: [10, "minutes"],
|
|
74
|
+
},
|
|
75
|
+
etag: true,
|
|
76
|
+
control: {
|
|
77
|
+
public: true,
|
|
78
|
+
maxAge: 600,
|
|
79
|
+
sMaxAge: 1200,
|
|
80
|
+
immutable: true,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
handler: () => `complex-control-${this.counter++}`,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
cacheWithDurationMaxAge = $action({
|
|
87
|
+
cache: {
|
|
88
|
+
store: true,
|
|
89
|
+
control: {
|
|
90
|
+
public: true,
|
|
91
|
+
maxAge: [5, "minutes"],
|
|
92
|
+
sMaxAge: [1, "hour"],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
handler: () => `duration-maxage-${this.counter++}`,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
cacheWith30Seconds = $action({
|
|
99
|
+
cache: {
|
|
100
|
+
store: true,
|
|
101
|
+
control: {
|
|
102
|
+
public: true,
|
|
103
|
+
maxAge: [30, "seconds"],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
handler: () => "test-30s",
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
cacheWith10Minutes = $action({
|
|
110
|
+
cache: {
|
|
111
|
+
store: true,
|
|
112
|
+
control: {
|
|
113
|
+
public: true,
|
|
114
|
+
maxAge: [10, "minutes"],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
handler: () => "test-10m",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
cacheWith2Hours = $action({
|
|
121
|
+
cache: {
|
|
122
|
+
store: true,
|
|
123
|
+
control: {
|
|
124
|
+
public: true,
|
|
125
|
+
maxAge: [2, "hours"],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
handler: () => "test-2h",
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
cacheWith1Day = $action({
|
|
132
|
+
cache: {
|
|
133
|
+
store: true,
|
|
134
|
+
control: {
|
|
135
|
+
public: true,
|
|
136
|
+
maxAge: [1, "day"],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
handler: () => "test-1d",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
cacheWithMixedMaxAge = $action({
|
|
143
|
+
cache: {
|
|
144
|
+
store: true,
|
|
145
|
+
control: {
|
|
146
|
+
public: true,
|
|
147
|
+
maxAge: 600, // number (seconds)
|
|
148
|
+
sMaxAge: [20, "minutes"], // DurationLike
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
handler: () => "mixed",
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
errorAction = $action({
|
|
155
|
+
cache: true,
|
|
156
|
+
handler: ({ reply }) => {
|
|
157
|
+
reply.status = 500;
|
|
158
|
+
return `error-${this.counter++}`;
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
notFoundAction = $action({
|
|
163
|
+
cache: true,
|
|
164
|
+
handler: ({ reply }) => {
|
|
165
|
+
reply.status = 404;
|
|
166
|
+
return `not-found-${this.counter++}`;
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
conditionalErrorAction = $action({
|
|
171
|
+
cache: true,
|
|
172
|
+
handler: ({ reply }) => {
|
|
173
|
+
if (this.counter === 0) {
|
|
174
|
+
reply.status = 500;
|
|
175
|
+
this.counter++;
|
|
176
|
+
return "error-0";
|
|
177
|
+
}
|
|
178
|
+
return `success-${this.counter++}`;
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
reset() {
|
|
183
|
+
this.counter = this.resetCounter++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
describe("ServerCacheProvider", () => {
|
|
188
|
+
let alepha: Alepha;
|
|
189
|
+
let app: TestApp;
|
|
190
|
+
let cacheProvider: ServerCacheProvider;
|
|
191
|
+
|
|
192
|
+
beforeEach(async () => {
|
|
193
|
+
alepha = Alepha.create().with(AlephaServerCache);
|
|
194
|
+
app = alepha.inject(TestApp);
|
|
195
|
+
cacheProvider = alepha.inject(ServerCacheProvider);
|
|
196
|
+
await alepha.start();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
afterEach(async () => {
|
|
200
|
+
await alepha?.stop();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("ETag support", () => {
|
|
204
|
+
test("should return 200 on first request and 304 on subsequent requests with same ETag", async ({
|
|
205
|
+
expect,
|
|
206
|
+
}) => {
|
|
207
|
+
const firstResponse = await app.cachedAction.fetch();
|
|
208
|
+
expect(firstResponse.status).toBe(200);
|
|
209
|
+
expect(firstResponse.data).toBe("cached-0");
|
|
210
|
+
|
|
211
|
+
const secondResponse = await app.cachedAction.fetch();
|
|
212
|
+
expect(secondResponse.status).toBe(304);
|
|
213
|
+
expect(secondResponse.data).toBe("cached-0");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("should return fresh data after cache invalidation", async ({
|
|
217
|
+
expect,
|
|
218
|
+
}) => {
|
|
219
|
+
const firstResponse = await app.cachedAction.fetch();
|
|
220
|
+
expect(firstResponse.data).toBe("cached-0");
|
|
221
|
+
|
|
222
|
+
await cacheProvider.invalidate(app.cachedAction.route);
|
|
223
|
+
|
|
224
|
+
const afterInvalidation = await app.cachedAction.fetch();
|
|
225
|
+
expect(afterInvalidation.status).toBe(200);
|
|
226
|
+
expect(afterInvalidation.data).toBe("cached-1");
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("should set etag property on route and handle caching correctly", async ({
|
|
230
|
+
expect,
|
|
231
|
+
}) => {
|
|
232
|
+
// Make first request to trigger caching
|
|
233
|
+
const response = await app.cachedAction.fetch();
|
|
234
|
+
expect(response.status).toBe(200);
|
|
235
|
+
expect(response.data).toBe("cached-0");
|
|
236
|
+
|
|
237
|
+
// Subsequent request should return 304 (cached with etag check)
|
|
238
|
+
const cachedResponse = await app.cachedAction.fetch();
|
|
239
|
+
expect(cachedResponse.status).toBe(304);
|
|
240
|
+
|
|
241
|
+
// Cache invalidation should work
|
|
242
|
+
await cacheProvider.invalidate(app.cachedAction.route);
|
|
243
|
+
const afterInvalidation = await app.cachedAction.fetch();
|
|
244
|
+
expect(afterInvalidation.status).toBe(200);
|
|
245
|
+
expect(afterInvalidation.data).toBe("cached-1");
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe("Cache behavior", () => {
|
|
250
|
+
test("should cache responses for actions with cache: true", async ({
|
|
251
|
+
expect,
|
|
252
|
+
}) => {
|
|
253
|
+
for (let i = 0; i < 5; i++) {
|
|
254
|
+
const response1 = await app.cachedAction.fetch();
|
|
255
|
+
const response2 = await app.cachedAction.fetch();
|
|
256
|
+
expect(response1.data).toBe(response2.data);
|
|
257
|
+
app.reset();
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("should not cache responses for actions with cache: false", async ({
|
|
262
|
+
expect,
|
|
263
|
+
}) => {
|
|
264
|
+
for (let i = 0; i < 5; i++) {
|
|
265
|
+
const response1 = await app.uncachedAction.fetch();
|
|
266
|
+
const response2 = await app.uncachedAction.fetch();
|
|
267
|
+
expect(response1.data).not.toBe(response2.data);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("should handle async cached actions correctly", async ({ expect }) => {
|
|
272
|
+
const response1 = await app.asyncCachedAction.fetch();
|
|
273
|
+
const response2 = await app.asyncCachedAction.fetch();
|
|
274
|
+
|
|
275
|
+
expect(response1.data).toBe("async-cached-0");
|
|
276
|
+
expect(response2.data).toBe("async-cached-0");
|
|
277
|
+
expect(response1.data).toBe(response2.data);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe("Cache invalidation", () => {
|
|
282
|
+
test("should invalidate cache using ServerCacheProvider.invalidate()", async ({
|
|
283
|
+
expect,
|
|
284
|
+
}) => {
|
|
285
|
+
const initialResponse = await app.cachedAction.fetch();
|
|
286
|
+
const cachedResponse = await app.cachedAction.fetch();
|
|
287
|
+
|
|
288
|
+
expect(initialResponse.data).toBe(cachedResponse.data);
|
|
289
|
+
|
|
290
|
+
await cacheProvider.invalidate(app.cachedAction.route);
|
|
291
|
+
|
|
292
|
+
const afterInvalidation = await app.cachedAction.fetch();
|
|
293
|
+
expect(afterInvalidation.data).not.toBe(initialResponse.data);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("should invalidate cache using action.invalidate() method", async ({
|
|
297
|
+
expect,
|
|
298
|
+
}) => {
|
|
299
|
+
const initialResponse = await app.cachedAction.fetch();
|
|
300
|
+
const cachedResponse = await app.cachedAction.fetch();
|
|
301
|
+
|
|
302
|
+
expect(initialResponse.data).toBe(cachedResponse.data);
|
|
303
|
+
|
|
304
|
+
await app.cachedAction.invalidate();
|
|
305
|
+
|
|
306
|
+
const afterInvalidation = await app.cachedAction.fetch();
|
|
307
|
+
expect(afterInvalidation.data).not.toBe(initialResponse.data);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("should not affect other cached actions when invalidating specific route", async ({
|
|
311
|
+
expect,
|
|
312
|
+
}) => {
|
|
313
|
+
const cachedResponse1 = await app.cachedAction.fetch();
|
|
314
|
+
const ttlCachedResponse1 = await app.cachedWithCustomTtl.fetch();
|
|
315
|
+
|
|
316
|
+
await cacheProvider.invalidate(app.cachedAction.route);
|
|
317
|
+
|
|
318
|
+
const cachedResponse2 = await app.cachedAction.fetch();
|
|
319
|
+
const ttlCachedResponse2 = await app.cachedWithCustomTtl.fetch();
|
|
320
|
+
|
|
321
|
+
expect(cachedResponse1.data).not.toBe(cachedResponse2.data);
|
|
322
|
+
expect(ttlCachedResponse1.data).toBe(ttlCachedResponse2.data);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe("Cache configuration", () => {
|
|
327
|
+
test("should respect custom TTL configuration", async ({ expect }) => {
|
|
328
|
+
const response1 = await app.cachedWithCustomTtl.fetch();
|
|
329
|
+
const response2 = await app.cachedWithCustomTtl.fetch();
|
|
330
|
+
|
|
331
|
+
expect(response1.data).toBe(response2.data);
|
|
332
|
+
expect(response1.data).toBe("ttl-cached-0");
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
describe("Error handling", () => {
|
|
337
|
+
test("should handle invalidation of non-existent route gracefully", async ({
|
|
338
|
+
expect,
|
|
339
|
+
}) => {
|
|
340
|
+
expect(async () => {
|
|
341
|
+
await cacheProvider.invalidate("non-existent-route" as any);
|
|
342
|
+
}).not.toThrow();
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("should handle multiple concurrent cache requests correctly", async ({
|
|
346
|
+
expect,
|
|
347
|
+
}) => {
|
|
348
|
+
const promises = Array.from({ length: 10 }, () =>
|
|
349
|
+
app.cachedAction.fetch(),
|
|
350
|
+
);
|
|
351
|
+
const responses = await Promise.all(promises);
|
|
352
|
+
|
|
353
|
+
const firstData = responses[0].data;
|
|
354
|
+
for (const response of responses) {
|
|
355
|
+
expect(response.data).toBe(firstData);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test("should handle cache invalidation during concurrent requests", async ({
|
|
360
|
+
expect,
|
|
361
|
+
}) => {
|
|
362
|
+
const initialResponse = await app.cachedAction.fetch();
|
|
363
|
+
|
|
364
|
+
const concurrentPromises = [
|
|
365
|
+
app.cachedAction.fetch(),
|
|
366
|
+
app.cachedAction.fetch(),
|
|
367
|
+
];
|
|
368
|
+
|
|
369
|
+
const [cachedResponse1, cachedResponse2] =
|
|
370
|
+
await Promise.all(concurrentPromises);
|
|
371
|
+
|
|
372
|
+
expect(cachedResponse1.data).toBe(initialResponse.data);
|
|
373
|
+
expect(cachedResponse2.data).toBe(initialResponse.data);
|
|
374
|
+
|
|
375
|
+
await cacheProvider.invalidate(app.cachedAction.route);
|
|
376
|
+
|
|
377
|
+
const finalResponse = await app.cachedAction.fetch();
|
|
378
|
+
expect(finalResponse.data).not.toBe(initialResponse.data);
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
describe("Provider lifecycle", () => {
|
|
383
|
+
test("should maintain cache state across multiple requests", async ({
|
|
384
|
+
expect,
|
|
385
|
+
}) => {
|
|
386
|
+
const responses = [];
|
|
387
|
+
for (let i = 0; i < 5; i++) {
|
|
388
|
+
responses.push(await app.cachedAction.fetch());
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const firstData = responses[0].data;
|
|
392
|
+
for (const response of responses) {
|
|
393
|
+
expect(response.data).toBe(firstData);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test("should clear cache when application stops and restarts", async ({
|
|
398
|
+
expect,
|
|
399
|
+
}) => {
|
|
400
|
+
await app.cachedAction.fetch();
|
|
401
|
+
await alepha.stop();
|
|
402
|
+
|
|
403
|
+
alepha = Alepha.create().with(AlephaServerCache);
|
|
404
|
+
app = alepha.inject(TestApp);
|
|
405
|
+
await alepha.start();
|
|
406
|
+
|
|
407
|
+
const afterRestartResponse = await app.cachedAction.fetch();
|
|
408
|
+
expect(afterRestartResponse.data).toBe("cached-0");
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe("ETag-only support (without cache)", () => {
|
|
413
|
+
test("should generate and return ETag header for etag-only routes", async ({
|
|
414
|
+
expect,
|
|
415
|
+
}) => {
|
|
416
|
+
const response = await app.etagOnlyAction.fetch();
|
|
417
|
+
|
|
418
|
+
expect(response.status).toBe(200);
|
|
419
|
+
expect(response.data).toBe("etag-only-0");
|
|
420
|
+
expect(response.headers.get("etag")).toBeDefined();
|
|
421
|
+
expect(response.headers.get("last-modified")).toBeDefined();
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test("should return 304 when client sends matching ETag", async ({
|
|
425
|
+
expect,
|
|
426
|
+
}) => {
|
|
427
|
+
const firstResponse = await app.etagOnlyAction.fetch();
|
|
428
|
+
const etag = firstResponse.headers.get("etag");
|
|
429
|
+
|
|
430
|
+
expect(firstResponse.status).toBe(200);
|
|
431
|
+
expect(firstResponse.data).toBe("etag-only-0");
|
|
432
|
+
expect(etag).toBeDefined();
|
|
433
|
+
|
|
434
|
+
// Send request with If-None-Match header
|
|
435
|
+
const secondResponse = await app.etagOnlyAction.fetch({
|
|
436
|
+
headers: { "if-none-match": etag! },
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
expect(secondResponse.status).toBe(200);
|
|
440
|
+
expect(secondResponse.data).toBe("etag-only-1");
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test("should NOT cache responses for etag-only routes", async ({
|
|
444
|
+
expect,
|
|
445
|
+
}) => {
|
|
446
|
+
const response1 = await app.etagOnlyAction.fetch();
|
|
447
|
+
const response2 = await app.etagOnlyAction.fetch();
|
|
448
|
+
|
|
449
|
+
// Counter should increment because responses are NOT cached
|
|
450
|
+
expect(response1.data).toBe("etag-only-0");
|
|
451
|
+
expect(response2.data).toBe("etag-only-1");
|
|
452
|
+
|
|
453
|
+
// But both should have different ETags
|
|
454
|
+
expect(response1.headers.get("etag")).not.toBe(
|
|
455
|
+
response2.headers.get("etag"),
|
|
456
|
+
);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test("should return 200 with new content when ETag doesn't match", async ({
|
|
460
|
+
expect,
|
|
461
|
+
}) => {
|
|
462
|
+
const firstResponse = await app.etagOnlyAction.fetch();
|
|
463
|
+
expect(firstResponse.status).toBe(200);
|
|
464
|
+
expect(firstResponse.data).toBe("etag-only-0");
|
|
465
|
+
|
|
466
|
+
// Send request with wrong ETag
|
|
467
|
+
const secondResponse = await app.etagOnlyAction.fetch({
|
|
468
|
+
headers: { "if-none-match": '"wrong-etag"' },
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
expect(secondResponse.status).toBe(200);
|
|
472
|
+
expect(secondResponse.data).toBe("etag-only-1");
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
test("should handle dynamic content with ETags correctly", async ({
|
|
476
|
+
expect,
|
|
477
|
+
}) => {
|
|
478
|
+
const response1 = await app.dynamicContentAction.fetch();
|
|
479
|
+
const etag1 = response1.headers.get("etag");
|
|
480
|
+
|
|
481
|
+
expect(response1.status).toBe(200);
|
|
482
|
+
expect(etag1).toBeDefined();
|
|
483
|
+
|
|
484
|
+
// Wait a bit to ensure timestamp changes
|
|
485
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
486
|
+
|
|
487
|
+
const response2 = await app.dynamicContentAction.fetch();
|
|
488
|
+
const etag2 = response2.headers.get("etag");
|
|
489
|
+
|
|
490
|
+
expect(response2.status).toBe(200);
|
|
491
|
+
expect(etag2).toBeDefined();
|
|
492
|
+
|
|
493
|
+
// ETags should be different because content is dynamic
|
|
494
|
+
expect(etag1).not.toBe(etag2);
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
describe("ETag with cache combined", () => {
|
|
499
|
+
test("should support both caching and ETag validation", async ({
|
|
500
|
+
expect,
|
|
501
|
+
}) => {
|
|
502
|
+
const response1 = await app.etagAndCacheAction.fetch();
|
|
503
|
+
const etag1 = response1.headers.get("etag");
|
|
504
|
+
|
|
505
|
+
expect(response1.status).toBe(200);
|
|
506
|
+
expect(response1.data).toBe("etag-and-cache-0");
|
|
507
|
+
expect(etag1).toBeDefined();
|
|
508
|
+
|
|
509
|
+
// Second request should return cached content with 304
|
|
510
|
+
const response2 = await app.etagAndCacheAction.fetch();
|
|
511
|
+
|
|
512
|
+
expect(response2.status).toBe(304);
|
|
513
|
+
expect(response2.data).toBe("etag-and-cache-0");
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
test("should return cached content even without ETag header", async ({
|
|
517
|
+
expect,
|
|
518
|
+
}) => {
|
|
519
|
+
const response1 = await app.etagAndCacheAction.fetch();
|
|
520
|
+
expect(response1.status).toBe(200);
|
|
521
|
+
expect(response1.data).toBe("etag-and-cache-0");
|
|
522
|
+
|
|
523
|
+
// Request without ETag should still get cached response
|
|
524
|
+
const response2 = await app.etagAndCacheAction.fetch({
|
|
525
|
+
headers: {
|
|
526
|
+
"if-none-match": "non-matching-etag",
|
|
527
|
+
},
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
expect(response2.status).toBe(200);
|
|
531
|
+
expect(response2.data).toBe("etag-and-cache-0");
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
test("should invalidate both cache and ETag when cache is cleared", async ({
|
|
535
|
+
expect,
|
|
536
|
+
}) => {
|
|
537
|
+
const response1 = await app.etagAndCacheAction.fetch();
|
|
538
|
+
const etag1 = response1.headers.get("etag");
|
|
539
|
+
|
|
540
|
+
expect(response1.status).toBe(200);
|
|
541
|
+
expect(response1.data).toBe("etag-and-cache-0");
|
|
542
|
+
|
|
543
|
+
// Invalidate cache
|
|
544
|
+
await cacheProvider.invalidate(app.etagAndCacheAction.route);
|
|
545
|
+
|
|
546
|
+
// Next request should generate new content and new ETag
|
|
547
|
+
const response2 = await app.etagAndCacheAction.fetch();
|
|
548
|
+
const etag2 = response2.headers.get("etag");
|
|
549
|
+
|
|
550
|
+
expect(response2.status).toBe(200);
|
|
551
|
+
expect(response2.data).toBe("etag-and-cache-1");
|
|
552
|
+
expect(etag2).toBeDefined();
|
|
553
|
+
expect(etag1).not.toBe(etag2);
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
describe("ETag header validation", () => {
|
|
558
|
+
test("should generate consistent ETags for same content", async ({
|
|
559
|
+
expect,
|
|
560
|
+
}) => {
|
|
561
|
+
const response1 = await app.cachedAction.fetch();
|
|
562
|
+
const etag1 = response1.headers.get("etag");
|
|
563
|
+
|
|
564
|
+
const response2 = await app.cachedAction.fetch();
|
|
565
|
+
const etag2 = response2.headers.get("etag");
|
|
566
|
+
|
|
567
|
+
// Same content should produce same ETag
|
|
568
|
+
expect(etag1).toBe(etag2);
|
|
569
|
+
expect(response1.data).toBe(response2.data);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test("should generate different ETags for different content", async ({
|
|
573
|
+
expect,
|
|
574
|
+
}) => {
|
|
575
|
+
const response1 = await app.etagOnlyAction.fetch();
|
|
576
|
+
const etag1 = response1.headers.get("etag");
|
|
577
|
+
|
|
578
|
+
const response2 = await app.etagOnlyAction.fetch();
|
|
579
|
+
const etag2 = response2.headers.get("etag");
|
|
580
|
+
|
|
581
|
+
// Different content should produce different ETags
|
|
582
|
+
expect(response1.data).not.toBe(response2.data);
|
|
583
|
+
expect(etag1).not.toBe(etag2);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
test("should set Last-Modified header with ETag", async ({ expect }) => {
|
|
587
|
+
const response = await app.etagOnlyAction.fetch();
|
|
588
|
+
|
|
589
|
+
expect(response.headers.get("etag")).toBeDefined();
|
|
590
|
+
expect(response.headers.get("last-modified")).toBeDefined();
|
|
591
|
+
|
|
592
|
+
// Verify it's a valid ISO date string
|
|
593
|
+
const lastModified = response.headers.get("last-modified");
|
|
594
|
+
expect(() => new Date(lastModified!)).not.toThrow();
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
describe("Mixed scenarios", () => {
|
|
599
|
+
test("should handle routes with different cache/etag configurations independently", async ({
|
|
600
|
+
expect,
|
|
601
|
+
}) => {
|
|
602
|
+
// Cached action
|
|
603
|
+
const cached1 = await app.cachedAction.fetch();
|
|
604
|
+
const cached2 = await app.cachedAction.fetch();
|
|
605
|
+
expect(cached1.data).toBe(cached2.data);
|
|
606
|
+
|
|
607
|
+
// Uncached action
|
|
608
|
+
const uncached1 = await app.uncachedAction.fetch();
|
|
609
|
+
const uncached2 = await app.uncachedAction.fetch();
|
|
610
|
+
expect(uncached1.data).not.toBe(uncached2.data);
|
|
611
|
+
|
|
612
|
+
// ETag-only action
|
|
613
|
+
const etag1 = await app.etagOnlyAction.fetch();
|
|
614
|
+
const etag2 = await app.etagOnlyAction.fetch();
|
|
615
|
+
expect(etag1.data).not.toBe(etag2.data);
|
|
616
|
+
|
|
617
|
+
// All should have their own independent state
|
|
618
|
+
expect(cached1.data).toContain("cached");
|
|
619
|
+
expect(uncached1.data).toContain("uncached");
|
|
620
|
+
expect(etag1.data).toContain("etag-only");
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test("should not interfere with each other's ETags", async ({ expect }) => {
|
|
624
|
+
const response1 = await app.etagOnlyAction.fetch();
|
|
625
|
+
const etag1 = response1.headers.get("etag");
|
|
626
|
+
|
|
627
|
+
const response2 = await app.cachedAction.fetch();
|
|
628
|
+
const etag2 = response2.headers.get("etag");
|
|
629
|
+
|
|
630
|
+
// Different actions should have different ETags
|
|
631
|
+
expect(etag1).not.toBe(etag2);
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
describe("Cache-Control header support", () => {
|
|
636
|
+
test("should set Cache-Control header when control: true", async ({
|
|
637
|
+
expect,
|
|
638
|
+
}) => {
|
|
639
|
+
const response = await app.cacheWithControlTrue.fetch();
|
|
640
|
+
|
|
641
|
+
expect(response.status).toBe(200);
|
|
642
|
+
expect(response.headers.get("cache-control")).toBe("public, max-age=300");
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
test("should set Cache-Control header with custom string value", async ({
|
|
646
|
+
expect,
|
|
647
|
+
}) => {
|
|
648
|
+
const response = await app.cacheWithControlString.fetch();
|
|
649
|
+
|
|
650
|
+
expect(response.status).toBe(200);
|
|
651
|
+
expect(response.headers.get("cache-control")).toBe(
|
|
652
|
+
"public, max-age=600, immutable",
|
|
653
|
+
);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
test("should build Cache-Control header from object directives", async ({
|
|
657
|
+
expect,
|
|
658
|
+
}) => {
|
|
659
|
+
const response = await app.cacheWithControlObject.fetch();
|
|
660
|
+
|
|
661
|
+
expect(response.status).toBe(200);
|
|
662
|
+
const cacheControl = response.headers.get("cache-control");
|
|
663
|
+
expect(cacheControl).toContain("public");
|
|
664
|
+
expect(cacheControl).toContain("max-age=3600");
|
|
665
|
+
expect(cacheControl).toContain("must-revalidate");
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
test("should support complex Cache-Control with multiple directives", async ({
|
|
669
|
+
expect,
|
|
670
|
+
}) => {
|
|
671
|
+
const response = await app.cacheWithComplexControl.fetch();
|
|
672
|
+
|
|
673
|
+
expect(response.status).toBe(200);
|
|
674
|
+
const cacheControl = response.headers.get("cache-control");
|
|
675
|
+
expect(cacheControl).toContain("public");
|
|
676
|
+
expect(cacheControl).toContain("max-age=600");
|
|
677
|
+
expect(cacheControl).toContain("s-maxage=1200");
|
|
678
|
+
expect(cacheControl).toContain("immutable");
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
test("should not set Cache-Control when cache is true without control option", async ({
|
|
682
|
+
expect,
|
|
683
|
+
}) => {
|
|
684
|
+
const response = await app.cachedAction.fetch();
|
|
685
|
+
|
|
686
|
+
expect(response.status).toBe(200);
|
|
687
|
+
expect(response.headers.get("cache-control")).toBeNull();
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
test("should cache responses with Cache-Control headers", async ({
|
|
691
|
+
expect,
|
|
692
|
+
}) => {
|
|
693
|
+
const response1 = await app.cacheWithControlObject.fetch();
|
|
694
|
+
const response2 = await app.cacheWithControlObject.fetch();
|
|
695
|
+
|
|
696
|
+
expect(response1.data).toBe(response2.data);
|
|
697
|
+
expect(response1.data).toBe("control-object-0");
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
test("should support private cache directive", async ({ expect }) => {
|
|
701
|
+
const alepha = Alepha.create().with(AlephaServerCache);
|
|
702
|
+
const privateAction = alepha.inject(
|
|
703
|
+
class TestPrivateCache {
|
|
704
|
+
action = $action({
|
|
705
|
+
cache: {
|
|
706
|
+
store: true,
|
|
707
|
+
control: {
|
|
708
|
+
private: true,
|
|
709
|
+
maxAge: 300,
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
handler: () => "private-cache",
|
|
713
|
+
});
|
|
714
|
+
},
|
|
715
|
+
).action;
|
|
716
|
+
await alepha.start();
|
|
717
|
+
|
|
718
|
+
const response = await privateAction.fetch();
|
|
719
|
+
const cacheControl = response.headers.get("cache-control");
|
|
720
|
+
|
|
721
|
+
expect(cacheControl).toContain("private");
|
|
722
|
+
expect(cacheControl).toContain("max-age=300");
|
|
723
|
+
expect(cacheControl).not.toContain("public");
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
test("should support no-cache and no-store directives", async ({
|
|
727
|
+
expect,
|
|
728
|
+
}) => {
|
|
729
|
+
const alepha = Alepha.create().with(AlephaServerCache);
|
|
730
|
+
const noCacheAction = alepha.inject(
|
|
731
|
+
class TestNoCache {
|
|
732
|
+
action = $action({
|
|
733
|
+
cache: {
|
|
734
|
+
store: true,
|
|
735
|
+
control: {
|
|
736
|
+
noCache: true,
|
|
737
|
+
noStore: true,
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
handler: () => "no-cache",
|
|
741
|
+
});
|
|
742
|
+
},
|
|
743
|
+
).action;
|
|
744
|
+
await alepha.start();
|
|
745
|
+
|
|
746
|
+
const response = await noCacheAction.fetch();
|
|
747
|
+
const cacheControl = response.headers.get("cache-control");
|
|
748
|
+
|
|
749
|
+
expect(cacheControl).toContain("no-cache");
|
|
750
|
+
expect(cacheControl).toContain("no-store");
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
test("should support proxy-revalidate directive", async ({ expect }) => {
|
|
754
|
+
const alepha = Alepha.create().with(AlephaServerCache);
|
|
755
|
+
const proxyAction = alepha.inject(
|
|
756
|
+
class TestProxyRevalidate {
|
|
757
|
+
action = $action({
|
|
758
|
+
cache: {
|
|
759
|
+
store: true,
|
|
760
|
+
control: {
|
|
761
|
+
public: true,
|
|
762
|
+
proxyRevalidate: true,
|
|
763
|
+
maxAge: 600,
|
|
764
|
+
},
|
|
765
|
+
},
|
|
766
|
+
handler: () => "proxy-revalidate",
|
|
767
|
+
});
|
|
768
|
+
},
|
|
769
|
+
).action;
|
|
770
|
+
await alepha.start();
|
|
771
|
+
|
|
772
|
+
const response = await proxyAction.fetch();
|
|
773
|
+
const cacheControl = response.headers.get("cache-control");
|
|
774
|
+
|
|
775
|
+
expect(cacheControl).toContain("public");
|
|
776
|
+
expect(cacheControl).toContain("proxy-revalidate");
|
|
777
|
+
expect(cacheControl).toContain("max-age=600");
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
test("should support s-maxage for shared cache control", async ({
|
|
781
|
+
expect,
|
|
782
|
+
}) => {
|
|
783
|
+
const alepha = Alepha.create().with(AlephaServerCache);
|
|
784
|
+
const sharedCacheAction = alepha.inject(
|
|
785
|
+
class TestSharedCache {
|
|
786
|
+
action = $action({
|
|
787
|
+
cache: {
|
|
788
|
+
store: true,
|
|
789
|
+
control: {
|
|
790
|
+
public: true,
|
|
791
|
+
maxAge: 300,
|
|
792
|
+
sMaxAge: 600,
|
|
793
|
+
},
|
|
794
|
+
},
|
|
795
|
+
handler: () => "shared-cache",
|
|
796
|
+
});
|
|
797
|
+
},
|
|
798
|
+
).action;
|
|
799
|
+
await alepha.start();
|
|
800
|
+
|
|
801
|
+
const response = await sharedCacheAction.fetch();
|
|
802
|
+
const cacheControl = response.headers.get("cache-control");
|
|
803
|
+
|
|
804
|
+
expect(cacheControl).toContain("public");
|
|
805
|
+
expect(cacheControl).toContain("max-age=300");
|
|
806
|
+
expect(cacheControl).toContain("s-maxage=600");
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
test("should support DurationLike for maxAge and sMaxAge", async ({
|
|
810
|
+
expect,
|
|
811
|
+
}) => {
|
|
812
|
+
const response = await app.cacheWithDurationMaxAge.fetch();
|
|
813
|
+
|
|
814
|
+
expect(response.status).toBe(200);
|
|
815
|
+
const cacheControl = response.headers.get("cache-control");
|
|
816
|
+
|
|
817
|
+
// 5 minutes = 300 seconds, 1 hour = 3600 seconds
|
|
818
|
+
expect(cacheControl).toContain("public");
|
|
819
|
+
expect(cacheControl).toContain("max-age=300");
|
|
820
|
+
expect(cacheControl).toContain("s-maxage=3600");
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
test("should handle various DurationLike formats for maxAge", ({
|
|
824
|
+
expect,
|
|
825
|
+
}) => {
|
|
826
|
+
// Test directly via buildCacheControlHeader method
|
|
827
|
+
expect(
|
|
828
|
+
cacheProvider.buildCacheControlHeader({
|
|
829
|
+
control: { public: true, maxAge: [30, "seconds"] },
|
|
830
|
+
}),
|
|
831
|
+
).toContain("max-age=30");
|
|
832
|
+
|
|
833
|
+
expect(
|
|
834
|
+
cacheProvider.buildCacheControlHeader({
|
|
835
|
+
control: { public: true, maxAge: [10, "minutes"] },
|
|
836
|
+
}),
|
|
837
|
+
).toContain("max-age=600");
|
|
838
|
+
|
|
839
|
+
expect(
|
|
840
|
+
cacheProvider.buildCacheControlHeader({
|
|
841
|
+
control: { public: true, maxAge: [2, "hours"] },
|
|
842
|
+
}),
|
|
843
|
+
).toContain("max-age=7200");
|
|
844
|
+
|
|
845
|
+
expect(
|
|
846
|
+
cacheProvider.buildCacheControlHeader({
|
|
847
|
+
control: { public: true, maxAge: [1, "day"] },
|
|
848
|
+
}),
|
|
849
|
+
).toContain("max-age=86400");
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
test("should mix number and DurationLike for maxAge and sMaxAge", ({
|
|
853
|
+
expect,
|
|
854
|
+
}) => {
|
|
855
|
+
const cacheControl = cacheProvider.buildCacheControlHeader({
|
|
856
|
+
control: {
|
|
857
|
+
public: true,
|
|
858
|
+
maxAge: 600, // number (seconds)
|
|
859
|
+
sMaxAge: [20, "minutes"], // DurationLike
|
|
860
|
+
},
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
expect(cacheControl).toContain("max-age=600");
|
|
864
|
+
expect(cacheControl).toContain("s-maxage=1200"); // 20 minutes = 1200 seconds
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
describe("Error response caching", () => {
|
|
869
|
+
test("should NOT cache 500 error responses", async ({ expect }) => {
|
|
870
|
+
const response1 = await app.errorAction.fetch();
|
|
871
|
+
expect(response1.status).toBe(500);
|
|
872
|
+
expect(response1.data).toBe("error-0");
|
|
873
|
+
|
|
874
|
+
// Second request should execute handler again (not cached)
|
|
875
|
+
const response2 = await app.errorAction.fetch();
|
|
876
|
+
expect(response2.status).toBe(500);
|
|
877
|
+
expect(response2.data).toBe("error-1");
|
|
878
|
+
|
|
879
|
+
// Verify counter incremented (handler was called)
|
|
880
|
+
expect(response1.data).not.toBe(response2.data);
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
test("should NOT cache 404 error responses", async ({ expect }) => {
|
|
884
|
+
const response1 = await app.notFoundAction.fetch();
|
|
885
|
+
expect(response1.status).toBe(404);
|
|
886
|
+
expect(response1.data).toBe("not-found-0");
|
|
887
|
+
|
|
888
|
+
// Second request should execute handler again (not cached)
|
|
889
|
+
const response2 = await app.notFoundAction.fetch();
|
|
890
|
+
expect(response2.status).toBe(404);
|
|
891
|
+
expect(response2.data).toBe("not-found-1");
|
|
892
|
+
|
|
893
|
+
// Verify counter incremented (handler was called)
|
|
894
|
+
expect(response1.data).not.toBe(response2.data);
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
test("should cache successful responses after error responses", async ({
|
|
898
|
+
expect,
|
|
899
|
+
}) => {
|
|
900
|
+
// First request returns 500 error
|
|
901
|
+
const errorResponse = await app.conditionalErrorAction.fetch();
|
|
902
|
+
expect(errorResponse.status).toBe(500);
|
|
903
|
+
expect(errorResponse.data).toBe("error-0");
|
|
904
|
+
|
|
905
|
+
// Second request returns success and should be cached
|
|
906
|
+
const successResponse1 = await app.conditionalErrorAction.fetch();
|
|
907
|
+
expect(successResponse1.status).toBe(200);
|
|
908
|
+
expect(successResponse1.data).toBe("success-1");
|
|
909
|
+
|
|
910
|
+
// Third request should return cached response
|
|
911
|
+
const successResponse2 = await app.conditionalErrorAction.fetch();
|
|
912
|
+
expect(successResponse2.status).toBe(304);
|
|
913
|
+
expect(successResponse2.data).toBe("success-1");
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
test("should NOT cache 4xx client errors", async ({ expect }) => {
|
|
917
|
+
// Test with 400 Bad Request
|
|
918
|
+
const alepha = Alepha.create();
|
|
919
|
+
const badRequestAction = alepha.inject(
|
|
920
|
+
class TestBadRequest {
|
|
921
|
+
action = $action({
|
|
922
|
+
cache: true,
|
|
923
|
+
handler: ({ reply }) => {
|
|
924
|
+
reply.status = 400;
|
|
925
|
+
return `bad-request-${app.counter++}`;
|
|
926
|
+
},
|
|
927
|
+
});
|
|
928
|
+
},
|
|
929
|
+
).action;
|
|
930
|
+
await alepha.start();
|
|
931
|
+
|
|
932
|
+
const response1 = await badRequestAction.fetch();
|
|
933
|
+
expect(response1.status).toBe(400);
|
|
934
|
+
|
|
935
|
+
const response2 = await badRequestAction.fetch();
|
|
936
|
+
expect(response2.status).toBe(400);
|
|
937
|
+
|
|
938
|
+
// Verify responses are different (not cached)
|
|
939
|
+
expect(response1.data).not.toBe(response2.data);
|
|
940
|
+
});
|
|
941
|
+
});
|
|
942
|
+
});
|