@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,51 @@
|
|
|
1
|
+
import { schema, idColumn, column } from "../schema/create";
|
|
2
|
+
|
|
3
|
+
// Constants for Fragno's internal settings table
|
|
4
|
+
export const SETTINGS_TABLE_NAME = "fragno_db_settings" as const;
|
|
5
|
+
// FIXME: In some places we simply use empty string "" as namespace, which is not correct.
|
|
6
|
+
export const SETTINGS_NAMESPACE = "fragno-db-settings" as const;
|
|
7
|
+
|
|
8
|
+
export const internalSchema = schema("fragno_internal", (s) => {
|
|
9
|
+
return s
|
|
10
|
+
.addTable(SETTINGS_TABLE_NAME, (t) => {
|
|
11
|
+
return t
|
|
12
|
+
.addColumn("id", idColumn())
|
|
13
|
+
.addColumn("key", column("string"))
|
|
14
|
+
.addColumn("value", column("string"))
|
|
15
|
+
.createIndex("unique_key", ["key"], { unique: true });
|
|
16
|
+
})
|
|
17
|
+
.addTable("fragno_hooks", (t) => {
|
|
18
|
+
return t
|
|
19
|
+
.addColumn("id", idColumn())
|
|
20
|
+
.addColumn("namespace", column("string"))
|
|
21
|
+
.addColumn("hookName", column("string"))
|
|
22
|
+
.addColumn("payload", column("json"))
|
|
23
|
+
.addColumn("status", column("string")) // "pending" | "processing" | "completed" | "failed"
|
|
24
|
+
.addColumn("attempts", column("integer").defaultTo(0))
|
|
25
|
+
.addColumn("maxAttempts", column("integer").defaultTo(5))
|
|
26
|
+
.addColumn("lastAttemptAt", column("timestamp").nullable())
|
|
27
|
+
.addColumn("nextRetryAt", column("timestamp").nullable())
|
|
28
|
+
.addColumn("error", column("string").nullable())
|
|
29
|
+
.addColumn(
|
|
30
|
+
"createdAt",
|
|
31
|
+
column("timestamp").defaultTo((b) => b.now()),
|
|
32
|
+
)
|
|
33
|
+
.addColumn("nonce", column("string"))
|
|
34
|
+
.createIndex("idx_namespace_status_retry", ["namespace", "status", "nextRetryAt"])
|
|
35
|
+
.createIndex("idx_nonce", ["nonce"]);
|
|
36
|
+
})
|
|
37
|
+
.addTable("fragno_db_outbox", (t) => {
|
|
38
|
+
return t
|
|
39
|
+
.addColumn("id", idColumn())
|
|
40
|
+
.addColumn("versionstamp", column("string"))
|
|
41
|
+
.addColumn("uowId", column("string"))
|
|
42
|
+
.addColumn("payload", column("json"))
|
|
43
|
+
.addColumn("refMap", column("json").nullable())
|
|
44
|
+
.addColumn(
|
|
45
|
+
"createdAt",
|
|
46
|
+
column("timestamp").defaultTo((b) => b.now()),
|
|
47
|
+
)
|
|
48
|
+
.createIndex("idx_outbox_versionstamp", ["versionstamp"], { unique: true })
|
|
49
|
+
.createIndex("idx_outbox_uow", ["uowId"]);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -2,16 +2,22 @@ import SQLite from "better-sqlite3";
|
|
|
2
2
|
import { SqliteDialect } from "kysely";
|
|
3
3
|
import { beforeAll, describe, expect, it } from "vitest";
|
|
4
4
|
import { instantiate } from "@fragno-dev/core";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
internalFragmentDef,
|
|
7
|
+
internalSchema,
|
|
8
|
+
SETTINGS_NAMESPACE,
|
|
9
|
+
getSchemaVersionFromDatabase,
|
|
10
|
+
} from "./internal-fragment";
|
|
6
11
|
import type { FragnoPublicConfigWithDatabase } from "../db-fragment-definition-builder";
|
|
7
|
-
import {
|
|
12
|
+
import { SqlAdapter } from "../adapters/generic-sql/generic-sql-adapter";
|
|
8
13
|
import { BetterSQLite3DriverConfig } from "../adapters/generic-sql/driver-config";
|
|
9
14
|
import { ExponentialBackoffRetryPolicy, NoRetryPolicy } from "../query/unit-of-work/retry-policy";
|
|
15
|
+
import { ConcurrencyConflictError } from "../query/unit-of-work/execute-unit-of-work";
|
|
10
16
|
import type { FragnoId } from "../schema/create";
|
|
11
17
|
|
|
12
18
|
describe("Internal Fragment", () => {
|
|
13
19
|
let sqliteDatabase: SQLite.Database;
|
|
14
|
-
let adapter:
|
|
20
|
+
let adapter: SqlAdapter;
|
|
15
21
|
let fragment: ReturnType<typeof instantiateFragment>;
|
|
16
22
|
|
|
17
23
|
function instantiateFragment(options: FragnoPublicConfigWithDatabase) {
|
|
@@ -25,19 +31,20 @@ describe("Internal Fragment", () => {
|
|
|
25
31
|
database: sqliteDatabase,
|
|
26
32
|
});
|
|
27
33
|
|
|
28
|
-
adapter = new
|
|
34
|
+
adapter = new SqlAdapter({
|
|
29
35
|
dialect,
|
|
30
36
|
driverConfig: new BetterSQLite3DriverConfig(),
|
|
31
37
|
});
|
|
32
38
|
|
|
33
39
|
{
|
|
34
|
-
const migrations = adapter.prepareMigrations(internalSchema,
|
|
40
|
+
const migrations = adapter.prepareMigrations(internalSchema, null);
|
|
35
41
|
await migrations.executeWithDriver(adapter.driver, 0);
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
// Instantiate fragment with shared database adapter
|
|
39
45
|
const options: FragnoPublicConfigWithDatabase = {
|
|
40
46
|
databaseAdapter: adapter,
|
|
47
|
+
databaseNamespace: null,
|
|
41
48
|
};
|
|
42
49
|
|
|
43
50
|
fragment = instantiateFragment(options);
|
|
@@ -151,7 +158,7 @@ describe("Internal Fragment", () => {
|
|
|
151
158
|
|
|
152
159
|
describe("Hook Service", () => {
|
|
153
160
|
let sqliteDatabase: SQLite.Database;
|
|
154
|
-
let adapter:
|
|
161
|
+
let adapter: SqlAdapter;
|
|
155
162
|
let fragment: ReturnType<typeof instantiateFragment>;
|
|
156
163
|
|
|
157
164
|
function instantiateFragment(options: FragnoPublicConfigWithDatabase) {
|
|
@@ -165,18 +172,19 @@ describe("Hook Service", () => {
|
|
|
165
172
|
database: sqliteDatabase,
|
|
166
173
|
});
|
|
167
174
|
|
|
168
|
-
adapter = new
|
|
175
|
+
adapter = new SqlAdapter({
|
|
169
176
|
dialect,
|
|
170
177
|
driverConfig: new BetterSQLite3DriverConfig(),
|
|
171
178
|
});
|
|
172
179
|
|
|
173
180
|
{
|
|
174
|
-
const migrations = adapter.prepareMigrations(internalSchema,
|
|
181
|
+
const migrations = adapter.prepareMigrations(internalSchema, null);
|
|
175
182
|
await migrations.executeWithDriver(adapter.driver, 0);
|
|
176
183
|
}
|
|
177
184
|
|
|
178
185
|
const options: FragnoPublicConfigWithDatabase = {
|
|
179
186
|
databaseAdapter: adapter,
|
|
187
|
+
databaseNamespace: null,
|
|
180
188
|
};
|
|
181
189
|
|
|
182
190
|
fragment = instantiateFragment(options);
|
|
@@ -282,6 +290,51 @@ describe("Hook Service", () => {
|
|
|
282
290
|
expect(result?.lastAttemptAt).toBeInstanceOf(Date);
|
|
283
291
|
});
|
|
284
292
|
|
|
293
|
+
it("should reject marking completed with a stale id", async () => {
|
|
294
|
+
const namespace = "complete-stale";
|
|
295
|
+
const nonce = "test-nonce-complete-stale";
|
|
296
|
+
let staleId!: FragnoId;
|
|
297
|
+
|
|
298
|
+
await fragment.inContext(async function () {
|
|
299
|
+
const createdId = await this.handlerTx()
|
|
300
|
+
.mutate(({ forSchema }) => {
|
|
301
|
+
const uow = forSchema(internalSchema);
|
|
302
|
+
return uow.create("fragno_hooks", {
|
|
303
|
+
namespace,
|
|
304
|
+
hookName: "onCompleteStale",
|
|
305
|
+
payload: { test: "data" },
|
|
306
|
+
status: "pending",
|
|
307
|
+
attempts: 0,
|
|
308
|
+
maxAttempts: 5,
|
|
309
|
+
lastAttemptAt: null,
|
|
310
|
+
nextRetryAt: null,
|
|
311
|
+
error: null,
|
|
312
|
+
nonce,
|
|
313
|
+
});
|
|
314
|
+
})
|
|
315
|
+
.execute();
|
|
316
|
+
staleId = createdId;
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
await fragment.inContext(async function () {
|
|
320
|
+
await this.handlerTx()
|
|
321
|
+
.withServiceCalls(
|
|
322
|
+
() => [fragment.services.hookService.claimPendingHookEvents(namespace)] as const,
|
|
323
|
+
)
|
|
324
|
+
.execute();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
await expect(
|
|
328
|
+
fragment.inContext(async function () {
|
|
329
|
+
await this.handlerTx()
|
|
330
|
+
.withServiceCalls(
|
|
331
|
+
() => [fragment.services.hookService.markHookCompleted(staleId)] as const,
|
|
332
|
+
)
|
|
333
|
+
.execute();
|
|
334
|
+
}),
|
|
335
|
+
).rejects.toThrow(ConcurrencyConflictError);
|
|
336
|
+
});
|
|
337
|
+
|
|
285
338
|
it("should mark a hook event as processing", async () => {
|
|
286
339
|
const nonce = "test-nonce-3";
|
|
287
340
|
let eventId: FragnoId;
|
|
@@ -477,6 +530,49 @@ describe("Hook Service", () => {
|
|
|
477
530
|
expect(staleEvent?.attempts).toBe(1);
|
|
478
531
|
});
|
|
479
532
|
|
|
533
|
+
it("should detect conflicts when requeueing after another update in the same transaction", async () => {
|
|
534
|
+
const namespace = "requeue-conflict";
|
|
535
|
+
const nonce = "test-nonce-requeue-conflict";
|
|
536
|
+
let eventId!: FragnoId;
|
|
537
|
+
|
|
538
|
+
const staleBefore = new Date(Date.now() - 60_000);
|
|
539
|
+
const lastAttemptAt = new Date(Date.now() - 120_000);
|
|
540
|
+
|
|
541
|
+
await fragment.inContext(async function () {
|
|
542
|
+
eventId = await this.handlerTx()
|
|
543
|
+
.mutate(({ forSchema }) => {
|
|
544
|
+
const uow = forSchema(internalSchema);
|
|
545
|
+
return uow.create("fragno_hooks", {
|
|
546
|
+
namespace,
|
|
547
|
+
hookName: "onRequeueConflict",
|
|
548
|
+
payload: { test: "requeue" },
|
|
549
|
+
status: "processing",
|
|
550
|
+
attempts: 0,
|
|
551
|
+
maxAttempts: 5,
|
|
552
|
+
lastAttemptAt,
|
|
553
|
+
nextRetryAt: null,
|
|
554
|
+
error: null,
|
|
555
|
+
nonce,
|
|
556
|
+
});
|
|
557
|
+
})
|
|
558
|
+
.execute();
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
await expect(
|
|
562
|
+
fragment.inContext(async function () {
|
|
563
|
+
await this.handlerTx({ retryPolicy: new NoRetryPolicy() })
|
|
564
|
+
.withServiceCalls(
|
|
565
|
+
() =>
|
|
566
|
+
[
|
|
567
|
+
fragment.services.hookService.markHookProcessing(eventId),
|
|
568
|
+
fragment.services.hookService.requeueStuckProcessingHooks(namespace, staleBefore),
|
|
569
|
+
] as const,
|
|
570
|
+
)
|
|
571
|
+
.execute();
|
|
572
|
+
}),
|
|
573
|
+
).rejects.toThrow(ConcurrencyConflictError);
|
|
574
|
+
});
|
|
575
|
+
|
|
480
576
|
it("should not retrieve events from different namespace", async () => {
|
|
481
577
|
const nonce = "test-nonce-7";
|
|
482
578
|
|
|
@@ -552,4 +648,358 @@ describe("Hook Service", () => {
|
|
|
552
648
|
const futureEvent = events.find((e) => e.id.externalId === eventId.externalId);
|
|
553
649
|
expect(futureEvent).toBeUndefined();
|
|
554
650
|
});
|
|
651
|
+
|
|
652
|
+
it("should claim only ready pending events and mark them processing", async () => {
|
|
653
|
+
const namespace = "claim-ready";
|
|
654
|
+
const pastTime = new Date(Date.now() - 10000);
|
|
655
|
+
const futureTime = new Date(Date.now() + 60000);
|
|
656
|
+
|
|
657
|
+
let nullRetryId!: FragnoId;
|
|
658
|
+
let pastRetryId!: FragnoId;
|
|
659
|
+
let futureRetryId!: FragnoId;
|
|
660
|
+
|
|
661
|
+
await fragment.inContext(async function () {
|
|
662
|
+
await this.handlerTx()
|
|
663
|
+
.mutate(({ forSchema }) => {
|
|
664
|
+
const uow = forSchema(internalSchema);
|
|
665
|
+
nullRetryId = uow.create("fragno_hooks", {
|
|
666
|
+
namespace,
|
|
667
|
+
hookName: "onNullRetry",
|
|
668
|
+
payload: { test: "null" },
|
|
669
|
+
status: "pending",
|
|
670
|
+
attempts: 0,
|
|
671
|
+
maxAttempts: 5,
|
|
672
|
+
lastAttemptAt: null,
|
|
673
|
+
nextRetryAt: null,
|
|
674
|
+
error: null,
|
|
675
|
+
nonce: "test-nonce-claim-null",
|
|
676
|
+
});
|
|
677
|
+
pastRetryId = uow.create("fragno_hooks", {
|
|
678
|
+
namespace,
|
|
679
|
+
hookName: "onPastRetry",
|
|
680
|
+
payload: { test: "past" },
|
|
681
|
+
status: "pending",
|
|
682
|
+
attempts: 1,
|
|
683
|
+
maxAttempts: 5,
|
|
684
|
+
lastAttemptAt: pastTime,
|
|
685
|
+
nextRetryAt: pastTime,
|
|
686
|
+
error: "Previous error",
|
|
687
|
+
nonce: "test-nonce-claim-past",
|
|
688
|
+
});
|
|
689
|
+
futureRetryId = uow.create("fragno_hooks", {
|
|
690
|
+
namespace,
|
|
691
|
+
hookName: "onFutureRetry",
|
|
692
|
+
payload: { test: "future" },
|
|
693
|
+
status: "pending",
|
|
694
|
+
attempts: 1,
|
|
695
|
+
maxAttempts: 5,
|
|
696
|
+
lastAttemptAt: new Date(),
|
|
697
|
+
nextRetryAt: futureTime,
|
|
698
|
+
error: "Previous error",
|
|
699
|
+
nonce: "test-nonce-claim-future",
|
|
700
|
+
});
|
|
701
|
+
})
|
|
702
|
+
.execute();
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
const claimed = await fragment.inContext(async function () {
|
|
706
|
+
return await this.handlerTx()
|
|
707
|
+
.withServiceCalls(
|
|
708
|
+
() => [fragment.services.hookService.claimPendingHookEvents(namespace)] as const,
|
|
709
|
+
)
|
|
710
|
+
.transform(({ serviceResult: [result] }) => result)
|
|
711
|
+
.execute();
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
expect(claimed).toHaveLength(2);
|
|
715
|
+
const claimedIds = new Set(claimed.map((event) => event.id.externalId));
|
|
716
|
+
expect(claimedIds.has(nullRetryId.externalId)).toBe(true);
|
|
717
|
+
expect(claimedIds.has(pastRetryId.externalId)).toBe(true);
|
|
718
|
+
expect(claimedIds.has(futureRetryId.externalId)).toBe(false);
|
|
719
|
+
|
|
720
|
+
const [nullEvent, pastEvent, futureEvent] = await fragment.inContext(async function () {
|
|
721
|
+
return await this.handlerTx()
|
|
722
|
+
.withServiceCalls(
|
|
723
|
+
() =>
|
|
724
|
+
[
|
|
725
|
+
fragment.services.hookService.getHookById(nullRetryId),
|
|
726
|
+
fragment.services.hookService.getHookById(pastRetryId),
|
|
727
|
+
fragment.services.hookService.getHookById(futureRetryId),
|
|
728
|
+
] as const,
|
|
729
|
+
)
|
|
730
|
+
.transform(({ serviceResult: [nullResult, pastResult, futureResult] }) => [
|
|
731
|
+
nullResult,
|
|
732
|
+
pastResult,
|
|
733
|
+
futureResult,
|
|
734
|
+
])
|
|
735
|
+
.execute();
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
expect(nullEvent?.status).toBe("processing");
|
|
739
|
+
expect(nullEvent?.lastAttemptAt).toBeInstanceOf(Date);
|
|
740
|
+
expect(pastEvent?.status).toBe("processing");
|
|
741
|
+
expect(pastEvent?.lastAttemptAt).toBeInstanceOf(Date);
|
|
742
|
+
expect(futureEvent?.status).toBe("pending");
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
it("should return claimed ids with incremented versions", async () => {
|
|
746
|
+
const namespace = "claim-version";
|
|
747
|
+
const nonce = "test-nonce-claim-version";
|
|
748
|
+
let createdId!: FragnoId;
|
|
749
|
+
|
|
750
|
+
await fragment.inContext(async function () {
|
|
751
|
+
createdId = await this.handlerTx()
|
|
752
|
+
.mutate(({ forSchema }) => {
|
|
753
|
+
const uow = forSchema(internalSchema);
|
|
754
|
+
return uow.create("fragno_hooks", {
|
|
755
|
+
namespace,
|
|
756
|
+
hookName: "onClaimVersion",
|
|
757
|
+
payload: { test: "version" },
|
|
758
|
+
status: "pending",
|
|
759
|
+
attempts: 0,
|
|
760
|
+
maxAttempts: 5,
|
|
761
|
+
lastAttemptAt: null,
|
|
762
|
+
nextRetryAt: null,
|
|
763
|
+
error: null,
|
|
764
|
+
nonce,
|
|
765
|
+
});
|
|
766
|
+
})
|
|
767
|
+
.execute();
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
const claimed = await fragment.inContext(async function () {
|
|
771
|
+
return await this.handlerTx()
|
|
772
|
+
.withServiceCalls(
|
|
773
|
+
() => [fragment.services.hookService.claimPendingHookEvents(namespace)] as const,
|
|
774
|
+
)
|
|
775
|
+
.transform(({ serviceResult: [result] }) => result)
|
|
776
|
+
.execute();
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
expect(claimed).toHaveLength(1);
|
|
780
|
+
expect(claimed[0]?.id.externalId).toBe(createdId.externalId);
|
|
781
|
+
expect(claimed[0]?.id.version).toBe(createdId.version + 1);
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
it("should return now when pending hooks have no nextRetryAt", async () => {
|
|
785
|
+
const namespace = "wake-now";
|
|
786
|
+
|
|
787
|
+
await fragment.inContext(async function () {
|
|
788
|
+
await this.handlerTx()
|
|
789
|
+
.mutate(({ forSchema }) => {
|
|
790
|
+
const uow = forSchema(internalSchema);
|
|
791
|
+
uow.create("fragno_hooks", {
|
|
792
|
+
namespace,
|
|
793
|
+
hookName: "onImmediate",
|
|
794
|
+
payload: { test: "now" },
|
|
795
|
+
status: "pending",
|
|
796
|
+
attempts: 0,
|
|
797
|
+
maxAttempts: 5,
|
|
798
|
+
lastAttemptAt: null,
|
|
799
|
+
nextRetryAt: null,
|
|
800
|
+
error: null,
|
|
801
|
+
nonce: "test-nonce-now",
|
|
802
|
+
});
|
|
803
|
+
})
|
|
804
|
+
.execute();
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
const wakeAt = await fragment.inContext(async function () {
|
|
808
|
+
return await this.handlerTx()
|
|
809
|
+
.withServiceCalls(
|
|
810
|
+
() => [fragment.services.hookService.getNextHookWakeAt(namespace)] as const,
|
|
811
|
+
)
|
|
812
|
+
.transform(({ serviceResult: [result] }) => result)
|
|
813
|
+
.execute();
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
expect(wakeAt).toBeInstanceOf(Date);
|
|
817
|
+
expect(Math.abs((wakeAt as Date).getTime() - Date.now())).toBeLessThan(5000);
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
it("should return earliest scheduled hook time", async () => {
|
|
821
|
+
const namespace = "wake-future";
|
|
822
|
+
const soon = new Date(Date.now() + 10000);
|
|
823
|
+
const later = new Date(Date.now() + 60000);
|
|
824
|
+
|
|
825
|
+
await fragment.inContext(async function () {
|
|
826
|
+
await this.handlerTx()
|
|
827
|
+
.mutate(({ forSchema }) => {
|
|
828
|
+
const uow = forSchema(internalSchema);
|
|
829
|
+
uow.create("fragno_hooks", {
|
|
830
|
+
namespace,
|
|
831
|
+
hookName: "onSoon",
|
|
832
|
+
payload: { test: "soon" },
|
|
833
|
+
status: "pending",
|
|
834
|
+
attempts: 0,
|
|
835
|
+
maxAttempts: 5,
|
|
836
|
+
lastAttemptAt: null,
|
|
837
|
+
nextRetryAt: soon,
|
|
838
|
+
error: null,
|
|
839
|
+
nonce: "test-nonce-soon",
|
|
840
|
+
});
|
|
841
|
+
uow.create("fragno_hooks", {
|
|
842
|
+
namespace,
|
|
843
|
+
hookName: "onLater",
|
|
844
|
+
payload: { test: "later" },
|
|
845
|
+
status: "pending",
|
|
846
|
+
attempts: 0,
|
|
847
|
+
maxAttempts: 5,
|
|
848
|
+
lastAttemptAt: null,
|
|
849
|
+
nextRetryAt: later,
|
|
850
|
+
error: null,
|
|
851
|
+
nonce: "test-nonce-later",
|
|
852
|
+
});
|
|
853
|
+
})
|
|
854
|
+
.execute();
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
const wakeAt = await fragment.inContext(async function () {
|
|
858
|
+
return await this.handlerTx()
|
|
859
|
+
.withServiceCalls(
|
|
860
|
+
() => [fragment.services.hookService.getNextHookWakeAt(namespace)] as const,
|
|
861
|
+
)
|
|
862
|
+
.transform(({ serviceResult: [result] }) => result)
|
|
863
|
+
.execute();
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
expect(wakeAt).toEqual(soon);
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
it("should return null when no pending hooks exist", async () => {
|
|
870
|
+
const namespace = "wake-none";
|
|
871
|
+
const wakeAt = await fragment.inContext(async function () {
|
|
872
|
+
return await this.handlerTx()
|
|
873
|
+
.withServiceCalls(
|
|
874
|
+
() => [fragment.services.hookService.getNextHookWakeAt(namespace)] as const,
|
|
875
|
+
)
|
|
876
|
+
.transform(({ serviceResult: [result] }) => result)
|
|
877
|
+
.execute();
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
expect(wakeAt).toBeNull();
|
|
881
|
+
});
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
describe("getSchemaVersionFromDatabase", () => {
|
|
885
|
+
function createTestSetup() {
|
|
886
|
+
const sqliteDatabase = new SQLite(":memory:");
|
|
887
|
+
const dialect = new SqliteDialect({ database: sqliteDatabase });
|
|
888
|
+
const adapter = new SqlAdapter({
|
|
889
|
+
dialect,
|
|
890
|
+
driverConfig: new BetterSQLite3DriverConfig(),
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
function instantiateFragment(options: FragnoPublicConfigWithDatabase) {
|
|
894
|
+
return instantiate(internalFragmentDef).withConfig({}).withOptions(options).build();
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
return { sqliteDatabase, adapter, instantiateFragment };
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
async function setupAndMigrate() {
|
|
901
|
+
const { sqliteDatabase, adapter, instantiateFragment } = createTestSetup();
|
|
902
|
+
// Create tables without writing a version record, so tests control version state
|
|
903
|
+
const migrations = adapter.prepareMigrations(internalSchema, "");
|
|
904
|
+
await migrations.executeWithDriver(adapter.driver, 0, undefined, {
|
|
905
|
+
updateVersionInMigration: false,
|
|
906
|
+
});
|
|
907
|
+
const fragment = instantiateFragment({
|
|
908
|
+
databaseAdapter: adapter,
|
|
909
|
+
databaseNamespace: null,
|
|
910
|
+
});
|
|
911
|
+
return { sqliteDatabase, adapter, fragment };
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
it("should return 0 when no version exists", async () => {
|
|
915
|
+
const { fragment } = await setupAndMigrate();
|
|
916
|
+
|
|
917
|
+
const version = await getSchemaVersionFromDatabase(fragment, "nonexistent");
|
|
918
|
+
expect(version).toBe(0);
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
it("should find version stored under empty-string namespace", async () => {
|
|
922
|
+
const { fragment } = await setupAndMigrate();
|
|
923
|
+
|
|
924
|
+
// Write version under empty-string namespace (key = ".schema_version")
|
|
925
|
+
await fragment.inContext(async function () {
|
|
926
|
+
await this.handlerTx()
|
|
927
|
+
.withServiceCalls(() => [fragment.services.settingsService.set("", "schema_version", "5")])
|
|
928
|
+
.execute();
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
const version = await getSchemaVersionFromDatabase(fragment, "");
|
|
932
|
+
expect(version).toBe(5);
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
it("should find version via back-compat when stored under internalSchema.name but read with empty string", async () => {
|
|
936
|
+
const { fragment } = await setupAndMigrate();
|
|
937
|
+
|
|
938
|
+
// Write version under "fragno_internal" namespace (legacy key from buggy code)
|
|
939
|
+
await fragment.inContext(async function () {
|
|
940
|
+
await this.handlerTx()
|
|
941
|
+
.withServiceCalls(() => [
|
|
942
|
+
fragment.services.settingsService.set(internalSchema.name, "schema_version", "3"),
|
|
943
|
+
])
|
|
944
|
+
.execute();
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
// Reading with "" should find it via back-compat fallback
|
|
948
|
+
const version = await getSchemaVersionFromDatabase(fragment, "");
|
|
949
|
+
expect(version).toBe(3);
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
it("should find version via back-compat when stored under empty string but read with internalSchema.name", async () => {
|
|
953
|
+
const { fragment } = await setupAndMigrate();
|
|
954
|
+
|
|
955
|
+
// Write version under empty-string namespace
|
|
956
|
+
await fragment.inContext(async function () {
|
|
957
|
+
await this.handlerTx()
|
|
958
|
+
.withServiceCalls(() => [fragment.services.settingsService.set("", "schema_version", "7")])
|
|
959
|
+
.execute();
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
// Reading with internalSchema.name should find it via back-compat fallback
|
|
963
|
+
const version = await getSchemaVersionFromDatabase(fragment, internalSchema.name);
|
|
964
|
+
expect(version).toBe(7);
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
it("should prefer primary namespace over back-compat fallback", async () => {
|
|
968
|
+
const { fragment } = await setupAndMigrate();
|
|
969
|
+
|
|
970
|
+
// Write version under BOTH namespaces with different values
|
|
971
|
+
await fragment.inContext(async function () {
|
|
972
|
+
await this.handlerTx()
|
|
973
|
+
.withServiceCalls(() => [
|
|
974
|
+
fragment.services.settingsService.set("", "schema_version", "10"),
|
|
975
|
+
fragment.services.settingsService.set(internalSchema.name, "schema_version", "20"),
|
|
976
|
+
])
|
|
977
|
+
.execute();
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
// Reading with "" should find 10 (primary), not 20 (back-compat)
|
|
981
|
+
const versionEmpty = await getSchemaVersionFromDatabase(fragment, "");
|
|
982
|
+
expect(versionEmpty).toBe(10);
|
|
983
|
+
|
|
984
|
+
// Reading with internalSchema.name should find 20 (primary), not 10 (back-compat)
|
|
985
|
+
const versionNamed = await getSchemaVersionFromDatabase(fragment, internalSchema.name);
|
|
986
|
+
expect(versionNamed).toBe(20);
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
it("should not use back-compat for non-internal namespaces", async () => {
|
|
990
|
+
const { fragment } = await setupAndMigrate();
|
|
991
|
+
|
|
992
|
+
// Write version under "some-fragment"
|
|
993
|
+
await fragment.inContext(async function () {
|
|
994
|
+
await this.handlerTx()
|
|
995
|
+
.withServiceCalls(() => [
|
|
996
|
+
fragment.services.settingsService.set("some-fragment", "schema_version", "4"),
|
|
997
|
+
])
|
|
998
|
+
.execute();
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
// Reading with a different non-internal namespace should NOT find it
|
|
1002
|
+
const version = await getSchemaVersionFromDatabase(fragment, "other-fragment");
|
|
1003
|
+
expect(version).toBe(0);
|
|
1004
|
+
});
|
|
555
1005
|
});
|