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
package/dist/batch/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["partitionKey: string","itemState: BatchItemState<TItem, TResponse>","promises: Promise<void>[]","itemsToProcess: TItem[]","result: any"],"sources":["../../src/batch/providers/BatchProvider.ts","../../src/batch/primitives/$batch.ts","../../src/batch/index.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { $inject, type Alepha } from \"alepha\";\nimport { DateTimeProvider, type DurationLike } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { type RetryBackoffOptions, RetryProvider } from \"alepha/retry\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface BatchOptions<TItem, TResponse = any> {\n /**\n * The batch processing handler function that processes arrays of validated items.\n */\n handler: (items: TItem[]) => TResponse;\n\n /**\n * Maximum number of items to collect before automatically flushing the batch.\n *\n * @default 10\n */\n maxSize?: number;\n\n /**\n * Maximum number of items that can be queued in a single partition.\n * If exceeded, push() will throw an error.\n */\n maxQueueSize?: number;\n\n /**\n * Maximum time to wait before flushing a batch, even if it hasn't reached maxSize.\n *\n * @default [1, \"second\"]\n */\n maxDuration?: DurationLike;\n\n /**\n * Function to determine partition keys for grouping items into separate batches.\n */\n partitionBy?: (item: TItem) => string;\n\n /**\n * Maximum number of batch handlers that can execute simultaneously.\n *\n * @default 1\n */\n concurrency?: number;\n\n /**\n * Retry configuration for failed batch processing operations.\n */\n retry?: {\n /**\n * The maximum number of attempts.\n *\n * @default 3\n */\n max?: number;\n\n /**\n * The backoff strategy for delays between retries.\n * Can be a fixed number (in ms) or a configuration object for exponential backoff.\n *\n * @default { initial: 200, factor: 2, jitter: true }\n */\n backoff?: number | RetryBackoffOptions;\n\n /**\n * An overall time limit for all retry attempts combined.\n *\n * e.g., `[5, 'seconds']`\n */\n maxDuration?: DurationLike;\n\n /**\n * A function that determines if a retry should be attempted based on the error.\n *\n * @default (error) => true (retries on any error)\n */\n when?: (error: Error) => boolean;\n\n /**\n * A custom callback for when a retry attempt fails.\n * This is called before the delay.\n */\n onError?: (error: Error, attempt: number) => void;\n };\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type BatchItemStatus = \"pending\" | \"processing\" | \"completed\" | \"failed\";\n\nexport interface BatchItemState<TItem, TResponse> {\n id: string;\n item: TItem;\n partitionKey: string;\n status: BatchItemStatus;\n result?: TResponse;\n error?: Error;\n promise?: Promise<TResponse>;\n resolve?: (value: TResponse) => void;\n reject?: (error: Error) => void;\n}\n\nexport interface PartitionState {\n itemIds: string[];\n timeout?: { clear: () => void };\n flushing: boolean;\n}\n\n/**\n * Context object that holds all state for a batch processor instance.\n */\nexport interface BatchContext<TItem, TResponse> {\n options: BatchOptions<TItem, TResponse>;\n itemStates: Map<string, BatchItemState<TItem, TResponse>>;\n partitions: Map<string, PartitionState>;\n activeHandlers: PromiseWithResolvers<void>[];\n isShuttingDown: boolean;\n isReady: boolean;\n alepha: Alepha;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Service for batch processing operations.\n * Provides methods to manage batches of items with automatic flushing based on size or time.\n */\nexport class BatchProvider {\n protected readonly log = $logger();\n protected readonly dateTime = $inject(DateTimeProvider);\n protected readonly retryProvider = $inject(RetryProvider);\n\n /**\n * Creates a new batch context with the given options.\n */\n createContext<TItem, TResponse>(\n alepha: Alepha,\n options: BatchOptions<TItem, TResponse>,\n ): BatchContext<TItem, TResponse> {\n return {\n options,\n itemStates: new Map(),\n partitions: new Map(),\n activeHandlers: [],\n isShuttingDown: false,\n isReady: false,\n alepha,\n };\n }\n\n /**\n * Get the effective maxSize for a context.\n */\n protected getMaxSize<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n ): number {\n return context.options.maxSize ?? 10;\n }\n\n /**\n * Get the effective concurrency for a context.\n */\n protected getConcurrency<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n ): number {\n return context.options.concurrency ?? 1;\n }\n\n /**\n * Get the effective maxDuration for a context.\n */\n protected getMaxDuration<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n ): DurationLike {\n return context.options.maxDuration ?? [1, \"second\"];\n }\n\n /**\n * Pushes an item into the batch and returns immediately with a unique ID.\n * The item will be processed asynchronously with other items when the batch is flushed.\n * Use wait(id) to get the processing result.\n *\n * @throws Error if maxQueueSize is exceeded\n */\n push<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n item: TItem,\n ): string {\n // 1. Generate unique ID\n const id = randomUUID();\n\n // 2. Determine the partition key (with error handling)\n let partitionKey: string;\n try {\n partitionKey = context.options.partitionBy\n ? context.options.partitionBy(item)\n : \"default\";\n } catch (error) {\n this.log.warn(\n \"partitionBy function threw an error, using 'default' partition\",\n { error },\n );\n partitionKey = \"default\";\n }\n\n // 3. Create item state\n const itemState: BatchItemState<TItem, TResponse> = {\n id,\n item,\n partitionKey,\n status: \"pending\",\n };\n\n // CAUTION: Do not log.debug/info here as it may cause infinite loops if logging is batched\n\n context.itemStates.set(id, itemState);\n\n // 4. Get or create the partition state\n if (!context.partitions.has(partitionKey)) {\n context.partitions.set(partitionKey, {\n itemIds: [],\n flushing: false,\n });\n }\n const partition = context.partitions.get(partitionKey)!;\n\n // 5. Check maxQueueSize before adding\n if (\n context.options.maxQueueSize !== undefined &&\n partition.itemIds.length >= context.options.maxQueueSize\n ) {\n throw new Error(\n `Batch queue size exceeded for partition '${partitionKey}' (max: ${context.options.maxQueueSize})`,\n );\n }\n\n // 6. Add item ID to partition\n partition.itemIds.push(id);\n\n const maxSize = this.getMaxSize(context);\n const maxDuration = this.getMaxDuration(context);\n\n // 7. Only start processing if the app is ready (after \"ready\" hook)\n // During startup, items are just buffered in memory\n if (context.isReady) {\n // Check if the batch is full\n if (partition.itemIds.length >= maxSize) {\n this.log.trace(\n `Batch partition '${partitionKey}' is full, flushing...`,\n );\n this.flushPartition(context, partitionKey).catch((error) =>\n this.log.error(\n `Failed to flush batch partition '${partitionKey}' on max size`,\n error,\n ),\n );\n } else if (!partition.timeout && !partition.flushing) {\n // 8. Start the timeout if it's not already running for this partition and not currently flushing\n partition.timeout = this.dateTime.createTimeout(() => {\n this.log.trace(\n `Batch partition '${partitionKey}' timed out, flushing...`,\n );\n this.flushPartition(context, partitionKey).catch((error) =>\n this.log.error(\n `Failed to flush batch partition '${partitionKey}' on timeout`,\n error,\n ),\n );\n }, maxDuration);\n }\n } else {\n // Not ready yet - just buffer items, no size checks or timeouts\n this.log.trace(\n `Buffering item in partition '${partitionKey}' (app not ready yet, ${partition.itemIds.length} items buffered)`,\n );\n }\n\n // 9. Return ID immediately\n return id;\n }\n\n /**\n * Wait for a specific item to be processed and get its result.\n * @param id The item ID returned from push()\n * @returns The processing result\n * @throws If the item doesn't exist or processing failed\n */\n async wait<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n id: string,\n ): Promise<TResponse> {\n const itemState = context.itemStates.get(id);\n if (!itemState) {\n throw new Error(`Item with id '${id}' not found`);\n }\n\n // If already completed or failed, return immediately\n if (itemState.status === \"completed\") {\n return itemState.result!;\n }\n if (itemState.status === \"failed\") {\n throw itemState.error!;\n }\n\n // Create promise on-demand if not already created\n if (!itemState.promise) {\n itemState.promise = new Promise<TResponse>((resolve, reject) => {\n itemState.resolve = resolve;\n itemState.reject = reject;\n });\n }\n\n return itemState.promise;\n }\n\n /**\n * Get the current status of an item.\n * @param id The item ID returned from push()\n * @returns Status information or undefined if item doesn't exist\n */\n status<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n id: string,\n ):\n | { status: \"pending\" | \"processing\" }\n | { status: \"completed\"; result: TResponse }\n | { status: \"failed\"; error: Error }\n | undefined {\n const itemState = context.itemStates.get(id);\n if (!itemState) {\n return undefined;\n }\n\n if (itemState.status === \"completed\") {\n return { status: \"completed\", result: itemState.result! };\n }\n if (itemState.status === \"failed\") {\n return { status: \"failed\", error: itemState.error! };\n }\n return { status: itemState.status };\n }\n\n /**\n * Clears completed and failed items from the context to free memory.\n * Returns the number of items cleared.\n *\n * @param context The batch context\n * @param status Optional: only clear items with this specific status ('completed' or 'failed')\n * @returns The number of items cleared\n */\n clearCompleted<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n status?: \"completed\" | \"failed\",\n ): number {\n let count = 0;\n for (const [id, state] of context.itemStates) {\n if (status) {\n if (state.status === status) {\n context.itemStates.delete(id);\n count++;\n }\n } else if (state.status === \"completed\" || state.status === \"failed\") {\n context.itemStates.delete(id);\n count++;\n }\n }\n return count;\n }\n\n /**\n * Flush all partitions or a specific partition.\n */\n async flush<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n partitionKey?: string,\n ): Promise<void> {\n const promises: Promise<void>[] = [];\n if (partitionKey) {\n if (context.partitions.has(partitionKey)) {\n promises.push(this.flushPartition(context, partitionKey));\n }\n } else {\n for (const key of context.partitions.keys()) {\n promises.push(this.flushPartition(context, key));\n }\n }\n await Promise.all(promises);\n }\n\n /**\n * Flush a specific partition.\n */\n protected async flushPartition<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n partitionKey: string,\n limit?: number,\n ): Promise<void> {\n const partition = context.partitions.get(partitionKey);\n if (!partition || partition.itemIds.length === 0) {\n context.partitions.delete(partitionKey);\n return;\n }\n\n // Clear the timeout and grab the item IDs (up to limit if specified)\n partition.timeout?.clear();\n partition.timeout = undefined;\n const itemsToTake =\n limit !== undefined\n ? Math.min(limit, partition.itemIds.length)\n : partition.itemIds.length;\n const itemIdsToProcess = partition.itemIds.splice(0, itemsToTake);\n\n // Mark partition as flushing to prevent race conditions\n partition.flushing = true;\n\n // Get the items and mark them as processing\n const itemsToProcess: TItem[] = [];\n for (const id of itemIdsToProcess) {\n const itemState = context.itemStates.get(id);\n if (itemState) {\n itemState.status = \"processing\";\n itemsToProcess.push(itemState.item);\n }\n }\n\n const concurrency = this.getConcurrency(context);\n const maxDuration = this.getMaxDuration(context);\n\n // Wait until there's a free slot (if at concurrency limit)\n while (context.activeHandlers.length >= concurrency) {\n this.log.trace(\n `Batch handler is at concurrency limit, waiting for a slot...`,\n );\n // Wait for any single handler to complete, not all of them\n await Promise.race(context.activeHandlers.map((it) => it.promise));\n }\n\n const promise = Promise.withResolvers<void>();\n context.activeHandlers.push(promise);\n let result: any;\n try {\n result = await context.alepha.context.run(() =>\n // during shutdown, call handler directly to avoid retry cancellation\n context.isShuttingDown\n ? context.options.handler(itemsToProcess)\n : this.retryProvider.retry(\n {\n ...context.options.retry,\n handler: context.options.handler,\n },\n itemsToProcess,\n ),\n );\n\n // Mark all items as completed and resolve their promises\n for (const id of itemIdsToProcess) {\n const itemState = context.itemStates.get(id);\n if (itemState) {\n itemState.status = \"completed\";\n itemState.result = result;\n // Only resolve if someone is waiting\n itemState.resolve?.(result);\n }\n }\n } catch (error) {\n this.log.error(`Batch handler failed`, error);\n\n // Mark all items as failed and reject their promises\n for (const id of itemIdsToProcess) {\n const itemState = context.itemStates.get(id);\n if (itemState) {\n itemState.status = \"failed\";\n itemState.error = error as Error;\n // Only reject if someone is waiting (promise was created)\n itemState.reject?.(error as Error);\n }\n }\n } finally {\n promise.resolve();\n context.activeHandlers = context.activeHandlers.filter(\n (it) => it !== promise,\n );\n\n // Only delete partition if no new items arrived during processing\n const currentPartition = context.partitions.get(partitionKey);\n if (currentPartition?.flushing && currentPartition.itemIds.length === 0) {\n context.partitions.delete(partitionKey);\n } else if (currentPartition) {\n // Reset flushing flag if partition still exists with items\n currentPartition.flushing = false;\n\n // Restart timeout for items that arrived during flush\n if (currentPartition.itemIds.length > 0 && !currentPartition.timeout) {\n currentPartition.timeout = this.dateTime.createTimeout(() => {\n this.log.trace(\n `Batch partition '${partitionKey}' timed out, flushing...`,\n );\n this.flushPartition(context, partitionKey).catch((error) =>\n this.log.error(\n `Failed to flush batch partition '${partitionKey}' on timeout`,\n error,\n ),\n );\n }, maxDuration);\n }\n }\n }\n }\n\n /**\n * Mark the context as ready and start processing buffered items.\n * Called after the \"ready\" hook.\n */\n async markReady<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n ): Promise<void> {\n this.log.debug(\n \"Batch processor is now ready, starting to process buffered items...\",\n );\n context.isReady = true;\n await this.startProcessing(context);\n }\n\n /**\n * Mark the context as shutting down and flush all remaining items.\n */\n async shutdown<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n ): Promise<void> {\n this.log.debug(\"Flushing all remaining batch partitions on shutdown...\");\n context.isShuttingDown = true;\n await this.flush(context);\n this.log.debug(\"All batch partitions flushed\");\n }\n\n /**\n * Called after the \"ready\" hook to start processing buffered items that were\n * pushed during startup. This checks all partitions and starts timeouts/flushes\n * for items that were accumulated before the app was ready.\n */\n protected async startProcessing<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n ): Promise<void> {\n const maxSize = this.getMaxSize(context);\n const maxDuration = this.getMaxDuration(context);\n\n for (const [partitionKey, partition] of context.partitions.entries()) {\n if (partition.itemIds.length === 0) {\n continue;\n }\n\n this.log.trace(\n `Starting processing for partition '${partitionKey}' with ${partition.itemIds.length} buffered items`,\n );\n\n // Flush batches of maxSize while we have items >= maxSize\n while (partition.itemIds.length >= maxSize) {\n this.log.trace(\n `Partition '${partitionKey}' has ${partition.itemIds.length} items, flushing batch of ${maxSize}...`,\n );\n await this.flushPartition(context, partitionKey, maxSize);\n }\n\n // After flushing full batches, start timeout for any remaining items\n if (\n partition.itemIds.length > 0 &&\n !partition.timeout &&\n !partition.flushing\n ) {\n this.log.trace(\n `Starting timeout for partition '${partitionKey}' with ${partition.itemIds.length} remaining items`,\n );\n partition.timeout = this.dateTime.createTimeout(() => {\n this.log.trace(\n `Batch partition '${partitionKey}' timed out, flushing...`,\n );\n this.flushPartition(context, partitionKey).catch((error) =>\n this.log.error(\n `Failed to flush partition '${partitionKey}' on timeout after startup`,\n error,\n ),\n );\n }, maxDuration);\n }\n }\n }\n}\n","import {\n $hook,\n $inject,\n createPrimitive,\n KIND,\n Primitive,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport type { DurationLike } from \"alepha/datetime\";\nimport type { RetryPrimitiveOptions } from \"alepha/retry\";\nimport {\n type BatchContext,\n type BatchItemState,\n type BatchItemStatus,\n BatchProvider,\n} from \"../providers/BatchProvider.ts\";\n\n/**\n * Creates a batch processing primitive for efficient grouping and processing of multiple operations.\n */\nexport const $batch = <TItem extends TSchema, TResponse>(\n options: BatchPrimitiveOptions<TItem, TResponse>,\n): BatchPrimitive<TItem, TResponse> =>\n createPrimitive(BatchPrimitive<TItem, TResponse>, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface BatchPrimitiveOptions<TItem extends TSchema, TResponse = any> {\n /**\n * TypeBox schema for validating each item added to the batch.\n */\n schema: TItem;\n\n /**\n * The batch processing handler function that processes arrays of validated items.\n */\n handler: (items: Static<TItem>[]) => TResponse;\n\n /**\n * Maximum number of items to collect before automatically flushing the batch.\n */\n maxSize?: number;\n\n /**\n * Maximum number of items that can be queued in a single partition.\n * If exceeded, push() will throw an error.\n */\n maxQueueSize?: number;\n\n /**\n * Maximum time to wait before flushing a batch, even if it hasn't reached maxSize.\n */\n maxDuration?: DurationLike;\n\n /**\n * Function to determine partition keys for grouping items into separate batches.\n */\n partitionBy?: (item: Static<TItem>) => string;\n\n /**\n * Maximum number of batch handlers that can execute simultaneously.\n */\n concurrency?: number;\n\n /**\n * Retry configuration for failed batch processing operations.\n */\n retry?: Omit<RetryPrimitiveOptions<() => Array<Static<TItem>>>, \"handler\">;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type { BatchItemState, BatchItemStatus };\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class BatchPrimitive<\n TItem extends TSchema,\n TResponse = any,\n> extends Primitive<BatchPrimitiveOptions<TItem, TResponse>> {\n protected readonly batchProvider = $inject(BatchProvider);\n protected readonly context: BatchContext<Static<TItem>, TResponse>;\n\n constructor(\n ...args: ConstructorParameters<\n typeof Primitive<BatchPrimitiveOptions<TItem, TResponse>>\n >\n ) {\n super(...args);\n this.context = this.batchProvider.createContext(this.alepha, {\n handler: this.options.handler,\n maxSize: this.options.maxSize,\n maxQueueSize: this.options.maxQueueSize,\n maxDuration: this.options.maxDuration,\n partitionBy: this.options.partitionBy,\n concurrency: this.options.concurrency,\n retry: this.options.retry,\n });\n }\n\n /**\n * Pushes an item into the batch and returns immediately with a unique ID.\n * The item will be processed asynchronously with other items when the batch is flushed.\n * Use wait(id) to get the processing result.\n */\n public async push(item: Static<TItem>): Promise<string> {\n // Validate the item against the schema\n const validatedItem = this.alepha.codec.validate(this.options.schema, item);\n return this.batchProvider.push(this.context, validatedItem);\n }\n\n /**\n * Wait for a specific item to be processed and get its result.\n * @param id The item ID returned from push()\n * @returns The processing result\n * @throws If the item doesn't exist or processing failed\n */\n public async wait(id: string): Promise<TResponse> {\n return this.batchProvider.wait(this.context, id);\n }\n\n /**\n * Get the current status of an item.\n * @param id The item ID returned from push()\n * @returns Status information or undefined if item doesn't exist\n */\n public status(\n id: string,\n ):\n | { status: \"pending\" | \"processing\" }\n | { status: \"completed\"; result: TResponse }\n | { status: \"failed\"; error: Error }\n | undefined {\n return this.batchProvider.status(this.context, id);\n }\n\n /**\n * Flush all partitions or a specific partition.\n */\n public async flush(partitionKey?: string): Promise<void> {\n return this.batchProvider.flush(this.context, partitionKey);\n }\n\n /**\n * Clears completed and failed items from memory.\n * Call this periodically in long-running applications to prevent memory leaks.\n *\n * @param status Optional: only clear items with this specific status ('completed' or 'failed')\n * @returns The number of items cleared\n */\n public clearCompleted(status?: \"completed\" | \"failed\"): number {\n return this.batchProvider.clearCompleted(this.context, status);\n }\n\n protected readonly onReady = $hook({\n on: \"ready\",\n handler: async () => {\n await this.batchProvider.markReady(this.context);\n },\n });\n\n protected readonly dispose = $hook({\n on: \"stop\",\n priority: \"first\",\n handler: async () => {\n await this.batchProvider.shutdown(this.context);\n },\n });\n}\n\n$batch[KIND] = BatchPrimitive;\n","import { $module } from \"alepha\";\nimport { $batch } from \"./primitives/$batch.ts\";\nimport { BatchProvider } from \"./providers/BatchProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$batch.ts\";\nexport * from \"./providers/BatchProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * This module allows you to group multiple asynchronous operations into a single \"batch,\" which is then processed together.\n * This is an essential pattern for improving performance, reducing I/O, and interacting efficiently with rate-limited APIs or databases.\n *\n * ```ts\n * import { Alepha, $hook, run, t } from \"alepha\";\n * import { $batch } from \"alepha/batch\";\n *\n * class LoggingService {\n * // define the batch processor\n * logBatch = $batch({\n * schema: t.text(),\n * maxSize: 10,\n * maxDuration: [5, \"seconds\"],\n * handler: async (items) => {\n * console.log(`[BATCH LOG] Processing ${items.length} events:`, items);\n * },\n * });\n *\n * // example of how to use it\n * onReady = $hook({\n * on: \"ready\",\n * handler: async () => {\n * // push() returns an ID immediately\n * const id1 = await this.logBatch.push(\"Application started.\");\n * const id2 = await this.logBatch.push(\"User authenticated.\");\n *\n * // optionally wait for processing to complete\n * await this.logBatch.wait(id1);\n *\n * // or check the status\n * const status = this.logBatch.status(id2);\n * console.log(status?.status); // \"pending\" | \"processing\" | \"completed\" | \"failed\"\n * },\n * });\n * }\n * ```\n *\n * @see {@link $batch}\n * @see {@link BatchProvider}\n * @module alepha.batch\n */\nexport const AlephaBatch = $module({\n name: \"alepha.batch\",\n primitives: [$batch],\n services: [BatchProvider],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/batch/providers/BatchProvider.ts","../../src/batch/primitives/$batch.ts","../../src/batch/index.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { $inject, type Alepha } from \"alepha\";\nimport { DateTimeProvider, type DurationLike } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { type RetryBackoffOptions, RetryProvider } from \"alepha/retry\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface BatchOptions<TItem, TResponse = any> {\n /**\n * The batch processing handler function that processes arrays of validated items.\n */\n handler: (items: TItem[]) => TResponse;\n\n /**\n * Maximum number of items to collect before automatically flushing the batch.\n *\n * @default 10\n */\n maxSize?: number;\n\n /**\n * Maximum number of items that can be queued in a single partition.\n * If exceeded, push() will throw an error.\n */\n maxQueueSize?: number;\n\n /**\n * Maximum time to wait before flushing a batch, even if it hasn't reached maxSize.\n *\n * @default [1, \"second\"]\n */\n maxDuration?: DurationLike;\n\n /**\n * Function to determine partition keys for grouping items into separate batches.\n */\n partitionBy?: (item: TItem) => string;\n\n /**\n * Maximum number of batch handlers that can execute simultaneously.\n *\n * @default 1\n */\n concurrency?: number;\n\n /**\n * Retry configuration for failed batch processing operations.\n */\n retry?: {\n /**\n * The maximum number of attempts.\n *\n * @default 3\n */\n max?: number;\n\n /**\n * The backoff strategy for delays between retries.\n * Can be a fixed number (in ms) or a configuration object for exponential backoff.\n *\n * @default { initial: 200, factor: 2, jitter: true }\n */\n backoff?: number | RetryBackoffOptions;\n\n /**\n * An overall time limit for all retry attempts combined.\n *\n * e.g., `[5, 'seconds']`\n */\n maxDuration?: DurationLike;\n\n /**\n * A function that determines if a retry should be attempted based on the error.\n *\n * @default (error) => true (retries on any error)\n */\n when?: (error: Error) => boolean;\n\n /**\n * A custom callback for when a retry attempt fails.\n * This is called before the delay.\n */\n onError?: (error: Error, attempt: number) => void;\n };\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type BatchItemStatus = \"pending\" | \"processing\" | \"completed\" | \"failed\";\n\nexport interface BatchItemState<TItem, TResponse> {\n id: string;\n item: TItem;\n partitionKey: string;\n status: BatchItemStatus;\n result?: TResponse;\n error?: Error;\n promise?: Promise<TResponse>;\n resolve?: (value: TResponse) => void;\n reject?: (error: Error) => void;\n}\n\nexport interface PartitionState {\n itemIds: string[];\n timeout?: { clear: () => void };\n flushing: boolean;\n}\n\n/**\n * Context object that holds all state for a batch processor instance.\n */\nexport interface BatchContext<TItem, TResponse> {\n options: BatchOptions<TItem, TResponse>;\n itemStates: Map<string, BatchItemState<TItem, TResponse>>;\n partitions: Map<string, PartitionState>;\n activeHandlers: PromiseWithResolvers<void>[];\n isShuttingDown: boolean;\n isReady: boolean;\n alepha: Alepha;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Service for batch processing operations.\n * Provides methods to manage batches of items with automatic flushing based on size or time.\n */\nexport class BatchProvider {\n protected readonly log = $logger();\n protected readonly dateTime = $inject(DateTimeProvider);\n protected readonly retryProvider = $inject(RetryProvider);\n\n /**\n * Creates a new batch context with the given options.\n */\n createContext<TItem, TResponse>(\n alepha: Alepha,\n options: BatchOptions<TItem, TResponse>,\n ): BatchContext<TItem, TResponse> {\n return {\n options,\n itemStates: new Map(),\n partitions: new Map(),\n activeHandlers: [],\n isShuttingDown: false,\n isReady: false,\n alepha,\n };\n }\n\n /**\n * Get the effective maxSize for a context.\n */\n protected getMaxSize<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n ): number {\n return context.options.maxSize ?? 10;\n }\n\n /**\n * Get the effective concurrency for a context.\n */\n protected getConcurrency<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n ): number {\n return context.options.concurrency ?? 1;\n }\n\n /**\n * Get the effective maxDuration for a context.\n */\n protected getMaxDuration<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n ): DurationLike {\n return context.options.maxDuration ?? [1, \"second\"];\n }\n\n /**\n * Pushes an item into the batch and returns immediately with a unique ID.\n * The item will be processed asynchronously with other items when the batch is flushed.\n * Use wait(id) to get the processing result.\n *\n * @throws Error if maxQueueSize is exceeded\n */\n push<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n item: TItem,\n ): string {\n // 1. Generate unique ID\n const id = randomUUID();\n\n // 2. Determine the partition key (with error handling)\n let partitionKey: string;\n try {\n partitionKey = context.options.partitionBy\n ? context.options.partitionBy(item)\n : \"default\";\n } catch (error) {\n this.log.warn(\n \"partitionBy function threw an error, using 'default' partition\",\n { error },\n );\n partitionKey = \"default\";\n }\n\n // 3. Create item state\n const itemState: BatchItemState<TItem, TResponse> = {\n id,\n item,\n partitionKey,\n status: \"pending\",\n };\n\n // CAUTION: Do not log.debug/info here as it may cause infinite loops if logging is batched\n\n context.itemStates.set(id, itemState);\n\n // 4. Get or create the partition state\n if (!context.partitions.has(partitionKey)) {\n context.partitions.set(partitionKey, {\n itemIds: [],\n flushing: false,\n });\n }\n const partition = context.partitions.get(partitionKey)!;\n\n // 5. Check maxQueueSize before adding\n if (\n context.options.maxQueueSize !== undefined &&\n partition.itemIds.length >= context.options.maxQueueSize\n ) {\n throw new Error(\n `Batch queue size exceeded for partition '${partitionKey}' (max: ${context.options.maxQueueSize})`,\n );\n }\n\n // 6. Add item ID to partition\n partition.itemIds.push(id);\n\n const maxSize = this.getMaxSize(context);\n const maxDuration = this.getMaxDuration(context);\n\n // 7. Only start processing if the app is ready (after \"ready\" hook)\n // During startup, items are just buffered in memory\n if (context.isReady) {\n // Check if the batch is full\n if (partition.itemIds.length >= maxSize) {\n this.log.trace(\n `Batch partition '${partitionKey}' is full, flushing...`,\n );\n this.flushPartition(context, partitionKey).catch((error) =>\n this.log.error(\n `Failed to flush batch partition '${partitionKey}' on max size`,\n error,\n ),\n );\n } else if (!partition.timeout && !partition.flushing) {\n // 8. Start the timeout if it's not already running for this partition and not currently flushing\n partition.timeout = this.dateTime.createTimeout(() => {\n this.log.trace(\n `Batch partition '${partitionKey}' timed out, flushing...`,\n );\n this.flushPartition(context, partitionKey).catch((error) =>\n this.log.error(\n `Failed to flush batch partition '${partitionKey}' on timeout`,\n error,\n ),\n );\n }, maxDuration);\n }\n } else {\n // Not ready yet - just buffer items, no size checks or timeouts\n this.log.trace(\n `Buffering item in partition '${partitionKey}' (app not ready yet, ${partition.itemIds.length} items buffered)`,\n );\n }\n\n // 9. Return ID immediately\n return id;\n }\n\n /**\n * Wait for a specific item to be processed and get its result.\n * @param id The item ID returned from push()\n * @returns The processing result\n * @throws If the item doesn't exist or processing failed\n */\n async wait<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n id: string,\n ): Promise<TResponse> {\n const itemState = context.itemStates.get(id);\n if (!itemState) {\n throw new Error(`Item with id '${id}' not found`);\n }\n\n // If already completed or failed, return immediately\n if (itemState.status === \"completed\") {\n return itemState.result!;\n }\n if (itemState.status === \"failed\") {\n throw itemState.error!;\n }\n\n // Create promise on-demand if not already created\n if (!itemState.promise) {\n itemState.promise = new Promise<TResponse>((resolve, reject) => {\n itemState.resolve = resolve;\n itemState.reject = reject;\n });\n }\n\n return itemState.promise;\n }\n\n /**\n * Get the current status of an item.\n * @param id The item ID returned from push()\n * @returns Status information or undefined if item doesn't exist\n */\n status<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n id: string,\n ):\n | { status: \"pending\" | \"processing\" }\n | { status: \"completed\"; result: TResponse }\n | { status: \"failed\"; error: Error }\n | undefined {\n const itemState = context.itemStates.get(id);\n if (!itemState) {\n return undefined;\n }\n\n if (itemState.status === \"completed\") {\n return { status: \"completed\", result: itemState.result! };\n }\n if (itemState.status === \"failed\") {\n return { status: \"failed\", error: itemState.error! };\n }\n return { status: itemState.status };\n }\n\n /**\n * Clears completed and failed items from the context to free memory.\n * Returns the number of items cleared.\n *\n * @param context The batch context\n * @param status Optional: only clear items with this specific status ('completed' or 'failed')\n * @returns The number of items cleared\n */\n clearCompleted<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n status?: \"completed\" | \"failed\",\n ): number {\n let count = 0;\n for (const [id, state] of context.itemStates) {\n if (status) {\n if (state.status === status) {\n context.itemStates.delete(id);\n count++;\n }\n } else if (state.status === \"completed\" || state.status === \"failed\") {\n context.itemStates.delete(id);\n count++;\n }\n }\n return count;\n }\n\n /**\n * Flush all partitions or a specific partition.\n */\n async flush<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n partitionKey?: string,\n ): Promise<void> {\n const promises: Promise<void>[] = [];\n if (partitionKey) {\n if (context.partitions.has(partitionKey)) {\n promises.push(this.flushPartition(context, partitionKey));\n }\n } else {\n for (const key of context.partitions.keys()) {\n promises.push(this.flushPartition(context, key));\n }\n }\n await Promise.all(promises);\n }\n\n /**\n * Flush a specific partition.\n */\n protected async flushPartition<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n partitionKey: string,\n limit?: number,\n ): Promise<void> {\n const partition = context.partitions.get(partitionKey);\n if (!partition || partition.itemIds.length === 0) {\n context.partitions.delete(partitionKey);\n return;\n }\n\n // Clear the timeout and grab the item IDs (up to limit if specified)\n partition.timeout?.clear();\n partition.timeout = undefined;\n const itemsToTake =\n limit !== undefined\n ? Math.min(limit, partition.itemIds.length)\n : partition.itemIds.length;\n const itemIdsToProcess = partition.itemIds.splice(0, itemsToTake);\n\n // Mark partition as flushing to prevent race conditions\n partition.flushing = true;\n\n // Get the items and mark them as processing\n const itemsToProcess: TItem[] = [];\n for (const id of itemIdsToProcess) {\n const itemState = context.itemStates.get(id);\n if (itemState) {\n itemState.status = \"processing\";\n itemsToProcess.push(itemState.item);\n }\n }\n\n const concurrency = this.getConcurrency(context);\n const maxDuration = this.getMaxDuration(context);\n\n // Wait until there's a free slot (if at concurrency limit)\n while (context.activeHandlers.length >= concurrency) {\n this.log.trace(\n `Batch handler is at concurrency limit, waiting for a slot...`,\n );\n // Wait for any single handler to complete, not all of them\n await Promise.race(context.activeHandlers.map((it) => it.promise));\n }\n\n const promise = Promise.withResolvers<void>();\n context.activeHandlers.push(promise);\n let result: any;\n try {\n result = await context.alepha.context.run(() =>\n // during shutdown, call handler directly to avoid retry cancellation\n context.isShuttingDown\n ? context.options.handler(itemsToProcess)\n : this.retryProvider.retry(\n {\n ...context.options.retry,\n handler: context.options.handler,\n },\n itemsToProcess,\n ),\n );\n\n // Mark all items as completed and resolve their promises\n for (const id of itemIdsToProcess) {\n const itemState = context.itemStates.get(id);\n if (itemState) {\n itemState.status = \"completed\";\n itemState.result = result;\n // Only resolve if someone is waiting\n itemState.resolve?.(result);\n }\n }\n } catch (error) {\n this.log.error(`Batch handler failed`, error);\n\n // Mark all items as failed and reject their promises\n for (const id of itemIdsToProcess) {\n const itemState = context.itemStates.get(id);\n if (itemState) {\n itemState.status = \"failed\";\n itemState.error = error as Error;\n // Only reject if someone is waiting (promise was created)\n itemState.reject?.(error as Error);\n }\n }\n } finally {\n promise.resolve();\n context.activeHandlers = context.activeHandlers.filter(\n (it) => it !== promise,\n );\n\n // Only delete partition if no new items arrived during processing\n const currentPartition = context.partitions.get(partitionKey);\n if (currentPartition?.flushing && currentPartition.itemIds.length === 0) {\n context.partitions.delete(partitionKey);\n } else if (currentPartition) {\n // Reset flushing flag if partition still exists with items\n currentPartition.flushing = false;\n\n // Restart timeout for items that arrived during flush\n if (currentPartition.itemIds.length > 0 && !currentPartition.timeout) {\n currentPartition.timeout = this.dateTime.createTimeout(() => {\n this.log.trace(\n `Batch partition '${partitionKey}' timed out, flushing...`,\n );\n this.flushPartition(context, partitionKey).catch((error) =>\n this.log.error(\n `Failed to flush batch partition '${partitionKey}' on timeout`,\n error,\n ),\n );\n }, maxDuration);\n }\n }\n }\n }\n\n /**\n * Mark the context as ready and start processing buffered items.\n * Called after the \"ready\" hook.\n */\n async markReady<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n ): Promise<void> {\n this.log.debug(\n \"Batch processor is now ready, starting to process buffered items...\",\n );\n context.isReady = true;\n await this.startProcessing(context);\n }\n\n /**\n * Mark the context as shutting down and flush all remaining items.\n */\n async shutdown<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n ): Promise<void> {\n this.log.debug(\"Flushing all remaining batch partitions on shutdown...\");\n context.isShuttingDown = true;\n await this.flush(context);\n this.log.debug(\"All batch partitions flushed\");\n }\n\n /**\n * Called after the \"ready\" hook to start processing buffered items that were\n * pushed during startup. This checks all partitions and starts timeouts/flushes\n * for items that were accumulated before the app was ready.\n */\n protected async startProcessing<TItem, TResponse>(\n context: BatchContext<TItem, TResponse>,\n ): Promise<void> {\n const maxSize = this.getMaxSize(context);\n const maxDuration = this.getMaxDuration(context);\n\n for (const [partitionKey, partition] of context.partitions.entries()) {\n if (partition.itemIds.length === 0) {\n continue;\n }\n\n this.log.trace(\n `Starting processing for partition '${partitionKey}' with ${partition.itemIds.length} buffered items`,\n );\n\n // Flush batches of maxSize while we have items >= maxSize\n while (partition.itemIds.length >= maxSize) {\n this.log.trace(\n `Partition '${partitionKey}' has ${partition.itemIds.length} items, flushing batch of ${maxSize}...`,\n );\n await this.flushPartition(context, partitionKey, maxSize);\n }\n\n // After flushing full batches, start timeout for any remaining items\n if (\n partition.itemIds.length > 0 &&\n !partition.timeout &&\n !partition.flushing\n ) {\n this.log.trace(\n `Starting timeout for partition '${partitionKey}' with ${partition.itemIds.length} remaining items`,\n );\n partition.timeout = this.dateTime.createTimeout(() => {\n this.log.trace(\n `Batch partition '${partitionKey}' timed out, flushing...`,\n );\n this.flushPartition(context, partitionKey).catch((error) =>\n this.log.error(\n `Failed to flush partition '${partitionKey}' on timeout after startup`,\n error,\n ),\n );\n }, maxDuration);\n }\n }\n }\n}\n","import {\n $hook,\n $inject,\n createPrimitive,\n KIND,\n Primitive,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport type { DurationLike } from \"alepha/datetime\";\nimport type { RetryPrimitiveOptions } from \"alepha/retry\";\nimport {\n type BatchContext,\n type BatchItemState,\n type BatchItemStatus,\n BatchProvider,\n} from \"../providers/BatchProvider.ts\";\n\n/**\n * Creates a batch processing primitive for efficient grouping and processing of multiple operations.\n */\nexport const $batch = <TItem extends TSchema, TResponse>(\n options: BatchPrimitiveOptions<TItem, TResponse>,\n): BatchPrimitive<TItem, TResponse> =>\n createPrimitive(BatchPrimitive<TItem, TResponse>, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface BatchPrimitiveOptions<TItem extends TSchema, TResponse = any> {\n /**\n * TypeBox schema for validating each item added to the batch.\n */\n schema: TItem;\n\n /**\n * The batch processing handler function that processes arrays of validated items.\n */\n handler: (items: Static<TItem>[]) => TResponse;\n\n /**\n * Maximum number of items to collect before automatically flushing the batch.\n */\n maxSize?: number;\n\n /**\n * Maximum number of items that can be queued in a single partition.\n * If exceeded, push() will throw an error.\n */\n maxQueueSize?: number;\n\n /**\n * Maximum time to wait before flushing a batch, even if it hasn't reached maxSize.\n */\n maxDuration?: DurationLike;\n\n /**\n * Function to determine partition keys for grouping items into separate batches.\n */\n partitionBy?: (item: Static<TItem>) => string;\n\n /**\n * Maximum number of batch handlers that can execute simultaneously.\n */\n concurrency?: number;\n\n /**\n * Retry configuration for failed batch processing operations.\n */\n retry?: Omit<RetryPrimitiveOptions<() => Array<Static<TItem>>>, \"handler\">;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type { BatchItemState, BatchItemStatus };\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class BatchPrimitive<\n TItem extends TSchema,\n TResponse = any,\n> extends Primitive<BatchPrimitiveOptions<TItem, TResponse>> {\n protected readonly batchProvider = $inject(BatchProvider);\n protected readonly context: BatchContext<Static<TItem>, TResponse>;\n\n constructor(\n ...args: ConstructorParameters<\n typeof Primitive<BatchPrimitiveOptions<TItem, TResponse>>\n >\n ) {\n super(...args);\n this.context = this.batchProvider.createContext(this.alepha, {\n handler: this.options.handler,\n maxSize: this.options.maxSize,\n maxQueueSize: this.options.maxQueueSize,\n maxDuration: this.options.maxDuration,\n partitionBy: this.options.partitionBy,\n concurrency: this.options.concurrency,\n retry: this.options.retry,\n });\n }\n\n /**\n * Pushes an item into the batch and returns immediately with a unique ID.\n * The item will be processed asynchronously with other items when the batch is flushed.\n * Use wait(id) to get the processing result.\n */\n public async push(item: Static<TItem>): Promise<string> {\n // Validate the item against the schema\n const validatedItem = this.alepha.codec.validate(this.options.schema, item);\n return this.batchProvider.push(this.context, validatedItem);\n }\n\n /**\n * Wait for a specific item to be processed and get its result.\n * @param id The item ID returned from push()\n * @returns The processing result\n * @throws If the item doesn't exist or processing failed\n */\n public async wait(id: string): Promise<TResponse> {\n return this.batchProvider.wait(this.context, id);\n }\n\n /**\n * Get the current status of an item.\n * @param id The item ID returned from push()\n * @returns Status information or undefined if item doesn't exist\n */\n public status(\n id: string,\n ):\n | { status: \"pending\" | \"processing\" }\n | { status: \"completed\"; result: TResponse }\n | { status: \"failed\"; error: Error }\n | undefined {\n return this.batchProvider.status(this.context, id);\n }\n\n /**\n * Flush all partitions or a specific partition.\n */\n public async flush(partitionKey?: string): Promise<void> {\n return this.batchProvider.flush(this.context, partitionKey);\n }\n\n /**\n * Clears completed and failed items from memory.\n * Call this periodically in long-running applications to prevent memory leaks.\n *\n * @param status Optional: only clear items with this specific status ('completed' or 'failed')\n * @returns The number of items cleared\n */\n public clearCompleted(status?: \"completed\" | \"failed\"): number {\n return this.batchProvider.clearCompleted(this.context, status);\n }\n\n protected readonly onReady = $hook({\n on: \"ready\",\n handler: async () => {\n await this.batchProvider.markReady(this.context);\n },\n });\n\n protected readonly dispose = $hook({\n on: \"stop\",\n priority: \"first\",\n handler: async () => {\n await this.batchProvider.shutdown(this.context);\n },\n });\n}\n\n$batch[KIND] = BatchPrimitive;\n","import { $module } from \"alepha\";\nimport { $batch } from \"./primitives/$batch.ts\";\nimport { BatchProvider } from \"./providers/BatchProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$batch.ts\";\nexport * from \"./providers/BatchProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * This module allows you to group multiple asynchronous operations into a single \"batch,\" which is then processed together.\n * This is an essential pattern for improving performance, reducing I/O, and interacting efficiently with rate-limited APIs or databases.\n *\n * ```ts\n * import { Alepha, $hook, run, t } from \"alepha\";\n * import { $batch } from \"alepha/batch\";\n *\n * class LoggingService {\n * // define the batch processor\n * logBatch = $batch({\n * schema: t.text(),\n * maxSize: 10,\n * maxDuration: [5, \"seconds\"],\n * handler: async (items) => {\n * console.log(`[BATCH LOG] Processing ${items.length} events:`, items);\n * },\n * });\n *\n * // example of how to use it\n * onReady = $hook({\n * on: \"ready\",\n * handler: async () => {\n * // push() returns an ID immediately\n * const id1 = await this.logBatch.push(\"Application started.\");\n * const id2 = await this.logBatch.push(\"User authenticated.\");\n *\n * // optionally wait for processing to complete\n * await this.logBatch.wait(id1);\n *\n * // or check the status\n * const status = this.logBatch.status(id2);\n * console.log(status?.status); // \"pending\" | \"processing\" | \"completed\" | \"failed\"\n * },\n * });\n * }\n * ```\n *\n * @see {@link $batch}\n * @see {@link BatchProvider}\n * @module alepha.batch\n */\nexport const AlephaBatch = $module({\n name: \"alepha.batch\",\n primitives: [$batch],\n services: [BatchProvider],\n});\n"],"mappings":"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"}
|
package/dist/bin/index.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import "tsx";
|
|
1
|
+
export { };
|
package/dist/bin/index.js
CHANGED
package/dist/bin/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/bin/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/bin/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Alepha, run } from \"alepha\";\nimport { AlephaCli, version } from \"alepha/cli\";\n\nconst alepha = Alepha.create({\n env: {\n APP_NAME: \"CLI\",\n LOG_LEVEL: \"alepha.core:warn,info\",\n LOG_FORMAT: \"raw\",\n CLI_NAME: \"alepha\",\n CLI_DESCRIPTION: `Alepha CLI v${version} - Create and manage Alepha projects.`,\n },\n});\n\nalepha.with(AlephaCli);\n\nrun(alepha);\n"],"mappings":";;;;;AAIA,MAAM,SAAS,OAAO,OAAO,EAC3B,KAAK;CACH,UAAU;CACV,WAAW;CACX,YAAY;CACZ,UAAU;CACV,iBAAiB,eAAe,QAAQ;CACzC,EACF,CAAC;AAEF,OAAO,KAAK,UAAU;AAEtB,IAAI,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["keysToDelete: string[]"],"sources":["../../../src/cache/core/errors/CacheError.ts","../../../src/cache/core/providers/CacheProvider.ts","../../../src/cache/core/providers/MemoryCacheProvider.ts","../../../src/cache/core/primitives/$cache.ts","../../../src/cache/core/index.ts"],"sourcesContent":["import { AlephaError } from \"alepha\";\n\nexport class CacheError extends AlephaError {}\n","/**\n * Cache provider interface.\n *\n * All methods are asynchronous and return promises.\n * Values are stored as Uint8Array.\n */\nexport abstract class CacheProvider {\n /**\n * Get the value of a key.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param key The key of the value to get.\n *\n * @return The value of the key, or undefined if the key does not exist.\n */\n public abstract get(\n name: string,\n key: string,\n ): Promise<Uint8Array | undefined>;\n\n /**\n * Set the string value of a key.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param key The key of the value to set.\n * @param value The value to set.\n * @param ttl The time-to-live of the key, in milliseconds.\n *\n * @return The value of the key.\n */\n public abstract set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array>;\n\n /**\n * Remove the specified keys.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param keys The keys to delete.\n */\n public abstract del(name: string, ...keys: string[]): Promise<void>;\n\n public abstract has(name: string, key: string): Promise<boolean>;\n\n public abstract keys(name: string, filter?: string): Promise<string[]>;\n\n /**\n * Remove all keys from all cache names.\n */\n public abstract clear(): Promise<void>;\n}\n","import { $inject } from \"alepha\";\nimport { DateTimeProvider, type Timeout } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport type { CacheProvider } from \"./CacheProvider.ts\";\n\ntype CacheName = string;\ntype CacheKey = string;\ntype CacheValue = {\n data?: Uint8Array;\n timeout?: Timeout;\n};\n\nexport class MemoryCacheProvider implements CacheProvider {\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly log = $logger();\n\n protected store: Record<CacheName, Record<CacheKey, CacheValue>> = {};\n\n public async get(name: string, key: string): Promise<Uint8Array | undefined> {\n return this.store[name]?.[key]?.data;\n }\n\n public async set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array> {\n if (this.store[name] == null) {\n this.store[name] = {};\n }\n\n this.store[name][key] ??= {};\n this.store[name][key].data = value;\n\n this.log.debug(`Setting cache for name`, { name, key, ttl });\n\n // clear previous timeout if exists\n if (this.store[name][key].timeout) {\n this.dateTimeProvider.clearTimeout(this.store[name][key].timeout);\n this.store[name][key].timeout = undefined;\n }\n\n if (ttl) {\n this.store[name][key].timeout = this.dateTimeProvider.createTimeout(\n () => this.del(name, key),\n ttl,\n );\n }\n\n return this.store[name][key].data;\n }\n\n public async del(name: string, ...keys: string[]): Promise<void> {\n // delete all keys in name\n if (keys.length === 0) {\n this.log.debug(`Deleting all cache for name`, { name });\n\n if (this.store[name]) {\n for (const key of Object.keys(this.store[name])) {\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n }\n }\n delete this.store[name];\n return;\n }\n\n this.log.debug(`Deleting cache for name`, { name, keys });\n\n // delete specific keys in name\n for (const key of keys) {\n if (this.store[name] == null) break;\n\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n\n delete this.store[name][key];\n }\n\n if (Object.keys(this.store[name] ?? {}).length === 0) {\n // if name is empty, delete it\n delete this.store[name];\n }\n }\n\n public async has(name: string, key: string): Promise<boolean> {\n return this.store[name]?.[key]?.data != null;\n }\n\n public async keys(name: string, filter?: string): Promise<string[]> {\n const store = this.store[name] ?? {};\n const keys = Object.keys(store);\n if (filter) {\n return keys.filter((key) => key.startsWith(filter));\n }\n return keys;\n }\n\n public async clear(): Promise<void> {\n this.log.debug(\"Clearing all cache\");\n\n // Clear all timeouts before clearing the store\n for (const name of Object.keys(this.store)) {\n for (const key of Object.keys(this.store[name])) {\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n }\n }\n\n this.store = {};\n }\n}\n","import {\n $env,\n $inject,\n createPrimitive,\n type InstantiableClass,\n KIND,\n Primitive,\n t,\n} from \"alepha\";\nimport { DateTimeProvider, type DurationLike } from \"alepha/datetime\";\nimport { CacheError } from \"../errors/CacheError.ts\";\nimport { CacheProvider } from \"../providers/CacheProvider.ts\";\nimport { MemoryCacheProvider } from \"../providers/MemoryCacheProvider.ts\";\n\n/**\n * Creates a cache primitive for high-performance data caching with automatic management.\n *\n * Provides a caching layer that improves application performance by storing frequently accessed\n * data in memory or external stores like Redis, with support for both function result caching\n * and manual cache operations.\n *\n * **Key Features**\n * - Automatic function result caching based on input parameters\n * - Multiple storage backends (in-memory, Redis, custom providers)\n * - Intelligent serialization for JSON, strings, and binary data\n * - Configurable TTL with automatic expiration\n * - Pattern-based cache invalidation with wildcard support\n * - Environment controls to enable/disable caching\n *\n * **Storage Backends**\n * - Memory: Fast in-memory cache (default for development)\n * - Redis: Distributed cache for production environments\n * - Custom providers: Implement your own storage backend\n *\n * @example\n * ```ts\n * class DataService {\n * // Function result caching\n * getUserData = $cache({\n * name: \"user-data\",\n * ttl: [10, \"minutes\"],\n * handler: async (userId: string) => {\n * return await database.users.findById(userId);\n * }\n * });\n *\n * // Manual cache operations\n * sessionCache = $cache<UserSession>({\n * name: \"sessions\",\n * ttl: [1, \"hour\"]\n * });\n *\n * async storeSession(id: string, session: UserSession) {\n * await this.sessionCache.set(id, session);\n * }\n *\n * async invalidateUserSessions(userId: string) {\n * await this.sessionCache.invalidate(`user:${userId}:*`);\n * }\n * }\n * ```\n */\nexport const $cache = <TReturn = string, TParameter extends any[] = any[]>(\n options: CachePrimitiveOptions<TReturn, TParameter> = {},\n): CachePrimitiveFn<TReturn, TParameter> => {\n const instance = createPrimitive(\n CachePrimitive<TReturn, TParameter>,\n options,\n );\n const fn = (...args: TParameter): Promise<TReturn> => instance.run(...args);\n return Object.setPrototypeOf(fn, instance) as CachePrimitiveFn<\n TReturn,\n TParameter\n >;\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface CachePrimitiveOptions<\n TReturn = any,\n TParameter extends any[] = any[],\n> {\n /**\n * The cache name. This is useful for invalidating multiple caches at once.\n *\n * Store key as `cache:$name:$key`.\n *\n * @default Name of the key of the class.\n */\n name?: string;\n\n /**\n * Function which returns cached data.\n */\n handler?: (...args: TParameter) => TReturn;\n\n /**\n * The key generator for the cache.\n * If not provided, the arguments will be json.stringify().\n */\n key?: (...args: TParameter) => string;\n\n /**\n * The store provider for the cache.\n * If not provided, the default store provider will be used.\n */\n provider?: InstantiableClass<CacheProvider> | \"memory\";\n\n /**\n * The time-to-live for the cache in seconds.\n * Set 0 to skip expiration.\n *\n * @default 300 (5 minutes).\n */\n ttl?: DurationLike;\n\n /**\n * If the cache is disabled.\n */\n disabled?: boolean;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nconst envSchema = t.object({\n CACHE_ENABLED: t.boolean({ default: true }),\n CACHE_DEFAULT_TTL: t.number({\n default: 300, // 5 minutes\n description: \"The default time to live for cache entries. In seconds.\",\n }),\n});\n\nexport class CachePrimitive<\n TReturn = any,\n TParameter extends any[] = any[],\n> extends Primitive<CachePrimitiveOptions<TReturn, TParameter>> {\n protected readonly env = $env(envSchema);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly provider = this.$provider();\n protected encoder: TextEncoder = new TextEncoder();\n protected decoder: TextDecoder = new TextDecoder();\n protected codes = {\n BINARY: 0x01,\n JSON: 0x02,\n STRING: 0x03,\n };\n\n public get container(): string {\n return (\n this.options.name ??\n `${this.config.service.name}:${this.config.propertyKey}`\n );\n }\n\n public async run(...args: TParameter): Promise<TReturn> {\n const handler = this.options.handler;\n if (!handler) {\n throw new Error(\"Cache handler is not defined.\");\n }\n\n const key = this.key(...args);\n const cached = await this.get(key);\n if (cached) {\n return cached;\n }\n\n const result = await handler(...args);\n // note: when exception occurs, don't cache the result\n\n await this.set(key, result);\n\n return result;\n }\n\n public key(...args: TParameter): string {\n return this.options.key ? this.options.key(...args) : JSON.stringify(args);\n }\n\n public async invalidate(...keys: string[]): Promise<void> {\n const keysToDelete: string[] = [];\n\n for (const key of keys) {\n if (key.endsWith(\"*\")) {\n const result = await this.provider.keys(\n this.container,\n key.slice(0, -1),\n );\n keysToDelete.push(...result);\n } else {\n keysToDelete.push(key);\n }\n }\n\n await this.provider.del(this.container, ...keysToDelete);\n }\n\n public async set(\n key: string,\n value: TReturn,\n ttl?: DurationLike,\n ): Promise<void> {\n const px = this.dateTimeProvider\n .duration(\n ttl ?? this.options.ttl ?? [this.env.CACHE_DEFAULT_TTL, \"seconds\"],\n )\n .as(\"milliseconds\");\n\n await this.provider.set(\n this.container,\n key,\n this.serialize(value),\n px > 0 ? px : undefined,\n );\n }\n\n public async get(key: string): Promise<TReturn | undefined> {\n if (\n !this.alepha.isStarted() ||\n this.options.disabled ||\n !this.env.CACHE_ENABLED\n ) {\n return undefined;\n }\n\n const data = await this.provider.get(this.container, key);\n if (data) {\n return await this.deserialize<TReturn>(data);\n }\n\n return undefined;\n }\n\n protected serialize<TReturn>(value: TReturn): Uint8Array {\n if (value instanceof Uint8Array) {\n return new Uint8Array([this.codes.BINARY, ...value]); // TODO: check if copy is ok?\n }\n\n if (typeof value === \"string\") {\n return new Uint8Array([this.codes.STRING, ...this.encoder.encode(value)]);\n }\n\n return new Uint8Array([\n this.codes.JSON,\n ...this.encoder.encode(JSON.stringify(value)),\n ]);\n }\n\n protected async deserialize<TReturn>(\n uint8Array: Uint8Array,\n ): Promise<TReturn> {\n const type = uint8Array[0];\n const payload = uint8Array.slice(1);\n\n if (type === this.codes.BINARY) {\n return payload as TReturn;\n }\n if (type === this.codes.JSON) {\n return JSON.parse(this.decoder.decode(payload)) as TReturn;\n }\n if (type === this.codes.STRING) {\n return this.decoder.decode(payload) as TReturn;\n }\n\n throw new CacheError(`Unknown serialization type: ${type}`);\n }\n\n protected $provider(): CacheProvider {\n if (!this.options.provider) {\n return this.alepha.inject(CacheProvider);\n }\n\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryCacheProvider);\n }\n\n return this.alepha.inject(this.options.provider);\n }\n}\n\nexport interface CachePrimitiveFn<\n TReturn = any,\n TParameter extends any[] = any[],\n> extends CachePrimitive<TReturn, TParameter> {\n /**\n * Run the cache primitive with the provided arguments.\n */\n (...args: TParameter): Promise<TReturn>;\n}\n\n$cache[KIND] = CachePrimitive;\n","import { $module } from \"alepha\";\nimport { $cache } from \"./primitives/$cache.ts\";\nimport { CacheProvider } from \"./providers/CacheProvider.ts\";\nimport { MemoryCacheProvider } from \"./providers/MemoryCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$cache.ts\";\nexport * from \"./providers/CacheProvider.ts\";\nexport * from \"./providers/MemoryCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Provides high-performance caching capabilities for Alepha applications with configurable TTL and multiple storage backends.\n *\n * The cache module enables declarative caching through the `$cache` primitive, allowing you to cache method results,\n * API responses, or computed values with automatic invalidation and type safety. It supports both in-memory and\n * persistent storage backends for different performance and durability requirements.\n *\n * @see {@link $cache}\n * @see {@link CacheProvider}\n * @module alepha.cache\n */\nexport const AlephaCache = $module({\n name: \"alepha.cache\",\n primitives: [$cache],\n services: [CacheProvider, MemoryCacheProvider],\n register: (alepha) =>\n alepha.with({\n optional: true,\n provide: CacheProvider,\n use: MemoryCacheProvider,\n }),\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/cache/core/errors/CacheError.ts","../../../src/cache/core/providers/CacheProvider.ts","../../../src/cache/core/providers/MemoryCacheProvider.ts","../../../src/cache/core/primitives/$cache.ts","../../../src/cache/core/index.ts"],"sourcesContent":["import { AlephaError } from \"alepha\";\n\nexport class CacheError extends AlephaError {}\n","/**\n * Cache provider interface.\n *\n * All methods are asynchronous and return promises.\n * Values are stored as Uint8Array.\n */\nexport abstract class CacheProvider {\n /**\n * Get the value of a key.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param key The key of the value to get.\n *\n * @return The value of the key, or undefined if the key does not exist.\n */\n public abstract get(\n name: string,\n key: string,\n ): Promise<Uint8Array | undefined>;\n\n /**\n * Set the string value of a key.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param key The key of the value to set.\n * @param value The value to set.\n * @param ttl The time-to-live of the key, in milliseconds.\n *\n * @return The value of the key.\n */\n public abstract set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array>;\n\n /**\n * Remove the specified keys.\n *\n * @param name Cache name, used to group keys. Should be Redis-like \"some:group:name\" format.\n * @param keys The keys to delete.\n */\n public abstract del(name: string, ...keys: string[]): Promise<void>;\n\n public abstract has(name: string, key: string): Promise<boolean>;\n\n public abstract keys(name: string, filter?: string): Promise<string[]>;\n\n /**\n * Remove all keys from all cache names.\n */\n public abstract clear(): Promise<void>;\n}\n","import { $inject } from \"alepha\";\nimport { DateTimeProvider, type Timeout } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport type { CacheProvider } from \"./CacheProvider.ts\";\n\ntype CacheName = string;\ntype CacheKey = string;\ntype CacheValue = {\n data?: Uint8Array;\n timeout?: Timeout;\n};\n\nexport class MemoryCacheProvider implements CacheProvider {\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly log = $logger();\n\n protected store: Record<CacheName, Record<CacheKey, CacheValue>> = {};\n\n public async get(name: string, key: string): Promise<Uint8Array | undefined> {\n return this.store[name]?.[key]?.data;\n }\n\n public async set(\n name: string,\n key: string,\n value: Uint8Array,\n ttl?: number,\n ): Promise<Uint8Array> {\n if (this.store[name] == null) {\n this.store[name] = {};\n }\n\n this.store[name][key] ??= {};\n this.store[name][key].data = value;\n\n this.log.debug(`Setting cache for name`, { name, key, ttl });\n\n // clear previous timeout if exists\n if (this.store[name][key].timeout) {\n this.dateTimeProvider.clearTimeout(this.store[name][key].timeout);\n this.store[name][key].timeout = undefined;\n }\n\n if (ttl) {\n this.store[name][key].timeout = this.dateTimeProvider.createTimeout(\n () => this.del(name, key),\n ttl,\n );\n }\n\n return this.store[name][key].data;\n }\n\n public async del(name: string, ...keys: string[]): Promise<void> {\n // delete all keys in name\n if (keys.length === 0) {\n this.log.debug(`Deleting all cache for name`, { name });\n\n if (this.store[name]) {\n for (const key of Object.keys(this.store[name])) {\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n }\n }\n delete this.store[name];\n return;\n }\n\n this.log.debug(`Deleting cache for name`, { name, keys });\n\n // delete specific keys in name\n for (const key of keys) {\n if (this.store[name] == null) break;\n\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n\n delete this.store[name][key];\n }\n\n if (Object.keys(this.store[name] ?? {}).length === 0) {\n // if name is empty, delete it\n delete this.store[name];\n }\n }\n\n public async has(name: string, key: string): Promise<boolean> {\n return this.store[name]?.[key]?.data != null;\n }\n\n public async keys(name: string, filter?: string): Promise<string[]> {\n const store = this.store[name] ?? {};\n const keys = Object.keys(store);\n if (filter) {\n return keys.filter((key) => key.startsWith(filter));\n }\n return keys;\n }\n\n public async clear(): Promise<void> {\n this.log.debug(\"Clearing all cache\");\n\n // Clear all timeouts before clearing the store\n for (const name of Object.keys(this.store)) {\n for (const key of Object.keys(this.store[name])) {\n const timeout = this.store[name][key]?.timeout;\n if (timeout) {\n this.dateTimeProvider.clearTimeout(timeout);\n }\n }\n }\n\n this.store = {};\n }\n}\n","import {\n $env,\n $inject,\n createPrimitive,\n type InstantiableClass,\n KIND,\n Primitive,\n t,\n} from \"alepha\";\nimport { DateTimeProvider, type DurationLike } from \"alepha/datetime\";\nimport { CacheError } from \"../errors/CacheError.ts\";\nimport { CacheProvider } from \"../providers/CacheProvider.ts\";\nimport { MemoryCacheProvider } from \"../providers/MemoryCacheProvider.ts\";\n\n/**\n * Creates a cache primitive for high-performance data caching with automatic management.\n *\n * Provides a caching layer that improves application performance by storing frequently accessed\n * data in memory or external stores like Redis, with support for both function result caching\n * and manual cache operations.\n *\n * **Key Features**\n * - Automatic function result caching based on input parameters\n * - Multiple storage backends (in-memory, Redis, custom providers)\n * - Intelligent serialization for JSON, strings, and binary data\n * - Configurable TTL with automatic expiration\n * - Pattern-based cache invalidation with wildcard support\n * - Environment controls to enable/disable caching\n *\n * **Storage Backends**\n * - Memory: Fast in-memory cache (default for development)\n * - Redis: Distributed cache for production environments\n * - Custom providers: Implement your own storage backend\n *\n * @example\n * ```ts\n * class DataService {\n * // Function result caching\n * getUserData = $cache({\n * name: \"user-data\",\n * ttl: [10, \"minutes\"],\n * handler: async (userId: string) => {\n * return await database.users.findById(userId);\n * }\n * });\n *\n * // Manual cache operations\n * sessionCache = $cache<UserSession>({\n * name: \"sessions\",\n * ttl: [1, \"hour\"]\n * });\n *\n * async storeSession(id: string, session: UserSession) {\n * await this.sessionCache.set(id, session);\n * }\n *\n * async invalidateUserSessions(userId: string) {\n * await this.sessionCache.invalidate(`user:${userId}:*`);\n * }\n * }\n * ```\n */\nexport const $cache = <TReturn = string, TParameter extends any[] = any[]>(\n options: CachePrimitiveOptions<TReturn, TParameter> = {},\n): CachePrimitiveFn<TReturn, TParameter> => {\n const instance = createPrimitive(\n CachePrimitive<TReturn, TParameter>,\n options,\n );\n const fn = (...args: TParameter): Promise<TReturn> => instance.run(...args);\n return Object.setPrototypeOf(fn, instance) as CachePrimitiveFn<\n TReturn,\n TParameter\n >;\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface CachePrimitiveOptions<\n TReturn = any,\n TParameter extends any[] = any[],\n> {\n /**\n * The cache name. This is useful for invalidating multiple caches at once.\n *\n * Store key as `cache:$name:$key`.\n *\n * @default Name of the key of the class.\n */\n name?: string;\n\n /**\n * Function which returns cached data.\n */\n handler?: (...args: TParameter) => TReturn;\n\n /**\n * The key generator for the cache.\n * If not provided, the arguments will be json.stringify().\n */\n key?: (...args: TParameter) => string;\n\n /**\n * The store provider for the cache.\n * If not provided, the default store provider will be used.\n */\n provider?: InstantiableClass<CacheProvider> | \"memory\";\n\n /**\n * The time-to-live for the cache in seconds.\n * Set 0 to skip expiration.\n *\n * @default 300 (5 minutes).\n */\n ttl?: DurationLike;\n\n /**\n * If the cache is disabled.\n */\n disabled?: boolean;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nconst envSchema = t.object({\n CACHE_ENABLED: t.boolean({ default: true }),\n CACHE_DEFAULT_TTL: t.number({\n default: 300, // 5 minutes\n description: \"The default time to live for cache entries. In seconds.\",\n }),\n});\n\nexport class CachePrimitive<\n TReturn = any,\n TParameter extends any[] = any[],\n> extends Primitive<CachePrimitiveOptions<TReturn, TParameter>> {\n protected readonly env = $env(envSchema);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly provider = this.$provider();\n protected encoder: TextEncoder = new TextEncoder();\n protected decoder: TextDecoder = new TextDecoder();\n protected codes = {\n BINARY: 0x01,\n JSON: 0x02,\n STRING: 0x03,\n };\n\n public get container(): string {\n return (\n this.options.name ??\n `${this.config.service.name}:${this.config.propertyKey}`\n );\n }\n\n public async run(...args: TParameter): Promise<TReturn> {\n const handler = this.options.handler;\n if (!handler) {\n throw new Error(\"Cache handler is not defined.\");\n }\n\n const key = this.key(...args);\n const cached = await this.get(key);\n if (cached) {\n return cached;\n }\n\n const result = await handler(...args);\n // note: when exception occurs, don't cache the result\n\n await this.set(key, result);\n\n return result;\n }\n\n public key(...args: TParameter): string {\n return this.options.key ? this.options.key(...args) : JSON.stringify(args);\n }\n\n public async invalidate(...keys: string[]): Promise<void> {\n const keysToDelete: string[] = [];\n\n for (const key of keys) {\n if (key.endsWith(\"*\")) {\n const result = await this.provider.keys(\n this.container,\n key.slice(0, -1),\n );\n keysToDelete.push(...result);\n } else {\n keysToDelete.push(key);\n }\n }\n\n await this.provider.del(this.container, ...keysToDelete);\n }\n\n public async set(\n key: string,\n value: TReturn,\n ttl?: DurationLike,\n ): Promise<void> {\n const px = this.dateTimeProvider\n .duration(\n ttl ?? this.options.ttl ?? [this.env.CACHE_DEFAULT_TTL, \"seconds\"],\n )\n .as(\"milliseconds\");\n\n await this.provider.set(\n this.container,\n key,\n this.serialize(value),\n px > 0 ? px : undefined,\n );\n }\n\n public async get(key: string): Promise<TReturn | undefined> {\n if (\n !this.alepha.isStarted() ||\n this.options.disabled ||\n !this.env.CACHE_ENABLED\n ) {\n return undefined;\n }\n\n const data = await this.provider.get(this.container, key);\n if (data) {\n return await this.deserialize<TReturn>(data);\n }\n\n return undefined;\n }\n\n protected serialize<TReturn>(value: TReturn): Uint8Array {\n if (value instanceof Uint8Array) {\n return new Uint8Array([this.codes.BINARY, ...value]); // TODO: check if copy is ok?\n }\n\n if (typeof value === \"string\") {\n return new Uint8Array([this.codes.STRING, ...this.encoder.encode(value)]);\n }\n\n return new Uint8Array([\n this.codes.JSON,\n ...this.encoder.encode(JSON.stringify(value)),\n ]);\n }\n\n protected async deserialize<TReturn>(\n uint8Array: Uint8Array,\n ): Promise<TReturn> {\n const type = uint8Array[0];\n const payload = uint8Array.slice(1);\n\n if (type === this.codes.BINARY) {\n return payload as TReturn;\n }\n if (type === this.codes.JSON) {\n return JSON.parse(this.decoder.decode(payload)) as TReturn;\n }\n if (type === this.codes.STRING) {\n return this.decoder.decode(payload) as TReturn;\n }\n\n throw new CacheError(`Unknown serialization type: ${type}`);\n }\n\n protected $provider(): CacheProvider {\n if (!this.options.provider) {\n return this.alepha.inject(CacheProvider);\n }\n\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryCacheProvider);\n }\n\n return this.alepha.inject(this.options.provider);\n }\n}\n\nexport interface CachePrimitiveFn<\n TReturn = any,\n TParameter extends any[] = any[],\n> extends CachePrimitive<TReturn, TParameter> {\n /**\n * Run the cache primitive with the provided arguments.\n */\n (...args: TParameter): Promise<TReturn>;\n}\n\n$cache[KIND] = CachePrimitive;\n","import { $module } from \"alepha\";\nimport { $cache } from \"./primitives/$cache.ts\";\nimport { CacheProvider } from \"./providers/CacheProvider.ts\";\nimport { MemoryCacheProvider } from \"./providers/MemoryCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./primitives/$cache.ts\";\nexport * from \"./providers/CacheProvider.ts\";\nexport * from \"./providers/MemoryCacheProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Provides high-performance caching capabilities for Alepha applications with configurable TTL and multiple storage backends.\n *\n * The cache module enables declarative caching through the `$cache` primitive, allowing you to cache method results,\n * API responses, or computed values with automatic invalidation and type safety. It supports both in-memory and\n * persistent storage backends for different performance and durability requirements.\n *\n * @see {@link $cache}\n * @see {@link CacheProvider}\n * @module alepha.cache\n */\nexport const AlephaCache = $module({\n name: \"alepha.cache\",\n primitives: [$cache],\n services: [CacheProvider, MemoryCacheProvider],\n register: (alepha) =>\n alepha.with({\n optional: true,\n provide: CacheProvider,\n use: MemoryCacheProvider,\n }),\n});\n"],"mappings":"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"}
|