@fragno-dev/db 0.2.2 → 0.3.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 +202 -140
- package/CHANGELOG.md +35 -0
- package/README.md +30 -9
- package/dist/adapters/adapters.d.ts +23 -21
- package/dist/adapters/adapters.d.ts.map +1 -1
- package/dist/adapters/adapters.js.map +1 -1
- package/dist/adapters/generic-sql/driver-config.d.ts +16 -1
- package/dist/adapters/generic-sql/driver-config.d.ts.map +1 -1
- package/dist/adapters/generic-sql/driver-config.js +23 -1
- package/dist/adapters/generic-sql/driver-config.js.map +1 -1
- package/dist/adapters/generic-sql/generic-sql-adapter.d.ts +27 -9
- package/dist/adapters/generic-sql/generic-sql-adapter.d.ts.map +1 -1
- package/dist/adapters/generic-sql/generic-sql-adapter.js +55 -16
- package/dist/adapters/generic-sql/generic-sql-adapter.js.map +1 -1
- package/dist/adapters/generic-sql/generic-sql-uow-executor.js +129 -3
- package/dist/adapters/generic-sql/generic-sql-uow-executor.js.map +1 -1
- package/dist/adapters/generic-sql/migration/dialect/mysql.js +24 -5
- package/dist/adapters/generic-sql/migration/dialect/mysql.js.map +1 -1
- package/dist/adapters/generic-sql/migration/dialect/postgres.js +6 -5
- package/dist/adapters/generic-sql/migration/dialect/postgres.js.map +1 -1
- package/dist/adapters/generic-sql/migration/dialect/sqlite.js +21 -10
- package/dist/adapters/generic-sql/migration/dialect/sqlite.js.map +1 -1
- package/dist/adapters/generic-sql/migration/prepared-migrations.d.ts.map +1 -1
- package/dist/adapters/generic-sql/migration/prepared-migrations.js +8 -8
- package/dist/adapters/generic-sql/migration/prepared-migrations.js.map +1 -1
- package/dist/adapters/generic-sql/migration/sql-generator.js +74 -51
- package/dist/adapters/generic-sql/migration/sql-generator.js.map +1 -1
- package/dist/adapters/generic-sql/query/create-sql-query-compiler.js +6 -5
- package/dist/adapters/generic-sql/query/create-sql-query-compiler.js.map +1 -1
- package/dist/adapters/generic-sql/query/cursor-utils.js +42 -4
- package/dist/adapters/generic-sql/query/cursor-utils.js.map +1 -1
- package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js +25 -17
- package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js.map +1 -1
- package/dist/adapters/generic-sql/query/select-builder.js +5 -3
- package/dist/adapters/generic-sql/query/select-builder.js.map +1 -1
- package/dist/adapters/generic-sql/query/sql-query-compiler.js +15 -12
- package/dist/adapters/generic-sql/query/sql-query-compiler.js.map +1 -1
- package/dist/adapters/generic-sql/query/where-builder.js +39 -29
- package/dist/adapters/generic-sql/query/where-builder.js.map +1 -1
- package/dist/adapters/generic-sql/sqlite-storage.d.ts +13 -0
- package/dist/adapters/generic-sql/sqlite-storage.d.ts.map +1 -0
- package/dist/adapters/generic-sql/sqlite-storage.js +15 -0
- package/dist/adapters/generic-sql/sqlite-storage.js.map +1 -0
- package/dist/adapters/generic-sql/uow-decoder.js +7 -3
- package/dist/adapters/generic-sql/uow-decoder.js.map +1 -1
- package/dist/adapters/generic-sql/uow-encoder.js +28 -8
- package/dist/adapters/generic-sql/uow-encoder.js.map +1 -1
- package/dist/adapters/in-memory/condition-evaluator.js +131 -0
- package/dist/adapters/in-memory/condition-evaluator.js.map +1 -0
- package/dist/adapters/in-memory/errors.d.ts +13 -0
- package/dist/adapters/in-memory/errors.d.ts.map +1 -0
- package/dist/adapters/in-memory/errors.js +23 -0
- package/dist/adapters/in-memory/errors.js.map +1 -0
- package/dist/adapters/in-memory/in-memory-adapter.d.ts +27 -0
- package/dist/adapters/in-memory/in-memory-adapter.d.ts.map +1 -0
- package/dist/adapters/in-memory/in-memory-adapter.js +176 -0
- package/dist/adapters/in-memory/in-memory-adapter.js.map +1 -0
- package/dist/adapters/in-memory/in-memory-uow.js +648 -0
- package/dist/adapters/in-memory/in-memory-uow.js.map +1 -0
- package/dist/adapters/in-memory/index.d.ts +4 -0
- package/dist/adapters/in-memory/index.js +4 -0
- package/dist/adapters/in-memory/options.d.ts +28 -0
- package/dist/adapters/in-memory/options.d.ts.map +1 -0
- package/dist/adapters/in-memory/options.js +61 -0
- package/dist/adapters/in-memory/options.js.map +1 -0
- package/dist/adapters/in-memory/reference-resolution.js +26 -0
- package/dist/adapters/in-memory/reference-resolution.js.map +1 -0
- package/dist/adapters/in-memory/sorted-array-index.js +129 -0
- package/dist/adapters/in-memory/sorted-array-index.js.map +1 -0
- package/dist/adapters/in-memory/store.js +71 -0
- package/dist/adapters/in-memory/store.js.map +1 -0
- package/dist/adapters/in-memory/value-comparison.js +28 -0
- package/dist/adapters/in-memory/value-comparison.js.map +1 -0
- package/dist/adapters/shared/from-unit-of-work-compiler.js.map +1 -1
- package/dist/adapters/shared/uow-operation-compiler.js +11 -11
- package/dist/adapters/shared/uow-operation-compiler.js.map +1 -1
- package/dist/adapters/sql/index.d.ts +5 -0
- package/dist/adapters/sql/index.js +4 -0
- package/dist/db-fragment-definition-builder.d.ts +18 -7
- package/dist/db-fragment-definition-builder.d.ts.map +1 -1
- package/dist/db-fragment-definition-builder.js +116 -54
- package/dist/db-fragment-definition-builder.js.map +1 -1
- package/dist/dispatchers/cloudflare-do/index.d.ts +26 -0
- package/dist/dispatchers/cloudflare-do/index.d.ts.map +1 -0
- package/dist/dispatchers/cloudflare-do/index.js +63 -0
- package/dist/dispatchers/cloudflare-do/index.js.map +1 -0
- package/dist/dispatchers/node/index.d.ts +17 -0
- package/dist/dispatchers/node/index.d.ts.map +1 -0
- package/dist/dispatchers/node/index.js +59 -0
- package/dist/dispatchers/node/index.js.map +1 -0
- package/dist/fragments/internal-fragment.d.ts +79 -2
- package/dist/fragments/internal-fragment.d.ts.map +1 -1
- package/dist/fragments/internal-fragment.js +150 -32
- package/dist/fragments/internal-fragment.js.map +1 -1
- package/dist/fragments/internal-fragment.routes.js +29 -0
- package/dist/fragments/internal-fragment.routes.js.map +1 -0
- package/dist/fragments/internal-fragment.schema.d.ts +9 -0
- package/dist/fragments/internal-fragment.schema.d.ts.map +1 -0
- package/dist/fragments/internal-fragment.schema.js +22 -0
- package/dist/fragments/internal-fragment.schema.js.map +1 -0
- package/dist/hooks/durable-hooks-processor.d.ts +14 -0
- package/dist/hooks/durable-hooks-processor.d.ts.map +1 -0
- package/dist/hooks/durable-hooks-processor.js +32 -0
- package/dist/hooks/durable-hooks-processor.js.map +1 -0
- package/dist/hooks/hooks.d.ts +42 -1
- package/dist/hooks/hooks.d.ts.map +1 -1
- package/dist/hooks/hooks.js +72 -6
- package/dist/hooks/hooks.js.map +1 -1
- package/dist/migration-engine/auto-from-schema.js +14 -11
- package/dist/migration-engine/auto-from-schema.js.map +1 -1
- package/dist/migration-engine/generation-engine.d.ts +16 -10
- package/dist/migration-engine/generation-engine.d.ts.map +1 -1
- package/dist/migration-engine/generation-engine.js +72 -33
- package/dist/migration-engine/generation-engine.js.map +1 -1
- package/dist/migration-engine/shared.js.map +1 -1
- package/dist/mod.d.ts +15 -8
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +14 -8
- package/dist/mod.js.map +1 -1
- package/dist/naming/sql-naming.d.ts +19 -0
- package/dist/naming/sql-naming.d.ts.map +1 -0
- package/dist/naming/sql-naming.js +116 -0
- package/dist/naming/sql-naming.js.map +1 -0
- package/dist/node_modules/.pnpm/{rou3@0.7.10 → rou3@0.7.12}/node_modules/rou3/dist/index.js +8 -5
- package/dist/node_modules/.pnpm/rou3@0.7.12/node_modules/rou3/dist/index.js.map +1 -0
- package/dist/outbox/outbox-builder.js +156 -0
- package/dist/outbox/outbox-builder.js.map +1 -0
- package/dist/outbox/outbox.d.ts +52 -0
- package/dist/outbox/outbox.d.ts.map +1 -0
- package/dist/outbox/outbox.js +37 -0
- package/dist/outbox/outbox.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js +3 -2
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +1 -1
- package/dist/packages/fragno/dist/api/fragment-instantiator.js +164 -20
- package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +1 -1
- package/dist/packages/fragno/dist/api/request-input-context.js +67 -0
- package/dist/packages/fragno/dist/api/request-input-context.js.map +1 -1
- package/dist/packages/fragno/dist/api/route.js +14 -1
- package/dist/packages/fragno/dist/api/route.js.map +1 -1
- package/dist/packages/fragno/dist/internal/trace-context.js +12 -0
- package/dist/packages/fragno/dist/internal/trace-context.js.map +1 -0
- package/dist/query/column-defaults.js +20 -4
- package/dist/query/column-defaults.js.map +1 -1
- package/dist/query/cursor.d.ts +3 -1
- package/dist/query/cursor.d.ts.map +1 -1
- package/dist/query/cursor.js +45 -14
- package/dist/query/cursor.js.map +1 -1
- package/dist/query/db-now.d.ts +8 -0
- package/dist/query/db-now.d.ts.map +1 -0
- package/dist/query/db-now.js +7 -0
- package/dist/query/db-now.js.map +1 -0
- package/dist/query/serialize/create-sql-serializer.js +3 -2
- package/dist/query/serialize/create-sql-serializer.js.map +1 -1
- package/dist/query/serialize/dialect/mysql-serializer.js +12 -6
- package/dist/query/serialize/dialect/mysql-serializer.js.map +1 -1
- package/dist/query/serialize/dialect/postgres-serializer.js +25 -7
- package/dist/query/serialize/dialect/postgres-serializer.js.map +1 -1
- package/dist/query/serialize/dialect/sqlite-serializer.js +55 -11
- package/dist/query/serialize/dialect/sqlite-serializer.js.map +1 -1
- package/dist/query/serialize/sql-serializer.js +2 -2
- package/dist/query/serialize/sql-serializer.js.map +1 -1
- package/dist/query/simple-query-interface.d.ts +6 -1
- package/dist/query/simple-query-interface.d.ts.map +1 -1
- package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work/execute-unit-of-work.js +11 -6
- package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -1
- package/dist/query/unit-of-work/unit-of-work.d.ts +50 -14
- package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work/unit-of-work.js +86 -5
- package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
- package/dist/query/value-decoding.js +9 -6
- package/dist/query/value-decoding.js.map +1 -1
- package/dist/query/value-encoding.js +29 -9
- package/dist/query/value-encoding.js.map +1 -1
- package/dist/schema/create.d.ts +38 -14
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +81 -42
- package/dist/schema/create.js.map +1 -1
- package/dist/schema/generate-id.js +2 -2
- package/dist/schema/generate-id.js.map +1 -1
- package/dist/schema/type-conversion/create-sql-type-mapper.js +3 -2
- package/dist/schema/type-conversion/create-sql-type-mapper.js.map +1 -1
- package/dist/schema/type-conversion/dialect/sqlite.js +9 -0
- package/dist/schema/type-conversion/dialect/sqlite.js.map +1 -1
- package/dist/schema/validator.d.ts +10 -0
- package/dist/schema/validator.d.ts.map +1 -0
- package/dist/schema/validator.js +123 -0
- package/dist/schema/validator.js.map +1 -0
- package/dist/schema-output/drizzle.d.ts +30 -0
- package/dist/schema-output/drizzle.d.ts.map +1 -0
- package/dist/{adapters/drizzle/generate.js → schema-output/drizzle.js} +82 -56
- package/dist/schema-output/drizzle.js.map +1 -0
- package/dist/schema-output/prisma.d.ts +17 -0
- package/dist/schema-output/prisma.d.ts.map +1 -0
- package/dist/schema-output/prisma.js +296 -0
- package/dist/schema-output/prisma.js.map +1 -0
- package/dist/util/default-database-adapter.js +61 -0
- package/dist/util/default-database-adapter.js.map +1 -0
- package/dist/with-database.d.ts +1 -1
- package/dist/with-database.d.ts.map +1 -1
- package/dist/with-database.js +12 -3
- package/dist/with-database.js.map +1 -1
- package/package.json +43 -28
- package/src/adapters/adapters.ts +30 -24
- package/src/adapters/drizzle/migrate-drizzle.test.ts +54 -33
- package/src/adapters/drizzle/migration-parity-drizzle-kit.test.ts +599 -0
- package/src/adapters/drizzle/test-utils.ts +12 -8
- package/src/adapters/generic-sql/driver-config.ts +38 -0
- package/src/adapters/generic-sql/generic-sql-adapter.test.ts +5 -5
- package/src/adapters/generic-sql/generic-sql-adapter.ts +110 -24
- package/src/adapters/generic-sql/generic-sql-uow-executor.test.ts +54 -0
- package/src/adapters/generic-sql/generic-sql-uow-executor.ts +231 -3
- package/src/adapters/generic-sql/migration/adapter-migration-parity.test.ts +118 -0
- package/src/adapters/generic-sql/migration/dialect/mysql.test.ts +26 -8
- package/src/adapters/generic-sql/migration/dialect/mysql.ts +46 -8
- package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +25 -7
- package/src/adapters/generic-sql/migration/dialect/postgres.ts +8 -4
- package/src/adapters/generic-sql/migration/dialect/sqlite.test.ts +47 -8
- package/src/adapters/generic-sql/migration/dialect/sqlite.ts +27 -12
- package/src/adapters/generic-sql/migration/prepared-migrations.test.ts +128 -39
- package/src/adapters/generic-sql/migration/prepared-migrations.ts +15 -8
- package/src/adapters/generic-sql/migration/sql-generator.ts +142 -65
- package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +9 -6
- package/src/adapters/generic-sql/query/cursor-utils.test.ts +271 -0
- package/src/adapters/generic-sql/query/cursor-utils.ts +41 -6
- package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.test.ts +27 -27
- package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +38 -24
- package/src/adapters/generic-sql/query/select-builder.test.ts +15 -11
- package/src/adapters/generic-sql/query/select-builder.ts +6 -2
- package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +52 -2
- package/src/adapters/generic-sql/query/sql-query-compiler.ts +50 -15
- package/src/adapters/generic-sql/query/where-builder.test.ts +91 -17
- package/src/adapters/generic-sql/query/where-builder.ts +90 -38
- package/src/adapters/{kysely/kysely-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-migrations.test.ts} +6 -6
- package/src/adapters/generic-sql/sql-adapter-pglite-pagination.test.ts +806 -0
- package/src/adapters/{drizzle/drizzle-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-queries.test.ts} +11 -11
- package/src/adapters/generic-sql/{test/generic-drizzle-adapter-sqlite3.test.ts → sql-adapter-sqlite3-driver.test.ts} +10 -10
- package/src/adapters/{drizzle/drizzle-adapter-sqlite3.test.ts → generic-sql/sql-adapter-sqlite3-uow.test.ts} +7 -7
- package/src/adapters/{kysely/kysely-adapter-sqlocal.test.ts → generic-sql/sql-adapter-sqlocal.test.ts} +6 -6
- package/src/adapters/generic-sql/sqlite-storage.ts +20 -0
- package/src/adapters/generic-sql/uow-decoder.test.ts +1 -1
- package/src/adapters/generic-sql/uow-decoder.ts +21 -3
- package/src/adapters/generic-sql/uow-encoder.test.ts +33 -2
- package/src/adapters/generic-sql/uow-encoder.ts +50 -11
- package/src/adapters/in-memory/condition-evaluator.test.ts +193 -0
- package/src/adapters/in-memory/condition-evaluator.ts +275 -0
- package/src/adapters/in-memory/errors.ts +20 -0
- package/src/adapters/in-memory/in-memory-adapter.ts +277 -0
- package/src/adapters/in-memory/in-memory-uow.mutations.test.ts +296 -0
- package/src/adapters/in-memory/in-memory-uow.retrieval.test.ts +100 -0
- package/src/adapters/in-memory/in-memory-uow.ts +1348 -0
- package/src/adapters/in-memory/index.ts +3 -0
- package/src/adapters/in-memory/options.test.ts +41 -0
- package/src/adapters/in-memory/options.ts +87 -0
- package/src/adapters/in-memory/reference-resolution.test.ts +50 -0
- package/src/adapters/in-memory/reference-resolution.ts +67 -0
- package/src/adapters/in-memory/sorted-array-index.test.ts +123 -0
- package/src/adapters/in-memory/sorted-array-index.ts +228 -0
- package/src/adapters/in-memory/store.test.ts +68 -0
- package/src/adapters/in-memory/store.ts +145 -0
- package/src/adapters/in-memory/value-comparison.ts +53 -0
- package/src/adapters/in-memory/value-normalization.test.ts +57 -0
- package/src/adapters/prisma/prisma-adapter-sqlite3.test.ts +1163 -0
- package/src/adapters/shared/from-unit-of-work-compiler.ts +3 -1
- package/src/adapters/shared/uow-operation-compiler.ts +26 -16
- package/src/adapters/sql/index.ts +12 -0
- package/src/db-fragment-definition-builder.test.ts +30 -12
- package/src/db-fragment-definition-builder.ts +142 -73
- package/src/db-fragment-instantiator.test.ts +105 -13
- package/src/db-fragment-integration.test.ts +9 -7
- package/src/dispatchers/cloudflare-do/index.test.ts +73 -0
- package/src/dispatchers/cloudflare-do/index.ts +104 -0
- package/src/dispatchers/node/index.test.ts +91 -0
- package/src/dispatchers/node/index.ts +87 -0
- package/src/fragments/internal-fragment.routes.ts +42 -0
- package/src/fragments/internal-fragment.schema.ts +51 -0
- package/src/fragments/internal-fragment.test.ts +458 -8
- package/src/fragments/internal-fragment.ts +322 -63
- package/src/hooks/durable-hooks-processor.test.ts +117 -0
- package/src/hooks/durable-hooks-processor.ts +67 -0
- package/src/hooks/hooks.test.ts +165 -5
- package/src/hooks/hooks.ts +197 -9
- package/src/migration-engine/auto-from-schema.test.ts +14 -14
- package/src/migration-engine/auto-from-schema.ts +5 -2
- package/src/migration-engine/create.test.ts +2 -2
- package/src/migration-engine/generation-engine.test.ts +229 -104
- package/src/migration-engine/generation-engine.ts +94 -64
- package/src/migration-engine/shared.ts +1 -0
- package/src/mod.ts +64 -26
- package/src/naming/sql-naming.ts +180 -0
- package/src/outbox/outbox-builder.ts +241 -0
- package/src/outbox/outbox.test.ts +253 -0
- package/src/outbox/outbox.ts +137 -0
- package/src/query/column-defaults.ts +41 -3
- package/src/query/condition-builder.test.ts +3 -3
- package/src/query/cursor.test.ts +116 -18
- package/src/query/cursor.ts +75 -26
- package/src/query/db-now.ts +6 -0
- package/src/query/query-type.test.ts +2 -2
- package/src/query/serialize/create-sql-serializer.ts +7 -2
- package/src/query/serialize/dialect/mysql-serializer.ts +12 -4
- package/src/query/serialize/dialect/postgres-serializer.ts +34 -4
- package/src/query/serialize/dialect/sqlite-serializer.test.ts +51 -1
- package/src/query/serialize/dialect/sqlite-serializer.ts +92 -9
- package/src/query/serialize/sql-serializer.ts +4 -4
- package/src/query/simple-query-interface.ts +5 -0
- package/src/query/unit-of-work/execute-unit-of-work.test.ts +25 -1
- package/src/query/unit-of-work/execute-unit-of-work.ts +25 -8
- package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +12 -12
- package/src/query/unit-of-work/unit-of-work-types.test.ts +1 -1
- package/src/query/unit-of-work/unit-of-work.test.ts +168 -37
- package/src/query/unit-of-work/unit-of-work.ts +203 -18
- package/src/query/value-decoding.test.ts +13 -2
- package/src/query/value-decoding.ts +17 -4
- package/src/query/value-encoding.test.ts +85 -2
- package/src/query/value-encoding.ts +56 -6
- package/src/schema/create.test.ts +129 -42
- package/src/schema/create.ts +185 -47
- package/src/schema/generate-id.test.ts +2 -2
- package/src/schema/generate-id.ts +2 -2
- package/src/schema/serialize.test.ts +14 -2
- package/src/schema/type-conversion/create-sql-type-mapper.ts +7 -2
- package/src/schema/type-conversion/dialect/sqlite.ts +18 -0
- package/src/schema/type-conversion/type-mapping.test.ts +25 -1
- package/src/schema/validator.test.ts +197 -0
- package/src/schema/validator.ts +231 -0
- package/src/{adapters/drizzle/generate.test.ts → schema-output/drizzle.test.ts} +179 -129
- package/src/{adapters/drizzle/generate.ts → schema-output/drizzle.ts} +143 -93
- package/src/schema-output/prisma.test.ts +536 -0
- package/src/schema-output/prisma.ts +573 -0
- package/src/util/default-database-adapter.ts +106 -0
- package/src/with-database.ts +22 -3
- package/tsdown.config.ts +6 -4
- package/dist/adapters/drizzle/drizzle-adapter.d.ts +0 -20
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +0 -1
- package/dist/adapters/drizzle/drizzle-adapter.js +0 -27
- package/dist/adapters/drizzle/drizzle-adapter.js.map +0 -1
- package/dist/adapters/drizzle/generate.d.ts +0 -30
- package/dist/adapters/drizzle/generate.d.ts.map +0 -1
- package/dist/adapters/drizzle/generate.js.map +0 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts +0 -19
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +0 -1
- package/dist/adapters/kysely/kysely-adapter.js +0 -17
- package/dist/adapters/kysely/kysely-adapter.js.map +0 -1
- package/dist/adapters/shared/table-name-mapper.d.ts +0 -12
- package/dist/adapters/shared/table-name-mapper.d.ts.map +0 -1
- package/dist/adapters/shared/table-name-mapper.js +0 -43
- package/dist/adapters/shared/table-name-mapper.js.map +0 -1
- package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js.map +0 -1
- package/dist/schema-generator/schema-generator.d.ts +0 -15
- package/dist/schema-generator/schema-generator.d.ts.map +0 -1
- package/src/adapters/drizzle/drizzle-adapter.ts +0 -39
- package/src/adapters/kysely/kysely-adapter.ts +0 -27
- package/src/adapters/shared/table-name-mapper.ts +0 -50
- package/src/schema-generator/schema-generator.ts +0 -12
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
schemaNamingStrategy,
|
|
3
|
+
suffixNamingStrategy,
|
|
4
|
+
type SqlNamingStrategy,
|
|
5
|
+
} from "../../naming/sql-naming";
|
|
6
|
+
|
|
1
7
|
export const supportedDatabases = ["sqlite", "postgresql", "mysql"] as const;
|
|
2
8
|
export type SupportedDatabase = (typeof supportedDatabases)[number];
|
|
3
9
|
|
|
@@ -18,6 +24,7 @@ export abstract class DriverConfig<T extends SupportedDriverType = SupportedDriv
|
|
|
18
24
|
|
|
19
25
|
abstract readonly supportsReturning: boolean;
|
|
20
26
|
abstract readonly supportsJson: boolean;
|
|
27
|
+
abstract readonly outboxVersionstampStrategy: OutboxVersionstampStrategy;
|
|
21
28
|
|
|
22
29
|
/**
|
|
23
30
|
* Column name for internal ID in RETURNING results.
|
|
@@ -25,10 +32,17 @@ export abstract class DriverConfig<T extends SupportedDriverType = SupportedDriv
|
|
|
25
32
|
*/
|
|
26
33
|
abstract readonly internalIdColumn: string | undefined;
|
|
27
34
|
|
|
35
|
+
/**
|
|
36
|
+
* SQLite storage selection is handled by adapters, not driver config.
|
|
37
|
+
*/
|
|
28
38
|
get supportsRowsAffected(): boolean {
|
|
29
39
|
return !!this.extractAffectedRows;
|
|
30
40
|
}
|
|
31
41
|
|
|
42
|
+
get defaultNamingStrategy(): SqlNamingStrategy {
|
|
43
|
+
return defaultNamingStrategyForDatabase(this.databaseType);
|
|
44
|
+
}
|
|
45
|
+
|
|
32
46
|
/**
|
|
33
47
|
* Extract the number of affected rows from a query result.
|
|
34
48
|
* Only implemented for drivers that support affected rows reporting.
|
|
@@ -40,12 +54,31 @@ export abstract class DriverConfig<T extends SupportedDriverType = SupportedDriv
|
|
|
40
54
|
extractAffectedRows?(result: Record<string, unknown>): bigint;
|
|
41
55
|
}
|
|
42
56
|
|
|
57
|
+
export const defaultNamingStrategyForDatabase = (
|
|
58
|
+
databaseType: SupportedDatabase,
|
|
59
|
+
): SqlNamingStrategy => {
|
|
60
|
+
switch (databaseType) {
|
|
61
|
+
case "postgresql":
|
|
62
|
+
return schemaNamingStrategy;
|
|
63
|
+
case "sqlite":
|
|
64
|
+
case "mysql":
|
|
65
|
+
default:
|
|
66
|
+
return suffixNamingStrategy;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type OutboxVersionstampStrategy =
|
|
71
|
+
| "update-returning"
|
|
72
|
+
| "insert-on-conflict-returning"
|
|
73
|
+
| "insert-on-duplicate-last-insert-id";
|
|
74
|
+
|
|
43
75
|
export class SQLocalDriverConfig extends DriverConfig<"sqlocal"> {
|
|
44
76
|
override readonly driverType = "sqlocal";
|
|
45
77
|
override readonly databaseType = "sqlite";
|
|
46
78
|
override readonly supportsReturning = true;
|
|
47
79
|
override readonly supportsJson = false;
|
|
48
80
|
override readonly internalIdColumn = "_internalId";
|
|
81
|
+
override readonly outboxVersionstampStrategy = "insert-on-conflict-returning";
|
|
49
82
|
}
|
|
50
83
|
|
|
51
84
|
export class CloudflareDurableObjectsDriverConfig extends DriverConfig<"cloudflare_durable_objects"> {
|
|
@@ -54,6 +87,7 @@ export class CloudflareDurableObjectsDriverConfig extends DriverConfig<"cloudfla
|
|
|
54
87
|
override readonly supportsReturning = true;
|
|
55
88
|
override readonly supportsJson = false;
|
|
56
89
|
override readonly internalIdColumn = "_internalId";
|
|
90
|
+
override readonly outboxVersionstampStrategy = "insert-on-conflict-returning";
|
|
57
91
|
}
|
|
58
92
|
|
|
59
93
|
export class BetterSQLite3DriverConfig extends DriverConfig<"better-sqlite3"> {
|
|
@@ -62,6 +96,7 @@ export class BetterSQLite3DriverConfig extends DriverConfig<"better-sqlite3"> {
|
|
|
62
96
|
override readonly supportsReturning = true;
|
|
63
97
|
override readonly supportsJson = false;
|
|
64
98
|
override readonly internalIdColumn = "_internalId";
|
|
99
|
+
override readonly outboxVersionstampStrategy = "insert-on-conflict-returning";
|
|
65
100
|
|
|
66
101
|
override extractAffectedRows(result: Record<string, unknown>): bigint {
|
|
67
102
|
if ("numAffectedRows" in result) {
|
|
@@ -86,6 +121,7 @@ export class NodePostgresDriverConfig extends DriverConfig<"pg"> {
|
|
|
86
121
|
override readonly supportsReturning = true;
|
|
87
122
|
override readonly supportsJson = true;
|
|
88
123
|
override readonly internalIdColumn = "_internalId";
|
|
124
|
+
override readonly outboxVersionstampStrategy = "insert-on-conflict-returning";
|
|
89
125
|
|
|
90
126
|
override extractAffectedRows(result: Record<string, unknown>): bigint {
|
|
91
127
|
if ("numAffectedRows" in result) {
|
|
@@ -118,6 +154,7 @@ export class PGLiteDriverConfig extends DriverConfig<"pglite"> {
|
|
|
118
154
|
override readonly supportsReturning = true;
|
|
119
155
|
override readonly supportsJson = true;
|
|
120
156
|
override readonly internalIdColumn = "_internalId";
|
|
157
|
+
override readonly outboxVersionstampStrategy = "insert-on-conflict-returning";
|
|
121
158
|
|
|
122
159
|
override extractAffectedRows(result: Record<string, unknown>): bigint {
|
|
123
160
|
if ("affectedRows" in result) {
|
|
@@ -141,4 +178,5 @@ export class MySQL2DriverConfig extends DriverConfig<"mysql2"> {
|
|
|
141
178
|
override readonly supportsReturning = false;
|
|
142
179
|
override readonly supportsJson = true;
|
|
143
180
|
override readonly internalIdColumn = undefined;
|
|
181
|
+
override readonly outboxVersionstampStrategy = "insert-on-duplicate-last-insert-id";
|
|
144
182
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { SQLocalKysely } from "sqlocal/kysely";
|
|
2
2
|
import { assert, describe, expect, it } from "vitest";
|
|
3
3
|
import { SQLocalDriverConfig } from "./driver-config";
|
|
4
|
-
import {
|
|
4
|
+
import { SqlAdapter } from "./generic-sql-adapter";
|
|
5
5
|
import { column, idColumn, schema } from "../../schema/create";
|
|
6
6
|
import { internalSchema } from "../../fragments/internal-fragment";
|
|
7
7
|
|
|
8
|
-
describe("
|
|
9
|
-
const testSchema = schema((s) => {
|
|
8
|
+
describe("SqlAdapter", () => {
|
|
9
|
+
const testSchema = schema("test", (s) => {
|
|
10
10
|
return s.addTable("products", (t) => {
|
|
11
11
|
return t
|
|
12
12
|
.addColumn("id", idColumn())
|
|
@@ -16,11 +16,11 @@ describe("GenericSQLAdapter", () => {
|
|
|
16
16
|
});
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
it("Should be able to query using
|
|
19
|
+
it("Should be able to query using SqlAdapter", async () => {
|
|
20
20
|
const { dialect } = new SQLocalKysely(":memory:");
|
|
21
21
|
const driverConfig = new SQLocalDriverConfig();
|
|
22
22
|
|
|
23
|
-
const adapter = new
|
|
23
|
+
const adapter = new SqlAdapter({ dialect, driverConfig });
|
|
24
24
|
|
|
25
25
|
// Create settings table first (needed for version tracking)
|
|
26
26
|
const settingsMigrations = adapter.prepareMigrations(internalSchema, "");
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
fragnoDatabaseAdapterVersionFakeSymbol,
|
|
5
5
|
type DatabaseAdapter,
|
|
6
6
|
type DatabaseContextStorage,
|
|
7
|
-
type
|
|
7
|
+
type DatabaseAdapterMetadata,
|
|
8
|
+
type SQLiteProfile,
|
|
8
9
|
} from "../adapters";
|
|
9
10
|
import type { CompiledQuery, Dialect, QueryResult } from "../../sql-driver/sql-driver";
|
|
10
11
|
import { SqlDriverAdapter } from "../../sql-driver/sql-driver-adapter";
|
|
11
12
|
import { sql } from "../../sql-driver/sql";
|
|
12
|
-
import type { AnySchema } from "../../schema/create";
|
|
13
|
-
import { createTableNameMapper } from "../shared/table-name-mapper";
|
|
13
|
+
import type { AnyColumn, AnySchema } from "../../schema/create";
|
|
14
14
|
import type { SimpleQueryInterface } from "../../query/simple-query-interface";
|
|
15
15
|
import { createExecutor } from "./generic-sql-uow-executor";
|
|
16
16
|
import { UnitOfWorkDecoder } from "./uow-decoder";
|
|
@@ -22,34 +22,87 @@ import {
|
|
|
22
22
|
fromUnitOfWorkCompiler,
|
|
23
23
|
type UnitOfWorkFactory,
|
|
24
24
|
} from "../shared/from-unit-of-work-compiler";
|
|
25
|
+
import type { UOWInstrumentation } from "../../query/unit-of-work/unit-of-work";
|
|
26
|
+
import type { SQLiteStorageMode } from "./sqlite-storage";
|
|
27
|
+
import { sqliteStorageDefault, sqliteStoragePrisma } from "./sqlite-storage";
|
|
28
|
+
import type { OutboxConfig } from "../../outbox/outbox";
|
|
29
|
+
import { createSQLSerializer } from "../../query/serialize/create-sql-serializer";
|
|
30
|
+
import {
|
|
31
|
+
createNamingResolver,
|
|
32
|
+
type NamingResolver,
|
|
33
|
+
type SqlNamingStrategy,
|
|
34
|
+
} from "../../naming/sql-naming";
|
|
25
35
|
|
|
26
36
|
export interface UnitOfWorkConfig {
|
|
27
37
|
onQuery?: (query: CompiledQuery) => void;
|
|
28
38
|
dryRun?: boolean;
|
|
39
|
+
instrumentation?: UOWInstrumentation;
|
|
29
40
|
}
|
|
30
41
|
|
|
31
|
-
export interface
|
|
42
|
+
export interface SqlAdapterOptions {
|
|
32
43
|
dialect: Dialect;
|
|
33
44
|
driverConfig: DriverConfig;
|
|
34
45
|
uowConfig?: UnitOfWorkConfig;
|
|
46
|
+
outbox?: OutboxConfig;
|
|
47
|
+
sqliteProfile?: SQLiteProfile;
|
|
48
|
+
sqliteStorageMode?: SQLiteStorageMode;
|
|
49
|
+
namingStrategy?: SqlNamingStrategy;
|
|
35
50
|
}
|
|
36
51
|
|
|
37
|
-
export
|
|
52
|
+
export const sqliteProfiles: Record<SQLiteProfile, SQLiteStorageMode> = {
|
|
53
|
+
default: sqliteStorageDefault,
|
|
54
|
+
prisma: sqliteStoragePrisma,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export class SqlAdapter implements DatabaseAdapter<UnitOfWorkConfig> {
|
|
38
58
|
readonly dialect: Dialect;
|
|
39
59
|
readonly driverConfig: DriverConfig;
|
|
40
60
|
readonly uowConfig?: UnitOfWorkConfig;
|
|
61
|
+
readonly outbox?: OutboxConfig;
|
|
62
|
+
readonly sqliteStorageMode?: SQLiteStorageMode;
|
|
63
|
+
readonly sqliteProfile?: SQLiteProfile;
|
|
64
|
+
readonly adapterMetadata: DatabaseAdapterMetadata;
|
|
65
|
+
readonly namingStrategy: SqlNamingStrategy;
|
|
41
66
|
|
|
42
|
-
#schemaNamespaceMap = new WeakMap<AnySchema, string>();
|
|
67
|
+
#schemaNamespaceMap = new WeakMap<AnySchema, string | null>();
|
|
43
68
|
#contextStorage: RequestContextStorage<DatabaseContextStorage>;
|
|
44
69
|
|
|
45
70
|
#driver: SqlDriverAdapter;
|
|
46
71
|
|
|
47
|
-
constructor({
|
|
72
|
+
constructor({
|
|
73
|
+
dialect,
|
|
74
|
+
driverConfig,
|
|
75
|
+
uowConfig,
|
|
76
|
+
outbox,
|
|
77
|
+
sqliteProfile,
|
|
78
|
+
sqliteStorageMode,
|
|
79
|
+
namingStrategy,
|
|
80
|
+
}: SqlAdapterOptions) {
|
|
48
81
|
this.dialect = dialect;
|
|
49
82
|
this.driverConfig = driverConfig;
|
|
50
83
|
this.uowConfig = uowConfig;
|
|
84
|
+
this.outbox = outbox;
|
|
85
|
+
this.namingStrategy = namingStrategy ?? driverConfig.defaultNamingStrategy;
|
|
86
|
+
const resolvedProfile = sqliteProfile ?? "default";
|
|
87
|
+
|
|
88
|
+
if (sqliteStorageMode && sqliteProfile) {
|
|
89
|
+
throw new Error("sqliteStorageMode cannot be used together with sqliteProfile.");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.sqliteStorageMode =
|
|
93
|
+
driverConfig.databaseType === "sqlite"
|
|
94
|
+
? (sqliteStorageMode ?? sqliteProfiles[resolvedProfile])
|
|
95
|
+
: undefined;
|
|
96
|
+
this.sqliteProfile =
|
|
97
|
+
driverConfig.databaseType === "sqlite" && !sqliteStorageMode ? resolvedProfile : undefined;
|
|
98
|
+
|
|
99
|
+
this.adapterMetadata = {
|
|
100
|
+
databaseType: driverConfig.databaseType,
|
|
101
|
+
sqliteProfile: this.sqliteProfile,
|
|
102
|
+
sqliteStorageMode: this.sqliteStorageMode,
|
|
103
|
+
};
|
|
51
104
|
|
|
52
|
-
this.#schemaNamespaceMap = new WeakMap<AnySchema, string>();
|
|
105
|
+
this.#schemaNamespaceMap = new WeakMap<AnySchema, string | null>();
|
|
53
106
|
this.#contextStorage = new RequestContextStorage();
|
|
54
107
|
|
|
55
108
|
this.#driver = new SqlDriverAdapter(dialect);
|
|
@@ -60,11 +113,11 @@ export class GenericSQLAdapter implements DatabaseAdapter<UnitOfWorkConfig> {
|
|
|
60
113
|
}
|
|
61
114
|
|
|
62
115
|
get [fragnoDatabaseAdapterNameFakeSymbol](): string {
|
|
63
|
-
return "
|
|
116
|
+
return "sql";
|
|
64
117
|
}
|
|
65
118
|
|
|
66
119
|
get [fragnoDatabaseAdapterVersionFakeSymbol](): number {
|
|
67
|
-
return
|
|
120
|
+
return 1;
|
|
68
121
|
}
|
|
69
122
|
|
|
70
123
|
get contextStorage(): RequestContextStorage<DatabaseContextStorage> {
|
|
@@ -77,23 +130,23 @@ export class GenericSQLAdapter implements DatabaseAdapter<UnitOfWorkConfig> {
|
|
|
77
130
|
|
|
78
131
|
async isConnectionHealthy(): Promise<boolean> {
|
|
79
132
|
const result = await this.#driver.executeQuery(sql`SELECT 1 as healthy`.compile(this.dialect));
|
|
80
|
-
|
|
133
|
+
const healthyValue = result.rows[0]?.["healthy"];
|
|
134
|
+
return healthyValue === 1 || healthyValue === 1n || healthyValue === "1";
|
|
81
135
|
}
|
|
82
136
|
|
|
83
|
-
prepareMigrations<T extends AnySchema>(schema: T, namespace: string): PreparedMigrations {
|
|
137
|
+
prepareMigrations<T extends AnySchema>(schema: T, namespace: string | null): PreparedMigrations {
|
|
138
|
+
const resolver = createNamingResolver(schema, namespace, this.namingStrategy);
|
|
84
139
|
return createPreparedMigrations({
|
|
85
140
|
schema,
|
|
86
|
-
namespace,
|
|
141
|
+
namespace: namespace ?? schema.name,
|
|
87
142
|
database: this.driverConfig.databaseType,
|
|
88
|
-
|
|
143
|
+
driverConfig: this.driverConfig,
|
|
144
|
+
sqliteStorageMode: this.sqliteStorageMode,
|
|
145
|
+
resolver,
|
|
89
146
|
driver: this.#driver,
|
|
90
147
|
});
|
|
91
148
|
}
|
|
92
149
|
|
|
93
|
-
createTableNameMapper(namespace: string): TableNameMapper {
|
|
94
|
-
return createTableNameMapper(namespace, false);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
150
|
async getSchemaVersion(namespace: string): Promise<string | undefined> {
|
|
98
151
|
const key = `${namespace}.schema_version`;
|
|
99
152
|
const query = sql`SELECT value FROM fragno_db_settings WHERE key = ${key};`.compile(
|
|
@@ -125,22 +178,55 @@ export class GenericSQLAdapter implements DatabaseAdapter<UnitOfWorkConfig> {
|
|
|
125
178
|
|
|
126
179
|
createQueryEngine<T extends AnySchema>(
|
|
127
180
|
schema: T,
|
|
128
|
-
namespace: string,
|
|
181
|
+
namespace: string | null,
|
|
129
182
|
): SimpleQueryInterface<T, UnitOfWorkConfig> {
|
|
130
183
|
this.#schemaNamespaceMap.set(schema, namespace);
|
|
184
|
+
const resolver = createNamingResolver(schema, namespace, this.namingStrategy);
|
|
131
185
|
|
|
132
|
-
const operationCompiler = new GenericSQLUOWOperationCompiler(
|
|
133
|
-
|
|
186
|
+
const operationCompiler = new GenericSQLUOWOperationCompiler(
|
|
187
|
+
this.driverConfig,
|
|
188
|
+
this.sqliteStorageMode,
|
|
189
|
+
(schemaForResolver, namespaceForResolver): NamingResolver =>
|
|
190
|
+
createNamingResolver(schemaForResolver, namespaceForResolver, this.namingStrategy),
|
|
134
191
|
);
|
|
135
192
|
|
|
136
193
|
const factory: UnitOfWorkFactory = {
|
|
137
194
|
compiler: createUOWCompilerFromOperationCompiler(operationCompiler),
|
|
138
|
-
executor: createExecutor(this.#driver, this.driverConfig,
|
|
139
|
-
|
|
195
|
+
executor: createExecutor(this.#driver, this.driverConfig, {
|
|
196
|
+
dialect: this.dialect,
|
|
197
|
+
dryRun: false,
|
|
198
|
+
outbox: this.outbox,
|
|
199
|
+
namingStrategy: this.namingStrategy,
|
|
200
|
+
}),
|
|
201
|
+
decoder: new UnitOfWorkDecoder(this.driverConfig, this.sqliteStorageMode, resolver),
|
|
140
202
|
uowConfig: this.uowConfig,
|
|
141
203
|
schemaNamespaceMap: this.#schemaNamespaceMap,
|
|
142
204
|
};
|
|
143
205
|
|
|
144
|
-
|
|
206
|
+
const queryEngine = fromUnitOfWorkCompiler(schema, factory) as SimpleQueryInterface<
|
|
207
|
+
T,
|
|
208
|
+
UnitOfWorkConfig
|
|
209
|
+
>;
|
|
210
|
+
|
|
211
|
+
const serializer = createSQLSerializer(this.driverConfig, this.sqliteStorageMode);
|
|
212
|
+
const timestampColumn = { type: "timestamp" } as AnyColumn;
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
...queryEngine,
|
|
216
|
+
now: async () => {
|
|
217
|
+
const result = await this.#driver.executeQuery(
|
|
218
|
+
sql`SELECT CURRENT_TIMESTAMP as now`.compile(this.dialect),
|
|
219
|
+
);
|
|
220
|
+
const rawValue = result.rows[0]?.["now"];
|
|
221
|
+
if (rawValue === undefined || rawValue === null) {
|
|
222
|
+
throw new Error("Failed to fetch database time");
|
|
223
|
+
}
|
|
224
|
+
return serializer.deserialize(rawValue, timestampColumn) as Date;
|
|
225
|
+
},
|
|
226
|
+
};
|
|
145
227
|
}
|
|
146
228
|
}
|
|
229
|
+
|
|
230
|
+
export type { SQLiteStorageMode } from "./sqlite-storage";
|
|
231
|
+
export { sqliteStorageDefault, sqliteStoragePrisma } from "./sqlite-storage";
|
|
232
|
+
export type { OutboxConfig } from "../../outbox/outbox";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { CompiledMutation } from "../../query/unit-of-work/unit-of-work";
|
|
3
|
+
import type { CompiledQuery, Dialect } from "../../sql-driver/sql-driver";
|
|
4
|
+
import type { SqlDriverAdapter } from "../../sql-driver/sql-driver-adapter";
|
|
5
|
+
import { NodePostgresDriverConfig } from "./driver-config";
|
|
6
|
+
import { executeMutation } from "./generic-sql-uow-executor";
|
|
7
|
+
|
|
8
|
+
const createError = (code: string) => {
|
|
9
|
+
const error = new Error(`DB_ERROR_${code}`);
|
|
10
|
+
(error as { code?: string }).code = code;
|
|
11
|
+
return error;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const createAdapterThatThrows = (error: Error) =>
|
|
15
|
+
({
|
|
16
|
+
transaction: async (
|
|
17
|
+
callback: (trx: { executeQuery: () => Promise<unknown> }) => Promise<unknown>,
|
|
18
|
+
) =>
|
|
19
|
+
await callback({
|
|
20
|
+
executeQuery: async () => {
|
|
21
|
+
throw error;
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
}) as unknown as SqlDriverAdapter;
|
|
25
|
+
|
|
26
|
+
describe("executeMutation", () => {
|
|
27
|
+
const dialect = {
|
|
28
|
+
createAdapter: () => ({}) as Dialect["createAdapter"] extends () => infer T ? T : never,
|
|
29
|
+
createDriver: () => ({}) as Dialect["createDriver"] extends () => infer T ? T : never,
|
|
30
|
+
createQueryCompiler: () =>
|
|
31
|
+
({}) as Dialect["createQueryCompiler"] extends () => infer T ? T : never,
|
|
32
|
+
} satisfies Dialect;
|
|
33
|
+
const compiledQuery: CompiledQuery = {
|
|
34
|
+
sql: "SELECT 1",
|
|
35
|
+
parameters: [],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const mutationBatch: CompiledMutation<CompiledQuery>[] = [
|
|
39
|
+
{
|
|
40
|
+
op: "update",
|
|
41
|
+
query: compiledQuery,
|
|
42
|
+
expectedAffectedRows: null,
|
|
43
|
+
expectedReturnedRows: null,
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
it.each(["40001", "40P01"])("returns success=false on SQLSTATE %s", async (code) => {
|
|
48
|
+
const adapter = createAdapterThatThrows(createError(code));
|
|
49
|
+
const result = await executeMutation(adapter, new NodePostgresDriverConfig(), mutationBatch, {
|
|
50
|
+
dialect,
|
|
51
|
+
});
|
|
52
|
+
expect(result.success).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -3,10 +3,33 @@ import type {
|
|
|
3
3
|
MutationResult,
|
|
4
4
|
UOWExecutor,
|
|
5
5
|
} from "../../query/unit-of-work/unit-of-work";
|
|
6
|
-
import type { CompiledQuery } from "../../sql-driver/sql-driver";
|
|
6
|
+
import type { CompiledQuery, Dialect } from "../../sql-driver/sql-driver";
|
|
7
7
|
import type { SqlDriverAdapter } from "../../sql-driver/sql-driver-adapter";
|
|
8
8
|
import type { DriverConfig } from "./driver-config";
|
|
9
9
|
import { ResultInterpreter } from "./result-interpreter";
|
|
10
|
+
import { sql } from "../../sql-driver/sql";
|
|
11
|
+
import { createId } from "../../id";
|
|
12
|
+
import superjson from "superjson";
|
|
13
|
+
import { createColdKysely } from "./migration/cold-kysely";
|
|
14
|
+
import { SETTINGS_NAMESPACE, internalSchema } from "../../fragments/internal-fragment.schema";
|
|
15
|
+
import {
|
|
16
|
+
type OutboxConfig,
|
|
17
|
+
type OutboxRefLookup,
|
|
18
|
+
type OutboxRefMap,
|
|
19
|
+
encodeVersionstamp,
|
|
20
|
+
parseOutboxVersionValue,
|
|
21
|
+
versionstampToHex,
|
|
22
|
+
} from "../../outbox/outbox";
|
|
23
|
+
import { buildOutboxPlan, finalizeOutboxPayload } from "../../outbox/outbox-builder";
|
|
24
|
+
import { createSQLSerializer } from "../../query/serialize/create-sql-serializer";
|
|
25
|
+
import { type SqlNamingStrategy } from "../../naming/sql-naming";
|
|
26
|
+
|
|
27
|
+
export interface ExecutorOptions {
|
|
28
|
+
dryRun?: boolean;
|
|
29
|
+
dialect: Dialect;
|
|
30
|
+
outbox?: OutboxConfig;
|
|
31
|
+
namingStrategy?: SqlNamingStrategy;
|
|
32
|
+
}
|
|
10
33
|
|
|
11
34
|
export async function executeRetrieval(
|
|
12
35
|
adapter: SqlDriverAdapter,
|
|
@@ -32,6 +55,7 @@ export async function executeMutation(
|
|
|
32
55
|
adapter: SqlDriverAdapter,
|
|
33
56
|
driverConfig: DriverConfig,
|
|
34
57
|
mutationBatch: CompiledMutation<CompiledQuery>[],
|
|
58
|
+
options: ExecutorOptions,
|
|
35
59
|
): Promise<MutationResult> {
|
|
36
60
|
if (mutationBatch.length === 0) {
|
|
37
61
|
return { success: true, createdInternalIds: [] };
|
|
@@ -39,9 +63,24 @@ export async function executeMutation(
|
|
|
39
63
|
|
|
40
64
|
const createdInternalIds: (bigint | null)[] = [];
|
|
41
65
|
const resultInterpreter = new ResultInterpreter(driverConfig);
|
|
66
|
+
const outboxEnabled = options.outbox?.enabled ?? false;
|
|
67
|
+
const namingStrategy = options.namingStrategy ?? driverConfig.defaultNamingStrategy;
|
|
68
|
+
|
|
69
|
+
const outboxOperations = outboxEnabled
|
|
70
|
+
? mutationBatch.flatMap((mutation) => (mutation.operation ? [mutation.operation] : []))
|
|
71
|
+
: [];
|
|
72
|
+
|
|
73
|
+
const outboxPlan = outboxOperations.length > 0 ? buildOutboxPlan(outboxOperations) : null;
|
|
74
|
+
const shouldWriteOutbox = outboxEnabled && outboxPlan !== null && outboxPlan.drafts.length > 0;
|
|
42
75
|
|
|
43
76
|
try {
|
|
44
77
|
await adapter.transaction(async (tx) => {
|
|
78
|
+
let outboxVersion: bigint | null = null;
|
|
79
|
+
|
|
80
|
+
if (shouldWriteOutbox) {
|
|
81
|
+
outboxVersion = await reserveOutboxVersion(tx, driverConfig, options.dialect);
|
|
82
|
+
}
|
|
83
|
+
|
|
45
84
|
for (const compiledMutation of mutationBatch) {
|
|
46
85
|
const result = await tx.executeQuery(compiledMutation.query);
|
|
47
86
|
|
|
@@ -86,6 +125,31 @@ export async function executeMutation(
|
|
|
86
125
|
}
|
|
87
126
|
}
|
|
88
127
|
}
|
|
128
|
+
|
|
129
|
+
if (shouldWriteOutbox && outboxPlan && outboxVersion !== null) {
|
|
130
|
+
const uowId = mutationBatch[0]?.uowId;
|
|
131
|
+
if (!uowId) {
|
|
132
|
+
throw new Error("Outbox mutation batch is missing uowId.");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const refMap = await resolveOutboxRefMap(
|
|
136
|
+
tx,
|
|
137
|
+
driverConfig,
|
|
138
|
+
outboxPlan.lookups,
|
|
139
|
+
namingStrategy,
|
|
140
|
+
);
|
|
141
|
+
const payload = finalizeOutboxPayload(outboxPlan, outboxVersion);
|
|
142
|
+
const payloadSerialized = superjson.serialize(payload);
|
|
143
|
+
const versionstamp = versionstampToHex(encodeVersionstamp(outboxVersion, 0));
|
|
144
|
+
|
|
145
|
+
await insertOutboxRow(tx, driverConfig, {
|
|
146
|
+
id: createId(),
|
|
147
|
+
versionstamp,
|
|
148
|
+
uowId,
|
|
149
|
+
payload: payloadSerialized,
|
|
150
|
+
refMap,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
89
153
|
});
|
|
90
154
|
|
|
91
155
|
return { success: true, createdInternalIds };
|
|
@@ -96,6 +160,15 @@ export async function executeMutation(
|
|
|
96
160
|
return { success: false };
|
|
97
161
|
}
|
|
98
162
|
|
|
163
|
+
const errorCode =
|
|
164
|
+
typeof error === "object" && error !== null && "code" in error
|
|
165
|
+
? (error as { code?: unknown }).code
|
|
166
|
+
: undefined;
|
|
167
|
+
|
|
168
|
+
if (errorCode === "40001" || errorCode === "40P01") {
|
|
169
|
+
return { success: false };
|
|
170
|
+
}
|
|
171
|
+
|
|
99
172
|
// Other database errors should be thrown
|
|
100
173
|
throw error;
|
|
101
174
|
}
|
|
@@ -104,8 +177,10 @@ export async function executeMutation(
|
|
|
104
177
|
export function createExecutor(
|
|
105
178
|
adapter: SqlDriverAdapter,
|
|
106
179
|
driverConfig: DriverConfig,
|
|
107
|
-
|
|
180
|
+
options: ExecutorOptions,
|
|
108
181
|
): UOWExecutor<CompiledQuery, unknown> {
|
|
182
|
+
const dryRun = options.dryRun ?? false;
|
|
183
|
+
|
|
109
184
|
return {
|
|
110
185
|
async executeRetrievalPhase(retrievalBatch: CompiledQuery[]) {
|
|
111
186
|
// In dryRun mode, skip execution and return empty results
|
|
@@ -124,7 +199,160 @@ export function createExecutor(
|
|
|
124
199
|
};
|
|
125
200
|
}
|
|
126
201
|
|
|
127
|
-
return executeMutation(adapter, driverConfig, mutationBatch);
|
|
202
|
+
return executeMutation(adapter, driverConfig, mutationBatch, options);
|
|
128
203
|
},
|
|
129
204
|
};
|
|
130
205
|
}
|
|
206
|
+
|
|
207
|
+
async function reserveOutboxVersion(
|
|
208
|
+
tx: SqlDriverAdapter,
|
|
209
|
+
driverConfig: DriverConfig,
|
|
210
|
+
dialect: Dialect,
|
|
211
|
+
): Promise<bigint> {
|
|
212
|
+
const key = `${SETTINGS_NAMESPACE}.outbox_version`;
|
|
213
|
+
const id = createId();
|
|
214
|
+
|
|
215
|
+
switch (driverConfig.outboxVersionstampStrategy) {
|
|
216
|
+
case "insert-on-conflict-returning": {
|
|
217
|
+
const query =
|
|
218
|
+
driverConfig.databaseType === "postgresql"
|
|
219
|
+
? sql`
|
|
220
|
+
insert into fragno_db_settings (id, key, value)
|
|
221
|
+
values (${id}, ${key}, '0')
|
|
222
|
+
on conflict (key) do update
|
|
223
|
+
set value = (fragno_db_settings.value::bigint + 1)::text
|
|
224
|
+
returning value;
|
|
225
|
+
`
|
|
226
|
+
: sql`
|
|
227
|
+
insert into fragno_db_settings (id, key, value)
|
|
228
|
+
values (${id}, ${key}, '0')
|
|
229
|
+
on conflict (key) do update
|
|
230
|
+
set value = cast(fragno_db_settings.value as integer) + 1
|
|
231
|
+
returning value;
|
|
232
|
+
`;
|
|
233
|
+
|
|
234
|
+
const result = await tx.executeQuery(query.compile(dialect));
|
|
235
|
+
const value = result.rows[0]?.["value"];
|
|
236
|
+
return parseOutboxVersionValue(value);
|
|
237
|
+
}
|
|
238
|
+
case "update-returning": {
|
|
239
|
+
const query =
|
|
240
|
+
driverConfig.databaseType === "postgresql"
|
|
241
|
+
? sql`
|
|
242
|
+
update fragno_db_settings
|
|
243
|
+
set value = (fragno_db_settings.value::bigint + 1)::text
|
|
244
|
+
where key = ${key}
|
|
245
|
+
returning value;
|
|
246
|
+
`
|
|
247
|
+
: sql`
|
|
248
|
+
update fragno_db_settings
|
|
249
|
+
set value = cast(fragno_db_settings.value as integer) + 1
|
|
250
|
+
where key = ${key}
|
|
251
|
+
returning value;
|
|
252
|
+
`;
|
|
253
|
+
|
|
254
|
+
const result = await tx.executeQuery(query.compile(dialect));
|
|
255
|
+
const value = result.rows[0]?.["value"];
|
|
256
|
+
if (value === undefined) {
|
|
257
|
+
throw new Error("Outbox version row was not found for update-returning strategy.");
|
|
258
|
+
}
|
|
259
|
+
return parseOutboxVersionValue(value);
|
|
260
|
+
}
|
|
261
|
+
case "insert-on-duplicate-last-insert-id": {
|
|
262
|
+
const insertQuery = sql`
|
|
263
|
+
insert into fragno_db_settings (id, key, value)
|
|
264
|
+
values (${id}, ${key}, LAST_INSERT_ID(0))
|
|
265
|
+
on duplicate key update value = LAST_INSERT_ID(cast(value as unsigned) + 1);
|
|
266
|
+
`;
|
|
267
|
+
|
|
268
|
+
await tx.executeQuery(insertQuery.compile(dialect));
|
|
269
|
+
|
|
270
|
+
const selectQuery = sql`select LAST_INSERT_ID() as value;`;
|
|
271
|
+
const result = await tx.executeQuery(selectQuery.compile(dialect));
|
|
272
|
+
const value = result.rows[0]?.["value"];
|
|
273
|
+
return parseOutboxVersionValue(value);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function resolveOutboxRefMap(
|
|
279
|
+
tx: SqlDriverAdapter,
|
|
280
|
+
driverConfig: DriverConfig,
|
|
281
|
+
lookups: OutboxRefLookup[],
|
|
282
|
+
namingStrategy: SqlNamingStrategy,
|
|
283
|
+
): Promise<OutboxRefMap | undefined> {
|
|
284
|
+
if (lookups.length === 0) {
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const refMap: OutboxRefMap = {};
|
|
289
|
+
const db = createColdKysely(driverConfig.databaseType);
|
|
290
|
+
|
|
291
|
+
for (const lookup of lookups) {
|
|
292
|
+
const namespace = lookup.namespace ?? null;
|
|
293
|
+
const logicalTable = lookup.table.name;
|
|
294
|
+
const schemaName =
|
|
295
|
+
namingStrategy.namespaceScope === "schema" && namespace && namespace.length > 0
|
|
296
|
+
? namingStrategy.namespaceToSchema(namespace)
|
|
297
|
+
: null;
|
|
298
|
+
const scopedDb = schemaName ? db.withSchema(schemaName) : db;
|
|
299
|
+
const tableName = namingStrategy.tableName(logicalTable, namespace);
|
|
300
|
+
const internalColumn = namingStrategy.columnName(
|
|
301
|
+
lookup.table.getInternalIdColumn().name,
|
|
302
|
+
logicalTable,
|
|
303
|
+
);
|
|
304
|
+
const externalColumn = namingStrategy.columnName(lookup.table.getIdColumn().name, logicalTable);
|
|
305
|
+
|
|
306
|
+
const query = scopedDb
|
|
307
|
+
.selectFrom(tableName)
|
|
308
|
+
.select(externalColumn)
|
|
309
|
+
.where(internalColumn, "=", lookup.internalId)
|
|
310
|
+
.compile();
|
|
311
|
+
|
|
312
|
+
const result = await tx.executeQuery(query);
|
|
313
|
+
const row = result.rows[0] as Record<string, unknown> | undefined;
|
|
314
|
+
const externalId = row?.[externalColumn];
|
|
315
|
+
|
|
316
|
+
if (typeof externalId !== "string") {
|
|
317
|
+
throw new Error(
|
|
318
|
+
`Failed to resolve outbox reference for ${tableName}.${internalColumn}=${String(lookup.internalId)}`,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
refMap[lookup.key] = externalId;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return Object.keys(refMap).length > 0 ? refMap : undefined;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function insertOutboxRow(
|
|
329
|
+
tx: SqlDriverAdapter,
|
|
330
|
+
driverConfig: DriverConfig,
|
|
331
|
+
options: {
|
|
332
|
+
id: string;
|
|
333
|
+
versionstamp: string;
|
|
334
|
+
uowId: string;
|
|
335
|
+
payload: { json: unknown; meta?: Record<string, unknown> };
|
|
336
|
+
refMap?: OutboxRefMap;
|
|
337
|
+
},
|
|
338
|
+
): Promise<void> {
|
|
339
|
+
const { id, versionstamp, uowId, payload, refMap } = options;
|
|
340
|
+
const refMapValue = refMap ?? null;
|
|
341
|
+
const serializer = createSQLSerializer(driverConfig);
|
|
342
|
+
const outboxTable = internalSchema.tables.fragno_db_outbox;
|
|
343
|
+
const values = { id, versionstamp, uowId, payload, refMap: refMapValue };
|
|
344
|
+
const serializedValues: Record<string, unknown> = {};
|
|
345
|
+
for (const [key, value] of Object.entries(values)) {
|
|
346
|
+
const col = outboxTable.columns[key];
|
|
347
|
+
if (!col) {
|
|
348
|
+
serializedValues[key] = value;
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
serializedValues[col.name] = serializer.serialize(value, col);
|
|
352
|
+
}
|
|
353
|
+
const db = createColdKysely(driverConfig.databaseType);
|
|
354
|
+
|
|
355
|
+
const query = db.insertInto("fragno_db_outbox").values(serializedValues).compile();
|
|
356
|
+
|
|
357
|
+
await tx.executeQuery(query);
|
|
358
|
+
}
|