@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
|
@@ -8,45 +8,20 @@ import {
|
|
|
8
8
|
type FragnoPublicConfigWithDatabase,
|
|
9
9
|
type ImplicitDatabaseDependencies,
|
|
10
10
|
} from "../db-fragment-definition-builder";
|
|
11
|
-
import
|
|
12
|
-
import { schema, idColumn, column } from "../schema/create";
|
|
11
|
+
import { FragnoId } from "../schema/create";
|
|
13
12
|
import type { RetryPolicy } from "../query/unit-of-work/retry-policy";
|
|
13
|
+
import { dbNow } from "../query/db-now";
|
|
14
|
+
import {
|
|
15
|
+
internalSchema,
|
|
16
|
+
SETTINGS_NAMESPACE,
|
|
17
|
+
SETTINGS_TABLE_NAME,
|
|
18
|
+
} from "./internal-fragment.schema";
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export const internalSchema = schema((s) => {
|
|
21
|
-
return s
|
|
22
|
-
.addTable(SETTINGS_TABLE_NAME, (t) => {
|
|
23
|
-
return t
|
|
24
|
-
.addColumn("id", idColumn())
|
|
25
|
-
.addColumn("key", column("string"))
|
|
26
|
-
.addColumn("value", column("string"))
|
|
27
|
-
.createIndex("unique_key", ["key"], { unique: true });
|
|
28
|
-
})
|
|
29
|
-
.addTable("fragno_hooks", (t) => {
|
|
30
|
-
return t
|
|
31
|
-
.addColumn("id", idColumn())
|
|
32
|
-
.addColumn("namespace", column("string"))
|
|
33
|
-
.addColumn("hookName", column("string"))
|
|
34
|
-
.addColumn("payload", column("json"))
|
|
35
|
-
.addColumn("status", column("string")) // "pending" | "processing" | "completed" | "failed"
|
|
36
|
-
.addColumn("attempts", column("integer").defaultTo(0))
|
|
37
|
-
.addColumn("maxAttempts", column("integer").defaultTo(5))
|
|
38
|
-
.addColumn("lastAttemptAt", column("timestamp").nullable())
|
|
39
|
-
.addColumn("nextRetryAt", column("timestamp").nullable())
|
|
40
|
-
.addColumn("error", column("string").nullable())
|
|
41
|
-
.addColumn(
|
|
42
|
-
"createdAt",
|
|
43
|
-
column("timestamp").defaultTo((b) => b.now()),
|
|
44
|
-
)
|
|
45
|
-
.addColumn("nonce", column("string"))
|
|
46
|
-
.createIndex("idx_namespace_status_retry", ["namespace", "status", "nextRetryAt"])
|
|
47
|
-
.createIndex("idx_nonce", ["nonce"]);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
20
|
+
export {
|
|
21
|
+
internalSchema,
|
|
22
|
+
SETTINGS_NAMESPACE,
|
|
23
|
+
SETTINGS_TABLE_NAME,
|
|
24
|
+
} from "./internal-fragment.schema";
|
|
50
25
|
|
|
51
26
|
// This uses DatabaseFragmentDefinitionBuilder directly
|
|
52
27
|
// to avoid circular dependency (it doesn't need to link to itself)
|
|
@@ -64,8 +39,15 @@ export const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(
|
|
|
64
39
|
DatabaseRequestStorage
|
|
65
40
|
>("$fragno-internal-fragment"),
|
|
66
41
|
internalSchema,
|
|
67
|
-
"", // intentionally blank namespace so there is no prefix
|
|
68
42
|
)
|
|
43
|
+
.providesBaseService(({ deps }) => ({
|
|
44
|
+
getDbNow: async () => {
|
|
45
|
+
if (deps.db.now) {
|
|
46
|
+
return deps.db.now();
|
|
47
|
+
}
|
|
48
|
+
return new Date();
|
|
49
|
+
},
|
|
50
|
+
}))
|
|
69
51
|
.providesService("settingsService", ({ defineService }) => {
|
|
70
52
|
return defineService({
|
|
71
53
|
/**
|
|
@@ -128,33 +110,240 @@ export const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(
|
|
|
128
110
|
* Returns all pending events for the given namespace that are ready to be processed.
|
|
129
111
|
*/
|
|
130
112
|
getPendingHookEvents(namespace: string) {
|
|
113
|
+
const now = dbNow();
|
|
131
114
|
return this.serviceTx(internalSchema)
|
|
132
115
|
.retrieve((uow) =>
|
|
133
116
|
uow.find("fragno_hooks", (b) =>
|
|
134
117
|
b.whereIndex("idx_namespace_status_retry", (eb) =>
|
|
135
|
-
eb.and(
|
|
118
|
+
eb.and(
|
|
119
|
+
eb("namespace", "=", namespace),
|
|
120
|
+
eb("status", "=", "pending"),
|
|
121
|
+
eb.or(eb.isNull("nextRetryAt"), eb("nextRetryAt", "<=", now)),
|
|
122
|
+
),
|
|
136
123
|
),
|
|
137
124
|
),
|
|
138
125
|
)
|
|
139
126
|
.transformRetrieve(([events]) => {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
127
|
+
return events.map((event) => ({
|
|
128
|
+
id: event.id,
|
|
129
|
+
hookName: event.hookName,
|
|
130
|
+
payload: event.payload as unknown,
|
|
131
|
+
attempts: event.attempts,
|
|
132
|
+
maxAttempts: event.maxAttempts,
|
|
133
|
+
idempotencyKey: event.nonce,
|
|
134
|
+
}));
|
|
135
|
+
})
|
|
136
|
+
.build();
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Claim pending hook events for processing.
|
|
141
|
+
* Returns ready events and marks them as processing in the same transaction.
|
|
142
|
+
*/
|
|
143
|
+
claimPendingHookEvents(namespace: string) {
|
|
144
|
+
const now = dbNow();
|
|
145
|
+
return this.serviceTx(internalSchema)
|
|
146
|
+
.retrieve((uow) =>
|
|
147
|
+
uow.find("fragno_hooks", (b) =>
|
|
148
|
+
b.whereIndex("idx_namespace_status_retry", (eb) =>
|
|
149
|
+
eb.and(
|
|
150
|
+
eb("namespace", "=", namespace),
|
|
151
|
+
eb("status", "=", "pending"),
|
|
152
|
+
eb.or(eb.isNull("nextRetryAt"), eb("nextRetryAt", "<=", now)),
|
|
153
|
+
),
|
|
154
|
+
),
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
.transformRetrieve(([events]) => {
|
|
158
|
+
return events.map((event) => ({
|
|
159
|
+
id: event.id,
|
|
160
|
+
hookName: event.hookName,
|
|
161
|
+
payload: event.payload,
|
|
162
|
+
attempts: event.attempts,
|
|
163
|
+
maxAttempts: event.maxAttempts,
|
|
164
|
+
idempotencyKey: event.nonce,
|
|
165
|
+
}));
|
|
166
|
+
})
|
|
167
|
+
.mutate(({ uow, retrieveResult }) => {
|
|
168
|
+
if (retrieveResult.length === 0) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
for (const event of retrieveResult) {
|
|
172
|
+
uow.update("fragno_hooks", event.id, (b) =>
|
|
173
|
+
b.set({ status: "processing", lastAttemptAt: now }).check(),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
.transform(({ retrieveResult }) =>
|
|
178
|
+
retrieveResult.map((event) => ({
|
|
179
|
+
...event,
|
|
180
|
+
id: new FragnoId({
|
|
181
|
+
externalId: event.id.externalId,
|
|
182
|
+
internalId: event.id.internalId,
|
|
183
|
+
version: event.id.version + 1,
|
|
184
|
+
}),
|
|
185
|
+
})),
|
|
186
|
+
)
|
|
187
|
+
.build();
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Re-queue hook events that have been stuck in processing for too long.
|
|
192
|
+
*/
|
|
193
|
+
requeueStuckProcessingHooks(namespace: string, staleBefore: Date) {
|
|
194
|
+
return this.serviceTx(internalSchema)
|
|
195
|
+
.retrieve((uow) =>
|
|
196
|
+
uow.find("fragno_hooks", (b) =>
|
|
197
|
+
b.whereIndex("idx_namespace_status_retry", (eb) =>
|
|
198
|
+
eb.and(eb("namespace", "=", namespace), eb("status", "=", "processing")),
|
|
199
|
+
),
|
|
200
|
+
),
|
|
201
|
+
)
|
|
202
|
+
.transformRetrieve(([events]) => {
|
|
203
|
+
const stuck = events.filter((event) => {
|
|
204
|
+
if (!event.lastAttemptAt) {
|
|
205
|
+
return true;
|
|
145
206
|
}
|
|
146
|
-
return event.
|
|
207
|
+
return event.lastAttemptAt <= staleBefore;
|
|
147
208
|
});
|
|
148
209
|
|
|
149
|
-
return
|
|
210
|
+
return stuck.map((event) => ({
|
|
150
211
|
id: event.id,
|
|
151
212
|
hookName: event.hookName,
|
|
152
|
-
payload: event.payload as unknown,
|
|
153
213
|
attempts: event.attempts,
|
|
154
214
|
maxAttempts: event.maxAttempts,
|
|
155
|
-
|
|
215
|
+
lastAttemptAt: event.lastAttemptAt,
|
|
216
|
+
nextRetryAt: event.nextRetryAt,
|
|
156
217
|
}));
|
|
157
218
|
})
|
|
219
|
+
.mutate(({ uow, retrieveResult }) => {
|
|
220
|
+
for (const event of retrieveResult) {
|
|
221
|
+
uow.update("fragno_hooks", event.id, (b) =>
|
|
222
|
+
b.set({ status: "pending", nextRetryAt: null }).check(),
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
.transform(({ retrieveResult }) => retrieveResult)
|
|
227
|
+
.build();
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Get the next time a processing hook becomes stale.
|
|
232
|
+
*/
|
|
233
|
+
getNextProcessingStaleAt(namespace: string, timeoutMinutes: number, now?: Date) {
|
|
234
|
+
return this.serviceTx(internalSchema)
|
|
235
|
+
.retrieve((uow) =>
|
|
236
|
+
uow.find("fragno_hooks", (b) =>
|
|
237
|
+
b.whereIndex("idx_namespace_status_retry", (eb) =>
|
|
238
|
+
eb.and(eb("namespace", "=", namespace), eb("status", "=", "processing")),
|
|
239
|
+
),
|
|
240
|
+
),
|
|
241
|
+
)
|
|
242
|
+
.transformRetrieve(([events]) => {
|
|
243
|
+
if (events.length === 0) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const baseNow = now ?? new Date();
|
|
248
|
+
const nowMs = baseNow.getTime();
|
|
249
|
+
const timeoutMs = timeoutMinutes * 60_000;
|
|
250
|
+
let earliestStaleAt: Date | null = null;
|
|
251
|
+
|
|
252
|
+
for (const event of events) {
|
|
253
|
+
if (!event.lastAttemptAt) {
|
|
254
|
+
return baseNow;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const staleAtMs = event.lastAttemptAt.getTime() + timeoutMs;
|
|
258
|
+
if (staleAtMs <= nowMs) {
|
|
259
|
+
return baseNow;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const staleAt = new Date(staleAtMs);
|
|
263
|
+
if (!earliestStaleAt || staleAt < earliestStaleAt) {
|
|
264
|
+
earliestStaleAt = staleAt;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return earliestStaleAt;
|
|
269
|
+
})
|
|
270
|
+
.build();
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get the earliest pending hook wake time for a namespace.
|
|
275
|
+
* Optionally considers processing hooks becoming stale when timeoutMinutes is provided.
|
|
276
|
+
*/
|
|
277
|
+
getNextHookWakeAt(namespace: string, timeoutMinutes?: number | false, now?: Date) {
|
|
278
|
+
const baseNow = now ?? new Date();
|
|
279
|
+
const includeProcessing = typeof timeoutMinutes === "number" && timeoutMinutes > 0;
|
|
280
|
+
const timeoutMs = includeProcessing ? timeoutMinutes * 60_000 : 0;
|
|
281
|
+
|
|
282
|
+
return this.serviceTx(internalSchema)
|
|
283
|
+
.retrieve((uow) =>
|
|
284
|
+
uow.find("fragno_hooks", (b) =>
|
|
285
|
+
b
|
|
286
|
+
.whereIndex("idx_namespace_status_retry", (eb) => {
|
|
287
|
+
if (includeProcessing) {
|
|
288
|
+
return eb.and(
|
|
289
|
+
eb("namespace", "=", namespace),
|
|
290
|
+
eb.or(eb("status", "=", "pending"), eb("status", "=", "processing")),
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
return eb.and(eb("namespace", "=", namespace), eb("status", "=", "pending"));
|
|
294
|
+
})
|
|
295
|
+
.select(["status", "nextRetryAt", "lastAttemptAt"]),
|
|
296
|
+
),
|
|
297
|
+
)
|
|
298
|
+
.transformRetrieve(([events]) => {
|
|
299
|
+
if (events.length === 0) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const nowMs = baseNow.getTime();
|
|
304
|
+
let earliestPendingAt: Date | null = null;
|
|
305
|
+
let earliestStaleAt: Date | null = null;
|
|
306
|
+
|
|
307
|
+
for (const event of events) {
|
|
308
|
+
if (event.status === "pending") {
|
|
309
|
+
const nextRetryAt = event.nextRetryAt;
|
|
310
|
+
if (!nextRetryAt || nextRetryAt.getTime() <= nowMs) {
|
|
311
|
+
return baseNow;
|
|
312
|
+
}
|
|
313
|
+
if (!earliestPendingAt || nextRetryAt < earliestPendingAt) {
|
|
314
|
+
earliestPendingAt = nextRetryAt;
|
|
315
|
+
}
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (!includeProcessing || event.status !== "processing") {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const lastAttemptAt = event.lastAttemptAt;
|
|
324
|
+
if (!lastAttemptAt) {
|
|
325
|
+
return baseNow;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const staleAtMs = lastAttemptAt.getTime() + timeoutMs;
|
|
329
|
+
if (staleAtMs <= nowMs) {
|
|
330
|
+
return baseNow;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const staleAt = new Date(staleAtMs);
|
|
334
|
+
if (!earliestStaleAt || staleAt < earliestStaleAt) {
|
|
335
|
+
earliestStaleAt = staleAt;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (!earliestPendingAt) {
|
|
340
|
+
return earliestStaleAt ?? null;
|
|
341
|
+
}
|
|
342
|
+
if (!earliestStaleAt) {
|
|
343
|
+
return earliestPendingAt;
|
|
344
|
+
}
|
|
345
|
+
return earliestPendingAt <= earliestStaleAt ? earliestPendingAt : earliestStaleAt;
|
|
346
|
+
})
|
|
158
347
|
.build();
|
|
159
348
|
},
|
|
160
349
|
|
|
@@ -165,7 +354,7 @@ export const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(
|
|
|
165
354
|
return this.serviceTx(internalSchema)
|
|
166
355
|
.mutate(({ uow }) =>
|
|
167
356
|
uow.update("fragno_hooks", eventId, (b) =>
|
|
168
|
-
b.set({ status: "completed", lastAttemptAt:
|
|
357
|
+
b.set({ status: "completed", lastAttemptAt: dbNow() }).check(),
|
|
169
358
|
),
|
|
170
359
|
)
|
|
171
360
|
.build();
|
|
@@ -174,7 +363,13 @@ export const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(
|
|
|
174
363
|
/**
|
|
175
364
|
* Mark a hook event as failed and schedule next retry.
|
|
176
365
|
*/
|
|
177
|
-
markHookFailed(
|
|
366
|
+
markHookFailed(
|
|
367
|
+
eventId: FragnoId,
|
|
368
|
+
error: string,
|
|
369
|
+
attempts: number,
|
|
370
|
+
retryPolicy: RetryPolicy,
|
|
371
|
+
now?: Date,
|
|
372
|
+
) {
|
|
178
373
|
const newAttempts = attempts + 1;
|
|
179
374
|
const shouldRetry = retryPolicy.shouldRetry(newAttempts - 1);
|
|
180
375
|
|
|
@@ -182,13 +377,14 @@ export const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(
|
|
|
182
377
|
.mutate(({ uow }) => {
|
|
183
378
|
if (shouldRetry) {
|
|
184
379
|
const delayMs = retryPolicy.getDelayMs(newAttempts - 1);
|
|
185
|
-
const
|
|
380
|
+
const baseNow = now ?? new Date();
|
|
381
|
+
const nextRetryAt = new Date(baseNow.getTime() + delayMs);
|
|
186
382
|
uow.update("fragno_hooks", eventId, (b) =>
|
|
187
383
|
b
|
|
188
384
|
.set({
|
|
189
385
|
status: "pending",
|
|
190
386
|
attempts: newAttempts,
|
|
191
|
-
lastAttemptAt:
|
|
387
|
+
lastAttemptAt: dbNow(),
|
|
192
388
|
nextRetryAt,
|
|
193
389
|
error,
|
|
194
390
|
})
|
|
@@ -200,7 +396,7 @@ export const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(
|
|
|
200
396
|
.set({
|
|
201
397
|
status: "failed",
|
|
202
398
|
attempts: newAttempts,
|
|
203
|
-
lastAttemptAt:
|
|
399
|
+
lastAttemptAt: dbNow(),
|
|
204
400
|
error,
|
|
205
401
|
})
|
|
206
402
|
.check(),
|
|
@@ -217,7 +413,7 @@ export const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(
|
|
|
217
413
|
return this.serviceTx(internalSchema)
|
|
218
414
|
.mutate(({ uow }) =>
|
|
219
415
|
uow.update("fragno_hooks", eventId, (b) =>
|
|
220
|
-
b.set({ status: "processing", lastAttemptAt:
|
|
416
|
+
b.set({ status: "processing", lastAttemptAt: dbNow() }).check(),
|
|
221
417
|
),
|
|
222
418
|
)
|
|
223
419
|
.build();
|
|
@@ -252,6 +448,44 @@ export const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(
|
|
|
252
448
|
},
|
|
253
449
|
});
|
|
254
450
|
})
|
|
451
|
+
.providesService("outboxService", ({ defineService }) => {
|
|
452
|
+
return defineService({
|
|
453
|
+
/**
|
|
454
|
+
* List outbox entries ordered by versionstamp (ascending).
|
|
455
|
+
*/
|
|
456
|
+
list({ afterVersionstamp, limit }: { afterVersionstamp?: string; limit?: number } = {}) {
|
|
457
|
+
const afterValue = afterVersionstamp?.toLowerCase();
|
|
458
|
+
|
|
459
|
+
return this.serviceTx(internalSchema)
|
|
460
|
+
.retrieve((uow) =>
|
|
461
|
+
uow.find("fragno_db_outbox", (b) => {
|
|
462
|
+
let builder = afterValue
|
|
463
|
+
? b.whereIndex("idx_outbox_versionstamp", (eb) =>
|
|
464
|
+
eb("versionstamp", ">", afterValue),
|
|
465
|
+
)
|
|
466
|
+
: b.whereIndex("idx_outbox_versionstamp");
|
|
467
|
+
|
|
468
|
+
builder = builder.orderByIndex("idx_outbox_versionstamp", "asc");
|
|
469
|
+
if (limit !== undefined) {
|
|
470
|
+
builder = builder.pageSize(limit);
|
|
471
|
+
}
|
|
472
|
+
return builder;
|
|
473
|
+
}),
|
|
474
|
+
)
|
|
475
|
+
.transformRetrieve(([entries]) =>
|
|
476
|
+
entries.map((entry) => ({
|
|
477
|
+
id: entry.id,
|
|
478
|
+
versionstamp: entry.versionstamp,
|
|
479
|
+
uowId: entry.uowId,
|
|
480
|
+
payload: entry.payload,
|
|
481
|
+
refMap: entry.refMap ?? undefined,
|
|
482
|
+
createdAt: entry.createdAt,
|
|
483
|
+
})),
|
|
484
|
+
)
|
|
485
|
+
.build();
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
})
|
|
255
489
|
.build();
|
|
256
490
|
|
|
257
491
|
/**
|
|
@@ -267,15 +501,40 @@ export async function getSchemaVersionFromDatabase(
|
|
|
267
501
|
namespace: string,
|
|
268
502
|
): Promise<number> {
|
|
269
503
|
try {
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
504
|
+
const readSchemaVersion = async (targetNamespace: string): Promise<number | undefined> => {
|
|
505
|
+
const setting = await fragment.inContext(async function () {
|
|
506
|
+
return await this.handlerTx()
|
|
507
|
+
.withServiceCalls(
|
|
508
|
+
() =>
|
|
509
|
+
[fragment.services.settingsService.get(targetNamespace, "schema_version")] as const,
|
|
510
|
+
)
|
|
511
|
+
.transform(({ serviceResult: [result] }) => result)
|
|
512
|
+
.execute();
|
|
513
|
+
});
|
|
514
|
+
if (!setting) {
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
const parsed = parseInt(setting.value, 10);
|
|
518
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
const primary = await readSchemaVersion(namespace);
|
|
522
|
+
if (primary !== undefined) {
|
|
523
|
+
return primary;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Back-compat: some installs stored internal schema version under a different namespace.
|
|
527
|
+
// Check the alternate key (empty string ↔ schema name) so we find the version either way.
|
|
528
|
+
const legacyNamespace =
|
|
529
|
+
namespace === "" ? internalSchema.name : namespace === internalSchema.name ? "" : null;
|
|
530
|
+
if (legacyNamespace !== null) {
|
|
531
|
+
const legacy = await readSchemaVersion(legacyNamespace);
|
|
532
|
+
if (legacy !== undefined) {
|
|
533
|
+
return legacy;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return 0;
|
|
279
538
|
} catch {
|
|
280
539
|
return 0;
|
|
281
540
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import SQLite from "better-sqlite3";
|
|
2
|
+
import { SqliteDialect } from "kysely";
|
|
3
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
4
|
+
import { defineFragment, instantiate } from "@fragno-dev/core";
|
|
5
|
+
import { withDatabase } from "../with-database";
|
|
6
|
+
import { schema, column, idColumn } from "../schema/create";
|
|
7
|
+
import { SqlAdapter } from "../adapters/generic-sql/generic-sql-adapter";
|
|
8
|
+
import { BetterSQLite3DriverConfig } from "../adapters/generic-sql/driver-config";
|
|
9
|
+
import { internalSchema } from "../fragments/internal-fragment";
|
|
10
|
+
import { createDurableHooksProcessor } from "./durable-hooks-processor";
|
|
11
|
+
|
|
12
|
+
const testSchema = schema("test", (s) =>
|
|
13
|
+
s.addTable("items", (t) => t.addColumn("id", idColumn()).addColumn("name", column("string"))),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const testFragmentDefinition = defineFragment("test")
|
|
17
|
+
.extend(withDatabase(testSchema))
|
|
18
|
+
.provideHooks(({ defineHook }) => ({
|
|
19
|
+
onTest: defineHook(async function () {}),
|
|
20
|
+
}))
|
|
21
|
+
.build();
|
|
22
|
+
|
|
23
|
+
describe("createDurableHooksProcessor", () => {
|
|
24
|
+
let adapter: SqlAdapter;
|
|
25
|
+
let fragment: ReturnType<typeof instantiateFragment>;
|
|
26
|
+
|
|
27
|
+
function instantiateFragment(options: { databaseAdapter: SqlAdapter }) {
|
|
28
|
+
return instantiate(testFragmentDefinition).withConfig({}).withOptions(options).build();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
beforeAll(async () => {
|
|
32
|
+
const sqliteDatabase = new SQLite(":memory:");
|
|
33
|
+
const dialect = new SqliteDialect({ database: sqliteDatabase });
|
|
34
|
+
|
|
35
|
+
adapter = new SqlAdapter({
|
|
36
|
+
dialect,
|
|
37
|
+
driverConfig: new BetterSQLite3DriverConfig(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const internalMigrations = adapter.prepareMigrations(internalSchema, null);
|
|
41
|
+
await internalMigrations.executeWithDriver(adapter.driver, 0);
|
|
42
|
+
|
|
43
|
+
const testMigrations = adapter.prepareMigrations(testSchema, "test");
|
|
44
|
+
await testMigrations.executeWithDriver(adapter.driver, 0);
|
|
45
|
+
|
|
46
|
+
fragment = instantiateFragment({ databaseAdapter: adapter });
|
|
47
|
+
|
|
48
|
+
return async () => {
|
|
49
|
+
await adapter.close();
|
|
50
|
+
};
|
|
51
|
+
}, 12000);
|
|
52
|
+
|
|
53
|
+
it("should process pending hooks and return counts", async () => {
|
|
54
|
+
const processor = createDurableHooksProcessor(fragment);
|
|
55
|
+
expect(processor).not.toBeNull();
|
|
56
|
+
|
|
57
|
+
const internalFragment = fragment.$internal.linkedFragments._fragno_internal;
|
|
58
|
+
await internalFragment.inContext(async function () {
|
|
59
|
+
await this.handlerTx()
|
|
60
|
+
.mutate(({ forSchema }) => {
|
|
61
|
+
const uow = forSchema(internalSchema);
|
|
62
|
+
uow.create("fragno_hooks", {
|
|
63
|
+
namespace: "test",
|
|
64
|
+
hookName: "onTest",
|
|
65
|
+
payload: { ok: true },
|
|
66
|
+
status: "pending",
|
|
67
|
+
attempts: 0,
|
|
68
|
+
maxAttempts: 1,
|
|
69
|
+
lastAttemptAt: null,
|
|
70
|
+
nextRetryAt: null,
|
|
71
|
+
error: null,
|
|
72
|
+
nonce: "test-nonce",
|
|
73
|
+
});
|
|
74
|
+
})
|
|
75
|
+
.execute();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const wakeAt = await processor!.getNextWakeAt();
|
|
79
|
+
expect(wakeAt).toBeInstanceOf(Date);
|
|
80
|
+
|
|
81
|
+
const processed = await processor!.process();
|
|
82
|
+
expect(processed).toBe(1);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should wake for stale processing hooks", async () => {
|
|
86
|
+
const processor = createDurableHooksProcessor(fragment);
|
|
87
|
+
expect(processor).not.toBeNull();
|
|
88
|
+
|
|
89
|
+
const internalFragment = fragment.$internal.linkedFragments._fragno_internal;
|
|
90
|
+
const services = internalFragment.services as { getDbNow?: () => Promise<Date> };
|
|
91
|
+
const baseNow = services.getDbNow ? await services.getDbNow() : new Date();
|
|
92
|
+
|
|
93
|
+
await internalFragment.inContext(async function () {
|
|
94
|
+
await this.handlerTx()
|
|
95
|
+
.mutate(({ forSchema }) => {
|
|
96
|
+
const uow = forSchema(internalSchema);
|
|
97
|
+
uow.create("fragno_hooks", {
|
|
98
|
+
namespace: "test",
|
|
99
|
+
hookName: "onTest",
|
|
100
|
+
payload: { ok: true },
|
|
101
|
+
status: "processing",
|
|
102
|
+
attempts: 0,
|
|
103
|
+
maxAttempts: 1,
|
|
104
|
+
lastAttemptAt: new Date(baseNow.getTime() - 20 * 60_000),
|
|
105
|
+
nextRetryAt: null,
|
|
106
|
+
error: null,
|
|
107
|
+
nonce: "test-nonce-stuck",
|
|
108
|
+
});
|
|
109
|
+
})
|
|
110
|
+
.execute();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const wakeAt = await processor!.getNextWakeAt();
|
|
114
|
+
expect(wakeAt).toBeInstanceOf(Date);
|
|
115
|
+
expect(wakeAt!.getTime()).toBeLessThanOrEqual(baseNow.getTime());
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { AnySchema } from "../schema/create";
|
|
2
|
+
import type { AnyFragnoInstantiatedDatabaseFragment } from "../mod";
|
|
3
|
+
import { createHookScheduler, type HookProcessorConfig } from "./hooks";
|
|
4
|
+
|
|
5
|
+
export type DurableHooksProcessor = {
|
|
6
|
+
process: () => Promise<number>;
|
|
7
|
+
getNextWakeAt: () => Promise<Date | null>;
|
|
8
|
+
drain: () => Promise<void>;
|
|
9
|
+
namespace: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type DurableHooksInternal = {
|
|
13
|
+
durableHooks?: HookProcessorConfig;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES = 10;
|
|
17
|
+
|
|
18
|
+
function resolveStuckProcessingTimeoutMinutes(value: number | false | undefined): number | false {
|
|
19
|
+
if (value === false) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (typeof value === "number") {
|
|
23
|
+
return value > 0 ? value : false;
|
|
24
|
+
}
|
|
25
|
+
return DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createDurableHooksProcessor<TSchema extends AnySchema>(
|
|
29
|
+
fragment: AnyFragnoInstantiatedDatabaseFragment<TSchema>,
|
|
30
|
+
): DurableHooksProcessor | null {
|
|
31
|
+
const durableHooks = (fragment.$internal as DurableHooksInternal).durableHooks;
|
|
32
|
+
if (!durableHooks) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { namespace, internalFragment } = durableHooks;
|
|
37
|
+
const stuckProcessingTimeoutMinutes = resolveStuckProcessingTimeoutMinutes(
|
|
38
|
+
durableHooks.stuckProcessingTimeoutMinutes,
|
|
39
|
+
);
|
|
40
|
+
const scheduler =
|
|
41
|
+
durableHooks.scheduler ?? (durableHooks.scheduler = createHookScheduler(durableHooks));
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
namespace,
|
|
45
|
+
process: async () => scheduler.schedule(),
|
|
46
|
+
drain: async () => scheduler.drain(),
|
|
47
|
+
getNextWakeAt: async () => {
|
|
48
|
+
const services = internalFragment.services as { getDbNow?: () => Promise<Date> };
|
|
49
|
+
const now = services.getDbNow ? await services.getDbNow() : new Date();
|
|
50
|
+
return await internalFragment.inContext(async function () {
|
|
51
|
+
return await this.handlerTx()
|
|
52
|
+
.withServiceCalls(
|
|
53
|
+
() =>
|
|
54
|
+
[
|
|
55
|
+
internalFragment.services.hookService.getNextHookWakeAt(
|
|
56
|
+
namespace,
|
|
57
|
+
stuckProcessingTimeoutMinutes,
|
|
58
|
+
now,
|
|
59
|
+
),
|
|
60
|
+
] as const,
|
|
61
|
+
)
|
|
62
|
+
.transform(({ serviceResult: [result] }) => result)
|
|
63
|
+
.execute();
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|