@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
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { AnyTable, FragnoId } from "../schema/create";
|
|
2
|
+
|
|
3
|
+
export type OutboxConfig = {
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type OutboxVersionstampStrategy =
|
|
8
|
+
| "update-returning"
|
|
9
|
+
| "insert-on-conflict-returning"
|
|
10
|
+
| "insert-on-duplicate-last-insert-id";
|
|
11
|
+
|
|
12
|
+
export type OutboxPayload = {
|
|
13
|
+
version: 1;
|
|
14
|
+
mutations: OutboxMutation[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type OutboxMutation =
|
|
18
|
+
| {
|
|
19
|
+
op: "create";
|
|
20
|
+
schema: string;
|
|
21
|
+
namespace?: string;
|
|
22
|
+
table: string;
|
|
23
|
+
externalId: string;
|
|
24
|
+
versionstamp: string;
|
|
25
|
+
values: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
| {
|
|
28
|
+
op: "update";
|
|
29
|
+
schema: string;
|
|
30
|
+
namespace?: string;
|
|
31
|
+
table: string;
|
|
32
|
+
externalId: string;
|
|
33
|
+
versionstamp: string;
|
|
34
|
+
set: Record<string, unknown>;
|
|
35
|
+
checkVersion?: number;
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
op: "delete";
|
|
39
|
+
schema: string;
|
|
40
|
+
namespace?: string;
|
|
41
|
+
table: string;
|
|
42
|
+
externalId: string;
|
|
43
|
+
versionstamp: string;
|
|
44
|
+
checkVersion?: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type OutboxRefMap = Record<string, string>;
|
|
48
|
+
|
|
49
|
+
export type OutboxPayloadSerialized = {
|
|
50
|
+
json: unknown;
|
|
51
|
+
meta?: Record<string, unknown>;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type OutboxEntry = {
|
|
55
|
+
id: FragnoId;
|
|
56
|
+
versionstamp: string;
|
|
57
|
+
uowId: string;
|
|
58
|
+
payload: OutboxPayloadSerialized;
|
|
59
|
+
refMap?: OutboxRefMap;
|
|
60
|
+
createdAt: Date;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type OutboxRefLookup = {
|
|
64
|
+
key: string;
|
|
65
|
+
internalId: bigint | number;
|
|
66
|
+
table: AnyTable;
|
|
67
|
+
namespace?: string;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export function encodeVersionstamp(transactionVersion: bigint, userVersion: number): Uint8Array {
|
|
71
|
+
if (userVersion < 0 || userVersion > 0xffff) {
|
|
72
|
+
throw new Error(`Invalid outbox user version: ${userVersion}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const txBytes = bigintToBytes(transactionVersion, 10);
|
|
76
|
+
const userBytes = new Uint8Array(2);
|
|
77
|
+
userBytes[0] = (userVersion >> 8) & 0xff;
|
|
78
|
+
userBytes[1] = userVersion & 0xff;
|
|
79
|
+
|
|
80
|
+
const combined = new Uint8Array(12);
|
|
81
|
+
combined.set(txBytes, 0);
|
|
82
|
+
combined.set(userBytes, 10);
|
|
83
|
+
|
|
84
|
+
return combined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function versionstampToHex(bytes: Uint8Array): string {
|
|
88
|
+
let hex = "";
|
|
89
|
+
for (const byte of bytes) {
|
|
90
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
91
|
+
}
|
|
92
|
+
return hex;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function hexToVersionstamp(hex: string): Uint8Array {
|
|
96
|
+
if (hex.length % 2 !== 0) {
|
|
97
|
+
throw new Error(`Invalid versionstamp hex length: ${hex.length}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
101
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
102
|
+
bytes[i / 2] = Number.parseInt(hex.slice(i, i + 2), 16);
|
|
103
|
+
}
|
|
104
|
+
return bytes;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function parseOutboxVersionValue(value: unknown): bigint {
|
|
108
|
+
if (typeof value === "bigint") {
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (typeof value === "number") {
|
|
113
|
+
return BigInt(value);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (typeof value === "string") {
|
|
117
|
+
return BigInt(value);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
throw new Error(`Invalid outbox version value: ${String(value)}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function bigintToBytes(value: bigint, length: number): Uint8Array {
|
|
124
|
+
const bytes = new Uint8Array(length);
|
|
125
|
+
let remaining = value;
|
|
126
|
+
|
|
127
|
+
for (let i = length - 1; i >= 0; i -= 1) {
|
|
128
|
+
bytes[i] = Number(remaining & 0xffn);
|
|
129
|
+
remaining >>= 8n;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (remaining !== 0n) {
|
|
133
|
+
throw new Error(`Outbox version ${value} exceeds ${length * 8} bits`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return bytes;
|
|
137
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { AnyColumn } from "../schema/create";
|
|
2
2
|
import { createId } from "../id";
|
|
3
3
|
|
|
4
|
+
export type RuntimeDefaultContext = {
|
|
5
|
+
now?: () => Date;
|
|
6
|
+
createId?: () => string;
|
|
7
|
+
};
|
|
8
|
+
|
|
4
9
|
/**
|
|
5
10
|
* Generate a runtime default value for a column that has defaultTo$()
|
|
6
11
|
*
|
|
@@ -12,7 +17,10 @@ import { createId } from "../id";
|
|
|
12
17
|
*
|
|
13
18
|
* @internal
|
|
14
19
|
*/
|
|
15
|
-
export function generateRuntimeDefault(
|
|
20
|
+
export function generateRuntimeDefault(
|
|
21
|
+
column: AnyColumn,
|
|
22
|
+
context: RuntimeDefaultContext = {},
|
|
23
|
+
): unknown {
|
|
16
24
|
// Check if column has a default value configuration
|
|
17
25
|
if (!column.default) {
|
|
18
26
|
return undefined;
|
|
@@ -34,11 +42,11 @@ export function generateRuntimeDefault(column: AnyColumn): unknown {
|
|
|
34
42
|
const runtime = column.default.runtime;
|
|
35
43
|
|
|
36
44
|
if (runtime === "cuid") {
|
|
37
|
-
return createId();
|
|
45
|
+
return (context.createId ?? createId)();
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
if (runtime === "now") {
|
|
41
|
-
return new Date();
|
|
49
|
+
return (context.now ?? (() => new Date()))();
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
if (typeof runtime === "function") {
|
|
@@ -47,3 +55,33 @@ export function generateRuntimeDefault(column: AnyColumn): unknown {
|
|
|
47
55
|
|
|
48
56
|
return undefined;
|
|
49
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generate a fallback value for database-level defaults.
|
|
61
|
+
*
|
|
62
|
+
* This is used by adapters that cannot rely on database DEFAULT constraints,
|
|
63
|
+
* such as the in-memory adapter.
|
|
64
|
+
*
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
export function generateDatabaseDefault(
|
|
68
|
+
column: AnyColumn,
|
|
69
|
+
context: RuntimeDefaultContext = {},
|
|
70
|
+
): unknown {
|
|
71
|
+
if (!column.default) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if ("value" in column.default) {
|
|
76
|
+
return column.default.value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if ("dbSpecial" in column.default) {
|
|
80
|
+
if (column.default.dbSpecial === "now") {
|
|
81
|
+
return (context.now ?? (() => new Date()))();
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
@@ -3,7 +3,7 @@ import { column, idColumn, schema } from "../schema/create";
|
|
|
3
3
|
import { createBuilder, createIndexedBuilder } from "./condition-builder";
|
|
4
4
|
|
|
5
5
|
describe("ConditionBuilder", () => {
|
|
6
|
-
const testSchema = schema((s) =>
|
|
6
|
+
const testSchema = schema("test", (s) =>
|
|
7
7
|
s
|
|
8
8
|
.addTable("users", (t) =>
|
|
9
9
|
t
|
|
@@ -11,7 +11,7 @@ describe("ConditionBuilder", () => {
|
|
|
11
11
|
.addColumn("email", column("string"))
|
|
12
12
|
.addColumn("name", column("string"))
|
|
13
13
|
.addColumn("age", column("integer").nullable())
|
|
14
|
-
.createIndex("
|
|
14
|
+
.createIndex("idx_users_primary", ["id"], { unique: true })
|
|
15
15
|
.createIndex("idx_email", ["email"], { unique: true })
|
|
16
16
|
.createIndex("idx_name_age", ["name", "age"]),
|
|
17
17
|
)
|
|
@@ -21,7 +21,7 @@ describe("ConditionBuilder", () => {
|
|
|
21
21
|
.addColumn("title", column("string"))
|
|
22
22
|
.addColumn("content", column("string"))
|
|
23
23
|
.addColumn("published", column("bool"))
|
|
24
|
-
.createIndex("
|
|
24
|
+
.createIndex("idx_posts_primary", ["id"], { unique: true })
|
|
25
25
|
.createIndex("idx_published", ["published"]),
|
|
26
26
|
),
|
|
27
27
|
);
|
package/src/query/cursor.test.ts
CHANGED
|
@@ -92,6 +92,39 @@ describe("Cursor utilities", () => {
|
|
|
92
92
|
const decoded = decodeCursor(encoded);
|
|
93
93
|
expect(decoded.indexValues).toEqual({});
|
|
94
94
|
});
|
|
95
|
+
|
|
96
|
+
it("should throw when index values contain undefined", () => {
|
|
97
|
+
const cursor = new Cursor({
|
|
98
|
+
indexName: "_primary",
|
|
99
|
+
orderDirection: "asc",
|
|
100
|
+
pageSize: 10,
|
|
101
|
+
indexValues: { id: undefined },
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(() => cursor.encode()).toThrow(/undefined/i);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should throw when index values contain BigInt", () => {
|
|
108
|
+
const cursor = new Cursor({
|
|
109
|
+
indexName: "_primary",
|
|
110
|
+
orderDirection: "asc",
|
|
111
|
+
pageSize: 10,
|
|
112
|
+
indexValues: { id: 1n },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(() => cursor.encode()).toThrow(/bigint/i);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should throw when index values contain non-finite numbers", () => {
|
|
119
|
+
const cursor = new Cursor({
|
|
120
|
+
indexName: "_primary",
|
|
121
|
+
orderDirection: "asc",
|
|
122
|
+
pageSize: 10,
|
|
123
|
+
indexValues: { id: Number.NaN },
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(() => cursor.encode()).toThrow(/finite/i);
|
|
127
|
+
});
|
|
95
128
|
});
|
|
96
129
|
|
|
97
130
|
describe("decodeCursor error handling", () => {
|
|
@@ -145,11 +178,58 @@ describe("Cursor utilities", () => {
|
|
|
145
178
|
const encoded = Buffer.from(JSON.stringify(invalidData), "utf-8").toString("base64");
|
|
146
179
|
expect(() => decodeCursor(encoded)).toThrow(/invalid cursor/i);
|
|
147
180
|
});
|
|
181
|
+
|
|
182
|
+
it("should throw error for array indexValues", () => {
|
|
183
|
+
const invalidData = {
|
|
184
|
+
v: 1,
|
|
185
|
+
indexValues: [],
|
|
186
|
+
indexName: "test",
|
|
187
|
+
orderDirection: "asc",
|
|
188
|
+
pageSize: 10,
|
|
189
|
+
};
|
|
190
|
+
const encoded = Buffer.from(JSON.stringify(invalidData), "utf-8").toString("base64");
|
|
191
|
+
expect(() => decodeCursor(encoded)).toThrow(/invalid cursor/i);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("should throw error for non-string indexName", () => {
|
|
195
|
+
const invalidData = {
|
|
196
|
+
v: 1,
|
|
197
|
+
indexValues: { id: "test" },
|
|
198
|
+
indexName: 123,
|
|
199
|
+
orderDirection: "asc",
|
|
200
|
+
pageSize: 10,
|
|
201
|
+
};
|
|
202
|
+
const encoded = Buffer.from(JSON.stringify(invalidData), "utf-8").toString("base64");
|
|
203
|
+
expect(() => decodeCursor(encoded)).toThrow(/invalid cursor/i);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should throw error for invalid pageSize values", () => {
|
|
207
|
+
const invalidData = {
|
|
208
|
+
v: 1,
|
|
209
|
+
indexValues: { id: "test" },
|
|
210
|
+
indexName: "test",
|
|
211
|
+
orderDirection: "asc",
|
|
212
|
+
pageSize: 0,
|
|
213
|
+
};
|
|
214
|
+
const encoded = Buffer.from(JSON.stringify(invalidData), "utf-8").toString("base64");
|
|
215
|
+
expect(() => decodeCursor(encoded)).toThrow(/invalid cursor/i);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("should throw error for missing cursor version", () => {
|
|
219
|
+
const invalidData = {
|
|
220
|
+
indexValues: { id: "test" },
|
|
221
|
+
indexName: "test",
|
|
222
|
+
orderDirection: "asc",
|
|
223
|
+
pageSize: 10,
|
|
224
|
+
};
|
|
225
|
+
const encoded = Buffer.from(JSON.stringify(invalidData), "utf-8").toString("base64");
|
|
226
|
+
expect(() => decodeCursor(encoded)).toThrow(/unsupported cursor version/i);
|
|
227
|
+
});
|
|
148
228
|
});
|
|
149
229
|
|
|
150
230
|
describe("createCursorFromRecord", () => {
|
|
151
231
|
it("should create a cursor from a record with single column index", () => {
|
|
152
|
-
const testSchema = schema((s) =>
|
|
232
|
+
const testSchema = schema("test", (s) =>
|
|
153
233
|
s.addTable("users", (t) =>
|
|
154
234
|
t.addColumn("id", idColumn()).addColumn("name", column("string")),
|
|
155
235
|
),
|
|
@@ -172,7 +252,7 @@ describe("Cursor utilities", () => {
|
|
|
172
252
|
});
|
|
173
253
|
|
|
174
254
|
it("should create a cursor from a record with multi-column index", () => {
|
|
175
|
-
const testSchema = schema((s) =>
|
|
255
|
+
const testSchema = schema("test", (s) =>
|
|
176
256
|
s.addTable("posts", (t) =>
|
|
177
257
|
t
|
|
178
258
|
.addColumn("id", idColumn())
|
|
@@ -207,8 +287,31 @@ describe("Cursor utilities", () => {
|
|
|
207
287
|
});
|
|
208
288
|
});
|
|
209
289
|
|
|
290
|
+
it("should throw when record is missing index columns", () => {
|
|
291
|
+
const testSchema = schema("test", (s) =>
|
|
292
|
+
s.addTable("posts", (t) =>
|
|
293
|
+
t
|
|
294
|
+
.addColumn("id", idColumn())
|
|
295
|
+
.addColumn("createdAt", column("integer"))
|
|
296
|
+
.createIndex("created", ["createdAt"]),
|
|
297
|
+
),
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const table = testSchema.tables.posts;
|
|
301
|
+
const record = { id: "post123" };
|
|
302
|
+
const indexColumns = table.indexes.created.columns;
|
|
303
|
+
|
|
304
|
+
expect(() =>
|
|
305
|
+
createCursorFromRecord(record, indexColumns, {
|
|
306
|
+
indexName: "created",
|
|
307
|
+
orderDirection: "asc",
|
|
308
|
+
pageSize: 10,
|
|
309
|
+
}),
|
|
310
|
+
).toThrow(/missing value/i);
|
|
311
|
+
});
|
|
312
|
+
|
|
210
313
|
it("should only include columns that are in the index", () => {
|
|
211
|
-
const testSchema = schema((s) =>
|
|
314
|
+
const testSchema = schema("test", (s) =>
|
|
212
315
|
s.addTable("users", (t) =>
|
|
213
316
|
t
|
|
214
317
|
.addColumn("id", idColumn())
|
|
@@ -235,7 +338,7 @@ describe("Cursor utilities", () => {
|
|
|
235
338
|
|
|
236
339
|
describe("serializeCursorValues", () => {
|
|
237
340
|
it("should serialize cursor values for database queries", () => {
|
|
238
|
-
const testSchema = schema((s) =>
|
|
341
|
+
const testSchema = schema("test", (s) =>
|
|
239
342
|
s.addTable("users", (t) =>
|
|
240
343
|
t.addColumn("id", idColumn()).addColumn("age", column("integer")),
|
|
241
344
|
),
|
|
@@ -260,8 +363,8 @@ describe("Cursor utilities", () => {
|
|
|
260
363
|
expect(serialized).toHaveProperty("age", 25);
|
|
261
364
|
});
|
|
262
365
|
|
|
263
|
-
it("should
|
|
264
|
-
const testSchema = schema((s) =>
|
|
366
|
+
it("should throw when cursor data is missing index values", () => {
|
|
367
|
+
const testSchema = schema("test", (s) =>
|
|
265
368
|
s.addTable("users", (t) =>
|
|
266
369
|
t.addColumn("id", idColumn()).addColumn("name", column("string")),
|
|
267
370
|
),
|
|
@@ -276,20 +379,15 @@ describe("Cursor utilities", () => {
|
|
|
276
379
|
});
|
|
277
380
|
|
|
278
381
|
const indexColumns = [table.columns.id, table.columns.name];
|
|
279
|
-
|
|
280
|
-
cursor,
|
|
281
|
-
|
|
282
|
-
new NodePostgresDriverConfig(),
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
expect(serialized).toHaveProperty("id", "user123");
|
|
286
|
-
expect(serialized).not.toHaveProperty("name");
|
|
382
|
+
expect(() =>
|
|
383
|
+
serializeCursorValues(cursor, indexColumns, new NodePostgresDriverConfig()),
|
|
384
|
+
).toThrow(/missing values/i);
|
|
287
385
|
});
|
|
288
386
|
});
|
|
289
387
|
|
|
290
388
|
describe("round-trip integration", () => {
|
|
291
389
|
it("should successfully round-trip cursor data through encode/decode", () => {
|
|
292
|
-
const testSchema = schema((s) =>
|
|
390
|
+
const testSchema = schema("test", (s) =>
|
|
293
391
|
s.addTable("posts", (t) =>
|
|
294
392
|
t
|
|
295
393
|
.addColumn("id", idColumn())
|
|
@@ -332,7 +430,7 @@ describe("Cursor utilities", () => {
|
|
|
332
430
|
});
|
|
333
431
|
|
|
334
432
|
it("should handle Date objects in cursors for PostgreSQL", () => {
|
|
335
|
-
const testSchema = schema((s) =>
|
|
433
|
+
const testSchema = schema("test", (s) =>
|
|
336
434
|
s.addTable("posts", (t) =>
|
|
337
435
|
t
|
|
338
436
|
.addColumn("id", idColumn())
|
|
@@ -379,7 +477,7 @@ describe("Cursor utilities", () => {
|
|
|
379
477
|
});
|
|
380
478
|
|
|
381
479
|
it("should handle Date objects in cursors for SQLite", () => {
|
|
382
|
-
const testSchema = schema((s) =>
|
|
480
|
+
const testSchema = schema("test", (s) =>
|
|
383
481
|
s.addTable("posts", (t) =>
|
|
384
482
|
t
|
|
385
483
|
.addColumn("id", idColumn())
|
|
@@ -421,7 +519,7 @@ describe("Cursor utilities", () => {
|
|
|
421
519
|
});
|
|
422
520
|
|
|
423
521
|
it("should handle Date objects in cursors for MySQL", () => {
|
|
424
|
-
const testSchema = schema((s) =>
|
|
522
|
+
const testSchema = schema("test", (s) =>
|
|
425
523
|
s.addTable("posts", (t) =>
|
|
426
524
|
t
|
|
427
525
|
.addColumn("id", idColumn())
|
package/src/query/cursor.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { AnyColumn } from "../schema/create";
|
|
|
2
2
|
import { createSQLSerializer } from "./serialize/create-sql-serializer";
|
|
3
3
|
import { resolveFragnoIdValue } from "./value-encoding";
|
|
4
4
|
import type { DriverConfig } from "../adapters/generic-sql/driver-config";
|
|
5
|
+
import type { SQLiteStorageMode } from "../adapters/generic-sql/sqlite-storage";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Cursor object containing all information needed for pagination
|
|
@@ -56,6 +57,7 @@ export class Cursor {
|
|
|
56
57
|
* Encode cursor to an opaque base64 string (safe to send to client)
|
|
57
58
|
*/
|
|
58
59
|
encode(): string {
|
|
60
|
+
assertSerializableIndexValues(this.#indexValues);
|
|
59
61
|
const data: CursorData = {
|
|
60
62
|
v: 1,
|
|
61
63
|
indexName: this.#indexName,
|
|
@@ -100,7 +102,12 @@ export interface CursorData {
|
|
|
100
102
|
* Encode cursor data to a base64 string (internal)
|
|
101
103
|
*/
|
|
102
104
|
function encodeCursorData(data: CursorData): string {
|
|
103
|
-
|
|
105
|
+
let json: string;
|
|
106
|
+
try {
|
|
107
|
+
json = JSON.stringify(data);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
throw new Error(`Invalid cursor: ${error instanceof Error ? error.message : "malformed data"}`);
|
|
110
|
+
}
|
|
104
111
|
// Use Buffer in Node.js or btoa in browsers
|
|
105
112
|
if (typeof Buffer !== "undefined") {
|
|
106
113
|
return Buffer.from(json, "utf-8").toString("base64");
|
|
@@ -129,32 +136,38 @@ export function decodeCursor(cursor: string): Cursor {
|
|
|
129
136
|
json = atob(cursor);
|
|
130
137
|
}
|
|
131
138
|
const data = JSON.parse(json);
|
|
139
|
+
const record = data as Record<string, unknown>;
|
|
132
140
|
|
|
133
141
|
// Validate structure
|
|
134
142
|
if (
|
|
135
|
-
!data ||
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
typeof
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
(
|
|
143
|
+
!isPlainObject(data) ||
|
|
144
|
+
!isPlainObject(record["indexValues"]) ||
|
|
145
|
+
typeof record["indexName"] !== "string" ||
|
|
146
|
+
record["indexName"].length === 0 ||
|
|
147
|
+
typeof record["orderDirection"] !== "string" ||
|
|
148
|
+
(record["orderDirection"] !== "asc" && record["orderDirection"] !== "desc") ||
|
|
149
|
+
typeof record["pageSize"] !== "number" ||
|
|
150
|
+
!Number.isFinite(record["pageSize"]) ||
|
|
151
|
+
!Number.isInteger(record["pageSize"]) ||
|
|
152
|
+
record["pageSize"] <= 0
|
|
143
153
|
) {
|
|
144
154
|
throw new Error("Invalid cursor structure");
|
|
145
155
|
}
|
|
146
156
|
|
|
147
157
|
// Only support v1
|
|
148
|
-
|
|
158
|
+
if (typeof record["v"] !== "number") {
|
|
159
|
+
throw new Error("Unsupported cursor version: missing. Only v1 is supported.");
|
|
160
|
+
}
|
|
161
|
+
const version = record["v"];
|
|
149
162
|
if (version !== 1) {
|
|
150
163
|
throw new Error(`Unsupported cursor version: ${version}. Only v1 is supported.`);
|
|
151
164
|
}
|
|
152
165
|
|
|
153
166
|
return new Cursor({
|
|
154
|
-
indexName:
|
|
155
|
-
orderDirection:
|
|
156
|
-
pageSize:
|
|
157
|
-
indexValues:
|
|
167
|
+
indexName: record["indexName"],
|
|
168
|
+
orderDirection: record["orderDirection"],
|
|
169
|
+
pageSize: record["pageSize"],
|
|
170
|
+
indexValues: record["indexValues"],
|
|
158
171
|
});
|
|
159
172
|
} catch (error) {
|
|
160
173
|
throw new Error(`Invalid cursor: ${error instanceof Error ? error.message : "malformed data"}`);
|
|
@@ -194,7 +207,11 @@ export function createCursorFromRecord(
|
|
|
194
207
|
const indexValues: Record<string, unknown> = {};
|
|
195
208
|
|
|
196
209
|
for (const col of indexColumns) {
|
|
197
|
-
|
|
210
|
+
const value = record[col.name];
|
|
211
|
+
if (value === undefined) {
|
|
212
|
+
throw new Error(`Record is missing value for index column "${col.name}".`);
|
|
213
|
+
}
|
|
214
|
+
indexValues[col.name] = value;
|
|
198
215
|
}
|
|
199
216
|
|
|
200
217
|
return new Cursor({
|
|
@@ -218,6 +235,7 @@ export function createCursorFromRecord(
|
|
|
218
235
|
* @param cursor - The cursor object
|
|
219
236
|
* @param indexColumns - The columns that make up the index
|
|
220
237
|
* @param driverConfig - The driver configuration
|
|
238
|
+
* @param sqliteStorageMode - Optional SQLite storage mode override
|
|
221
239
|
* @returns Serialized values ready for database queries
|
|
222
240
|
*
|
|
223
241
|
* @example
|
|
@@ -233,23 +251,54 @@ export function serializeCursorValues(
|
|
|
233
251
|
cursor: Cursor,
|
|
234
252
|
indexColumns: AnyColumn[],
|
|
235
253
|
driverConfig: DriverConfig,
|
|
254
|
+
sqliteStorageMode?: SQLiteStorageMode,
|
|
236
255
|
): Record<string, unknown> {
|
|
237
|
-
const serializer = createSQLSerializer(driverConfig);
|
|
256
|
+
const serializer = createSQLSerializer(driverConfig, sqliteStorageMode);
|
|
238
257
|
const serialized: Record<string, unknown> = {};
|
|
258
|
+
const missingColumns: string[] = [];
|
|
239
259
|
|
|
240
260
|
for (const col of indexColumns) {
|
|
241
|
-
const value = cursor.indexValues[col.
|
|
242
|
-
if (value
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const deserialized = serializer.deserialize(value, col);
|
|
246
|
-
// Resolve FragnoId/FragnoReference to primitive values (if present)
|
|
247
|
-
const resolved = resolveFragnoIdValue(deserialized, col);
|
|
248
|
-
// Then serialize to database format
|
|
249
|
-
// (e.g., Date → database driver format)
|
|
250
|
-
serialized[col.ormName] = serializer.serialize(resolved, col);
|
|
261
|
+
const value = cursor.indexValues[col.name];
|
|
262
|
+
if (value === undefined) {
|
|
263
|
+
missingColumns.push(col.name);
|
|
264
|
+
continue;
|
|
251
265
|
}
|
|
266
|
+
// First deserialize from JSON format to application format
|
|
267
|
+
// (e.g., "2025-11-07T09:36:57.959Z" string → Date object)
|
|
268
|
+
const deserialized = serializer.deserialize(value, col);
|
|
269
|
+
// Resolve FragnoId/FragnoReference to primitive values (if present)
|
|
270
|
+
const resolved = resolveFragnoIdValue(deserialized, col);
|
|
271
|
+
// Then serialize to database format
|
|
272
|
+
// (e.g., Date → database driver format)
|
|
273
|
+
serialized[col.name] = serializer.serialize(resolved, col);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (missingColumns.length > 0) {
|
|
277
|
+
const suffix = cursor.indexName ? ` for index "${cursor.indexName}"` : "";
|
|
278
|
+
const columns = missingColumns.map((name) => `"${name}"`).join(", ");
|
|
279
|
+
const plural = missingColumns.length === 1 ? "" : "s";
|
|
280
|
+
throw new Error(`Cursor is missing values for index column${plural} ${columns}${suffix}.`);
|
|
252
281
|
}
|
|
253
282
|
|
|
254
283
|
return serialized;
|
|
255
284
|
}
|
|
285
|
+
|
|
286
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
287
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
288
|
+
|
|
289
|
+
const assertSerializableIndexValues = (values: Record<string, unknown>): void => {
|
|
290
|
+
for (const [key, value] of Object.entries(values)) {
|
|
291
|
+
if (value === undefined) {
|
|
292
|
+
throw new Error(`Cursor index value "${key}" is undefined.`);
|
|
293
|
+
}
|
|
294
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
295
|
+
throw new Error(`Cursor index value "${key}" must be a finite number.`);
|
|
296
|
+
}
|
|
297
|
+
if (typeof value === "bigint") {
|
|
298
|
+
throw new Error(`Cursor index value "${key}" must not be a BigInt.`);
|
|
299
|
+
}
|
|
300
|
+
if (typeof value === "function" || typeof value === "symbol") {
|
|
301
|
+
throw new Error(`Cursor index value "${key}" is not JSON-serializable.`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
};
|
|
@@ -13,7 +13,7 @@ import type { ConditionBuilder } from "./condition-builder";
|
|
|
13
13
|
|
|
14
14
|
describe("query type tests", () => {
|
|
15
15
|
// Create test schema
|
|
16
|
-
const _testSchema = schema((s) => {
|
|
16
|
+
const _testSchema = schema("_test", (s) => {
|
|
17
17
|
return s
|
|
18
18
|
.addTable("users", (t) => {
|
|
19
19
|
return t
|
|
@@ -337,7 +337,7 @@ describe("query type tests", () => {
|
|
|
337
337
|
});
|
|
338
338
|
|
|
339
339
|
describe("join", () => {
|
|
340
|
-
const userSchema = schema((s) => {
|
|
340
|
+
const userSchema = schema("user", (s) => {
|
|
341
341
|
return s
|
|
342
342
|
.addTable("users", (t) => {
|
|
343
343
|
return t.addColumn("id", idColumn()).addColumn("name", column("string"));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DriverConfig } from "../../adapters/generic-sql/driver-config";
|
|
2
|
+
import type { SQLiteStorageMode } from "../../adapters/generic-sql/sqlite-storage";
|
|
2
3
|
import { SQLSerializer } from "./sql-serializer";
|
|
3
4
|
import { SQLiteSerializer } from "./dialect/sqlite-serializer";
|
|
4
5
|
import { PostgreSQLSerializer } from "./dialect/postgres-serializer";
|
|
@@ -14,9 +15,13 @@ export { SQLSerializer } from "./sql-serializer";
|
|
|
14
15
|
* (PostgreSQL, MySQL, or SQLite).
|
|
15
16
|
*
|
|
16
17
|
* @param driverConfig - The driver configuration
|
|
18
|
+
* @param sqliteStorageMode - Optional SQLite storage mode override
|
|
17
19
|
* @returns Dialect-specific SQLSerializer instance
|
|
18
20
|
*/
|
|
19
|
-
export function createSQLSerializer(
|
|
21
|
+
export function createSQLSerializer(
|
|
22
|
+
driverConfig: DriverConfig,
|
|
23
|
+
sqliteStorageMode?: SQLiteStorageMode,
|
|
24
|
+
): SQLSerializer {
|
|
20
25
|
// TODO: The serializers are pretty lenient in what they accept (lost of typeof checks), it may
|
|
21
26
|
// be beneficial to implement serializers per DriverConfig, and be less lenient.
|
|
22
27
|
switch (driverConfig.databaseType) {
|
|
@@ -25,7 +30,7 @@ export function createSQLSerializer(driverConfig: DriverConfig): SQLSerializer {
|
|
|
25
30
|
case "mysql":
|
|
26
31
|
return new MySQLSerializer(driverConfig);
|
|
27
32
|
case "sqlite":
|
|
28
|
-
return new SQLiteSerializer(driverConfig);
|
|
33
|
+
return new SQLiteSerializer(driverConfig, sqliteStorageMode);
|
|
29
34
|
default: {
|
|
30
35
|
const exhaustiveCheck: never = driverConfig.databaseType;
|
|
31
36
|
throw new Error(`Unsupported database type: ${exhaustiveCheck}`);
|