alepha 0.14.2 → 0.14.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/api/audits/index.browser.js +5 -5
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.d.ts +706 -785
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +13 -13
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.browser.js +5 -5
- package/dist/api/files/index.browser.js.map +1 -1
- package/dist/api/files/index.d.ts +58 -137
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +71 -71
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +5 -5
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +29 -108
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +10 -10
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js +10 -10
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +504 -171
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +12 -12
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.browser.js +163 -10
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +277 -351
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +196 -91
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.browser.js +19 -19
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +787 -852
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +827 -596
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.browser.js +6 -6
- package/dist/api/verifications/index.browser.js.map +1 -1
- package/dist/api/verifications/index.d.ts +128 -128
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +6 -6
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/bin/index.d.ts +1 -2
- package/dist/bin/index.js +0 -1
- package/dist/bin/index.js.map +1 -1
- package/dist/cli/index.d.ts +252 -131
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +595 -395
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +46 -11
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +99 -19
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +40 -22
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +45 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +40 -22
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +40 -22
- package/dist/core/index.native.js.map +1 -1
- package/dist/fake/index.js +195 -168
- package/dist/fake/index.js.map +1 -1
- package/dist/file/index.d.ts +8 -0
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js +3 -0
- package/dist/file/index.js.map +1 -1
- package/dist/logger/index.d.ts +1 -1
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +12 -2
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.d.ts +59 -195
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +201 -430
- package/dist/orm/index.js.map +1 -1
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +171 -155
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +0 -1
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +12 -0
- package/dist/server/cache/index.d.ts.map +1 -1
- package/dist/server/cache/index.js +55 -2
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts +6 -0
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/compress/index.js +38 -1
- package/dist/server/compress/index.js.map +1 -1
- package/dist/server/core/index.browser.js +2 -2
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +10 -10
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +7 -4
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/links/index.browser.js +22 -6
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +46 -44
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +24 -41
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/static/index.js +4 -0
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +2 -1
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +9 -5
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/vite/index.d.ts +101 -106
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +574 -503
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts +7 -7
- package/package.json +7 -7
- package/src/api/audits/controllers/{AuditController.ts → AdminAuditController.ts} +5 -6
- package/src/api/audits/entities/audits.ts +5 -5
- package/src/api/audits/index.browser.ts +1 -1
- package/src/api/audits/index.ts +3 -3
- package/src/api/audits/primitives/$audit.spec.ts +276 -0
- package/src/api/audits/services/AuditService.spec.ts +495 -0
- package/src/api/files/__tests__/$bucket.spec.ts +91 -0
- package/src/api/files/controllers/AdminFileStatsController.spec.ts +166 -0
- package/src/api/files/controllers/{StorageStatsController.ts → AdminFileStatsController.ts} +2 -2
- package/src/api/files/controllers/FileController.spec.ts +558 -0
- package/src/api/files/controllers/FileController.ts +4 -5
- package/src/api/files/entities/files.ts +5 -5
- package/src/api/files/index.browser.ts +1 -1
- package/src/api/files/index.ts +4 -4
- package/src/api/files/jobs/FileJobs.spec.ts +52 -0
- package/src/api/files/services/FileService.spec.ts +109 -0
- package/src/api/jobs/__tests__/JobController.spec.ts +343 -0
- package/src/api/jobs/controllers/{JobController.ts → AdminJobController.ts} +2 -2
- package/src/api/jobs/entities/jobExecutions.ts +5 -5
- package/src/api/jobs/index.ts +3 -3
- package/src/api/jobs/primitives/$job.spec.ts +476 -0
- package/src/api/notifications/controllers/{NotificationController.ts → AdminNotificationController.ts} +4 -5
- package/src/api/notifications/entities/notifications.ts +5 -5
- package/src/api/notifications/index.browser.ts +1 -1
- package/src/api/notifications/index.ts +4 -4
- package/src/api/parameters/controllers/{ConfigController.ts → AdminConfigController.ts} +46 -107
- package/src/api/parameters/entities/parameters.ts +7 -17
- package/src/api/parameters/index.ts +3 -3
- package/src/api/parameters/primitives/$config.spec.ts +356 -0
- package/src/api/parameters/schemas/activateConfigBodySchema.ts +12 -0
- package/src/api/parameters/schemas/checkScheduledResponseSchema.ts +8 -0
- package/src/api/parameters/schemas/configCurrentResponseSchema.ts +13 -0
- package/src/api/parameters/schemas/configHistoryResponseSchema.ts +9 -0
- package/src/api/parameters/schemas/configNameParamSchema.ts +10 -0
- package/src/api/parameters/schemas/configNamesResponseSchema.ts +8 -0
- package/src/api/parameters/schemas/configTreeNodeSchema.ts +13 -0
- package/src/api/parameters/schemas/configVersionParamSchema.ts +9 -0
- package/src/api/parameters/schemas/configVersionResponseSchema.ts +9 -0
- package/src/api/parameters/schemas/configsByStatusResponseSchema.ts +9 -0
- package/src/api/parameters/schemas/createConfigVersionBodySchema.ts +24 -0
- package/src/api/parameters/schemas/index.ts +15 -0
- package/src/api/parameters/schemas/parameterResponseSchema.ts +26 -0
- package/src/api/parameters/schemas/parameterStatusSchema.ts +13 -0
- package/src/api/parameters/schemas/rollbackConfigBodySchema.ts +15 -0
- package/src/api/parameters/schemas/statusParamSchema.ts +9 -0
- package/src/api/users/__tests__/EmailVerification.spec.ts +369 -0
- package/src/api/users/__tests__/PasswordReset.spec.ts +550 -0
- package/src/api/users/controllers/AdminIdentityController.spec.ts +365 -0
- package/src/api/users/controllers/{IdentityController.ts → AdminIdentityController.ts} +3 -4
- package/src/api/users/controllers/AdminSessionController.spec.ts +274 -0
- package/src/api/users/controllers/{SessionController.ts → AdminSessionController.ts} +3 -4
- package/src/api/users/controllers/AdminUserController.spec.ts +372 -0
- package/src/api/users/controllers/AdminUserController.ts +116 -0
- package/src/api/users/controllers/UserController.ts +4 -107
- package/src/api/users/controllers/UserRealmController.ts +3 -0
- package/src/api/users/entities/identities.ts +6 -6
- package/src/api/users/entities/sessions.ts +6 -6
- package/src/api/users/entities/users.ts +9 -9
- package/src/api/users/index.ts +9 -6
- package/src/api/users/primitives/$userRealm.ts +13 -8
- package/src/api/users/services/CredentialService.spec.ts +509 -0
- package/src/api/users/services/CredentialService.ts +46 -0
- package/src/api/users/services/IdentityService.ts +15 -0
- package/src/api/users/services/RegistrationService.spec.ts +630 -0
- package/src/api/users/services/RegistrationService.ts +18 -0
- package/src/api/users/services/SessionService.spec.ts +301 -0
- package/src/api/users/services/SessionService.ts +110 -1
- package/src/api/users/services/UserService.ts +67 -2
- package/src/api/verifications/__tests__/CodeVerification.spec.ts +318 -0
- package/src/api/verifications/__tests__/LinkVerification.spec.ts +279 -0
- package/src/api/verifications/entities/verifications.ts +6 -6
- package/src/api/verifications/jobs/VerificationJobs.spec.ts +50 -0
- package/src/batch/__tests__/startup-buffering.spec.ts +458 -0
- package/src/batch/primitives/$batch.spec.ts +766 -0
- package/src/batch/providers/BatchProvider.spec.ts +786 -0
- package/src/bin/index.ts +0 -1
- package/src/bucket/__tests__/shared.ts +194 -0
- package/src/bucket/primitives/$bucket.spec.ts +104 -0
- package/src/bucket/providers/FileStorageProvider.spec.ts +13 -0
- package/src/bucket/providers/LocalFileStorageProvider.spec.ts +77 -0
- package/src/bucket/providers/MemoryFileStorageProvider.spec.ts +82 -0
- package/src/cache/core/__tests__/shared.ts +377 -0
- package/src/cache/core/primitives/$cache.spec.ts +111 -0
- package/src/cache/redis/__tests__/cache-redis.spec.ts +70 -0
- package/src/cli/apps/AlephaCli.ts +25 -6
- package/src/cli/atoms/buildOptions.ts +88 -0
- package/src/cli/commands/build.ts +32 -69
- package/src/cli/commands/db.ts +0 -4
- package/src/cli/commands/dev.ts +34 -10
- package/src/cli/commands/gen/changelog.spec.ts +315 -0
- package/src/cli/commands/{changelog.ts → gen/changelog.ts} +9 -9
- package/src/cli/commands/gen/env.ts +53 -0
- package/src/cli/commands/gen/openapi.ts +71 -0
- package/src/cli/commands/gen/resource.ts +15 -0
- package/src/cli/commands/gen.ts +24 -0
- package/src/cli/commands/init.ts +2 -1
- package/src/cli/commands/root.ts +12 -3
- package/src/cli/commands/test.ts +0 -1
- package/src/cli/commands/typecheck.ts +5 -0
- package/src/cli/commands/verify.ts +1 -1
- package/src/cli/defineConfig.ts +49 -7
- package/src/cli/index.ts +2 -2
- package/src/cli/services/AlephaCliUtils.ts +105 -55
- package/src/cli/services/GitMessageParser.ts +1 -1
- package/src/command/helpers/Asker.spec.ts +127 -0
- package/src/command/helpers/Runner.spec.ts +126 -0
- package/src/command/helpers/Runner.ts +1 -1
- package/src/command/primitives/$command.spec.ts +1588 -0
- package/src/command/primitives/$command.ts +0 -6
- package/src/command/providers/CliProvider.ts +75 -27
- package/src/core/Alepha.ts +87 -0
- package/src/core/__tests__/Alepha-emit.spec.ts +22 -0
- package/src/core/__tests__/Alepha-graph.spec.ts +93 -0
- package/src/core/__tests__/Alepha-has.spec.ts +41 -0
- package/src/core/__tests__/Alepha-inject.spec.ts +93 -0
- package/src/core/__tests__/Alepha-register.spec.ts +81 -0
- package/src/core/__tests__/Alepha-start.spec.ts +176 -0
- package/src/core/__tests__/Alepha-with.spec.ts +14 -0
- package/src/core/__tests__/TypeBox-usecases.spec.ts +35 -0
- package/src/core/__tests__/TypeBoxLocale.spec.ts +15 -0
- package/src/core/__tests__/descriptor.spec.ts +34 -0
- package/src/core/__tests__/fixtures/A.ts +5 -0
- package/src/core/__tests__/pagination.spec.ts +77 -0
- package/src/core/helpers/jsonSchemaToTypeBox.ts +2 -2
- package/src/core/primitives/$atom.spec.ts +43 -0
- package/src/core/primitives/$hook.spec.ts +130 -0
- package/src/core/primitives/$inject.spec.ts +175 -0
- package/src/core/primitives/$module.spec.ts +115 -0
- package/src/core/providers/CodecManager.spec.ts +740 -0
- package/src/core/providers/EventManager.spec.ts +762 -0
- package/src/core/providers/EventManager.ts +4 -0
- package/src/core/providers/StateManager.spec.ts +365 -0
- package/src/core/providers/TypeProvider.spec.ts +1607 -0
- package/src/core/providers/TypeProvider.ts +20 -26
- package/src/datetime/primitives/$interval.spec.ts +103 -0
- package/src/datetime/providers/DateTimeProvider.spec.ts +86 -0
- package/src/email/primitives/$email.spec.ts +175 -0
- package/src/email/providers/LocalEmailProvider.spec.ts +341 -0
- package/src/fake/__tests__/keyName.example.ts +40 -0
- package/src/fake/__tests__/keyName.spec.ts +152 -0
- package/src/fake/__tests__/module.example.ts +32 -0
- package/src/fake/providers/FakeProvider.spec.ts +438 -0
- package/src/file/providers/FileSystemProvider.ts +8 -0
- package/src/file/providers/NodeFileSystemProvider.spec.ts +418 -0
- package/src/file/providers/NodeFileSystemProvider.ts +5 -0
- package/src/file/services/FileDetector.spec.ts +591 -0
- package/src/lock/core/__tests__/shared.ts +190 -0
- package/src/lock/core/providers/MemoryLockProvider.spec.ts +25 -0
- package/src/lock/redis/providers/RedisLockProvider.spec.ts +25 -0
- package/src/logger/__tests__/SimpleFormatterProvider.spec.ts +109 -0
- package/src/logger/index.ts +15 -3
- package/src/logger/primitives/$logger.spec.ts +108 -0
- package/src/logger/services/Logger.spec.ts +295 -0
- package/src/mcp/__tests__/errors.spec.ts +175 -0
- package/src/mcp/__tests__/integration.spec.ts +450 -0
- package/src/mcp/helpers/jsonrpc.spec.ts +380 -0
- package/src/mcp/primitives/$prompt.spec.ts +468 -0
- package/src/mcp/primitives/$resource.spec.ts +390 -0
- package/src/mcp/primitives/$tool.spec.ts +406 -0
- package/src/mcp/providers/McpServerProvider.spec.ts +797 -0
- package/src/mcp/transports/StdioMcpTransport.ts +1 -1
- package/src/orm/__tests__/$repository-crud.spec.ts +276 -0
- package/src/orm/__tests__/$repository-hooks.spec.ts +325 -0
- package/src/orm/__tests__/$repository-orderBy.spec.ts +128 -0
- package/src/orm/__tests__/$repository-pagination-sort.spec.ts +149 -0
- package/src/orm/__tests__/$repository-save.spec.ts +37 -0
- package/src/orm/__tests__/ModelBuilder-integration.spec.ts +490 -0
- package/src/orm/__tests__/ModelBuilder-types.spec.ts +186 -0
- package/src/orm/__tests__/PostgresProvider.spec.ts +46 -0
- package/src/orm/__tests__/delete-returning.spec.ts +256 -0
- package/src/orm/__tests__/deletedAt.spec.ts +80 -0
- package/src/orm/__tests__/enums.spec.ts +315 -0
- package/src/orm/__tests__/execute.spec.ts +72 -0
- package/src/orm/__tests__/fixtures/bigEntitySchema.ts +65 -0
- package/src/orm/__tests__/fixtures/userEntitySchema.ts +27 -0
- package/src/orm/__tests__/joins.spec.ts +1114 -0
- package/src/orm/__tests__/page.spec.ts +287 -0
- package/src/orm/__tests__/primaryKey.spec.ts +87 -0
- package/src/orm/__tests__/query-date-encoding.spec.ts +402 -0
- package/src/orm/__tests__/ref-auto-onDelete.spec.ts +156 -0
- package/src/orm/__tests__/references.spec.ts +102 -0
- package/src/orm/__tests__/security.spec.ts +710 -0
- package/src/orm/__tests__/sqlite.spec.ts +111 -0
- package/src/orm/__tests__/string-operators.spec.ts +429 -0
- package/src/orm/__tests__/timestamps.spec.ts +388 -0
- package/src/orm/__tests__/validation.spec.ts +183 -0
- package/src/orm/__tests__/version.spec.ts +64 -0
- package/src/orm/helpers/parseQueryString.spec.ts +196 -0
- package/src/orm/index.ts +2 -8
- package/src/orm/primitives/$repository.spec.ts +137 -0
- package/src/orm/primitives/$sequence.spec.ts +29 -0
- package/src/orm/primitives/$transaction.spec.ts +82 -0
- package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -3
- package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +1 -1
- package/src/orm/providers/drivers/DatabaseProvider.ts +1 -1
- package/src/orm/providers/drivers/NodePostgresProvider.ts +3 -3
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +1 -1
- package/src/orm/providers/drivers/PglitePostgresProvider.ts +2 -2
- package/src/orm/services/ModelBuilder.spec.ts +575 -0
- package/src/orm/services/Repository.spec.ts +137 -0
- package/src/queue/core/__tests__/shared.ts +143 -0
- package/src/queue/core/providers/MemoryQueueProvider.spec.ts +23 -0
- package/src/queue/core/providers/WorkerProvider.spec.ts +394 -0
- package/src/queue/redis/providers/RedisQueueProvider.spec.ts +23 -0
- package/src/redis/__tests__/redis.spec.ts +58 -0
- package/src/retry/primitives/$retry.spec.ts +234 -0
- package/src/retry/providers/RetryProvider.spec.ts +438 -0
- package/src/router/__tests__/match.spec.ts +252 -0
- package/src/router/providers/RouterProvider.spec.ts +197 -0
- package/src/scheduler/__tests__/$scheduler-cron.spec.ts +25 -0
- package/src/scheduler/__tests__/$scheduler-interval.spec.ts +25 -0
- package/src/scheduler/__tests__/shared.ts +77 -0
- package/src/security/__tests__/bug-1-wildcard-after-start.spec.ts +229 -0
- package/src/security/__tests__/bug-2-password-validation.spec.ts +245 -0
- package/src/security/__tests__/bug-3-regex-vulnerability.spec.ts +407 -0
- package/src/security/__tests__/bug-4-oauth2-validation.spec.ts +439 -0
- package/src/security/__tests__/multi-layer-permissions.spec.ts +522 -0
- package/src/security/primitives/$permission.spec.ts +30 -0
- package/src/security/primitives/$permission.ts +2 -2
- package/src/security/primitives/$realm.spec.ts +101 -0
- package/src/security/primitives/$role.spec.ts +52 -0
- package/src/security/primitives/$serviceAccount.spec.ts +61 -0
- package/src/security/providers/SecurityProvider.spec.ts +350 -0
- package/src/server/auth/providers/ServerAuthProvider.ts +0 -2
- package/src/server/cache/providers/ServerCacheProvider.spec.ts +1125 -0
- package/src/server/cache/providers/ServerCacheProvider.ts +94 -9
- package/src/server/compress/providers/ServerCompressProvider.spec.ts +31 -0
- package/src/server/compress/providers/ServerCompressProvider.ts +63 -2
- package/src/server/cookies/providers/ServerCookiesProvider.spec.ts +253 -0
- package/src/server/core/__tests__/ServerRouterProvider-getRoutes.spec.ts +334 -0
- package/src/server/core/__tests__/ServerRouterProvider-requestId.spec.ts +129 -0
- package/src/server/core/helpers/ServerReply.ts +2 -2
- package/src/server/core/primitives/$action.spec.ts +191 -0
- package/src/server/core/primitives/$route.spec.ts +65 -0
- package/src/server/core/providers/ServerBodyParserProvider.spec.ts +93 -0
- package/src/server/core/providers/ServerLoggerProvider.spec.ts +100 -0
- package/src/server/core/providers/ServerProvider.ts +14 -2
- package/src/server/core/services/HttpClient.spec.ts +123 -0
- package/src/server/core/services/UserAgentParser.spec.ts +111 -0
- package/src/server/cors/providers/ServerCorsProvider.spec.ts +481 -0
- package/src/server/health/providers/ServerHealthProvider.spec.ts +22 -0
- package/src/server/helmet/providers/ServerHelmetProvider.spec.ts +105 -0
- package/src/server/links/__tests__/$action.spec.ts +238 -0
- package/src/server/links/__tests__/fixtures/CrudApp.ts +122 -0
- package/src/server/links/__tests__/requestId.spec.ts +120 -0
- package/src/server/links/primitives/$remote.spec.ts +228 -0
- package/src/server/links/providers/LinkProvider.spec.ts +54 -0
- package/src/server/links/providers/LinkProvider.ts +49 -3
- package/src/server/links/providers/ServerLinksProvider.ts +1 -53
- package/src/server/links/schemas/apiLinksResponseSchema.ts +7 -0
- package/src/server/metrics/providers/ServerMetricsProvider.spec.ts +25 -0
- package/src/server/multipart/providers/ServerMultipartProvider.spec.ts +528 -0
- package/src/server/proxy/primitives/$proxy.spec.ts +87 -0
- package/src/server/rate-limit/__tests__/ActionRateLimit.spec.ts +211 -0
- package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +344 -0
- package/src/server/security/__tests__/BasicAuth.spec.ts +684 -0
- package/src/server/security/__tests__/ServerSecurityProvider-realm.spec.ts +388 -0
- package/src/server/security/providers/ServerSecurityProvider.spec.ts +123 -0
- package/src/server/static/primitives/$serve.spec.ts +193 -0
- package/src/server/static/providers/ServerStaticProvider.ts +10 -0
- package/src/server/swagger/__tests__/ui.spec.ts +52 -0
- package/src/server/swagger/primitives/$swagger.spec.ts +193 -0
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +19 -12
- package/src/sms/primitives/$sms.spec.ts +165 -0
- package/src/sms/providers/LocalSmsProvider.spec.ts +224 -0
- package/src/sms/providers/MemorySmsProvider.spec.ts +193 -0
- package/src/thread/primitives/$thread.spec.ts +186 -0
- package/src/topic/core/__tests__/shared.ts +144 -0
- package/src/topic/core/providers/MemoryTopicProvider.spec.ts +23 -0
- package/src/topic/redis/providers/RedisTopicProvider.spec.ts +23 -0
- package/src/vite/helpers/importViteReact.ts +13 -0
- package/src/vite/index.ts +1 -21
- package/src/vite/plugins/viteAlephaDev.ts +32 -5
- package/src/vite/plugins/viteAlephaSsrPreload.ts +222 -0
- package/src/vite/tasks/buildClient.ts +11 -0
- package/src/vite/tasks/buildServer.ts +47 -3
- package/src/vite/tasks/devServer.ts +69 -0
- package/src/vite/tasks/index.ts +2 -1
- package/src/vite/tasks/runAlepha.ts +7 -1
- package/src/websocket/__tests__/$websocket-new.spec.ts +195 -0
- package/src/websocket/primitives/$channel.spec.ts +30 -0
- package/src/cli/assets/viteConfigTs.ts +0 -14
- package/src/cli/commands/run.ts +0 -24
- package/src/vite/plugins/viteAlepha.ts +0 -37
- package/src/vite/plugins/viteAlephaBuild.ts +0 -281
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/api/jobs/schemas/jobExecutionQuerySchema.ts","../../../src/api/jobs/entities/jobExecutions.ts","../../../src/api/jobs/schemas/jobExecutionResourceSchema.ts","../../../src/api/jobs/schemas/triggerJobSchema.ts","../../../src/api/jobs/providers/JobProvider.ts","../../../src/api/jobs/primitives/$job.ts","../../../src/api/jobs/services/JobService.ts","../../../src/api/jobs/controllers/JobController.ts","../../../src/api/jobs/index.ts"],"sourcesContent":["import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const jobExecutionQuerySchema = t.extend(pageQuerySchema, {\n status: t.optional(t.enum([\"STARTED\", \"FAILED\", \"COMPLETED\"])),\n job: t.optional(\n t.text({\n description: \"Filter by job name\",\n }),\n ),\n});\n\nexport type JobExecutionQuery = Static<typeof jobExecutionQuerySchema>;\n","import { type Static, t } from \"alepha\";\nimport { logEntrySchema } from \"alepha/logger\";\nimport { $entity, pg } from \"alepha/orm\";\n\nexport const jobExecutions = $entity({\n name: \"job_executions\",\n schema: t.object({\n id: pg.primaryKey(t.uuid()),\n version: pg.version(),\n createdAt: pg.createdAt(),\n updatedAt: pg.updatedAt(),\n finishedAt: t.optional(t.datetime()),\n job: t.string(),\n status: t.enum([\"STARTED\", \"FAILED\", \"COMPLETED\"]),\n error: t.optional(t.string()),\n logs: t.optional(t.array(logEntrySchema)),\n }),\n});\n\nexport type JobExecutionEntity = Static<typeof jobExecutions.schema>;\n","import { type Static, t } from \"alepha\";\nimport { jobExecutions } from \"../entities/jobExecutions.ts\";\n\nexport const jobExecutionResourceSchema = t.extend(\n jobExecutions.schema,\n {},\n {\n title: \"JobExecutionResource\",\n description:\n \"A job execution resource representing the execution details of a job.\",\n },\n);\n\nexport type JobExecutionResource = Static<typeof jobExecutionResourceSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const triggerJobSchema = t.object({\n name: t.string(),\n});\n\nexport type TriggerJob = Static<typeof triggerJobSchema>;\n","import { $env, $inject, Alepha, type Async, type Static, t } from \"alepha\";\nimport { type DateTime, DateTimeProvider } from \"alepha/datetime\";\nimport { $lock, type LockPrimitive } from \"alepha/lock\";\nimport type { LogEntry } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport { CronProvider } from \"alepha/scheduler\";\nimport { jobExecutions } from \"../entities/jobExecutions.ts\";\n\nconst envSchema = t.object({\n JOB_PREFIX: t.optional(\n t.text({\n description: \"Prefix for job lock keys\",\n }),\n ),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\n/**\n * Provider for job management and execution.\n * Handles job lifecycle, execution tracking, log capturing, and event emission.\n */\nexport class JobProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly cronProvider = $inject(CronProvider);\n protected readonly executionRepository = $repository(jobExecutions);\n protected readonly env = $env(envSchema);\n protected readonly logs = new Map<string, LogEntry[]>();\n protected readonly jobs = new Map<string, JobRegistration>();\n\n /**\n * Register and set up a job for execution (called during primitive initialization).\n */\n public registerJob(options: Job): JobRegistration {\n const jobName = options.name;\n\n // Set up log capturing for this job (only once)\n if (this.jobs.size === 0) {\n this.alepha.events.on(\"log\", ({ entry }) => {\n const context = entry.context;\n if (!context) {\n return;\n }\n\n const entries = this.logs.get(context);\n if (!entries) {\n return;\n }\n\n entries.push(entry);\n this.logs.set(context, entries);\n });\n }\n\n // Create lock primitive if locking is enabled\n const lockPrimitive =\n options.lock !== false\n ? $lock({\n name: () => {\n const prefix = this.env.JOB_PREFIX\n ? `${this.env.JOB_PREFIX}:`\n : \"\";\n return `${prefix}job:${jobName}`;\n },\n handler: async () => {\n await this.executeJob(jobName, options.handler);\n },\n })\n : null;\n\n const registration: JobRegistration = {\n name: jobName,\n options,\n lockPrimitive,\n };\n\n this.jobs.set(jobName, registration);\n\n // Set up cron scheduling if provided\n if (options.cron) {\n this.cronProvider.createCronJob(jobName, options.cron, () =>\n this.triggerJob(jobName),\n );\n }\n\n return registration;\n }\n\n /**\n * Trigger a job by name.\n */\n public async triggerJob(jobName: string): Promise<void> {\n const registration = this.jobs.get(jobName);\n if (!registration) {\n throw new Error(`Job not registered: ${jobName}`);\n }\n\n // Execute handler with or without lock\n if (registration.options.lock !== false && registration.lockPrimitive) {\n await registration.lockPrimitive.run();\n } else {\n await this.executeJob(jobName, registration.options.handler);\n }\n }\n\n /**\n * Execute a job handler (called by the job primitive).\n */\n public async executeJob(\n jobName: string,\n handler: (args: { now: DateTime }) => Async<void>,\n ): Promise<void> {\n if (!this.alepha.isStarted()) {\n return;\n }\n\n const context = this.alepha.context.createContextId();\n\n await this.alepha.context.run(\n async () => {\n try {\n const now = this.dateTimeProvider.now();\n\n // Initialize log collection for this context\n this.logs.set(context, []);\n\n // Create execution record\n await this.executionRepository.create({\n job: jobName,\n status: \"STARTED\",\n });\n\n await this.alepha.events.emit(\"scheduler:begin\", {\n name: jobName,\n now,\n context,\n });\n\n // Execute the handler\n await handler({ now });\n\n // Update execution as completed\n const logs = this.logs.get(context) || [];\n const exec = await this.executionRepository.findOne({\n where: {\n job: jobName,\n status: \"STARTED\",\n },\n });\n\n exec.status = \"COMPLETED\";\n exec.logs = logs;\n exec.finishedAt = this.dateTimeProvider.nowISOString();\n\n await this.executionRepository.save(exec);\n\n await this.alepha.events.emit(\n \"scheduler:success\",\n {\n name: jobName,\n context,\n },\n {\n catch: true,\n },\n );\n } catch (error) {\n // Update execution as failed\n const logs = this.logs.get(context) || [];\n const exec = await this.executionRepository.findOne({\n where: {\n job: jobName,\n status: \"STARTED\",\n },\n });\n\n exec.status = \"FAILED\";\n exec.error = (error as Error).message;\n exec.logs = logs;\n exec.finishedAt = this.dateTimeProvider.nowISOString();\n\n await this.executionRepository.save(exec);\n\n await this.alepha.events.emit(\n \"scheduler:error\",\n {\n name: jobName,\n error: error as Error,\n context,\n },\n {\n catch: true,\n },\n );\n\n // Don't re-throw, jobs should handle errors gracefully\n }\n\n // Clean up logs\n this.logs.delete(context);\n\n await this.alepha.events.emit(\n \"scheduler:end\",\n {\n name: jobName,\n context,\n },\n {\n catch: true,\n },\n );\n },\n {\n context,\n },\n );\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface Job {\n /**\n * Name of the job.\n */\n name: string;\n\n /**\n * Optional description of the job.\n */\n description?: string;\n\n /**\n * Function to run on schedule.\n */\n handler: (args: { now: DateTime }) => Async<void>;\n\n /**\n * Cron expression to run the job.\n */\n cron?: string;\n\n /**\n * If true, the job will be locked and only one instance will run at a time.\n * You probably need to import {@link AlephaLockRedis} for distributed locking.\n *\n * @default true\n */\n lock?: boolean;\n\n /**\n * Optional prefix for job lock keys.\n */\n lockPrefix?: string;\n}\n\nexport interface JobRegistration {\n name: string;\n options: Job;\n lockPrimitive: LockPrimitive<() => Promise<void>> | null;\n}\n","import { $inject, createPrimitive, KIND, Primitive } from \"alepha\";\nimport type { DateTime } from \"alepha/datetime\";\nimport { type Job, JobProvider } from \"../providers/JobProvider.ts\";\n\n/**\n * Job primitive - a drop-in replacement for $scheduler with built-in execution tracking.\n */\nexport const $job = (options: JobPrimitiveOptions): JobPrimitive => {\n return createPrimitive(JobPrimitive, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type JobPrimitiveOptions = Omit<Job, \"name\"> & {\n /**\n * Name of the job. Defaults to the primitive property name.\n */\n name?: string;\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class JobPrimitive extends Primitive<JobPrimitiveOptions> {\n protected readonly jobProvider = $inject(JobProvider);\n\n public get name(): string {\n return (\n this.options.name ??\n `${this.config.service.name}.${this.config.propertyKey}`\n );\n }\n\n protected onInit() {\n // Register job with JobProvider\n this.jobProvider.registerJob({\n ...this.options,\n name: this.name,\n });\n }\n\n public async trigger(): Promise<void> {\n await this.jobProvider.triggerJob(this.name);\n }\n}\n\n$job[KIND] = JobPrimitive;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface JobHandlerArguments {\n now: DateTime;\n}\n","import { $inject, Alepha } from \"alepha\";\nimport { $repository } from \"alepha/orm\";\nimport { jobExecutions } from \"../entities/jobExecutions.ts\";\nimport { $job } from \"../primitives/$job.ts\";\nimport type { JobExecutionQuery } from \"../schemas/jobExecutionQuerySchema.ts\";\n\nexport class JobService {\n protected readonly alepha = $inject(Alepha);\n protected readonly executionRepository = $repository(jobExecutions);\n\n public async getJobs(): Promise<string[]> {\n const jobPrimitives = this.alepha.primitives($job);\n return jobPrimitives.map((job) => job.name);\n }\n\n public async getJobExecutions(query: JobExecutionQuery = {}) {\n query.sort ??= \"-createdAt\";\n\n const where = this.executionRepository.createQueryWhere();\n\n if (query.job) {\n where.job = { eq: query.job };\n }\n\n if (query.status) {\n where.status = { eq: query.status };\n }\n\n return await this.executionRepository.paginate(\n query,\n { where },\n { count: true },\n );\n }\n\n public async triggerJob(name: string): Promise<{ ok: boolean }> {\n const jobPrimitives = this.alepha.primitives($job);\n const job = jobPrimitives.find((j) => j.name === name);\n\n if (!job) {\n throw new Error(`Job not found: ${name}`);\n }\n\n await job.trigger();\n return { ok: true };\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { jobExecutionQuerySchema } from \"../schemas/jobExecutionQuerySchema.ts\";\nimport { jobExecutionResourceSchema } from \"../schemas/jobExecutionResourceSchema.ts\";\nimport { triggerJobSchema } from \"../schemas/triggerJobSchema.ts\";\nimport { JobService } from \"../services/JobService.ts\";\n\nexport class JobController {\n protected readonly url: string = \"/jobs\";\n protected readonly group: string = \"jobs\";\n protected readonly jobService = $inject(JobService);\n\n public readonly getJobs = $action({\n path: this.url,\n group: this.group,\n schema: {\n response: t.array(t.string()),\n },\n handler: () => this.jobService.getJobs(),\n });\n\n public readonly getJobExecutions = $action({\n path: `${this.url}/executions`,\n group: this.group,\n schema: {\n query: jobExecutionQuerySchema,\n response: t.page(jobExecutionResourceSchema),\n },\n handler: ({ query }) => this.jobService.getJobExecutions(query),\n });\n\n public readonly triggerJob = $action({\n method: \"POST\",\n path: `${this.url}/trigger`,\n group: this.group,\n schema: {\n body: triggerJobSchema,\n response: okSchema,\n },\n handler: ({ body }) => this.jobService.triggerJob(body.name),\n });\n}\n","import { $module } from \"alepha\";\nimport { JobController } from \"./controllers/JobController.ts\";\nimport { JobProvider } from \"./providers/JobProvider.ts\";\nimport { JobService } from \"./services/JobService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./controllers/JobController.ts\";\nexport * from \"./entities/jobExecutions.ts\";\nexport * from \"./primitives/$job.ts\";\nexport * from \"./providers/JobProvider.ts\";\nexport * from \"./schemas/jobExecutionQuerySchema.ts\";\nexport * from \"./schemas/jobExecutionResourceSchema.ts\";\nexport * from \"./schemas/triggerJobSchema.ts\";\nexport * from \"./services/JobService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Provides job management API endpoints for Alepha applications.\n *\n * This module includes job queue operations, job status monitoring,\n * and background task management capabilities.\n *\n * @module alepha.api.jobs\n */\nexport const AlephaApiJobs = $module({\n name: \"alepha.api.jobs\",\n services: [JobController, JobProvider, JobService],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/api/jobs/schemas/jobExecutionQuerySchema.ts","../../../src/api/jobs/entities/jobExecutions.ts","../../../src/api/jobs/schemas/jobExecutionResourceSchema.ts","../../../src/api/jobs/schemas/triggerJobSchema.ts","../../../src/api/jobs/providers/JobProvider.ts","../../../src/api/jobs/primitives/$job.ts","../../../src/api/jobs/services/JobService.ts","../../../src/api/jobs/controllers/AdminJobController.ts","../../../src/api/jobs/index.ts"],"sourcesContent":["import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const jobExecutionQuerySchema = t.extend(pageQuerySchema, {\n status: t.optional(t.enum([\"STARTED\", \"FAILED\", \"COMPLETED\"])),\n job: t.optional(\n t.text({\n description: \"Filter by job name\",\n }),\n ),\n});\n\nexport type JobExecutionQuery = Static<typeof jobExecutionQuerySchema>;\n","import { type Static, t } from \"alepha\";\nimport { logEntrySchema } from \"alepha/logger\";\nimport { $entity, db } from \"alepha/orm\";\n\nexport const jobExecutions = $entity({\n name: \"job_executions\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n finishedAt: t.optional(t.datetime()),\n job: t.string(),\n status: t.enum([\"STARTED\", \"FAILED\", \"COMPLETED\"]),\n error: t.optional(t.string()),\n logs: t.optional(t.array(logEntrySchema)),\n }),\n});\n\nexport type JobExecutionEntity = Static<typeof jobExecutions.schema>;\n","import { type Static, t } from \"alepha\";\nimport { jobExecutions } from \"../entities/jobExecutions.ts\";\n\nexport const jobExecutionResourceSchema = t.extend(\n jobExecutions.schema,\n {},\n {\n title: \"JobExecutionResource\",\n description:\n \"A job execution resource representing the execution details of a job.\",\n },\n);\n\nexport type JobExecutionResource = Static<typeof jobExecutionResourceSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const triggerJobSchema = t.object({\n name: t.string(),\n});\n\nexport type TriggerJob = Static<typeof triggerJobSchema>;\n","import { $env, $inject, Alepha, type Async, type Static, t } from \"alepha\";\nimport { type DateTime, DateTimeProvider } from \"alepha/datetime\";\nimport { $lock, type LockPrimitive } from \"alepha/lock\";\nimport type { LogEntry } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport { CronProvider } from \"alepha/scheduler\";\nimport { jobExecutions } from \"../entities/jobExecutions.ts\";\n\nconst envSchema = t.object({\n JOB_PREFIX: t.optional(\n t.text({\n description: \"Prefix for job lock keys\",\n }),\n ),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\n/**\n * Provider for job management and execution.\n * Handles job lifecycle, execution tracking, log capturing, and event emission.\n */\nexport class JobProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly cronProvider = $inject(CronProvider);\n protected readonly executionRepository = $repository(jobExecutions);\n protected readonly env = $env(envSchema);\n protected readonly logs = new Map<string, LogEntry[]>();\n protected readonly jobs = new Map<string, JobRegistration>();\n\n /**\n * Register and set up a job for execution (called during primitive initialization).\n */\n public registerJob(options: Job): JobRegistration {\n const jobName = options.name;\n\n // Set up log capturing for this job (only once)\n if (this.jobs.size === 0) {\n this.alepha.events.on(\"log\", ({ entry }) => {\n const context = entry.context;\n if (!context) {\n return;\n }\n\n const entries = this.logs.get(context);\n if (!entries) {\n return;\n }\n\n entries.push(entry);\n this.logs.set(context, entries);\n });\n }\n\n // Create lock primitive if locking is enabled\n const lockPrimitive =\n options.lock !== false\n ? $lock({\n name: () => {\n const prefix = this.env.JOB_PREFIX\n ? `${this.env.JOB_PREFIX}:`\n : \"\";\n return `${prefix}job:${jobName}`;\n },\n handler: async () => {\n await this.executeJob(jobName, options.handler);\n },\n })\n : null;\n\n const registration: JobRegistration = {\n name: jobName,\n options,\n lockPrimitive,\n };\n\n this.jobs.set(jobName, registration);\n\n // Set up cron scheduling if provided\n if (options.cron) {\n this.cronProvider.createCronJob(jobName, options.cron, () =>\n this.triggerJob(jobName),\n );\n }\n\n return registration;\n }\n\n /**\n * Trigger a job by name.\n */\n public async triggerJob(jobName: string): Promise<void> {\n const registration = this.jobs.get(jobName);\n if (!registration) {\n throw new Error(`Job not registered: ${jobName}`);\n }\n\n // Execute handler with or without lock\n if (registration.options.lock !== false && registration.lockPrimitive) {\n await registration.lockPrimitive.run();\n } else {\n await this.executeJob(jobName, registration.options.handler);\n }\n }\n\n /**\n * Execute a job handler (called by the job primitive).\n */\n public async executeJob(\n jobName: string,\n handler: (args: { now: DateTime }) => Async<void>,\n ): Promise<void> {\n if (!this.alepha.isStarted()) {\n return;\n }\n\n const context = this.alepha.context.createContextId();\n\n await this.alepha.context.run(\n async () => {\n try {\n const now = this.dateTimeProvider.now();\n\n // Initialize log collection for this context\n this.logs.set(context, []);\n\n // Create execution record\n await this.executionRepository.create({\n job: jobName,\n status: \"STARTED\",\n });\n\n await this.alepha.events.emit(\"scheduler:begin\", {\n name: jobName,\n now,\n context,\n });\n\n // Execute the handler\n await handler({ now });\n\n // Update execution as completed\n const logs = this.logs.get(context) || [];\n const exec = await this.executionRepository.findOne({\n where: {\n job: jobName,\n status: \"STARTED\",\n },\n });\n\n exec.status = \"COMPLETED\";\n exec.logs = logs;\n exec.finishedAt = this.dateTimeProvider.nowISOString();\n\n await this.executionRepository.save(exec);\n\n await this.alepha.events.emit(\n \"scheduler:success\",\n {\n name: jobName,\n context,\n },\n {\n catch: true,\n },\n );\n } catch (error) {\n // Update execution as failed\n const logs = this.logs.get(context) || [];\n const exec = await this.executionRepository.findOne({\n where: {\n job: jobName,\n status: \"STARTED\",\n },\n });\n\n exec.status = \"FAILED\";\n exec.error = (error as Error).message;\n exec.logs = logs;\n exec.finishedAt = this.dateTimeProvider.nowISOString();\n\n await this.executionRepository.save(exec);\n\n await this.alepha.events.emit(\n \"scheduler:error\",\n {\n name: jobName,\n error: error as Error,\n context,\n },\n {\n catch: true,\n },\n );\n\n // Don't re-throw, jobs should handle errors gracefully\n }\n\n // Clean up logs\n this.logs.delete(context);\n\n await this.alepha.events.emit(\n \"scheduler:end\",\n {\n name: jobName,\n context,\n },\n {\n catch: true,\n },\n );\n },\n {\n context,\n },\n );\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface Job {\n /**\n * Name of the job.\n */\n name: string;\n\n /**\n * Optional description of the job.\n */\n description?: string;\n\n /**\n * Function to run on schedule.\n */\n handler: (args: { now: DateTime }) => Async<void>;\n\n /**\n * Cron expression to run the job.\n */\n cron?: string;\n\n /**\n * If true, the job will be locked and only one instance will run at a time.\n * You probably need to import {@link AlephaLockRedis} for distributed locking.\n *\n * @default true\n */\n lock?: boolean;\n\n /**\n * Optional prefix for job lock keys.\n */\n lockPrefix?: string;\n}\n\nexport interface JobRegistration {\n name: string;\n options: Job;\n lockPrimitive: LockPrimitive<() => Promise<void>> | null;\n}\n","import { $inject, createPrimitive, KIND, Primitive } from \"alepha\";\nimport type { DateTime } from \"alepha/datetime\";\nimport { type Job, JobProvider } from \"../providers/JobProvider.ts\";\n\n/**\n * Job primitive - a drop-in replacement for $scheduler with built-in execution tracking.\n */\nexport const $job = (options: JobPrimitiveOptions): JobPrimitive => {\n return createPrimitive(JobPrimitive, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport type JobPrimitiveOptions = Omit<Job, \"name\"> & {\n /**\n * Name of the job. Defaults to the primitive property name.\n */\n name?: string;\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class JobPrimitive extends Primitive<JobPrimitiveOptions> {\n protected readonly jobProvider = $inject(JobProvider);\n\n public get name(): string {\n return (\n this.options.name ??\n `${this.config.service.name}.${this.config.propertyKey}`\n );\n }\n\n protected onInit() {\n // Register job with JobProvider\n this.jobProvider.registerJob({\n ...this.options,\n name: this.name,\n });\n }\n\n public async trigger(): Promise<void> {\n await this.jobProvider.triggerJob(this.name);\n }\n}\n\n$job[KIND] = JobPrimitive;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface JobHandlerArguments {\n now: DateTime;\n}\n","import { $inject, Alepha } from \"alepha\";\nimport { $repository } from \"alepha/orm\";\nimport { jobExecutions } from \"../entities/jobExecutions.ts\";\nimport { $job } from \"../primitives/$job.ts\";\nimport type { JobExecutionQuery } from \"../schemas/jobExecutionQuerySchema.ts\";\n\nexport class JobService {\n protected readonly alepha = $inject(Alepha);\n protected readonly executionRepository = $repository(jobExecutions);\n\n public async getJobs(): Promise<string[]> {\n const jobPrimitives = this.alepha.primitives($job);\n return jobPrimitives.map((job) => job.name);\n }\n\n public async getJobExecutions(query: JobExecutionQuery = {}) {\n query.sort ??= \"-createdAt\";\n\n const where = this.executionRepository.createQueryWhere();\n\n if (query.job) {\n where.job = { eq: query.job };\n }\n\n if (query.status) {\n where.status = { eq: query.status };\n }\n\n return await this.executionRepository.paginate(\n query,\n { where },\n { count: true },\n );\n }\n\n public async triggerJob(name: string): Promise<{ ok: boolean }> {\n const jobPrimitives = this.alepha.primitives($job);\n const job = jobPrimitives.find((j) => j.name === name);\n\n if (!job) {\n throw new Error(`Job not found: ${name}`);\n }\n\n await job.trigger();\n return { ok: true };\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { jobExecutionQuerySchema } from \"../schemas/jobExecutionQuerySchema.ts\";\nimport { jobExecutionResourceSchema } from \"../schemas/jobExecutionResourceSchema.ts\";\nimport { triggerJobSchema } from \"../schemas/triggerJobSchema.ts\";\nimport { JobService } from \"../services/JobService.ts\";\n\nexport class AdminJobController {\n protected readonly url: string = \"/jobs\";\n protected readonly group: string = \"admin:jobs\";\n protected readonly jobService = $inject(JobService);\n\n public readonly getJobs = $action({\n path: this.url,\n group: this.group,\n schema: {\n response: t.array(t.string()),\n },\n handler: () => this.jobService.getJobs(),\n });\n\n public readonly getJobExecutions = $action({\n path: `${this.url}/executions`,\n group: this.group,\n schema: {\n query: jobExecutionQuerySchema,\n response: t.page(jobExecutionResourceSchema),\n },\n handler: ({ query }) => this.jobService.getJobExecutions(query),\n });\n\n public readonly triggerJob = $action({\n method: \"POST\",\n path: `${this.url}/trigger`,\n group: this.group,\n schema: {\n body: triggerJobSchema,\n response: okSchema,\n },\n handler: ({ body }) => this.jobService.triggerJob(body.name),\n });\n}\n","import { $module } from \"alepha\";\nimport { AdminJobController } from \"./controllers/AdminJobController.ts\";\nimport { JobProvider } from \"./providers/JobProvider.ts\";\nimport { JobService } from \"./services/JobService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./controllers/AdminJobController.ts\";\nexport * from \"./entities/jobExecutions.ts\";\nexport * from \"./primitives/$job.ts\";\nexport * from \"./providers/JobProvider.ts\";\nexport * from \"./schemas/jobExecutionQuerySchema.ts\";\nexport * from \"./schemas/jobExecutionResourceSchema.ts\";\nexport * from \"./schemas/triggerJobSchema.ts\";\nexport * from \"./services/JobService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Provides job management API endpoints for Alepha applications.\n *\n * This module includes job queue operations, job status monitoring,\n * and background task management capabilities.\n *\n * @module alepha.api.jobs\n */\nexport const AlephaApiJobs = $module({\n name: \"alepha.api.jobs\",\n services: [AdminJobController, JobProvider, JobService],\n});\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $env, $inject, $module, Alepha, AlephaError, KIND, Primitive, createPrimitive, t } from "alepha";
|
|
2
|
-
import { $entity, $repository, pageQuerySchema, pg } from "alepha/orm";
|
|
3
2
|
import { $action } from "alepha/server";
|
|
3
|
+
import { $entity, $repository, db, pageQuerySchema } from "alepha/orm";
|
|
4
4
|
import { $batch } from "alepha/batch";
|
|
5
5
|
import { DateTimeProvider } from "alepha/datetime";
|
|
6
6
|
import { $logger } from "alepha/logger";
|
|
@@ -12,10 +12,10 @@ import { SmsProvider } from "alepha/sms";
|
|
|
12
12
|
const notifications = $entity({
|
|
13
13
|
name: "notifications",
|
|
14
14
|
schema: t.object({
|
|
15
|
-
id:
|
|
16
|
-
version:
|
|
17
|
-
createdAt:
|
|
18
|
-
updatedAt:
|
|
15
|
+
id: db.primaryKey(t.uuid()),
|
|
16
|
+
version: db.version(),
|
|
17
|
+
createdAt: db.createdAt(),
|
|
18
|
+
updatedAt: db.updatedAt(),
|
|
19
19
|
type: t.enum(["email", "sms"]),
|
|
20
20
|
template: t.text(),
|
|
21
21
|
category: t.optional(t.text({ description: "For grouping related notifications (e.g., 'authentication', 'marketing'). Contact can filter notifications by category." })),
|
|
@@ -333,10 +333,10 @@ var NotificationService = class {
|
|
|
333
333
|
};
|
|
334
334
|
|
|
335
335
|
//#endregion
|
|
336
|
-
//#region ../../src/api/notifications/controllers/
|
|
337
|
-
var
|
|
336
|
+
//#region ../../src/api/notifications/controllers/AdminNotificationController.ts
|
|
337
|
+
var AdminNotificationController = class {
|
|
338
338
|
url = "/notifications";
|
|
339
|
-
group = "notifications";
|
|
339
|
+
group = "admin:notifications";
|
|
340
340
|
notificationService = $inject(NotificationService);
|
|
341
341
|
/**
|
|
342
342
|
* Find notifications with pagination and filtering.
|
|
@@ -347,7 +347,7 @@ var NotificationController = class {
|
|
|
347
347
|
description: "Find notifications with pagination and filtering",
|
|
348
348
|
schema: {
|
|
349
349
|
query: notificationQuerySchema,
|
|
350
|
-
response:
|
|
350
|
+
response: t.page(notifications.schema)
|
|
351
351
|
},
|
|
352
352
|
handler: ({ query }) => this.notificationService.findNotifications(query)
|
|
353
353
|
});
|
|
@@ -378,5 +378,5 @@ const AlephaApiNotifications = $module({
|
|
|
378
378
|
});
|
|
379
379
|
|
|
380
380
|
//#endregion
|
|
381
|
-
export {
|
|
381
|
+
export { AdminNotificationController, AlephaApiNotifications, notificationContactPreferencesSchema, notificationContactSchema, notificationCreateSchema, notificationQuerySchema, notifications };
|
|
382
382
|
//# sourceMappingURL=index.browser.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.js","names":["notifications"],"sources":["../../../src/api/notifications/entities/notifications.ts","../../../src/api/notifications/schemas/notificationQuerySchema.ts","../../../src/api/notifications/primitives/$notification.ts","../../../src/api/notifications/services/NotificationSenderService.ts","../../../src/api/notifications/queues/NotificationQueues.ts","../../../src/api/notifications/schemas/notificationCreateSchema.ts","../../../src/api/notifications/services/NotificationService.ts","../../../src/api/notifications/controllers/NotificationController.ts","../../../src/api/notifications/schemas/notificationContactPreferencesSchema.ts","../../../src/api/notifications/schemas/notificationContactSchema.ts","../../../src/api/notifications/index.browser.ts"],"sourcesContent":["import { type Static, t } from \"alepha\";\nimport { $entity, pg } from \"alepha/orm\";\n\nexport const notifications = $entity({\n name: \"notifications\",\n schema: t.object({\n id: pg.primaryKey(t.uuid()),\n\n version: pg.version(),\n\n createdAt: pg.createdAt(),\n\n updatedAt: pg.updatedAt(),\n\n // -----------------------------------------------------------------------------------------------------------------\n\n type: t.enum([\"email\", \"sms\"]),\n\n template: t.text(), // e.g. 'resetPassword'\n\n category: t.optional(\n t.text({\n description:\n \"For grouping related notifications (e.g., 'authentication', 'marketing'). Contact can filter notifications by category.\",\n }),\n ),\n\n critical: t.optional(\n t.boolean({\n description:\n \"Prioritize delivery of this notification. Set to true for important system alerts.\",\n }),\n ),\n\n sensitive: t.optional(\n t.boolean({\n description:\n \"Message won't be logged or stored in plain text. Set to true when notification contains passwords or codes.\",\n }),\n ),\n\n // -----------------------------------------------------------------------------------------------------------------\n\n contact: t.text(), // e.g. email address or phone number or user ID or whatever\n\n variables: t.optional(t.record(t.text(), t.any())),\n\n scheduledAt: t.optional(\n t.datetime({\n description:\n \"When set, the notification will be sent at or after this date/time.\",\n }),\n ),\n\n // -----------------------------------------------------------------------------------------------------------------\n\n sentAt: t.optional(t.datetime()),\n\n error: t.optional(\n t.object({\n at: t.datetime(),\n name: t.text(),\n message: t.text({ size: \"rich\" }),\n }),\n ),\n\n // TODO: retryCount, lastRetryAt, etc.\n }),\n});\n\nexport type NotificationEntity = Static<typeof notifications.schema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const notificationQuerySchema = t.extend(pageQuerySchema, {\n type: t.optional(t.enum([\"email\", \"sms\"])),\n template: t.optional(t.string()),\n contact: t.optional(t.string()),\n category: t.optional(t.string()),\n status: t.optional(t.enum([\"pending\", \"sent\", \"failed\"])),\n});\n\nexport type NotificationQuery = Static<typeof notificationQuerySchema>;\n","import {\n $inject,\n createPrimitive,\n KIND,\n Primitive,\n type Static,\n type StaticEncode,\n type TObject,\n} from \"alepha\";\nimport { NotificationService } from \"../services/NotificationService.ts\";\n\n/**\n * Creates a notification primitive for managing email/SMS notification templates.\n *\n * Provides type-safe, reusable notification templates with multi-language support,\n * variable substitution, and categorization for different notification channels.\n *\n * @example\n * ```ts\n * class NotificationTemplates {\n * welcomeEmail = $notification({\n * name: \"welcome-email\",\n * category: \"onboarding\",\n * schema: t.object({ username: t.text(), activationLink: t.text() }),\n * email: {\n * subject: \"Welcome to our platform!\",\n * body: (vars) => `Hello ${vars.username}, click: ${vars.activationLink}`\n * }\n * });\n *\n * async sendWelcome(user: User) {\n * await this.welcomeEmail.push({\n * variables: { username: user.name, activationLink: generateLink() },\n * contact: user.email\n * });\n * }\n * }\n * ```\n */\nexport const $notification = <T extends TObject>(\n options: NotificationPrimitiveOptions<T>,\n) => createPrimitive(NotificationPrimitive<T>, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface NotificationPrimitiveOptions<T extends TObject>\n extends NotificationMessage<T> {\n name?: string;\n description?: string;\n category?: string;\n critical?: boolean;\n sensitive?: boolean;\n translations?: {\n // e.g., \"en\", \"fr\", even \"en-US\"\n [lang: string]: NotificationMessage<T>;\n };\n schema: T;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class NotificationPrimitive<T extends TObject> extends Primitive<\n NotificationPrimitiveOptions<T>\n> {\n protected readonly notificationService = $inject(NotificationService);\n\n public get name() {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n public async push(options: NotificationPushOptions<T>) {\n if (this.options.email) {\n await this.notificationService.createNotification({\n ...options,\n type: \"email\",\n template: this.name,\n });\n }\n }\n\n public configure(options: Partial<NotificationPrimitiveOptions<T>>) {\n Object.assign(this.options, options);\n }\n}\n\n$notification[KIND] = NotificationPrimitive;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface NotificationPushOptions<T extends TObject> {\n variables: StaticEncode<T>;\n contact: string;\n}\n\nexport interface NotificationMessage<T extends TObject> {\n email?: {\n subject: string;\n body: string | ((variables: Static<T>) => string);\n };\n sms?: {\n message: string | ((variables: Static<T>) => string);\n };\n}\n","import { $inject, Alepha, AlephaError } from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { EmailProvider } from \"alepha/email\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport { SmsProvider } from \"alepha/sms\";\nimport {\n type NotificationEntity,\n notifications,\n} from \"../entities/notifications.ts\";\nimport { $notification } from \"../primitives/$notification.ts\";\n\nexport class NotificationSenderService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly notificationRepository = $repository(notifications);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly emailProvider = $inject(EmailProvider);\n protected readonly smsProvider = $inject(SmsProvider);\n\n public async send(notificationId: string | NotificationEntity) {\n this.log.trace(\"Sending notification\", {\n notificationId:\n typeof notificationId === \"string\" ? notificationId : notificationId.id,\n });\n\n const notification =\n typeof notificationId === \"string\"\n ? await this.notificationRepository.findById(notificationId)\n : notificationId;\n\n if (notification.sentAt) {\n this.log.debug(\"Notification already sent\", {\n notificationId: notification.id,\n sentAt: notification.sentAt,\n });\n return;\n }\n\n this.log.debug(\"Processing notification\", {\n id: notification.id,\n type: notification.type,\n template: notification.template,\n contact: notification.contact,\n });\n\n try {\n if (notification.type === \"email\") {\n await this.emailProvider.send(this.renderEmail(notification));\n notification.sentAt = this.dateTimeProvider.nowISOString();\n this.log.info(\"Email notification sent\", {\n id: notification.id,\n template: notification.template,\n contact: notification.contact,\n });\n }\n if (notification.type === \"sms\") {\n await this.smsProvider.send(this.renderSms(notification));\n notification.sentAt = this.dateTimeProvider.nowISOString();\n this.log.info(\"SMS notification sent\", {\n id: notification.id,\n template: notification.template,\n contact: notification.contact,\n });\n }\n } catch (e) {\n this.log.error(\"Failed to send notification\", {\n id: notification.id,\n type: notification.type,\n template: notification.template,\n contact: notification.contact,\n error: e,\n });\n if (e instanceof Error) {\n notification.error = {\n at: this.dateTimeProvider.nowISOString(),\n name: e.name,\n message: e.message,\n };\n }\n } finally {\n await this.notificationRepository.save(notification);\n }\n }\n\n public renderSms(notification: NotificationEntity) {\n this.log.trace(\"Rendering SMS notification\", {\n id: notification.id,\n template: notification.template,\n });\n\n const { variables, contact, template } = this.load(notification);\n\n const sms = template.options.sms;\n if (!sms) {\n this.log.error(\"Notification template has no SMS defined\", {\n id: notification.id,\n template: notification.template,\n });\n throw new AlephaError(\n `Notification template ${notification.template} has no sms defined`,\n );\n }\n\n this.log.debug(\"Rendering SMS\", {\n template: notification.template,\n contact,\n });\n\n const message =\n typeof sms.message === \"function\"\n ? sms.message(variables as any)\n : sms.message;\n\n return {\n to: contact,\n message,\n };\n }\n\n public renderEmail(notification: NotificationEntity) {\n this.log.trace(\"Rendering email notification\", {\n id: notification.id,\n template: notification.template,\n });\n\n const { variables, contact, template } = this.load(notification);\n\n const email = template.options.email;\n if (!email) {\n this.log.error(\"Notification template has no email defined\", {\n id: notification.id,\n template: notification.template,\n });\n throw new AlephaError(\n `Notification template ${notification.template} has no email defined`,\n );\n }\n\n this.log.debug(\"Rendering email\", {\n template: notification.template,\n contact,\n subject: email.subject,\n });\n\n const subject = email.subject;\n\n const body =\n typeof email.body === \"function\"\n ? email.body(variables as any)\n : email.body;\n\n return {\n to: contact,\n subject,\n body,\n };\n }\n\n protected load(notification: NotificationEntity) {\n const variables = notification.variables || {};\n const contact = notification.contact;\n const template = this.alepha\n .primitives($notification)\n .find((it) => it.name === notification.template);\n\n if (!template) {\n this.log.error(\"Notification template not found\", {\n id: notification.id,\n template: notification.template,\n });\n throw new AlephaError(\n `No notification template found for ${notification.template}`,\n );\n }\n\n return {\n template,\n variables,\n contact,\n };\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $queue } from \"alepha/queue\";\nimport { NotificationSenderService } from \"../services/NotificationSenderService.ts\";\n\nexport class NotificationQueues {\n protected readonly notificationSenderService = $inject(\n NotificationSenderService,\n );\n\n public readonly processNotification = $queue({\n description: \"Queue for processing notifications\",\n schema: t.object({\n notificationId: t.string({ format: \"uuid\" }),\n }),\n handler: async (message) => {\n await this.notificationSenderService.send(message.payload.notificationId);\n },\n });\n}\n","import { type Static, t } from \"alepha\";\nimport { notifications } from \"../entities/notifications.ts\";\n\nexport const notificationCreateSchema = t.pick(notifications.schema, [\n \"type\",\n \"contact\",\n \"template\",\n \"variables\",\n]);\n\nexport type NotificationCreate = Static<typeof notificationCreateSchema>;\n","import { $env, $inject, Alepha, type Static, t } from \"alepha\";\nimport { $batch } from \"alepha/batch\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository, type Page } from \"alepha/orm\";\nimport {\n type NotificationEntity,\n notifications,\n} from \"../entities/notifications.ts\";\nimport { NotificationQueues } from \"../queues/NotificationQueues.ts\";\nimport {\n type NotificationCreate,\n notificationCreateSchema,\n} from \"../schemas/notificationCreateSchema.ts\";\nimport type { NotificationQuery } from \"../schemas/notificationQuerySchema.ts\";\nimport { NotificationSenderService } from \"./NotificationSenderService.ts\";\n\nexport const notificationServiceEnvSchema = t.object({\n NOTIFICATION_QUEUE: t.optional(\n t.boolean({\n description:\n \"If true, notifications will be queued instead of sent immediately\",\n }),\n ),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof notificationServiceEnvSchema>> {}\n}\n\nexport class NotificationService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly env = $env(notificationServiceEnvSchema);\n protected readonly notificationRepository = $repository(notifications);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly notificationSenderService = $inject(\n NotificationSenderService,\n );\n\n public readonly notificationBatch = $batch({\n maxSize: 100,\n maxDuration: [15, \"seconds\"],\n schema: notificationCreateSchema,\n handler: async (notifications: NotificationCreate[]) => {\n this.log.debug(\"Processing notification batch\", {\n size: notifications.length,\n templates: [...new Set(notifications.map((n) => n.template))],\n });\n\n const entities =\n await this.notificationRepository.createMany(notifications);\n\n await this.alepha\n .inject(NotificationQueues)\n .processNotification.push(\n ...entities.map((it) => ({ notificationId: it.id })),\n );\n\n this.log.info(\"Notification batch queued\", {\n count: entities.length,\n ids: entities.map((it) => it.id),\n });\n },\n });\n\n public async findNotificationById(id: string) {\n this.log.trace(\"Finding notification by ID\", { id });\n return this.notificationRepository.findOne({ where: { id } });\n }\n\n public async findNotifications(\n q: NotificationQuery = {},\n ): Promise<Page<NotificationEntity>> {\n this.log.trace(\"Finding notifications\", { query: q });\n q.sort ??= \"-createdAt\";\n\n const where = this.notificationRepository.createQueryWhere();\n\n if (q.type) {\n where.type = { eq: q.type };\n }\n\n if (q.template) {\n where.template = { like: `%${q.template}%` };\n }\n\n if (q.contact) {\n where.contact = { like: `%${q.contact}%` };\n }\n\n if (q.category) {\n where.category = { eq: q.category };\n }\n\n if (q.status) {\n if (q.status === \"sent\") {\n where.sentAt = { isNotNull: true };\n where.error = { isNull: true };\n } else if (q.status === \"failed\") {\n where.error = { isNotNull: true };\n } else if (q.status === \"pending\") {\n where.sentAt = { isNull: true };\n where.error = { isNull: true };\n }\n }\n\n return this.notificationRepository.paginate(q, { where }, { count: true });\n }\n\n /**\n * Create a new notification.\n */\n public async createNotification(entry: NotificationCreate): Promise<void> {\n this.log.trace(\"Creating notification\", {\n template: entry.template,\n type: entry.type,\n contact: entry.contact,\n });\n\n if (\n this.env.NOTIFICATION_QUEUE !== true ||\n this.alepha.isServerless() ||\n this.alepha.isTest()\n ) {\n this.log.debug(\"Sending notification immediately\", {\n template: entry.template,\n type: entry.type,\n contact: entry.contact,\n });\n const notification = await this.notificationRepository.create(entry);\n await this.notificationSenderService.send(notification);\n return;\n }\n\n this.log.debug(\"Queuing notification to batch\", {\n template: entry.template,\n type: entry.type,\n contact: entry.contact,\n });\n\n this.notificationBatch.push(entry).catch((e) => {\n this.log.error(\"Failed to push notification to batch\", {\n template: entry.template,\n type: entry.type,\n contact: entry.contact,\n error: e,\n });\n });\n }\n}\n","import { $inject } from \"alepha\";\nimport { pg } from \"alepha/orm\";\nimport { $action } from \"alepha/server\";\nimport { notifications } from \"../entities/notifications.ts\";\nimport { notificationQuerySchema } from \"../schemas/notificationQuerySchema.ts\";\nimport { NotificationService } from \"../services/NotificationService.ts\";\n\nexport class NotificationController {\n protected readonly url = \"/notifications\";\n protected readonly group = \"notifications\";\n protected readonly notificationService = $inject(NotificationService);\n\n /**\n * Find notifications with pagination and filtering.\n */\n public readonly findNotifications = $action({\n path: this.url,\n group: this.group,\n description: \"Find notifications with pagination and filtering\",\n schema: {\n query: notificationQuerySchema,\n response: pg.page(notifications.schema),\n },\n handler: ({ query }) => this.notificationService.findNotifications(query),\n });\n}\n","import { type Static, t } from \"alepha\";\n\nexport const notificationContactPreferencesSchema = t.object({\n language: t.optional(t.text()),\n exclude: t.array(t.text()),\n});\n\nexport type NotificationContactPreferences = Static<\n typeof notificationContactPreferencesSchema\n>;\n","import { type Static, t } from \"alepha\";\n\nexport const notificationContactSchema = t.object({\n email: t.optional(t.email()),\n phoneNumber: t.optional(t.e164()),\n firstName: t.optional(t.shortText()),\n lastName: t.optional(t.text({ size: \"short\" })),\n language: t.optional(t.bcp47()),\n});\n\nexport type NotificationContact = Static<typeof notificationContactSchema>;\n","import { $module } from \"alepha\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./controllers/NotificationController.ts\";\nexport * from \"./entities/notifications.ts\";\nexport * from \"./schemas/notificationContactPreferencesSchema.ts\";\nexport * from \"./schemas/notificationContactSchema.ts\";\nexport * from \"./schemas/notificationCreateSchema.ts\";\nexport * from \"./schemas/notificationQuerySchema.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const AlephaApiNotifications = $module({\n name: \"alepha.api.notifications\",\n services: [],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.browser.js","names":["notifications"],"sources":["../../../src/api/notifications/entities/notifications.ts","../../../src/api/notifications/schemas/notificationQuerySchema.ts","../../../src/api/notifications/primitives/$notification.ts","../../../src/api/notifications/services/NotificationSenderService.ts","../../../src/api/notifications/queues/NotificationQueues.ts","../../../src/api/notifications/schemas/notificationCreateSchema.ts","../../../src/api/notifications/services/NotificationService.ts","../../../src/api/notifications/controllers/AdminNotificationController.ts","../../../src/api/notifications/schemas/notificationContactPreferencesSchema.ts","../../../src/api/notifications/schemas/notificationContactSchema.ts","../../../src/api/notifications/index.browser.ts"],"sourcesContent":["import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\nexport const notifications = $entity({\n name: \"notifications\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n\n version: db.version(),\n\n createdAt: db.createdAt(),\n\n updatedAt: db.updatedAt(),\n\n // -----------------------------------------------------------------------------------------------------------------\n\n type: t.enum([\"email\", \"sms\"]),\n\n template: t.text(), // e.g. 'resetPassword'\n\n category: t.optional(\n t.text({\n description:\n \"For grouping related notifications (e.g., 'authentication', 'marketing'). Contact can filter notifications by category.\",\n }),\n ),\n\n critical: t.optional(\n t.boolean({\n description:\n \"Prioritize delivery of this notification. Set to true for important system alerts.\",\n }),\n ),\n\n sensitive: t.optional(\n t.boolean({\n description:\n \"Message won't be logged or stored in plain text. Set to true when notification contains passwords or codes.\",\n }),\n ),\n\n // -----------------------------------------------------------------------------------------------------------------\n\n contact: t.text(), // e.g. email address or phone number or user ID or whatever\n\n variables: t.optional(t.record(t.text(), t.any())),\n\n scheduledAt: t.optional(\n t.datetime({\n description:\n \"When set, the notification will be sent at or after this date/time.\",\n }),\n ),\n\n // -----------------------------------------------------------------------------------------------------------------\n\n sentAt: t.optional(t.datetime()),\n\n error: t.optional(\n t.object({\n at: t.datetime(),\n name: t.text(),\n message: t.text({ size: \"rich\" }),\n }),\n ),\n\n // TODO: retryCount, lastRetryAt, etc.\n }),\n});\n\nexport type NotificationEntity = Static<typeof notifications.schema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const notificationQuerySchema = t.extend(pageQuerySchema, {\n type: t.optional(t.enum([\"email\", \"sms\"])),\n template: t.optional(t.string()),\n contact: t.optional(t.string()),\n category: t.optional(t.string()),\n status: t.optional(t.enum([\"pending\", \"sent\", \"failed\"])),\n});\n\nexport type NotificationQuery = Static<typeof notificationQuerySchema>;\n","import {\n $inject,\n createPrimitive,\n KIND,\n Primitive,\n type Static,\n type StaticEncode,\n type TObject,\n} from \"alepha\";\nimport { NotificationService } from \"../services/NotificationService.ts\";\n\n/**\n * Creates a notification primitive for managing email/SMS notification templates.\n *\n * Provides type-safe, reusable notification templates with multi-language support,\n * variable substitution, and categorization for different notification channels.\n *\n * @example\n * ```ts\n * class NotificationTemplates {\n * welcomeEmail = $notification({\n * name: \"welcome-email\",\n * category: \"onboarding\",\n * schema: t.object({ username: t.text(), activationLink: t.text() }),\n * email: {\n * subject: \"Welcome to our platform!\",\n * body: (vars) => `Hello ${vars.username}, click: ${vars.activationLink}`\n * }\n * });\n *\n * async sendWelcome(user: User) {\n * await this.welcomeEmail.push({\n * variables: { username: user.name, activationLink: generateLink() },\n * contact: user.email\n * });\n * }\n * }\n * ```\n */\nexport const $notification = <T extends TObject>(\n options: NotificationPrimitiveOptions<T>,\n) => createPrimitive(NotificationPrimitive<T>, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface NotificationPrimitiveOptions<T extends TObject>\n extends NotificationMessage<T> {\n name?: string;\n description?: string;\n category?: string;\n critical?: boolean;\n sensitive?: boolean;\n translations?: {\n // e.g., \"en\", \"fr\", even \"en-US\"\n [lang: string]: NotificationMessage<T>;\n };\n schema: T;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class NotificationPrimitive<T extends TObject> extends Primitive<\n NotificationPrimitiveOptions<T>\n> {\n protected readonly notificationService = $inject(NotificationService);\n\n public get name() {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n public async push(options: NotificationPushOptions<T>) {\n if (this.options.email) {\n await this.notificationService.createNotification({\n ...options,\n type: \"email\",\n template: this.name,\n });\n }\n }\n\n public configure(options: Partial<NotificationPrimitiveOptions<T>>) {\n Object.assign(this.options, options);\n }\n}\n\n$notification[KIND] = NotificationPrimitive;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface NotificationPushOptions<T extends TObject> {\n variables: StaticEncode<T>;\n contact: string;\n}\n\nexport interface NotificationMessage<T extends TObject> {\n email?: {\n subject: string;\n body: string | ((variables: Static<T>) => string);\n };\n sms?: {\n message: string | ((variables: Static<T>) => string);\n };\n}\n","import { $inject, Alepha, AlephaError } from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { EmailProvider } from \"alepha/email\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport { SmsProvider } from \"alepha/sms\";\nimport {\n type NotificationEntity,\n notifications,\n} from \"../entities/notifications.ts\";\nimport { $notification } from \"../primitives/$notification.ts\";\n\nexport class NotificationSenderService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly notificationRepository = $repository(notifications);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly emailProvider = $inject(EmailProvider);\n protected readonly smsProvider = $inject(SmsProvider);\n\n public async send(notificationId: string | NotificationEntity) {\n this.log.trace(\"Sending notification\", {\n notificationId:\n typeof notificationId === \"string\" ? notificationId : notificationId.id,\n });\n\n const notification =\n typeof notificationId === \"string\"\n ? await this.notificationRepository.findById(notificationId)\n : notificationId;\n\n if (notification.sentAt) {\n this.log.debug(\"Notification already sent\", {\n notificationId: notification.id,\n sentAt: notification.sentAt,\n });\n return;\n }\n\n this.log.debug(\"Processing notification\", {\n id: notification.id,\n type: notification.type,\n template: notification.template,\n contact: notification.contact,\n });\n\n try {\n if (notification.type === \"email\") {\n await this.emailProvider.send(this.renderEmail(notification));\n notification.sentAt = this.dateTimeProvider.nowISOString();\n this.log.info(\"Email notification sent\", {\n id: notification.id,\n template: notification.template,\n contact: notification.contact,\n });\n }\n if (notification.type === \"sms\") {\n await this.smsProvider.send(this.renderSms(notification));\n notification.sentAt = this.dateTimeProvider.nowISOString();\n this.log.info(\"SMS notification sent\", {\n id: notification.id,\n template: notification.template,\n contact: notification.contact,\n });\n }\n } catch (e) {\n this.log.error(\"Failed to send notification\", {\n id: notification.id,\n type: notification.type,\n template: notification.template,\n contact: notification.contact,\n error: e,\n });\n if (e instanceof Error) {\n notification.error = {\n at: this.dateTimeProvider.nowISOString(),\n name: e.name,\n message: e.message,\n };\n }\n } finally {\n await this.notificationRepository.save(notification);\n }\n }\n\n public renderSms(notification: NotificationEntity) {\n this.log.trace(\"Rendering SMS notification\", {\n id: notification.id,\n template: notification.template,\n });\n\n const { variables, contact, template } = this.load(notification);\n\n const sms = template.options.sms;\n if (!sms) {\n this.log.error(\"Notification template has no SMS defined\", {\n id: notification.id,\n template: notification.template,\n });\n throw new AlephaError(\n `Notification template ${notification.template} has no sms defined`,\n );\n }\n\n this.log.debug(\"Rendering SMS\", {\n template: notification.template,\n contact,\n });\n\n const message =\n typeof sms.message === \"function\"\n ? sms.message(variables as any)\n : sms.message;\n\n return {\n to: contact,\n message,\n };\n }\n\n public renderEmail(notification: NotificationEntity) {\n this.log.trace(\"Rendering email notification\", {\n id: notification.id,\n template: notification.template,\n });\n\n const { variables, contact, template } = this.load(notification);\n\n const email = template.options.email;\n if (!email) {\n this.log.error(\"Notification template has no email defined\", {\n id: notification.id,\n template: notification.template,\n });\n throw new AlephaError(\n `Notification template ${notification.template} has no email defined`,\n );\n }\n\n this.log.debug(\"Rendering email\", {\n template: notification.template,\n contact,\n subject: email.subject,\n });\n\n const subject = email.subject;\n\n const body =\n typeof email.body === \"function\"\n ? email.body(variables as any)\n : email.body;\n\n return {\n to: contact,\n subject,\n body,\n };\n }\n\n protected load(notification: NotificationEntity) {\n const variables = notification.variables || {};\n const contact = notification.contact;\n const template = this.alepha\n .primitives($notification)\n .find((it) => it.name === notification.template);\n\n if (!template) {\n this.log.error(\"Notification template not found\", {\n id: notification.id,\n template: notification.template,\n });\n throw new AlephaError(\n `No notification template found for ${notification.template}`,\n );\n }\n\n return {\n template,\n variables,\n contact,\n };\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $queue } from \"alepha/queue\";\nimport { NotificationSenderService } from \"../services/NotificationSenderService.ts\";\n\nexport class NotificationQueues {\n protected readonly notificationSenderService = $inject(\n NotificationSenderService,\n );\n\n public readonly processNotification = $queue({\n description: \"Queue for processing notifications\",\n schema: t.object({\n notificationId: t.string({ format: \"uuid\" }),\n }),\n handler: async (message) => {\n await this.notificationSenderService.send(message.payload.notificationId);\n },\n });\n}\n","import { type Static, t } from \"alepha\";\nimport { notifications } from \"../entities/notifications.ts\";\n\nexport const notificationCreateSchema = t.pick(notifications.schema, [\n \"type\",\n \"contact\",\n \"template\",\n \"variables\",\n]);\n\nexport type NotificationCreate = Static<typeof notificationCreateSchema>;\n","import { $env, $inject, Alepha, type Static, t } from \"alepha\";\nimport { $batch } from \"alepha/batch\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository, type Page } from \"alepha/orm\";\nimport {\n type NotificationEntity,\n notifications,\n} from \"../entities/notifications.ts\";\nimport { NotificationQueues } from \"../queues/NotificationQueues.ts\";\nimport {\n type NotificationCreate,\n notificationCreateSchema,\n} from \"../schemas/notificationCreateSchema.ts\";\nimport type { NotificationQuery } from \"../schemas/notificationQuerySchema.ts\";\nimport { NotificationSenderService } from \"./NotificationSenderService.ts\";\n\nexport const notificationServiceEnvSchema = t.object({\n NOTIFICATION_QUEUE: t.optional(\n t.boolean({\n description:\n \"If true, notifications will be queued instead of sent immediately\",\n }),\n ),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof notificationServiceEnvSchema>> {}\n}\n\nexport class NotificationService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly env = $env(notificationServiceEnvSchema);\n protected readonly notificationRepository = $repository(notifications);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly notificationSenderService = $inject(\n NotificationSenderService,\n );\n\n public readonly notificationBatch = $batch({\n maxSize: 100,\n maxDuration: [15, \"seconds\"],\n schema: notificationCreateSchema,\n handler: async (notifications: NotificationCreate[]) => {\n this.log.debug(\"Processing notification batch\", {\n size: notifications.length,\n templates: [...new Set(notifications.map((n) => n.template))],\n });\n\n const entities =\n await this.notificationRepository.createMany(notifications);\n\n await this.alepha\n .inject(NotificationQueues)\n .processNotification.push(\n ...entities.map((it) => ({ notificationId: it.id })),\n );\n\n this.log.info(\"Notification batch queued\", {\n count: entities.length,\n ids: entities.map((it) => it.id),\n });\n },\n });\n\n public async findNotificationById(id: string) {\n this.log.trace(\"Finding notification by ID\", { id });\n return this.notificationRepository.findOne({ where: { id } });\n }\n\n public async findNotifications(\n q: NotificationQuery = {},\n ): Promise<Page<NotificationEntity>> {\n this.log.trace(\"Finding notifications\", { query: q });\n q.sort ??= \"-createdAt\";\n\n const where = this.notificationRepository.createQueryWhere();\n\n if (q.type) {\n where.type = { eq: q.type };\n }\n\n if (q.template) {\n where.template = { like: `%${q.template}%` };\n }\n\n if (q.contact) {\n where.contact = { like: `%${q.contact}%` };\n }\n\n if (q.category) {\n where.category = { eq: q.category };\n }\n\n if (q.status) {\n if (q.status === \"sent\") {\n where.sentAt = { isNotNull: true };\n where.error = { isNull: true };\n } else if (q.status === \"failed\") {\n where.error = { isNotNull: true };\n } else if (q.status === \"pending\") {\n where.sentAt = { isNull: true };\n where.error = { isNull: true };\n }\n }\n\n return this.notificationRepository.paginate(q, { where }, { count: true });\n }\n\n /**\n * Create a new notification.\n */\n public async createNotification(entry: NotificationCreate): Promise<void> {\n this.log.trace(\"Creating notification\", {\n template: entry.template,\n type: entry.type,\n contact: entry.contact,\n });\n\n if (\n this.env.NOTIFICATION_QUEUE !== true ||\n this.alepha.isServerless() ||\n this.alepha.isTest()\n ) {\n this.log.debug(\"Sending notification immediately\", {\n template: entry.template,\n type: entry.type,\n contact: entry.contact,\n });\n const notification = await this.notificationRepository.create(entry);\n await this.notificationSenderService.send(notification);\n return;\n }\n\n this.log.debug(\"Queuing notification to batch\", {\n template: entry.template,\n type: entry.type,\n contact: entry.contact,\n });\n\n this.notificationBatch.push(entry).catch((e) => {\n this.log.error(\"Failed to push notification to batch\", {\n template: entry.template,\n type: entry.type,\n contact: entry.contact,\n error: e,\n });\n });\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $action } from \"alepha/server\";\nimport { notifications } from \"../entities/notifications.ts\";\nimport { notificationQuerySchema } from \"../schemas/notificationQuerySchema.ts\";\nimport { NotificationService } from \"../services/NotificationService.ts\";\n\nexport class AdminNotificationController {\n protected readonly url = \"/notifications\";\n protected readonly group = \"admin:notifications\";\n protected readonly notificationService = $inject(NotificationService);\n\n /**\n * Find notifications with pagination and filtering.\n */\n public readonly findNotifications = $action({\n path: this.url,\n group: this.group,\n description: \"Find notifications with pagination and filtering\",\n schema: {\n query: notificationQuerySchema,\n response: t.page(notifications.schema),\n },\n handler: ({ query }) => this.notificationService.findNotifications(query),\n });\n}\n","import { type Static, t } from \"alepha\";\n\nexport const notificationContactPreferencesSchema = t.object({\n language: t.optional(t.text()),\n exclude: t.array(t.text()),\n});\n\nexport type NotificationContactPreferences = Static<\n typeof notificationContactPreferencesSchema\n>;\n","import { type Static, t } from \"alepha\";\n\nexport const notificationContactSchema = t.object({\n email: t.optional(t.email()),\n phoneNumber: t.optional(t.e164()),\n firstName: t.optional(t.shortText()),\n lastName: t.optional(t.text({ size: \"short\" })),\n language: t.optional(t.bcp47()),\n});\n\nexport type NotificationContact = Static<typeof notificationContactSchema>;\n","import { $module } from \"alepha\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./controllers/AdminNotificationController.ts\";\nexport * from \"./entities/notifications.ts\";\nexport * from \"./schemas/notificationContactPreferencesSchema.ts\";\nexport * from \"./schemas/notificationContactSchema.ts\";\nexport * from \"./schemas/notificationCreateSchema.ts\";\nexport * from \"./schemas/notificationQuerySchema.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const AlephaApiNotifications = $module({\n name: \"alepha.api.notifications\",\n services: [],\n});\n"],"mappings":"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"}
|