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,786 @@
|
|
|
1
|
+
import { Alepha } from "alepha";
|
|
2
|
+
import { DateTimeProvider } from "alepha/datetime";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
4
|
+
import { AlephaBatch, BatchProvider } from "../index.ts";
|
|
5
|
+
|
|
6
|
+
describe("BatchProvider", () => {
|
|
7
|
+
let alepha: Alepha;
|
|
8
|
+
let batchProvider: BatchProvider;
|
|
9
|
+
let time: DateTimeProvider;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
alepha = Alepha.create().with(AlephaBatch);
|
|
13
|
+
batchProvider = alepha.inject(BatchProvider);
|
|
14
|
+
time = alepha.inject(DateTimeProvider);
|
|
15
|
+
await alepha.start();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
await alepha.stop();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("should create a batch context", () => {
|
|
23
|
+
const handler = vi.fn();
|
|
24
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
25
|
+
|
|
26
|
+
expect(context).toBeDefined();
|
|
27
|
+
expect(context.itemStates).toBeInstanceOf(Map);
|
|
28
|
+
expect(context.partitions).toBeInstanceOf(Map);
|
|
29
|
+
expect(context.activeHandlers).toEqual([]);
|
|
30
|
+
expect(context.isShuttingDown).toBe(false);
|
|
31
|
+
expect(context.isReady).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("should push items and return unique IDs", () => {
|
|
35
|
+
const handler = vi.fn();
|
|
36
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
37
|
+
|
|
38
|
+
const id1 = batchProvider.push(context, "item-1");
|
|
39
|
+
const id2 = batchProvider.push(context, "item-2");
|
|
40
|
+
|
|
41
|
+
expect(id1).toBeDefined();
|
|
42
|
+
expect(id2).toBeDefined();
|
|
43
|
+
expect(id1).not.toBe(id2);
|
|
44
|
+
|
|
45
|
+
// Verify IDs are valid UUIDs
|
|
46
|
+
const uuidRegex =
|
|
47
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
48
|
+
expect(id1).toMatch(uuidRegex);
|
|
49
|
+
expect(id2).toMatch(uuidRegex);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("should store items in the correct partition", () => {
|
|
53
|
+
const handler = vi.fn();
|
|
54
|
+
const context = batchProvider.createContext(alepha, {
|
|
55
|
+
handler,
|
|
56
|
+
partitionBy: (item: { key: string; value: number }) => item.key,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
batchProvider.push(context, { key: "A", value: 1 });
|
|
60
|
+
batchProvider.push(context, { key: "B", value: 2 });
|
|
61
|
+
batchProvider.push(context, { key: "A", value: 3 });
|
|
62
|
+
|
|
63
|
+
expect(context.partitions.has("A")).toBe(true);
|
|
64
|
+
expect(context.partitions.has("B")).toBe(true);
|
|
65
|
+
|
|
66
|
+
const partitionA = context.partitions.get("A");
|
|
67
|
+
const partitionB = context.partitions.get("B");
|
|
68
|
+
|
|
69
|
+
expect(partitionA?.itemIds).toHaveLength(2);
|
|
70
|
+
expect(partitionB?.itemIds).toHaveLength(1);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("should use default partition when partitionBy is not provided", () => {
|
|
74
|
+
const handler = vi.fn();
|
|
75
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
76
|
+
|
|
77
|
+
batchProvider.push(context, "item-1");
|
|
78
|
+
batchProvider.push(context, "item-2");
|
|
79
|
+
|
|
80
|
+
expect(context.partitions.has("default")).toBe(true);
|
|
81
|
+
expect(context.partitions.get("default")?.itemIds).toHaveLength(2);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("should get item status", () => {
|
|
85
|
+
const handler = vi.fn();
|
|
86
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
87
|
+
|
|
88
|
+
const id = batchProvider.push(context, "item-1");
|
|
89
|
+
|
|
90
|
+
const status = batchProvider.status(context, id);
|
|
91
|
+
expect(status).toEqual({ status: "pending" });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("should return undefined status for non-existent item", () => {
|
|
95
|
+
const handler = vi.fn();
|
|
96
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
97
|
+
|
|
98
|
+
const status = batchProvider.status(context, "non-existent-id");
|
|
99
|
+
expect(status).toBeUndefined();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("should throw error when waiting for non-existent item", async () => {
|
|
103
|
+
const handler = vi.fn();
|
|
104
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
105
|
+
|
|
106
|
+
await expect(
|
|
107
|
+
batchProvider.wait(context, "non-existent-id"),
|
|
108
|
+
).rejects.toThrow("Item with id 'non-existent-id' not found");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("should flush partition and process items", async () => {
|
|
112
|
+
const handler = vi.fn(async (items: string[]) => {
|
|
113
|
+
return items.map((item) => `processed-${item}`);
|
|
114
|
+
});
|
|
115
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
116
|
+
|
|
117
|
+
const id1 = batchProvider.push(context, "item-1");
|
|
118
|
+
const id2 = batchProvider.push(context, "item-2");
|
|
119
|
+
|
|
120
|
+
await batchProvider.flush(context);
|
|
121
|
+
|
|
122
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
123
|
+
expect(handler).toHaveBeenCalledWith(["item-1", "item-2"]);
|
|
124
|
+
|
|
125
|
+
// Check status after flush
|
|
126
|
+
const status1 = batchProvider.status(context, id1);
|
|
127
|
+
const status2 = batchProvider.status(context, id2);
|
|
128
|
+
|
|
129
|
+
expect(status1?.status).toBe("completed");
|
|
130
|
+
expect(status2?.status).toBe("completed");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("should flush specific partition only", async () => {
|
|
134
|
+
const handler = vi.fn(async (items: any[]) => "processed");
|
|
135
|
+
const context = batchProvider.createContext(alepha, {
|
|
136
|
+
handler,
|
|
137
|
+
partitionBy: (item: { key: string; value: number }) => item.key,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
batchProvider.push(context, { key: "A", value: 1 });
|
|
141
|
+
batchProvider.push(context, { key: "B", value: 2 });
|
|
142
|
+
|
|
143
|
+
await batchProvider.flush(context, "A");
|
|
144
|
+
|
|
145
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
146
|
+
expect(handler).toHaveBeenCalledWith([{ key: "A", value: 1 }]);
|
|
147
|
+
|
|
148
|
+
// Partition B should still have items
|
|
149
|
+
expect(context.partitions.has("B")).toBe(true);
|
|
150
|
+
expect(context.partitions.get("B")?.itemIds).toHaveLength(1);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("should wait for item result after processing", async () => {
|
|
154
|
+
const handler = vi.fn(async (items: string[]) => {
|
|
155
|
+
return `batch-result-${items.length}`;
|
|
156
|
+
});
|
|
157
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
158
|
+
|
|
159
|
+
const id = batchProvider.push(context, "item-1");
|
|
160
|
+
|
|
161
|
+
// Start wait before flush (promise created)
|
|
162
|
+
const waitPromise = batchProvider.wait(context, id);
|
|
163
|
+
|
|
164
|
+
// Flush to process items
|
|
165
|
+
await batchProvider.flush(context);
|
|
166
|
+
|
|
167
|
+
const result = await waitPromise;
|
|
168
|
+
expect(result).toBe("batch-result-1");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("should return cached result on subsequent wait calls", async () => {
|
|
172
|
+
const handler = vi.fn(async () => "result");
|
|
173
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
174
|
+
|
|
175
|
+
const id = batchProvider.push(context, "item");
|
|
176
|
+
await batchProvider.flush(context);
|
|
177
|
+
|
|
178
|
+
const result1 = await batchProvider.wait(context, id);
|
|
179
|
+
const result2 = await batchProvider.wait(context, id);
|
|
180
|
+
|
|
181
|
+
expect(result1).toBe("result");
|
|
182
|
+
expect(result2).toBe("result");
|
|
183
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("should handle handler errors and mark items as failed", async () => {
|
|
187
|
+
const handler = vi.fn(async () => {
|
|
188
|
+
throw new Error("Handler failed");
|
|
189
|
+
});
|
|
190
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
191
|
+
|
|
192
|
+
const id = batchProvider.push(context, "item");
|
|
193
|
+
|
|
194
|
+
await batchProvider.flush(context);
|
|
195
|
+
|
|
196
|
+
const status = batchProvider.status(context, id);
|
|
197
|
+
expect(status?.status).toBe("failed");
|
|
198
|
+
if (status?.status === "failed") {
|
|
199
|
+
expect(status.error.message).toBe("Handler failed");
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("should reject wait promise on handler error", async () => {
|
|
204
|
+
const handler = vi.fn(async () => {
|
|
205
|
+
throw new Error("Handler failed");
|
|
206
|
+
});
|
|
207
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
208
|
+
|
|
209
|
+
const id = batchProvider.push(context, "item");
|
|
210
|
+
const waitPromise = batchProvider.wait(context, id);
|
|
211
|
+
|
|
212
|
+
await batchProvider.flush(context);
|
|
213
|
+
|
|
214
|
+
await expect(waitPromise).rejects.toThrow("Handler failed");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("should respect maxSize when flushing", async () => {
|
|
218
|
+
const handler = vi.fn(async (items: string[]) => `batch-${items.length}`);
|
|
219
|
+
const context = batchProvider.createContext(alepha, {
|
|
220
|
+
handler,
|
|
221
|
+
maxSize: 2,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
batchProvider.push(context, "item-1");
|
|
225
|
+
batchProvider.push(context, "item-2");
|
|
226
|
+
batchProvider.push(context, "item-3");
|
|
227
|
+
batchProvider.push(context, "item-4");
|
|
228
|
+
batchProvider.push(context, "item-5");
|
|
229
|
+
|
|
230
|
+
// Mark context as ready to enable size-based flushing
|
|
231
|
+
await batchProvider.markReady(context);
|
|
232
|
+
|
|
233
|
+
// Should have flushed twice (2 items each), leaving 1 item
|
|
234
|
+
await vi.waitFor(() => {
|
|
235
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(handler).toHaveBeenNthCalledWith(1, ["item-1", "item-2"]);
|
|
239
|
+
expect(handler).toHaveBeenNthCalledWith(2, ["item-3", "item-4"]);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("should respect concurrency limit", async () => {
|
|
243
|
+
// This test verifies that concurrency limits work by measuring
|
|
244
|
+
// when handlers start - first 2 should start together, next 2 after delay
|
|
245
|
+
|
|
246
|
+
const handlerStartTimes: number[] = [];
|
|
247
|
+
|
|
248
|
+
const handler = vi.fn(async (items: string[]) => {
|
|
249
|
+
const startTime = Date.now();
|
|
250
|
+
handlerStartTimes.push(startTime);
|
|
251
|
+
// Use a real delay (not time.wait) to ensure parallel execution
|
|
252
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
253
|
+
return "done";
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const context = batchProvider.createContext(alepha, {
|
|
257
|
+
handler,
|
|
258
|
+
maxSize: 1,
|
|
259
|
+
concurrency: 2,
|
|
260
|
+
partitionBy: (item: string) => item, // Each item in its own partition
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
batchProvider.push(context, "a");
|
|
264
|
+
batchProvider.push(context, "b");
|
|
265
|
+
batchProvider.push(context, "c");
|
|
266
|
+
batchProvider.push(context, "d");
|
|
267
|
+
|
|
268
|
+
// Flush all partitions - this should respect concurrency
|
|
269
|
+
await batchProvider.flush(context);
|
|
270
|
+
|
|
271
|
+
expect(handler).toHaveBeenCalledTimes(4);
|
|
272
|
+
|
|
273
|
+
// With concurrency=2, first 2 handlers should start at roughly the same time,
|
|
274
|
+
// and the other 2 should start ~50ms later (after first batch completes)
|
|
275
|
+
const [t1, t2, t3, t4] = handlerStartTimes;
|
|
276
|
+
|
|
277
|
+
// First two should start within 10ms of each other
|
|
278
|
+
expect(Math.abs(t1 - t2)).toBeLessThan(20);
|
|
279
|
+
|
|
280
|
+
// Third and fourth should start after first batch (50ms later)
|
|
281
|
+
// They should also start within 10ms of each other
|
|
282
|
+
expect(Math.abs(t3 - t4)).toBeLessThan(20);
|
|
283
|
+
|
|
284
|
+
// Gap between first and third should be around 50ms (handler duration)
|
|
285
|
+
expect(t3 - t1).toBeGreaterThanOrEqual(40);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("should markReady and start processing buffered items", async () => {
|
|
289
|
+
const handler = vi.fn(async (items: string[]) => "processed");
|
|
290
|
+
const context = batchProvider.createContext(alepha, {
|
|
291
|
+
handler,
|
|
292
|
+
maxSize: 5,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Push items before marking ready
|
|
296
|
+
batchProvider.push(context, "item-1");
|
|
297
|
+
batchProvider.push(context, "item-2");
|
|
298
|
+
batchProvider.push(context, "item-3");
|
|
299
|
+
batchProvider.push(context, "item-4");
|
|
300
|
+
batchProvider.push(context, "item-5");
|
|
301
|
+
|
|
302
|
+
expect(handler).not.toHaveBeenCalled();
|
|
303
|
+
expect(context.isReady).toBe(false);
|
|
304
|
+
|
|
305
|
+
await batchProvider.markReady(context);
|
|
306
|
+
|
|
307
|
+
expect(context.isReady).toBe(true);
|
|
308
|
+
|
|
309
|
+
await vi.waitFor(() => {
|
|
310
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("should handle shutdown and flush all remaining items", async () => {
|
|
315
|
+
const handler = vi.fn(async (items: string[]) => "processed");
|
|
316
|
+
const context = batchProvider.createContext(alepha, {
|
|
317
|
+
handler,
|
|
318
|
+
maxSize: 100, // Large size to prevent auto-flush
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
batchProvider.push(context, "item-1");
|
|
322
|
+
batchProvider.push(context, "item-2");
|
|
323
|
+
|
|
324
|
+
expect(handler).not.toHaveBeenCalled();
|
|
325
|
+
|
|
326
|
+
await batchProvider.shutdown(context);
|
|
327
|
+
|
|
328
|
+
expect(context.isShuttingDown).toBe(true);
|
|
329
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
330
|
+
expect(handler).toHaveBeenCalledWith(["item-1", "item-2"]);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test("should handle empty flush gracefully", async () => {
|
|
334
|
+
const handler = vi.fn();
|
|
335
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
336
|
+
|
|
337
|
+
await batchProvider.flush(context);
|
|
338
|
+
|
|
339
|
+
expect(handler).not.toHaveBeenCalled();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("should handle flush of non-existent partition gracefully", async () => {
|
|
343
|
+
const handler = vi.fn();
|
|
344
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
345
|
+
|
|
346
|
+
await batchProvider.flush(context, "non-existent");
|
|
347
|
+
|
|
348
|
+
expect(handler).not.toHaveBeenCalled();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("should use default options when not specified", () => {
|
|
352
|
+
const handler = vi.fn();
|
|
353
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
354
|
+
|
|
355
|
+
// Access protected methods through any cast for testing
|
|
356
|
+
const provider = batchProvider as any;
|
|
357
|
+
|
|
358
|
+
expect(provider.getMaxSize(context)).toBe(10);
|
|
359
|
+
expect(provider.getConcurrency(context)).toBe(1);
|
|
360
|
+
expect(provider.getMaxDuration(context)).toEqual([1, "second"]);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("should use custom options when specified", () => {
|
|
364
|
+
const handler = vi.fn();
|
|
365
|
+
const context = batchProvider.createContext(alepha, {
|
|
366
|
+
handler,
|
|
367
|
+
maxSize: 50,
|
|
368
|
+
concurrency: 5,
|
|
369
|
+
maxDuration: [30, "seconds"],
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
const provider = batchProvider as any;
|
|
373
|
+
|
|
374
|
+
expect(provider.getMaxSize(context)).toBe(50);
|
|
375
|
+
expect(provider.getConcurrency(context)).toBe(5);
|
|
376
|
+
expect(provider.getMaxDuration(context)).toEqual([30, "seconds"]);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("should retry failed handler with retry options", async () => {
|
|
380
|
+
let attempts = 0;
|
|
381
|
+
const handler = vi.fn(async (items: string[]) => {
|
|
382
|
+
attempts++;
|
|
383
|
+
if (attempts < 3) {
|
|
384
|
+
throw new Error("Temporary failure");
|
|
385
|
+
}
|
|
386
|
+
return "success";
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const context = batchProvider.createContext(alepha, {
|
|
390
|
+
handler,
|
|
391
|
+
retry: { max: 3, backoff: 10 },
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const id = batchProvider.push(context, "item");
|
|
395
|
+
await batchProvider.flush(context);
|
|
396
|
+
|
|
397
|
+
const status = batchProvider.status(context, id);
|
|
398
|
+
expect(status?.status).toBe("completed");
|
|
399
|
+
expect(handler).toHaveBeenCalledTimes(3);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("should fail after max retries exceeded", async () => {
|
|
403
|
+
const handler = vi.fn(async () => {
|
|
404
|
+
throw new Error("Always fails");
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const context = batchProvider.createContext(alepha, {
|
|
408
|
+
handler,
|
|
409
|
+
retry: { max: 2, backoff: 10 },
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const id = batchProvider.push(context, "item");
|
|
413
|
+
await batchProvider.flush(context);
|
|
414
|
+
|
|
415
|
+
const status = batchProvider.status(context, id);
|
|
416
|
+
expect(status?.status).toBe("failed");
|
|
417
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test("should handle timeout-based flushing when ready", async () => {
|
|
421
|
+
const handler = vi.fn(async (items: string[]) => "processed");
|
|
422
|
+
const context = batchProvider.createContext(alepha, {
|
|
423
|
+
handler,
|
|
424
|
+
maxSize: 100,
|
|
425
|
+
maxDuration: [100, "milliseconds"],
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
await batchProvider.markReady(context);
|
|
429
|
+
|
|
430
|
+
batchProvider.push(context, "item-1");
|
|
431
|
+
batchProvider.push(context, "item-2");
|
|
432
|
+
|
|
433
|
+
expect(handler).not.toHaveBeenCalled();
|
|
434
|
+
|
|
435
|
+
// Wait for timeout
|
|
436
|
+
await time.travel([150, "milliseconds"]);
|
|
437
|
+
|
|
438
|
+
await vi.waitFor(() => {
|
|
439
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
expect(handler).toHaveBeenCalledWith(["item-1", "item-2"]);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test("should not create timeout for items pushed before ready", async () => {
|
|
446
|
+
const handler = vi.fn(async () => "processed");
|
|
447
|
+
const context = batchProvider.createContext(alepha, {
|
|
448
|
+
handler,
|
|
449
|
+
maxSize: 100,
|
|
450
|
+
maxDuration: [100, "milliseconds"],
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// Push before ready
|
|
454
|
+
batchProvider.push(context, "item");
|
|
455
|
+
|
|
456
|
+
// Travel time forward before marking ready
|
|
457
|
+
await time.travel([200, "milliseconds"]);
|
|
458
|
+
|
|
459
|
+
// Handler should NOT have been called
|
|
460
|
+
expect(handler).not.toHaveBeenCalled();
|
|
461
|
+
|
|
462
|
+
// Now mark ready
|
|
463
|
+
await batchProvider.markReady(context);
|
|
464
|
+
|
|
465
|
+
// Wait for timeout
|
|
466
|
+
await time.travel([150, "milliseconds"]);
|
|
467
|
+
|
|
468
|
+
await vi.waitFor(() => {
|
|
469
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("should handle items arriving during flush", async () => {
|
|
474
|
+
let flushCount = 0;
|
|
475
|
+
const handler = vi.fn(async (items: string[]) => {
|
|
476
|
+
flushCount++;
|
|
477
|
+
await time.wait(50); // Simulate slow processing
|
|
478
|
+
return `batch-${flushCount}`;
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
const context = batchProvider.createContext(alepha, {
|
|
482
|
+
handler,
|
|
483
|
+
maxSize: 2,
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
await batchProvider.markReady(context);
|
|
487
|
+
|
|
488
|
+
// Push 2 items to trigger first flush
|
|
489
|
+
batchProvider.push(context, "item-1");
|
|
490
|
+
batchProvider.push(context, "item-2");
|
|
491
|
+
|
|
492
|
+
// Wait for processing to start
|
|
493
|
+
await time.wait(10);
|
|
494
|
+
|
|
495
|
+
// Push more items during processing
|
|
496
|
+
batchProvider.push(context, "item-3");
|
|
497
|
+
batchProvider.push(context, "item-4");
|
|
498
|
+
|
|
499
|
+
// Wait for all processing
|
|
500
|
+
await vi.waitFor(() => {
|
|
501
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
expect(handler).toHaveBeenNthCalledWith(1, ["item-1", "item-2"]);
|
|
505
|
+
expect(handler).toHaveBeenNthCalledWith(2, ["item-3", "item-4"]);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
test("should skip retries during shutdown", async () => {
|
|
509
|
+
let attempts = 0;
|
|
510
|
+
const handler = vi.fn(async () => {
|
|
511
|
+
attempts++;
|
|
512
|
+
throw new Error("Always fails");
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
const context = batchProvider.createContext(alepha, {
|
|
516
|
+
handler,
|
|
517
|
+
retry: { max: 5, backoff: 100 },
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
batchProvider.push(context, "item");
|
|
521
|
+
|
|
522
|
+
// Shutdown should call handler directly without retries
|
|
523
|
+
await batchProvider.shutdown(context);
|
|
524
|
+
|
|
525
|
+
// Handler should only be called once (no retries during shutdown)
|
|
526
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
test("should mark items as processing during flush", async () => {
|
|
530
|
+
let statusDuringProcessing: any;
|
|
531
|
+
const handler = vi.fn(async (items: string[]) => {
|
|
532
|
+
// Capture status while processing
|
|
533
|
+
statusDuringProcessing = batchProvider.status(context, id);
|
|
534
|
+
await time.wait(10);
|
|
535
|
+
return "done";
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
539
|
+
|
|
540
|
+
const id = batchProvider.push(context, "item");
|
|
541
|
+
|
|
542
|
+
// Status before flush
|
|
543
|
+
expect(batchProvider.status(context, id)?.status).toBe("pending");
|
|
544
|
+
|
|
545
|
+
const flushPromise = batchProvider.flush(context);
|
|
546
|
+
|
|
547
|
+
await flushPromise;
|
|
548
|
+
|
|
549
|
+
// Status should have been "processing" during handler execution
|
|
550
|
+
expect(statusDuringProcessing?.status).toBe("processing");
|
|
551
|
+
|
|
552
|
+
// Status after flush
|
|
553
|
+
expect(batchProvider.status(context, id)?.status).toBe("completed");
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
test("should clear partition timeout on flush", async () => {
|
|
557
|
+
const handler = vi.fn(async () => "processed");
|
|
558
|
+
const context = batchProvider.createContext(alepha, {
|
|
559
|
+
handler,
|
|
560
|
+
maxSize: 100,
|
|
561
|
+
maxDuration: [5, "seconds"],
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
await batchProvider.markReady(context);
|
|
565
|
+
|
|
566
|
+
batchProvider.push(context, "item");
|
|
567
|
+
|
|
568
|
+
const partition = context.partitions.get("default");
|
|
569
|
+
expect(partition?.timeout).toBeDefined();
|
|
570
|
+
|
|
571
|
+
await batchProvider.flush(context);
|
|
572
|
+
|
|
573
|
+
// Partition should be deleted after flush
|
|
574
|
+
expect(context.partitions.has("default")).toBe(false);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
test("should restart timeout after flush if items remain", async () => {
|
|
578
|
+
const handler = vi.fn(async (items: string[]) => {
|
|
579
|
+
// Slow handler to allow new items to arrive
|
|
580
|
+
await time.wait(50);
|
|
581
|
+
return "processed";
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
const context = batchProvider.createContext(alepha, {
|
|
585
|
+
handler,
|
|
586
|
+
maxSize: 2,
|
|
587
|
+
maxDuration: [100, "milliseconds"],
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
await batchProvider.markReady(context);
|
|
591
|
+
|
|
592
|
+
// Push 2 items to trigger flush
|
|
593
|
+
batchProvider.push(context, "item-1");
|
|
594
|
+
batchProvider.push(context, "item-2");
|
|
595
|
+
|
|
596
|
+
// Wait for processing to start
|
|
597
|
+
await time.wait(10);
|
|
598
|
+
|
|
599
|
+
// Push another item during processing
|
|
600
|
+
batchProvider.push(context, "item-3");
|
|
601
|
+
|
|
602
|
+
// Wait for first flush to complete
|
|
603
|
+
await time.wait(100);
|
|
604
|
+
|
|
605
|
+
// Partition should still exist with timeout for item-3
|
|
606
|
+
const partition = context.partitions.get("default");
|
|
607
|
+
if (partition && partition.itemIds.length > 0) {
|
|
608
|
+
expect(partition.timeout).toBeDefined();
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Wait for timeout to flush item-3
|
|
612
|
+
await time.travel([150, "milliseconds"]);
|
|
613
|
+
|
|
614
|
+
await vi.waitFor(() => {
|
|
615
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
expect(handler).toHaveBeenNthCalledWith(2, ["item-3"]);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// Tests for new quick win features
|
|
622
|
+
|
|
623
|
+
test("should clear completed items with clearCompleted", async () => {
|
|
624
|
+
const handler = vi.fn(async () => "result");
|
|
625
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
626
|
+
|
|
627
|
+
const id1 = batchProvider.push(context, "item-1");
|
|
628
|
+
const id2 = batchProvider.push(context, "item-2");
|
|
629
|
+
const id3 = batchProvider.push(context, "item-3");
|
|
630
|
+
|
|
631
|
+
await batchProvider.flush(context);
|
|
632
|
+
|
|
633
|
+
// All items should be completed
|
|
634
|
+
expect(context.itemStates.size).toBe(3);
|
|
635
|
+
|
|
636
|
+
// Clear completed items
|
|
637
|
+
const cleared = batchProvider.clearCompleted(context);
|
|
638
|
+
|
|
639
|
+
expect(cleared).toBe(3);
|
|
640
|
+
expect(context.itemStates.size).toBe(0);
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
test("should clear only failed items when status filter is provided", async () => {
|
|
644
|
+
// Use partitionBy to control which items fail
|
|
645
|
+
const handler = vi.fn(async (items: Array<{ type: string }>) => {
|
|
646
|
+
if (items[0].type === "fail") {
|
|
647
|
+
throw new Error("This batch fails");
|
|
648
|
+
}
|
|
649
|
+
return "success";
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
const context = batchProvider.createContext(alepha, {
|
|
653
|
+
handler,
|
|
654
|
+
maxSize: 1,
|
|
655
|
+
retry: { max: 1 }, // Disable retries - only 1 attempt
|
|
656
|
+
partitionBy: (item: { type: string }) => item.type,
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
batchProvider.push(context, { type: "fail" }); // Will fail
|
|
660
|
+
batchProvider.push(context, { type: "success" }); // Will succeed
|
|
661
|
+
|
|
662
|
+
await batchProvider.flush(context);
|
|
663
|
+
|
|
664
|
+
expect(context.itemStates.size).toBe(2);
|
|
665
|
+
|
|
666
|
+
// Clear only failed items
|
|
667
|
+
const cleared = batchProvider.clearCompleted(context, "failed");
|
|
668
|
+
|
|
669
|
+
expect(cleared).toBe(1);
|
|
670
|
+
expect(context.itemStates.size).toBe(1);
|
|
671
|
+
|
|
672
|
+
// The remaining item should be completed
|
|
673
|
+
const remaining = Array.from(context.itemStates.values())[0];
|
|
674
|
+
expect(remaining.status).toBe("completed");
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
test("should clear only completed items when status filter is provided", async () => {
|
|
678
|
+
// Use partitionBy to control which items fail
|
|
679
|
+
const handler = vi.fn(async (items: Array<{ type: string }>) => {
|
|
680
|
+
if (items[0].type === "fail") {
|
|
681
|
+
throw new Error("This batch fails");
|
|
682
|
+
}
|
|
683
|
+
return "success";
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
const context = batchProvider.createContext(alepha, {
|
|
687
|
+
handler,
|
|
688
|
+
maxSize: 1,
|
|
689
|
+
retry: { max: 1 }, // Disable retries - only 1 attempt
|
|
690
|
+
partitionBy: (item: { type: string }) => item.type,
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
batchProvider.push(context, { type: "fail" }); // Will fail
|
|
694
|
+
batchProvider.push(context, { type: "success" }); // Will succeed
|
|
695
|
+
|
|
696
|
+
await batchProvider.flush(context);
|
|
697
|
+
|
|
698
|
+
// Clear only completed items
|
|
699
|
+
const cleared = batchProvider.clearCompleted(context, "completed");
|
|
700
|
+
|
|
701
|
+
expect(cleared).toBe(1);
|
|
702
|
+
expect(context.itemStates.size).toBe(1);
|
|
703
|
+
|
|
704
|
+
// The remaining item should be failed
|
|
705
|
+
const remaining = Array.from(context.itemStates.values())[0];
|
|
706
|
+
expect(remaining.status).toBe("failed");
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
test("should enforce maxQueueSize limit", () => {
|
|
710
|
+
const handler = vi.fn();
|
|
711
|
+
const context = batchProvider.createContext(alepha, {
|
|
712
|
+
handler,
|
|
713
|
+
maxQueueSize: 3,
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// Push up to the limit
|
|
717
|
+
batchProvider.push(context, "item-1");
|
|
718
|
+
batchProvider.push(context, "item-2");
|
|
719
|
+
batchProvider.push(context, "item-3");
|
|
720
|
+
|
|
721
|
+
// Fourth push should throw
|
|
722
|
+
expect(() => batchProvider.push(context, "item-4")).toThrow(
|
|
723
|
+
"Batch queue size exceeded for partition 'default' (max: 3)",
|
|
724
|
+
);
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
test("should enforce maxQueueSize per partition", () => {
|
|
728
|
+
const handler = vi.fn();
|
|
729
|
+
const context = batchProvider.createContext(alepha, {
|
|
730
|
+
handler,
|
|
731
|
+
maxQueueSize: 2,
|
|
732
|
+
partitionBy: (item: { key: string; value: number }) => item.key,
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
// Fill partition A
|
|
736
|
+
batchProvider.push(context, { key: "A", value: 1 });
|
|
737
|
+
batchProvider.push(context, { key: "A", value: 2 });
|
|
738
|
+
|
|
739
|
+
// Partition A is full, but B should still work
|
|
740
|
+
batchProvider.push(context, { key: "B", value: 1 });
|
|
741
|
+
|
|
742
|
+
// Partition A should throw
|
|
743
|
+
expect(() => batchProvider.push(context, { key: "A", value: 3 })).toThrow(
|
|
744
|
+
"Batch queue size exceeded for partition 'A' (max: 2)",
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
// Partition B should still accept
|
|
748
|
+
batchProvider.push(context, { key: "B", value: 2 });
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
test("should handle partitionBy errors gracefully", () => {
|
|
752
|
+
const handler = vi.fn();
|
|
753
|
+
const context = batchProvider.createContext(alepha, {
|
|
754
|
+
handler,
|
|
755
|
+
partitionBy: (item: { key?: string; value?: string }) => {
|
|
756
|
+
if (!item.key) {
|
|
757
|
+
throw new Error("Missing key");
|
|
758
|
+
}
|
|
759
|
+
return item.key;
|
|
760
|
+
},
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// Item with key should work
|
|
764
|
+
batchProvider.push(context, { key: "A" });
|
|
765
|
+
expect(context.partitions.has("A")).toBe(true);
|
|
766
|
+
|
|
767
|
+
// Item without key should fall back to 'default' partition
|
|
768
|
+
batchProvider.push(context, { value: "no-key" });
|
|
769
|
+
expect(context.partitions.has("default")).toBe(true);
|
|
770
|
+
expect(context.partitions.get("default")?.itemIds).toHaveLength(1);
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
test("should return 0 when clearing with no completed items", () => {
|
|
774
|
+
const handler = vi.fn();
|
|
775
|
+
const context = batchProvider.createContext(alepha, { handler });
|
|
776
|
+
|
|
777
|
+
// Push items but don't flush
|
|
778
|
+
batchProvider.push(context, "item-1");
|
|
779
|
+
batchProvider.push(context, "item-2");
|
|
780
|
+
|
|
781
|
+
const cleared = batchProvider.clearCompleted(context);
|
|
782
|
+
|
|
783
|
+
expect(cleared).toBe(0);
|
|
784
|
+
expect(context.itemStates.size).toBe(2);
|
|
785
|
+
});
|
|
786
|
+
});
|