@fragno-dev/db 0.1.14 → 0.2.0
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/.turbo/turbo-build.log +242 -139
- package/CHANGELOG.md +47 -0
- package/README.md +123 -8
- package/dist/adapters/adapters.d.ts +19 -5
- package/dist/adapters/adapters.d.ts.map +1 -1
- package/dist/adapters/adapters.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.d.ts +6 -19
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.js +7 -47
- package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
- package/dist/adapters/drizzle/generate.d.ts +7 -1
- package/dist/adapters/drizzle/generate.d.ts.map +1 -1
- package/dist/adapters/drizzle/generate.js +46 -45
- package/dist/adapters/drizzle/generate.js.map +1 -1
- package/dist/adapters/generic-sql/driver-config.d.ts +74 -0
- package/dist/adapters/generic-sql/driver-config.d.ts.map +1 -0
- package/dist/adapters/generic-sql/driver-config.js +94 -0
- package/dist/adapters/generic-sql/driver-config.js.map +1 -0
- package/dist/adapters/generic-sql/generic-sql-adapter.d.ts +43 -0
- package/dist/adapters/generic-sql/generic-sql-adapter.d.ts.map +1 -0
- package/dist/adapters/generic-sql/generic-sql-adapter.js +87 -0
- package/dist/adapters/generic-sql/generic-sql-adapter.js.map +1 -0
- package/dist/adapters/generic-sql/generic-sql-uow-executor.js +67 -0
- package/dist/adapters/generic-sql/generic-sql-uow-executor.js.map +1 -0
- package/dist/adapters/generic-sql/migration/cold-kysely.js +33 -0
- package/dist/adapters/generic-sql/migration/cold-kysely.js.map +1 -0
- package/dist/adapters/generic-sql/migration/dialect/mysql.js +60 -0
- package/dist/adapters/generic-sql/migration/dialect/mysql.js.map +1 -0
- package/dist/adapters/generic-sql/migration/dialect/postgres.js +59 -0
- package/dist/adapters/generic-sql/migration/dialect/postgres.js.map +1 -0
- package/dist/adapters/generic-sql/migration/dialect/sqlite.js +96 -0
- package/dist/adapters/generic-sql/migration/dialect/sqlite.js.map +1 -0
- package/dist/adapters/generic-sql/migration/executor.d.ts +15 -0
- package/dist/adapters/generic-sql/migration/executor.d.ts.map +1 -0
- package/dist/adapters/generic-sql/migration/executor.js +18 -0
- package/dist/adapters/generic-sql/migration/executor.js.map +1 -0
- package/dist/adapters/generic-sql/migration/prepared-migrations.d.ts +66 -0
- package/dist/adapters/generic-sql/migration/prepared-migrations.d.ts.map +1 -0
- package/dist/adapters/generic-sql/migration/prepared-migrations.js +68 -0
- package/dist/adapters/generic-sql/migration/prepared-migrations.js.map +1 -0
- package/dist/adapters/generic-sql/migration/sql-generator.js +212 -0
- package/dist/adapters/generic-sql/migration/sql-generator.js.map +1 -0
- package/dist/adapters/generic-sql/query/create-sql-query-compiler.js +32 -0
- package/dist/adapters/generic-sql/query/create-sql-query-compiler.js.map +1 -0
- package/dist/adapters/generic-sql/query/cursor-utils.js +37 -0
- package/dist/adapters/generic-sql/query/cursor-utils.js.map +1 -0
- package/dist/adapters/generic-sql/query/dialect/mysql.js +33 -0
- package/dist/adapters/generic-sql/query/dialect/mysql.js.map +1 -0
- package/dist/adapters/generic-sql/query/dialect/postgres.js +32 -0
- package/dist/adapters/generic-sql/query/dialect/postgres.js.map +1 -0
- package/dist/adapters/generic-sql/query/dialect/sqlite.js +32 -0
- package/dist/adapters/generic-sql/query/dialect/sqlite.js.map +1 -0
- package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js +152 -0
- package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js.map +1 -0
- package/dist/adapters/generic-sql/query/select-builder.js +69 -0
- package/dist/adapters/generic-sql/query/select-builder.js.map +1 -0
- package/dist/adapters/generic-sql/query/sql-query-compiler.js +145 -0
- package/dist/adapters/generic-sql/query/sql-query-compiler.js.map +1 -0
- package/dist/adapters/generic-sql/query/where-builder.js +129 -0
- package/dist/adapters/generic-sql/query/where-builder.js.map +1 -0
- package/dist/adapters/generic-sql/result-interpreter.js +74 -0
- package/dist/adapters/generic-sql/result-interpreter.js.map +1 -0
- package/dist/adapters/generic-sql/uow-decoder.js +105 -0
- package/dist/adapters/generic-sql/uow-decoder.js.map +1 -0
- package/dist/adapters/generic-sql/uow-encoder.js +93 -0
- package/dist/adapters/generic-sql/uow-encoder.js.map +1 -0
- package/dist/adapters/kysely/kysely-adapter.d.ts +5 -16
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-adapter.js +6 -159
- package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
- package/dist/adapters/{drizzle/drizzle-query.js → shared/from-unit-of-work-compiler.js} +48 -62
- package/dist/adapters/shared/from-unit-of-work-compiler.js.map +1 -0
- package/dist/adapters/{kysely/kysely-shared.d.ts → shared/table-name-mapper.d.ts} +3 -2
- package/dist/adapters/shared/table-name-mapper.d.ts.map +1 -0
- package/dist/adapters/shared/table-name-mapper.js +43 -0
- package/dist/adapters/shared/table-name-mapper.js.map +1 -0
- package/dist/adapters/shared/uow-operation-compiler.js +105 -0
- package/dist/adapters/shared/uow-operation-compiler.js.map +1 -0
- package/dist/db-fragment-definition-builder.d.ts +186 -0
- package/dist/db-fragment-definition-builder.d.ts.map +1 -0
- package/dist/db-fragment-definition-builder.js +207 -0
- package/dist/db-fragment-definition-builder.js.map +1 -0
- package/dist/fragments/internal-fragment.d.ts +53 -0
- package/dist/fragments/internal-fragment.d.ts.map +1 -0
- package/dist/fragments/internal-fragment.js +111 -0
- package/dist/fragments/internal-fragment.js.map +1 -0
- package/dist/hooks/hooks.d.ts +51 -0
- package/dist/hooks/hooks.d.ts.map +1 -0
- package/dist/hooks/hooks.js +88 -0
- package/dist/hooks/hooks.js.map +1 -0
- package/dist/migration-engine/generation-engine.d.ts +0 -2
- package/dist/migration-engine/generation-engine.d.ts.map +1 -1
- package/dist/migration-engine/generation-engine.js +38 -56
- package/dist/migration-engine/generation-engine.js.map +1 -1
- package/dist/mod.d.ts +35 -23
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +48 -45
- package/dist/mod.js.map +1 -1
- package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js +165 -0
- package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js.map +1 -0
- package/dist/packages/fragno/dist/api/bind-services.js +20 -0
- package/dist/packages/fragno/dist/api/bind-services.js.map +1 -0
- package/dist/packages/fragno/dist/api/error.js +48 -0
- package/dist/packages/fragno/dist/api/error.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js +320 -0
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragment-instantiator.js +525 -0
- package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragno-response.js +73 -0
- package/dist/packages/fragno/dist/api/fragno-response.js.map +1 -0
- package/dist/packages/fragno/dist/api/internal/response-stream.js +81 -0
- package/dist/packages/fragno/dist/api/internal/response-stream.js.map +1 -0
- package/dist/packages/fragno/dist/api/internal/route.js +10 -0
- package/dist/packages/fragno/dist/api/internal/route.js.map +1 -0
- package/dist/packages/fragno/dist/api/mutable-request-state.js +97 -0
- package/dist/packages/fragno/dist/api/mutable-request-state.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-context-storage.js +43 -0
- package/dist/packages/fragno/dist/api/request-context-storage.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-input-context.js +118 -0
- package/dist/packages/fragno/dist/api/request-input-context.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-middleware.js +83 -0
- package/dist/packages/fragno/dist/api/request-middleware.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-output-context.js +119 -0
- package/dist/packages/fragno/dist/api/request-output-context.js.map +1 -0
- package/dist/packages/fragno/dist/api/route.js +17 -0
- package/dist/packages/fragno/dist/api/route.js.map +1 -0
- package/dist/packages/fragno/dist/internal/symbols.js +10 -0
- package/dist/packages/fragno/dist/internal/symbols.js.map +1 -0
- package/dist/query/column-defaults.js +27 -0
- package/dist/query/column-defaults.js.map +1 -0
- package/dist/query/cursor.d.ts +14 -6
- package/dist/query/cursor.d.ts.map +1 -1
- package/dist/query/cursor.js +16 -7
- package/dist/query/cursor.js.map +1 -1
- package/dist/query/orm/orm.d.ts +1 -1
- package/dist/query/orm/orm.js.map +1 -1
- package/dist/query/serialize/create-sql-serializer.js +30 -0
- package/dist/query/serialize/create-sql-serializer.js.map +1 -0
- package/dist/query/serialize/dialect/mysql-serializer.js +87 -0
- package/dist/query/serialize/dialect/mysql-serializer.js.map +1 -0
- package/dist/query/serialize/dialect/postgres-serializer.js +80 -0
- package/dist/query/serialize/dialect/postgres-serializer.js.map +1 -0
- package/dist/query/serialize/dialect/sqlite-serializer.js +93 -0
- package/dist/query/serialize/dialect/sqlite-serializer.js.map +1 -0
- package/dist/query/serialize/sql-serializer.js +67 -0
- package/dist/query/serialize/sql-serializer.js.map +1 -0
- package/dist/query/{query.d.ts → simple-query-interface.d.ts} +6 -6
- package/dist/query/simple-query-interface.d.ts.map +1 -0
- package/dist/query/unit-of-work/execute-unit-of-work.d.ts +133 -0
- package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -0
- package/dist/query/unit-of-work/execute-unit-of-work.js +197 -0
- package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -0
- package/dist/query/unit-of-work/retry-policy.d.ts +88 -0
- package/dist/query/unit-of-work/retry-policy.d.ts.map +1 -0
- package/dist/query/unit-of-work/retry-policy.js +61 -0
- package/dist/query/unit-of-work/retry-policy.js.map +1 -0
- package/dist/query/{unit-of-work.d.ts → unit-of-work/unit-of-work.d.ts} +145 -58
- package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -0
- package/dist/query/{unit-of-work.js → unit-of-work/unit-of-work.js} +435 -198
- package/dist/query/unit-of-work/unit-of-work.js.map +1 -0
- package/dist/query/value-decoding.js +71 -0
- package/dist/query/value-decoding.js.map +1 -0
- package/dist/query/value-encoding.js +124 -0
- package/dist/query/value-encoding.js.map +1 -0
- package/dist/schema/create.d.ts +3 -0
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +4 -0
- package/dist/schema/create.js.map +1 -1
- package/dist/schema/type-conversion/create-sql-type-mapper.js +29 -0
- package/dist/schema/type-conversion/create-sql-type-mapper.js.map +1 -0
- package/dist/schema/type-conversion/dialect/mysql.js +57 -0
- package/dist/schema/type-conversion/dialect/mysql.js.map +1 -0
- package/dist/schema/type-conversion/dialect/postgres.js +56 -0
- package/dist/schema/type-conversion/dialect/postgres.js.map +1 -0
- package/dist/schema/type-conversion/dialect/sqlite.js +52 -0
- package/dist/schema/type-conversion/dialect/sqlite.js.map +1 -0
- package/dist/schema/type-conversion/type-mapping.js +63 -0
- package/dist/schema/type-conversion/type-mapping.js.map +1 -0
- package/dist/sql-driver/connection/connection-provider.d.ts +13 -0
- package/dist/sql-driver/connection/connection-provider.d.ts.map +1 -0
- package/dist/sql-driver/connection/connection-provider.js +19 -0
- package/dist/sql-driver/connection/connection-provider.js.map +1 -0
- package/dist/sql-driver/connection/single-connection-provider.js +23 -0
- package/dist/sql-driver/connection/single-connection-provider.js.map +1 -0
- package/dist/sql-driver/dialect-adapter/dialect-adapter.d.ts +7 -0
- package/dist/sql-driver/dialect-adapter/dialect-adapter.d.ts.map +1 -0
- package/dist/sql-driver/dialects/dialects.d.ts +2 -0
- package/dist/sql-driver/dialects/dialects.js +3 -0
- package/dist/sql-driver/dialects/durable-object-dialect.d.ts +72 -0
- package/dist/sql-driver/dialects/durable-object-dialect.d.ts.map +1 -0
- package/dist/sql-driver/dialects/durable-object-dialect.js +130 -0
- package/dist/sql-driver/dialects/durable-object-dialect.js.map +1 -0
- package/dist/sql-driver/driver/runtime-driver.d.ts +23 -0
- package/dist/sql-driver/driver/runtime-driver.d.ts.map +1 -0
- package/dist/sql-driver/driver/runtime-driver.js +56 -0
- package/dist/sql-driver/driver/runtime-driver.js.map +1 -0
- package/dist/sql-driver/query-executor/default-query-executor.js +26 -0
- package/dist/sql-driver/query-executor/default-query-executor.js.map +1 -0
- package/dist/sql-driver/query-executor/plugin.d.ts +17 -0
- package/dist/sql-driver/query-executor/plugin.d.ts.map +1 -0
- package/dist/sql-driver/query-executor/query-executor-base.js +25 -0
- package/dist/sql-driver/query-executor/query-executor-base.js.map +1 -0
- package/dist/sql-driver/query-executor/query-executor.d.ts +36 -0
- package/dist/sql-driver/query-executor/query-executor.d.ts.map +1 -0
- package/dist/sql-driver/sql-driver-adapter.d.ts +29 -0
- package/dist/sql-driver/sql-driver-adapter.d.ts.map +1 -0
- package/dist/sql-driver/sql-driver-adapter.js +68 -0
- package/dist/sql-driver/sql-driver-adapter.js.map +1 -0
- package/dist/sql-driver/sql-driver.d.ts +38 -0
- package/dist/sql-driver/sql-driver.d.ts.map +1 -0
- package/dist/sql-driver/sql-driver.js +1 -0
- package/dist/sql-driver/sql.js +50 -0
- package/dist/sql-driver/sql.js.map +1 -0
- package/dist/with-database.d.ts +32 -0
- package/dist/with-database.d.ts.map +1 -0
- package/dist/with-database.js +34 -0
- package/dist/with-database.js.map +1 -0
- package/package.json +43 -9
- package/src/adapters/adapters.ts +23 -4
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +140 -185
- package/src/adapters/drizzle/{drizzle-adapter-sqlite.test.ts → drizzle-adapter-sqlite3.test.ts} +187 -55
- package/src/adapters/drizzle/drizzle-adapter.ts +14 -93
- package/src/adapters/drizzle/generate.test.ts +102 -269
- package/src/adapters/drizzle/generate.ts +89 -63
- package/src/adapters/drizzle/migrate-drizzle.test.ts +19 -0
- package/src/adapters/drizzle/shared.ts +0 -34
- package/src/adapters/drizzle/test-utils.ts +36 -5
- package/src/adapters/generic-sql/README.md +14 -0
- package/src/adapters/generic-sql/driver-config.ts +144 -0
- package/src/adapters/generic-sql/generic-sql-adapter.test.ts +50 -0
- package/src/adapters/generic-sql/generic-sql-adapter.ts +146 -0
- package/src/adapters/generic-sql/generic-sql-uow-executor.ts +130 -0
- package/src/adapters/generic-sql/migration/cold-kysely.ts +55 -0
- package/src/adapters/{kysely/migration/execute-mysql.test.ts → generic-sql/migration/dialect/mysql.test.ts} +342 -484
- package/src/adapters/generic-sql/migration/dialect/mysql.ts +104 -0
- package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +1008 -0
- package/src/adapters/generic-sql/migration/dialect/postgres.ts +113 -0
- package/src/adapters/{kysely/migration/execute-sqlite.test.ts → generic-sql/migration/dialect/sqlite.test.ts} +307 -510
- package/src/adapters/generic-sql/migration/dialect/sqlite.ts +189 -0
- package/src/adapters/generic-sql/migration/executor.ts +33 -0
- package/src/adapters/generic-sql/migration/prepared-migrations.test.ts +661 -0
- package/src/adapters/generic-sql/migration/prepared-migrations.ts +214 -0
- package/src/adapters/generic-sql/migration/sql-generator.ts +413 -0
- package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +36 -0
- package/src/adapters/generic-sql/query/cursor-utils.ts +56 -0
- package/src/adapters/generic-sql/query/dialect/mysql.ts +34 -0
- package/src/adapters/generic-sql/query/dialect/postgres.ts +32 -0
- package/src/adapters/generic-sql/query/dialect/sqlite.ts +32 -0
- package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.test.ts +1568 -0
- package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +314 -0
- package/src/adapters/generic-sql/query/select-builder.test.ts +256 -0
- package/src/adapters/generic-sql/query/select-builder.ts +137 -0
- package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +195 -0
- package/src/adapters/generic-sql/query/sql-query-compiler.ts +367 -0
- package/src/adapters/generic-sql/query/where-builder.test.ts +744 -0
- package/src/adapters/generic-sql/query/where-builder.ts +211 -0
- package/src/adapters/generic-sql/result-interpreter.ts +102 -0
- package/src/adapters/generic-sql/test/generic-drizzle-adapter-sqlite3.test.ts +899 -0
- package/src/adapters/generic-sql/uow-decoder.test.ts +399 -0
- package/src/adapters/generic-sql/uow-decoder.ts +152 -0
- package/src/adapters/generic-sql/uow-encoder.test.ts +183 -0
- package/src/adapters/generic-sql/uow-encoder.ts +131 -0
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +90 -96
- package/src/adapters/kysely/kysely-adapter-sqlocal.test.ts +215 -0
- package/src/adapters/kysely/kysely-adapter.ts +10 -242
- package/src/adapters/{drizzle/drizzle-query.ts → shared/from-unit-of-work-compiler.ts} +111 -106
- package/src/adapters/shared/table-name-mapper.ts +50 -0
- package/src/adapters/shared/uow-operation-compiler.ts +211 -0
- package/src/db-fragment-definition-builder.test.ts +887 -0
- package/src/db-fragment-definition-builder.ts +737 -0
- package/src/db-fragment-instantiator.test.ts +543 -0
- package/src/db-fragment-integration.test.ts +406 -0
- package/src/fragments/internal-fragment.test.ts +549 -0
- package/src/fragments/internal-fragment.ts +249 -0
- package/src/hooks/hooks.test.ts +575 -0
- package/src/hooks/hooks.ts +179 -0
- package/src/migration-engine/generation-engine.test.ts +60 -27
- package/src/migration-engine/generation-engine.ts +99 -92
- package/src/mod.ts +139 -78
- package/src/query/column-defaults.ts +49 -0
- package/src/query/cursor.test.ts +147 -3
- package/src/query/cursor.ts +25 -8
- package/src/query/orm/orm.ts +1 -1
- package/src/query/query-type.test.ts +9 -9
- package/src/query/serialize/create-sql-serializer.ts +34 -0
- package/src/query/serialize/dialect/mysql-serializer.ts +142 -0
- package/src/query/serialize/dialect/postgres-serializer.ts +129 -0
- package/src/query/serialize/dialect/sqlite-serializer.test.ts +251 -0
- package/src/query/serialize/dialect/sqlite-serializer.ts +156 -0
- package/src/query/serialize/sql-serializer.ts +143 -0
- package/src/query/{query.ts → simple-query-interface.ts} +4 -4
- package/src/query/unit-of-work/execute-unit-of-work.test.ts +1310 -0
- package/src/query/unit-of-work/execute-unit-of-work.ts +504 -0
- package/src/query/unit-of-work/retry-policy.test.ts +217 -0
- package/src/query/unit-of-work/retry-policy.ts +141 -0
- package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +831 -0
- package/src/query/{unit-of-work-types.test.ts → unit-of-work/unit-of-work-types.test.ts} +7 -5
- package/src/query/unit-of-work/unit-of-work.test.ts +1716 -0
- package/src/query/{unit-of-work.ts → unit-of-work/unit-of-work.ts} +716 -420
- package/src/query/{result-transform.test.ts → value-decoding.test.ts} +45 -298
- package/src/query/value-decoding.ts +113 -0
- package/src/query/value-encoding.test.ts +390 -0
- package/src/query/value-encoding.ts +168 -0
- package/src/schema/create.test.ts +5 -1
- package/src/schema/create.ts +5 -0
- package/src/schema/serialize.test.ts +165 -407
- package/src/schema/type-conversion/create-sql-type-mapper.ts +28 -0
- package/src/schema/type-conversion/dialect/mysql.ts +64 -0
- package/src/schema/type-conversion/dialect/postgres.ts +62 -0
- package/src/schema/type-conversion/dialect/sqlite.ts +63 -0
- package/src/schema/type-conversion/type-mapping.test.ts +137 -0
- package/src/schema/type-conversion/type-mapping.ts +153 -0
- package/src/shared/connection-pool.ts +5 -5
- package/src/sql-driver/better-sqlite3.test.ts +126 -0
- package/src/sql-driver/connection/connection-provider.ts +27 -0
- package/src/sql-driver/connection/single-connection-provider.ts +42 -0
- package/src/sql-driver/dialect-adapter/dialect-adapter.ts +9 -0
- package/src/sql-driver/dialect-adapter/sqlite-dialect-adapter.ts +7 -0
- package/src/sql-driver/dialects/dialects.ts +1 -0
- package/src/sql-driver/dialects/durable-object-dialect.ts +260 -0
- package/src/sql-driver/driver/runtime-driver.ts +91 -0
- package/src/sql-driver/query-executor/default-query-executor.ts +38 -0
- package/src/sql-driver/query-executor/plugin.ts +22 -0
- package/src/sql-driver/query-executor/query-executor-base.ts +53 -0
- package/src/sql-driver/query-executor/query-executor.ts +44 -0
- package/src/sql-driver/sql-driver-adapter.ts +96 -0
- package/src/sql-driver/sql-driver.ts +53 -0
- package/src/sql-driver/sql.ts +57 -0
- package/src/sql-driver/sqlocal.test.ts +117 -0
- package/src/with-database.ts +152 -0
- package/tsdown.config.ts +8 -2
- package/dist/adapters/drizzle/drizzle-connection-pool.js +0 -40
- package/dist/adapters/drizzle/drizzle-connection-pool.js.map +0 -1
- package/dist/adapters/drizzle/drizzle-query.d.ts +0 -23
- package/dist/adapters/drizzle/drizzle-query.d.ts.map +0 -1
- package/dist/adapters/drizzle/drizzle-query.js.map +0 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +0 -10
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +0 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.js +0 -315
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +0 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js +0 -116
- package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +0 -1
- package/dist/adapters/drizzle/drizzle-uow-executor.js +0 -149
- package/dist/adapters/drizzle/drizzle-uow-executor.js.map +0 -1
- package/dist/adapters/drizzle/join-column-utils.js +0 -28
- package/dist/adapters/drizzle/join-column-utils.js.map +0 -1
- package/dist/adapters/drizzle/shared.d.ts +0 -14
- package/dist/adapters/drizzle/shared.d.ts.map +0 -1
- package/dist/adapters/drizzle/shared.js +0 -35
- package/dist/adapters/drizzle/shared.js.map +0 -1
- package/dist/adapters/kysely/kysely-connection-pool.js +0 -41
- package/dist/adapters/kysely/kysely-connection-pool.js.map +0 -1
- package/dist/adapters/kysely/kysely-query-builder.js +0 -321
- package/dist/adapters/kysely/kysely-query-builder.js.map +0 -1
- package/dist/adapters/kysely/kysely-query-compiler.js +0 -66
- package/dist/adapters/kysely/kysely-query-compiler.js.map +0 -1
- package/dist/adapters/kysely/kysely-query.d.ts +0 -22
- package/dist/adapters/kysely/kysely-query.d.ts.map +0 -1
- package/dist/adapters/kysely/kysely-query.js +0 -223
- package/dist/adapters/kysely/kysely-query.js.map +0 -1
- package/dist/adapters/kysely/kysely-shared.d.ts.map +0 -1
- package/dist/adapters/kysely/kysely-shared.js +0 -18
- package/dist/adapters/kysely/kysely-shared.js.map +0 -1
- package/dist/adapters/kysely/kysely-uow-compiler.js +0 -170
- package/dist/adapters/kysely/kysely-uow-compiler.js.map +0 -1
- package/dist/adapters/kysely/kysely-uow-executor.js +0 -89
- package/dist/adapters/kysely/kysely-uow-executor.js.map +0 -1
- package/dist/adapters/kysely/migration/execute-base.js +0 -128
- package/dist/adapters/kysely/migration/execute-base.js.map +0 -1
- package/dist/adapters/kysely/migration/execute-factory.js +0 -34
- package/dist/adapters/kysely/migration/execute-factory.js.map +0 -1
- package/dist/adapters/kysely/migration/execute-mssql.js +0 -112
- package/dist/adapters/kysely/migration/execute-mssql.js.map +0 -1
- package/dist/adapters/kysely/migration/execute-mysql.js +0 -93
- package/dist/adapters/kysely/migration/execute-mysql.js.map +0 -1
- package/dist/adapters/kysely/migration/execute-postgres.js +0 -104
- package/dist/adapters/kysely/migration/execute-postgres.js.map +0 -1
- package/dist/adapters/kysely/migration/execute-sqlite.js +0 -123
- package/dist/adapters/kysely/migration/execute-sqlite.js.map +0 -1
- package/dist/adapters/kysely/migration/execute.js +0 -34
- package/dist/adapters/kysely/migration/execute.js.map +0 -1
- package/dist/bind-services.d.ts +0 -7
- package/dist/bind-services.d.ts.map +0 -1
- package/dist/bind-services.js +0 -14
- package/dist/bind-services.js.map +0 -1
- package/dist/fragment.d.ts +0 -173
- package/dist/fragment.d.ts.map +0 -1
- package/dist/fragment.js +0 -191
- package/dist/fragment.js.map +0 -1
- package/dist/migration-engine/create.d.ts +0 -37
- package/dist/migration-engine/create.d.ts.map +0 -1
- package/dist/migration-engine/create.js +0 -58
- package/dist/migration-engine/create.js.map +0 -1
- package/dist/migration-engine/shared.d.ts +0 -112
- package/dist/migration-engine/shared.d.ts.map +0 -1
- package/dist/query/query.d.ts.map +0 -1
- package/dist/query/result-transform.js +0 -168
- package/dist/query/result-transform.js.map +0 -1
- package/dist/query/unit-of-work.d.ts.map +0 -1
- package/dist/query/unit-of-work.js.map +0 -1
- package/dist/schema/serialize.js +0 -106
- package/dist/schema/serialize.js.map +0 -1
- package/dist/shared/settings-schema.js +0 -36
- package/dist/shared/settings-schema.js.map +0 -1
- package/src/adapters/drizzle/drizzle-adapter.test.ts +0 -170
- package/src/adapters/drizzle/drizzle-connection-pool.ts +0 -66
- package/src/adapters/drizzle/drizzle-query.test.ts +0 -499
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +0 -1383
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +0 -636
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +0 -218
- package/src/adapters/drizzle/drizzle-uow-executor.ts +0 -276
- package/src/adapters/drizzle/join-column-utils.test.ts +0 -79
- package/src/adapters/drizzle/join-column-utils.ts +0 -39
- package/src/adapters/kysely/kysely-connection-pool.ts +0 -70
- package/src/adapters/kysely/kysely-query-builder.test.ts +0 -1344
- package/src/adapters/kysely/kysely-query-builder.ts +0 -666
- package/src/adapters/kysely/kysely-query-compiler.ts +0 -132
- package/src/adapters/kysely/kysely-query.test.ts +0 -498
- package/src/adapters/kysely/kysely-query.ts +0 -390
- package/src/adapters/kysely/kysely-shared.ts +0 -23
- package/src/adapters/kysely/kysely-uow-compiler.test.ts +0 -998
- package/src/adapters/kysely/kysely-uow-compiler.ts +0 -318
- package/src/adapters/kysely/kysely-uow-executor.ts +0 -145
- package/src/adapters/kysely/kysely-uow-joins.test.ts +0 -811
- package/src/adapters/kysely/migration/execute-base.ts +0 -256
- package/src/adapters/kysely/migration/execute-factory.ts +0 -53
- package/src/adapters/kysely/migration/execute-mssql.ts +0 -250
- package/src/adapters/kysely/migration/execute-mysql.ts +0 -211
- package/src/adapters/kysely/migration/execute-postgres.test.ts +0 -2657
- package/src/adapters/kysely/migration/execute-postgres.ts +0 -234
- package/src/adapters/kysely/migration/execute-sqlite.ts +0 -247
- package/src/adapters/kysely/migration/execute.ts +0 -50
- package/src/adapters/kysely/migration/kysely-migrator.test.ts +0 -261
- package/src/bind-services.test.ts +0 -214
- package/src/bind-services.ts +0 -37
- package/src/db-fragment.test.ts +0 -800
- package/src/fragment.ts +0 -727
- package/src/query/result-transform.ts +0 -271
- package/src/query/unit-of-work-multi-schema.test.ts +0 -64
- package/src/query/unit-of-work.test.ts +0 -943
- package/src/schema/serialize.ts +0 -396
- package/src/shared/settings-schema.ts +0 -61
- package/src/uow-context-integration.test.ts +0 -102
- package/src/uow-context.test.ts +0 -182
- /package/dist/query/{query.js → simple-query-interface.js} +0 -0
|
@@ -0,0 +1,1716 @@
|
|
|
1
|
+
import { describe, it, expect, assert, expectTypeOf } from "vitest";
|
|
2
|
+
import { column, schema, idColumn, FragnoId } from "../../schema/create";
|
|
3
|
+
import {
|
|
4
|
+
type UOWCompiler,
|
|
5
|
+
type UOWDecoder,
|
|
6
|
+
createUnitOfWork,
|
|
7
|
+
type InferIdColumnName,
|
|
8
|
+
type IndexColumns,
|
|
9
|
+
} from "./unit-of-work";
|
|
10
|
+
import { createIndexedBuilder } from "../condition-builder";
|
|
11
|
+
import type { SimpleQueryInterface } from "../simple-query-interface";
|
|
12
|
+
|
|
13
|
+
// Mock compiler and executor for testing
|
|
14
|
+
function createMockCompiler(): UOWCompiler<unknown> {
|
|
15
|
+
return {
|
|
16
|
+
compileRetrievalOperation: () => null,
|
|
17
|
+
compileMutationOperation: () => null,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createMockExecutor() {
|
|
22
|
+
return {
|
|
23
|
+
executeRetrievalPhase: async () => [],
|
|
24
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function createMockDecoder(): UOWDecoder {
|
|
29
|
+
return {
|
|
30
|
+
decode(rawResults, operations) {
|
|
31
|
+
if (rawResults.length !== operations.length) {
|
|
32
|
+
throw new Error("rawResults and operations must have the same length");
|
|
33
|
+
}
|
|
34
|
+
return rawResults;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("FindBuilder", () => {
|
|
40
|
+
it("should support primary index", () => {
|
|
41
|
+
const testSchema = schema((s) =>
|
|
42
|
+
s.addTable("users", (t) =>
|
|
43
|
+
t
|
|
44
|
+
.addColumn("id", idColumn())
|
|
45
|
+
.addColumn("email", "string")
|
|
46
|
+
.addColumn("name", "string")
|
|
47
|
+
.addColumn("age", "integer")
|
|
48
|
+
.createIndex("idx_email", ["email"], { unique: true })
|
|
49
|
+
.createIndex("idx_name_age", ["name", "age"]),
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
54
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
55
|
+
|
|
56
|
+
const ops = uow.getRetrievalOperations();
|
|
57
|
+
expect(ops).toHaveLength(1);
|
|
58
|
+
expect(ops[0].indexName).toBe("_primary");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should support custom indexes", () => {
|
|
62
|
+
const testSchema = schema((s) =>
|
|
63
|
+
s.addTable("users", (t) =>
|
|
64
|
+
t
|
|
65
|
+
.addColumn("id", idColumn())
|
|
66
|
+
.addColumn("email", "string")
|
|
67
|
+
.addColumn("name", "string")
|
|
68
|
+
.createIndex("idx_email", ["email"], { unique: true }),
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
73
|
+
uow
|
|
74
|
+
.forSchema(testSchema)
|
|
75
|
+
.find("users", (b) =>
|
|
76
|
+
b.whereIndex("idx_email", (eb) => eb("email", "=", "test@example.com")),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const ops = uow.getRetrievalOperations();
|
|
80
|
+
expect(ops).toHaveLength(1);
|
|
81
|
+
expect(ops[0].indexName).toBe("idx_email");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should support cursor-based pagination", () => {
|
|
85
|
+
const testSchema = schema((s) =>
|
|
86
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
90
|
+
|
|
91
|
+
const cursor = "eyJpbmRleFZhbHVlcyI6eyJpZCI6InVzZXIxMjMifSwiZGlyZWN0aW9uIjoiZm9yd2FyZCJ9";
|
|
92
|
+
uow
|
|
93
|
+
.forSchema(testSchema)
|
|
94
|
+
.find("users", (b) => b.whereIndex("primary").after(cursor).pageSize(10));
|
|
95
|
+
|
|
96
|
+
const ops = uow.getRetrievalOperations();
|
|
97
|
+
expect(ops).toHaveLength(1);
|
|
98
|
+
const op = ops[0];
|
|
99
|
+
assert(op.type === "find");
|
|
100
|
+
expect(op.options.after).toBe(cursor);
|
|
101
|
+
expect(op.options.pageSize).toBe(10);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should support backward cursor pagination", () => {
|
|
105
|
+
const testSchema = schema((s) =>
|
|
106
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
110
|
+
|
|
111
|
+
const cursor = "eyJpbmRleFZhbHVlcyI6eyJpZCI6InVzZXI0NTYifSwiZGlyZWN0aW9uIjoiYmFja3dhcmQifQ==";
|
|
112
|
+
uow
|
|
113
|
+
.forSchema(testSchema)
|
|
114
|
+
.find("users", (b) => b.whereIndex("primary").before(cursor).pageSize(5));
|
|
115
|
+
|
|
116
|
+
const ops = uow.getRetrievalOperations();
|
|
117
|
+
expect(ops).toHaveLength(1);
|
|
118
|
+
const op = ops[0];
|
|
119
|
+
assert(op.type === "find");
|
|
120
|
+
expect(op.options.before).toBe(cursor);
|
|
121
|
+
expect(op.options.pageSize).toBe(5);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should throw RangeError for pageSize <= 0", () => {
|
|
125
|
+
const testSchema = schema((s) =>
|
|
126
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
130
|
+
|
|
131
|
+
expect(() => {
|
|
132
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").pageSize(0));
|
|
133
|
+
}).toThrow(RangeError);
|
|
134
|
+
|
|
135
|
+
expect(() => {
|
|
136
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").pageSize(-1));
|
|
137
|
+
}).toThrow(RangeError);
|
|
138
|
+
|
|
139
|
+
expect(() => {
|
|
140
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").pageSize(-10));
|
|
141
|
+
}).toThrow(RangeError);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should throw RangeError for non-integer pageSize", () => {
|
|
145
|
+
const testSchema = schema((s) =>
|
|
146
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
150
|
+
|
|
151
|
+
expect(() => {
|
|
152
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").pageSize(1.5));
|
|
153
|
+
}).toThrow(RangeError);
|
|
154
|
+
|
|
155
|
+
expect(() => {
|
|
156
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").pageSize(3.14));
|
|
157
|
+
}).toThrow(RangeError);
|
|
158
|
+
|
|
159
|
+
expect(() => {
|
|
160
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").pageSize(NaN));
|
|
161
|
+
}).toThrow(RangeError);
|
|
162
|
+
|
|
163
|
+
expect(() => {
|
|
164
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").pageSize(Infinity));
|
|
165
|
+
}).toThrow(RangeError);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should throw if index doesn't exist", () => {
|
|
169
|
+
const testSchema = schema((s) =>
|
|
170
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
174
|
+
expect(() => {
|
|
175
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("nonexistent" as "primary"));
|
|
176
|
+
}).toThrow('Index "nonexistent" not found on table "users"');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should throw if finalized without index", () => {
|
|
180
|
+
const testSchema = schema((s) =>
|
|
181
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
185
|
+
expect(() => {
|
|
186
|
+
uow.forSchema(testSchema).find("users", (b) => b);
|
|
187
|
+
}).toThrow(
|
|
188
|
+
'Must specify an index using .whereIndex() before finalizing find operation on table "users"',
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should support count operations", () => {
|
|
193
|
+
const testSchema = schema((s) =>
|
|
194
|
+
s.addTable("users", (t) =>
|
|
195
|
+
t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("age", "integer"),
|
|
196
|
+
),
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
200
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").selectCount());
|
|
201
|
+
|
|
202
|
+
const ops = uow.getRetrievalOperations();
|
|
203
|
+
expect(ops).toHaveLength(1);
|
|
204
|
+
expect(ops[0]?.type).toBe("count");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("should throw when using both select and selectCount", () => {
|
|
208
|
+
const testSchema = schema((s) =>
|
|
209
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
213
|
+
|
|
214
|
+
// select() then selectCount()
|
|
215
|
+
expect(() => {
|
|
216
|
+
uow
|
|
217
|
+
.forSchema(testSchema)
|
|
218
|
+
.find("users", (b) => b.whereIndex("primary").select(["name"]).selectCount());
|
|
219
|
+
}).toThrow(/cannot call selectCount/i);
|
|
220
|
+
|
|
221
|
+
// selectCount() then select()
|
|
222
|
+
const uow2 = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
223
|
+
expect(() => {
|
|
224
|
+
uow2
|
|
225
|
+
.forSchema(testSchema)
|
|
226
|
+
.find("users", (b) => b.whereIndex("primary").selectCount().select(["name"]));
|
|
227
|
+
}).toThrow(/cannot call select/i);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should support orderByIndex", () => {
|
|
231
|
+
const testSchema = schema((s) =>
|
|
232
|
+
s.addTable("users", (t) =>
|
|
233
|
+
t
|
|
234
|
+
.addColumn("id", idColumn())
|
|
235
|
+
.addColumn("name", "string")
|
|
236
|
+
.addColumn("createdAt", "integer")
|
|
237
|
+
.createIndex("idx_created", ["createdAt"]),
|
|
238
|
+
),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
242
|
+
uow
|
|
243
|
+
.forSchema(testSchema)
|
|
244
|
+
.find("users", (b) => b.whereIndex("primary").orderByIndex("idx_created", "desc"));
|
|
245
|
+
|
|
246
|
+
const ops = uow.getRetrievalOperations();
|
|
247
|
+
expect(ops).toHaveLength(1);
|
|
248
|
+
const op = ops[0];
|
|
249
|
+
if (op?.type === "find") {
|
|
250
|
+
expect(op.options.orderByIndex).toEqual({
|
|
251
|
+
indexName: "idx_created",
|
|
252
|
+
direction: "desc",
|
|
253
|
+
});
|
|
254
|
+
} else {
|
|
255
|
+
throw new Error("Expected find operation");
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("should support join operations", () => {
|
|
260
|
+
const testSchema = schema((s) =>
|
|
261
|
+
s
|
|
262
|
+
.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
|
|
263
|
+
.addTable("posts", (t) =>
|
|
264
|
+
t
|
|
265
|
+
.addColumn("id", idColumn())
|
|
266
|
+
.addColumn("userId", column("string"))
|
|
267
|
+
.addColumn("title", "string")
|
|
268
|
+
.createIndex("idx_user", ["userId"]),
|
|
269
|
+
)
|
|
270
|
+
.addReference("user", {
|
|
271
|
+
type: "one",
|
|
272
|
+
from: { table: "posts", column: "userId" },
|
|
273
|
+
to: { table: "users", column: "id" },
|
|
274
|
+
}),
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
278
|
+
|
|
279
|
+
uow
|
|
280
|
+
.forSchema(testSchema)
|
|
281
|
+
.find("posts", (b) =>
|
|
282
|
+
b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.select(["name"]))),
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const ops = uow.getRetrievalOperations();
|
|
286
|
+
expect(ops).toHaveLength(1);
|
|
287
|
+
const op = ops[0];
|
|
288
|
+
assert(op.type === "find");
|
|
289
|
+
expect(op.options.joins).toBeDefined();
|
|
290
|
+
expect(op.options.joins).toHaveLength(1);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("should support join operations without builder function", () => {
|
|
294
|
+
const testSchema = schema((s) =>
|
|
295
|
+
s
|
|
296
|
+
.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
|
|
297
|
+
.addTable("posts", (t) =>
|
|
298
|
+
t
|
|
299
|
+
.addColumn("id", idColumn())
|
|
300
|
+
.addColumn("userId", column("string"))
|
|
301
|
+
.addColumn("title", "string")
|
|
302
|
+
.createIndex("idx_user", ["userId"]),
|
|
303
|
+
)
|
|
304
|
+
.addReference("user", {
|
|
305
|
+
type: "one",
|
|
306
|
+
from: { table: "posts", column: "userId" },
|
|
307
|
+
to: { table: "users", column: "id" },
|
|
308
|
+
}),
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
312
|
+
|
|
313
|
+
// Join without builder function should use default options
|
|
314
|
+
uow.forSchema(testSchema).find("posts", (b) => b.whereIndex("primary").join((jb) => jb.user()));
|
|
315
|
+
|
|
316
|
+
const ops = uow.getRetrievalOperations();
|
|
317
|
+
expect(ops).toHaveLength(1);
|
|
318
|
+
const op = ops[0];
|
|
319
|
+
assert(op.type === "find");
|
|
320
|
+
expect(op.options.joins).toBeDefined();
|
|
321
|
+
expect(op.options.joins).toHaveLength(1);
|
|
322
|
+
const joinOptions = op.options.joins![0]!.options;
|
|
323
|
+
assert(joinOptions !== false);
|
|
324
|
+
expect(joinOptions.select).toBe(true); // Should default to selecting all columns
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it("should support join with whereIndex", () => {
|
|
328
|
+
const testSchema = schema((s) =>
|
|
329
|
+
s
|
|
330
|
+
.addTable("users", (t) =>
|
|
331
|
+
t
|
|
332
|
+
.addColumn("id", idColumn())
|
|
333
|
+
.addColumn("name", "string")
|
|
334
|
+
.createIndex("idx_name", ["name"]),
|
|
335
|
+
)
|
|
336
|
+
.addTable("posts", (t) =>
|
|
337
|
+
t
|
|
338
|
+
.addColumn("id", idColumn())
|
|
339
|
+
.addColumn("userId", column("string"))
|
|
340
|
+
.addColumn("title", "string")
|
|
341
|
+
.createIndex("idx_user", ["userId"]),
|
|
342
|
+
)
|
|
343
|
+
.addReference("user", {
|
|
344
|
+
type: "one",
|
|
345
|
+
from: { table: "posts", column: "userId" },
|
|
346
|
+
to: { table: "users", column: "id" },
|
|
347
|
+
}),
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
351
|
+
|
|
352
|
+
uow
|
|
353
|
+
.forSchema(testSchema)
|
|
354
|
+
.find("posts", (b) =>
|
|
355
|
+
b
|
|
356
|
+
.whereIndex("primary")
|
|
357
|
+
.join((jb) =>
|
|
358
|
+
jb["user"]((builder) =>
|
|
359
|
+
builder.whereIndex("idx_name", (eb) => eb("name", "=", "Alice")).select(["name"]),
|
|
360
|
+
),
|
|
361
|
+
),
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
const ops = uow.getRetrievalOperations();
|
|
365
|
+
expect(ops).toHaveLength(1);
|
|
366
|
+
const op = ops[0];
|
|
367
|
+
assert(op.type === "find");
|
|
368
|
+
expect(op.options.joins).toBeDefined();
|
|
369
|
+
expect(op.options.joins).toHaveLength(1);
|
|
370
|
+
const joinOptions = op.options.joins![0]!.options;
|
|
371
|
+
assert(joinOptions !== false);
|
|
372
|
+
expect(joinOptions.where).toBeDefined();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("should support join with orderByIndex", () => {
|
|
376
|
+
const testSchema = schema((s) =>
|
|
377
|
+
s
|
|
378
|
+
.addTable("users", (t) =>
|
|
379
|
+
t
|
|
380
|
+
.addColumn("id", idColumn())
|
|
381
|
+
.addColumn("name", "string")
|
|
382
|
+
.addColumn("createdAt", "integer")
|
|
383
|
+
.createIndex("idx_created", ["createdAt"]),
|
|
384
|
+
)
|
|
385
|
+
.addTable("posts", (t) =>
|
|
386
|
+
t
|
|
387
|
+
.addColumn("id", idColumn())
|
|
388
|
+
.addColumn("userId", column("string"))
|
|
389
|
+
.addColumn("title", "string")
|
|
390
|
+
.createIndex("idx_user", ["userId"]),
|
|
391
|
+
)
|
|
392
|
+
.addReference("user", {
|
|
393
|
+
type: "one",
|
|
394
|
+
from: { table: "posts", column: "userId" },
|
|
395
|
+
to: { table: "users", column: "id" },
|
|
396
|
+
}),
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
400
|
+
|
|
401
|
+
uow
|
|
402
|
+
.forSchema(testSchema)
|
|
403
|
+
.find("posts", (b) =>
|
|
404
|
+
b
|
|
405
|
+
.whereIndex("primary")
|
|
406
|
+
.join((jb) => jb["user"]((builder) => builder.orderByIndex("idx_created", "desc"))),
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
const ops = uow.getRetrievalOperations();
|
|
410
|
+
expect(ops).toHaveLength(1);
|
|
411
|
+
const op = ops[0];
|
|
412
|
+
assert(op.type === "find");
|
|
413
|
+
expect(op.options.joins).toBeDefined();
|
|
414
|
+
const joinOptions = op.options.joins![0]!.options;
|
|
415
|
+
assert(joinOptions !== false);
|
|
416
|
+
expect(joinOptions.orderBy).toBeDefined();
|
|
417
|
+
expect(joinOptions.orderBy).toHaveLength(1);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it("should support join with pageSize", () => {
|
|
421
|
+
const testSchema = schema((s) =>
|
|
422
|
+
s
|
|
423
|
+
.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
|
|
424
|
+
.addTable("posts", (t) =>
|
|
425
|
+
t
|
|
426
|
+
.addColumn("id", idColumn())
|
|
427
|
+
.addColumn("userId", column("string"))
|
|
428
|
+
.addColumn("title", "string")
|
|
429
|
+
.createIndex("idx_user", ["userId"]),
|
|
430
|
+
)
|
|
431
|
+
.addReference("user", {
|
|
432
|
+
type: "one",
|
|
433
|
+
from: { table: "posts", column: "userId" },
|
|
434
|
+
to: { table: "users", column: "id" },
|
|
435
|
+
}),
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
439
|
+
|
|
440
|
+
uow
|
|
441
|
+
.forSchema(testSchema)
|
|
442
|
+
.find("posts", (b) =>
|
|
443
|
+
b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.pageSize(5))),
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
const ops = uow.getRetrievalOperations();
|
|
447
|
+
expect(ops).toHaveLength(1);
|
|
448
|
+
const op = ops[0];
|
|
449
|
+
assert(op.type === "find");
|
|
450
|
+
const joinOptions = op.options.joins![0]!.options;
|
|
451
|
+
assert(joinOptions !== false);
|
|
452
|
+
expect(joinOptions.limit).toBe(5);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it("should throw RangeError for invalid pageSize in join", () => {
|
|
456
|
+
const testSchema = schema((s) =>
|
|
457
|
+
s
|
|
458
|
+
.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
|
|
459
|
+
.addTable("posts", (t) =>
|
|
460
|
+
t
|
|
461
|
+
.addColumn("id", idColumn())
|
|
462
|
+
.addColumn("userId", column("string"))
|
|
463
|
+
.addColumn("title", "string")
|
|
464
|
+
.createIndex("idx_user", ["userId"]),
|
|
465
|
+
)
|
|
466
|
+
.addReference("user", {
|
|
467
|
+
type: "one",
|
|
468
|
+
from: { table: "posts", column: "userId" },
|
|
469
|
+
to: { table: "users", column: "id" },
|
|
470
|
+
}),
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
474
|
+
|
|
475
|
+
expect(() => {
|
|
476
|
+
uow
|
|
477
|
+
.forSchema(testSchema)
|
|
478
|
+
.find("posts", (b) =>
|
|
479
|
+
b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.pageSize(0))),
|
|
480
|
+
);
|
|
481
|
+
}).toThrow(RangeError);
|
|
482
|
+
|
|
483
|
+
expect(() => {
|
|
484
|
+
uow
|
|
485
|
+
.forSchema(testSchema)
|
|
486
|
+
.find("posts", (b) =>
|
|
487
|
+
b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.pageSize(-5))),
|
|
488
|
+
);
|
|
489
|
+
}).toThrow(RangeError);
|
|
490
|
+
|
|
491
|
+
expect(() => {
|
|
492
|
+
uow
|
|
493
|
+
.forSchema(testSchema)
|
|
494
|
+
.find("posts", (b) =>
|
|
495
|
+
b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.pageSize(2.5))),
|
|
496
|
+
);
|
|
497
|
+
}).toThrow(RangeError);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it("should support nested joins", () => {
|
|
501
|
+
const testSchema = schema((s) =>
|
|
502
|
+
s
|
|
503
|
+
.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
|
|
504
|
+
.addTable("posts", (t) =>
|
|
505
|
+
t
|
|
506
|
+
.addColumn("id", idColumn())
|
|
507
|
+
.addColumn("userId", column("string"))
|
|
508
|
+
.addColumn("authorId", column("string"))
|
|
509
|
+
.addColumn("title", "string")
|
|
510
|
+
.createIndex("idx_user", ["userId"])
|
|
511
|
+
.createIndex("idx_author", ["authorId"]),
|
|
512
|
+
)
|
|
513
|
+
.addTable("comments", (t) =>
|
|
514
|
+
t
|
|
515
|
+
.addColumn("id", idColumn())
|
|
516
|
+
.addColumn("postId", column("string"))
|
|
517
|
+
.addColumn("text", "string")
|
|
518
|
+
.createIndex("idx_post", ["postId"]),
|
|
519
|
+
)
|
|
520
|
+
.addReference("user", {
|
|
521
|
+
type: "one",
|
|
522
|
+
from: { table: "posts", column: "userId" },
|
|
523
|
+
to: { table: "users", column: "id" },
|
|
524
|
+
})
|
|
525
|
+
.addReference("post", {
|
|
526
|
+
type: "one",
|
|
527
|
+
from: { table: "comments", column: "postId" },
|
|
528
|
+
to: { table: "posts", column: "id" },
|
|
529
|
+
}),
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
533
|
+
|
|
534
|
+
uow
|
|
535
|
+
.forSchema(testSchema)
|
|
536
|
+
.find("comments", (b) =>
|
|
537
|
+
b
|
|
538
|
+
.whereIndex("primary")
|
|
539
|
+
.join((jb) =>
|
|
540
|
+
jb["post"]((postBuilder) =>
|
|
541
|
+
postBuilder
|
|
542
|
+
.select(["title"])
|
|
543
|
+
.join((jb2) => jb2["user"]((userBuilder) => userBuilder.select(["name"]))),
|
|
544
|
+
),
|
|
545
|
+
),
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
const ops = uow.getRetrievalOperations();
|
|
549
|
+
expect(ops).toHaveLength(1);
|
|
550
|
+
const op = ops[0];
|
|
551
|
+
assert(op.type === "find");
|
|
552
|
+
expect(op.options.joins).toBeDefined();
|
|
553
|
+
expect(op.options.joins).toHaveLength(1);
|
|
554
|
+
|
|
555
|
+
const postJoin = op.options.joins![0]!;
|
|
556
|
+
assert(postJoin.options !== false);
|
|
557
|
+
expect(postJoin.options.join).toBeDefined();
|
|
558
|
+
expect(postJoin.options.join).toHaveLength(1);
|
|
559
|
+
|
|
560
|
+
const userJoin = postJoin.options.join![0]!;
|
|
561
|
+
assert(userJoin.options !== false);
|
|
562
|
+
expect(userJoin.relation.name).toBe("user");
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
describe("IndexedConditionBuilder", () => {
|
|
567
|
+
const testSchema = schema((s) =>
|
|
568
|
+
s.addTable("users", (t) =>
|
|
569
|
+
t
|
|
570
|
+
.addColumn("id", idColumn())
|
|
571
|
+
.addColumn("email", column("string"))
|
|
572
|
+
.addColumn("name", column("string"))
|
|
573
|
+
.addColumn("age", column("integer").nullable())
|
|
574
|
+
.addColumn("bio", column("string").nullable()) // Not indexed
|
|
575
|
+
.createIndex("_primary", ["id"], { unique: true })
|
|
576
|
+
.createIndex("idx_email", ["email"], { unique: true })
|
|
577
|
+
.createIndex("idx_name_age", ["name", "age"]),
|
|
578
|
+
),
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
const usersTable = testSchema.tables.users;
|
|
582
|
+
|
|
583
|
+
it("should enforce indexed columns at runtime", () => {
|
|
584
|
+
// Collect all indexed column names from all indexes
|
|
585
|
+
const indexedColumns = new Set<string>();
|
|
586
|
+
for (const index of Object.values(usersTable.indexes)) {
|
|
587
|
+
for (const col of index.columns) {
|
|
588
|
+
indexedColumns.add(col.ormName);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
|
|
593
|
+
|
|
594
|
+
// Should work with indexed columns
|
|
595
|
+
expect(() => builder("id", "=", "123")).not.toThrow();
|
|
596
|
+
expect(() => builder("email", "=", "test@example.com")).not.toThrow();
|
|
597
|
+
expect(() => builder("name", "=", "Alice")).not.toThrow();
|
|
598
|
+
expect(() => builder("age", ">", 18)).not.toThrow();
|
|
599
|
+
|
|
600
|
+
// Should throw when using non-indexed column
|
|
601
|
+
expect(() => builder("bio" as "email", "=", "Some bio")).toThrow('Column "bio" is not indexed');
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it("should work with complex conditions", () => {
|
|
605
|
+
const indexedColumns = new Set(["id", "email", "name", "age"]);
|
|
606
|
+
const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
|
|
607
|
+
|
|
608
|
+
// Complex AND condition with indexed columns
|
|
609
|
+
const condition = builder.and(
|
|
610
|
+
builder("name", "=", "Alice"),
|
|
611
|
+
builder("age", ">", 18),
|
|
612
|
+
builder("email", "contains", "example"),
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
expect(condition).toEqual({
|
|
616
|
+
type: "and",
|
|
617
|
+
items: [
|
|
618
|
+
{
|
|
619
|
+
type: "compare",
|
|
620
|
+
a: usersTable.columns.name,
|
|
621
|
+
operator: "=",
|
|
622
|
+
b: "Alice",
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
type: "compare",
|
|
626
|
+
a: usersTable.columns.age,
|
|
627
|
+
operator: ">",
|
|
628
|
+
b: 18,
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
type: "compare",
|
|
632
|
+
a: usersTable.columns.email,
|
|
633
|
+
operator: "contains",
|
|
634
|
+
b: "example",
|
|
635
|
+
},
|
|
636
|
+
],
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
it("should provide helpful error message listing available columns", () => {
|
|
641
|
+
const indexedColumns = new Set(["id", "email"]);
|
|
642
|
+
const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
|
|
643
|
+
|
|
644
|
+
expect(() => builder("name" as "email", "=", "Alice")).toThrow(
|
|
645
|
+
"Only indexed columns can be used in Unit of Work queries. Available indexed columns: id, email",
|
|
646
|
+
);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
it("should work with all builder helper methods", () => {
|
|
650
|
+
const indexedColumns = new Set(["id", "email", "age"]);
|
|
651
|
+
const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
|
|
652
|
+
|
|
653
|
+
// isNull
|
|
654
|
+
expect(() => builder.isNull("age")).not.toThrow();
|
|
655
|
+
expect(() => builder.isNull("bio" as "age")).toThrow('Column "bio" is not indexed');
|
|
656
|
+
|
|
657
|
+
// isNotNull
|
|
658
|
+
expect(() => builder.isNotNull("email")).not.toThrow();
|
|
659
|
+
expect(() => builder.isNotNull("bio" as "email")).toThrow('Column "bio" is not indexed');
|
|
660
|
+
|
|
661
|
+
// not
|
|
662
|
+
const notCondition = builder.not(builder("id", "=", "123"));
|
|
663
|
+
expect(notCondition).toEqual({
|
|
664
|
+
type: "not",
|
|
665
|
+
item: {
|
|
666
|
+
type: "compare",
|
|
667
|
+
a: usersTable.columns.id,
|
|
668
|
+
operator: "=",
|
|
669
|
+
b: "123",
|
|
670
|
+
},
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// or
|
|
674
|
+
const orCondition = builder.or(builder("email", "contains", "gmail"), builder("age", ">", 30));
|
|
675
|
+
expect(orCondition).toEqual({
|
|
676
|
+
type: "or",
|
|
677
|
+
items: [
|
|
678
|
+
{
|
|
679
|
+
type: "compare",
|
|
680
|
+
a: usersTable.columns.email,
|
|
681
|
+
operator: "contains",
|
|
682
|
+
b: "gmail",
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
type: "compare",
|
|
686
|
+
a: usersTable.columns.age,
|
|
687
|
+
operator: ">",
|
|
688
|
+
b: 30,
|
|
689
|
+
},
|
|
690
|
+
],
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
it("should enforce index restrictions in nested conditions", () => {
|
|
695
|
+
const indexedColumns = new Set(["id", "email"]);
|
|
696
|
+
const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
|
|
697
|
+
|
|
698
|
+
// This should throw because "name" is not indexed, even though it's nested
|
|
699
|
+
expect(() => {
|
|
700
|
+
builder.and(
|
|
701
|
+
builder("email", "=", "test@example.com"),
|
|
702
|
+
builder("name" as "email", "=", "Alice"),
|
|
703
|
+
);
|
|
704
|
+
}).toThrow('Column "name" is not indexed');
|
|
705
|
+
|
|
706
|
+
// This should throw because "bio" is not indexed, even in OR
|
|
707
|
+
expect(() => {
|
|
708
|
+
builder.or(builder("id", "=", "123"), builder("bio" as "id", "=", "Some bio"));
|
|
709
|
+
}).toThrow('Column "bio" is not indexed');
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
describe("type safety", () => {
|
|
713
|
+
it("should restrict to only indexed columns at type level", () => {
|
|
714
|
+
// This schema has "bio" column that is NOT indexed
|
|
715
|
+
const typeTestSchema = schema((s) =>
|
|
716
|
+
s.addTable("users", (t) =>
|
|
717
|
+
t
|
|
718
|
+
.addColumn("id", idColumn())
|
|
719
|
+
.addColumn("email", column("string"))
|
|
720
|
+
.addColumn("name", column("string"))
|
|
721
|
+
.addColumn("age", column("integer").nullable())
|
|
722
|
+
.addColumn("bio", column("string").nullable()) // Not indexed!
|
|
723
|
+
.createIndex("idx_email", ["email"], { unique: true })
|
|
724
|
+
.createIndex("idx_name_age", ["name", "age"]),
|
|
725
|
+
),
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
type _IdColumnName = InferIdColumnName<typeof typeTestSchema.tables.users>;
|
|
729
|
+
expectTypeOf<_IdColumnName>().toEqualTypeOf<"id">();
|
|
730
|
+
type _IndexColumnNames = IndexColumns<
|
|
731
|
+
typeof typeTestSchema.tables.users.indexes.idx_name_age
|
|
732
|
+
>;
|
|
733
|
+
expectTypeOf<_IndexColumnNames>().toEqualTypeOf<"name" | "age">();
|
|
734
|
+
|
|
735
|
+
const baseUow = createUnitOfWork(
|
|
736
|
+
createMockCompiler(),
|
|
737
|
+
createMockExecutor(),
|
|
738
|
+
createMockDecoder(),
|
|
739
|
+
);
|
|
740
|
+
const uow = baseUow.forSchema(typeTestSchema);
|
|
741
|
+
expectTypeOf<keyof typeof typeTestSchema.tables>().toEqualTypeOf<"users">();
|
|
742
|
+
type _Query = SimpleQueryInterface<typeof typeTestSchema>;
|
|
743
|
+
expectTypeOf<Parameters<_Query["create"]>[0]>().toEqualTypeOf<"users">();
|
|
744
|
+
|
|
745
|
+
expectTypeOf<Parameters<typeof uow.find>[0]>().toEqualTypeOf<"users">();
|
|
746
|
+
|
|
747
|
+
uow.find("users", (b) =>
|
|
748
|
+
b.whereIndex("primary", (eb) => {
|
|
749
|
+
type _EbFirstParameter = Parameters<typeof eb>[0];
|
|
750
|
+
expectTypeOf<_EbFirstParameter>().toEqualTypeOf<"id">();
|
|
751
|
+
return eb("id", "=", "123");
|
|
752
|
+
}),
|
|
753
|
+
);
|
|
754
|
+
|
|
755
|
+
uow.find("users", (b) =>
|
|
756
|
+
b.whereIndex("idx_email", (eb) => {
|
|
757
|
+
expectTypeOf(eb).parameter(0).toEqualTypeOf<"email">();
|
|
758
|
+
return eb("email", "=", "123");
|
|
759
|
+
}),
|
|
760
|
+
);
|
|
761
|
+
|
|
762
|
+
uow.find("users", (b) =>
|
|
763
|
+
b.whereIndex("idx_name_age", (eb) => {
|
|
764
|
+
expectTypeOf(eb).parameter(0).toEqualTypeOf<"name" | "age">();
|
|
765
|
+
return eb("name", "=", "123");
|
|
766
|
+
}),
|
|
767
|
+
);
|
|
768
|
+
});
|
|
769
|
+
});
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
describe("UpdateBuilder with string ID", () => {
|
|
773
|
+
const testSchema = schema((s) =>
|
|
774
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
it("should allow update with string ID", async () => {
|
|
778
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
779
|
+
|
|
780
|
+
// Should work with string ID
|
|
781
|
+
uow.forSchema(testSchema).update("users", "user-123", (b) => b.set({ name: "New Name" }));
|
|
782
|
+
|
|
783
|
+
const ops = uow.getMutationOperations();
|
|
784
|
+
expect(ops).toHaveLength(1);
|
|
785
|
+
expect(ops).toMatchObject([
|
|
786
|
+
{
|
|
787
|
+
type: "update",
|
|
788
|
+
id: "user-123",
|
|
789
|
+
checkVersion: false,
|
|
790
|
+
},
|
|
791
|
+
]);
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
it("should throw when using check() with string ID", async () => {
|
|
795
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
796
|
+
|
|
797
|
+
// Should throw because check() is not allowed with string ID
|
|
798
|
+
expect(() => {
|
|
799
|
+
uow
|
|
800
|
+
.forSchema(testSchema)
|
|
801
|
+
.update("users", "user-123", (b) => b.set({ name: "New Name" }).check());
|
|
802
|
+
}).toThrow(
|
|
803
|
+
'Cannot use check() with a string ID on table "users". Version checking requires a FragnoId with version information.',
|
|
804
|
+
);
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
describe("DeleteBuilder with string ID", () => {
|
|
809
|
+
const testSchema = schema((s) =>
|
|
810
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
811
|
+
);
|
|
812
|
+
|
|
813
|
+
it("should allow delete with string ID", async () => {
|
|
814
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
815
|
+
|
|
816
|
+
// Should work with string ID
|
|
817
|
+
uow.forSchema(testSchema).delete("users", "user-123");
|
|
818
|
+
|
|
819
|
+
const ops = uow.getMutationOperations();
|
|
820
|
+
expect(ops).toMatchObject([
|
|
821
|
+
{
|
|
822
|
+
type: "delete",
|
|
823
|
+
id: "user-123",
|
|
824
|
+
checkVersion: false,
|
|
825
|
+
},
|
|
826
|
+
]);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
it("should throw when using check() with string ID", async () => {
|
|
830
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
831
|
+
|
|
832
|
+
// Should throw because check() is not allowed with string ID
|
|
833
|
+
expect(() => {
|
|
834
|
+
uow.forSchema(testSchema).delete("users", "user-123", (b) => b.check());
|
|
835
|
+
}).toThrow(
|
|
836
|
+
'Cannot use check() with a string ID on table "users". Version checking requires a FragnoId with version information.',
|
|
837
|
+
);
|
|
838
|
+
});
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
describe("getCreatedIds", () => {
|
|
842
|
+
const testSchema = schema((s) =>
|
|
843
|
+
s.addTable("users", (t) =>
|
|
844
|
+
t.addColumn("id", idColumn()).addColumn("email", "string").addColumn("name", "string"),
|
|
845
|
+
),
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
it("should return created IDs after executeMutations with internal IDs", async () => {
|
|
849
|
+
const executor = {
|
|
850
|
+
executeRetrievalPhase: async () => [],
|
|
851
|
+
executeMutationPhase: async () => ({
|
|
852
|
+
success: true,
|
|
853
|
+
createdInternalIds: [1n, 2n],
|
|
854
|
+
}),
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
858
|
+
|
|
859
|
+
uow.forSchema(testSchema).create("users", { email: "user1@example.com", name: "User 1" });
|
|
860
|
+
uow.forSchema(testSchema).create("users", { email: "user2@example.com", name: "User 2" });
|
|
861
|
+
|
|
862
|
+
await uow.executeMutations();
|
|
863
|
+
const createdIds = uow.getCreatedIds();
|
|
864
|
+
|
|
865
|
+
expect(createdIds).toHaveLength(2);
|
|
866
|
+
expect(createdIds[0].externalId).toBeDefined();
|
|
867
|
+
expect(createdIds[0].internalId).toBe(1n);
|
|
868
|
+
expect(createdIds[0].version).toBe(0);
|
|
869
|
+
expect(createdIds[1].externalId).toBeDefined();
|
|
870
|
+
expect(createdIds[1].internalId).toBe(2n);
|
|
871
|
+
expect(createdIds[1].version).toBe(0);
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
it("should return created IDs without internal IDs when not supported", async () => {
|
|
875
|
+
const executor = {
|
|
876
|
+
executeRetrievalPhase: async () => [],
|
|
877
|
+
executeMutationPhase: async () => ({
|
|
878
|
+
success: true,
|
|
879
|
+
createdInternalIds: [null, null],
|
|
880
|
+
}),
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
884
|
+
|
|
885
|
+
uow.forSchema(testSchema).create("users", { email: "user1@example.com", name: "User 1" });
|
|
886
|
+
uow.forSchema(testSchema).create("users", { email: "user2@example.com", name: "User 2" });
|
|
887
|
+
|
|
888
|
+
await uow.executeMutations();
|
|
889
|
+
const createdIds = uow.getCreatedIds();
|
|
890
|
+
|
|
891
|
+
expect(createdIds).toHaveLength(2);
|
|
892
|
+
expect(createdIds[0].externalId).toBeDefined();
|
|
893
|
+
expect(createdIds[0].internalId).toBeUndefined();
|
|
894
|
+
expect(createdIds[1].externalId).toBeDefined();
|
|
895
|
+
expect(createdIds[1].internalId).toBeUndefined();
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
it("should preserve user-provided external IDs", async () => {
|
|
899
|
+
const executor = {
|
|
900
|
+
executeRetrievalPhase: async () => [],
|
|
901
|
+
executeMutationPhase: async () => ({
|
|
902
|
+
success: true,
|
|
903
|
+
createdInternalIds: [1n],
|
|
904
|
+
}),
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
908
|
+
|
|
909
|
+
uow
|
|
910
|
+
.forSchema(testSchema)
|
|
911
|
+
.create("users", { id: "my-custom-id", email: "user@example.com", name: "User" });
|
|
912
|
+
|
|
913
|
+
await uow.executeMutations();
|
|
914
|
+
const createdIds = uow.getCreatedIds();
|
|
915
|
+
|
|
916
|
+
expect(createdIds).toHaveLength(1);
|
|
917
|
+
expect(createdIds[0].externalId).toBe("my-custom-id");
|
|
918
|
+
expect(createdIds[0].internalId).toBe(1n);
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
it("should only return IDs for create operations, not updates or deletes", async () => {
|
|
922
|
+
const executor = {
|
|
923
|
+
executeRetrievalPhase: async () => [],
|
|
924
|
+
executeMutationPhase: async () => ({
|
|
925
|
+
success: true,
|
|
926
|
+
createdInternalIds: [1n],
|
|
927
|
+
}),
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
931
|
+
|
|
932
|
+
uow.forSchema(testSchema).create("users", { email: "user@example.com", name: "User" });
|
|
933
|
+
uow.forSchema(testSchema).update("users", "existing-id", (b) => b.set({ name: "Updated" }));
|
|
934
|
+
uow.forSchema(testSchema).delete("users", "other-id");
|
|
935
|
+
|
|
936
|
+
await uow.executeMutations();
|
|
937
|
+
const createdIds = uow.getCreatedIds();
|
|
938
|
+
|
|
939
|
+
// Only one create operation, so only one ID returned
|
|
940
|
+
expect(createdIds).toHaveLength(1);
|
|
941
|
+
expect(createdIds[0].internalId).toBe(1n);
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
it("should throw when called before executeMutations", () => {
|
|
945
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
946
|
+
|
|
947
|
+
uow.forSchema(testSchema).create("users", { email: "user@example.com", name: "User" });
|
|
948
|
+
|
|
949
|
+
expect(() => uow.getCreatedIds()).toThrow(
|
|
950
|
+
"getCreatedIds() can only be called after executeMutations()",
|
|
951
|
+
);
|
|
952
|
+
});
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
describe("Phase promises with multiple views", () => {
|
|
956
|
+
it("should return only operations added to the current view when using retrievalPhase promise", async () => {
|
|
957
|
+
// Create two separate schemas
|
|
958
|
+
const schema1 = schema((s) =>
|
|
959
|
+
s.addTable("users", (t) =>
|
|
960
|
+
t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
|
|
961
|
+
),
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
const schema2 = schema((s) =>
|
|
965
|
+
s.addTable("posts", (t) =>
|
|
966
|
+
t.addColumn("id", idColumn()).addColumn("title", "string").addColumn("content", "string"),
|
|
967
|
+
),
|
|
968
|
+
);
|
|
969
|
+
|
|
970
|
+
// Create a schema namespace map
|
|
971
|
+
const schemaNamespaceMap = new WeakMap<typeof schema1 | typeof schema2, string>();
|
|
972
|
+
schemaNamespaceMap.set(schema1, "namespace1");
|
|
973
|
+
schemaNamespaceMap.set(schema2, "namespace2");
|
|
974
|
+
|
|
975
|
+
// Mock executor that returns distinct results
|
|
976
|
+
const executor = {
|
|
977
|
+
executeRetrievalPhase: async () => {
|
|
978
|
+
return [
|
|
979
|
+
[{ id: "user1", name: "Alice", email: "alice@example.com" }],
|
|
980
|
+
[{ id: "user2", name: "Bob", email: "bob@example.com" }],
|
|
981
|
+
[{ id: "post1", title: "Post 1", content: "Content 1" }],
|
|
982
|
+
];
|
|
983
|
+
},
|
|
984
|
+
executeMutationPhase: async () => ({
|
|
985
|
+
success: true,
|
|
986
|
+
createdInternalIds: [],
|
|
987
|
+
}),
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
// Create parent UOW
|
|
991
|
+
const parentUow = createUnitOfWork(
|
|
992
|
+
createMockCompiler(),
|
|
993
|
+
executor,
|
|
994
|
+
createMockDecoder(),
|
|
995
|
+
schemaNamespaceMap,
|
|
996
|
+
"test-uow",
|
|
997
|
+
);
|
|
998
|
+
|
|
999
|
+
// Add a find operation via a schema1 view (but don't keep a reference to this view)
|
|
1000
|
+
parentUow.forSchema(schema1).find("users", (b) => b.whereIndex("primary"));
|
|
1001
|
+
|
|
1002
|
+
// Create a view for schema1 and add another find operation
|
|
1003
|
+
const view1 = parentUow.forSchema(schema1).find("users", (b) => b.whereIndex("primary"));
|
|
1004
|
+
|
|
1005
|
+
// Create a view for schema2 and add a find operation
|
|
1006
|
+
const view2 = parentUow.forSchema(schema2).find("posts", (b) => b.whereIndex("primary"));
|
|
1007
|
+
|
|
1008
|
+
// Execute retrieval phase on parent
|
|
1009
|
+
const parentResults = await parentUow.executeRetrieve();
|
|
1010
|
+
|
|
1011
|
+
// Parent should have all 3 results
|
|
1012
|
+
expect(parentResults).toHaveLength(3);
|
|
1013
|
+
|
|
1014
|
+
// View1's retrievalPhase promise should only contain results from operations added through view1
|
|
1015
|
+
// (which is index 1 in the parent's operations)
|
|
1016
|
+
const view1Results = await view1.retrievalPhase;
|
|
1017
|
+
expect(view1Results).toHaveLength(1);
|
|
1018
|
+
expect(view1Results[0]).toEqual([{ id: "user2", name: "Bob", email: "bob@example.com" }]);
|
|
1019
|
+
|
|
1020
|
+
// View2's retrievalPhase promise should only contain results from operations added through view2
|
|
1021
|
+
// (which is index 2 in the parent's operations)
|
|
1022
|
+
const view2Results = await view2.retrievalPhase;
|
|
1023
|
+
expect(view2Results).toHaveLength(1);
|
|
1024
|
+
expect(view2Results[0]).toEqual([{ id: "post1", title: "Post 1", content: "Content 1" }]);
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
it("should isolate operations when getUnitOfWork is called multiple times with same schema", async () => {
|
|
1028
|
+
const testSchema = schema((s) =>
|
|
1029
|
+
s.addTable("users", (t) =>
|
|
1030
|
+
t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
|
|
1031
|
+
),
|
|
1032
|
+
);
|
|
1033
|
+
|
|
1034
|
+
const executor = {
|
|
1035
|
+
executeRetrievalPhase: async () => {
|
|
1036
|
+
return [
|
|
1037
|
+
[{ id: "user1", name: "Alice", email: "alice@example.com" }],
|
|
1038
|
+
[{ id: "user2", name: "Bob", email: "bob@example.com" }],
|
|
1039
|
+
];
|
|
1040
|
+
},
|
|
1041
|
+
executeMutationPhase: async () => ({
|
|
1042
|
+
success: true,
|
|
1043
|
+
createdInternalIds: [],
|
|
1044
|
+
}),
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
const parentUow = createUnitOfWork(
|
|
1048
|
+
createMockCompiler(),
|
|
1049
|
+
executor,
|
|
1050
|
+
createMockDecoder(),
|
|
1051
|
+
undefined,
|
|
1052
|
+
"test-uow",
|
|
1053
|
+
);
|
|
1054
|
+
|
|
1055
|
+
// Simulate what happens in db-fragment-definition-builder when getUnitOfWork(schema) is called twice
|
|
1056
|
+
const view1 = parentUow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
1057
|
+
|
|
1058
|
+
const view2 = parentUow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
1059
|
+
|
|
1060
|
+
// Execute retrieval
|
|
1061
|
+
await parentUow.executeRetrieve();
|
|
1062
|
+
|
|
1063
|
+
// Each view should only see its own operation's results
|
|
1064
|
+
const view1Results = await view1.retrievalPhase;
|
|
1065
|
+
expect(view1Results).toHaveLength(1);
|
|
1066
|
+
expect(view1Results[0]).toEqual([{ id: "user1", name: "Alice", email: "alice@example.com" }]);
|
|
1067
|
+
|
|
1068
|
+
const view2Results = await view2.retrievalPhase;
|
|
1069
|
+
expect(view2Results).toHaveLength(1);
|
|
1070
|
+
expect(view2Results[0]).toEqual([{ id: "user2", name: "Bob", email: "bob@example.com" }]);
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
it("should show that getCreatedIds returns ALL created IDs regardless of which view created them", async () => {
|
|
1074
|
+
const schema1 = schema((s) =>
|
|
1075
|
+
s.addTable("users", (t) =>
|
|
1076
|
+
t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
|
|
1077
|
+
),
|
|
1078
|
+
);
|
|
1079
|
+
|
|
1080
|
+
const schema2 = schema((s) =>
|
|
1081
|
+
s.addTable("posts", (t) =>
|
|
1082
|
+
t.addColumn("id", idColumn()).addColumn("title", "string").addColumn("content", "string"),
|
|
1083
|
+
),
|
|
1084
|
+
);
|
|
1085
|
+
|
|
1086
|
+
const schemaNamespaceMap = new WeakMap<typeof schema1 | typeof schema2, string>();
|
|
1087
|
+
schemaNamespaceMap.set(schema1, "namespace1");
|
|
1088
|
+
schemaNamespaceMap.set(schema2, "namespace2");
|
|
1089
|
+
|
|
1090
|
+
const executor = {
|
|
1091
|
+
executeRetrievalPhase: async () => [],
|
|
1092
|
+
executeMutationPhase: async () => ({
|
|
1093
|
+
success: true,
|
|
1094
|
+
createdInternalIds: [1n, 2n],
|
|
1095
|
+
}),
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
const parentUow = createUnitOfWork(
|
|
1099
|
+
createMockCompiler(),
|
|
1100
|
+
executor,
|
|
1101
|
+
createMockDecoder(),
|
|
1102
|
+
schemaNamespaceMap,
|
|
1103
|
+
"test-uow",
|
|
1104
|
+
);
|
|
1105
|
+
|
|
1106
|
+
// View1 creates one user
|
|
1107
|
+
const view1 = parentUow.forSchema(schema1);
|
|
1108
|
+
view1.create("users", { name: "Alice", email: "alice@example.com" });
|
|
1109
|
+
|
|
1110
|
+
// View2 creates one post
|
|
1111
|
+
const view2 = parentUow.forSchema(schema2);
|
|
1112
|
+
view2.create("posts", { title: "Post 1", content: "Content 1" });
|
|
1113
|
+
|
|
1114
|
+
// Execute mutations
|
|
1115
|
+
await parentUow.executeMutations();
|
|
1116
|
+
|
|
1117
|
+
// Both views see ALL created IDs (not filtered by view)
|
|
1118
|
+
const view1Ids = view1.getCreatedIds();
|
|
1119
|
+
const view2Ids = view2.getCreatedIds();
|
|
1120
|
+
|
|
1121
|
+
expect(view1Ids).toHaveLength(2); // Sees both IDs, not just the one it created
|
|
1122
|
+
expect(view2Ids).toHaveLength(2); // Sees both IDs, not just the one it created
|
|
1123
|
+
|
|
1124
|
+
// They're the same array
|
|
1125
|
+
expect(view1Ids).toEqual(view2Ids);
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
it("should generate unique IDs when multiple views create items", async () => {
|
|
1129
|
+
const schema1 = schema((s) =>
|
|
1130
|
+
s.addTable("users", (t) =>
|
|
1131
|
+
t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
|
|
1132
|
+
),
|
|
1133
|
+
);
|
|
1134
|
+
|
|
1135
|
+
const schema2 = schema((s) =>
|
|
1136
|
+
s.addTable("posts", (t) =>
|
|
1137
|
+
t.addColumn("id", idColumn()).addColumn("title", "string").addColumn("content", "string"),
|
|
1138
|
+
),
|
|
1139
|
+
);
|
|
1140
|
+
|
|
1141
|
+
const schemaNamespaceMap = new WeakMap<typeof schema1 | typeof schema2, string>();
|
|
1142
|
+
schemaNamespaceMap.set(schema1, "namespace1");
|
|
1143
|
+
schemaNamespaceMap.set(schema2, "namespace2");
|
|
1144
|
+
|
|
1145
|
+
const executor = {
|
|
1146
|
+
executeRetrievalPhase: async () => [],
|
|
1147
|
+
executeMutationPhase: async () => ({
|
|
1148
|
+
success: true,
|
|
1149
|
+
createdInternalIds: [1n, 2n],
|
|
1150
|
+
}),
|
|
1151
|
+
};
|
|
1152
|
+
|
|
1153
|
+
const parentUow = createUnitOfWork(
|
|
1154
|
+
createMockCompiler(),
|
|
1155
|
+
executor,
|
|
1156
|
+
createMockDecoder(),
|
|
1157
|
+
schemaNamespaceMap,
|
|
1158
|
+
"test-uow",
|
|
1159
|
+
);
|
|
1160
|
+
|
|
1161
|
+
// View1 creates a user
|
|
1162
|
+
const view1 = parentUow.forSchema(schema1);
|
|
1163
|
+
const userId = view1.create("users", { name: "Alice", email: "alice@example.com" });
|
|
1164
|
+
|
|
1165
|
+
// View2 creates a post
|
|
1166
|
+
const view2 = parentUow.forSchema(schema2);
|
|
1167
|
+
const postId = view2.create("posts", { title: "Post 1", content: "Content 1" });
|
|
1168
|
+
|
|
1169
|
+
// IDs should be unique before execution
|
|
1170
|
+
expect(userId.externalId).not.toBe(postId.externalId);
|
|
1171
|
+
expect(userId.externalId).toBeTruthy();
|
|
1172
|
+
expect(postId.externalId).toBeTruthy();
|
|
1173
|
+
expect(userId.internalId).toBeUndefined();
|
|
1174
|
+
expect(postId.internalId).toBeUndefined();
|
|
1175
|
+
|
|
1176
|
+
// Execute mutations
|
|
1177
|
+
await parentUow.executeMutations();
|
|
1178
|
+
|
|
1179
|
+
// Get the created IDs after execution
|
|
1180
|
+
const createdIds = parentUow.getCreatedIds();
|
|
1181
|
+
expect(createdIds).toHaveLength(2);
|
|
1182
|
+
|
|
1183
|
+
// Both should have external IDs set (from create operation)
|
|
1184
|
+
expect(createdIds[0].externalId).toBe(userId.externalId);
|
|
1185
|
+
expect(createdIds[1].externalId).toBe(postId.externalId);
|
|
1186
|
+
|
|
1187
|
+
// Both should now have internal IDs (from database)
|
|
1188
|
+
expect(createdIds[0].internalId).toBe(1n);
|
|
1189
|
+
expect(createdIds[1].internalId).toBe(2n);
|
|
1190
|
+
|
|
1191
|
+
// IDs should still be unique
|
|
1192
|
+
expect(createdIds[0].externalId).not.toBe(createdIds[1].externalId);
|
|
1193
|
+
});
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
describe("Error Handling", () => {
|
|
1197
|
+
const testSchema = schema((s) =>
|
|
1198
|
+
s.addTable("users", (t) =>
|
|
1199
|
+
t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
|
|
1200
|
+
),
|
|
1201
|
+
);
|
|
1202
|
+
|
|
1203
|
+
it("should throw error from executeRetrieve() when retrieval fails", async () => {
|
|
1204
|
+
const executor = {
|
|
1205
|
+
executeRetrievalPhase: async () => {
|
|
1206
|
+
throw new Error("Database connection failed");
|
|
1207
|
+
},
|
|
1208
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1212
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
1213
|
+
|
|
1214
|
+
await expect(uow.executeRetrieve()).rejects.toThrow("Database connection failed");
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
it("should throw error from executeMutations() when mutation fails", async () => {
|
|
1218
|
+
const executor = {
|
|
1219
|
+
executeRetrievalPhase: async () => [],
|
|
1220
|
+
executeMutationPhase: async () => {
|
|
1221
|
+
throw new Error("Write conflict");
|
|
1222
|
+
},
|
|
1223
|
+
};
|
|
1224
|
+
|
|
1225
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1226
|
+
uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
|
|
1227
|
+
|
|
1228
|
+
await expect(uow.executeMutations()).rejects.toThrow("Write conflict");
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
it("should reject retrievalPhase promise when executeRetrieve() fails", async () => {
|
|
1232
|
+
const executor = {
|
|
1233
|
+
executeRetrievalPhase: async () => {
|
|
1234
|
+
throw new Error("Query timeout");
|
|
1235
|
+
},
|
|
1236
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1237
|
+
};
|
|
1238
|
+
|
|
1239
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1240
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
1241
|
+
|
|
1242
|
+
// Start executing (this will fail)
|
|
1243
|
+
const executePromise = uow.executeRetrieve().catch((e) => {
|
|
1244
|
+
return (e as Error).message;
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
uow.retrievalPhase
|
|
1248
|
+
.then(() => {
|
|
1249
|
+
throw new Error("Should not be called");
|
|
1250
|
+
})
|
|
1251
|
+
.catch(() => {
|
|
1252
|
+
throw new Error("Should not be called");
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
// uow.retrievalPhase should not be settled yet
|
|
1256
|
+
|
|
1257
|
+
// Wait for execute to complete
|
|
1258
|
+
expect(await executePromise).toBe("Query timeout");
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
it("should reject mutationPhase promise when executeMutations() fails", async () => {
|
|
1262
|
+
const executor = {
|
|
1263
|
+
executeRetrievalPhase: async () => [],
|
|
1264
|
+
executeMutationPhase: async () => {
|
|
1265
|
+
throw new Error("Constraint violation");
|
|
1266
|
+
},
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1270
|
+
uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
|
|
1271
|
+
|
|
1272
|
+
// Start executing (this will fail)
|
|
1273
|
+
const executePromise = uow.executeMutations().catch((e) => {
|
|
1274
|
+
return (e as Error).message;
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
uow.mutationPhase
|
|
1278
|
+
.then(() => {
|
|
1279
|
+
throw new Error("Should not be called");
|
|
1280
|
+
})
|
|
1281
|
+
.catch(() => {
|
|
1282
|
+
throw new Error("Should not be called");
|
|
1283
|
+
});
|
|
1284
|
+
// Wait for execute to complete
|
|
1285
|
+
expect(await executePromise).toBe("Constraint violation");
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
it("should not cause unhandled promise rejection when executeRetrieve() fails and coordination promise is not awaited", async () => {
|
|
1289
|
+
const executor = {
|
|
1290
|
+
executeRetrievalPhase: async () => {
|
|
1291
|
+
throw new Error("Table does not exist");
|
|
1292
|
+
},
|
|
1293
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1297
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
1298
|
+
|
|
1299
|
+
// Access the retrievalPhase promise but don't await it
|
|
1300
|
+
// This simulates the internal coordination promise that might not be awaited
|
|
1301
|
+
const _retrievalPhase = uow.retrievalPhase;
|
|
1302
|
+
|
|
1303
|
+
const errorResolver = Promise.withResolvers<void>();
|
|
1304
|
+
|
|
1305
|
+
// Execute and catch the error from executeRetrieve()
|
|
1306
|
+
try {
|
|
1307
|
+
await uow.executeRetrieve();
|
|
1308
|
+
} catch (error) {
|
|
1309
|
+
// Error is caught, this is expected
|
|
1310
|
+
expect(error).toBeInstanceOf(Error);
|
|
1311
|
+
expect((error as Error).message).toBe("Table does not exist");
|
|
1312
|
+
errorResolver.resolve();
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
await errorResolver.promise;
|
|
1316
|
+
});
|
|
1317
|
+
|
|
1318
|
+
it("should not cause unhandled promise rejection when executeMutations() fails and coordination promise is not awaited", async () => {
|
|
1319
|
+
const executor = {
|
|
1320
|
+
executeRetrievalPhase: async () => [],
|
|
1321
|
+
executeMutationPhase: async () => {
|
|
1322
|
+
throw new Error("Deadlock detected");
|
|
1323
|
+
},
|
|
1324
|
+
};
|
|
1325
|
+
|
|
1326
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1327
|
+
uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
|
|
1328
|
+
|
|
1329
|
+
// Access the mutationPhase promise but don't await it
|
|
1330
|
+
// This simulates the internal coordination promise that might not be awaited
|
|
1331
|
+
const _mutationPhase = uow.mutationPhase;
|
|
1332
|
+
|
|
1333
|
+
const errorResolver = Promise.withResolvers<void>();
|
|
1334
|
+
|
|
1335
|
+
// Execute and catch the error from executeMutations()
|
|
1336
|
+
try {
|
|
1337
|
+
await uow.executeMutations();
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
// Error is caught, this is expected
|
|
1340
|
+
expect(error).toBeInstanceOf(Error);
|
|
1341
|
+
expect((error as Error).message).toBe("Deadlock detected");
|
|
1342
|
+
errorResolver.resolve();
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
await errorResolver.promise;
|
|
1346
|
+
});
|
|
1347
|
+
|
|
1348
|
+
it("should handle error in executeRetrieve() when coordination promise is never accessed", async () => {
|
|
1349
|
+
const executor = {
|
|
1350
|
+
executeRetrievalPhase: async () => {
|
|
1351
|
+
throw new Error("Connection lost");
|
|
1352
|
+
},
|
|
1353
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1354
|
+
};
|
|
1355
|
+
|
|
1356
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1357
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
1358
|
+
|
|
1359
|
+
// Don't access retrievalPhase at all - this is the most common case
|
|
1360
|
+
// The internal coordination promise should not cause unhandled rejection
|
|
1361
|
+
const errorResolver = Promise.withResolvers<void>();
|
|
1362
|
+
|
|
1363
|
+
// Execute and catch the error
|
|
1364
|
+
try {
|
|
1365
|
+
await uow.executeRetrieve();
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
expect(error).toBeInstanceOf(Error);
|
|
1368
|
+
expect((error as Error).message).toBe("Connection lost");
|
|
1369
|
+
errorResolver.resolve();
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
await errorResolver.promise;
|
|
1373
|
+
});
|
|
1374
|
+
|
|
1375
|
+
it("should handle error in executeMutations() when coordination promise is never accessed", async () => {
|
|
1376
|
+
const executor = {
|
|
1377
|
+
executeRetrievalPhase: async () => [],
|
|
1378
|
+
executeMutationPhase: async () => {
|
|
1379
|
+
throw new Error("Transaction aborted");
|
|
1380
|
+
},
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1384
|
+
uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
|
|
1385
|
+
|
|
1386
|
+
// Don't access mutationPhase at all - this is the most common case
|
|
1387
|
+
// The internal coordination promise should not cause unhandled rejection
|
|
1388
|
+
const errorResolver = Promise.withResolvers<void>();
|
|
1389
|
+
// Execute and catch the error
|
|
1390
|
+
try {
|
|
1391
|
+
await uow.executeMutations();
|
|
1392
|
+
} catch (error) {
|
|
1393
|
+
expect(error).toBeInstanceOf(Error);
|
|
1394
|
+
expect((error as Error).message).toBe("Transaction aborted");
|
|
1395
|
+
errorResolver.resolve();
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
await errorResolver.promise;
|
|
1399
|
+
});
|
|
1400
|
+
|
|
1401
|
+
it("should handle reset() after retrieval error", async () => {
|
|
1402
|
+
const executor = {
|
|
1403
|
+
executeRetrievalPhase: async () => {
|
|
1404
|
+
throw new Error("First attempt failed");
|
|
1405
|
+
},
|
|
1406
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1409
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1410
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
1411
|
+
|
|
1412
|
+
// First attempt fails
|
|
1413
|
+
const errorResolver = Promise.withResolvers<void>();
|
|
1414
|
+
try {
|
|
1415
|
+
await uow.executeRetrieve();
|
|
1416
|
+
} catch (error) {
|
|
1417
|
+
expect((error as Error).message).toBe("First attempt failed");
|
|
1418
|
+
errorResolver.resolve();
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// Reset the UOW
|
|
1422
|
+
uow.reset();
|
|
1423
|
+
|
|
1424
|
+
// The UOW should be in a clean state
|
|
1425
|
+
expect(uow.state).toBe("building-retrieval");
|
|
1426
|
+
expect(uow.getRetrievalOperations()).toHaveLength(0);
|
|
1427
|
+
|
|
1428
|
+
await errorResolver.promise;
|
|
1429
|
+
});
|
|
1430
|
+
|
|
1431
|
+
it("should handle reset() after mutation error", async () => {
|
|
1432
|
+
const executor = {
|
|
1433
|
+
executeRetrievalPhase: async () => [],
|
|
1434
|
+
executeMutationPhase: async () => {
|
|
1435
|
+
throw new Error("First mutation failed");
|
|
1436
|
+
},
|
|
1437
|
+
};
|
|
1438
|
+
|
|
1439
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1440
|
+
uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
|
|
1441
|
+
|
|
1442
|
+
// First attempt fails
|
|
1443
|
+
const errorResolver = Promise.withResolvers<void>();
|
|
1444
|
+
try {
|
|
1445
|
+
await uow.executeMutations();
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
expect((error as Error).message).toBe("First mutation failed");
|
|
1448
|
+
errorResolver.resolve();
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// Reset the UOW
|
|
1452
|
+
uow.reset();
|
|
1453
|
+
|
|
1454
|
+
// The UOW should be in a clean state
|
|
1455
|
+
expect(uow.state).toBe("building-retrieval");
|
|
1456
|
+
expect(uow.getMutationOperations()).toHaveLength(0);
|
|
1457
|
+
|
|
1458
|
+
await errorResolver.promise;
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
it("should support standalone check() operation", () => {
|
|
1462
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
1463
|
+
const typedUow = uow.forSchema(testSchema);
|
|
1464
|
+
|
|
1465
|
+
// Create a FragnoId for testing
|
|
1466
|
+
const userId = FragnoId.fromExternal("user-123", 5);
|
|
1467
|
+
|
|
1468
|
+
typedUow.check("users", userId);
|
|
1469
|
+
|
|
1470
|
+
const mutationOps = uow.getMutationOperations();
|
|
1471
|
+
expect(mutationOps).toHaveLength(1);
|
|
1472
|
+
|
|
1473
|
+
const checkOp = mutationOps[0];
|
|
1474
|
+
assert(checkOp);
|
|
1475
|
+
assert(checkOp.type === "check");
|
|
1476
|
+
expect(checkOp.table).toBe("users");
|
|
1477
|
+
expect(checkOp.id).toBe(userId);
|
|
1478
|
+
});
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1481
|
+
describe("findFirst convenience method", () => {
|
|
1482
|
+
const testSchema = schema((s) =>
|
|
1483
|
+
s
|
|
1484
|
+
.addTable("users", (t) =>
|
|
1485
|
+
t
|
|
1486
|
+
.addColumn("id", idColumn())
|
|
1487
|
+
.addColumn("name", "string")
|
|
1488
|
+
.addColumn("email", "string")
|
|
1489
|
+
.createIndex("idx_email", ["email"])
|
|
1490
|
+
.createIndex("idx_name", ["name"]),
|
|
1491
|
+
)
|
|
1492
|
+
.addTable("posts", (t) =>
|
|
1493
|
+
t
|
|
1494
|
+
.addColumn("id", idColumn())
|
|
1495
|
+
.addColumn("userId", "string")
|
|
1496
|
+
.addColumn("title", "string")
|
|
1497
|
+
.createIndex("idx_user", ["userId"]),
|
|
1498
|
+
),
|
|
1499
|
+
);
|
|
1500
|
+
|
|
1501
|
+
it("should return a single result instead of an array", async () => {
|
|
1502
|
+
const executor = {
|
|
1503
|
+
executeRetrievalPhase: async () => {
|
|
1504
|
+
return [[{ id: "mock-id", name: "Mock User", email: "mock@example.com" }]];
|
|
1505
|
+
},
|
|
1506
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1507
|
+
};
|
|
1508
|
+
|
|
1509
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1510
|
+
|
|
1511
|
+
// Use findFirst instead of find
|
|
1512
|
+
const typedUow = uow.forSchema(testSchema).findFirst("users", (b) => b.whereIndex("primary"));
|
|
1513
|
+
|
|
1514
|
+
// Execute retrieval
|
|
1515
|
+
await uow.executeRetrieve();
|
|
1516
|
+
const results = await typedUow.retrievalPhase;
|
|
1517
|
+
|
|
1518
|
+
// Result should be a single object, not an array
|
|
1519
|
+
const [user] = results;
|
|
1520
|
+
expect(user).toEqual({ id: "mock-id", name: "Mock User", email: "mock@example.com" });
|
|
1521
|
+
expect(Array.isArray(user)).toBe(false);
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
it("should return null when no results are found", async () => {
|
|
1525
|
+
// Create executor that returns empty results
|
|
1526
|
+
const emptyExecutor = {
|
|
1527
|
+
executeRetrievalPhase: async () => {
|
|
1528
|
+
return [[]]; // Empty array for no results
|
|
1529
|
+
},
|
|
1530
|
+
executeMutationPhase: async () => {
|
|
1531
|
+
return { success: true, createdInternalIds: [] };
|
|
1532
|
+
},
|
|
1533
|
+
};
|
|
1534
|
+
|
|
1535
|
+
const uow = createUnitOfWork(createMockCompiler(), emptyExecutor, createMockDecoder());
|
|
1536
|
+
|
|
1537
|
+
const typedUow = uow.forSchema(testSchema).findFirst("users", (b) => b.whereIndex("primary"));
|
|
1538
|
+
|
|
1539
|
+
await uow.executeRetrieve();
|
|
1540
|
+
const results = await typedUow.retrievalPhase;
|
|
1541
|
+
const [user] = results;
|
|
1542
|
+
|
|
1543
|
+
// Should be null when no results
|
|
1544
|
+
expect(user).toBeNull();
|
|
1545
|
+
});
|
|
1546
|
+
|
|
1547
|
+
it("should automatically set pageSize to 1", () => {
|
|
1548
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
1549
|
+
|
|
1550
|
+
uow.forSchema(testSchema).findFirst("users", (b) => b.whereIndex("primary"));
|
|
1551
|
+
|
|
1552
|
+
// Check that pageSize was set to 1 in the operation
|
|
1553
|
+
const ops = uow.getRetrievalOperations();
|
|
1554
|
+
expect(ops).toHaveLength(1);
|
|
1555
|
+
expect(ops[0]?.type).toBe("find");
|
|
1556
|
+
if (ops[0]?.type === "find") {
|
|
1557
|
+
expect(ops[0].options.pageSize).toBe(1);
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
|
|
1561
|
+
it("should work with custom select clause", async () => {
|
|
1562
|
+
const executor = {
|
|
1563
|
+
executeRetrievalPhase: async () => {
|
|
1564
|
+
return [[{ id: "mock-id", name: "Mock User" }]];
|
|
1565
|
+
},
|
|
1566
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1567
|
+
};
|
|
1568
|
+
|
|
1569
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1570
|
+
|
|
1571
|
+
const typedUow = uow
|
|
1572
|
+
.forSchema(testSchema)
|
|
1573
|
+
.findFirst("users", (b) => b.whereIndex("primary").select(["id", "name"] as const));
|
|
1574
|
+
|
|
1575
|
+
await uow.executeRetrieve();
|
|
1576
|
+
const results = await typedUow.retrievalPhase;
|
|
1577
|
+
const [user] = results;
|
|
1578
|
+
|
|
1579
|
+
expect(user).toBeDefined();
|
|
1580
|
+
expect(user).not.toBeNull();
|
|
1581
|
+
expect(user).toEqual({ id: "mock-id", name: "Mock User" });
|
|
1582
|
+
});
|
|
1583
|
+
|
|
1584
|
+
it("should work with where conditions", async () => {
|
|
1585
|
+
const executor = {
|
|
1586
|
+
executeRetrievalPhase: async () => {
|
|
1587
|
+
return [[{ id: "mock-id", name: "Mock User", email: "test@example.com" }]];
|
|
1588
|
+
},
|
|
1589
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1590
|
+
};
|
|
1591
|
+
|
|
1592
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1593
|
+
|
|
1594
|
+
const typedUow = uow
|
|
1595
|
+
.forSchema(testSchema)
|
|
1596
|
+
.findFirst("users", (b) =>
|
|
1597
|
+
b.whereIndex("idx_email", (eb) => eb("email", "=", "test@example.com")),
|
|
1598
|
+
);
|
|
1599
|
+
|
|
1600
|
+
await uow.executeRetrieve();
|
|
1601
|
+
const results = await typedUow.retrievalPhase;
|
|
1602
|
+
const [user] = results;
|
|
1603
|
+
|
|
1604
|
+
expect(user).toEqual({ id: "mock-id", name: "Mock User", email: "test@example.com" });
|
|
1605
|
+
});
|
|
1606
|
+
|
|
1607
|
+
it("should handle multiple findFirst operations in the same UOW", async () => {
|
|
1608
|
+
const executor = {
|
|
1609
|
+
executeRetrievalPhase: async () => {
|
|
1610
|
+
return [
|
|
1611
|
+
[{ id: "user-1", name: "User 1", email: "user1@example.com" }],
|
|
1612
|
+
[{ id: "post-1", userId: "user-1", title: "Post 1" }],
|
|
1613
|
+
];
|
|
1614
|
+
},
|
|
1615
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1616
|
+
};
|
|
1617
|
+
|
|
1618
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1619
|
+
|
|
1620
|
+
const typedUow = uow
|
|
1621
|
+
.forSchema(testSchema)
|
|
1622
|
+
.findFirst("users", (b) => b.whereIndex("primary"))
|
|
1623
|
+
.findFirst("posts", (b) => b.whereIndex("primary"));
|
|
1624
|
+
|
|
1625
|
+
await uow.executeRetrieve();
|
|
1626
|
+
const results = await typedUow.retrievalPhase;
|
|
1627
|
+
const [user, post] = results;
|
|
1628
|
+
|
|
1629
|
+
// Both should be single objects, not arrays
|
|
1630
|
+
expect(user).toEqual({ id: "user-1", name: "User 1", email: "user1@example.com" });
|
|
1631
|
+
expect(post).toEqual({ id: "post-1", userId: "user-1", title: "Post 1" });
|
|
1632
|
+
expect(Array.isArray(user)).toBe(false);
|
|
1633
|
+
expect(Array.isArray(post)).toBe(false);
|
|
1634
|
+
});
|
|
1635
|
+
|
|
1636
|
+
it("should handle mix of find and findFirst operations", async () => {
|
|
1637
|
+
const executor = {
|
|
1638
|
+
executeRetrievalPhase: async () => {
|
|
1639
|
+
return [
|
|
1640
|
+
[{ id: "user-1", name: "User 1", email: "user1@example.com" }],
|
|
1641
|
+
[
|
|
1642
|
+
{ id: "post-1", userId: "user-1", title: "Post 1" },
|
|
1643
|
+
{ id: "post-2", userId: "user-1", title: "Post 2" },
|
|
1644
|
+
],
|
|
1645
|
+
];
|
|
1646
|
+
},
|
|
1647
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1648
|
+
};
|
|
1649
|
+
|
|
1650
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1651
|
+
|
|
1652
|
+
const typedUow = uow
|
|
1653
|
+
.forSchema(testSchema)
|
|
1654
|
+
.findFirst("users", (b) => b.whereIndex("primary")) // Single result
|
|
1655
|
+
.find("posts", (b) => b.whereIndex("primary")); // Array of results
|
|
1656
|
+
|
|
1657
|
+
await uow.executeRetrieve();
|
|
1658
|
+
const results = await typedUow.retrievalPhase;
|
|
1659
|
+
const [user, posts] = results;
|
|
1660
|
+
|
|
1661
|
+
// User should be a single object
|
|
1662
|
+
expect(user).toEqual({ id: "user-1", name: "User 1", email: "user1@example.com" });
|
|
1663
|
+
expect(Array.isArray(user)).toBe(false);
|
|
1664
|
+
|
|
1665
|
+
// Posts should be an array
|
|
1666
|
+
expect(Array.isArray(posts)).toBe(true);
|
|
1667
|
+
expect(posts).toHaveLength(2);
|
|
1668
|
+
});
|
|
1669
|
+
|
|
1670
|
+
it("should work with orderByIndex", async () => {
|
|
1671
|
+
const executor = {
|
|
1672
|
+
executeRetrievalPhase: async () => {
|
|
1673
|
+
return [[{ id: "user-1", name: "Alice", email: "alice@example.com" }]];
|
|
1674
|
+
},
|
|
1675
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1676
|
+
};
|
|
1677
|
+
|
|
1678
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1679
|
+
|
|
1680
|
+
const typedUow = uow
|
|
1681
|
+
.forSchema(testSchema)
|
|
1682
|
+
.findFirst("users", (b) => b.whereIndex("idx_name").orderByIndex("idx_name", "asc"));
|
|
1683
|
+
|
|
1684
|
+
await uow.executeRetrieve();
|
|
1685
|
+
const results = await typedUow.retrievalPhase;
|
|
1686
|
+
const [user] = results;
|
|
1687
|
+
|
|
1688
|
+
expect(user).toEqual({ id: "user-1", name: "Alice", email: "alice@example.com" });
|
|
1689
|
+
expect(Array.isArray(user)).toBe(false);
|
|
1690
|
+
});
|
|
1691
|
+
|
|
1692
|
+
it("should work without explicit builder function", async () => {
|
|
1693
|
+
const executor = {
|
|
1694
|
+
executeRetrievalPhase: async () => {
|
|
1695
|
+
return [[{ id: "user-1", name: "User 1", email: "user1@example.com" }]];
|
|
1696
|
+
},
|
|
1697
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1698
|
+
};
|
|
1699
|
+
|
|
1700
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1701
|
+
|
|
1702
|
+
// findFirst without builder function should use primary index by default
|
|
1703
|
+
const typedUow = uow.forSchema(testSchema).findFirst("users");
|
|
1704
|
+
|
|
1705
|
+
await uow.executeRetrieve();
|
|
1706
|
+
const results = await typedUow.retrievalPhase;
|
|
1707
|
+
const [user] = results;
|
|
1708
|
+
|
|
1709
|
+
expect(user).toEqual({ id: "user-1", name: "User 1", email: "user1@example.com" });
|
|
1710
|
+
expect(Array.isArray(user)).toBe(false);
|
|
1711
|
+
|
|
1712
|
+
// Verify the operation used the primary index
|
|
1713
|
+
const ops = uow.getRetrievalOperations();
|
|
1714
|
+
expect(ops[0]?.indexName).toBe("_primary");
|
|
1715
|
+
});
|
|
1716
|
+
});
|