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,234 @@
|
|
|
1
|
+
import { Alepha } from "alepha";
|
|
2
|
+
import { DateTimeProvider } from "alepha/datetime";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
4
|
+
import { $retry } from "../index.ts";
|
|
5
|
+
|
|
6
|
+
describe("$retry", () => {
|
|
7
|
+
let alepha: Alepha;
|
|
8
|
+
let time: DateTimeProvider;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
alepha = Alepha.create();
|
|
12
|
+
time = alepha.inject(DateTimeProvider);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await alepha.stop();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("should retry handler up to max retries", async () => {
|
|
20
|
+
class Dummy {
|
|
21
|
+
inc = 0;
|
|
22
|
+
workRetry = $retry({
|
|
23
|
+
max: 3,
|
|
24
|
+
handler: (n: number, end: number) => {
|
|
25
|
+
this.inc += n;
|
|
26
|
+
if (this.inc < end) {
|
|
27
|
+
throw new Error("Retry");
|
|
28
|
+
}
|
|
29
|
+
return this.inc;
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
work = async (n: number, end: number) => {
|
|
34
|
+
this.inc = 0;
|
|
35
|
+
return await this.workRetry(n, end);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const basic = alepha.inject(Dummy);
|
|
40
|
+
|
|
41
|
+
expect(await basic.work(1, 2)).toBe(2);
|
|
42
|
+
expect(await basic.work(1, 3)).toBe(3);
|
|
43
|
+
await expect(() => basic.work(1, 4)).rejects.toThrow(Error);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("should only retry when condition matches", async () => {
|
|
47
|
+
class Dummy {
|
|
48
|
+
inc = 0;
|
|
49
|
+
workRetry = $retry({
|
|
50
|
+
max: 10,
|
|
51
|
+
when: (err: Error) => err.message === "Retry1",
|
|
52
|
+
handler: (n: number, end: number) => {
|
|
53
|
+
this.inc += n;
|
|
54
|
+
if (this.inc < end) {
|
|
55
|
+
throw new Error(`Retry${this.inc}`);
|
|
56
|
+
}
|
|
57
|
+
return this.inc;
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
async work(n: number, end: number) {
|
|
62
|
+
this.inc = 0;
|
|
63
|
+
return await this.workRetry(n, end);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const basic = alepha.inject(Dummy);
|
|
68
|
+
|
|
69
|
+
expect(await basic.work(1, 2)).toBe(2);
|
|
70
|
+
await expect(() => basic.work(1, 3)).rejects.toThrow(Error);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("should succeed on the first attempt", async () => {
|
|
74
|
+
const handler = vi.fn().mockResolvedValue("success");
|
|
75
|
+
const retryFunc = alepha.inject(
|
|
76
|
+
class {
|
|
77
|
+
retry = $retry({ handler });
|
|
78
|
+
},
|
|
79
|
+
).retry;
|
|
80
|
+
|
|
81
|
+
await expect(retryFunc()).resolves.toBe("success");
|
|
82
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("should retry up to max attempts and then fail", async () => {
|
|
86
|
+
const handler = vi.fn().mockRejectedValue(new Error("Failed"));
|
|
87
|
+
const onError = vi.fn();
|
|
88
|
+
|
|
89
|
+
const retryFunc = alepha.inject(
|
|
90
|
+
class {
|
|
91
|
+
retry = $retry({ handler, max: 3, backoff: 0, onError });
|
|
92
|
+
},
|
|
93
|
+
).retry;
|
|
94
|
+
|
|
95
|
+
await expect(retryFunc()).rejects.toThrow("Failed");
|
|
96
|
+
expect(handler).toHaveBeenCalledTimes(3);
|
|
97
|
+
expect(onError).toHaveBeenCalledTimes(3); // onError is called for ALL failed attempts, including the last one
|
|
98
|
+
expect(onError).toHaveBeenCalledWith(expect.any(Error), 1);
|
|
99
|
+
expect(onError).toHaveBeenCalledWith(expect.any(Error), 2);
|
|
100
|
+
expect(onError).toHaveBeenCalledWith(expect.any(Error), 3);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("should succeed after a few failed attempts", async () => {
|
|
104
|
+
let attempt = 0;
|
|
105
|
+
const handler = vi.fn(() => {
|
|
106
|
+
attempt++;
|
|
107
|
+
if (attempt < 3) {
|
|
108
|
+
return Promise.reject(new Error("Try again"));
|
|
109
|
+
}
|
|
110
|
+
return Promise.resolve("success");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const retryFunc = alepha.inject(
|
|
114
|
+
class {
|
|
115
|
+
retry = $retry({ handler, max: 4, backoff: 0 });
|
|
116
|
+
},
|
|
117
|
+
).retry;
|
|
118
|
+
|
|
119
|
+
await expect(retryFunc()).resolves.toBe("success");
|
|
120
|
+
expect(handler).toHaveBeenCalledTimes(3);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("should respect maxDuration and time out", async () => {
|
|
124
|
+
const handler = vi.fn(async () => {
|
|
125
|
+
await time.wait(200);
|
|
126
|
+
throw new Error("Failed");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const retryFunc = alepha.inject(
|
|
130
|
+
class {
|
|
131
|
+
retry = $retry({
|
|
132
|
+
handler,
|
|
133
|
+
max: 5,
|
|
134
|
+
maxDuration: [300, "ms"],
|
|
135
|
+
backoff: 0,
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
).retry;
|
|
139
|
+
|
|
140
|
+
await expect(retryFunc()).rejects.toThrow(
|
|
141
|
+
"Retry operation timed out after 300ms.",
|
|
142
|
+
);
|
|
143
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("should be cancellable with an AbortSignal", async () => {
|
|
147
|
+
const handler = vi.fn(async () => {
|
|
148
|
+
await time.wait(500); // Long delay
|
|
149
|
+
throw new Error("Failed");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const abortController = new AbortController();
|
|
153
|
+
const retryFunc = alepha.inject(
|
|
154
|
+
class {
|
|
155
|
+
retry = $retry({
|
|
156
|
+
handler,
|
|
157
|
+
max: 5,
|
|
158
|
+
backoff: 100,
|
|
159
|
+
signal: abortController.signal,
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
).retry;
|
|
163
|
+
|
|
164
|
+
const promise = retryFunc();
|
|
165
|
+
|
|
166
|
+
// Let the first attempt start
|
|
167
|
+
await time.travel(100);
|
|
168
|
+
|
|
169
|
+
// Abort during the first handler execution
|
|
170
|
+
abortController.abort();
|
|
171
|
+
|
|
172
|
+
await expect(promise).rejects.toThrow("Retry operation was cancelled.");
|
|
173
|
+
|
|
174
|
+
// Handler was called once but never completed
|
|
175
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("should be cancellable by application shutdown", async () => {
|
|
179
|
+
// Create a fresh alepha instance for this test
|
|
180
|
+
const testAlepha = Alepha.create();
|
|
181
|
+
const testTime = testAlepha.inject(DateTimeProvider);
|
|
182
|
+
|
|
183
|
+
const handler = vi.fn(async () => {
|
|
184
|
+
// Throw immediately to trigger retry
|
|
185
|
+
throw new Error("Failed");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const retryFunc = testAlepha.inject(
|
|
189
|
+
class {
|
|
190
|
+
retry = $retry({ handler, max: 10, backoff: 100 });
|
|
191
|
+
},
|
|
192
|
+
).retry;
|
|
193
|
+
|
|
194
|
+
await testAlepha.start();
|
|
195
|
+
|
|
196
|
+
// Start the retry operation
|
|
197
|
+
const promise = retryFunc();
|
|
198
|
+
|
|
199
|
+
// Give it a moment to start
|
|
200
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
201
|
+
|
|
202
|
+
// Stop the application which should abort all retries
|
|
203
|
+
await testAlepha.stop();
|
|
204
|
+
|
|
205
|
+
// The promise should reject with cancellation error
|
|
206
|
+
await expect(promise).rejects.toThrow("Retry operation was cancelled.");
|
|
207
|
+
|
|
208
|
+
// Handler should have been called at least once but not 10 times
|
|
209
|
+
expect(handler).toHaveBeenCalled();
|
|
210
|
+
expect(handler.mock.calls.length).toBeLessThan(10);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("should not retry if `when` condition returns false", async () => {
|
|
214
|
+
class CustomError extends Error {}
|
|
215
|
+
const handler = vi
|
|
216
|
+
.fn()
|
|
217
|
+
.mockRejectedValueOnce(new CustomError("Do not retry me"))
|
|
218
|
+
.mockRejectedValue(new Error("Retry me"));
|
|
219
|
+
|
|
220
|
+
const retryFunc = alepha.inject(
|
|
221
|
+
class {
|
|
222
|
+
retry = $retry({
|
|
223
|
+
handler,
|
|
224
|
+
max: 3,
|
|
225
|
+
backoff: 0,
|
|
226
|
+
when: (error) => !(error instanceof CustomError),
|
|
227
|
+
});
|
|
228
|
+
},
|
|
229
|
+
).retry;
|
|
230
|
+
|
|
231
|
+
await expect(retryFunc()).rejects.toThrow(CustomError);
|
|
232
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { Alepha } from "alepha";
|
|
2
|
+
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
3
|
+
import { RetryCancelError } from "../errors/RetryCancelError.ts";
|
|
4
|
+
import { RetryTimeoutError } from "../errors/RetryTimeoutError.ts";
|
|
5
|
+
import { RetryProvider } from "../providers/RetryProvider.ts";
|
|
6
|
+
|
|
7
|
+
describe("RetryProvider", () => {
|
|
8
|
+
let alepha: Alepha;
|
|
9
|
+
let retryProvider: RetryProvider;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
alepha = Alepha.create();
|
|
13
|
+
retryProvider = alepha.inject(RetryProvider);
|
|
14
|
+
await alepha.start();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("should retry handler up to max retries", async () => {
|
|
18
|
+
let attempts = 0;
|
|
19
|
+
const handler = vi.fn(() => {
|
|
20
|
+
attempts++;
|
|
21
|
+
if (attempts < 3) {
|
|
22
|
+
throw new Error("Retry");
|
|
23
|
+
}
|
|
24
|
+
return "success";
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const result = await retryProvider.retry({ handler, max: 3 });
|
|
28
|
+
|
|
29
|
+
expect(result).toBe("success");
|
|
30
|
+
expect(handler).toHaveBeenCalledTimes(3);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("should throw error after max retries exceeded", async () => {
|
|
34
|
+
const handler = vi.fn(() => {
|
|
35
|
+
throw new Error("Always fails");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
await expect(retryProvider.retry({ handler, max: 3 })).rejects.toThrowError(
|
|
39
|
+
"Always fails",
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(handler).toHaveBeenCalledTimes(3);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("should only retry when condition matches", async () => {
|
|
46
|
+
let attempts = 0;
|
|
47
|
+
const handler = vi.fn(() => {
|
|
48
|
+
attempts++;
|
|
49
|
+
throw new Error(`Error${attempts}`);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const when = (error: Error) => error.message === "Error1";
|
|
53
|
+
|
|
54
|
+
await expect(
|
|
55
|
+
retryProvider.retry({ handler, max: 10, when }),
|
|
56
|
+
).rejects.toThrowError("Error2");
|
|
57
|
+
|
|
58
|
+
// Should fail on second attempt because when() returns false
|
|
59
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("should use fixed backoff delay", async () => {
|
|
63
|
+
const handler = vi.fn(() => {
|
|
64
|
+
throw new Error("Retry");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const startTime = Date.now();
|
|
68
|
+
|
|
69
|
+
await expect(
|
|
70
|
+
retryProvider.retry({ handler, max: 3, backoff: 100 }),
|
|
71
|
+
).rejects.toThrowError("Retry");
|
|
72
|
+
|
|
73
|
+
const duration = Date.now() - startTime;
|
|
74
|
+
|
|
75
|
+
// Should have waited ~200ms total (2 retries × 100ms)
|
|
76
|
+
expect(duration).toBeGreaterThanOrEqual(190);
|
|
77
|
+
expect(duration).toBeLessThan(300);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("should use exponential backoff with jitter", async () => {
|
|
81
|
+
const handler = vi.fn(() => {
|
|
82
|
+
throw new Error("Retry");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const startTime = Date.now();
|
|
86
|
+
|
|
87
|
+
await expect(
|
|
88
|
+
retryProvider.retry({
|
|
89
|
+
handler,
|
|
90
|
+
max: 3,
|
|
91
|
+
backoff: { initial: 100, factor: 2, jitter: true },
|
|
92
|
+
}),
|
|
93
|
+
).rejects.toThrowError("Retry");
|
|
94
|
+
|
|
95
|
+
const duration = Date.now() - startTime;
|
|
96
|
+
|
|
97
|
+
// First retry: ~100ms, Second retry: ~200ms
|
|
98
|
+
// With jitter, expect at least base delays
|
|
99
|
+
expect(duration).toBeGreaterThanOrEqual(280);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("should respect maxDuration timeout", { retry: 3 }, async () => {
|
|
103
|
+
const handler = vi.fn(() => {
|
|
104
|
+
throw new Error("Retry");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await expect(
|
|
108
|
+
retryProvider.retry({
|
|
109
|
+
handler,
|
|
110
|
+
max: 10,
|
|
111
|
+
maxDuration: [100, "milliseconds"],
|
|
112
|
+
backoff: 50,
|
|
113
|
+
}),
|
|
114
|
+
).rejects.toThrowError(RetryTimeoutError);
|
|
115
|
+
|
|
116
|
+
// Should not reach max retries due to timeout
|
|
117
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("should respect AbortSignal cancellation", async () => {
|
|
121
|
+
const handler = vi.fn(() => {
|
|
122
|
+
throw new Error("Retry");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const abortController = new AbortController();
|
|
126
|
+
|
|
127
|
+
// Abort after first attempt
|
|
128
|
+
setTimeout(() => abortController.abort(), 50);
|
|
129
|
+
|
|
130
|
+
await expect(
|
|
131
|
+
retryProvider.retry({
|
|
132
|
+
handler,
|
|
133
|
+
max: 10,
|
|
134
|
+
backoff: 100,
|
|
135
|
+
signal: abortController.signal,
|
|
136
|
+
}),
|
|
137
|
+
).rejects.toThrowError(RetryCancelError);
|
|
138
|
+
|
|
139
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("should call onError callback on each failure", async () => {
|
|
143
|
+
let attempts = 0;
|
|
144
|
+
const handler = vi.fn(() => {
|
|
145
|
+
attempts++;
|
|
146
|
+
if (attempts < 3) {
|
|
147
|
+
throw new Error(`Attempt ${attempts}`);
|
|
148
|
+
}
|
|
149
|
+
return "success";
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const onError = vi.fn();
|
|
153
|
+
|
|
154
|
+
await retryProvider.retry({ handler, max: 3, onError });
|
|
155
|
+
|
|
156
|
+
expect(onError).toHaveBeenCalledTimes(2);
|
|
157
|
+
expect(onError).toHaveBeenNthCalledWith(
|
|
158
|
+
1,
|
|
159
|
+
expect.objectContaining({ message: "Attempt 1" }),
|
|
160
|
+
1,
|
|
161
|
+
);
|
|
162
|
+
expect(onError).toHaveBeenNthCalledWith(
|
|
163
|
+
2,
|
|
164
|
+
expect.objectContaining({ message: "Attempt 2" }),
|
|
165
|
+
2,
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("should pass arguments to handler", async () => {
|
|
170
|
+
const handler = vi.fn((a: number, b: string) => {
|
|
171
|
+
return `${a}-${b}`;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const result = await retryProvider.retry({ handler, max: 1 }, 42, "test");
|
|
175
|
+
|
|
176
|
+
expect(result).toBe("42-test");
|
|
177
|
+
expect(handler).toHaveBeenCalledWith(42, "test");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("should log warnings on retry failures", async () => {
|
|
181
|
+
const handler = vi.fn(() => {
|
|
182
|
+
throw new Error("Test error");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Spy on the log method
|
|
186
|
+
// @ts-expect-error - accessing protected property for testing
|
|
187
|
+
const logSpy = vi.spyOn(retryProvider.log, "warn");
|
|
188
|
+
|
|
189
|
+
await expect(retryProvider.retry({ handler, max: 2 })).rejects.toThrowError(
|
|
190
|
+
"Test error",
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
expect(logSpy).toHaveBeenCalledTimes(2);
|
|
194
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
195
|
+
"Retry attempt failed",
|
|
196
|
+
expect.objectContaining({
|
|
197
|
+
attempt: 1,
|
|
198
|
+
maxAttempts: 2,
|
|
199
|
+
remainingAttempts: 1,
|
|
200
|
+
error: "Test error",
|
|
201
|
+
errorName: "Error",
|
|
202
|
+
}),
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("should support additionalSignal for combined cancellation", async () => {
|
|
207
|
+
const handler = vi.fn(() => {
|
|
208
|
+
throw new Error("Retry");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const additionalController = new AbortController();
|
|
212
|
+
|
|
213
|
+
// Abort the additional signal
|
|
214
|
+
setTimeout(() => additionalController.abort(), 50);
|
|
215
|
+
|
|
216
|
+
await expect(
|
|
217
|
+
retryProvider.retry({
|
|
218
|
+
handler,
|
|
219
|
+
max: 10,
|
|
220
|
+
backoff: 100,
|
|
221
|
+
additionalSignal: additionalController.signal,
|
|
222
|
+
}),
|
|
223
|
+
).rejects.toThrowError(RetryCancelError);
|
|
224
|
+
|
|
225
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("should not retry on non-Error throws", async () => {
|
|
229
|
+
const handler = vi.fn(() => {
|
|
230
|
+
throw "string error";
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
await expect(retryProvider.retry({ handler, max: 3 })).rejects.toBe(
|
|
234
|
+
"string error",
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("should succeed on first attempt without retries", async () => {
|
|
241
|
+
const handler = vi.fn(() => "immediate success");
|
|
242
|
+
|
|
243
|
+
const result = await retryProvider.retry({ handler, max: 3 });
|
|
244
|
+
|
|
245
|
+
expect(result).toBe("immediate success");
|
|
246
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("should use default max of 3 when not specified", async () => {
|
|
250
|
+
const handler = vi.fn(() => {
|
|
251
|
+
throw new Error("Retry");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
await expect(retryProvider.retry({ handler })).rejects.toThrowError(
|
|
255
|
+
"Retry",
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
expect(handler).toHaveBeenCalledTimes(3);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Bug #1 (Red/Critical): Signal precedence issue
|
|
262
|
+
// When both signal and additionalSignal are provided, ONLY signal is respected during backoff
|
|
263
|
+
// This means additionalSignal (app lifecycle) cannot cancel during backoff if user signal exists
|
|
264
|
+
test("should respect both user signal and additionalSignal during backoff waits", async () => {
|
|
265
|
+
const handler = vi.fn(() => {
|
|
266
|
+
throw new Error("Retry");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const userController = new AbortController();
|
|
270
|
+
const additionalController = new AbortController();
|
|
271
|
+
|
|
272
|
+
// Start retry with both signals provided
|
|
273
|
+
const promise = retryProvider.retry({
|
|
274
|
+
handler,
|
|
275
|
+
max: 10,
|
|
276
|
+
backoff: 200, // Long backoff to ensure we have time to abort
|
|
277
|
+
signal: userController.signal,
|
|
278
|
+
additionalSignal: additionalController.signal,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Wait for first attempt to fail and backoff to start
|
|
282
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
283
|
+
|
|
284
|
+
// Abort additionalSignal during backoff (user signal still active)
|
|
285
|
+
additionalController.abort();
|
|
286
|
+
|
|
287
|
+
// BUG: This currently does NOT throw RetryCancelError because line 169 only uses signal
|
|
288
|
+
// The additionalSignal abort is ignored during wait
|
|
289
|
+
// EXPECTED: Should throw RetryCancelError
|
|
290
|
+
// ACTUAL: Continues retrying until user signal aborts
|
|
291
|
+
const startTime = Date.now();
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
await promise;
|
|
295
|
+
// If we reach here, the bug exists - additionalSignal was ignored
|
|
296
|
+
throw new Error("Should have thrown RetryCancelError");
|
|
297
|
+
} catch (error) {
|
|
298
|
+
const duration = Date.now() - startTime;
|
|
299
|
+
|
|
300
|
+
// If bug exists: continues for ~200ms+ (full backoff)
|
|
301
|
+
// If fixed: cancels immediately (~10-30ms)
|
|
302
|
+
if (error instanceof RetryCancelError) {
|
|
303
|
+
// Fixed: should cancel quickly when additionalSignal aborts
|
|
304
|
+
expect(duration).toBeLessThan(100);
|
|
305
|
+
} else {
|
|
306
|
+
// Bug exists: keeps waiting for full backoff despite additionalSignal abort
|
|
307
|
+
expect(duration).toBeGreaterThanOrEqual(180);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// Bug #2 (Yellow/High): Abort race condition
|
|
313
|
+
test("should throw RetryCancelError when aborted after handler error", async () => {
|
|
314
|
+
let attempts = 0;
|
|
315
|
+
const handler = vi.fn(() => {
|
|
316
|
+
attempts++;
|
|
317
|
+
if (attempts === 1) {
|
|
318
|
+
throw new Error("Handler failed");
|
|
319
|
+
}
|
|
320
|
+
throw new Error("Should not reach here");
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const abortController = new AbortController();
|
|
324
|
+
|
|
325
|
+
// Abort immediately after first error (before backoff wait)
|
|
326
|
+
setTimeout(() => abortController.abort(), 10);
|
|
327
|
+
|
|
328
|
+
await expect(
|
|
329
|
+
retryProvider.retry({
|
|
330
|
+
handler,
|
|
331
|
+
max: 10,
|
|
332
|
+
backoff: 100,
|
|
333
|
+
signal: abortController.signal,
|
|
334
|
+
}),
|
|
335
|
+
).rejects.toThrowError(RetryCancelError);
|
|
336
|
+
|
|
337
|
+
// Should have attempted once, then aborted
|
|
338
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Bug #3 (Yellow/High): Timeout check timing
|
|
342
|
+
test("should not allow handler to complete after maxDuration", async () => {
|
|
343
|
+
const handler = vi.fn(async () => {
|
|
344
|
+
// Handler takes 150ms
|
|
345
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
346
|
+
throw new Error("Retry");
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
const startTime = Date.now();
|
|
350
|
+
|
|
351
|
+
await expect(
|
|
352
|
+
retryProvider.retry({
|
|
353
|
+
handler,
|
|
354
|
+
max: 10,
|
|
355
|
+
maxDuration: [100, "milliseconds"],
|
|
356
|
+
backoff: 10,
|
|
357
|
+
}),
|
|
358
|
+
).rejects.toThrowError(RetryTimeoutError);
|
|
359
|
+
|
|
360
|
+
const duration = Date.now() - startTime;
|
|
361
|
+
|
|
362
|
+
// Should timeout around 100ms, not wait for handler to complete (150ms)
|
|
363
|
+
// With current bug, this will fail because handler completes at ~150ms
|
|
364
|
+
expect(duration).toBeLessThan(200);
|
|
365
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Bug #7 (Medium): onError not called on final attempt
|
|
369
|
+
test("should call onError on the final failed attempt", async () => {
|
|
370
|
+
const handler = vi.fn(() => {
|
|
371
|
+
throw new Error("Always fails");
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const onError = vi.fn();
|
|
375
|
+
|
|
376
|
+
await expect(
|
|
377
|
+
retryProvider.retry({
|
|
378
|
+
handler,
|
|
379
|
+
max: 3,
|
|
380
|
+
backoff: 0,
|
|
381
|
+
onError,
|
|
382
|
+
}),
|
|
383
|
+
).rejects.toThrowError("Always fails");
|
|
384
|
+
|
|
385
|
+
// Should call onError for ALL attempts, including the final one
|
|
386
|
+
// BUG: Currently only called 2 times (attempts 1-2), not 3 times
|
|
387
|
+
expect(onError).toHaveBeenCalledTimes(3);
|
|
388
|
+
expect(onError).toHaveBeenNthCalledWith(
|
|
389
|
+
1,
|
|
390
|
+
expect.objectContaining({ message: "Always fails" }),
|
|
391
|
+
1,
|
|
392
|
+
);
|
|
393
|
+
expect(onError).toHaveBeenNthCalledWith(
|
|
394
|
+
2,
|
|
395
|
+
expect.objectContaining({ message: "Always fails" }),
|
|
396
|
+
2,
|
|
397
|
+
);
|
|
398
|
+
expect(onError).toHaveBeenNthCalledWith(
|
|
399
|
+
3,
|
|
400
|
+
expect.objectContaining({ message: "Always fails" }),
|
|
401
|
+
3,
|
|
402
|
+
);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Bug #8 (Critical): AbortSignal.any() memory leak
|
|
406
|
+
test("should not create multiple AbortSignal instances during retries", async () => {
|
|
407
|
+
const handler = vi.fn(() => {
|
|
408
|
+
throw new Error("Retry");
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const userController = new AbortController();
|
|
412
|
+
const additionalController = new AbortController();
|
|
413
|
+
|
|
414
|
+
// Track how many times AbortSignal.any is called
|
|
415
|
+
const originalAny = AbortSignal.any;
|
|
416
|
+
const anySpy = vi.fn(originalAny);
|
|
417
|
+
AbortSignal.any = anySpy as any;
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
await expect(
|
|
421
|
+
retryProvider.retry({
|
|
422
|
+
handler,
|
|
423
|
+
max: 5,
|
|
424
|
+
backoff: 10,
|
|
425
|
+
signal: userController.signal,
|
|
426
|
+
additionalSignal: additionalController.signal,
|
|
427
|
+
}),
|
|
428
|
+
).rejects.toThrowError("Retry");
|
|
429
|
+
|
|
430
|
+
// BUG: Currently creates a NEW signal for each backoff (4 times for 5 attempts)
|
|
431
|
+
// EXPECTED: Should only create ONE combined signal at the start
|
|
432
|
+
expect(anySpy).toHaveBeenCalledTimes(1);
|
|
433
|
+
} finally {
|
|
434
|
+
// Restore original
|
|
435
|
+
AbortSignal.any = originalAny;
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
});
|