@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
package/src/hooks/hooks.test.ts
CHANGED
|
@@ -2,17 +2,26 @@ import SQLite from "better-sqlite3";
|
|
|
2
2
|
import { SqliteDialect } from "kysely";
|
|
3
3
|
import { beforeAll, describe, expect, it, vi } from "vitest";
|
|
4
4
|
import { instantiate } from "@fragno-dev/core";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
prepareHookMutations,
|
|
7
|
+
processHooks,
|
|
8
|
+
type HooksMap,
|
|
9
|
+
type HookContext,
|
|
10
|
+
type HookHandlerTx,
|
|
11
|
+
} from "./hooks";
|
|
6
12
|
import { internalFragmentDef, internalSchema } from "../fragments/internal-fragment";
|
|
7
13
|
import type { FragnoPublicConfigWithDatabase } from "../db-fragment-definition-builder";
|
|
8
|
-
import {
|
|
14
|
+
import { SqlAdapter } from "../adapters/generic-sql/generic-sql-adapter";
|
|
9
15
|
import { BetterSQLite3DriverConfig } from "../adapters/generic-sql/driver-config";
|
|
10
16
|
import { ExponentialBackoffRetryPolicy, NoRetryPolicy } from "../query/unit-of-work/retry-policy";
|
|
11
17
|
import type { FragnoId } from "../schema/create";
|
|
12
18
|
|
|
13
19
|
describe("Hook System", () => {
|
|
20
|
+
const handlerTx = (() => {
|
|
21
|
+
throw new Error("handlerTx not configured for hooks test");
|
|
22
|
+
}) as HookHandlerTx;
|
|
14
23
|
let sqliteDatabase: SQLite.Database;
|
|
15
|
-
let adapter:
|
|
24
|
+
let adapter: SqlAdapter;
|
|
16
25
|
let internalFragment: ReturnType<typeof instantiateFragment>;
|
|
17
26
|
|
|
18
27
|
function instantiateFragment(options: FragnoPublicConfigWithDatabase) {
|
|
@@ -26,18 +35,19 @@ describe("Hook System", () => {
|
|
|
26
35
|
database: sqliteDatabase,
|
|
27
36
|
});
|
|
28
37
|
|
|
29
|
-
adapter = new
|
|
38
|
+
adapter = new SqlAdapter({
|
|
30
39
|
dialect,
|
|
31
40
|
driverConfig: new BetterSQLite3DriverConfig(),
|
|
32
41
|
});
|
|
33
42
|
|
|
34
43
|
{
|
|
35
|
-
const migrations = adapter.prepareMigrations(internalSchema,
|
|
44
|
+
const migrations = adapter.prepareMigrations(internalSchema, null);
|
|
36
45
|
await migrations.executeWithDriver(adapter.driver, 0);
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
const options: FragnoPublicConfigWithDatabase = {
|
|
40
49
|
databaseAdapter: adapter,
|
|
50
|
+
databaseNamespace: null,
|
|
41
51
|
};
|
|
42
52
|
|
|
43
53
|
internalFragment = instantiateFragment(options);
|
|
@@ -72,6 +82,7 @@ describe("Hook System", () => {
|
|
|
72
82
|
hooks,
|
|
73
83
|
namespace,
|
|
74
84
|
internalFragment,
|
|
85
|
+
handlerTx,
|
|
75
86
|
defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
|
|
76
87
|
});
|
|
77
88
|
})
|
|
@@ -118,6 +129,7 @@ describe("Hook System", () => {
|
|
|
118
129
|
hooks,
|
|
119
130
|
namespace,
|
|
120
131
|
internalFragment,
|
|
132
|
+
handlerTx,
|
|
121
133
|
defaultRetryPolicy: new NoRetryPolicy(),
|
|
122
134
|
});
|
|
123
135
|
})
|
|
@@ -160,6 +172,7 @@ describe("Hook System", () => {
|
|
|
160
172
|
hooks,
|
|
161
173
|
namespace,
|
|
162
174
|
internalFragment,
|
|
175
|
+
handlerTx,
|
|
163
176
|
defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 10 }),
|
|
164
177
|
});
|
|
165
178
|
})
|
|
@@ -177,6 +190,84 @@ describe("Hook System", () => {
|
|
|
177
190
|
|
|
178
191
|
expect(events[0]?.maxAttempts).toBe(1);
|
|
179
192
|
});
|
|
193
|
+
|
|
194
|
+
it("should set nextRetryAt when processAt is in the future", async () => {
|
|
195
|
+
const namespace = "test-process-at-future";
|
|
196
|
+
const hooks: HooksMap = {
|
|
197
|
+
onScheduled: vi.fn(),
|
|
198
|
+
};
|
|
199
|
+
const futureTime = new Date(Date.now() + 60000);
|
|
200
|
+
|
|
201
|
+
await internalFragment.inContext(async function () {
|
|
202
|
+
await this.handlerTx()
|
|
203
|
+
.mutate(({ forSchema }) => {
|
|
204
|
+
const uow = forSchema(internalSchema, hooks);
|
|
205
|
+
|
|
206
|
+
uow.triggerHook("onScheduled", { data: "test" }, { processAt: futureTime });
|
|
207
|
+
|
|
208
|
+
prepareHookMutations(uow, {
|
|
209
|
+
hooks,
|
|
210
|
+
namespace,
|
|
211
|
+
internalFragment,
|
|
212
|
+
handlerTx,
|
|
213
|
+
defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
|
|
214
|
+
});
|
|
215
|
+
})
|
|
216
|
+
.execute();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const events = await internalFragment.inContext(async function () {
|
|
220
|
+
return await this.handlerTx()
|
|
221
|
+
.withServiceCalls(
|
|
222
|
+
() => [internalFragment.services.hookService.getHooksByNamespace(namespace)] as const,
|
|
223
|
+
)
|
|
224
|
+
.transform(({ serviceResult: [result] }) => result)
|
|
225
|
+
.execute();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(events).toHaveLength(1);
|
|
229
|
+
expect(events[0]?.nextRetryAt).toBeInstanceOf(Date);
|
|
230
|
+
expect(events[0]?.nextRetryAt?.getTime()).toBe(futureTime.getTime());
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("should keep processAt in the past while remaining immediately eligible", async () => {
|
|
234
|
+
const namespace = "test-process-at-past";
|
|
235
|
+
const hooks: HooksMap = {
|
|
236
|
+
onImmediate: vi.fn(),
|
|
237
|
+
};
|
|
238
|
+
const pastTime = new Date(Date.now() - 60000);
|
|
239
|
+
|
|
240
|
+
await internalFragment.inContext(async function () {
|
|
241
|
+
await this.handlerTx()
|
|
242
|
+
.mutate(({ forSchema }) => {
|
|
243
|
+
const uow = forSchema(internalSchema, hooks);
|
|
244
|
+
|
|
245
|
+
uow.triggerHook("onImmediate", { data: "test" }, { processAt: pastTime });
|
|
246
|
+
|
|
247
|
+
prepareHookMutations(uow, {
|
|
248
|
+
hooks,
|
|
249
|
+
namespace,
|
|
250
|
+
internalFragment,
|
|
251
|
+
handlerTx,
|
|
252
|
+
defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 5 }),
|
|
253
|
+
});
|
|
254
|
+
})
|
|
255
|
+
.execute();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const events = await internalFragment.inContext(async function () {
|
|
259
|
+
return await this.handlerTx()
|
|
260
|
+
.withServiceCalls(
|
|
261
|
+
() => [internalFragment.services.hookService.getHooksByNamespace(namespace)] as const,
|
|
262
|
+
)
|
|
263
|
+
.transform(({ serviceResult: [result] }) => result)
|
|
264
|
+
.execute();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(events).toHaveLength(1);
|
|
268
|
+
expect(events[0]?.nextRetryAt).toBeInstanceOf(Date);
|
|
269
|
+
expect(events[0]?.nextRetryAt?.getTime()).toBe(pastTime.getTime());
|
|
270
|
+
});
|
|
180
271
|
});
|
|
181
272
|
|
|
182
273
|
describe("processHooks", () => {
|
|
@@ -216,6 +307,7 @@ describe("Hook System", () => {
|
|
|
216
307
|
hooks,
|
|
217
308
|
namespace,
|
|
218
309
|
internalFragment,
|
|
310
|
+
handlerTx,
|
|
219
311
|
defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
|
|
220
312
|
});
|
|
221
313
|
|
|
@@ -275,6 +367,7 @@ describe("Hook System", () => {
|
|
|
275
367
|
hooks,
|
|
276
368
|
namespace,
|
|
277
369
|
internalFragment,
|
|
370
|
+
handlerTx,
|
|
278
371
|
defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
|
|
279
372
|
});
|
|
280
373
|
|
|
@@ -329,6 +422,7 @@ describe("Hook System", () => {
|
|
|
329
422
|
hooks,
|
|
330
423
|
namespace,
|
|
331
424
|
internalFragment,
|
|
425
|
+
handlerTx,
|
|
332
426
|
defaultRetryPolicy: new NoRetryPolicy(),
|
|
333
427
|
});
|
|
334
428
|
|
|
@@ -379,6 +473,7 @@ describe("Hook System", () => {
|
|
|
379
473
|
hooks,
|
|
380
474
|
namespace,
|
|
381
475
|
internalFragment,
|
|
476
|
+
handlerTx,
|
|
382
477
|
defaultRetryPolicy: new NoRetryPolicy(),
|
|
383
478
|
});
|
|
384
479
|
|
|
@@ -395,6 +490,68 @@ describe("Hook System", () => {
|
|
|
395
490
|
expect(result?.error).toBe("Hook 'onMissing' not found in hooks map");
|
|
396
491
|
});
|
|
397
492
|
|
|
493
|
+
it("should re-queue stuck processing hooks and call the handler", async () => {
|
|
494
|
+
const namespace = "test-stuck-processing";
|
|
495
|
+
const hookFn = vi.fn();
|
|
496
|
+
const hooks: HooksMap = {
|
|
497
|
+
onStuck: hookFn,
|
|
498
|
+
};
|
|
499
|
+
const onStuckProcessingHooks = vi.fn();
|
|
500
|
+
|
|
501
|
+
let eventId: FragnoId;
|
|
502
|
+
|
|
503
|
+
await internalFragment.inContext(async function () {
|
|
504
|
+
const createdId = await this.handlerTx()
|
|
505
|
+
.mutate(({ forSchema }) => {
|
|
506
|
+
const uow = forSchema(internalSchema);
|
|
507
|
+
return uow.create("fragno_hooks", {
|
|
508
|
+
namespace,
|
|
509
|
+
hookName: "onStuck",
|
|
510
|
+
payload: { ok: true },
|
|
511
|
+
status: "processing",
|
|
512
|
+
attempts: 0,
|
|
513
|
+
maxAttempts: 5,
|
|
514
|
+
lastAttemptAt: new Date(Date.now() - 20 * 60_000),
|
|
515
|
+
nextRetryAt: null,
|
|
516
|
+
error: null,
|
|
517
|
+
nonce: "test-nonce",
|
|
518
|
+
});
|
|
519
|
+
})
|
|
520
|
+
.execute();
|
|
521
|
+
eventId = createdId;
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
await processHooks({
|
|
525
|
+
hooks,
|
|
526
|
+
namespace,
|
|
527
|
+
internalFragment,
|
|
528
|
+
handlerTx,
|
|
529
|
+
stuckProcessingTimeoutMinutes: 1,
|
|
530
|
+
onStuckProcessingHooks,
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
expect(hookFn).toHaveBeenCalledOnce();
|
|
534
|
+
expect(onStuckProcessingHooks).toHaveBeenCalledOnce();
|
|
535
|
+
expect(onStuckProcessingHooks).toHaveBeenCalledWith(
|
|
536
|
+
expect.objectContaining({
|
|
537
|
+
namespace,
|
|
538
|
+
timeoutMinutes: 1,
|
|
539
|
+
events: [expect.objectContaining({ hookName: "onStuck" })],
|
|
540
|
+
}),
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
const result = await internalFragment.inContext(async function () {
|
|
544
|
+
return await this.handlerTx()
|
|
545
|
+
.withServiceCalls(
|
|
546
|
+
() => [internalFragment.services.hookService.getHookById(eventId)] as const,
|
|
547
|
+
)
|
|
548
|
+
.transform(({ serviceResult: [event] }) => event)
|
|
549
|
+
.execute();
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
expect(result?.status).toBe("completed");
|
|
553
|
+
});
|
|
554
|
+
|
|
398
555
|
it("should process multiple hooks in parallel", async () => {
|
|
399
556
|
const namespace = "test-parallel";
|
|
400
557
|
const hook1 = vi.fn();
|
|
@@ -454,6 +611,7 @@ describe("Hook System", () => {
|
|
|
454
611
|
hooks,
|
|
455
612
|
namespace,
|
|
456
613
|
internalFragment,
|
|
614
|
+
handlerTx,
|
|
457
615
|
defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
|
|
458
616
|
});
|
|
459
617
|
|
|
@@ -534,6 +692,7 @@ describe("Hook System", () => {
|
|
|
534
692
|
hooks,
|
|
535
693
|
namespace,
|
|
536
694
|
internalFragment,
|
|
695
|
+
handlerTx,
|
|
537
696
|
defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
|
|
538
697
|
});
|
|
539
698
|
|
|
@@ -570,6 +729,7 @@ describe("Hook System", () => {
|
|
|
570
729
|
hooks,
|
|
571
730
|
namespace,
|
|
572
731
|
internalFragment,
|
|
732
|
+
handlerTx,
|
|
573
733
|
defaultRetryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 }),
|
|
574
734
|
});
|
|
575
735
|
|
package/src/hooks/hooks.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExecuteTxOptions,
|
|
3
|
+
HandlerTxBuilder,
|
|
4
|
+
} from "../query/unit-of-work/execute-unit-of-work";
|
|
1
5
|
import type { RetryPolicy } from "../query/unit-of-work/retry-policy";
|
|
2
6
|
import { ExponentialBackoffRetryPolicy } from "../query/unit-of-work/retry-policy";
|
|
3
7
|
import type { IUnitOfWork } from "../query/unit-of-work/unit-of-work";
|
|
4
8
|
import type { InternalFragmentInstance } from "../fragments/internal-fragment";
|
|
5
9
|
import type { TxResult } from "../query/unit-of-work/execute-unit-of-work";
|
|
10
|
+
import type { FragnoId } from "../schema/create";
|
|
6
11
|
|
|
7
12
|
/**
|
|
8
13
|
* Context available in hook functions via `this`.
|
|
@@ -14,6 +19,10 @@ export interface HookContext {
|
|
|
14
19
|
* Use this for idempotency checks in your hook implementation.
|
|
15
20
|
*/
|
|
16
21
|
idempotencyKey: string;
|
|
22
|
+
/**
|
|
23
|
+
* Create a handler transaction builder to run atomic operations.
|
|
24
|
+
*/
|
|
25
|
+
handlerTx: HookHandlerTx;
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
/**
|
|
@@ -43,6 +52,11 @@ export interface TriggerHookOptions {
|
|
|
43
52
|
* If not provided, uses the default retry policy.
|
|
44
53
|
*/
|
|
45
54
|
retryPolicy?: RetryPolicy;
|
|
55
|
+
/**
|
|
56
|
+
* Absolute time for the first attempt. If in the future, the hook is
|
|
57
|
+
* scheduled for that time; if in the past, it runs immediately.
|
|
58
|
+
*/
|
|
59
|
+
processAt?: Date;
|
|
46
60
|
}
|
|
47
61
|
|
|
48
62
|
/**
|
|
@@ -55,14 +69,88 @@ export interface TriggeredHook {
|
|
|
55
69
|
options?: TriggerHookOptions;
|
|
56
70
|
}
|
|
57
71
|
|
|
72
|
+
export type HookScheduler = {
|
|
73
|
+
schedule: () => Promise<number>;
|
|
74
|
+
drain: () => Promise<void>;
|
|
75
|
+
};
|
|
76
|
+
|
|
58
77
|
/**
|
|
59
78
|
* Configuration for hook processing.
|
|
60
79
|
*/
|
|
61
|
-
export interface HookProcessorConfig {
|
|
62
|
-
hooks:
|
|
80
|
+
export interface HookProcessorConfig<THooks extends HooksMap = HooksMap> {
|
|
81
|
+
hooks: THooks;
|
|
63
82
|
namespace: string;
|
|
64
83
|
internalFragment: InternalFragmentInstance;
|
|
84
|
+
handlerTx: HookHandlerTx;
|
|
85
|
+
/**
|
|
86
|
+
* Internal hook scheduler used to coordinate processing/drain.
|
|
87
|
+
*/
|
|
88
|
+
scheduler?: HookScheduler;
|
|
65
89
|
defaultRetryPolicy?: RetryPolicy;
|
|
90
|
+
/**
|
|
91
|
+
* Re-queue hooks that have been in `processing` for at least this many minutes.
|
|
92
|
+
* Use `false` to disable stuck-processing recovery entirely.
|
|
93
|
+
* Values <= 0 are treated as `false`.
|
|
94
|
+
*
|
|
95
|
+
* Default: 10 minutes.
|
|
96
|
+
*/
|
|
97
|
+
stuckProcessingTimeoutMinutes?: StuckHookProcessingTimeoutMinutes;
|
|
98
|
+
/**
|
|
99
|
+
* Called when stuck processing hooks are detected and re-queued.
|
|
100
|
+
* Invoked after the hooks are moved back to `pending`.
|
|
101
|
+
*/
|
|
102
|
+
onStuckProcessingHooks?: (info: StuckHookProcessingInfo) => void;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type StuckHookProcessingTimeoutMinutes = number | false;
|
|
106
|
+
|
|
107
|
+
export type StuckHookProcessingEvent = {
|
|
108
|
+
id: FragnoId;
|
|
109
|
+
hookName: string;
|
|
110
|
+
attempts: number;
|
|
111
|
+
maxAttempts: number;
|
|
112
|
+
lastAttemptAt: Date | null;
|
|
113
|
+
nextRetryAt: Date | null;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export type StuckHookProcessingInfo = {
|
|
117
|
+
namespace: string;
|
|
118
|
+
timeoutMinutes: number;
|
|
119
|
+
events: StuckHookProcessingEvent[];
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export type DurableHooksProcessingOptions = {
|
|
123
|
+
/**
|
|
124
|
+
* Re-queue hooks that have been in `processing` for at least this many minutes.
|
|
125
|
+
* Use `false` to disable stuck-processing recovery entirely.
|
|
126
|
+
* Values <= 0 are treated as `false`.
|
|
127
|
+
*
|
|
128
|
+
* Default: 10 minutes.
|
|
129
|
+
*/
|
|
130
|
+
stuckProcessingTimeoutMinutes?: StuckHookProcessingTimeoutMinutes;
|
|
131
|
+
/**
|
|
132
|
+
* Called when stuck processing hooks are detected and re-queued.
|
|
133
|
+
* Invoked after the hooks are moved back to `pending`.
|
|
134
|
+
*/
|
|
135
|
+
onStuckProcessingHooks?: (info: StuckHookProcessingInfo) => void;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export type HookHandlerTx = (
|
|
139
|
+
execOptions?: Omit<ExecuteTxOptions, "createUnitOfWork">,
|
|
140
|
+
) => HandlerTxBuilder<readonly [], [], [], unknown, unknown, false, false, false, false, HooksMap>;
|
|
141
|
+
|
|
142
|
+
const DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES = 10;
|
|
143
|
+
|
|
144
|
+
function resolveStuckProcessingTimeoutMinutes(
|
|
145
|
+
value: StuckHookProcessingTimeoutMinutes | undefined,
|
|
146
|
+
): number | false {
|
|
147
|
+
if (value === false) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
if (typeof value === "number") {
|
|
151
|
+
return value > 0 ? value : false;
|
|
152
|
+
}
|
|
153
|
+
return DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES;
|
|
66
154
|
}
|
|
67
155
|
|
|
68
156
|
/**
|
|
@@ -70,7 +158,10 @@ export interface HookProcessorConfig {
|
|
|
70
158
|
* This should be called before executeMutations() so hook records are created
|
|
71
159
|
* in the same transaction as the user's mutations.
|
|
72
160
|
*/
|
|
73
|
-
export function prepareHookMutations
|
|
161
|
+
export function prepareHookMutations<THooks extends HooksMap>(
|
|
162
|
+
uow: IUnitOfWork,
|
|
163
|
+
config: HookProcessorConfig<THooks>,
|
|
164
|
+
): void {
|
|
74
165
|
const { namespace, internalFragment, defaultRetryPolicy } = config;
|
|
75
166
|
const retryPolicy = defaultRetryPolicy ?? new ExponentialBackoffRetryPolicy({ maxRetries: 5 });
|
|
76
167
|
|
|
@@ -86,6 +177,8 @@ export function prepareHookMutations(uow: IUnitOfWork, config: HookProcessorConf
|
|
|
86
177
|
for (const hook of triggeredHooks) {
|
|
87
178
|
const hookRetryPolicy = hook.options?.retryPolicy ?? retryPolicy;
|
|
88
179
|
const maxAttempts = hookRetryPolicy.shouldRetry(4) ? 5 : 1;
|
|
180
|
+
const processAt = hook.options?.processAt ? new Date(hook.options.processAt) : null;
|
|
181
|
+
const nextRetryAt = processAt ?? null;
|
|
89
182
|
internalUow.create("fragno_hooks", {
|
|
90
183
|
namespace,
|
|
91
184
|
hookName: hook.hookName,
|
|
@@ -94,7 +187,7 @@ export function prepareHookMutations(uow: IUnitOfWork, config: HookProcessorConf
|
|
|
94
187
|
attempts: 0,
|
|
95
188
|
maxAttempts,
|
|
96
189
|
lastAttemptAt: null,
|
|
97
|
-
nextRetryAt
|
|
190
|
+
nextRetryAt,
|
|
98
191
|
error: null,
|
|
99
192
|
nonce: uow.idempotencyKey,
|
|
100
193
|
});
|
|
@@ -105,22 +198,65 @@ export function prepareHookMutations(uow: IUnitOfWork, config: HookProcessorConf
|
|
|
105
198
|
* Process pending hook events after the transaction has committed.
|
|
106
199
|
* This should be called in the onSuccess callback after executeMutations().
|
|
107
200
|
*/
|
|
108
|
-
export async function processHooks
|
|
201
|
+
export async function processHooks<THooks extends HooksMap>(
|
|
202
|
+
config: HookProcessorConfig<THooks>,
|
|
203
|
+
): Promise<number> {
|
|
109
204
|
const { hooks, namespace, internalFragment, defaultRetryPolicy } = config;
|
|
110
205
|
const retryPolicy = defaultRetryPolicy ?? new ExponentialBackoffRetryPolicy({ maxRetries: 5 });
|
|
206
|
+
const stuckProcessingTimeoutMinutes = resolveStuckProcessingTimeoutMinutes(
|
|
207
|
+
config.stuckProcessingTimeoutMinutes,
|
|
208
|
+
);
|
|
209
|
+
const getDbNow = async () => {
|
|
210
|
+
const services = internalFragment.services as { getDbNow?: () => Promise<Date> };
|
|
211
|
+
if (services.getDbNow) {
|
|
212
|
+
return services.getDbNow();
|
|
213
|
+
}
|
|
214
|
+
return new Date();
|
|
215
|
+
};
|
|
216
|
+
const dbNow = await getDbNow();
|
|
111
217
|
|
|
112
|
-
|
|
218
|
+
if (stuckProcessingTimeoutMinutes !== false) {
|
|
219
|
+
const staleBefore = new Date(dbNow.getTime() - stuckProcessingTimeoutMinutes * 60_000);
|
|
220
|
+
const stuckEvents = await internalFragment.inContext(async function () {
|
|
221
|
+
return await this.handlerTx()
|
|
222
|
+
.withServiceCalls(
|
|
223
|
+
() =>
|
|
224
|
+
[
|
|
225
|
+
internalFragment.services.hookService.requeueStuckProcessingHooks(
|
|
226
|
+
namespace,
|
|
227
|
+
staleBefore,
|
|
228
|
+
),
|
|
229
|
+
] as const,
|
|
230
|
+
)
|
|
231
|
+
.transform(({ serviceResult: [events] }) => events)
|
|
232
|
+
.execute();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
if (stuckEvents.length > 0) {
|
|
236
|
+
try {
|
|
237
|
+
config.onStuckProcessingHooks?.({
|
|
238
|
+
namespace,
|
|
239
|
+
timeoutMinutes: stuckProcessingTimeoutMinutes,
|
|
240
|
+
events: stuckEvents,
|
|
241
|
+
});
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error("Error calling onStuckProcessingHooks", error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Claim pending events in the same transaction to avoid double-processing.
|
|
113
249
|
const pendingEvents = await internalFragment.inContext(async function () {
|
|
114
250
|
return await this.handlerTx()
|
|
115
251
|
.withServiceCalls(
|
|
116
|
-
() => [internalFragment.services.hookService.
|
|
252
|
+
() => [internalFragment.services.hookService.claimPendingHookEvents(namespace)] as const,
|
|
117
253
|
)
|
|
118
254
|
.transform(({ serviceResult: [events] }) => events)
|
|
119
255
|
.execute();
|
|
120
256
|
});
|
|
121
257
|
|
|
122
258
|
if (pendingEvents.length === 0) {
|
|
123
|
-
return;
|
|
259
|
+
return 0;
|
|
124
260
|
}
|
|
125
261
|
|
|
126
262
|
// Process events (async work outside transaction)
|
|
@@ -138,7 +274,10 @@ export async function processHooks(config: HookProcessorConfig): Promise<void> {
|
|
|
138
274
|
}
|
|
139
275
|
|
|
140
276
|
try {
|
|
141
|
-
const hookContext: HookContext = {
|
|
277
|
+
const hookContext: HookContext = {
|
|
278
|
+
idempotencyKey: event.idempotencyKey,
|
|
279
|
+
handlerTx: config.handlerTx,
|
|
280
|
+
};
|
|
142
281
|
await hookFn.call(hookContext, event.payload);
|
|
143
282
|
return {
|
|
144
283
|
eventId: event.id,
|
|
@@ -179,6 +318,7 @@ export async function processHooks(config: HookProcessorConfig): Promise<void> {
|
|
|
179
318
|
error,
|
|
180
319
|
attempts,
|
|
181
320
|
retryPolicy,
|
|
321
|
+
dbNow,
|
|
182
322
|
),
|
|
183
323
|
);
|
|
184
324
|
}
|
|
@@ -187,4 +327,52 @@ export async function processHooks(config: HookProcessorConfig): Promise<void> {
|
|
|
187
327
|
})
|
|
188
328
|
.execute();
|
|
189
329
|
});
|
|
330
|
+
|
|
331
|
+
const processedCount = processedEvents.reduce(
|
|
332
|
+
(count, result) => count + (result.status === "fulfilled" ? 1 : 0),
|
|
333
|
+
0,
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
return processedCount;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function createHookScheduler(config: HookProcessorConfig): HookScheduler {
|
|
340
|
+
let processing = false;
|
|
341
|
+
let queued = false;
|
|
342
|
+
let currentPromise: Promise<number> | null = null;
|
|
343
|
+
|
|
344
|
+
const schedule = async () => {
|
|
345
|
+
if (processing) {
|
|
346
|
+
queued = true;
|
|
347
|
+
return currentPromise ?? Promise.resolve(0);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
processing = true;
|
|
351
|
+
currentPromise = (async () => {
|
|
352
|
+
let lastCount = 0;
|
|
353
|
+
try {
|
|
354
|
+
do {
|
|
355
|
+
queued = false;
|
|
356
|
+
lastCount = await processHooks(config);
|
|
357
|
+
} while (queued);
|
|
358
|
+
return lastCount;
|
|
359
|
+
} finally {
|
|
360
|
+
processing = false;
|
|
361
|
+
queued = false;
|
|
362
|
+
}
|
|
363
|
+
})();
|
|
364
|
+
|
|
365
|
+
return currentPromise;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const drain = async () => {
|
|
369
|
+
while (true) {
|
|
370
|
+
const processed = await schedule();
|
|
371
|
+
if (processed === 0) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
return { schedule, drain };
|
|
190
378
|
}
|
|
@@ -4,7 +4,7 @@ import { generateMigrationFromSchema } from "./auto-from-schema";
|
|
|
4
4
|
|
|
5
5
|
describe("generateMigrationFromSchema", () => {
|
|
6
6
|
it("should generate create-table operation for new tables", () => {
|
|
7
|
-
const mySchema = schema((s) => {
|
|
7
|
+
const mySchema = schema("my", (s) => {
|
|
8
8
|
return s
|
|
9
9
|
.addTable("users", (t) => {
|
|
10
10
|
return t.addColumn("id", idColumn()).addColumn("name", column("string"));
|
|
@@ -35,7 +35,7 @@ describe("generateMigrationFromSchema", () => {
|
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
it("should generate multiple table operations in sequence", () => {
|
|
38
|
-
const mySchema = schema((s) => {
|
|
38
|
+
const mySchema = schema("my", (s) => {
|
|
39
39
|
return s
|
|
40
40
|
.addTable("users", (t) => {
|
|
41
41
|
return t.addColumn("id", idColumn()).addColumn("name", column("string"));
|
|
@@ -79,7 +79,7 @@ describe("generateMigrationFromSchema", () => {
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
it("should generate add-foreign-key operation for new foreign keys", () => {
|
|
82
|
-
const mySchema = schema((s) => {
|
|
82
|
+
const mySchema = schema("my", (s) => {
|
|
83
83
|
return s
|
|
84
84
|
.addTable("users", (t) => {
|
|
85
85
|
return t.addColumn("id", idColumn());
|
|
@@ -108,7 +108,7 @@ describe("generateMigrationFromSchema", () => {
|
|
|
108
108
|
const fkOp = operations[0];
|
|
109
109
|
if (fkOp.type === "add-foreign-key") {
|
|
110
110
|
expect(fkOp.value).toMatchObject({
|
|
111
|
-
name: "
|
|
111
|
+
name: "author",
|
|
112
112
|
referencedTable: "users",
|
|
113
113
|
columns: ["authorId"],
|
|
114
114
|
referencedColumns: ["_internalId"],
|
|
@@ -117,7 +117,7 @@ describe("generateMigrationFromSchema", () => {
|
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
it("should generate add-index operation for indexes added via alterTable", () => {
|
|
120
|
-
const mySchema = schema((s) => {
|
|
120
|
+
const mySchema = schema("my", (s) => {
|
|
121
121
|
return s
|
|
122
122
|
.addTable("users", (t) => {
|
|
123
123
|
return t.addColumn("id", idColumn()).addColumn("email", column("string"));
|
|
@@ -141,7 +141,7 @@ describe("generateMigrationFromSchema", () => {
|
|
|
141
141
|
});
|
|
142
142
|
|
|
143
143
|
it("should generate mixed operations for tables and foreign keys", () => {
|
|
144
|
-
const mySchema = schema((s) => {
|
|
144
|
+
const mySchema = schema("my", (s) => {
|
|
145
145
|
return s
|
|
146
146
|
.addTable("users", (t) => {
|
|
147
147
|
return t.addColumn("id", idColumn()).addColumn("name", column("string"));
|
|
@@ -166,7 +166,7 @@ describe("generateMigrationFromSchema", () => {
|
|
|
166
166
|
});
|
|
167
167
|
|
|
168
168
|
it("should generate mixed operations for tables, indexes, and foreign keys", () => {
|
|
169
|
-
const mySchema = schema((s) => {
|
|
169
|
+
const mySchema = schema("my", (s) => {
|
|
170
170
|
return s
|
|
171
171
|
.addTable("users", (t) => {
|
|
172
172
|
return t.addColumn("id", idColumn()).addColumn("email", column("string"));
|
|
@@ -195,7 +195,7 @@ describe("generateMigrationFromSchema", () => {
|
|
|
195
195
|
});
|
|
196
196
|
|
|
197
197
|
it("should generate no operations when version range is empty", () => {
|
|
198
|
-
const mySchema = schema((s) => {
|
|
198
|
+
const mySchema = schema("my", (s) => {
|
|
199
199
|
return s.addTable("users", (t) => {
|
|
200
200
|
return t.addColumn("id", idColumn()).addColumn("name", column("string"));
|
|
201
201
|
});
|
|
@@ -207,7 +207,7 @@ describe("generateMigrationFromSchema", () => {
|
|
|
207
207
|
});
|
|
208
208
|
|
|
209
209
|
it("should throw error when fromVersion exceeds schema version", () => {
|
|
210
|
-
const mySchema = schema((s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
|
|
210
|
+
const mySchema = schema("my", (s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
|
|
211
211
|
|
|
212
212
|
expect(() => {
|
|
213
213
|
generateMigrationFromSchema(mySchema, 999, 1000);
|
|
@@ -215,7 +215,7 @@ describe("generateMigrationFromSchema", () => {
|
|
|
215
215
|
});
|
|
216
216
|
|
|
217
217
|
it("should throw error when toVersion exceeds schema version", () => {
|
|
218
|
-
const mySchema = schema((s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
|
|
218
|
+
const mySchema = schema("my", (s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
|
|
219
219
|
|
|
220
220
|
expect(() => {
|
|
221
221
|
generateMigrationFromSchema(mySchema, 0, 999);
|
|
@@ -223,7 +223,7 @@ describe("generateMigrationFromSchema", () => {
|
|
|
223
223
|
});
|
|
224
224
|
|
|
225
225
|
it("should throw error when trying to migrate backwards", () => {
|
|
226
|
-
const mySchema = schema((s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
|
|
226
|
+
const mySchema = schema("my", (s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
|
|
227
227
|
|
|
228
228
|
expect(() => {
|
|
229
229
|
generateMigrationFromSchema(mySchema, 1, 0);
|
|
@@ -231,7 +231,7 @@ describe("generateMigrationFromSchema", () => {
|
|
|
231
231
|
});
|
|
232
232
|
|
|
233
233
|
it("should throw error for negative fromVersion", () => {
|
|
234
|
-
const mySchema = schema((s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
|
|
234
|
+
const mySchema = schema("my", (s) => s.addTable("users", (t) => t.addColumn("id", idColumn())));
|
|
235
235
|
|
|
236
236
|
expect(() => {
|
|
237
237
|
generateMigrationFromSchema(mySchema, -1, 1);
|
|
@@ -239,7 +239,7 @@ describe("generateMigrationFromSchema", () => {
|
|
|
239
239
|
});
|
|
240
240
|
|
|
241
241
|
it("should only create constraints for references, not for referenceColumns", () => {
|
|
242
|
-
const mySchema = schema((s) => {
|
|
242
|
+
const mySchema = schema("my", (s) => {
|
|
243
243
|
return s
|
|
244
244
|
.addTable("posts", (t) => {
|
|
245
245
|
return t
|
|
@@ -276,7 +276,7 @@ describe("generateMigrationFromSchema", () => {
|
|
|
276
276
|
});
|
|
277
277
|
|
|
278
278
|
it("should not create duplicate foreign key constraints", () => {
|
|
279
|
-
const mySchema = schema((s) => {
|
|
279
|
+
const mySchema = schema("my", (s) => {
|
|
280
280
|
return s
|
|
281
281
|
.addTable("users", (t) => t.addColumn("id", idColumn()))
|
|
282
282
|
.addTable("posts", (t) =>
|