@fragno-dev/db 0.2.1 → 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 +206 -140
- package/CHANGELOG.md +67 -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 +38 -28
- 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 +45 -96
- package/dist/db-fragment-definition-builder.d.ts.map +1 -1
- package/dist/db-fragment-definition-builder.js +121 -99
- 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 +172 -9
- package/dist/fragments/internal-fragment.d.ts.map +1 -1
- package/dist/fragments/internal-fragment.js +193 -74
- 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 +47 -4
- package/dist/hooks/hooks.d.ts.map +1 -1
- package/dist/hooks/hooks.js +106 -39
- 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 +17 -10
- 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 +351 -100
- 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 +440 -267
- 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 +67 -22
- package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work/unit-of-work.js +110 -13
- package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
- package/dist/query/value-decoding.js +8 -5
- 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 +40 -14
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +82 -42
- package/dist/schema/create.js.map +1 -1
- package/dist/schema/generate-id.d.ts +20 -0
- package/dist/schema/generate-id.d.ts.map +1 -0
- package/dist/schema/generate-id.js +28 -0
- package/dist/schema/generate-id.js.map +1 -0
- 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} +49 -35
- package/src/adapters/{drizzle/drizzle-adapter-sqlite3.test.ts → generic-sql/sql-adapter-sqlite3-uow.test.ts} +48 -32
- 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 +88 -54
- package/src/db-fragment-definition-builder.ts +201 -322
- package/src/db-fragment-instantiator.test.ts +169 -101
- package/src/db-fragment-integration.test.ts +301 -149
- 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 +730 -274
- package/src/fragments/internal-fragment.ts +447 -154
- 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 +411 -259
- package/src/hooks/hooks.ts +265 -66
- 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 +78 -30
- 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 +1512 -1458
- package/src/query/unit-of-work/execute-unit-of-work.ts +1708 -596
- package/src/query/unit-of-work/tx-builder.test.ts +1041 -0
- package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +32 -32
- 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 +231 -36
- package/src/query/unit-of-work/unit-of-work.ts +229 -31
- 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 +187 -47
- package/src/schema/generate-id.test.ts +57 -0
- package/src/schema/generate-id.ts +38 -0
- 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
- package/src/shared/config.ts +0 -10
- package/src/shared/connection-pool.ts +0 -24
- package/src/shared/prisma.ts +0 -45
|
@@ -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);
|
|
@@ -49,11 +56,12 @@ describe("Internal Fragment", () => {
|
|
|
49
56
|
|
|
50
57
|
it("should get undefined for non-existent key", async () => {
|
|
51
58
|
const result = await fragment.inContext(async function () {
|
|
52
|
-
return await this.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
return await this.handlerTx()
|
|
60
|
+
.withServiceCalls(
|
|
61
|
+
() => [fragment.services.settingsService.get(SETTINGS_NAMESPACE, "test-key")] as const,
|
|
62
|
+
)
|
|
63
|
+
.transform(({ serviceResult: [value] }) => value)
|
|
64
|
+
.execute();
|
|
57
65
|
});
|
|
58
66
|
|
|
59
67
|
expect(result).toBeUndefined();
|
|
@@ -61,23 +69,20 @@ describe("Internal Fragment", () => {
|
|
|
61
69
|
|
|
62
70
|
it("should set and get a value", async () => {
|
|
63
71
|
await fragment.inContext(async function () {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
SETTINGS_NAMESPACE,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
);
|
|
70
|
-
await executeMutate();
|
|
71
|
-
await setPromise;
|
|
72
|
-
});
|
|
72
|
+
await this.handlerTx()
|
|
73
|
+
.withServiceCalls(() => [
|
|
74
|
+
fragment.services.settingsService.set(SETTINGS_NAMESPACE, "test-key", "test-value"),
|
|
75
|
+
])
|
|
76
|
+
.execute();
|
|
73
77
|
});
|
|
74
78
|
|
|
75
79
|
const result = await fragment.inContext(async function () {
|
|
76
|
-
return await this.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
return await this.handlerTx()
|
|
81
|
+
.withServiceCalls(
|
|
82
|
+
() => [fragment.services.settingsService.get(SETTINGS_NAMESPACE, "test-key")] as const,
|
|
83
|
+
)
|
|
84
|
+
.transform(({ serviceResult: [value] }) => value)
|
|
85
|
+
.execute();
|
|
81
86
|
});
|
|
82
87
|
|
|
83
88
|
expect(result).toMatchObject({
|
|
@@ -88,23 +93,27 @@ describe("Internal Fragment", () => {
|
|
|
88
93
|
|
|
89
94
|
it("should update an existing value", async () => {
|
|
90
95
|
await fragment.inContext(async function () {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
await this.handlerTx()
|
|
97
|
+
.withServiceCalls(
|
|
98
|
+
() =>
|
|
99
|
+
[
|
|
100
|
+
fragment.services.settingsService.set(
|
|
101
|
+
SETTINGS_NAMESPACE,
|
|
102
|
+
"test-key",
|
|
103
|
+
"updated-value",
|
|
104
|
+
),
|
|
105
|
+
] as const,
|
|
106
|
+
)
|
|
107
|
+
.execute();
|
|
100
108
|
});
|
|
101
109
|
|
|
102
110
|
const result = await fragment.inContext(async function () {
|
|
103
|
-
return await this.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
return await this.handlerTx()
|
|
112
|
+
.withServiceCalls(
|
|
113
|
+
() => [fragment.services.settingsService.get(SETTINGS_NAMESPACE, "test-key")] as const,
|
|
114
|
+
)
|
|
115
|
+
.transform(({ serviceResult: [value] }) => value)
|
|
116
|
+
.execute();
|
|
108
117
|
});
|
|
109
118
|
|
|
110
119
|
expect(result).toMatchObject({
|
|
@@ -116,31 +125,31 @@ describe("Internal Fragment", () => {
|
|
|
116
125
|
it("should delete a value", async () => {
|
|
117
126
|
// First get the ID
|
|
118
127
|
const setting = await fragment.inContext(async function () {
|
|
119
|
-
return await this.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
128
|
+
return await this.handlerTx()
|
|
129
|
+
.withServiceCalls(
|
|
130
|
+
() => [fragment.services.settingsService.get(SETTINGS_NAMESPACE, "test-key")] as const,
|
|
131
|
+
)
|
|
132
|
+
.transform(({ serviceResult: [value] }) => value)
|
|
133
|
+
.execute();
|
|
124
134
|
});
|
|
125
135
|
|
|
126
136
|
expect(setting).toBeDefined();
|
|
127
137
|
|
|
128
138
|
// Delete it
|
|
129
139
|
await fragment.inContext(async function () {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
await deletePromise;
|
|
134
|
-
});
|
|
140
|
+
await this.handlerTx()
|
|
141
|
+
.withServiceCalls(() => [fragment.services.settingsService.delete(setting!.id)] as const)
|
|
142
|
+
.execute();
|
|
135
143
|
});
|
|
136
144
|
|
|
137
145
|
// Verify it's gone
|
|
138
146
|
const result = await fragment.inContext(async function () {
|
|
139
|
-
return await this.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
return await this.handlerTx()
|
|
148
|
+
.withServiceCalls(
|
|
149
|
+
() => [fragment.services.settingsService.get(SETTINGS_NAMESPACE, "test-key")] as const,
|
|
150
|
+
)
|
|
151
|
+
.transform(({ serviceResult: [value] }) => value)
|
|
152
|
+
.execute();
|
|
144
153
|
});
|
|
145
154
|
|
|
146
155
|
expect(result).toBeUndefined();
|
|
@@ -149,7 +158,7 @@ describe("Internal Fragment", () => {
|
|
|
149
158
|
|
|
150
159
|
describe("Hook Service", () => {
|
|
151
160
|
let sqliteDatabase: SQLite.Database;
|
|
152
|
-
let adapter:
|
|
161
|
+
let adapter: SqlAdapter;
|
|
153
162
|
let fragment: ReturnType<typeof instantiateFragment>;
|
|
154
163
|
|
|
155
164
|
function instantiateFragment(options: FragnoPublicConfigWithDatabase) {
|
|
@@ -163,18 +172,19 @@ describe("Hook Service", () => {
|
|
|
163
172
|
database: sqliteDatabase,
|
|
164
173
|
});
|
|
165
174
|
|
|
166
|
-
adapter = new
|
|
175
|
+
adapter = new SqlAdapter({
|
|
167
176
|
dialect,
|
|
168
177
|
driverConfig: new BetterSQLite3DriverConfig(),
|
|
169
178
|
});
|
|
170
179
|
|
|
171
180
|
{
|
|
172
|
-
const migrations = adapter.prepareMigrations(internalSchema,
|
|
181
|
+
const migrations = adapter.prepareMigrations(internalSchema, null);
|
|
173
182
|
await migrations.executeWithDriver(adapter.driver, 0);
|
|
174
183
|
}
|
|
175
184
|
|
|
176
185
|
const options: FragnoPublicConfigWithDatabase = {
|
|
177
186
|
databaseAdapter: adapter,
|
|
187
|
+
databaseNamespace: null,
|
|
178
188
|
};
|
|
179
189
|
|
|
180
190
|
fragment = instantiateFragment(options);
|
|
@@ -188,42 +198,44 @@ describe("Hook Service", () => {
|
|
|
188
198
|
const nonce = "test-nonce-1";
|
|
189
199
|
|
|
190
200
|
await fragment.inContext(async function () {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
201
|
+
await this.handlerTx()
|
|
202
|
+
.mutate(({ forSchema }) => {
|
|
203
|
+
const uow = forSchema(internalSchema);
|
|
204
|
+
uow.create("fragno_hooks", {
|
|
205
|
+
namespace: "test-namespace",
|
|
206
|
+
hookName: "onTest",
|
|
207
|
+
payload: { test: "data" },
|
|
208
|
+
status: "pending",
|
|
209
|
+
attempts: 0,
|
|
210
|
+
maxAttempts: 5,
|
|
211
|
+
lastAttemptAt: null,
|
|
212
|
+
nextRetryAt: null,
|
|
213
|
+
error: null,
|
|
214
|
+
nonce,
|
|
215
|
+
});
|
|
216
|
+
uow.create("fragno_hooks", {
|
|
217
|
+
namespace: "test-namespace",
|
|
218
|
+
hookName: "onTest",
|
|
219
|
+
payload: { test: "already-completed-data" },
|
|
220
|
+
status: "completed",
|
|
221
|
+
attempts: 0,
|
|
222
|
+
maxAttempts: 5,
|
|
223
|
+
lastAttemptAt: null,
|
|
224
|
+
nextRetryAt: null,
|
|
225
|
+
error: null,
|
|
226
|
+
nonce,
|
|
227
|
+
});
|
|
228
|
+
})
|
|
229
|
+
.execute();
|
|
219
230
|
});
|
|
220
231
|
|
|
221
232
|
const events = await fragment.inContext(async function () {
|
|
222
|
-
return await this.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
233
|
+
return await this.handlerTx()
|
|
234
|
+
.withServiceCalls(
|
|
235
|
+
() => [fragment.services.hookService.getPendingHookEvents("test-namespace")] as const,
|
|
236
|
+
)
|
|
237
|
+
.transform(({ serviceResult: [result] }) => result)
|
|
238
|
+
.execute();
|
|
227
239
|
});
|
|
228
240
|
|
|
229
241
|
expect(events).toHaveLength(1);
|
|
@@ -232,7 +244,7 @@ describe("Hook Service", () => {
|
|
|
232
244
|
payload: { test: "data" },
|
|
233
245
|
attempts: 0,
|
|
234
246
|
maxAttempts: 5,
|
|
235
|
-
nonce,
|
|
247
|
+
idempotencyKey: nonce,
|
|
236
248
|
});
|
|
237
249
|
});
|
|
238
250
|
|
|
@@ -241,41 +253,36 @@ describe("Hook Service", () => {
|
|
|
241
253
|
let eventId: FragnoId;
|
|
242
254
|
|
|
243
255
|
await fragment.inContext(async function () {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
256
|
+
await this.handlerTx()
|
|
257
|
+
.mutate(({ forSchema }) => {
|
|
258
|
+
const uow = forSchema(internalSchema);
|
|
259
|
+
eventId = uow.create("fragno_hooks", {
|
|
260
|
+
namespace: "test-namespace",
|
|
261
|
+
hookName: "onComplete",
|
|
262
|
+
payload: { test: "data" },
|
|
263
|
+
status: "pending",
|
|
264
|
+
attempts: 0,
|
|
265
|
+
maxAttempts: 5,
|
|
266
|
+
lastAttemptAt: null,
|
|
267
|
+
nextRetryAt: null,
|
|
268
|
+
error: null,
|
|
269
|
+
nonce,
|
|
270
|
+
});
|
|
271
|
+
})
|
|
272
|
+
.execute();
|
|
260
273
|
});
|
|
261
274
|
|
|
262
275
|
await fragment.inContext(async function () {
|
|
263
|
-
|
|
264
|
-
fragment.services.hookService.markHookCompleted(eventId)
|
|
265
|
-
|
|
266
|
-
});
|
|
276
|
+
await this.handlerTx()
|
|
277
|
+
.withServiceCalls(() => [fragment.services.hookService.markHookCompleted(eventId)] as const)
|
|
278
|
+
.execute();
|
|
267
279
|
});
|
|
268
280
|
|
|
269
281
|
const result = await fragment.inContext(async function () {
|
|
270
|
-
return await this.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
);
|
|
275
|
-
await executeRetrieve();
|
|
276
|
-
const [events] = await findUow.retrievalPhase;
|
|
277
|
-
return events?.[0];
|
|
278
|
-
});
|
|
282
|
+
return await this.handlerTx()
|
|
283
|
+
.withServiceCalls(() => [fragment.services.hookService.getHookById(eventId)] as const)
|
|
284
|
+
.transform(({ serviceResult: [event] }) => event)
|
|
285
|
+
.execute();
|
|
279
286
|
});
|
|
280
287
|
|
|
281
288
|
expect(result).toBeDefined();
|
|
@@ -283,46 +290,88 @@ describe("Hook Service", () => {
|
|
|
283
290
|
expect(result?.lastAttemptAt).toBeInstanceOf(Date);
|
|
284
291
|
});
|
|
285
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
|
+
|
|
286
338
|
it("should mark a hook event as processing", async () => {
|
|
287
339
|
const nonce = "test-nonce-3";
|
|
288
340
|
let eventId: FragnoId;
|
|
289
341
|
|
|
290
342
|
await fragment.inContext(async function () {
|
|
291
|
-
return
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
343
|
+
return this.handlerTx()
|
|
344
|
+
.mutate(({ forSchema }) => {
|
|
345
|
+
const uow = forSchema(internalSchema);
|
|
346
|
+
eventId = uow.create("fragno_hooks", {
|
|
347
|
+
namespace: "test-namespace",
|
|
348
|
+
hookName: "onProcess",
|
|
349
|
+
payload: { test: "data" },
|
|
350
|
+
status: "pending",
|
|
351
|
+
attempts: 0,
|
|
352
|
+
maxAttempts: 5,
|
|
353
|
+
lastAttemptAt: null,
|
|
354
|
+
nextRetryAt: null,
|
|
355
|
+
error: null,
|
|
356
|
+
nonce,
|
|
357
|
+
});
|
|
358
|
+
})
|
|
359
|
+
.execute();
|
|
307
360
|
});
|
|
308
361
|
|
|
309
362
|
await fragment.inContext(async function () {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
363
|
+
await this.handlerTx()
|
|
364
|
+
.withServiceCalls(
|
|
365
|
+
() => [fragment.services.hookService.markHookProcessing(eventId)] as const,
|
|
366
|
+
)
|
|
367
|
+
.execute();
|
|
314
368
|
});
|
|
315
369
|
|
|
316
370
|
const result = await fragment.inContext(async function () {
|
|
317
|
-
return await this.
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
);
|
|
322
|
-
await executeRetrieve();
|
|
323
|
-
const [events] = await findUow.retrievalPhase;
|
|
324
|
-
return events?.[0];
|
|
325
|
-
});
|
|
371
|
+
return await this.handlerTx()
|
|
372
|
+
.withServiceCalls(() => [fragment.services.hookService.getHookById(eventId)] as const)
|
|
373
|
+
.transform(({ serviceResult: [event] }) => event)
|
|
374
|
+
.execute();
|
|
326
375
|
});
|
|
327
376
|
|
|
328
377
|
expect(result).toBeDefined();
|
|
@@ -335,43 +384,44 @@ describe("Hook Service", () => {
|
|
|
335
384
|
let eventId: FragnoId;
|
|
336
385
|
|
|
337
386
|
await fragment.inContext(async function () {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
387
|
+
const createdId = await this.handlerTx()
|
|
388
|
+
.mutate(({ forSchema }) => {
|
|
389
|
+
const uow = forSchema(internalSchema);
|
|
390
|
+
return uow.create("fragno_hooks", {
|
|
391
|
+
namespace: "test-namespace",
|
|
392
|
+
hookName: "onFail",
|
|
393
|
+
payload: { test: "data" },
|
|
394
|
+
status: "pending",
|
|
395
|
+
attempts: 0,
|
|
396
|
+
maxAttempts: 5,
|
|
397
|
+
lastAttemptAt: null,
|
|
398
|
+
nextRetryAt: null,
|
|
399
|
+
error: null,
|
|
400
|
+
nonce,
|
|
401
|
+
});
|
|
402
|
+
})
|
|
403
|
+
.execute();
|
|
404
|
+
eventId = createdId;
|
|
354
405
|
});
|
|
355
406
|
|
|
356
407
|
const retryPolicy = new ExponentialBackoffRetryPolicy({ maxRetries: 3 });
|
|
357
408
|
|
|
358
409
|
await fragment.inContext(async function () {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
410
|
+
await this.handlerTx()
|
|
411
|
+
.withServiceCalls(
|
|
412
|
+
() =>
|
|
413
|
+
[
|
|
414
|
+
fragment.services.hookService.markHookFailed(eventId, "Test error", 0, retryPolicy),
|
|
415
|
+
] as const,
|
|
416
|
+
)
|
|
417
|
+
.execute();
|
|
363
418
|
});
|
|
364
419
|
|
|
365
420
|
const result = await fragment.inContext(async function () {
|
|
366
|
-
return await this.
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
);
|
|
371
|
-
await executeRetrieve();
|
|
372
|
-
const [events] = await findUow.retrievalPhase;
|
|
373
|
-
return events?.[0];
|
|
374
|
-
});
|
|
421
|
+
return await this.handlerTx()
|
|
422
|
+
.withServiceCalls(() => [fragment.services.hookService.getHookById(eventId)] as const)
|
|
423
|
+
.transform(({ serviceResult: [event] }) => event)
|
|
424
|
+
.execute();
|
|
375
425
|
});
|
|
376
426
|
|
|
377
427
|
expect(result).toBeDefined();
|
|
@@ -387,48 +437,49 @@ describe("Hook Service", () => {
|
|
|
387
437
|
let eventId: FragnoId;
|
|
388
438
|
|
|
389
439
|
await fragment.inContext(async function () {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
440
|
+
const createdId = await this.handlerTx()
|
|
441
|
+
.mutate(({ forSchema }) => {
|
|
442
|
+
const uow = forSchema(internalSchema);
|
|
443
|
+
return uow.create("fragno_hooks", {
|
|
444
|
+
namespace: "test-namespace",
|
|
445
|
+
hookName: "onMaxFail",
|
|
446
|
+
payload: { test: "data" },
|
|
447
|
+
status: "pending",
|
|
448
|
+
attempts: 0,
|
|
449
|
+
maxAttempts: 1,
|
|
450
|
+
lastAttemptAt: null,
|
|
451
|
+
nextRetryAt: null,
|
|
452
|
+
error: null,
|
|
453
|
+
nonce,
|
|
454
|
+
});
|
|
455
|
+
})
|
|
456
|
+
.execute();
|
|
457
|
+
eventId = createdId;
|
|
406
458
|
});
|
|
407
459
|
|
|
408
460
|
const retryPolicy = new NoRetryPolicy();
|
|
409
461
|
|
|
410
462
|
await fragment.inContext(async function () {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
463
|
+
await this.handlerTx()
|
|
464
|
+
.withServiceCalls(
|
|
465
|
+
() =>
|
|
466
|
+
[
|
|
467
|
+
fragment.services.hookService.markHookFailed(
|
|
468
|
+
eventId,
|
|
469
|
+
"Max attempts reached",
|
|
470
|
+
0,
|
|
471
|
+
retryPolicy,
|
|
472
|
+
),
|
|
473
|
+
] as const,
|
|
474
|
+
)
|
|
475
|
+
.execute();
|
|
420
476
|
});
|
|
421
477
|
|
|
422
478
|
const result = await fragment.inContext(async function () {
|
|
423
|
-
return await this.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
);
|
|
428
|
-
await executeRetrieve();
|
|
429
|
-
const [events] = await findUow.retrievalPhase;
|
|
430
|
-
return events?.[0];
|
|
431
|
-
});
|
|
479
|
+
return await this.handlerTx()
|
|
480
|
+
.withServiceCalls(() => [fragment.services.hookService.getHookById(eventId)] as const)
|
|
481
|
+
.transform(({ serviceResult: [event] }) => event)
|
|
482
|
+
.execute();
|
|
432
483
|
});
|
|
433
484
|
|
|
434
485
|
expect(result).toBeDefined();
|
|
@@ -444,30 +495,33 @@ describe("Hook Service", () => {
|
|
|
444
495
|
const pastTime = new Date(Date.now() - 10000);
|
|
445
496
|
|
|
446
497
|
await fragment.inContext(async function () {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
498
|
+
const createdId = await this.handlerTx()
|
|
499
|
+
.mutate(({ forSchema }) => {
|
|
500
|
+
const uow = forSchema(internalSchema);
|
|
501
|
+
return uow.create("fragno_hooks", {
|
|
502
|
+
namespace: "test-namespace",
|
|
503
|
+
hookName: "onStale",
|
|
504
|
+
payload: { test: "stale" },
|
|
505
|
+
status: "pending",
|
|
506
|
+
attempts: 1,
|
|
507
|
+
maxAttempts: 5,
|
|
508
|
+
lastAttemptAt: pastTime,
|
|
509
|
+
nextRetryAt: pastTime,
|
|
510
|
+
error: "Previous error",
|
|
511
|
+
nonce,
|
|
512
|
+
});
|
|
513
|
+
})
|
|
514
|
+
.execute();
|
|
515
|
+
eventId = createdId;
|
|
463
516
|
});
|
|
464
517
|
|
|
465
518
|
const events = await fragment.inContext(async function () {
|
|
466
|
-
return await this.
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
519
|
+
return await this.handlerTx()
|
|
520
|
+
.withServiceCalls(
|
|
521
|
+
() => [fragment.services.hookService.getPendingHookEvents("test-namespace")] as const,
|
|
522
|
+
)
|
|
523
|
+
.transform(({ serviceResult: [result] }) => result)
|
|
524
|
+
.execute();
|
|
471
525
|
});
|
|
472
526
|
|
|
473
527
|
const staleEvent = events.find((e) => e.id.externalId === eventId.externalId);
|
|
@@ -476,34 +530,79 @@ describe("Hook Service", () => {
|
|
|
476
530
|
expect(staleEvent?.attempts).toBe(1);
|
|
477
531
|
});
|
|
478
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
|
+
|
|
479
576
|
it("should not retrieve events from different namespace", async () => {
|
|
480
577
|
const nonce = "test-nonce-7";
|
|
481
578
|
|
|
482
579
|
await fragment.inContext(async function () {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
580
|
+
await this.handlerTx()
|
|
581
|
+
.mutate(({ forSchema }) => {
|
|
582
|
+
const uow = forSchema(internalSchema);
|
|
583
|
+
uow.create("fragno_hooks", {
|
|
584
|
+
namespace: "other-namespace",
|
|
585
|
+
hookName: "onOther",
|
|
586
|
+
payload: { test: "other" },
|
|
587
|
+
status: "pending",
|
|
588
|
+
attempts: 0,
|
|
589
|
+
maxAttempts: 5,
|
|
590
|
+
lastAttemptAt: null,
|
|
591
|
+
nextRetryAt: null,
|
|
592
|
+
error: null,
|
|
593
|
+
nonce,
|
|
594
|
+
});
|
|
595
|
+
})
|
|
596
|
+
.execute();
|
|
499
597
|
});
|
|
500
598
|
|
|
501
599
|
const events = await fragment.inContext(async function () {
|
|
502
|
-
return await this.
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
600
|
+
return await this.handlerTx()
|
|
601
|
+
.withServiceCalls(
|
|
602
|
+
() => [fragment.services.hookService.getPendingHookEvents("test-namespace")] as const,
|
|
603
|
+
)
|
|
604
|
+
.transform(({ serviceResult: [result] }) => result)
|
|
605
|
+
.execute();
|
|
507
606
|
});
|
|
508
607
|
|
|
509
608
|
const otherEvent = events.find((e) => e.hookName === "onOther");
|
|
@@ -517,33 +616,390 @@ describe("Hook Service", () => {
|
|
|
517
616
|
const futureTime = new Date(Date.now() + 60000);
|
|
518
617
|
|
|
519
618
|
await fragment.inContext(async function () {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
619
|
+
const createdId = await this.handlerTx()
|
|
620
|
+
.mutate(({ forSchema }) => {
|
|
621
|
+
const uow = forSchema(internalSchema);
|
|
622
|
+
return uow.create("fragno_hooks", {
|
|
623
|
+
namespace: "test-namespace",
|
|
624
|
+
hookName: "onFuture",
|
|
625
|
+
payload: { test: "future" },
|
|
626
|
+
status: "pending",
|
|
627
|
+
attempts: 1,
|
|
628
|
+
maxAttempts: 5,
|
|
629
|
+
lastAttemptAt: new Date(),
|
|
630
|
+
nextRetryAt: futureTime,
|
|
631
|
+
error: "Previous error",
|
|
632
|
+
nonce,
|
|
633
|
+
});
|
|
634
|
+
})
|
|
635
|
+
.execute();
|
|
636
|
+
eventId = createdId;
|
|
536
637
|
});
|
|
537
638
|
|
|
538
639
|
const events = await fragment.inContext(async function () {
|
|
539
|
-
return await this.
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
640
|
+
return await this.handlerTx()
|
|
641
|
+
.withServiceCalls(
|
|
642
|
+
() => [fragment.services.hookService.getPendingHookEvents("test-namespace")] as const,
|
|
643
|
+
)
|
|
644
|
+
.transform(({ serviceResult: [result] }) => result)
|
|
645
|
+
.execute();
|
|
544
646
|
});
|
|
545
647
|
|
|
546
648
|
const futureEvent = events.find((e) => e.id.externalId === eventId.externalId);
|
|
547
649
|
expect(futureEvent).toBeUndefined();
|
|
548
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
|
+
});
|
|
549
1005
|
});
|