alepha 0.14.1 → 0.14.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/api/audits/index.browser.js +5 -5
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.d.ts +784 -784
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +13 -13
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.browser.js +5 -5
- package/dist/api/files/index.browser.js.map +1 -1
- package/dist/api/files/index.d.ts +57 -57
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +71 -71
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +5 -5
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +165 -165
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +10 -10
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js +10 -10
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +583 -171
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +12 -12
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.browser.js +163 -10
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +281 -276
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +196 -91
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.browser.js +19 -19
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +778 -764
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +831 -596
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.browser.js +6 -6
- package/dist/api/verifications/index.browser.js.map +1 -1
- package/dist/api/verifications/index.d.ts +125 -125
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +6 -6
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/bin/index.d.ts +1 -2
- package/dist/bin/index.js +0 -1
- package/dist/bin/index.js.map +1 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cli/index.d.ts +249 -218
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +951 -821
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +40 -0
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +97 -17
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +14 -18
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +29 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +21 -24
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +21 -24
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.js.map +1 -1
- package/dist/fake/index.js +195 -168
- package/dist/fake/index.js.map +1 -1
- package/dist/file/index.d.ts +8 -0
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js +3 -0
- package/dist/file/index.js.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.browser.js +26 -5
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.d.ts +146 -121
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +49 -24
- package/dist/orm/index.js.map +1 -1
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.js.map +1 -1
- package/dist/router/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +6 -6
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +29 -29
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +171 -155
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +0 -1
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/compress/index.js +2 -0
- package/dist/server/compress/index.js.map +1 -1
- package/dist/server/cookies/index.browser.js.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +1 -1
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/health/index.d.ts +17 -17
- package/dist/server/helmet/index.js.map +1 -1
- package/dist/server/links/index.browser.js +22 -6
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +46 -44
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +24 -41
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/multipart/index.js.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/security/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +2 -1
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +8 -3
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +12 -4
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.js.map +1 -1
- package/package.json +7 -7
- package/src/api/audits/controllers/{AuditController.ts → AdminAuditController.ts} +5 -6
- package/src/api/audits/entities/audits.ts +5 -5
- package/src/api/audits/index.browser.ts +1 -1
- package/src/api/audits/index.ts +3 -3
- package/src/api/audits/primitives/$audit.spec.ts +276 -0
- package/src/api/audits/services/AuditService.spec.ts +495 -0
- package/src/api/files/__tests__/$bucket.spec.ts +91 -0
- package/src/api/files/controllers/AdminFileStatsController.spec.ts +166 -0
- package/src/api/files/controllers/{StorageStatsController.ts → AdminFileStatsController.ts} +2 -2
- package/src/api/files/controllers/FileController.spec.ts +558 -0
- package/src/api/files/controllers/FileController.ts +4 -5
- package/src/api/files/entities/files.ts +5 -5
- package/src/api/files/index.browser.ts +1 -1
- package/src/api/files/index.ts +4 -4
- package/src/api/files/jobs/FileJobs.spec.ts +52 -0
- package/src/api/files/services/FileService.spec.ts +109 -0
- package/src/api/jobs/__tests__/JobController.spec.ts +343 -0
- package/src/api/jobs/controllers/{JobController.ts → AdminJobController.ts} +2 -2
- package/src/api/jobs/entities/jobExecutions.ts +5 -5
- package/src/api/jobs/index.ts +3 -3
- package/src/api/jobs/primitives/$job.spec.ts +476 -0
- package/src/api/notifications/controllers/{NotificationController.ts → AdminNotificationController.ts} +4 -5
- package/src/api/notifications/entities/notifications.ts +5 -5
- package/src/api/notifications/index.browser.ts +1 -1
- package/src/api/notifications/index.ts +4 -4
- package/src/api/parameters/controllers/{ConfigController.ts → AdminConfigController.ts} +46 -107
- package/src/api/parameters/entities/parameters.ts +7 -17
- package/src/api/parameters/index.ts +3 -3
- package/src/api/parameters/primitives/$config.spec.ts +356 -0
- package/src/api/parameters/schemas/activateConfigBodySchema.ts +12 -0
- package/src/api/parameters/schemas/checkScheduledResponseSchema.ts +8 -0
- package/src/api/parameters/schemas/configCurrentResponseSchema.ts +13 -0
- package/src/api/parameters/schemas/configHistoryResponseSchema.ts +9 -0
- package/src/api/parameters/schemas/configNameParamSchema.ts +10 -0
- package/src/api/parameters/schemas/configNamesResponseSchema.ts +8 -0
- package/src/api/parameters/schemas/configTreeNodeSchema.ts +13 -0
- package/src/api/parameters/schemas/configVersionParamSchema.ts +9 -0
- package/src/api/parameters/schemas/configVersionResponseSchema.ts +9 -0
- package/src/api/parameters/schemas/configsByStatusResponseSchema.ts +9 -0
- package/src/api/parameters/schemas/createConfigVersionBodySchema.ts +24 -0
- package/src/api/parameters/schemas/index.ts +15 -0
- package/src/api/parameters/schemas/parameterResponseSchema.ts +26 -0
- package/src/api/parameters/schemas/parameterStatusSchema.ts +13 -0
- package/src/api/parameters/schemas/rollbackConfigBodySchema.ts +15 -0
- package/src/api/parameters/schemas/statusParamSchema.ts +9 -0
- package/src/api/users/__tests__/EmailVerification.spec.ts +369 -0
- package/src/api/users/__tests__/PasswordReset.spec.ts +550 -0
- package/src/api/users/controllers/AdminIdentityController.spec.ts +365 -0
- package/src/api/users/controllers/{IdentityController.ts → AdminIdentityController.ts} +3 -4
- package/src/api/users/controllers/AdminSessionController.spec.ts +274 -0
- package/src/api/users/controllers/{SessionController.ts → AdminSessionController.ts} +3 -4
- package/src/api/users/controllers/AdminUserController.spec.ts +372 -0
- package/src/api/users/controllers/AdminUserController.ts +116 -0
- package/src/api/users/controllers/UserController.ts +4 -107
- package/src/api/users/controllers/UserRealmController.ts +3 -0
- package/src/api/users/entities/identities.ts +6 -6
- package/src/api/users/entities/sessions.ts +6 -6
- package/src/api/users/entities/users.ts +9 -9
- package/src/api/users/index.ts +13 -6
- package/src/api/users/primitives/$userRealm.ts +13 -8
- package/src/api/users/services/CredentialService.spec.ts +509 -0
- package/src/api/users/services/CredentialService.ts +46 -0
- package/src/api/users/services/IdentityService.ts +15 -0
- package/src/api/users/services/RegistrationService.spec.ts +630 -0
- package/src/api/users/services/RegistrationService.ts +18 -0
- package/src/api/users/services/SessionService.spec.ts +301 -0
- package/src/api/users/services/SessionService.ts +110 -1
- package/src/api/users/services/UserService.ts +67 -2
- package/src/api/verifications/__tests__/CodeVerification.spec.ts +318 -0
- package/src/api/verifications/__tests__/LinkVerification.spec.ts +279 -0
- package/src/api/verifications/entities/verifications.ts +6 -6
- package/src/api/verifications/jobs/VerificationJobs.spec.ts +50 -0
- package/src/batch/__tests__/startup-buffering.spec.ts +458 -0
- package/src/batch/primitives/$batch.spec.ts +766 -0
- package/src/batch/providers/BatchProvider.spec.ts +786 -0
- package/src/bin/index.ts +0 -1
- package/src/bucket/__tests__/shared.ts +194 -0
- package/src/bucket/primitives/$bucket.spec.ts +104 -0
- package/src/bucket/providers/FileStorageProvider.spec.ts +13 -0
- package/src/bucket/providers/LocalFileStorageProvider.spec.ts +77 -0
- package/src/bucket/providers/MemoryFileStorageProvider.spec.ts +82 -0
- package/src/cache/core/__tests__/shared.ts +377 -0
- package/src/cache/core/primitives/$cache.spec.ts +111 -0
- package/src/cache/redis/__tests__/cache-redis.spec.ts +70 -0
- package/src/cli/apps/AlephaCli.ts +54 -16
- package/src/cli/apps/AlephaPackageBuilderCli.ts +2 -1
- package/src/cli/assets/appRouterTs.ts +1 -1
- package/src/cli/commands/{ViteCommands.ts → build.ts} +2 -105
- package/src/cli/commands/clean.ts +14 -0
- package/src/cli/commands/{DrizzleCommands.ts → db.ts} +10 -117
- package/src/cli/commands/{DeployCommands.ts → deploy.ts} +1 -1
- package/src/cli/commands/dev.ts +69 -0
- package/src/cli/commands/format.ts +17 -0
- package/src/cli/commands/gen/changelog.spec.ts +315 -0
- package/src/cli/commands/{ChangelogCommands.ts → gen/changelog.ts} +16 -31
- package/src/cli/commands/gen/openapi.ts +71 -0
- package/src/cli/commands/gen.ts +18 -0
- package/src/cli/commands/{CoreCommands.ts → init.ts} +4 -40
- package/src/cli/commands/lint.ts +17 -0
- package/src/cli/commands/root.ts +41 -0
- package/src/cli/commands/run.ts +24 -0
- package/src/cli/commands/test.ts +42 -0
- package/src/cli/commands/typecheck.ts +24 -0
- package/src/cli/commands/{VerifyCommands.ts → verify.ts} +1 -13
- package/src/cli/defineConfig.ts +10 -1
- package/src/cli/index.ts +17 -7
- package/src/cli/services/AlephaCliUtils.ts +71 -32
- package/src/cli/services/GitMessageParser.ts +1 -1
- package/src/command/helpers/Asker.spec.ts +127 -0
- package/src/command/helpers/Runner.spec.ts +126 -0
- package/src/command/primitives/$command.spec.ts +1588 -0
- package/src/command/providers/CliProvider.ts +74 -24
- package/src/core/Alepha.ts +52 -4
- package/src/core/__tests__/Alepha-emit.spec.ts +22 -0
- package/src/core/__tests__/Alepha-graph.spec.ts +93 -0
- package/src/core/__tests__/Alepha-has.spec.ts +41 -0
- package/src/core/__tests__/Alepha-inject.spec.ts +93 -0
- package/src/core/__tests__/Alepha-register.spec.ts +81 -0
- package/src/core/__tests__/Alepha-start.spec.ts +176 -0
- package/src/core/__tests__/Alepha-with.spec.ts +14 -0
- package/src/core/__tests__/TypeBox-usecases.spec.ts +35 -0
- package/src/core/__tests__/TypeBoxLocale.spec.ts +15 -0
- package/src/core/__tests__/descriptor.spec.ts +34 -0
- package/src/core/__tests__/fixtures/A.ts +5 -0
- package/src/core/__tests__/pagination.spec.ts +77 -0
- package/src/core/helpers/jsonSchemaToTypeBox.ts +2 -2
- package/src/core/primitives/$atom.spec.ts +43 -0
- package/src/core/primitives/$hook.spec.ts +130 -0
- package/src/core/primitives/$inject.spec.ts +175 -0
- package/src/core/primitives/$module.spec.ts +115 -0
- package/src/core/providers/CodecManager.spec.ts +740 -0
- package/src/core/providers/EventManager.spec.ts +762 -0
- package/src/core/providers/EventManager.ts +4 -0
- package/src/core/providers/StateManager.spec.ts +365 -0
- package/src/core/providers/TypeProvider.spec.ts +1607 -0
- package/src/core/providers/TypeProvider.ts +20 -26
- package/src/datetime/primitives/$interval.spec.ts +103 -0
- package/src/datetime/providers/DateTimeProvider.spec.ts +86 -0
- package/src/email/primitives/$email.spec.ts +175 -0
- package/src/email/providers/LocalEmailProvider.spec.ts +341 -0
- package/src/fake/__tests__/keyName.example.ts +40 -0
- package/src/fake/__tests__/keyName.spec.ts +152 -0
- package/src/fake/__tests__/module.example.ts +32 -0
- package/src/fake/providers/FakeProvider.spec.ts +438 -0
- package/src/file/providers/FileSystemProvider.ts +8 -0
- package/src/file/providers/NodeFileSystemProvider.spec.ts +418 -0
- package/src/file/providers/NodeFileSystemProvider.ts +5 -0
- package/src/file/services/FileDetector.spec.ts +591 -0
- package/src/lock/core/__tests__/shared.ts +190 -0
- package/src/lock/core/providers/MemoryLockProvider.spec.ts +25 -0
- package/src/lock/redis/providers/RedisLockProvider.spec.ts +25 -0
- package/src/logger/__tests__/SimpleFormatterProvider.spec.ts +109 -0
- package/src/logger/primitives/$logger.spec.ts +108 -0
- package/src/logger/services/Logger.spec.ts +295 -0
- package/src/mcp/__tests__/errors.spec.ts +175 -0
- package/src/mcp/__tests__/integration.spec.ts +450 -0
- package/src/mcp/helpers/jsonrpc.spec.ts +380 -0
- package/src/mcp/primitives/$prompt.spec.ts +468 -0
- package/src/mcp/primitives/$resource.spec.ts +390 -0
- package/src/mcp/primitives/$tool.spec.ts +406 -0
- package/src/mcp/providers/McpServerProvider.spec.ts +797 -0
- package/src/orm/__tests__/$repository-crud.spec.ts +276 -0
- package/src/orm/__tests__/$repository-hooks.spec.ts +325 -0
- package/src/orm/__tests__/$repository-orderBy.spec.ts +128 -0
- package/src/orm/__tests__/$repository-pagination-sort.spec.ts +149 -0
- package/src/orm/__tests__/$repository-save.spec.ts +37 -0
- package/src/orm/__tests__/ModelBuilder-integration.spec.ts +490 -0
- package/src/orm/__tests__/ModelBuilder-types.spec.ts +186 -0
- package/src/orm/__tests__/PostgresProvider.spec.ts +46 -0
- package/src/orm/__tests__/delete-returning.spec.ts +256 -0
- package/src/orm/__tests__/deletedAt.spec.ts +80 -0
- package/src/orm/__tests__/enums.spec.ts +315 -0
- package/src/orm/__tests__/execute.spec.ts +72 -0
- package/src/orm/__tests__/fixtures/bigEntitySchema.ts +65 -0
- package/src/orm/__tests__/fixtures/userEntitySchema.ts +27 -0
- package/src/orm/__tests__/joins.spec.ts +1114 -0
- package/src/orm/__tests__/page.spec.ts +287 -0
- package/src/orm/__tests__/primaryKey.spec.ts +87 -0
- package/src/orm/__tests__/query-date-encoding.spec.ts +402 -0
- package/src/orm/__tests__/ref-auto-onDelete.spec.ts +156 -0
- package/src/orm/__tests__/references.spec.ts +102 -0
- package/src/orm/__tests__/security.spec.ts +710 -0
- package/src/orm/__tests__/sqlite.spec.ts +111 -0
- package/src/orm/__tests__/string-operators.spec.ts +429 -0
- package/src/orm/__tests__/timestamps.spec.ts +388 -0
- package/src/orm/__tests__/validation.spec.ts +183 -0
- package/src/orm/__tests__/version.spec.ts +64 -0
- package/src/orm/helpers/parseQueryString.spec.ts +196 -0
- package/src/orm/index.browser.ts +1 -1
- package/src/orm/index.ts +10 -6
- package/src/orm/primitives/$repository.spec.ts +137 -0
- package/src/orm/primitives/$sequence.spec.ts +29 -0
- package/src/orm/primitives/$transaction.spec.ts +82 -0
- package/src/orm/providers/{PostgresTypeProvider.ts → DatabaseTypeProvider.ts} +25 -3
- package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -3
- package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +1 -1
- package/src/orm/providers/drivers/DatabaseProvider.ts +1 -1
- package/src/orm/providers/drivers/NodePostgresProvider.ts +3 -3
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +1 -1
- package/src/orm/providers/drivers/PglitePostgresProvider.ts +2 -2
- package/src/orm/services/ModelBuilder.spec.ts +575 -0
- package/src/orm/services/Repository.spec.ts +137 -0
- package/src/queue/core/__tests__/shared.ts +143 -0
- package/src/queue/core/providers/MemoryQueueProvider.spec.ts +23 -0
- package/src/queue/core/providers/WorkerProvider.spec.ts +378 -0
- package/src/queue/redis/providers/RedisQueueProvider.spec.ts +23 -0
- package/src/redis/__tests__/redis.spec.ts +58 -0
- package/src/retry/primitives/$retry.spec.ts +234 -0
- package/src/retry/providers/RetryProvider.spec.ts +438 -0
- package/src/router/__tests__/match.spec.ts +252 -0
- package/src/router/providers/RouterProvider.spec.ts +197 -0
- package/src/scheduler/__tests__/$scheduler-cron.spec.ts +25 -0
- package/src/scheduler/__tests__/$scheduler-interval.spec.ts +25 -0
- package/src/scheduler/__tests__/shared.ts +77 -0
- package/src/security/__tests__/bug-1-wildcard-after-start.spec.ts +229 -0
- package/src/security/__tests__/bug-2-password-validation.spec.ts +245 -0
- package/src/security/__tests__/bug-3-regex-vulnerability.spec.ts +407 -0
- package/src/security/__tests__/bug-4-oauth2-validation.spec.ts +439 -0
- package/src/security/__tests__/multi-layer-permissions.spec.ts +522 -0
- package/src/security/primitives/$permission.spec.ts +30 -0
- package/src/security/primitives/$permission.ts +2 -2
- package/src/security/primitives/$realm.spec.ts +101 -0
- package/src/security/primitives/$role.spec.ts +52 -0
- package/src/security/primitives/$serviceAccount.spec.ts +61 -0
- package/src/security/providers/SecurityProvider.spec.ts +350 -0
- package/src/server/auth/providers/ServerAuthProvider.ts +0 -2
- package/src/server/cache/providers/ServerCacheProvider.spec.ts +942 -0
- package/src/server/compress/providers/ServerCompressProvider.spec.ts +31 -0
- package/src/server/compress/providers/ServerCompressProvider.ts +2 -0
- package/src/server/cookies/providers/ServerCookiesProvider.spec.ts +253 -0
- package/src/server/core/__tests__/ServerRouterProvider-getRoutes.spec.ts +334 -0
- package/src/server/core/__tests__/ServerRouterProvider-requestId.spec.ts +129 -0
- package/src/server/core/primitives/$action.spec.ts +191 -0
- package/src/server/core/primitives/$route.spec.ts +65 -0
- package/src/server/core/providers/ServerBodyParserProvider.spec.ts +93 -0
- package/src/server/core/providers/ServerLoggerProvider.spec.ts +100 -0
- package/src/server/core/providers/ServerProvider.ts +3 -1
- package/src/server/core/services/HttpClient.spec.ts +123 -0
- package/src/server/core/services/UserAgentParser.spec.ts +111 -0
- package/src/server/cors/providers/ServerCorsProvider.spec.ts +481 -0
- package/src/server/health/providers/ServerHealthProvider.spec.ts +22 -0
- package/src/server/helmet/providers/ServerHelmetProvider.spec.ts +105 -0
- package/src/server/links/__tests__/$action.spec.ts +238 -0
- package/src/server/links/__tests__/fixtures/CrudApp.ts +122 -0
- package/src/server/links/__tests__/requestId.spec.ts +120 -0
- package/src/server/links/primitives/$remote.spec.ts +228 -0
- package/src/server/links/providers/LinkProvider.spec.ts +54 -0
- package/src/server/links/providers/LinkProvider.ts +49 -3
- package/src/server/links/providers/ServerLinksProvider.ts +1 -53
- package/src/server/links/schemas/apiLinksResponseSchema.ts +7 -0
- package/src/server/metrics/providers/ServerMetricsProvider.spec.ts +25 -0
- package/src/server/multipart/providers/ServerMultipartProvider.spec.ts +528 -0
- package/src/server/proxy/primitives/$proxy.spec.ts +87 -0
- package/src/server/rate-limit/__tests__/ActionRateLimit.spec.ts +211 -0
- package/src/server/rate-limit/providers/ServerRateLimitProvider.spec.ts +344 -0
- package/src/server/security/__tests__/BasicAuth.spec.ts +684 -0
- package/src/server/security/__tests__/ServerSecurityProvider-realm.spec.ts +388 -0
- package/src/server/security/providers/ServerSecurityProvider.spec.ts +123 -0
- package/src/server/static/primitives/$serve.spec.ts +193 -0
- package/src/server/swagger/__tests__/ui.spec.ts +52 -0
- package/src/server/swagger/primitives/$swagger.spec.ts +193 -0
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +18 -8
- package/src/sms/primitives/$sms.spec.ts +165 -0
- package/src/sms/providers/LocalSmsProvider.spec.ts +224 -0
- package/src/sms/providers/MemorySmsProvider.spec.ts +193 -0
- package/src/thread/primitives/$thread.spec.ts +186 -0
- package/src/topic/core/__tests__/shared.ts +144 -0
- package/src/topic/core/providers/MemoryTopicProvider.spec.ts +23 -0
- package/src/topic/redis/providers/RedisTopicProvider.spec.ts +23 -0
- package/src/vite/plugins/viteAlephaDev.ts +16 -4
- package/src/vite/tasks/runAlepha.ts +7 -1
- package/src/websocket/__tests__/$websocket-new.spec.ts +195 -0
- package/src/websocket/primitives/$channel.spec.ts +30 -0
- package/src/cli/commands/BiomeCommands.ts +0 -29
|
@@ -0,0 +1,1607 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Alepha } from "../Alepha.ts";
|
|
3
|
+
import { TypeProvider, t } from "../providers/TypeProvider.ts";
|
|
4
|
+
|
|
5
|
+
describe("TypeProvider", () => {
|
|
6
|
+
describe("Primitive Types", () => {
|
|
7
|
+
describe("string", () => {
|
|
8
|
+
it("should decode valid strings", async () => {
|
|
9
|
+
const alepha = Alepha.create();
|
|
10
|
+
await alepha.start();
|
|
11
|
+
const schema = t.string();
|
|
12
|
+
|
|
13
|
+
expect(alepha.codec.decode(schema, "hello")).toBe("hello");
|
|
14
|
+
expect(alepha.codec.decode(schema, "")).toBe("");
|
|
15
|
+
expect(alepha.codec.decode(schema, "with spaces")).toBe("with spaces");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should encode strings", async () => {
|
|
19
|
+
const alepha = Alepha.create();
|
|
20
|
+
await alepha.start();
|
|
21
|
+
const schema = t.string();
|
|
22
|
+
|
|
23
|
+
expect(alepha.codec.encode(schema, "test")).toBe("test");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should handle type coercion", async () => {
|
|
27
|
+
const alepha = Alepha.create();
|
|
28
|
+
await alepha.start();
|
|
29
|
+
const schema = t.string();
|
|
30
|
+
|
|
31
|
+
// TypeBox's Compile with codecs may coerce some types
|
|
32
|
+
expect(alepha.codec.validate(schema, 123)).toBe("123");
|
|
33
|
+
expect(alepha.codec.validate(schema, true)).toBe("true");
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("number", () => {
|
|
38
|
+
it("should decode valid numbers", async () => {
|
|
39
|
+
const alepha = Alepha.create();
|
|
40
|
+
await alepha.start();
|
|
41
|
+
const schema = t.number();
|
|
42
|
+
|
|
43
|
+
expect(alepha.codec.decode(schema, 123)).toBe(123);
|
|
44
|
+
expect(alepha.codec.decode(schema, 0)).toBe(0);
|
|
45
|
+
expect(alepha.codec.decode(schema, -456.789)).toBe(-456.789);
|
|
46
|
+
expect(alepha.codec.decode(schema, Math.PI)).toBe(Math.PI);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should encode numbers", async () => {
|
|
50
|
+
const alepha = Alepha.create();
|
|
51
|
+
await alepha.start();
|
|
52
|
+
const schema = t.number();
|
|
53
|
+
|
|
54
|
+
expect(alepha.codec.encode(schema, 42)).toBe(42);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should handle type coercion", async () => {
|
|
58
|
+
const alepha = Alepha.create();
|
|
59
|
+
await alepha.start();
|
|
60
|
+
const schema = t.number();
|
|
61
|
+
|
|
62
|
+
// TypeBox's Compile with codecs may coerce some types
|
|
63
|
+
expect(alepha.codec.validate(schema, "123")).toBe(123);
|
|
64
|
+
expect(alepha.codec.validate(schema, true)).toBe(1);
|
|
65
|
+
expect(alepha.codec.validate(schema, false)).toBe(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should validate constraints", async () => {
|
|
69
|
+
const alepha = Alepha.create();
|
|
70
|
+
await alepha.start();
|
|
71
|
+
const schema = t.number({ minimum: 0, maximum: 100 });
|
|
72
|
+
|
|
73
|
+
expect(alepha.codec.validate(schema, 0)).toBe(0);
|
|
74
|
+
expect(alepha.codec.validate(schema, 50)).toBe(50);
|
|
75
|
+
expect(alepha.codec.validate(schema, 100)).toBe(100);
|
|
76
|
+
|
|
77
|
+
expect(() => alepha.codec.validate(schema, -1)).toThrow();
|
|
78
|
+
expect(() => alepha.codec.validate(schema, 101)).toThrow();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("boolean", () => {
|
|
83
|
+
it("should decode valid booleans", async () => {
|
|
84
|
+
const alepha = Alepha.create();
|
|
85
|
+
await alepha.start();
|
|
86
|
+
const schema = t.boolean();
|
|
87
|
+
|
|
88
|
+
expect(alepha.codec.decode(schema, true)).toBe(true);
|
|
89
|
+
expect(alepha.codec.decode(schema, false)).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should encode booleans", async () => {
|
|
93
|
+
const alepha = Alepha.create();
|
|
94
|
+
await alepha.start();
|
|
95
|
+
const schema = t.boolean();
|
|
96
|
+
|
|
97
|
+
expect(alepha.codec.encode(schema, true)).toBe(true);
|
|
98
|
+
expect(alepha.codec.encode(schema, false)).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should handle type coercion", async () => {
|
|
102
|
+
const alepha = Alepha.create();
|
|
103
|
+
await alepha.start();
|
|
104
|
+
const schema = t.boolean();
|
|
105
|
+
|
|
106
|
+
// TypeBox's Compile with codecs may coerce some types
|
|
107
|
+
expect(alepha.codec.validate(schema, "true")).toBe(true);
|
|
108
|
+
expect(alepha.codec.validate(schema, "false")).toBe(false);
|
|
109
|
+
expect(alepha.codec.validate(schema, 1)).toBe(true);
|
|
110
|
+
expect(alepha.codec.validate(schema, 0)).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("null", () => {
|
|
115
|
+
it("should decode null", async () => {
|
|
116
|
+
const alepha = Alepha.create();
|
|
117
|
+
await alepha.start();
|
|
118
|
+
const schema = t.null();
|
|
119
|
+
|
|
120
|
+
expect(alepha.codec.decode(schema, null)).toBe(null);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("undefined", () => {
|
|
125
|
+
it("should decode undefined", async () => {
|
|
126
|
+
const alepha = Alepha.create();
|
|
127
|
+
await alepha.start();
|
|
128
|
+
const schema = t.undefined();
|
|
129
|
+
|
|
130
|
+
expect(alepha.codec.decode(schema, undefined)).toBe(undefined);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("void", () => {
|
|
135
|
+
it("should decode void as undefined", async () => {
|
|
136
|
+
const alepha = Alepha.create();
|
|
137
|
+
await alepha.start();
|
|
138
|
+
const schema = t.void();
|
|
139
|
+
|
|
140
|
+
expect(alepha.codec.decode(schema, undefined)).toBe(undefined);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("any", () => {
|
|
145
|
+
it("should accept any value", async () => {
|
|
146
|
+
const alepha = Alepha.create();
|
|
147
|
+
await alepha.start();
|
|
148
|
+
const schema = t.any();
|
|
149
|
+
|
|
150
|
+
expect(alepha.codec.decode(schema, "string")).toBe("string");
|
|
151
|
+
expect(alepha.codec.decode(schema, 123)).toBe(123);
|
|
152
|
+
expect(alepha.codec.decode(schema, true)).toBe(true);
|
|
153
|
+
expect(alepha.codec.decode(schema, null)).toBe(undefined);
|
|
154
|
+
expect(alepha.codec.decode(schema, undefined)).toBe(undefined);
|
|
155
|
+
expect(alepha.codec.decode(schema, { key: "value" })).toEqual({
|
|
156
|
+
key: "value",
|
|
157
|
+
});
|
|
158
|
+
expect(alepha.codec.decode(schema, [1, 2, 3])).toEqual([1, 2, 3]);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe("Text Types", () => {
|
|
164
|
+
describe("text with default size (regular)", () => {
|
|
165
|
+
it("should accept text within 255 chars", async () => {
|
|
166
|
+
const alepha = Alepha.create();
|
|
167
|
+
await alepha.start();
|
|
168
|
+
const schema = t.text();
|
|
169
|
+
|
|
170
|
+
const validText = "a".repeat(255);
|
|
171
|
+
expect(alepha.codec.decode(schema, validText)).toBe(validText);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("should trim whitespace by default", async () => {
|
|
175
|
+
const alepha = Alepha.create();
|
|
176
|
+
await alepha.start();
|
|
177
|
+
const schema = t.text();
|
|
178
|
+
|
|
179
|
+
expect(alepha.codec.decode(schema, " hello ")).toBe("hello");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should reject text over 255 chars", async () => {
|
|
183
|
+
const alepha = Alepha.create();
|
|
184
|
+
await alepha.start();
|
|
185
|
+
const schema = t.text();
|
|
186
|
+
|
|
187
|
+
const tooLong = "a".repeat(256);
|
|
188
|
+
expect(() => alepha.codec.validate(schema, tooLong)).toThrow();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should encode text", async () => {
|
|
192
|
+
const alepha = Alepha.create();
|
|
193
|
+
await alepha.start();
|
|
194
|
+
const schema = t.text();
|
|
195
|
+
|
|
196
|
+
expect(alepha.codec.encode(schema, "test")).toBe("test");
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("text with different sizes", () => {
|
|
201
|
+
it("should validate short text (64 chars max)", async () => {
|
|
202
|
+
const alepha = Alepha.create();
|
|
203
|
+
await alepha.start();
|
|
204
|
+
const shortSchema = t.text({ size: "short" });
|
|
205
|
+
|
|
206
|
+
expect(alepha.codec.validate(shortSchema, "a".repeat(64))).toBe(
|
|
207
|
+
"a".repeat(64),
|
|
208
|
+
);
|
|
209
|
+
expect(() =>
|
|
210
|
+
alepha.codec.validate(shortSchema, "a".repeat(65)),
|
|
211
|
+
).toThrow();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("should validate regular text (255 chars max)", async () => {
|
|
215
|
+
const alepha = Alepha.create();
|
|
216
|
+
await alepha.start();
|
|
217
|
+
const regularSchema = t.text({ size: "regular" });
|
|
218
|
+
|
|
219
|
+
expect(alepha.codec.validate(regularSchema, "a".repeat(255))).toBe(
|
|
220
|
+
"a".repeat(255),
|
|
221
|
+
);
|
|
222
|
+
expect(() =>
|
|
223
|
+
alepha.codec.validate(regularSchema, "a".repeat(256)),
|
|
224
|
+
).toThrow();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("should validate long text (1024 chars max)", async () => {
|
|
228
|
+
const alepha = Alepha.create();
|
|
229
|
+
await alepha.start();
|
|
230
|
+
const longSchema = t.text({ size: "long" });
|
|
231
|
+
|
|
232
|
+
expect(alepha.codec.validate(longSchema, "a".repeat(1024))).toBe(
|
|
233
|
+
"a".repeat(1024),
|
|
234
|
+
);
|
|
235
|
+
expect(() =>
|
|
236
|
+
alepha.codec.validate(longSchema, "a".repeat(1025)),
|
|
237
|
+
).toThrow();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("should validate rich text (65535 chars max)", async () => {
|
|
241
|
+
const alepha = Alepha.create();
|
|
242
|
+
await alepha.start();
|
|
243
|
+
const richSchema = t.text({ size: "rich" });
|
|
244
|
+
|
|
245
|
+
expect(alepha.codec.validate(richSchema, "a".repeat(1000))).toBe(
|
|
246
|
+
"a".repeat(1000),
|
|
247
|
+
);
|
|
248
|
+
expect(alepha.codec.validate(richSchema, "a".repeat(65535))).toBe(
|
|
249
|
+
"a".repeat(65535),
|
|
250
|
+
);
|
|
251
|
+
expect(() =>
|
|
252
|
+
alepha.codec.validate(richSchema, "a".repeat(65536)),
|
|
253
|
+
).toThrow();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe("text helper methods", () => {
|
|
258
|
+
it("should validate shortText (64 chars)", async () => {
|
|
259
|
+
const alepha = Alepha.create();
|
|
260
|
+
await alepha.start();
|
|
261
|
+
const shortSchema = t.shortText();
|
|
262
|
+
|
|
263
|
+
expect(alepha.codec.validate(shortSchema, "a".repeat(64))).toBe(
|
|
264
|
+
"a".repeat(64),
|
|
265
|
+
);
|
|
266
|
+
expect(() =>
|
|
267
|
+
alepha.codec.validate(shortSchema, "a".repeat(65)),
|
|
268
|
+
).toThrow();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("should validate longText (1024 chars)", async () => {
|
|
272
|
+
const alepha = Alepha.create();
|
|
273
|
+
await alepha.start();
|
|
274
|
+
const longSchema = t.longText();
|
|
275
|
+
|
|
276
|
+
expect(alepha.codec.validate(longSchema, "a".repeat(1024))).toBe(
|
|
277
|
+
"a".repeat(1024),
|
|
278
|
+
);
|
|
279
|
+
expect(() =>
|
|
280
|
+
alepha.codec.validate(longSchema, "a".repeat(1025)),
|
|
281
|
+
).toThrow();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("should validate richText (65535 chars)", async () => {
|
|
285
|
+
const alepha = Alepha.create();
|
|
286
|
+
await alepha.start();
|
|
287
|
+
const richSchema = t.richText();
|
|
288
|
+
|
|
289
|
+
expect(alepha.codec.decode(richSchema, "a".repeat(1000))).toBe(
|
|
290
|
+
"a".repeat(1000),
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
describe("text trimming option", () => {
|
|
296
|
+
it("should trim when enabled", async () => {
|
|
297
|
+
const alepha = Alepha.create();
|
|
298
|
+
await alepha.start();
|
|
299
|
+
const trimSchema = t.text({ trim: true });
|
|
300
|
+
|
|
301
|
+
expect(alepha.codec.decode(trimSchema, " hello ")).toBe("hello");
|
|
302
|
+
expect(alepha.codec.decode(trimSchema, "\n\ttest\n\t")).toBe("test");
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("should not trim when disabled", async () => {
|
|
306
|
+
const alepha = Alepha.create();
|
|
307
|
+
await alepha.start();
|
|
308
|
+
const noTrimSchema = t.text({ trim: false });
|
|
309
|
+
|
|
310
|
+
expect(alepha.codec.decode(noTrimSchema, " hello ")).toBe(
|
|
311
|
+
" hello ",
|
|
312
|
+
);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe("text lowercase option", () => {
|
|
317
|
+
it("should lowercase with t.text when enabled", async () => {
|
|
318
|
+
const alepha = Alepha.create();
|
|
319
|
+
await alepha.start();
|
|
320
|
+
const schema = t.text({ lowercase: true });
|
|
321
|
+
|
|
322
|
+
expect(alepha.codec.decode(schema, "HELLO")).toBe("hello");
|
|
323
|
+
expect(alepha.codec.decode(schema, "Hello World")).toBe("hello world");
|
|
324
|
+
expect(alepha.codec.decode(schema, "MixedCase123")).toBe(
|
|
325
|
+
"mixedcase123",
|
|
326
|
+
);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("should lowercase with t.enum when enabled", async () => {
|
|
330
|
+
const alepha = Alepha.create();
|
|
331
|
+
await alepha.start();
|
|
332
|
+
const schema = t.enum(["active", "inactive", "pending"], {
|
|
333
|
+
lowercase: true,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
expect(alepha.codec.decode(schema, "ACTIVE")).toBe("active");
|
|
337
|
+
expect(alepha.codec.decode(schema, "Active")).toBe("active");
|
|
338
|
+
expect(alepha.codec.validate(schema, "INACTIVE")).toBe("inactive");
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("should combine trim and lowercase with t.text", async () => {
|
|
342
|
+
const alepha = Alepha.create();
|
|
343
|
+
await alepha.start();
|
|
344
|
+
const schema = t.text({ trim: true, lowercase: true });
|
|
345
|
+
|
|
346
|
+
expect(alepha.codec.decode(schema, " HELLO ")).toBe("hello");
|
|
347
|
+
expect(alepha.codec.decode(schema, "\n\tTEST\n\t")).toBe("test");
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it("should not lowercase when not enabled", async () => {
|
|
351
|
+
const alepha = Alepha.create();
|
|
352
|
+
await alepha.start();
|
|
353
|
+
const schema = t.text();
|
|
354
|
+
|
|
355
|
+
expect(alepha.codec.decode(schema, "HELLO")).toBe("HELLO");
|
|
356
|
+
expect(alepha.codec.decode(schema, "MixedCase")).toBe("MixedCase");
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
describe("text pattern option (regex)", () => {
|
|
361
|
+
it("should accept values matching the pattern", async () => {
|
|
362
|
+
const alepha = Alepha.create();
|
|
363
|
+
await alepha.start();
|
|
364
|
+
const schema = t.text({ pattern: "^[A-Z]{3}$" });
|
|
365
|
+
|
|
366
|
+
expect(alepha.codec.decode(schema, "ABC")).toBe("ABC");
|
|
367
|
+
expect(alepha.codec.decode(schema, "XYZ")).toBe("XYZ");
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("should reject values not matching the pattern", async () => {
|
|
371
|
+
const alepha = Alepha.create();
|
|
372
|
+
await alepha.start();
|
|
373
|
+
const schema = t.text({ pattern: "^[A-Z]{3}$" });
|
|
374
|
+
|
|
375
|
+
expect(() => alepha.codec.validate(schema, "abc")).toThrow();
|
|
376
|
+
expect(() => alepha.codec.validate(schema, "ABCD")).toThrow();
|
|
377
|
+
expect(() => alepha.codec.validate(schema, "AB")).toThrow();
|
|
378
|
+
expect(() => alepha.codec.validate(schema, "123")).toThrow();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("should work with alphanumeric pattern", async () => {
|
|
382
|
+
const alepha = Alepha.create();
|
|
383
|
+
await alepha.start();
|
|
384
|
+
const schema = t.text({ pattern: "^[a-zA-Z0-9]+$" });
|
|
385
|
+
|
|
386
|
+
expect(alepha.codec.decode(schema, "Hello123")).toBe("Hello123");
|
|
387
|
+
expect(alepha.codec.decode(schema, "test")).toBe("test");
|
|
388
|
+
expect(alepha.codec.decode(schema, "123")).toBe("123");
|
|
389
|
+
|
|
390
|
+
expect(() => alepha.codec.validate(schema, "hello world")).toThrow();
|
|
391
|
+
expect(() => alepha.codec.validate(schema, "hello@test")).toThrow();
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it("should work with slug pattern", async () => {
|
|
395
|
+
const alepha = Alepha.create();
|
|
396
|
+
await alepha.start();
|
|
397
|
+
const schema = t.text({ pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$" });
|
|
398
|
+
|
|
399
|
+
expect(alepha.codec.decode(schema, "hello-world")).toBe("hello-world");
|
|
400
|
+
expect(alepha.codec.decode(schema, "my-blog-post")).toBe(
|
|
401
|
+
"my-blog-post",
|
|
402
|
+
);
|
|
403
|
+
expect(alepha.codec.decode(schema, "test")).toBe("test");
|
|
404
|
+
|
|
405
|
+
expect(() => alepha.codec.validate(schema, "Hello-World")).toThrow();
|
|
406
|
+
expect(() => alepha.codec.validate(schema, "hello_world")).toThrow();
|
|
407
|
+
expect(() => alepha.codec.validate(schema, "-hello")).toThrow();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("should combine pattern with maxLength", async () => {
|
|
411
|
+
const alepha = Alepha.create();
|
|
412
|
+
await alepha.start();
|
|
413
|
+
const schema = t.text({ pattern: "^[A-Z]+$", maxLength: 5 });
|
|
414
|
+
|
|
415
|
+
expect(alepha.codec.decode(schema, "HELLO")).toBe("HELLO");
|
|
416
|
+
expect(alepha.codec.decode(schema, "AB")).toBe("AB");
|
|
417
|
+
|
|
418
|
+
expect(() => alepha.codec.validate(schema, "TOOLONG")).toThrow();
|
|
419
|
+
expect(() => alepha.codec.validate(schema, "hello")).toThrow();
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it("should trim before pattern validation", async () => {
|
|
423
|
+
const alepha = Alepha.create();
|
|
424
|
+
await alepha.start();
|
|
425
|
+
const schema = t.text({ pattern: "^[A-Z]+$", trim: true });
|
|
426
|
+
|
|
427
|
+
expect(alepha.codec.decode(schema, " HELLO ")).toBe("HELLO");
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
describe("Integer Types", () => {
|
|
433
|
+
describe("int (32-bit integer)", () => {
|
|
434
|
+
it("should decode valid 32-bit integers", async () => {
|
|
435
|
+
const alepha = Alepha.create();
|
|
436
|
+
await alepha.start();
|
|
437
|
+
const schema = t.integer();
|
|
438
|
+
|
|
439
|
+
expect(alepha.codec.decode(schema, 0)).toBe(0);
|
|
440
|
+
expect(alepha.codec.decode(schema, 123456)).toBe(123456);
|
|
441
|
+
expect(alepha.codec.decode(schema, -123456)).toBe(-123456);
|
|
442
|
+
expect(alepha.codec.decode(schema, 2147483647)).toBe(2147483647);
|
|
443
|
+
expect(alepha.codec.decode(schema, -2147483647)).toBe(-2147483647);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it("should reject non-integers", async () => {
|
|
447
|
+
const alepha = Alepha.create();
|
|
448
|
+
await alepha.start();
|
|
449
|
+
const schema = t.integer();
|
|
450
|
+
|
|
451
|
+
expect(alepha.codec.decode(schema, 3.14)).toBe(3);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it("should reject values outside 32-bit range", async () => {
|
|
455
|
+
const alepha = Alepha.create();
|
|
456
|
+
await alepha.start();
|
|
457
|
+
const schema = t.int32();
|
|
458
|
+
|
|
459
|
+
expect(() => alepha.codec.validate(schema, 2147483648)).toThrow();
|
|
460
|
+
expect(() => alepha.codec.validate(schema, -2147483648)).toThrow();
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
describe("integer alias", () => {
|
|
465
|
+
it("should work as alias for int", async () => {
|
|
466
|
+
const alepha = Alepha.create();
|
|
467
|
+
await alepha.start();
|
|
468
|
+
const schema = t.int32();
|
|
469
|
+
|
|
470
|
+
expect(alepha.codec.decode(schema, 42)).toBe(42);
|
|
471
|
+
expect(alepha.codec.decode(schema, 3.14)).toBe(3);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
describe("int64", () => {
|
|
476
|
+
it("should decode valid safe integers", async () => {
|
|
477
|
+
const alepha = Alepha.create();
|
|
478
|
+
await alepha.start();
|
|
479
|
+
const schema = t.int64();
|
|
480
|
+
|
|
481
|
+
expect(alepha.codec.decode(schema, 0)).toBe(0);
|
|
482
|
+
expect(alepha.codec.decode(schema, 9007199254740991)).toBe(
|
|
483
|
+
9007199254740991,
|
|
484
|
+
);
|
|
485
|
+
expect(alepha.codec.decode(schema, -9007199254740991)).toBe(
|
|
486
|
+
-9007199254740991,
|
|
487
|
+
);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it("should reject non-integers", async () => {
|
|
491
|
+
const alepha = Alepha.create();
|
|
492
|
+
await alepha.start();
|
|
493
|
+
const schema = t.int64();
|
|
494
|
+
|
|
495
|
+
expect(() => alepha.codec.validate(schema, 3.14)).toThrow();
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it("should reject values outside safe integer range", async () => {
|
|
499
|
+
const alepha = Alepha.create();
|
|
500
|
+
await alepha.start();
|
|
501
|
+
const schema = t.int64();
|
|
502
|
+
|
|
503
|
+
expect(() => alepha.codec.validate(schema, 9007199254740992)).toThrow();
|
|
504
|
+
expect(() =>
|
|
505
|
+
alepha.codec.validate(schema, -9007199254740992),
|
|
506
|
+
).toThrow();
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// Codec Types tests removed - t.bigint(), t.url(), t.binary() are now plain strings without transformation
|
|
512
|
+
|
|
513
|
+
describe("Format Types", () => {
|
|
514
|
+
describe("uuid", () => {
|
|
515
|
+
it("should accept valid UUIDs", async () => {
|
|
516
|
+
const alepha = Alepha.create();
|
|
517
|
+
await alepha.start();
|
|
518
|
+
const schema = t.uuid();
|
|
519
|
+
|
|
520
|
+
const validUuid = "550e8400-e29b-41d4-a716-446655440000";
|
|
521
|
+
expect(alepha.codec.decode(schema, validUuid)).toBe(validUuid);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it("should encode UUIDs", async () => {
|
|
525
|
+
const alepha = Alepha.create();
|
|
526
|
+
await alepha.start();
|
|
527
|
+
const schema = t.uuid();
|
|
528
|
+
|
|
529
|
+
const validUuid = "550e8400-e29b-41d4-a716-446655440000";
|
|
530
|
+
expect(alepha.codec.encode(schema, validUuid)).toBe(validUuid);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it("should reject invalid UUIDs", async () => {
|
|
534
|
+
const alepha = Alepha.create();
|
|
535
|
+
await alepha.start();
|
|
536
|
+
const schema = t.uuid();
|
|
537
|
+
|
|
538
|
+
expect(() => alepha.codec.validate(schema, "not-a-uuid")).toThrow();
|
|
539
|
+
expect(() =>
|
|
540
|
+
alepha.codec.validate(schema, "550e8400-e29b-41d4-a716"),
|
|
541
|
+
).toThrow();
|
|
542
|
+
expect(() => alepha.codec.validate(schema, "")).toThrow();
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
describe("email", () => {
|
|
547
|
+
it("should accept valid emails", async () => {
|
|
548
|
+
const alepha = Alepha.create();
|
|
549
|
+
await alepha.start();
|
|
550
|
+
const schema = t.email();
|
|
551
|
+
|
|
552
|
+
expect(alepha.codec.decode(schema, "user@example.com")).toBe(
|
|
553
|
+
"user@example.com",
|
|
554
|
+
);
|
|
555
|
+
expect(
|
|
556
|
+
alepha.codec.decode(schema, "test.user+tag@subdomain.example.co.uk"),
|
|
557
|
+
).toBe("test.user+tag@subdomain.example.co.uk");
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it("should trim emails", async () => {
|
|
561
|
+
const alepha = Alepha.create();
|
|
562
|
+
await alepha.start();
|
|
563
|
+
const schema = t.email();
|
|
564
|
+
|
|
565
|
+
expect(alepha.codec.decode(schema, " user@example.com ")).toBe(
|
|
566
|
+
"user@example.com",
|
|
567
|
+
);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it("should lowercase emails", async () => {
|
|
571
|
+
const alepha = Alepha.create();
|
|
572
|
+
await alepha.start();
|
|
573
|
+
const schema = t.email();
|
|
574
|
+
|
|
575
|
+
expect(alepha.codec.decode(schema, "User@Example.COM")).toBe(
|
|
576
|
+
"user@example.com",
|
|
577
|
+
);
|
|
578
|
+
expect(alepha.codec.decode(schema, "JOHN.DOE@GMAIL.COM")).toBe(
|
|
579
|
+
"john.doe@gmail.com",
|
|
580
|
+
);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it("should trim and lowercase emails together", async () => {
|
|
584
|
+
const alepha = Alepha.create();
|
|
585
|
+
await alepha.start();
|
|
586
|
+
const schema = t.email();
|
|
587
|
+
|
|
588
|
+
expect(alepha.codec.decode(schema, " User@Example.COM ")).toBe(
|
|
589
|
+
"user@example.com",
|
|
590
|
+
);
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
it("should reject invalid emails", async () => {
|
|
594
|
+
const alepha = Alepha.create();
|
|
595
|
+
await alepha.start();
|
|
596
|
+
const schema = t.email();
|
|
597
|
+
|
|
598
|
+
expect(() => alepha.codec.validate(schema, "not an email")).toThrow();
|
|
599
|
+
expect(() => alepha.codec.validate(schema, "@example.com")).toThrow();
|
|
600
|
+
expect(() => alepha.codec.validate(schema, "user@")).toThrow();
|
|
601
|
+
expect(() => alepha.codec.validate(schema, "")).toThrow();
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
describe("e164 phone number", () => {
|
|
606
|
+
it("should accept valid E.164 phone numbers", async () => {
|
|
607
|
+
const alepha = Alepha.create();
|
|
608
|
+
await alepha.start();
|
|
609
|
+
const schema = t.e164();
|
|
610
|
+
|
|
611
|
+
expect(alepha.codec.decode(schema, "+1234567890")).toBe("+1234567890");
|
|
612
|
+
expect(alepha.codec.decode(schema, "+12025551234")).toBe(
|
|
613
|
+
"+12025551234",
|
|
614
|
+
);
|
|
615
|
+
expect(alepha.codec.decode(schema, "+441234567890")).toBe(
|
|
616
|
+
"+441234567890",
|
|
617
|
+
);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it("should reject invalid E.164 phone numbers", async () => {
|
|
621
|
+
const alepha = Alepha.create();
|
|
622
|
+
await alepha.start();
|
|
623
|
+
const schema = t.e164();
|
|
624
|
+
|
|
625
|
+
expect(() => alepha.codec.validate(schema, "1234567890")).toThrow(); // Missing +
|
|
626
|
+
expect(() => alepha.codec.validate(schema, "+0123456789")).toThrow(); // Starts with 0
|
|
627
|
+
expect(() => alepha.codec.validate(schema, "+1")).toThrow(); // Too short
|
|
628
|
+
expect(() =>
|
|
629
|
+
alepha.codec.validate(schema, "+12345678901234567"),
|
|
630
|
+
).toThrow(); // Too long
|
|
631
|
+
expect(() => alepha.codec.validate(schema, "+1234-567-890")).toThrow(); // Contains dashes
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
describe("bcp47 language tag", () => {
|
|
636
|
+
it("should accept valid BCP 47 language tags", async () => {
|
|
637
|
+
const alepha = Alepha.create();
|
|
638
|
+
await alepha.start();
|
|
639
|
+
const schema = t.bcp47();
|
|
640
|
+
|
|
641
|
+
expect(alepha.codec.decode(schema, "en")).toBe("en");
|
|
642
|
+
expect(alepha.codec.decode(schema, "en-US")).toBe("en-US");
|
|
643
|
+
expect(alepha.codec.decode(schema, "fr")).toBe("fr");
|
|
644
|
+
expect(alepha.codec.decode(schema, "fr-CA")).toBe("fr-CA");
|
|
645
|
+
expect(alepha.codec.decode(schema, "pt-BR")).toBe("pt-BR");
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
it("should reject invalid BCP 47 tags", async () => {
|
|
649
|
+
const alepha = Alepha.create();
|
|
650
|
+
await alepha.start();
|
|
651
|
+
const schema = t.bcp47();
|
|
652
|
+
|
|
653
|
+
expect(() => alepha.codec.validate(schema, "EN")).toThrow(); // Uppercase language
|
|
654
|
+
expect(() => alepha.codec.validate(schema, "en-us")).toThrow(); // Lowercase region
|
|
655
|
+
expect(() => alepha.codec.validate(schema, "e")).toThrow(); // Too short
|
|
656
|
+
expect(() => alepha.codec.validate(schema, "english")).toThrow(); // Too long
|
|
657
|
+
expect(() => alepha.codec.validate(schema, "en-US-variant")).toThrow(); // Too many parts
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
describe("snakeCase", () => {
|
|
662
|
+
it("should accept valid snake_case strings", async () => {
|
|
663
|
+
const alepha = Alepha.create();
|
|
664
|
+
await alepha.start();
|
|
665
|
+
const schema = t.snakeCase();
|
|
666
|
+
|
|
667
|
+
expect(alepha.codec.decode(schema, "HELLO")).toBe("HELLO");
|
|
668
|
+
expect(alepha.codec.decode(schema, "HELLO_WORLD")).toBe("HELLO_WORLD");
|
|
669
|
+
expect(alepha.codec.decode(schema, "TEST-VALUE")).toBe("TEST-VALUE");
|
|
670
|
+
expect(alepha.codec.decode(schema, "A_B_C_D")).toBe("A_B_C_D");
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it("should reject invalid snake_case", async () => {
|
|
674
|
+
const alepha = Alepha.create();
|
|
675
|
+
await alepha.start();
|
|
676
|
+
const schema = t.snakeCase();
|
|
677
|
+
|
|
678
|
+
expect(() => alepha.codec.validate(schema, "hello")).toThrow(); // Lowercase
|
|
679
|
+
expect(() => alepha.codec.validate(schema, "Hello_World")).toThrow(); // Mixed case
|
|
680
|
+
expect(() => alepha.codec.validate(schema, "HELLO WORLD")).toThrow(); // Space
|
|
681
|
+
expect(() => alepha.codec.validate(schema, "HELLO.WORLD")).toThrow(); // Dot
|
|
682
|
+
});
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
describe("Complex Types", () => {
|
|
687
|
+
describe("object", () => {
|
|
688
|
+
it("should decode valid objects", async () => {
|
|
689
|
+
const alepha = Alepha.create();
|
|
690
|
+
await alepha.start();
|
|
691
|
+
const schema = t.object({
|
|
692
|
+
name: t.text(),
|
|
693
|
+
age: t.integer(),
|
|
694
|
+
active: t.boolean(),
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
const valid = { name: "John", age: 30, active: true };
|
|
698
|
+
const decoded = alepha.codec.decode(schema, valid);
|
|
699
|
+
expect(decoded).toEqual(valid);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it("should encode objects", async () => {
|
|
703
|
+
const alepha = Alepha.create();
|
|
704
|
+
await alepha.start();
|
|
705
|
+
const schema = t.object({
|
|
706
|
+
name: t.text(),
|
|
707
|
+
age: t.integer(),
|
|
708
|
+
active: t.boolean(),
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
const valid = { name: "John", age: 30, active: true };
|
|
712
|
+
const encoded = alepha.codec.encode(schema, valid);
|
|
713
|
+
expect(encoded).toEqual(valid);
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
it("should reject missing required fields", async () => {
|
|
717
|
+
const alepha = Alepha.create();
|
|
718
|
+
await alepha.start();
|
|
719
|
+
const schema = t.object({
|
|
720
|
+
name: t.text(),
|
|
721
|
+
age: t.integer(),
|
|
722
|
+
active: t.boolean(),
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
expect(() =>
|
|
726
|
+
alepha.codec.validate(schema, { name: "John", age: 30 }),
|
|
727
|
+
).toThrow();
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
it("should fix invalid field types", async () => {
|
|
731
|
+
const alepha = Alepha.create();
|
|
732
|
+
await alepha.start();
|
|
733
|
+
const schema = t.object({
|
|
734
|
+
name: t.text(),
|
|
735
|
+
age: t.integer(),
|
|
736
|
+
active: t.boolean(),
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
expect(
|
|
740
|
+
alepha.codec.decode(schema, {
|
|
741
|
+
name: "John",
|
|
742
|
+
age: "30",
|
|
743
|
+
active: true,
|
|
744
|
+
}),
|
|
745
|
+
).toEqual({
|
|
746
|
+
name: "John",
|
|
747
|
+
age: 30,
|
|
748
|
+
active: true,
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
it("should fix additional properties by default", async () => {
|
|
753
|
+
const alepha = Alepha.create();
|
|
754
|
+
await alepha.start();
|
|
755
|
+
const schema = t.object({
|
|
756
|
+
name: t.text(),
|
|
757
|
+
age: t.integer(),
|
|
758
|
+
active: t.boolean(),
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
expect(
|
|
762
|
+
alepha.codec.decode(schema, {
|
|
763
|
+
name: "John",
|
|
764
|
+
age: 30,
|
|
765
|
+
active: true,
|
|
766
|
+
extra: "field",
|
|
767
|
+
}),
|
|
768
|
+
).toEqual({
|
|
769
|
+
name: "John",
|
|
770
|
+
age: 30,
|
|
771
|
+
active: true,
|
|
772
|
+
});
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
it("should support nested objects", async () => {
|
|
776
|
+
const alepha = Alepha.create();
|
|
777
|
+
await alepha.start();
|
|
778
|
+
const schema = t.object({
|
|
779
|
+
user: t.object({
|
|
780
|
+
name: t.text(),
|
|
781
|
+
email: t.email(),
|
|
782
|
+
}),
|
|
783
|
+
metadata: t.object({
|
|
784
|
+
createdAt: t.string(),
|
|
785
|
+
updatedAt: t.string(),
|
|
786
|
+
}),
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
const valid = {
|
|
790
|
+
user: {
|
|
791
|
+
name: "John Doe",
|
|
792
|
+
email: "john@example.com",
|
|
793
|
+
},
|
|
794
|
+
metadata: {
|
|
795
|
+
createdAt: "2025-01-01",
|
|
796
|
+
updatedAt: "2025-01-02",
|
|
797
|
+
},
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
expect(alepha.codec.decode(schema, valid)).toEqual(valid);
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
describe("array", () => {
|
|
805
|
+
it("should decode valid arrays", async () => {
|
|
806
|
+
const alepha = Alepha.create();
|
|
807
|
+
await alepha.start();
|
|
808
|
+
const schema = t.array(t.text());
|
|
809
|
+
|
|
810
|
+
expect(alepha.codec.decode(schema, [])).toEqual([]);
|
|
811
|
+
expect(alepha.codec.decode(schema, ["a", "b", "c"])).toEqual([
|
|
812
|
+
"a",
|
|
813
|
+
"b",
|
|
814
|
+
"c",
|
|
815
|
+
]);
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
it("should enforce default max items (1000)", async () => {
|
|
819
|
+
const alepha = Alepha.create();
|
|
820
|
+
await alepha.start();
|
|
821
|
+
const schema = t.array(t.text());
|
|
822
|
+
|
|
823
|
+
const maxArray = new Array(1000).fill("x");
|
|
824
|
+
expect(alepha.codec.decode(schema, maxArray)).toEqual(maxArray);
|
|
825
|
+
|
|
826
|
+
const tooManyItems = new Array(1001).fill("x");
|
|
827
|
+
expect(() => alepha.codec.validate(schema, tooManyItems)).toThrow();
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
it("should support custom maxItems", async () => {
|
|
831
|
+
const alepha = Alepha.create();
|
|
832
|
+
await alepha.start();
|
|
833
|
+
const schema = t.array(t.integer(), { maxItems: 5 });
|
|
834
|
+
|
|
835
|
+
expect(alepha.codec.decode(schema, [1, 2, 3, 4, 5])).toEqual([
|
|
836
|
+
1, 2, 3, 4, 5,
|
|
837
|
+
]);
|
|
838
|
+
expect(() =>
|
|
839
|
+
alepha.codec.validate(schema, [1, 2, 3, 4, 5, 6]),
|
|
840
|
+
).toThrow();
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
it("should reject invalid item types", async () => {
|
|
844
|
+
const alepha = Alepha.create();
|
|
845
|
+
await alepha.start();
|
|
846
|
+
const schema = t.array(t.text());
|
|
847
|
+
|
|
848
|
+
expect(
|
|
849
|
+
alepha.codec.decode(schema, ["valid", 123, "also valid"]),
|
|
850
|
+
).toEqual(["valid", "123", "also valid"]);
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
it("should reject non-array values", async () => {
|
|
854
|
+
const alepha = Alepha.create();
|
|
855
|
+
await alepha.start();
|
|
856
|
+
const schema = t.array(t.text());
|
|
857
|
+
|
|
858
|
+
expect(() => alepha.codec.validate(schema, "not an array")).toThrow();
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
it("should support array of objects", async () => {
|
|
862
|
+
const alepha = Alepha.create();
|
|
863
|
+
await alepha.start();
|
|
864
|
+
const schema = t.array(
|
|
865
|
+
t.object({
|
|
866
|
+
id: t.integer(),
|
|
867
|
+
name: t.text(),
|
|
868
|
+
}),
|
|
869
|
+
);
|
|
870
|
+
|
|
871
|
+
const valid = [
|
|
872
|
+
{ id: 1, name: "Alice" },
|
|
873
|
+
{ id: 2, name: "Bob" },
|
|
874
|
+
];
|
|
875
|
+
|
|
876
|
+
expect(alepha.codec.decode(schema, valid)).toEqual(valid);
|
|
877
|
+
});
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
describe("union", () => {
|
|
881
|
+
it("should accept any type in the union", async () => {
|
|
882
|
+
const alepha = Alepha.create();
|
|
883
|
+
await alepha.start();
|
|
884
|
+
const schema = t.union([t.text(), t.integer()]);
|
|
885
|
+
|
|
886
|
+
expect(alepha.codec.decode(schema, "hello")).toBe("hello");
|
|
887
|
+
expect(alepha.codec.decode(schema, 123)).toBe(123);
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
it("should reject types not in the union", async () => {
|
|
891
|
+
const alepha = Alepha.create();
|
|
892
|
+
await alepha.start();
|
|
893
|
+
const schema = t.union([t.text(), t.integer()]);
|
|
894
|
+
|
|
895
|
+
expect(() => alepha.codec.validate(schema, {})).toThrow();
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
it("should support union of object types", async () => {
|
|
899
|
+
const alepha = Alepha.create();
|
|
900
|
+
await alepha.start();
|
|
901
|
+
const schema = t.union([
|
|
902
|
+
t.object({ type: t.const("A"), value: t.text() }),
|
|
903
|
+
t.object({ type: t.const("B"), count: t.integer() }),
|
|
904
|
+
]);
|
|
905
|
+
|
|
906
|
+
expect(
|
|
907
|
+
alepha.codec.decode(schema, { type: "A", value: "test" }),
|
|
908
|
+
).toEqual({
|
|
909
|
+
type: "A",
|
|
910
|
+
value: "test",
|
|
911
|
+
});
|
|
912
|
+
expect(alepha.codec.decode(schema, { type: "B", count: 42 })).toEqual({
|
|
913
|
+
type: "B",
|
|
914
|
+
count: 42,
|
|
915
|
+
});
|
|
916
|
+
});
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
describe("record", () => {
|
|
920
|
+
it("should decode valid records", async () => {
|
|
921
|
+
const alepha = Alepha.create();
|
|
922
|
+
await alepha.start();
|
|
923
|
+
const schema = t.record(t.text(), t.integer());
|
|
924
|
+
|
|
925
|
+
const valid = { a: 1, b: 2, c: 3 };
|
|
926
|
+
expect(alepha.codec.decode(schema, valid)).toEqual(valid);
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
it("should accept empty records", async () => {
|
|
930
|
+
const alepha = Alepha.create();
|
|
931
|
+
await alepha.start();
|
|
932
|
+
const schema = t.record(t.text(), t.integer());
|
|
933
|
+
|
|
934
|
+
expect(alepha.codec.decode(schema, {})).toEqual({});
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
it("should reject invalid value types", async () => {
|
|
938
|
+
const alepha = Alepha.create();
|
|
939
|
+
await alepha.start();
|
|
940
|
+
const schema = t.record(t.text(), t.integer());
|
|
941
|
+
|
|
942
|
+
expect(() =>
|
|
943
|
+
alepha.codec.validate(schema, { a: "not a number" }),
|
|
944
|
+
).toThrow();
|
|
945
|
+
});
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
describe("json", () => {
|
|
949
|
+
it("should accept any JSON object", async () => {
|
|
950
|
+
const alepha = Alepha.create();
|
|
951
|
+
await alepha.start();
|
|
952
|
+
const schema = t.json();
|
|
953
|
+
|
|
954
|
+
expect(alepha.codec.decode(schema, { key: "value" })).toEqual({
|
|
955
|
+
key: "value",
|
|
956
|
+
});
|
|
957
|
+
expect(
|
|
958
|
+
alepha.codec.decode(schema, { nested: { key: "value" } }),
|
|
959
|
+
).toEqual({
|
|
960
|
+
nested: { key: "value" },
|
|
961
|
+
});
|
|
962
|
+
expect(alepha.codec.decode(schema, {})).toEqual({});
|
|
963
|
+
});
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
describe("tuple", () => {
|
|
967
|
+
it("should decode valid tuples", async () => {
|
|
968
|
+
const alepha = Alepha.create();
|
|
969
|
+
await alepha.start();
|
|
970
|
+
const schema = t.tuple([t.text(), t.integer(), t.boolean()]);
|
|
971
|
+
|
|
972
|
+
expect(alepha.codec.decode(schema, ["hello", 42, true])).toEqual([
|
|
973
|
+
"hello",
|
|
974
|
+
42,
|
|
975
|
+
true,
|
|
976
|
+
]);
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
it("should reject tuples with wrong length", async () => {
|
|
980
|
+
const alepha = Alepha.create();
|
|
981
|
+
await alepha.start();
|
|
982
|
+
const schema = t.tuple([
|
|
983
|
+
t.text(),
|
|
984
|
+
t.integer(),
|
|
985
|
+
t.object({
|
|
986
|
+
flag: t.boolean(),
|
|
987
|
+
}),
|
|
988
|
+
]);
|
|
989
|
+
|
|
990
|
+
expect(() => alepha.codec.validate(schema, ["hello", 42])).toThrow();
|
|
991
|
+
expect(
|
|
992
|
+
alepha.codec.decode(schema, ["hello", 42, { flag: true }, "extra"]),
|
|
993
|
+
).toEqual(["hello", 42, { flag: true }]);
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
it("should reject tuples with wrong types", async () => {
|
|
997
|
+
const alepha = Alepha.create();
|
|
998
|
+
await alepha.start();
|
|
999
|
+
const schema = t.tuple([t.text(), t.integer(), t.boolean()]);
|
|
1000
|
+
|
|
1001
|
+
expect(() =>
|
|
1002
|
+
alepha.codec.validate(schema, ["hello", "not a number", true]),
|
|
1003
|
+
).toThrow();
|
|
1004
|
+
});
|
|
1005
|
+
});
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
describe("Modifier Types", () => {
|
|
1009
|
+
describe("optional", () => {
|
|
1010
|
+
it("should make fields optional", async () => {
|
|
1011
|
+
const alepha = Alepha.create();
|
|
1012
|
+
await alepha.start();
|
|
1013
|
+
const schema = t.object({
|
|
1014
|
+
required: t.text(),
|
|
1015
|
+
optional: t.optional(t.text()),
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
expect(
|
|
1019
|
+
alepha.codec.decode(schema, { required: "test", optional: "value" }),
|
|
1020
|
+
).toEqual({
|
|
1021
|
+
required: "test",
|
|
1022
|
+
optional: "value",
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
expect(alepha.codec.decode(schema, { required: "test" })).toEqual({
|
|
1026
|
+
required: "test",
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
expect(
|
|
1030
|
+
alepha.codec.decode(schema, {
|
|
1031
|
+
required: "test",
|
|
1032
|
+
optional: undefined,
|
|
1033
|
+
}),
|
|
1034
|
+
).toEqual({
|
|
1035
|
+
required: "test",
|
|
1036
|
+
optional: undefined,
|
|
1037
|
+
});
|
|
1038
|
+
});
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
describe("nullable", () => {
|
|
1042
|
+
it("should allow null values", async () => {
|
|
1043
|
+
const alepha = Alepha.create();
|
|
1044
|
+
await alepha.start();
|
|
1045
|
+
const schema = t.object({
|
|
1046
|
+
value: t.nullable(t.text()),
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
expect(alepha.codec.decode(schema, { value: "test" })).toEqual({
|
|
1050
|
+
value: "test",
|
|
1051
|
+
});
|
|
1052
|
+
expect(alepha.codec.decode(schema, { value: null })).toEqual({
|
|
1053
|
+
value: null,
|
|
1054
|
+
});
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
it("should require field to be present", async () => {
|
|
1058
|
+
const alepha = Alepha.create();
|
|
1059
|
+
await alepha.start();
|
|
1060
|
+
const schema = t.object({
|
|
1061
|
+
value: t.nullable(t.text()),
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
expect(() => alepha.codec.validate(schema, {})).toThrow();
|
|
1065
|
+
});
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
describe("optional nullable", () => {
|
|
1069
|
+
it("should allow string, null, or omitted", async () => {
|
|
1070
|
+
const alepha = Alepha.create();
|
|
1071
|
+
await alepha.start();
|
|
1072
|
+
const schema = t.object({
|
|
1073
|
+
value: t.optional(t.nullable(t.text())),
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
expect(alepha.codec.decode(schema, { value: "test" })).toEqual({
|
|
1077
|
+
value: "test",
|
|
1078
|
+
});
|
|
1079
|
+
expect(alepha.codec.decode(schema, { value: null })).toEqual({
|
|
1080
|
+
value: null,
|
|
1081
|
+
});
|
|
1082
|
+
expect(alepha.codec.decode(schema, {})).toEqual({});
|
|
1083
|
+
});
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
describe("partial", () => {
|
|
1087
|
+
it("should make all fields optional", async () => {
|
|
1088
|
+
const alepha = Alepha.create();
|
|
1089
|
+
await alepha.start();
|
|
1090
|
+
const baseSchema = t.object({
|
|
1091
|
+
name: t.text(),
|
|
1092
|
+
age: t.integer(),
|
|
1093
|
+
email: t.email(),
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
const partialSchema = t.partial(baseSchema);
|
|
1097
|
+
|
|
1098
|
+
expect(alepha.codec.decode(partialSchema, {})).toEqual({});
|
|
1099
|
+
expect(alepha.codec.decode(partialSchema, { name: "John" })).toEqual({
|
|
1100
|
+
name: "John",
|
|
1101
|
+
});
|
|
1102
|
+
expect(
|
|
1103
|
+
alepha.codec.decode(partialSchema, { name: "John", age: 30 }),
|
|
1104
|
+
).toEqual({
|
|
1105
|
+
name: "John",
|
|
1106
|
+
age: 30,
|
|
1107
|
+
});
|
|
1108
|
+
expect(
|
|
1109
|
+
alepha.codec.decode(partialSchema, {
|
|
1110
|
+
name: "John",
|
|
1111
|
+
age: 30,
|
|
1112
|
+
email: "john@example.com",
|
|
1113
|
+
}),
|
|
1114
|
+
).toEqual({
|
|
1115
|
+
name: "John",
|
|
1116
|
+
age: 30,
|
|
1117
|
+
email: "john@example.com",
|
|
1118
|
+
});
|
|
1119
|
+
});
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
describe("pick", () => {
|
|
1123
|
+
it("should pick only specified fields", async () => {
|
|
1124
|
+
const alepha = Alepha.create();
|
|
1125
|
+
await alepha.start();
|
|
1126
|
+
const baseSchema = t.object({
|
|
1127
|
+
id: t.integer(),
|
|
1128
|
+
name: t.text(),
|
|
1129
|
+
email: t.email(),
|
|
1130
|
+
age: t.integer(),
|
|
1131
|
+
});
|
|
1132
|
+
|
|
1133
|
+
const pickedSchema = t.pick(baseSchema, ["name", "email"]);
|
|
1134
|
+
|
|
1135
|
+
expect(
|
|
1136
|
+
alepha.codec.decode(pickedSchema, {
|
|
1137
|
+
name: "John",
|
|
1138
|
+
email: "john@example.com",
|
|
1139
|
+
}),
|
|
1140
|
+
).toEqual({
|
|
1141
|
+
name: "John",
|
|
1142
|
+
email: "john@example.com",
|
|
1143
|
+
});
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
it("should reject unpicked fields", async () => {
|
|
1147
|
+
const alepha = Alepha.create();
|
|
1148
|
+
await alepha.start();
|
|
1149
|
+
const baseSchema = t.object({
|
|
1150
|
+
id: t.integer(),
|
|
1151
|
+
name: t.text(),
|
|
1152
|
+
email: t.email(),
|
|
1153
|
+
age: t.integer(),
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
const pickedSchema = t.pick(baseSchema, ["name", "email"]);
|
|
1157
|
+
|
|
1158
|
+
expect(
|
|
1159
|
+
alepha.codec.decode(pickedSchema, {
|
|
1160
|
+
name: "John",
|
|
1161
|
+
email: "john@example.com",
|
|
1162
|
+
age: 30,
|
|
1163
|
+
}),
|
|
1164
|
+
).toEqual({
|
|
1165
|
+
name: "John",
|
|
1166
|
+
email: "john@example.com",
|
|
1167
|
+
});
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
it("should require all picked fields", async () => {
|
|
1171
|
+
const alepha = Alepha.create();
|
|
1172
|
+
await alepha.start();
|
|
1173
|
+
const baseSchema = t.object({
|
|
1174
|
+
id: t.integer(),
|
|
1175
|
+
name: t.text(),
|
|
1176
|
+
email: t.email(),
|
|
1177
|
+
age: t.integer(),
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
const pickedSchema = t.pick(baseSchema, ["name", "email"]);
|
|
1181
|
+
|
|
1182
|
+
expect(() =>
|
|
1183
|
+
alepha.codec.validate(pickedSchema, { name: "John" }),
|
|
1184
|
+
).toThrow();
|
|
1185
|
+
});
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
describe("omit", () => {
|
|
1189
|
+
it("should omit specified fields", async () => {
|
|
1190
|
+
const alepha = Alepha.create();
|
|
1191
|
+
await alepha.start();
|
|
1192
|
+
const baseSchema = t.object({
|
|
1193
|
+
id: t.integer(),
|
|
1194
|
+
name: t.text(),
|
|
1195
|
+
email: t.email(),
|
|
1196
|
+
password: t.text(),
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
const omittedSchema = t.omit(baseSchema, ["password"]);
|
|
1200
|
+
|
|
1201
|
+
expect(
|
|
1202
|
+
alepha.codec.decode(omittedSchema, {
|
|
1203
|
+
id: 1,
|
|
1204
|
+
name: "John",
|
|
1205
|
+
email: "john@example.com",
|
|
1206
|
+
}),
|
|
1207
|
+
).toEqual({
|
|
1208
|
+
id: 1,
|
|
1209
|
+
name: "John",
|
|
1210
|
+
email: "john@example.com",
|
|
1211
|
+
});
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
it("should reject omitted fields", async () => {
|
|
1215
|
+
const alepha = Alepha.create();
|
|
1216
|
+
await alepha.start();
|
|
1217
|
+
const baseSchema = t.object({
|
|
1218
|
+
id: t.integer(),
|
|
1219
|
+
name: t.text(),
|
|
1220
|
+
email: t.email(),
|
|
1221
|
+
password: t.text(),
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
const omittedSchema = t.omit(baseSchema, ["password"]);
|
|
1225
|
+
|
|
1226
|
+
expect(
|
|
1227
|
+
alepha.codec.decode(omittedSchema, {
|
|
1228
|
+
id: 1,
|
|
1229
|
+
name: "John",
|
|
1230
|
+
email: "john@example.com",
|
|
1231
|
+
password: "secret",
|
|
1232
|
+
}),
|
|
1233
|
+
).toEqual({
|
|
1234
|
+
id: 1,
|
|
1235
|
+
name: "John",
|
|
1236
|
+
email: "john@example.com",
|
|
1237
|
+
});
|
|
1238
|
+
});
|
|
1239
|
+
});
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
describe("Literal Types", () => {
|
|
1243
|
+
describe("enum", () => {
|
|
1244
|
+
it("should accept valid enum values", async () => {
|
|
1245
|
+
const alepha = Alepha.create();
|
|
1246
|
+
await alepha.start();
|
|
1247
|
+
const schema = t.enum(["ACTIVE", "INACTIVE", "PENDING"]);
|
|
1248
|
+
|
|
1249
|
+
expect(alepha.codec.decode(schema, "ACTIVE")).toBe("ACTIVE");
|
|
1250
|
+
expect(alepha.codec.decode(schema, "INACTIVE")).toBe("INACTIVE");
|
|
1251
|
+
expect(alepha.codec.decode(schema, "PENDING")).toBe("PENDING");
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
it("should reject invalid enum values", async () => {
|
|
1255
|
+
const alepha = Alepha.create();
|
|
1256
|
+
await alepha.start();
|
|
1257
|
+
const schema = t.enum(["ACTIVE", "INACTIVE", "PENDING"]);
|
|
1258
|
+
|
|
1259
|
+
expect(() => alepha.codec.validate(schema, "INVALID")).toThrow();
|
|
1260
|
+
expect(() => alepha.codec.validate(schema, "active")).toThrow(); // Case sensitive
|
|
1261
|
+
expect(() => alepha.codec.validate(schema, "")).toThrow();
|
|
1262
|
+
});
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
describe("const (literal)", () => {
|
|
1266
|
+
it("should accept only the literal value", async () => {
|
|
1267
|
+
const alepha = Alepha.create();
|
|
1268
|
+
await alepha.start();
|
|
1269
|
+
const schema = t.const("FIXED_VALUE");
|
|
1270
|
+
|
|
1271
|
+
expect(alepha.codec.decode(schema, "FIXED_VALUE")).toBe("FIXED_VALUE");
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
it("should reject non-matching values", async () => {
|
|
1275
|
+
const alepha = Alepha.create();
|
|
1276
|
+
await alepha.start();
|
|
1277
|
+
const schema = t.const("FIXED_VALUE");
|
|
1278
|
+
|
|
1279
|
+
expect(() => alepha.codec.validate(schema, "OTHER_VALUE")).toThrow();
|
|
1280
|
+
expect(() => alepha.codec.validate(schema, "")).toThrow();
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
it("should support number literals", async () => {
|
|
1284
|
+
const alepha = Alepha.create();
|
|
1285
|
+
await alepha.start();
|
|
1286
|
+
const schema = t.const(42);
|
|
1287
|
+
|
|
1288
|
+
expect(alepha.codec.decode(schema, 42)).toBe(42);
|
|
1289
|
+
expect(() => alepha.codec.decode(schema, 43)).toThrow();
|
|
1290
|
+
});
|
|
1291
|
+
});
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
describe("Helper Types", () => {
|
|
1295
|
+
describe("valueLabel", () => {
|
|
1296
|
+
it("should decode valid valueLabel objects", async () => {
|
|
1297
|
+
const alepha = Alepha.create();
|
|
1298
|
+
await alepha.start();
|
|
1299
|
+
const schema = t.valueLabel();
|
|
1300
|
+
|
|
1301
|
+
const valid = {
|
|
1302
|
+
value: "ACTIVE",
|
|
1303
|
+
label: "Active Status",
|
|
1304
|
+
};
|
|
1305
|
+
expect(alepha.codec.decode(schema, valid)).toEqual(valid);
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
it("should support optional description", async () => {
|
|
1309
|
+
const alepha = Alepha.create();
|
|
1310
|
+
await alepha.start();
|
|
1311
|
+
const schema = t.valueLabel();
|
|
1312
|
+
|
|
1313
|
+
const withDesc = {
|
|
1314
|
+
value: "PENDING",
|
|
1315
|
+
label: "Pending Status",
|
|
1316
|
+
description: "Item is pending approval",
|
|
1317
|
+
};
|
|
1318
|
+
expect(alepha.codec.decode(schema, withDesc)).toEqual(withDesc);
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
it("should validate value is snake_case", async () => {
|
|
1322
|
+
const alepha = Alepha.create();
|
|
1323
|
+
await alepha.start();
|
|
1324
|
+
const schema = t.valueLabel();
|
|
1325
|
+
|
|
1326
|
+
expect(() =>
|
|
1327
|
+
alepha.codec.decode(schema, {
|
|
1328
|
+
value: "not-snake-case",
|
|
1329
|
+
label: "Label",
|
|
1330
|
+
}),
|
|
1331
|
+
).toThrow();
|
|
1332
|
+
});
|
|
1333
|
+
});
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
describe("File Types", () => {
|
|
1337
|
+
describe("file", () => {
|
|
1338
|
+
it("should create file schema with binary format", async () => {
|
|
1339
|
+
const alepha = Alepha.create();
|
|
1340
|
+
await alepha.start();
|
|
1341
|
+
const schema = t.file();
|
|
1342
|
+
|
|
1343
|
+
expect(schema).toBeDefined();
|
|
1344
|
+
expect((schema as any).format).toBe("binary");
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
it("should support maxSize option", async () => {
|
|
1348
|
+
const alepha = Alepha.create();
|
|
1349
|
+
await alepha.start();
|
|
1350
|
+
const schema = t.file({ maxSize: 1024 * 1024 }); // 1MB
|
|
1351
|
+
|
|
1352
|
+
expect(schema).toBeDefined();
|
|
1353
|
+
expect((schema as any).format).toBe("binary");
|
|
1354
|
+
});
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
describe("stream", () => {
|
|
1358
|
+
it("should create stream schema", async () => {
|
|
1359
|
+
const alepha = Alepha.create();
|
|
1360
|
+
await alepha.start();
|
|
1361
|
+
const schema = t.stream();
|
|
1362
|
+
|
|
1363
|
+
expect(schema).toBeDefined();
|
|
1364
|
+
expect((schema as any).format).toBe("stream");
|
|
1365
|
+
});
|
|
1366
|
+
});
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
describe("Complex Scenarios", () => {
|
|
1370
|
+
it("should handle complex nested structures", async () => {
|
|
1371
|
+
const alepha = Alepha.create();
|
|
1372
|
+
await alepha.start();
|
|
1373
|
+
|
|
1374
|
+
const schema = t.object({
|
|
1375
|
+
id: t.uuid(),
|
|
1376
|
+
user: t.object({
|
|
1377
|
+
name: t.shortText(),
|
|
1378
|
+
email: t.email(),
|
|
1379
|
+
age: t.optional(t.integer()),
|
|
1380
|
+
roles: t.array(t.enum(["ADMIN", "USER", "GUEST"])),
|
|
1381
|
+
}),
|
|
1382
|
+
metadata: t.nullable(
|
|
1383
|
+
t.object({
|
|
1384
|
+
createdAt: t.string(),
|
|
1385
|
+
tags: t.array(t.text()),
|
|
1386
|
+
}),
|
|
1387
|
+
),
|
|
1388
|
+
settings: t.record(t.text(), t.boolean()),
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1391
|
+
const valid = {
|
|
1392
|
+
id: "550e8400-e29b-41d4-a716-446655440000",
|
|
1393
|
+
user: {
|
|
1394
|
+
name: "John Doe",
|
|
1395
|
+
email: "john@example.com",
|
|
1396
|
+
roles: ["ADMIN", "USER"],
|
|
1397
|
+
},
|
|
1398
|
+
metadata: {
|
|
1399
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
1400
|
+
tags: ["important", "reviewed"],
|
|
1401
|
+
},
|
|
1402
|
+
settings: {
|
|
1403
|
+
notifications: true,
|
|
1404
|
+
darkMode: false,
|
|
1405
|
+
},
|
|
1406
|
+
};
|
|
1407
|
+
|
|
1408
|
+
const decoded = alepha.codec.decode(schema, valid);
|
|
1409
|
+
expect(decoded).toEqual(valid);
|
|
1410
|
+
|
|
1411
|
+
// With null metadata
|
|
1412
|
+
const withNull = {
|
|
1413
|
+
...valid,
|
|
1414
|
+
metadata: null,
|
|
1415
|
+
};
|
|
1416
|
+
expect(alepha.codec.decode(schema, withNull)).toEqual(withNull);
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
it("should handle encode/decode round trips", async () => {
|
|
1420
|
+
const alepha = Alepha.create();
|
|
1421
|
+
await alepha.start();
|
|
1422
|
+
|
|
1423
|
+
const schema = t.object({
|
|
1424
|
+
text: t.text(),
|
|
1425
|
+
number: t.number(),
|
|
1426
|
+
bool: t.boolean(),
|
|
1427
|
+
array: t.array(t.integer()),
|
|
1428
|
+
nested: t.object({
|
|
1429
|
+
key: t.text(),
|
|
1430
|
+
}),
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
const original = {
|
|
1434
|
+
text: "hello",
|
|
1435
|
+
number: 123.45,
|
|
1436
|
+
bool: true,
|
|
1437
|
+
array: [1, 2, 3],
|
|
1438
|
+
nested: {
|
|
1439
|
+
key: "value",
|
|
1440
|
+
},
|
|
1441
|
+
};
|
|
1442
|
+
|
|
1443
|
+
const encoded = alepha.codec.encode(schema, original);
|
|
1444
|
+
const decoded = alepha.codec.decode(schema, encoded);
|
|
1445
|
+
|
|
1446
|
+
expect(decoded).toEqual(original);
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
it("should provide validation error context", async () => {
|
|
1450
|
+
const alepha = Alepha.create();
|
|
1451
|
+
await alepha.start();
|
|
1452
|
+
|
|
1453
|
+
const schema = t.object({
|
|
1454
|
+
name: t.text(),
|
|
1455
|
+
age: t.integer(),
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
try {
|
|
1459
|
+
alepha.codec.decode(schema, { name: "John", age: "not a number" });
|
|
1460
|
+
expect.fail("Should have thrown");
|
|
1461
|
+
} catch (error: any) {
|
|
1462
|
+
expect(error).toBeDefined();
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1467
|
+
describe("TypeGuard", () => {
|
|
1468
|
+
it("should correctly identify string schemas", () => {
|
|
1469
|
+
const { schema } = t;
|
|
1470
|
+
|
|
1471
|
+
expect(schema.isString(t.text())).toBe(true);
|
|
1472
|
+
expect(schema.isString(t.integer())).toBe(false);
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
it("should correctly identify number schemas", () => {
|
|
1476
|
+
const { schema } = t;
|
|
1477
|
+
|
|
1478
|
+
expect(schema.isNumber(t.number())).toBe(true);
|
|
1479
|
+
expect(schema.isNumber(t.text())).toBe(false);
|
|
1480
|
+
});
|
|
1481
|
+
|
|
1482
|
+
it("should correctly identify integer schemas", () => {
|
|
1483
|
+
const { schema } = t;
|
|
1484
|
+
|
|
1485
|
+
expect(schema.isInteger(t.integer())).toBe(true);
|
|
1486
|
+
expect(schema.isInteger(t.number())).toBe(false);
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
it("should correctly identify boolean schemas", () => {
|
|
1490
|
+
const { schema } = t;
|
|
1491
|
+
|
|
1492
|
+
expect(schema.isBoolean(t.boolean())).toBe(true);
|
|
1493
|
+
expect(schema.isBoolean(t.text())).toBe(false);
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
it("should correctly identify object schemas", () => {
|
|
1497
|
+
const { schema } = t;
|
|
1498
|
+
|
|
1499
|
+
expect(schema.isObject(t.object({ key: t.text() }))).toBe(true);
|
|
1500
|
+
expect(schema.isObject(t.array(t.text()))).toBe(false);
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
it("should correctly identify array schemas", () => {
|
|
1504
|
+
const { schema } = t;
|
|
1505
|
+
|
|
1506
|
+
expect(schema.isArray(t.array(t.text()))).toBe(true);
|
|
1507
|
+
expect(schema.isArray(t.text())).toBe(false);
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
it("should correctly identify union schemas", () => {
|
|
1511
|
+
const { schema } = t;
|
|
1512
|
+
|
|
1513
|
+
expect(schema.isUnion(t.union([t.text(), t.integer()]))).toBe(true);
|
|
1514
|
+
expect(schema.isUnion(t.text())).toBe(false);
|
|
1515
|
+
});
|
|
1516
|
+
|
|
1517
|
+
it("should correctly identify optional schemas", () => {
|
|
1518
|
+
const { schema } = t;
|
|
1519
|
+
|
|
1520
|
+
expect(schema.isOptional(t.optional(t.text()))).toBe(true);
|
|
1521
|
+
expect(schema.isOptional(t.text())).toBe(false);
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
it("should correctly identify null schemas", () => {
|
|
1525
|
+
const { schema } = t;
|
|
1526
|
+
|
|
1527
|
+
expect(schema.isNull(t.null())).toBe(true);
|
|
1528
|
+
expect(schema.isNull(t.undefined())).toBe(false);
|
|
1529
|
+
});
|
|
1530
|
+
|
|
1531
|
+
it("should correctly identify undefined schemas", () => {
|
|
1532
|
+
const { schema } = t;
|
|
1533
|
+
|
|
1534
|
+
expect(schema.isUndefined(t.undefined())).toBe(true);
|
|
1535
|
+
expect(schema.isUndefined(t.null())).toBe(false);
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
it("should correctly identify any schemas", () => {
|
|
1539
|
+
const { schema } = t;
|
|
1540
|
+
|
|
1541
|
+
expect(schema.isAny(t.any())).toBe(true);
|
|
1542
|
+
expect(schema.isAny(t.text())).toBe(false);
|
|
1543
|
+
});
|
|
1544
|
+
|
|
1545
|
+
it("should correctly identify record schemas", () => {
|
|
1546
|
+
const { schema } = t;
|
|
1547
|
+
|
|
1548
|
+
expect(schema.isRecord(t.record(t.text(), t.integer()))).toBe(true);
|
|
1549
|
+
expect(schema.isRecord(t.object({}))).toBe(false);
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1552
|
+
it("should correctly identify tuple schemas", () => {
|
|
1553
|
+
const { schema } = t;
|
|
1554
|
+
|
|
1555
|
+
expect(schema.isTuple(t.tuple([t.text(), t.integer()]))).toBe(true);
|
|
1556
|
+
expect(schema.isTuple(t.array(t.text()))).toBe(false);
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
it("should correctly identify void schemas", () => {
|
|
1560
|
+
const { schema } = t;
|
|
1561
|
+
|
|
1562
|
+
expect(schema.isVoid(t.void())).toBe(true);
|
|
1563
|
+
expect(schema.isVoid(t.undefined())).toBe(false);
|
|
1564
|
+
});
|
|
1565
|
+
|
|
1566
|
+
it("should correctly identify uuid schemas", () => {
|
|
1567
|
+
const { schema } = t;
|
|
1568
|
+
|
|
1569
|
+
expect(schema.isUUID(t.uuid())).toBe(true);
|
|
1570
|
+
expect(schema.isUUID(t.text())).toBe(false);
|
|
1571
|
+
});
|
|
1572
|
+
|
|
1573
|
+
it("should correctly identify bigint schemas", () => {
|
|
1574
|
+
const { schema } = t;
|
|
1575
|
+
|
|
1576
|
+
expect(schema.isBigInt(t.bigint())).toBe(true);
|
|
1577
|
+
expect(schema.isBigInt(t.integer())).toBe(false);
|
|
1578
|
+
});
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1581
|
+
describe("TypeProvider static methods", () => {
|
|
1582
|
+
describe("isValidBigInt", () => {
|
|
1583
|
+
it("should validate valid bigint strings", () => {
|
|
1584
|
+
expect(TypeProvider.isValidBigInt("123")).toBe(true);
|
|
1585
|
+
expect(TypeProvider.isValidBigInt("0")).toBe(true);
|
|
1586
|
+
expect(TypeProvider.isValidBigInt("-456")).toBe(true);
|
|
1587
|
+
expect(
|
|
1588
|
+
TypeProvider.isValidBigInt("123456789012345678901234567890"),
|
|
1589
|
+
).toBe(true);
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
it("should validate integer numbers", () => {
|
|
1593
|
+
expect(TypeProvider.isValidBigInt(123)).toBe(true);
|
|
1594
|
+
expect(TypeProvider.isValidBigInt(0)).toBe(true);
|
|
1595
|
+
expect(TypeProvider.isValidBigInt(-456)).toBe(true);
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
it("should reject invalid bigint values", () => {
|
|
1599
|
+
expect(TypeProvider.isValidBigInt("12.34")).toBe(false);
|
|
1600
|
+
expect(TypeProvider.isValidBigInt("not a number")).toBe(false);
|
|
1601
|
+
expect(TypeProvider.isValidBigInt("")).toBe(false);
|
|
1602
|
+
expect(TypeProvider.isValidBigInt(" ")).toBe(false);
|
|
1603
|
+
expect(TypeProvider.isValidBigInt(3.14)).toBe(false);
|
|
1604
|
+
});
|
|
1605
|
+
});
|
|
1606
|
+
});
|
|
1607
|
+
});
|