alepha 0.14.2 → 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/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 +1137 -1123
- 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 +13 -13
- 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 +137 -112
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +371 -259
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +45 -5
- 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 +14 -18
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +14 -18
- 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/mcp/index.d.ts.map +1 -1
- package/dist/orm/index.d.ts +32 -32
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +12 -12
- 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/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/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/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/security/index.d.ts +9 -9
- 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/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.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 -4
- package/src/cli/commands/dev.ts +19 -7
- 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/openapi.ts +71 -0
- package/src/cli/commands/gen.ts +18 -0
- package/src/cli/commands/init.ts +2 -0
- package/src/cli/commands/root.ts +12 -3
- package/src/cli/commands/typecheck.ts +5 -0
- package/src/cli/index.ts +2 -1
- 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 +45 -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/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/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 +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
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import { Alepha, t } from "alepha";
|
|
2
|
+
import { sql } from "drizzle-orm";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { $entity, $repository } from "../index.ts";
|
|
5
|
+
import { pg } from "../providers/DatabaseTypeProvider.ts";
|
|
6
|
+
|
|
7
|
+
// Define test entities with all features
|
|
8
|
+
const roleEntity = $entity({
|
|
9
|
+
name: "roles",
|
|
10
|
+
schema: t.object({
|
|
11
|
+
id: pg.primaryKey(),
|
|
12
|
+
name: t.string(),
|
|
13
|
+
description: t.optional(t.string()),
|
|
14
|
+
}),
|
|
15
|
+
indexes: ["name"],
|
|
16
|
+
constraints: [
|
|
17
|
+
{
|
|
18
|
+
columns: ["name"],
|
|
19
|
+
unique: true,
|
|
20
|
+
name: "unique_role_name",
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const userEntity = $entity({
|
|
26
|
+
name: "users",
|
|
27
|
+
schema: t.object({
|
|
28
|
+
id: pg.primaryKey(),
|
|
29
|
+
email: t.email(),
|
|
30
|
+
username: t.string(),
|
|
31
|
+
age: t.integer(),
|
|
32
|
+
roleId: t.integer(),
|
|
33
|
+
status: t.string(),
|
|
34
|
+
}),
|
|
35
|
+
indexes: [
|
|
36
|
+
// Simple index
|
|
37
|
+
"email",
|
|
38
|
+
// Unique index with name
|
|
39
|
+
{
|
|
40
|
+
column: "username",
|
|
41
|
+
unique: true,
|
|
42
|
+
name: "unique_username_idx",
|
|
43
|
+
},
|
|
44
|
+
// Composite index
|
|
45
|
+
{
|
|
46
|
+
columns: ["roleId", "status"],
|
|
47
|
+
name: "role_status_idx",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
foreignKeys: [
|
|
51
|
+
{
|
|
52
|
+
columns: ["roleId"],
|
|
53
|
+
foreignColumns: [() => roleEntity.cols.id],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
constraints: [
|
|
57
|
+
// Check constraint
|
|
58
|
+
{
|
|
59
|
+
columns: ["age"],
|
|
60
|
+
check: sql`age >= 18 AND age <= 120`,
|
|
61
|
+
name: "valid_age_range",
|
|
62
|
+
},
|
|
63
|
+
// Composite unique constraint
|
|
64
|
+
{
|
|
65
|
+
columns: ["email", "username"],
|
|
66
|
+
unique: true,
|
|
67
|
+
name: "unique_email_username",
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const postEntity = $entity({
|
|
73
|
+
name: "posts",
|
|
74
|
+
schema: t.object({
|
|
75
|
+
id: pg.primaryKey(),
|
|
76
|
+
userId: t.integer(),
|
|
77
|
+
title: t.string(),
|
|
78
|
+
content: t.text(),
|
|
79
|
+
published: t.boolean(),
|
|
80
|
+
views: t.integer(),
|
|
81
|
+
tags: t.optional(t.array(t.string())),
|
|
82
|
+
}),
|
|
83
|
+
indexes: [
|
|
84
|
+
// Multiple indexes
|
|
85
|
+
"title",
|
|
86
|
+
{
|
|
87
|
+
column: "published",
|
|
88
|
+
name: "published_idx",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
columns: ["userId", "published"],
|
|
92
|
+
name: "user_published_idx",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
columns: ["userId", "views"],
|
|
96
|
+
unique: true,
|
|
97
|
+
name: "unique_user_views",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
foreignKeys: [
|
|
101
|
+
{
|
|
102
|
+
name: "posts_user_fk",
|
|
103
|
+
columns: ["userId"],
|
|
104
|
+
foreignColumns: [() => userEntity.cols.id],
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
constraints: [
|
|
108
|
+
{
|
|
109
|
+
columns: ["views"],
|
|
110
|
+
check: sql`views >= 0`,
|
|
111
|
+
name: "positive_views",
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Test app with repositories
|
|
117
|
+
class TestApp {
|
|
118
|
+
roles = $repository(roleEntity);
|
|
119
|
+
users = $repository(userEntity);
|
|
120
|
+
posts = $repository(postEntity);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Shared test function that runs on both PostgreSQL and SQLite
|
|
125
|
+
*/
|
|
126
|
+
const testModelBuilderFeatures = async (alepha: Alepha) => {
|
|
127
|
+
const app = alepha.inject(TestApp);
|
|
128
|
+
await alepha.start();
|
|
129
|
+
|
|
130
|
+
// Test 1: Create roles (tests unique constraint on name)
|
|
131
|
+
const adminRole = await app.roles.create({
|
|
132
|
+
name: "admin",
|
|
133
|
+
description: "Administrator role",
|
|
134
|
+
});
|
|
135
|
+
expect(adminRole.id).toBeDefined();
|
|
136
|
+
expect(adminRole.name).toBe("admin");
|
|
137
|
+
|
|
138
|
+
const userRole = await app.roles.create({
|
|
139
|
+
name: "user",
|
|
140
|
+
description: "Regular user role",
|
|
141
|
+
});
|
|
142
|
+
expect(userRole.id).toBeDefined();
|
|
143
|
+
|
|
144
|
+
// Test unique constraint violation
|
|
145
|
+
try {
|
|
146
|
+
await app.roles.create({
|
|
147
|
+
name: "admin", // Duplicate name
|
|
148
|
+
description: "Another admin",
|
|
149
|
+
});
|
|
150
|
+
expect.fail("Should have thrown unique constraint error");
|
|
151
|
+
} catch (error: any) {
|
|
152
|
+
// Expected: unique constraint violation
|
|
153
|
+
expect(error).toBeDefined();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Test 2: Create users (tests foreign key, check constraint, and unique indexes)
|
|
157
|
+
const user1 = await app.users.create({
|
|
158
|
+
email: "alice@example.com",
|
|
159
|
+
username: "alice",
|
|
160
|
+
age: 25,
|
|
161
|
+
roleId: adminRole.id,
|
|
162
|
+
status: "active",
|
|
163
|
+
});
|
|
164
|
+
expect(user1.id).toBeDefined();
|
|
165
|
+
expect(user1.username).toBe("alice");
|
|
166
|
+
|
|
167
|
+
const user2 = await app.users.create({
|
|
168
|
+
email: "bob@example.com",
|
|
169
|
+
username: "bob",
|
|
170
|
+
age: 30,
|
|
171
|
+
roleId: userRole.id,
|
|
172
|
+
status: "active",
|
|
173
|
+
});
|
|
174
|
+
expect(user2.id).toBeDefined();
|
|
175
|
+
|
|
176
|
+
// Test unique index on username
|
|
177
|
+
try {
|
|
178
|
+
await app.users.create({
|
|
179
|
+
email: "charlie@example.com",
|
|
180
|
+
username: "alice", // Duplicate username
|
|
181
|
+
age: 28,
|
|
182
|
+
roleId: userRole.id,
|
|
183
|
+
status: "active",
|
|
184
|
+
});
|
|
185
|
+
expect.fail("Should have thrown unique constraint error on username");
|
|
186
|
+
} catch (error: any) {
|
|
187
|
+
// Expected: unique constraint violation
|
|
188
|
+
expect(error).toBeDefined();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Test check constraint (age must be >= 18)
|
|
192
|
+
try {
|
|
193
|
+
await app.users.create({
|
|
194
|
+
email: "young@example.com",
|
|
195
|
+
username: "younguser",
|
|
196
|
+
age: 15, // Below minimum age
|
|
197
|
+
roleId: userRole.id,
|
|
198
|
+
status: "pending",
|
|
199
|
+
});
|
|
200
|
+
expect.fail("Should have thrown check constraint error for age");
|
|
201
|
+
} catch (error: any) {
|
|
202
|
+
// Expected: check constraint violation
|
|
203
|
+
expect(error).toBeDefined();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Test composite unique constraint (email + username)
|
|
207
|
+
try {
|
|
208
|
+
await app.users.create({
|
|
209
|
+
email: "alice@example.com", // Same email as user1
|
|
210
|
+
username: "alice", // Same username as user1
|
|
211
|
+
age: 22,
|
|
212
|
+
roleId: userRole.id,
|
|
213
|
+
status: "inactive",
|
|
214
|
+
});
|
|
215
|
+
expect.fail("Should have thrown composite unique constraint error");
|
|
216
|
+
} catch (error: any) {
|
|
217
|
+
// Expected: composite unique constraint violation
|
|
218
|
+
expect(error).toBeDefined();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Test 3: Create posts (tests multiple indexes and foreign key)
|
|
222
|
+
const post1 = await app.posts.create({
|
|
223
|
+
userId: user1.id,
|
|
224
|
+
title: "First Post",
|
|
225
|
+
content: "This is the first post",
|
|
226
|
+
published: true,
|
|
227
|
+
views: 100,
|
|
228
|
+
});
|
|
229
|
+
expect(post1.id).toBeDefined();
|
|
230
|
+
expect(post1.views).toBe(100);
|
|
231
|
+
|
|
232
|
+
const post2 = await app.posts.create({
|
|
233
|
+
userId: user1.id,
|
|
234
|
+
title: "Second Post",
|
|
235
|
+
content: "This is the second post",
|
|
236
|
+
published: false,
|
|
237
|
+
views: 50,
|
|
238
|
+
tags: ["tech", "tutorial"],
|
|
239
|
+
});
|
|
240
|
+
expect(post2.id).toBeDefined();
|
|
241
|
+
expect(post2.tags).toEqual(["tech", "tutorial"]);
|
|
242
|
+
|
|
243
|
+
// Test check constraint (views must be >= 0)
|
|
244
|
+
try {
|
|
245
|
+
await app.posts.create({
|
|
246
|
+
userId: user2.id,
|
|
247
|
+
title: "Invalid Post",
|
|
248
|
+
content: "This post has negative views",
|
|
249
|
+
published: true,
|
|
250
|
+
views: -10, // Negative views
|
|
251
|
+
});
|
|
252
|
+
expect.fail("Should have thrown check constraint error for views");
|
|
253
|
+
} catch (error: any) {
|
|
254
|
+
// Expected: check constraint violation
|
|
255
|
+
expect(error).toBeDefined();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Test unique index on (userId, views) - only for testing unique composite index
|
|
259
|
+
try {
|
|
260
|
+
await app.posts.create({
|
|
261
|
+
userId: user1.id,
|
|
262
|
+
title: "Duplicate Views Post",
|
|
263
|
+
content: "This has the same userId and views",
|
|
264
|
+
published: true,
|
|
265
|
+
views: 100, // Same views as post1 for same user
|
|
266
|
+
});
|
|
267
|
+
expect.fail(
|
|
268
|
+
"Should have thrown unique constraint error on (userId, views)",
|
|
269
|
+
);
|
|
270
|
+
} catch (error: any) {
|
|
271
|
+
// Expected: unique constraint violation
|
|
272
|
+
expect(error).toBeDefined();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Test 4: Query using indexes (verify they improve query performance)
|
|
276
|
+
// Find users by email (uses email index)
|
|
277
|
+
const usersByEmail = await app.users.findMany({
|
|
278
|
+
where: { email: { eq: "alice@example.com" } },
|
|
279
|
+
});
|
|
280
|
+
expect(usersByEmail.length).toBe(1);
|
|
281
|
+
expect(usersByEmail[0].username).toBe("alice");
|
|
282
|
+
|
|
283
|
+
// Find users by roleId and status (uses composite index)
|
|
284
|
+
const adminUsers = await app.users.findMany({
|
|
285
|
+
where: { roleId: { eq: adminRole.id } },
|
|
286
|
+
orderBy: "status",
|
|
287
|
+
});
|
|
288
|
+
expect(adminUsers.length).toBeGreaterThan(0);
|
|
289
|
+
expect(adminUsers[0].roleId).toBe(adminRole.id);
|
|
290
|
+
|
|
291
|
+
// Find posts by userId and published status (uses composite index)
|
|
292
|
+
const publishedPosts = await app.posts.findMany({
|
|
293
|
+
where: {
|
|
294
|
+
userId: { eq: user1.id },
|
|
295
|
+
published: { eq: true },
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
expect(publishedPosts.length).toBeGreaterThan(0);
|
|
299
|
+
expect(publishedPosts[0].published).toBe(true);
|
|
300
|
+
|
|
301
|
+
// Test 5: Foreign key cascade behavior
|
|
302
|
+
// When we delete a user, their posts should be affected based on FK settings
|
|
303
|
+
// (Note: The actual cascade behavior depends on the FK configuration)
|
|
304
|
+
|
|
305
|
+
// Clean up
|
|
306
|
+
await app.posts.clear({ force: true });
|
|
307
|
+
await app.users.clear({ force: true });
|
|
308
|
+
await app.roles.clear({ force: true });
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
describe("ModelBuilder Integration Tests", () => {
|
|
312
|
+
it("should handle all entity options correctly in PostgreSQL", async () => {
|
|
313
|
+
await testModelBuilderFeatures(Alepha.create());
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("should handle all entity options correctly in SQLite", async () => {
|
|
317
|
+
await testModelBuilderFeatures(
|
|
318
|
+
Alepha.create({
|
|
319
|
+
env: {
|
|
320
|
+
DATABASE_URL: "sqlite://:memory:",
|
|
321
|
+
},
|
|
322
|
+
}),
|
|
323
|
+
);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("should handle entities with custom config in PostgreSQL", async () => {
|
|
327
|
+
// Test entity with custom config function
|
|
328
|
+
const customEntity = $entity({
|
|
329
|
+
name: "custom_table",
|
|
330
|
+
schema: t.object({
|
|
331
|
+
id: pg.primaryKey(),
|
|
332
|
+
data: t.string(),
|
|
333
|
+
}),
|
|
334
|
+
config: (self) => {
|
|
335
|
+
// Custom config could add additional indexes, constraints, etc.
|
|
336
|
+
// This is called during table creation
|
|
337
|
+
return [];
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
class CustomApp {
|
|
342
|
+
custom = $repository(customEntity);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const alepha = Alepha.create();
|
|
346
|
+
const app = alepha.inject(CustomApp);
|
|
347
|
+
await alepha.start();
|
|
348
|
+
|
|
349
|
+
const record = await app.custom.create({
|
|
350
|
+
data: "test data",
|
|
351
|
+
});
|
|
352
|
+
expect(record.id).toBeDefined();
|
|
353
|
+
expect(record.data).toBe("test data");
|
|
354
|
+
|
|
355
|
+
await app.custom.clear({ force: true });
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("should handle entities with custom config in SQLite", async () => {
|
|
359
|
+
// Test entity with custom config function
|
|
360
|
+
const customEntity = $entity({
|
|
361
|
+
name: "custom_table_sqlite",
|
|
362
|
+
schema: t.object({
|
|
363
|
+
id: pg.primaryKey(),
|
|
364
|
+
data: t.string(),
|
|
365
|
+
}),
|
|
366
|
+
config: (self) => {
|
|
367
|
+
// Custom config for SQLite
|
|
368
|
+
return [];
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
class CustomApp {
|
|
373
|
+
custom = $repository(customEntity);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const alepha = Alepha.create({
|
|
377
|
+
env: {
|
|
378
|
+
DATABASE_URL: "sqlite://:memory:",
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
const app = alepha.inject(CustomApp);
|
|
382
|
+
await alepha.start();
|
|
383
|
+
|
|
384
|
+
const record = await app.custom.create({
|
|
385
|
+
data: "test data sqlite",
|
|
386
|
+
});
|
|
387
|
+
expect(record.id).toBeDefined();
|
|
388
|
+
expect(record.data).toBe("test data sqlite");
|
|
389
|
+
|
|
390
|
+
await app.custom.clear({ force: true });
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("should handle complex nested relationships", async () => {
|
|
394
|
+
const testComplexRelationships = async (alepha: Alepha) => {
|
|
395
|
+
// Category -> Product -> OrderItem -> Order -> User
|
|
396
|
+
const categoryEntity = $entity({
|
|
397
|
+
name: "categories",
|
|
398
|
+
schema: t.object({
|
|
399
|
+
id: pg.primaryKey(),
|
|
400
|
+
name: t.string(),
|
|
401
|
+
}),
|
|
402
|
+
indexes: [
|
|
403
|
+
{
|
|
404
|
+
column: "name",
|
|
405
|
+
unique: true,
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const productEntity = $entity({
|
|
411
|
+
name: "products",
|
|
412
|
+
schema: t.object({
|
|
413
|
+
id: pg.primaryKey(),
|
|
414
|
+
name: t.string(),
|
|
415
|
+
categoryId: t.integer(),
|
|
416
|
+
price: t.number(),
|
|
417
|
+
}),
|
|
418
|
+
indexes: [
|
|
419
|
+
"name",
|
|
420
|
+
{
|
|
421
|
+
columns: ["categoryId", "price"],
|
|
422
|
+
name: "category_price_idx",
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
foreignKeys: [
|
|
426
|
+
{
|
|
427
|
+
columns: ["categoryId"],
|
|
428
|
+
foreignColumns: [() => categoryEntity.cols.id],
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
constraints: [
|
|
432
|
+
{
|
|
433
|
+
columns: ["price"],
|
|
434
|
+
check: sql`price > 0`,
|
|
435
|
+
name: "positive_price",
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
class ComplexApp {
|
|
441
|
+
categories = $repository(categoryEntity);
|
|
442
|
+
products = $repository(productEntity);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const app = alepha.inject(ComplexApp);
|
|
446
|
+
await alepha.start();
|
|
447
|
+
|
|
448
|
+
// Create test data
|
|
449
|
+
const electronics = await app.categories.create({
|
|
450
|
+
name: "Electronics",
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const laptop = await app.products.create({
|
|
454
|
+
name: "Laptop",
|
|
455
|
+
categoryId: electronics.id,
|
|
456
|
+
price: 999.99,
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
expect(laptop.categoryId).toBe(electronics.id);
|
|
460
|
+
expect(laptop.price).toBe(999.99);
|
|
461
|
+
|
|
462
|
+
// Test constraint
|
|
463
|
+
try {
|
|
464
|
+
await app.products.create({
|
|
465
|
+
name: "Invalid Product",
|
|
466
|
+
categoryId: electronics.id,
|
|
467
|
+
price: -10, // Invalid price
|
|
468
|
+
});
|
|
469
|
+
expect.fail("Should have thrown check constraint error");
|
|
470
|
+
} catch (error) {
|
|
471
|
+
// Expected
|
|
472
|
+
expect(error).toBeDefined();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Clean up
|
|
476
|
+
await app.products.clear({ force: true });
|
|
477
|
+
await app.categories.clear({ force: true });
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// Test on both databases
|
|
481
|
+
await testComplexRelationships(Alepha.create());
|
|
482
|
+
await testComplexRelationships(
|
|
483
|
+
Alepha.create({
|
|
484
|
+
env: {
|
|
485
|
+
DATABASE_URL: "sqlite://:memory:",
|
|
486
|
+
},
|
|
487
|
+
}),
|
|
488
|
+
);
|
|
489
|
+
});
|
|
490
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { t } from "alepha";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { $entity } from "../primitives/$entity.ts";
|
|
4
|
+
import { pg } from "../providers/DatabaseTypeProvider.ts";
|
|
5
|
+
|
|
6
|
+
describe("ModelBuilder Type Safety", () => {
|
|
7
|
+
it("should enforce type-safe foreign key references", () => {
|
|
8
|
+
const roleEntity = $entity({
|
|
9
|
+
name: "roles",
|
|
10
|
+
schema: t.object({
|
|
11
|
+
id: pg.primaryKey(),
|
|
12
|
+
name: t.string(),
|
|
13
|
+
}),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const userEntity = $entity({
|
|
17
|
+
name: "users",
|
|
18
|
+
schema: t.object({
|
|
19
|
+
id: pg.primaryKey(),
|
|
20
|
+
email: t.email(),
|
|
21
|
+
roleId: t.integer(),
|
|
22
|
+
}),
|
|
23
|
+
foreignKeys: [
|
|
24
|
+
{
|
|
25
|
+
columns: ["roleId"],
|
|
26
|
+
// This should reference an EntityColumn from roleEntity
|
|
27
|
+
foreignColumns: [() => roleEntity.cols.id],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Test that we can access the column references
|
|
33
|
+
expect(roleEntity.cols.id).toBeDefined();
|
|
34
|
+
expect(roleEntity.cols.id.name).toBe("id");
|
|
35
|
+
expect(roleEntity.cols.id.entity).toBe(roleEntity);
|
|
36
|
+
|
|
37
|
+
expect(userEntity.cols.email).toBeDefined();
|
|
38
|
+
expect(userEntity.cols.email.name).toBe("email");
|
|
39
|
+
|
|
40
|
+
// Test that foreign key references work
|
|
41
|
+
const fkDef = userEntity.options.foreignKeys![0];
|
|
42
|
+
expect(fkDef.columns).toEqual(["roleId"]);
|
|
43
|
+
|
|
44
|
+
// Execute the foreign column reference function
|
|
45
|
+
const foreignCol = fkDef.foreignColumns[0]();
|
|
46
|
+
expect(foreignCol).toBeDefined();
|
|
47
|
+
expect(foreignCol.name).toBe("id");
|
|
48
|
+
expect(foreignCol.entity.name).toBe("roles");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should support multiple foreign key references", () => {
|
|
52
|
+
const categoryEntity = $entity({
|
|
53
|
+
name: "categories",
|
|
54
|
+
schema: t.object({
|
|
55
|
+
id: pg.primaryKey(),
|
|
56
|
+
name: t.string(),
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const userEntity = $entity({
|
|
61
|
+
name: "users",
|
|
62
|
+
schema: t.object({
|
|
63
|
+
id: pg.primaryKey(),
|
|
64
|
+
username: t.string(),
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const postEntity = $entity({
|
|
69
|
+
name: "posts",
|
|
70
|
+
schema: t.object({
|
|
71
|
+
id: pg.primaryKey(),
|
|
72
|
+
title: t.string(),
|
|
73
|
+
userId: t.integer(),
|
|
74
|
+
categoryId: t.integer(),
|
|
75
|
+
}),
|
|
76
|
+
foreignKeys: [
|
|
77
|
+
{
|
|
78
|
+
columns: ["userId"],
|
|
79
|
+
foreignColumns: [() => userEntity.cols.id],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
columns: ["categoryId"],
|
|
83
|
+
foreignColumns: [() => categoryEntity.cols.id],
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const fks = postEntity.options.foreignKeys!;
|
|
89
|
+
expect(fks).toHaveLength(2);
|
|
90
|
+
|
|
91
|
+
// Check first foreign key (userId -> users.id)
|
|
92
|
+
const userFk = fks[0];
|
|
93
|
+
const userForeignCol = userFk.foreignColumns[0]();
|
|
94
|
+
expect(userForeignCol.entity.name).toBe("users");
|
|
95
|
+
expect(userForeignCol.name).toBe("id");
|
|
96
|
+
|
|
97
|
+
// Check second foreign key (categoryId -> categories.id)
|
|
98
|
+
const categoryFk = fks[1];
|
|
99
|
+
const categoryForeignCol = categoryFk.foreignColumns[0]();
|
|
100
|
+
expect(categoryForeignCol.entity.name).toBe("categories");
|
|
101
|
+
expect(categoryForeignCol.name).toBe("id");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should support composite foreign keys", () => {
|
|
105
|
+
const tenantEntity = $entity({
|
|
106
|
+
name: "tenants",
|
|
107
|
+
schema: t.object({
|
|
108
|
+
id: pg.primaryKey(),
|
|
109
|
+
code: t.string(),
|
|
110
|
+
name: t.string(),
|
|
111
|
+
}),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const userEntity = $entity({
|
|
115
|
+
name: "users",
|
|
116
|
+
schema: t.object({
|
|
117
|
+
id: pg.primaryKey(),
|
|
118
|
+
tenantId: t.integer(),
|
|
119
|
+
tenantCode: t.string(),
|
|
120
|
+
username: t.string(),
|
|
121
|
+
}),
|
|
122
|
+
foreignKeys: [
|
|
123
|
+
{
|
|
124
|
+
columns: ["tenantId", "tenantCode"],
|
|
125
|
+
foreignColumns: [
|
|
126
|
+
() => tenantEntity.cols.id,
|
|
127
|
+
() => tenantEntity.cols.code,
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const fk = userEntity.options.foreignKeys![0];
|
|
134
|
+
expect(fk.columns).toEqual(["tenantId", "tenantCode"]);
|
|
135
|
+
expect(fk.foreignColumns).toHaveLength(2);
|
|
136
|
+
|
|
137
|
+
const foreignCol1 = fk.foreignColumns[0]();
|
|
138
|
+
const foreignCol2 = fk.foreignColumns[1]();
|
|
139
|
+
|
|
140
|
+
expect(foreignCol1.name).toBe("id");
|
|
141
|
+
expect(foreignCol2.name).toBe("code");
|
|
142
|
+
expect(foreignCol1.entity.name).toBe("tenants");
|
|
143
|
+
expect(foreignCol2.entity.name).toBe("tenants");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should maintain referential integrity through EntityColumn", () => {
|
|
147
|
+
const entity1 = $entity({
|
|
148
|
+
name: "entity1",
|
|
149
|
+
schema: t.object({
|
|
150
|
+
id: pg.primaryKey(),
|
|
151
|
+
value: t.string(),
|
|
152
|
+
}),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const entity2 = $entity({
|
|
156
|
+
name: "entity2",
|
|
157
|
+
schema: t.object({
|
|
158
|
+
id: pg.primaryKey(),
|
|
159
|
+
entity1Id: t.integer(),
|
|
160
|
+
entity1Value: t.string(),
|
|
161
|
+
}),
|
|
162
|
+
foreignKeys: [
|
|
163
|
+
{
|
|
164
|
+
name: "entity2_entity1_fk",
|
|
165
|
+
columns: ["entity1Id", "entity1Value"],
|
|
166
|
+
foreignColumns: [() => entity1.cols.id, () => entity1.cols.value],
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Verify that the foreign key correctly references entity1's columns
|
|
172
|
+
const fk = entity2.options.foreignKeys![0];
|
|
173
|
+
expect(fk.name).toBe("entity2_entity1_fk");
|
|
174
|
+
|
|
175
|
+
const idRef = fk.foreignColumns[0]();
|
|
176
|
+
const valueRef = fk.foreignColumns[1]();
|
|
177
|
+
|
|
178
|
+
// Both columns should reference the same entity
|
|
179
|
+
expect(idRef.entity).toBe(valueRef.entity);
|
|
180
|
+
expect(idRef.entity.name).toBe("entity1");
|
|
181
|
+
|
|
182
|
+
// But different columns
|
|
183
|
+
expect(idRef.name).toBe("id");
|
|
184
|
+
expect(valueRef.name).toBe("value");
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Alepha } from "alepha";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { $repository, AlephaPostgres } from "../index.ts";
|
|
4
|
+
import { userEntity } from "./fixtures/userEntitySchema.ts";
|
|
5
|
+
|
|
6
|
+
describe("PostgresProvider", () => {
|
|
7
|
+
it("should handle basic CRUD operations with timestamps", async () => {
|
|
8
|
+
class UserService {
|
|
9
|
+
users = $repository(userEntity);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const alepha = Alepha.create().with(AlephaPostgres);
|
|
13
|
+
|
|
14
|
+
const userService = alepha.inject(UserService);
|
|
15
|
+
|
|
16
|
+
await alepha.start();
|
|
17
|
+
|
|
18
|
+
await userService.users.create({
|
|
19
|
+
name: "John",
|
|
20
|
+
profile: {
|
|
21
|
+
age: 30,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const [r1] = await userService.users.findMany({
|
|
26
|
+
where: { name: { eq: "John" } },
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(r1.name).toEqual("John");
|
|
30
|
+
expect(r1.createdAt).toBe(r1.updatedAt);
|
|
31
|
+
|
|
32
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
33
|
+
|
|
34
|
+
const r2 = await userService.users.updateOne(
|
|
35
|
+
{ name: { eq: "John" } },
|
|
36
|
+
{
|
|
37
|
+
profile: { age: 31 },
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
expect(r2.name).toEqual("John");
|
|
42
|
+
expect(r2.profile.age).toEqual(31);
|
|
43
|
+
expect(r2.createdAt).toBe(r1.createdAt);
|
|
44
|
+
expect(r2.updatedAt).not.toBe(r1.updatedAt);
|
|
45
|
+
});
|
|
46
|
+
});
|