alepha 0.14.2 → 0.14.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/api/audits/index.browser.js +5 -5
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.d.ts +706 -785
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +13 -13
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.browser.js +5 -5
- package/dist/api/files/index.browser.js.map +1 -1
- package/dist/api/files/index.d.ts +58 -137
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +71 -71
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +5 -5
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +29 -108
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +10 -10
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js +10 -10
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +504 -171
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +12 -12
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.browser.js +163 -10
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +277 -351
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +196 -91
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.browser.js +19 -19
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +787 -852
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +827 -596
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.browser.js +6 -6
- package/dist/api/verifications/index.browser.js.map +1 -1
- package/dist/api/verifications/index.d.ts +128 -128
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +6 -6
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/bin/index.d.ts +1 -2
- package/dist/bin/index.js +0 -1
- package/dist/bin/index.js.map +1 -1
- package/dist/cli/index.d.ts +252 -131
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +595 -395
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +46 -11
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +99 -19
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +40 -22
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +45 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +40 -22
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +40 -22
- package/dist/core/index.native.js.map +1 -1
- package/dist/fake/index.js +195 -168
- package/dist/fake/index.js.map +1 -1
- package/dist/file/index.d.ts +8 -0
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js +3 -0
- package/dist/file/index.js.map +1 -1
- package/dist/logger/index.d.ts +1 -1
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +12 -2
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.d.ts +59 -195
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +201 -430
- package/dist/orm/index.js.map +1 -1
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +171 -155
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +0 -1
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +12 -0
- package/dist/server/cache/index.d.ts.map +1 -1
- package/dist/server/cache/index.js +55 -2
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts +6 -0
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/compress/index.js +38 -1
- package/dist/server/compress/index.js.map +1 -1
- package/dist/server/core/index.browser.js +2 -2
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +10 -10
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +7 -4
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/links/index.browser.js +22 -6
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +46 -44
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +24 -41
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/static/index.js +4 -0
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +2 -1
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +9 -5
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/vite/index.d.ts +101 -106
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +574 -503
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts +7 -7
- package/package.json +7 -7
- package/src/api/audits/controllers/{AuditController.ts → AdminAuditController.ts} +5 -6
- package/src/api/audits/entities/audits.ts +5 -5
- package/src/api/audits/index.browser.ts +1 -1
- package/src/api/audits/index.ts +3 -3
- package/src/api/audits/primitives/$audit.spec.ts +276 -0
- package/src/api/audits/services/AuditService.spec.ts +495 -0
- package/src/api/files/__tests__/$bucket.spec.ts +91 -0
- package/src/api/files/controllers/AdminFileStatsController.spec.ts +166 -0
- package/src/api/files/controllers/{StorageStatsController.ts → AdminFileStatsController.ts} +2 -2
- package/src/api/files/controllers/FileController.spec.ts +558 -0
- package/src/api/files/controllers/FileController.ts +4 -5
- package/src/api/files/entities/files.ts +5 -5
- package/src/api/files/index.browser.ts +1 -1
- package/src/api/files/index.ts +4 -4
- package/src/api/files/jobs/FileJobs.spec.ts +52 -0
- package/src/api/files/services/FileService.spec.ts +109 -0
- package/src/api/jobs/__tests__/JobController.spec.ts +343 -0
- package/src/api/jobs/controllers/{JobController.ts → AdminJobController.ts} +2 -2
- package/src/api/jobs/entities/jobExecutions.ts +5 -5
- package/src/api/jobs/index.ts +3 -3
- package/src/api/jobs/primitives/$job.spec.ts +476 -0
- package/src/api/notifications/controllers/{NotificationController.ts → AdminNotificationController.ts} +4 -5
- package/src/api/notifications/entities/notifications.ts +5 -5
- package/src/api/notifications/index.browser.ts +1 -1
- package/src/api/notifications/index.ts +4 -4
- package/src/api/parameters/controllers/{ConfigController.ts → AdminConfigController.ts} +46 -107
- package/src/api/parameters/entities/parameters.ts +7 -17
- package/src/api/parameters/index.ts +3 -3
- package/src/api/parameters/primitives/$config.spec.ts +356 -0
- package/src/api/parameters/schemas/activateConfigBodySchema.ts +12 -0
- package/src/api/parameters/schemas/checkScheduledResponseSchema.ts +8 -0
- package/src/api/parameters/schemas/configCurrentResponseSchema.ts +13 -0
- package/src/api/parameters/schemas/configHistoryResponseSchema.ts +9 -0
- package/src/api/parameters/schemas/configNameParamSchema.ts +10 -0
- package/src/api/parameters/schemas/configNamesResponseSchema.ts +8 -0
- package/src/api/parameters/schemas/configTreeNodeSchema.ts +13 -0
- package/src/api/parameters/schemas/configVersionParamSchema.ts +9 -0
- package/src/api/parameters/schemas/configVersionResponseSchema.ts +9 -0
- package/src/api/parameters/schemas/configsByStatusResponseSchema.ts +9 -0
- package/src/api/parameters/schemas/createConfigVersionBodySchema.ts +24 -0
- package/src/api/parameters/schemas/index.ts +15 -0
- package/src/api/parameters/schemas/parameterResponseSchema.ts +26 -0
- package/src/api/parameters/schemas/parameterStatusSchema.ts +13 -0
- package/src/api/parameters/schemas/rollbackConfigBodySchema.ts +15 -0
- package/src/api/parameters/schemas/statusParamSchema.ts +9 -0
- package/src/api/users/__tests__/EmailVerification.spec.ts +369 -0
- package/src/api/users/__tests__/PasswordReset.spec.ts +550 -0
- package/src/api/users/controllers/AdminIdentityController.spec.ts +365 -0
- package/src/api/users/controllers/{IdentityController.ts → AdminIdentityController.ts} +3 -4
- package/src/api/users/controllers/AdminSessionController.spec.ts +274 -0
- package/src/api/users/controllers/{SessionController.ts → AdminSessionController.ts} +3 -4
- package/src/api/users/controllers/AdminUserController.spec.ts +372 -0
- package/src/api/users/controllers/AdminUserController.ts +116 -0
- package/src/api/users/controllers/UserController.ts +4 -107
- package/src/api/users/controllers/UserRealmController.ts +3 -0
- package/src/api/users/entities/identities.ts +6 -6
- package/src/api/users/entities/sessions.ts +6 -6
- package/src/api/users/entities/users.ts +9 -9
- package/src/api/users/index.ts +9 -6
- package/src/api/users/primitives/$userRealm.ts +13 -8
- package/src/api/users/services/CredentialService.spec.ts +509 -0
- package/src/api/users/services/CredentialService.ts +46 -0
- package/src/api/users/services/IdentityService.ts +15 -0
- package/src/api/users/services/RegistrationService.spec.ts +630 -0
- package/src/api/users/services/RegistrationService.ts +18 -0
- package/src/api/users/services/SessionService.spec.ts +301 -0
- package/src/api/users/services/SessionService.ts +110 -1
- package/src/api/users/services/UserService.ts +67 -2
- package/src/api/verifications/__tests__/CodeVerification.spec.ts +318 -0
- package/src/api/verifications/__tests__/LinkVerification.spec.ts +279 -0
- package/src/api/verifications/entities/verifications.ts +6 -6
- package/src/api/verifications/jobs/VerificationJobs.spec.ts +50 -0
- package/src/batch/__tests__/startup-buffering.spec.ts +458 -0
- package/src/batch/primitives/$batch.spec.ts +766 -0
- package/src/batch/providers/BatchProvider.spec.ts +786 -0
- package/src/bin/index.ts +0 -1
- package/src/bucket/__tests__/shared.ts +194 -0
- package/src/bucket/primitives/$bucket.spec.ts +104 -0
- package/src/bucket/providers/FileStorageProvider.spec.ts +13 -0
- package/src/bucket/providers/LocalFileStorageProvider.spec.ts +77 -0
- package/src/bucket/providers/MemoryFileStorageProvider.spec.ts +82 -0
- package/src/cache/core/__tests__/shared.ts +377 -0
- package/src/cache/core/primitives/$cache.spec.ts +111 -0
- package/src/cache/redis/__tests__/cache-redis.spec.ts +70 -0
- package/src/cli/apps/AlephaCli.ts +25 -6
- package/src/cli/atoms/buildOptions.ts +88 -0
- package/src/cli/commands/build.ts +32 -69
- package/src/cli/commands/db.ts +0 -4
- package/src/cli/commands/dev.ts +34 -10
- package/src/cli/commands/gen/changelog.spec.ts +315 -0
- package/src/cli/commands/{changelog.ts → gen/changelog.ts} +9 -9
- package/src/cli/commands/gen/env.ts +53 -0
- package/src/cli/commands/gen/openapi.ts +71 -0
- package/src/cli/commands/gen/resource.ts +15 -0
- package/src/cli/commands/gen.ts +24 -0
- package/src/cli/commands/init.ts +2 -1
- package/src/cli/commands/root.ts +12 -3
- package/src/cli/commands/test.ts +0 -1
- package/src/cli/commands/typecheck.ts +5 -0
- package/src/cli/commands/verify.ts +1 -1
- package/src/cli/defineConfig.ts +49 -7
- package/src/cli/index.ts +2 -2
- package/src/cli/services/AlephaCliUtils.ts +105 -55
- package/src/cli/services/GitMessageParser.ts +1 -1
- package/src/command/helpers/Asker.spec.ts +127 -0
- package/src/command/helpers/Runner.spec.ts +126 -0
- package/src/command/helpers/Runner.ts +1 -1
- package/src/command/primitives/$command.spec.ts +1588 -0
- package/src/command/primitives/$command.ts +0 -6
- package/src/command/providers/CliProvider.ts +75 -27
- package/src/core/Alepha.ts +87 -0
- package/src/core/__tests__/Alepha-emit.spec.ts +22 -0
- package/src/core/__tests__/Alepha-graph.spec.ts +93 -0
- package/src/core/__tests__/Alepha-has.spec.ts +41 -0
- package/src/core/__tests__/Alepha-inject.spec.ts +93 -0
- package/src/core/__tests__/Alepha-register.spec.ts +81 -0
- package/src/core/__tests__/Alepha-start.spec.ts +176 -0
- package/src/core/__tests__/Alepha-with.spec.ts +14 -0
- package/src/core/__tests__/TypeBox-usecases.spec.ts +35 -0
- package/src/core/__tests__/TypeBoxLocale.spec.ts +15 -0
- package/src/core/__tests__/descriptor.spec.ts +34 -0
- package/src/core/__tests__/fixtures/A.ts +5 -0
- package/src/core/__tests__/pagination.spec.ts +77 -0
- package/src/core/helpers/jsonSchemaToTypeBox.ts +2 -2
- package/src/core/primitives/$atom.spec.ts +43 -0
- package/src/core/primitives/$hook.spec.ts +130 -0
- package/src/core/primitives/$inject.spec.ts +175 -0
- package/src/core/primitives/$module.spec.ts +115 -0
- package/src/core/providers/CodecManager.spec.ts +740 -0
- package/src/core/providers/EventManager.spec.ts +762 -0
- package/src/core/providers/EventManager.ts +4 -0
- package/src/core/providers/StateManager.spec.ts +365 -0
- package/src/core/providers/TypeProvider.spec.ts +1607 -0
- package/src/core/providers/TypeProvider.ts +20 -26
- package/src/datetime/primitives/$interval.spec.ts +103 -0
- package/src/datetime/providers/DateTimeProvider.spec.ts +86 -0
- package/src/email/primitives/$email.spec.ts +175 -0
- package/src/email/providers/LocalEmailProvider.spec.ts +341 -0
- package/src/fake/__tests__/keyName.example.ts +40 -0
- package/src/fake/__tests__/keyName.spec.ts +152 -0
- package/src/fake/__tests__/module.example.ts +32 -0
- package/src/fake/providers/FakeProvider.spec.ts +438 -0
- package/src/file/providers/FileSystemProvider.ts +8 -0
- package/src/file/providers/NodeFileSystemProvider.spec.ts +418 -0
- package/src/file/providers/NodeFileSystemProvider.ts +5 -0
- package/src/file/services/FileDetector.spec.ts +591 -0
- package/src/lock/core/__tests__/shared.ts +190 -0
- package/src/lock/core/providers/MemoryLockProvider.spec.ts +25 -0
- package/src/lock/redis/providers/RedisLockProvider.spec.ts +25 -0
- package/src/logger/__tests__/SimpleFormatterProvider.spec.ts +109 -0
- package/src/logger/index.ts +15 -3
- package/src/logger/primitives/$logger.spec.ts +108 -0
- package/src/logger/services/Logger.spec.ts +295 -0
- package/src/mcp/__tests__/errors.spec.ts +175 -0
- package/src/mcp/__tests__/integration.spec.ts +450 -0
- package/src/mcp/helpers/jsonrpc.spec.ts +380 -0
- package/src/mcp/primitives/$prompt.spec.ts +468 -0
- package/src/mcp/primitives/$resource.spec.ts +390 -0
- package/src/mcp/primitives/$tool.spec.ts +406 -0
- package/src/mcp/providers/McpServerProvider.spec.ts +797 -0
- package/src/mcp/transports/StdioMcpTransport.ts +1 -1
- package/src/orm/__tests__/$repository-crud.spec.ts +276 -0
- package/src/orm/__tests__/$repository-hooks.spec.ts +325 -0
- package/src/orm/__tests__/$repository-orderBy.spec.ts +128 -0
- package/src/orm/__tests__/$repository-pagination-sort.spec.ts +149 -0
- package/src/orm/__tests__/$repository-save.spec.ts +37 -0
- package/src/orm/__tests__/ModelBuilder-integration.spec.ts +490 -0
- package/src/orm/__tests__/ModelBuilder-types.spec.ts +186 -0
- package/src/orm/__tests__/PostgresProvider.spec.ts +46 -0
- package/src/orm/__tests__/delete-returning.spec.ts +256 -0
- package/src/orm/__tests__/deletedAt.spec.ts +80 -0
- package/src/orm/__tests__/enums.spec.ts +315 -0
- package/src/orm/__tests__/execute.spec.ts +72 -0
- package/src/orm/__tests__/fixtures/bigEntitySchema.ts +65 -0
- package/src/orm/__tests__/fixtures/userEntitySchema.ts +27 -0
- package/src/orm/__tests__/joins.spec.ts +1114 -0
- package/src/orm/__tests__/page.spec.ts +287 -0
- package/src/orm/__tests__/primaryKey.spec.ts +87 -0
- package/src/orm/__tests__/query-date-encoding.spec.ts +402 -0
- package/src/orm/__tests__/ref-auto-onDelete.spec.ts +156 -0
- package/src/orm/__tests__/references.spec.ts +102 -0
- package/src/orm/__tests__/security.spec.ts +710 -0
- package/src/orm/__tests__/sqlite.spec.ts +111 -0
- package/src/orm/__tests__/string-operators.spec.ts +429 -0
- package/src/orm/__tests__/timestamps.spec.ts +388 -0
- package/src/orm/__tests__/validation.spec.ts +183 -0
- package/src/orm/__tests__/version.spec.ts +64 -0
- package/src/orm/helpers/parseQueryString.spec.ts +196 -0
- package/src/orm/index.ts +2 -8
- package/src/orm/primitives/$repository.spec.ts +137 -0
- package/src/orm/primitives/$sequence.spec.ts +29 -0
- package/src/orm/primitives/$transaction.spec.ts +82 -0
- package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -3
- package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +1 -1
- package/src/orm/providers/drivers/DatabaseProvider.ts +1 -1
- package/src/orm/providers/drivers/NodePostgresProvider.ts +3 -3
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +1 -1
- package/src/orm/providers/drivers/PglitePostgresProvider.ts +2 -2
- package/src/orm/services/ModelBuilder.spec.ts +575 -0
- package/src/orm/services/Repository.spec.ts +137 -0
- package/src/queue/core/__tests__/shared.ts +143 -0
- package/src/queue/core/providers/MemoryQueueProvider.spec.ts +23 -0
- package/src/queue/core/providers/WorkerProvider.spec.ts +394 -0
- package/src/queue/redis/providers/RedisQueueProvider.spec.ts +23 -0
- package/src/redis/__tests__/redis.spec.ts +58 -0
- package/src/retry/primitives/$retry.spec.ts +234 -0
- package/src/retry/providers/RetryProvider.spec.ts +438 -0
- package/src/router/__tests__/match.spec.ts +252 -0
- package/src/router/providers/RouterProvider.spec.ts +197 -0
- package/src/scheduler/__tests__/$scheduler-cron.spec.ts +25 -0
- package/src/scheduler/__tests__/$scheduler-interval.spec.ts +25 -0
- package/src/scheduler/__tests__/shared.ts +77 -0
- package/src/security/__tests__/bug-1-wildcard-after-start.spec.ts +229 -0
- package/src/security/__tests__/bug-2-password-validation.spec.ts +245 -0
- package/src/security/__tests__/bug-3-regex-vulnerability.spec.ts +407 -0
- package/src/security/__tests__/bug-4-oauth2-validation.spec.ts +439 -0
- package/src/security/__tests__/multi-layer-permissions.spec.ts +522 -0
- package/src/security/primitives/$permission.spec.ts +30 -0
- package/src/security/primitives/$permission.ts +2 -2
- package/src/security/primitives/$realm.spec.ts +101 -0
- package/src/security/primitives/$role.spec.ts +52 -0
- package/src/security/primitives/$serviceAccount.spec.ts +61 -0
- package/src/security/providers/SecurityProvider.spec.ts +350 -0
- package/src/server/auth/providers/ServerAuthProvider.ts +0 -2
- package/src/server/cache/providers/ServerCacheProvider.spec.ts +1125 -0
- package/src/server/cache/providers/ServerCacheProvider.ts +94 -9
- package/src/server/compress/providers/ServerCompressProvider.spec.ts +31 -0
- package/src/server/compress/providers/ServerCompressProvider.ts +63 -2
- package/src/server/cookies/providers/ServerCookiesProvider.spec.ts +253 -0
- package/src/server/core/__tests__/ServerRouterProvider-getRoutes.spec.ts +334 -0
- package/src/server/core/__tests__/ServerRouterProvider-requestId.spec.ts +129 -0
- package/src/server/core/helpers/ServerReply.ts +2 -2
- package/src/server/core/primitives/$action.spec.ts +191 -0
- package/src/server/core/primitives/$route.spec.ts +65 -0
- package/src/server/core/providers/ServerBodyParserProvider.spec.ts +93 -0
- package/src/server/core/providers/ServerLoggerProvider.spec.ts +100 -0
- package/src/server/core/providers/ServerProvider.ts +14 -2
- package/src/server/core/services/HttpClient.spec.ts +123 -0
- package/src/server/core/services/UserAgentParser.spec.ts +111 -0
- package/src/server/cors/providers/ServerCorsProvider.spec.ts +481 -0
- package/src/server/health/providers/ServerHealthProvider.spec.ts +22 -0
- package/src/server/helmet/providers/ServerHelmetProvider.spec.ts +105 -0
- package/src/server/links/__tests__/$action.spec.ts +238 -0
- package/src/server/links/__tests__/fixtures/CrudApp.ts +122 -0
- package/src/server/links/__tests__/requestId.spec.ts +120 -0
- package/src/server/links/primitives/$remote.spec.ts +228 -0
- package/src/server/links/providers/LinkProvider.spec.ts +54 -0
- package/src/server/links/providers/LinkProvider.ts +49 -3
- package/src/server/links/providers/ServerLinksProvider.ts +1 -53
- package/src/server/links/schemas/apiLinksResponseSchema.ts +7 -0
- package/src/server/metrics/providers/ServerMetricsProvider.spec.ts +25 -0
- package/src/server/multipart/providers/ServerMultipartProvider.spec.ts +528 -0
- package/src/server/proxy/primitives/$proxy.spec.ts +87 -0
- package/src/server/rate-limit/__tests__/ActionRateLimit.spec.ts +211 -0
- package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +344 -0
- package/src/server/security/__tests__/BasicAuth.spec.ts +684 -0
- package/src/server/security/__tests__/ServerSecurityProvider-realm.spec.ts +388 -0
- package/src/server/security/providers/ServerSecurityProvider.spec.ts +123 -0
- package/src/server/static/primitives/$serve.spec.ts +193 -0
- package/src/server/static/providers/ServerStaticProvider.ts +10 -0
- package/src/server/swagger/__tests__/ui.spec.ts +52 -0
- package/src/server/swagger/primitives/$swagger.spec.ts +193 -0
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +19 -12
- package/src/sms/primitives/$sms.spec.ts +165 -0
- package/src/sms/providers/LocalSmsProvider.spec.ts +224 -0
- package/src/sms/providers/MemorySmsProvider.spec.ts +193 -0
- package/src/thread/primitives/$thread.spec.ts +186 -0
- package/src/topic/core/__tests__/shared.ts +144 -0
- package/src/topic/core/providers/MemoryTopicProvider.spec.ts +23 -0
- package/src/topic/redis/providers/RedisTopicProvider.spec.ts +23 -0
- package/src/vite/helpers/importViteReact.ts +13 -0
- package/src/vite/index.ts +1 -21
- package/src/vite/plugins/viteAlephaDev.ts +32 -5
- package/src/vite/plugins/viteAlephaSsrPreload.ts +222 -0
- package/src/vite/tasks/buildClient.ts +11 -0
- package/src/vite/tasks/buildServer.ts +47 -3
- package/src/vite/tasks/devServer.ts +69 -0
- package/src/vite/tasks/index.ts +2 -1
- package/src/vite/tasks/runAlepha.ts +7 -1
- package/src/websocket/__tests__/$websocket-new.spec.ts +195 -0
- package/src/websocket/primitives/$channel.spec.ts +30 -0
- package/src/cli/assets/viteConfigTs.ts +0 -14
- package/src/cli/commands/run.ts +0 -24
- package/src/vite/plugins/viteAlepha.ts +0 -37
- package/src/vite/plugins/viteAlephaBuild.ts +0 -281
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
import { Alepha, t } from "alepha";
|
|
2
|
+
import { DateTimeProvider } from "alepha/datetime";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
4
|
+
import { $batch, AlephaBatch } from "../index.ts";
|
|
5
|
+
|
|
6
|
+
// Mock handler to track calls and received items
|
|
7
|
+
const createMockHandler = () => {
|
|
8
|
+
return vi.fn(async (items: any[]) => {
|
|
9
|
+
// Default successful handler
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe("$batch primitive", () => {
|
|
14
|
+
let alepha: Alepha;
|
|
15
|
+
let time: DateTimeProvider;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
alepha = Alepha.create().with(AlephaBatch);
|
|
19
|
+
time = alepha.inject(DateTimeProvider);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(async () => {
|
|
23
|
+
await alepha.stop();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("should batch items and flush when maxSize is reached", async () => {
|
|
27
|
+
const mockHandler = createMockHandler();
|
|
28
|
+
class TestApp {
|
|
29
|
+
batcher = $batch({
|
|
30
|
+
schema: t.text(),
|
|
31
|
+
maxSize: 3,
|
|
32
|
+
handler: mockHandler,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const app = alepha.inject(TestApp);
|
|
37
|
+
await alepha.start();
|
|
38
|
+
|
|
39
|
+
const id1 = await app.batcher.push("A");
|
|
40
|
+
const id2 = await app.batcher.push("B");
|
|
41
|
+
expect(mockHandler).not.toHaveBeenCalled();
|
|
42
|
+
|
|
43
|
+
const id3 = await app.batcher.push("C"); // This should trigger the flush
|
|
44
|
+
|
|
45
|
+
await vi.waitFor(() => {
|
|
46
|
+
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
47
|
+
});
|
|
48
|
+
expect(mockHandler).toHaveBeenCalledWith(["A", "B", "C"]);
|
|
49
|
+
|
|
50
|
+
// Verify IDs are returned
|
|
51
|
+
expect(typeof id1).toBe("string");
|
|
52
|
+
expect(typeof id2).toBe("string");
|
|
53
|
+
expect(typeof id3).toBe("string");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("should flush remaining items when maxDuration is reached", async () => {
|
|
57
|
+
const mockHandler = createMockHandler();
|
|
58
|
+
class TestApp {
|
|
59
|
+
batcher = $batch({
|
|
60
|
+
schema: t.text(),
|
|
61
|
+
maxSize: 10,
|
|
62
|
+
maxDuration: [5, "seconds"],
|
|
63
|
+
handler: mockHandler,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const app = alepha.inject(TestApp);
|
|
68
|
+
await alepha.start();
|
|
69
|
+
|
|
70
|
+
app.batcher.push("A");
|
|
71
|
+
app.batcher.push("B");
|
|
72
|
+
expect(mockHandler).not.toHaveBeenCalled();
|
|
73
|
+
|
|
74
|
+
await time.travel([6, "seconds"]); // Exceed maxDuration
|
|
75
|
+
|
|
76
|
+
await vi.waitFor(() => {
|
|
77
|
+
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
78
|
+
});
|
|
79
|
+
expect(mockHandler).toHaveBeenCalledWith(["A", "B"]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("should handle partitioning correctly", async () => {
|
|
83
|
+
const mockHandler = createMockHandler();
|
|
84
|
+
class TestApp {
|
|
85
|
+
batcher = $batch({
|
|
86
|
+
schema: t.object({ id: t.number(), value: t.text() }),
|
|
87
|
+
maxSize: 2,
|
|
88
|
+
partitionBy: (item) => `partition-${item.id}`,
|
|
89
|
+
handler: mockHandler,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const app = alepha.inject(TestApp);
|
|
94
|
+
await alepha.start();
|
|
95
|
+
|
|
96
|
+
app.batcher.push({ id: 1, value: "A" });
|
|
97
|
+
app.batcher.push({ id: 2, value: "B" });
|
|
98
|
+
app.batcher.push({ id: 1, value: "C" }); // Flushes partition 1
|
|
99
|
+
|
|
100
|
+
await vi.waitFor(
|
|
101
|
+
() => {
|
|
102
|
+
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
103
|
+
},
|
|
104
|
+
{ timeout: 1000 },
|
|
105
|
+
);
|
|
106
|
+
expect(mockHandler).toHaveBeenCalledWith([
|
|
107
|
+
{ id: 1, value: "A" },
|
|
108
|
+
{ id: 1, value: "C" },
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
app.batcher.push({ id: 2, value: "D" }); // Flushes partition 2
|
|
112
|
+
|
|
113
|
+
await vi.waitFor(
|
|
114
|
+
() => {
|
|
115
|
+
expect(mockHandler).toHaveBeenCalledTimes(2);
|
|
116
|
+
},
|
|
117
|
+
{ timeout: 2000 },
|
|
118
|
+
);
|
|
119
|
+
expect(mockHandler).toHaveBeenCalledWith([
|
|
120
|
+
{ id: 2, value: "B" },
|
|
121
|
+
{ id: 2, value: "D" },
|
|
122
|
+
]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("should flush all pending items on application stop", async () => {
|
|
126
|
+
const mockHandler = createMockHandler();
|
|
127
|
+
class TestApp {
|
|
128
|
+
batcher = $batch({
|
|
129
|
+
schema: t.text(),
|
|
130
|
+
maxSize: 10,
|
|
131
|
+
handler: mockHandler,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const app = alepha.inject(TestApp);
|
|
136
|
+
await alepha.start();
|
|
137
|
+
|
|
138
|
+
app.batcher.push("A");
|
|
139
|
+
app.batcher.push("B");
|
|
140
|
+
|
|
141
|
+
await alepha.stop(); // Graceful shutdown should trigger flush
|
|
142
|
+
|
|
143
|
+
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
144
|
+
expect(mockHandler).toHaveBeenCalledWith(["A", "B"]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("should reject wait promise if handler fails after retries", async () => {
|
|
148
|
+
const failingHandler = vi.fn(async (items: any[]) => {
|
|
149
|
+
throw new Error("Handler failed");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
class TestApp {
|
|
153
|
+
batcher = $batch({
|
|
154
|
+
schema: t.text(),
|
|
155
|
+
maxSize: 1,
|
|
156
|
+
handler: failingHandler,
|
|
157
|
+
retry: { max: 2 }, // Try a total of 2 times
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const app = alepha.inject(TestApp);
|
|
162
|
+
await alepha.start();
|
|
163
|
+
|
|
164
|
+
const id = await app.batcher.push("A");
|
|
165
|
+
expect(typeof id).toBe("string");
|
|
166
|
+
|
|
167
|
+
const waitPromise = app.batcher.wait(id);
|
|
168
|
+
await expect(waitPromise).rejects.toThrow("Handler failed");
|
|
169
|
+
|
|
170
|
+
await vi.waitFor(() => {
|
|
171
|
+
expect(failingHandler).toHaveBeenCalledTimes(2);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Verify status is failed
|
|
175
|
+
const status = app.batcher.status(id);
|
|
176
|
+
expect(status?.status).toBe("failed");
|
|
177
|
+
if (status?.status === "failed") {
|
|
178
|
+
expect(status.error.message).toBe("Handler failed");
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("should resolve wait promise on successful processing", async () => {
|
|
183
|
+
const mockHandler = vi.fn(async (items: any[]) => {
|
|
184
|
+
return "success";
|
|
185
|
+
});
|
|
186
|
+
class TestApp {
|
|
187
|
+
batcher = $batch({
|
|
188
|
+
schema: t.text(),
|
|
189
|
+
maxSize: 1,
|
|
190
|
+
handler: mockHandler,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const app = alepha.inject(TestApp);
|
|
195
|
+
await alepha.start();
|
|
196
|
+
|
|
197
|
+
const id = await app.batcher.push("A");
|
|
198
|
+
expect(typeof id).toBe("string");
|
|
199
|
+
|
|
200
|
+
// Status should be pending or processing
|
|
201
|
+
let status = app.batcher.status(id);
|
|
202
|
+
expect(status?.status).toMatch(/pending|processing/);
|
|
203
|
+
|
|
204
|
+
const result = await app.batcher.wait(id);
|
|
205
|
+
expect(result).toBe("success");
|
|
206
|
+
|
|
207
|
+
// Status should now be completed
|
|
208
|
+
status = app.batcher.status(id);
|
|
209
|
+
expect(status?.status).toBe("completed");
|
|
210
|
+
if (status?.status === "completed") {
|
|
211
|
+
expect(status.result).toBe("success");
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("should respect concurrency option", async () => {
|
|
216
|
+
let activeHandlers = 0;
|
|
217
|
+
let maxActiveHandlers = 0;
|
|
218
|
+
|
|
219
|
+
const slowHandler = vi.fn(async (items: any[]) => {
|
|
220
|
+
activeHandlers++;
|
|
221
|
+
maxActiveHandlers = Math.max(maxActiveHandlers, activeHandlers);
|
|
222
|
+
await time.wait(100); // Simulate work
|
|
223
|
+
activeHandlers--;
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
class TestApp {
|
|
227
|
+
batcher = $batch({
|
|
228
|
+
schema: t.text(),
|
|
229
|
+
maxSize: 1,
|
|
230
|
+
concurrency: 2,
|
|
231
|
+
handler: slowHandler,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const app = alepha.inject(TestApp);
|
|
236
|
+
await alepha.start();
|
|
237
|
+
|
|
238
|
+
// Push 4 items to trigger 4 batches
|
|
239
|
+
const ids = await Promise.all([
|
|
240
|
+
app.batcher.push("A"),
|
|
241
|
+
app.batcher.push("B"),
|
|
242
|
+
app.batcher.push("C"),
|
|
243
|
+
app.batcher.push("D"),
|
|
244
|
+
]);
|
|
245
|
+
|
|
246
|
+
// Wait for all to complete
|
|
247
|
+
await Promise.all(ids.map((id) => app.batcher.wait(id)));
|
|
248
|
+
|
|
249
|
+
expect(slowHandler).toHaveBeenCalledTimes(4);
|
|
250
|
+
expect(maxActiveHandlers).toBe(2);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("should flush manually a specific partition", async () => {
|
|
254
|
+
const mockHandler = createMockHandler();
|
|
255
|
+
class TestApp {
|
|
256
|
+
batcher = $batch({
|
|
257
|
+
schema: t.object({ id: t.number(), value: t.text() }),
|
|
258
|
+
maxSize: 5,
|
|
259
|
+
partitionBy: (item) => `p-${item.id}`,
|
|
260
|
+
handler: mockHandler,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const app = alepha.inject(TestApp);
|
|
265
|
+
await alepha.start();
|
|
266
|
+
|
|
267
|
+
app.batcher.push({ id: 1, value: "A" });
|
|
268
|
+
app.batcher.push({ id: 2, value: "B" });
|
|
269
|
+
app.batcher.push({ id: 1, value: "C" });
|
|
270
|
+
|
|
271
|
+
expect(mockHandler).not.toHaveBeenCalled();
|
|
272
|
+
|
|
273
|
+
await app.batcher.flush("p-1");
|
|
274
|
+
|
|
275
|
+
await vi.waitFor(() => {
|
|
276
|
+
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
277
|
+
});
|
|
278
|
+
expect(mockHandler).toHaveBeenCalledWith([
|
|
279
|
+
{ id: 1, value: "A" },
|
|
280
|
+
{ id: 1, value: "C" },
|
|
281
|
+
]);
|
|
282
|
+
|
|
283
|
+
// The other partition should remain
|
|
284
|
+
await app.batcher.flush("p-2");
|
|
285
|
+
|
|
286
|
+
await vi.waitFor(() => {
|
|
287
|
+
expect(mockHandler).toHaveBeenCalledTimes(2);
|
|
288
|
+
});
|
|
289
|
+
expect(mockHandler).toHaveBeenCalledWith([{ id: 2, value: "B" }]);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("should flush all partitions manually", async () => {
|
|
293
|
+
const mockHandler = createMockHandler();
|
|
294
|
+
class TestApp {
|
|
295
|
+
batcher = $batch({
|
|
296
|
+
schema: t.object({ id: t.number(), value: t.text() }),
|
|
297
|
+
maxSize: 5,
|
|
298
|
+
partitionBy: (item) => `p-${item.id}`,
|
|
299
|
+
handler: mockHandler,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const app = alepha.inject(TestApp);
|
|
304
|
+
await alepha.start();
|
|
305
|
+
|
|
306
|
+
app.batcher.push({ id: 1, value: "A" });
|
|
307
|
+
app.batcher.push({ id: 2, value: "B" });
|
|
308
|
+
|
|
309
|
+
await app.batcher.flush();
|
|
310
|
+
|
|
311
|
+
await vi.waitFor(() => {
|
|
312
|
+
expect(mockHandler).toHaveBeenCalledTimes(2);
|
|
313
|
+
});
|
|
314
|
+
expect(mockHandler).toHaveBeenCalledWith([{ id: 1, value: "A" }]);
|
|
315
|
+
expect(mockHandler).toHaveBeenCalledWith([{ id: 2, value: "B" }]);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("should validate items against schema", async () => {
|
|
319
|
+
const mockHandler = createMockHandler();
|
|
320
|
+
class TestApp {
|
|
321
|
+
batcher = $batch({
|
|
322
|
+
schema: t.number(), // Expects numbers
|
|
323
|
+
maxSize: 1,
|
|
324
|
+
handler: mockHandler,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const app = alepha.inject(TestApp);
|
|
329
|
+
await alepha.start();
|
|
330
|
+
|
|
331
|
+
// Vitest doesn't properly catch type errors in async promises thrown by TypeBox,
|
|
332
|
+
// so we test the rejection with a generic Error.
|
|
333
|
+
await expect(app.batcher.push("not-a-number" as any)).rejects.toThrow();
|
|
334
|
+
expect(mockHandler).not.toHaveBeenCalled();
|
|
335
|
+
|
|
336
|
+
const id = await app.batcher.push(123);
|
|
337
|
+
expect(typeof id).toBe("string");
|
|
338
|
+
await vi.waitFor(() => {
|
|
339
|
+
expect(mockHandler).toHaveBeenCalledWith([123]);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("should handle empty batches gracefully", async () => {
|
|
344
|
+
const mockHandler = createMockHandler();
|
|
345
|
+
class TestApp {
|
|
346
|
+
batcher = $batch({
|
|
347
|
+
schema: t.text(),
|
|
348
|
+
maxSize: 5,
|
|
349
|
+
handler: mockHandler,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const app = alepha.inject(TestApp);
|
|
354
|
+
await alepha.start();
|
|
355
|
+
|
|
356
|
+
await app.batcher.flush(); // Should not throw or call handler
|
|
357
|
+
|
|
358
|
+
expect(mockHandler).not.toHaveBeenCalled();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("should handle empty partitions gracefully", async () => {
|
|
362
|
+
const mockHandler = createMockHandler();
|
|
363
|
+
class TestApp {
|
|
364
|
+
batcher = $batch({
|
|
365
|
+
schema: t.text(),
|
|
366
|
+
maxSize: 5,
|
|
367
|
+
maxDuration: [1, "second"],
|
|
368
|
+
partitionBy: (item) => item,
|
|
369
|
+
handler: mockHandler,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
const app = alepha.inject(TestApp);
|
|
373
|
+
await alepha.start();
|
|
374
|
+
await app.batcher.push("D");
|
|
375
|
+
await app.batcher.flush("D");
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
test("should allow to get resolved items", async () => {
|
|
379
|
+
let tick = 0;
|
|
380
|
+
|
|
381
|
+
class TestApp {
|
|
382
|
+
httpBatch = $batch({
|
|
383
|
+
schema: t.text(),
|
|
384
|
+
maxSize: 10,
|
|
385
|
+
maxDuration: [100, "milliseconds"],
|
|
386
|
+
handler: async (urls) => {
|
|
387
|
+
tick += 1;
|
|
388
|
+
|
|
389
|
+
if (urls.length === 1) {
|
|
390
|
+
return { [urls[0]]: `Response for ${urls[0]}` };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const response: Record<string, string> = {};
|
|
394
|
+
for (const url of urls) {
|
|
395
|
+
response[url] = `(batch) Response for ${url}`;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return response;
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
async fetch(url: string) {
|
|
403
|
+
const id = await this.httpBatch.push(url);
|
|
404
|
+
const response = await this.httpBatch.wait(id);
|
|
405
|
+
return response[url];
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const app = alepha.inject(TestApp);
|
|
410
|
+
await alepha.start();
|
|
411
|
+
|
|
412
|
+
const tasks: Promise<any>[] = [];
|
|
413
|
+
|
|
414
|
+
tasks.push(app.fetch("https://example.com/A"));
|
|
415
|
+
tasks.push(app.fetch("https://example.com/B"));
|
|
416
|
+
await time.wait(200); // Wait for batch to accumulate items
|
|
417
|
+
tasks.push(app.fetch("https://example.com/C"));
|
|
418
|
+
|
|
419
|
+
const result = await Promise.all(tasks);
|
|
420
|
+
|
|
421
|
+
expect(tick).toBe(2);
|
|
422
|
+
expect(result).toEqual([
|
|
423
|
+
"(batch) Response for https://example.com/A",
|
|
424
|
+
"(batch) Response for https://example.com/B",
|
|
425
|
+
"Response for https://example.com/C",
|
|
426
|
+
]);
|
|
427
|
+
|
|
428
|
+
const response = await app.fetch("https://example.com/D");
|
|
429
|
+
expect(response).toBe("Response for https://example.com/D");
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test("should handle items arriving during batch processing (race condition fix)", async () => {
|
|
433
|
+
let handlerCallCount = 0;
|
|
434
|
+
const handlerCalls: string[][] = [];
|
|
435
|
+
|
|
436
|
+
class TestApp {
|
|
437
|
+
batcher = $batch({
|
|
438
|
+
schema: t.text(),
|
|
439
|
+
maxSize: 2,
|
|
440
|
+
maxDuration: [10, "seconds"],
|
|
441
|
+
handler: async (items: string[]) => {
|
|
442
|
+
handlerCallCount++;
|
|
443
|
+
handlerCalls.push([...items]);
|
|
444
|
+
// Simulate slow processing
|
|
445
|
+
await time.wait(100);
|
|
446
|
+
return `batch-${handlerCallCount}`;
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const app = alepha.inject(TestApp);
|
|
452
|
+
await alepha.start();
|
|
453
|
+
|
|
454
|
+
// Push 2 items to trigger first batch
|
|
455
|
+
const id1 = await app.batcher.push("A");
|
|
456
|
+
const id2 = await app.batcher.push("B");
|
|
457
|
+
|
|
458
|
+
// Wait a bit for processing to start but not complete
|
|
459
|
+
await time.wait(50);
|
|
460
|
+
|
|
461
|
+
// Push more items while first batch is processing
|
|
462
|
+
const id3 = await app.batcher.push("C");
|
|
463
|
+
const id4 = await app.batcher.push("D");
|
|
464
|
+
|
|
465
|
+
// Wait for all items to complete
|
|
466
|
+
const results = await Promise.all([
|
|
467
|
+
app.batcher.wait(id1),
|
|
468
|
+
app.batcher.wait(id2),
|
|
469
|
+
app.batcher.wait(id3),
|
|
470
|
+
app.batcher.wait(id4),
|
|
471
|
+
]);
|
|
472
|
+
|
|
473
|
+
// Should have 2 batches
|
|
474
|
+
expect(handlerCallCount).toBe(2);
|
|
475
|
+
expect(handlerCalls[0]).toEqual(["A", "B"]);
|
|
476
|
+
expect(handlerCalls[1]).toEqual(["C", "D"]);
|
|
477
|
+
expect(results).toEqual(["batch-1", "batch-1", "batch-2", "batch-2"]);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
test("should handle concurrent pushes to same partition during flush", async () => {
|
|
481
|
+
let processingBatch1 = false;
|
|
482
|
+
|
|
483
|
+
class TestApp {
|
|
484
|
+
batcher = $batch({
|
|
485
|
+
schema: t.object({ id: t.number(), value: t.text() }),
|
|
486
|
+
maxSize: 2,
|
|
487
|
+
partitionBy: (item) => `p-${item.id}`,
|
|
488
|
+
handler: async (items) => {
|
|
489
|
+
if (!processingBatch1) {
|
|
490
|
+
processingBatch1 = true;
|
|
491
|
+
// Simulate slow processing for first batch only
|
|
492
|
+
await time.wait(100);
|
|
493
|
+
}
|
|
494
|
+
return items.map((item) => ({ ...item, processed: true }));
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const app = alepha.inject(TestApp);
|
|
500
|
+
await alepha.start();
|
|
501
|
+
|
|
502
|
+
// Push 2 items to partition p-1 to trigger flush
|
|
503
|
+
const id1 = await app.batcher.push({ id: 1, value: "A" });
|
|
504
|
+
const id2 = await app.batcher.push({ id: 1, value: "B" });
|
|
505
|
+
|
|
506
|
+
// Wait for processing to start
|
|
507
|
+
await time.wait(50);
|
|
508
|
+
|
|
509
|
+
// Push more items to same partition while processing
|
|
510
|
+
const id3 = await app.batcher.push({ id: 1, value: "C" });
|
|
511
|
+
const id4 = await app.batcher.push({ id: 1, value: "D" });
|
|
512
|
+
|
|
513
|
+
await Promise.all([
|
|
514
|
+
app.batcher.wait(id1),
|
|
515
|
+
app.batcher.wait(id2),
|
|
516
|
+
app.batcher.wait(id3),
|
|
517
|
+
app.batcher.wait(id4),
|
|
518
|
+
]);
|
|
519
|
+
|
|
520
|
+
// All items should be processed successfully
|
|
521
|
+
expect(true).toBe(true); // If we got here without errors, test passes
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test("should use default values for maxSize, concurrency, and maxDuration", async () => {
|
|
525
|
+
const mockHandler = createMockHandler();
|
|
526
|
+
|
|
527
|
+
class TestApp {
|
|
528
|
+
batcher = $batch({
|
|
529
|
+
schema: t.text(),
|
|
530
|
+
// Not providing maxSize, concurrency, or maxDuration
|
|
531
|
+
handler: mockHandler,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const app = alepha.inject(TestApp);
|
|
536
|
+
await alepha.start();
|
|
537
|
+
|
|
538
|
+
// Test default maxSize (10)
|
|
539
|
+
for (let i = 0; i < 9; i++) {
|
|
540
|
+
app.batcher.push(`item-${i}`);
|
|
541
|
+
}
|
|
542
|
+
expect(mockHandler).not.toHaveBeenCalled();
|
|
543
|
+
|
|
544
|
+
app.batcher.push("item-9"); // 10th item should trigger flush
|
|
545
|
+
|
|
546
|
+
await vi.waitFor(() => {
|
|
547
|
+
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
548
|
+
});
|
|
549
|
+
expect(mockHandler).toHaveBeenCalledWith([
|
|
550
|
+
"item-0",
|
|
551
|
+
"item-1",
|
|
552
|
+
"item-2",
|
|
553
|
+
"item-3",
|
|
554
|
+
"item-4",
|
|
555
|
+
"item-5",
|
|
556
|
+
"item-6",
|
|
557
|
+
"item-7",
|
|
558
|
+
"item-8",
|
|
559
|
+
"item-9",
|
|
560
|
+
]);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test("should use default maxDuration (1 second)", async () => {
|
|
564
|
+
const mockHandler = createMockHandler();
|
|
565
|
+
|
|
566
|
+
class TestApp {
|
|
567
|
+
batcher = $batch({
|
|
568
|
+
schema: t.text(),
|
|
569
|
+
// Not providing maxDuration, should default to 1 second
|
|
570
|
+
handler: mockHandler,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const app = alepha.inject(TestApp);
|
|
575
|
+
await alepha.start();
|
|
576
|
+
|
|
577
|
+
app.batcher.push("A");
|
|
578
|
+
expect(mockHandler).not.toHaveBeenCalled();
|
|
579
|
+
|
|
580
|
+
// Wait for default timeout (1 second)
|
|
581
|
+
await time.travel([1.1, "seconds"]);
|
|
582
|
+
|
|
583
|
+
await vi.waitFor(() => {
|
|
584
|
+
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
585
|
+
});
|
|
586
|
+
expect(mockHandler).toHaveBeenCalledWith(["A"]);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
test("should use default concurrency (1)", async () => {
|
|
590
|
+
let activeHandlers = 0;
|
|
591
|
+
let maxActiveHandlers = 0;
|
|
592
|
+
|
|
593
|
+
const slowHandler = vi.fn(async (items: any[]) => {
|
|
594
|
+
activeHandlers++;
|
|
595
|
+
maxActiveHandlers = Math.max(maxActiveHandlers, activeHandlers);
|
|
596
|
+
await time.wait(100);
|
|
597
|
+
activeHandlers--;
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
class TestApp {
|
|
601
|
+
batcher = $batch({
|
|
602
|
+
schema: t.text(),
|
|
603
|
+
maxSize: 1,
|
|
604
|
+
// Not providing concurrency, should default to 1
|
|
605
|
+
handler: slowHandler,
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const app = alepha.inject(TestApp);
|
|
610
|
+
await alepha.start();
|
|
611
|
+
|
|
612
|
+
// Push 3 items to trigger 3 batches
|
|
613
|
+
const ids = await Promise.all([
|
|
614
|
+
app.batcher.push("A"),
|
|
615
|
+
app.batcher.push("B"),
|
|
616
|
+
app.batcher.push("C"),
|
|
617
|
+
]);
|
|
618
|
+
|
|
619
|
+
await Promise.all(ids.map((id) => app.batcher.wait(id)));
|
|
620
|
+
|
|
621
|
+
expect(slowHandler).toHaveBeenCalledTimes(3);
|
|
622
|
+
expect(maxActiveHandlers).toBe(1); // Should never exceed default concurrency of 1
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
test("should track item status through lifecycle", async () => {
|
|
626
|
+
class TestApp {
|
|
627
|
+
batcher = $batch({
|
|
628
|
+
schema: t.text(),
|
|
629
|
+
maxSize: 10,
|
|
630
|
+
maxDuration: [100, "milliseconds"],
|
|
631
|
+
handler: async (items: string[]) => {
|
|
632
|
+
await time.wait(50);
|
|
633
|
+
return items.map((item) => `processed-${item}`);
|
|
634
|
+
},
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const app = alepha.inject(TestApp);
|
|
639
|
+
await alepha.start();
|
|
640
|
+
|
|
641
|
+
// Push an item and verify it gets an ID
|
|
642
|
+
const id = await app.batcher.push("test-item");
|
|
643
|
+
expect(typeof id).toBe("string");
|
|
644
|
+
|
|
645
|
+
// Status should be pending initially
|
|
646
|
+
let status = app.batcher.status(id);
|
|
647
|
+
expect(status?.status).toMatch(/pending/);
|
|
648
|
+
|
|
649
|
+
// Wait for processing to complete
|
|
650
|
+
const result = await app.batcher.wait(id);
|
|
651
|
+
expect(result).toEqual(["processed-test-item"]);
|
|
652
|
+
|
|
653
|
+
// Status should now be completed
|
|
654
|
+
status = app.batcher.status(id);
|
|
655
|
+
expect(status?.status).toBe("completed");
|
|
656
|
+
if (status?.status === "completed") {
|
|
657
|
+
expect(status.result).toEqual(["processed-test-item"]);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Calling wait again should return the cached result immediately
|
|
661
|
+
const result2 = await app.batcher.wait(id);
|
|
662
|
+
expect(result2).toEqual(["processed-test-item"]);
|
|
663
|
+
|
|
664
|
+
// Test non-existent ID
|
|
665
|
+
const nonExistentStatus = app.batcher.status("non-existent-id");
|
|
666
|
+
expect(nonExistentStatus).toBeUndefined();
|
|
667
|
+
|
|
668
|
+
await expect(app.batcher.wait("non-existent-id")).rejects.toThrow(
|
|
669
|
+
"Item with id 'non-existent-id' not found",
|
|
670
|
+
);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
test("should restart timeout for items that arrive during flush (maxDuration bug)", async () => {
|
|
674
|
+
const mockHandler = vi.fn(async (items: string[]) => {
|
|
675
|
+
// Simulate slow processing
|
|
676
|
+
await time.wait(50);
|
|
677
|
+
return items.map((item) => `processed-${item}`);
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
class TestApp {
|
|
681
|
+
batcher = $batch({
|
|
682
|
+
schema: t.text(),
|
|
683
|
+
maxSize: 10,
|
|
684
|
+
maxDuration: [100, "milliseconds"],
|
|
685
|
+
handler: mockHandler,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const app = alepha.inject(TestApp);
|
|
690
|
+
await alepha.start();
|
|
691
|
+
|
|
692
|
+
// Push first item, timeout starts
|
|
693
|
+
const id1 = await app.batcher.push("A");
|
|
694
|
+
|
|
695
|
+
// Wait for timeout to fire and flush to start
|
|
696
|
+
await time.travel([100, "milliseconds"]);
|
|
697
|
+
|
|
698
|
+
// Handler is now processing (takes 50ms)
|
|
699
|
+
// At T=25ms into processing, push another item
|
|
700
|
+
await time.wait(25);
|
|
701
|
+
const id2 = await app.batcher.push("B");
|
|
702
|
+
|
|
703
|
+
// Wait for first flush to complete
|
|
704
|
+
await time.wait(30); // Total 55ms, first flush completes at 50ms
|
|
705
|
+
|
|
706
|
+
// At this point:
|
|
707
|
+
// - First batch ["A"] has been processed
|
|
708
|
+
// - Item "B" is waiting in the partition
|
|
709
|
+
// - BUG: No timeout exists for "B" because it was pushed during flush
|
|
710
|
+
|
|
711
|
+
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
712
|
+
expect(mockHandler).toHaveBeenCalledWith(["A"]);
|
|
713
|
+
|
|
714
|
+
// Item "B" should flush after maxDuration (100ms)
|
|
715
|
+
await time.travel([110, "milliseconds"]); // More than enough time
|
|
716
|
+
|
|
717
|
+
// Expected: 2 calls (first batch "A", second batch "B")
|
|
718
|
+
// Without fix: 1 call (only "A" was processed, "B" is stuck)
|
|
719
|
+
await vi.waitFor(
|
|
720
|
+
() => {
|
|
721
|
+
expect(mockHandler).toHaveBeenCalledTimes(2);
|
|
722
|
+
},
|
|
723
|
+
{ timeout: 200 },
|
|
724
|
+
);
|
|
725
|
+
expect(mockHandler).toHaveBeenNthCalledWith(2, ["B"]);
|
|
726
|
+
|
|
727
|
+
// Verify both items completed successfully
|
|
728
|
+
const result1 = await app.batcher.wait(id1);
|
|
729
|
+
const result2 = await app.batcher.wait(id2);
|
|
730
|
+
expect(result1).toEqual(["processed-A"]);
|
|
731
|
+
expect(result2).toEqual(["processed-B"]);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
test("should return unique IDs for each push", async () => {
|
|
735
|
+
const mockHandler = createMockHandler();
|
|
736
|
+
class TestApp {
|
|
737
|
+
batcher = $batch({
|
|
738
|
+
schema: t.text(),
|
|
739
|
+
maxSize: 10,
|
|
740
|
+
handler: mockHandler,
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const app = alepha.inject(TestApp);
|
|
745
|
+
await alepha.start();
|
|
746
|
+
|
|
747
|
+
const id1 = await app.batcher.push("A");
|
|
748
|
+
const id2 = await app.batcher.push("B");
|
|
749
|
+
const id3 = await app.batcher.push("C");
|
|
750
|
+
|
|
751
|
+
expect(id1).not.toBe(id2);
|
|
752
|
+
expect(id2).not.toBe(id3);
|
|
753
|
+
expect(id1).not.toBe(id3);
|
|
754
|
+
|
|
755
|
+
// All should be valid UUIDs (rough check)
|
|
756
|
+
expect(id1).toMatch(
|
|
757
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
758
|
+
);
|
|
759
|
+
expect(id2).toMatch(
|
|
760
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
761
|
+
);
|
|
762
|
+
expect(id3).toMatch(
|
|
763
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
764
|
+
);
|
|
765
|
+
});
|
|
766
|
+
});
|