@fragno-dev/db 0.3.0 → 0.4.1
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 +327 -160
- package/CHANGELOG.md +74 -0
- package/README.md +24 -0
- package/dist/adapters/adapters.d.ts +1 -1
- package/dist/adapters/adapters.d.ts.map +1 -1
- package/dist/adapters/adapters.js.map +1 -1
- package/dist/adapters/generic-sql/generic-sql-adapter.d.ts +0 -3
- package/dist/adapters/generic-sql/generic-sql-adapter.d.ts.map +1 -1
- package/dist/adapters/generic-sql/generic-sql-adapter.js +11 -12
- package/dist/adapters/generic-sql/generic-sql-adapter.js.map +1 -1
- package/dist/adapters/generic-sql/generic-sql-uow-executor.js +46 -6
- package/dist/adapters/generic-sql/generic-sql-uow-executor.js.map +1 -1
- package/dist/adapters/generic-sql/migration/cold-kysely.js.map +1 -1
- package/dist/adapters/generic-sql/migration/dialect/mysql.js +1 -1
- package/dist/adapters/generic-sql/migration/dialect/mysql.js.map +1 -1
- package/dist/adapters/generic-sql/migration/dialect/postgres.js +1 -1
- package/dist/adapters/generic-sql/migration/dialect/postgres.js.map +1 -1
- package/dist/adapters/generic-sql/migration/dialect/sqlite.js +185 -19
- package/dist/adapters/generic-sql/migration/dialect/sqlite.js.map +1 -1
- package/dist/adapters/generic-sql/migration/executor.d.ts.map +1 -1
- package/dist/adapters/generic-sql/migration/executor.js +30 -3
- package/dist/adapters/generic-sql/migration/executor.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 +3 -3
- package/dist/adapters/generic-sql/migration/prepared-migrations.js.map +1 -1
- package/dist/adapters/generic-sql/migration/sql-generator.js +1 -1
- package/dist/adapters/generic-sql/migration/sql-generator.js.map +1 -1
- package/dist/adapters/generic-sql/query/create-sql-query-compiler.js +1 -1
- package/dist/adapters/generic-sql/query/create-sql-query-compiler.js.map +1 -1
- package/dist/adapters/generic-sql/query/cursor-utils.js.map +1 -1
- package/dist/adapters/generic-sql/query/db-now-sql.js +27 -0
- package/dist/adapters/generic-sql/query/db-now-sql.js.map +1 -0
- package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js +9 -6
- package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js.map +1 -1
- package/dist/adapters/generic-sql/query/select-builder.js.map +1 -1
- package/dist/adapters/generic-sql/query/sql-query-compiler.js +37 -9
- package/dist/adapters/generic-sql/query/sql-query-compiler.js.map +1 -1
- package/dist/adapters/generic-sql/query/where-builder.js +24 -20
- package/dist/adapters/generic-sql/query/where-builder.js.map +1 -1
- package/dist/adapters/generic-sql/uow-decoder.js +1 -1
- package/dist/adapters/generic-sql/uow-decoder.js.map +1 -1
- package/dist/adapters/generic-sql/uow-encoder.js +8 -9
- package/dist/adapters/generic-sql/uow-encoder.js.map +1 -1
- package/dist/adapters/in-memory/condition-evaluator.js +10 -6
- package/dist/adapters/in-memory/condition-evaluator.js.map +1 -1
- package/dist/adapters/in-memory/in-memory-adapter.d.ts.map +1 -1
- package/dist/adapters/in-memory/in-memory-adapter.js +45 -25
- package/dist/adapters/in-memory/in-memory-adapter.js.map +1 -1
- package/dist/adapters/in-memory/in-memory-uow.js +236 -13
- package/dist/adapters/in-memory/in-memory-uow.js.map +1 -1
- package/dist/adapters/in-memory/options.d.ts +2 -0
- package/dist/adapters/in-memory/options.d.ts.map +1 -1
- package/dist/adapters/in-memory/options.js +3 -2
- package/dist/adapters/in-memory/options.js.map +1 -1
- package/dist/adapters/in-memory/reference-resolution.js.map +1 -1
- package/dist/adapters/in-memory/store.js +1 -1
- package/dist/adapters/in-memory/store.js.map +1 -1
- package/dist/adapters/shared/from-unit-of-work-compiler.js +51 -24
- package/dist/adapters/shared/from-unit-of-work-compiler.js.map +1 -1
- package/dist/adapters/shared/uow-operation-compiler.js.map +1 -1
- package/dist/browser/adapters/adapters.d.ts +61 -0
- package/dist/browser/adapters/adapters.d.ts.map +1 -0
- package/dist/browser/adapters/generic-sql/migration/executor.d.ts +15 -0
- package/dist/browser/adapters/generic-sql/migration/executor.d.ts.map +1 -0
- package/dist/browser/adapters/generic-sql/migration/prepared-migrations.d.ts +66 -0
- package/dist/browser/adapters/generic-sql/migration/prepared-migrations.d.ts.map +1 -0
- package/dist/browser/adapters/generic-sql/sqlite-storage.d.ts +11 -0
- package/dist/browser/adapters/generic-sql/sqlite-storage.d.ts.map +1 -0
- package/dist/browser/adapters/in-memory/in-memory-adapter.d.ts +5 -0
- package/dist/browser/adapters/in-memory/index.d.ts +2 -0
- package/dist/browser/adapters/in-memory/options.d.ts +1 -0
- package/dist/browser/db-fragment-definition-builder.d.ts +237 -0
- package/dist/browser/db-fragment-definition-builder.d.ts.map +1 -0
- package/dist/browser/durable-hooks.d.ts +3 -0
- package/dist/browser/fragments/internal-fragment.d.ts +317 -0
- package/dist/browser/fragments/internal-fragment.d.ts.map +1 -0
- package/dist/browser/fragments/internal-fragment.schema.d.ts +1 -0
- package/dist/browser/hooks/durable-hooks-logger.d.ts +10 -0
- package/dist/browser/hooks/durable-hooks-logger.d.ts.map +1 -0
- package/dist/browser/hooks/hooks.d.ts +146 -0
- package/dist/browser/hooks/hooks.d.ts.map +1 -0
- package/dist/browser/id.js +1 -0
- package/dist/browser/internal/adapter-registry.d.ts +4 -0
- package/dist/browser/internal/outbox-state.d.ts +2 -0
- package/dist/browser/mod.d.ts +15 -0
- package/dist/browser/mod.d.ts.map +1 -0
- package/dist/browser/mod.js +17 -0
- package/dist/browser/mod.js.map +1 -0
- package/dist/browser/mod2.d.ts +48 -0
- package/dist/browser/mod2.d.ts.map +1 -0
- package/dist/browser/naming/sql-naming.d.ts +19 -0
- package/dist/browser/naming/sql-naming.d.ts.map +1 -0
- package/dist/browser/outbox/outbox.d.ts +21 -0
- package/dist/browser/outbox/outbox.d.ts.map +1 -0
- package/dist/browser/query/column-defaults.js +1 -0
- package/dist/browser/query/condition-builder.d.ts +44 -0
- package/dist/browser/query/condition-builder.d.ts.map +1 -0
- package/dist/browser/query/condition-builder.js +97 -0
- package/dist/browser/query/condition-builder.js.map +1 -0
- package/dist/browser/query/cursor.d.ts +105 -0
- package/dist/browser/query/cursor.d.ts.map +1 -0
- package/dist/browser/query/cursor.js +150 -0
- package/dist/browser/query/cursor.js.map +1 -0
- package/dist/browser/query/db-now.d.ts +22 -0
- package/dist/browser/query/db-now.d.ts.map +1 -0
- package/dist/browser/query/db-now.js +33 -0
- package/dist/browser/query/db-now.js.map +1 -0
- package/dist/browser/query/orm/orm.d.ts +18 -0
- package/dist/browser/query/orm/orm.d.ts.map +1 -0
- package/dist/browser/query/simple-query-interface.d.ts +108 -0
- package/dist/browser/query/simple-query-interface.d.ts.map +1 -0
- package/dist/browser/query/unit-of-work/execute-unit-of-work.d.ts +423 -0
- package/dist/browser/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -0
- package/dist/browser/query/unit-of-work/execute-unit-of-work.js +507 -0
- package/dist/browser/query/unit-of-work/execute-unit-of-work.js.map +1 -0
- package/dist/browser/query/unit-of-work/retry-policy.d.ts +23 -0
- package/dist/browser/query/unit-of-work/retry-policy.d.ts.map +1 -0
- package/dist/browser/query/unit-of-work/retry-policy.js +40 -0
- package/dist/browser/query/unit-of-work/retry-policy.js.map +1 -0
- package/dist/browser/query/unit-of-work/unit-of-work.d.ts +703 -0
- package/dist/browser/query/unit-of-work/unit-of-work.d.ts.map +1 -0
- package/dist/browser/query/unit-of-work/unit-of-work.js +1206 -0
- package/dist/browser/query/unit-of-work/unit-of-work.js.map +1 -0
- package/dist/browser/query/value-encoding.js +38 -0
- package/dist/browser/query/value-encoding.js.map +1 -0
- package/dist/browser/schema/create.d.ts +326 -0
- package/dist/browser/schema/create.d.ts.map +1 -0
- package/dist/browser/schema/create.js +89 -0
- package/dist/browser/schema/create.js.map +1 -0
- package/dist/browser/schema/generate-id.js +28 -0
- package/dist/browser/schema/generate-id.js.map +1 -0
- package/dist/browser/shared/providers.d.ts +6 -0
- package/dist/browser/shared/providers.d.ts.map +1 -0
- package/dist/browser/sql-driver/connection/connection-provider.d.ts +13 -0
- package/dist/browser/sql-driver/connection/connection-provider.d.ts.map +1 -0
- package/dist/browser/sql-driver/dialect-adapter/dialect-adapter.d.ts +7 -0
- package/dist/browser/sql-driver/dialect-adapter/dialect-adapter.d.ts.map +1 -0
- package/dist/browser/sql-driver/driver/runtime-driver.d.ts +23 -0
- package/dist/browser/sql-driver/driver/runtime-driver.d.ts.map +1 -0
- package/dist/browser/sql-driver/query-executor/plugin.d.ts +17 -0
- package/dist/browser/sql-driver/query-executor/plugin.d.ts.map +1 -0
- package/dist/browser/sql-driver/query-executor/query-executor.d.ts +36 -0
- package/dist/browser/sql-driver/query-executor/query-executor.d.ts.map +1 -0
- package/dist/browser/sql-driver/sql-driver-adapter.d.ts +29 -0
- package/dist/browser/sql-driver/sql-driver-adapter.d.ts.map +1 -0
- package/dist/browser/sql-driver/sql-driver.d.ts +38 -0
- package/dist/browser/sql-driver/sql-driver.d.ts.map +1 -0
- package/dist/browser/sync/commands.d.ts +15 -0
- package/dist/browser/sync/commands.d.ts.map +1 -0
- package/dist/browser/sync/commands.js +27 -0
- package/dist/browser/sync/commands.js.map +1 -0
- package/dist/browser/sync/types.d.ts +63 -0
- package/dist/browser/sync/types.d.ts.map +1 -0
- package/dist/browser/util/types.d.ts +8 -0
- package/dist/browser/util/types.d.ts.map +1 -0
- package/dist/browser/with-database.d.ts +29 -0
- package/dist/browser/with-database.d.ts.map +1 -0
- package/dist/client.d.ts +4 -0
- package/dist/client.js +5 -0
- package/dist/db-fragment-definition-builder.d.ts +85 -28
- package/dist/db-fragment-definition-builder.d.ts.map +1 -1
- package/dist/db-fragment-definition-builder.js +374 -46
- package/dist/db-fragment-definition-builder.js.map +1 -1
- package/dist/dispatchers/cloudflare-do/dispatcher.d.ts +20 -0
- package/dist/dispatchers/cloudflare-do/dispatcher.d.ts.map +1 -0
- package/dist/dispatchers/cloudflare-do/dispatcher.js +147 -0
- package/dist/dispatchers/cloudflare-do/dispatcher.js.map +1 -0
- package/dist/dispatchers/cloudflare-do/index.d.ts +5 -20
- package/dist/dispatchers/cloudflare-do/index.d.ts.map +1 -1
- package/dist/dispatchers/cloudflare-do/index.js +23 -55
- package/dist/dispatchers/cloudflare-do/index.js.map +1 -1
- package/dist/dispatchers/node/dispatcher.d.ts +14 -0
- package/dist/dispatchers/node/dispatcher.d.ts.map +1 -0
- package/dist/dispatchers/node/dispatcher.js +80 -0
- package/dist/dispatchers/node/dispatcher.js.map +1 -0
- package/dist/dispatchers/node/index.d.ts +5 -10
- package/dist/dispatchers/node/index.d.ts.map +1 -1
- package/dist/dispatchers/node/index.js +21 -53
- package/dist/dispatchers/node/index.js.map +1 -1
- package/dist/durable-hooks.d.ts +31 -0
- package/dist/durable-hooks.d.ts.map +1 -0
- package/dist/durable-hooks.js +23 -0
- package/dist/durable-hooks.js.map +1 -0
- package/dist/fragments/internal-fragment.d.ts +128 -27
- package/dist/fragments/internal-fragment.d.ts.map +1 -1
- package/dist/fragments/internal-fragment.js +125 -78
- package/dist/fragments/internal-fragment.js.map +1 -1
- package/dist/fragments/internal-fragment.routes.js +138 -3
- package/dist/fragments/internal-fragment.routes.js.map +1 -1
- package/dist/fragments/internal-fragment.schema.d.ts +7 -1
- package/dist/fragments/internal-fragment.schema.d.ts.map +1 -1
- package/dist/fragments/internal-fragment.schema.js +18 -1
- package/dist/fragments/internal-fragment.schema.js.map +1 -1
- package/dist/hooks/durable-hooks-logger.d.ts +10 -0
- package/dist/hooks/durable-hooks-logger.d.ts.map +1 -0
- package/dist/hooks/durable-hooks-logger.js +75 -0
- package/dist/hooks/durable-hooks-logger.js.map +1 -0
- package/dist/hooks/durable-hooks-processor.d.ts +1 -14
- package/dist/hooks/durable-hooks-processor.js +58 -10
- package/dist/hooks/durable-hooks-processor.js.map +1 -1
- package/dist/hooks/durable-hooks-runtime.js +44 -0
- package/dist/hooks/durable-hooks-runtime.js.map +1 -0
- package/dist/hooks/hooks.d.ts +60 -2
- package/dist/hooks/hooks.d.ts.map +1 -1
- package/dist/hooks/hooks.js +214 -53
- package/dist/hooks/hooks.js.map +1 -1
- package/dist/id.d.ts +2 -2
- package/dist/id.js +2 -2
- package/dist/internal/adapter-registry.d.ts +11 -0
- package/dist/internal/adapter-registry.d.ts.map +1 -0
- package/dist/internal/adapter-registry.js +135 -0
- package/dist/internal/adapter-registry.js.map +1 -0
- package/dist/internal/outbox-state.d.ts +2 -0
- package/dist/internal/outbox-state.js +26 -0
- package/dist/internal/outbox-state.js.map +1 -0
- package/dist/migration-engine/auto-from-schema.d.ts +33 -0
- package/dist/migration-engine/auto-from-schema.d.ts.map +1 -0
- package/dist/migration-engine/auto-from-schema.js +210 -27
- package/dist/migration-engine/auto-from-schema.js.map +1 -1
- package/dist/migration-engine/generation-engine.d.ts.map +1 -1
- package/dist/migration-engine/generation-engine.js +17 -5
- package/dist/migration-engine/generation-engine.js.map +1 -1
- package/dist/migration-engine/shared.d.ts +113 -0
- package/dist/migration-engine/shared.d.ts.map +1 -0
- package/dist/migration-engine/shared.js.map +1 -1
- package/dist/mod.d.ts +12 -11
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +10 -10
- package/dist/mod.js.map +1 -1
- package/dist/naming/sql-naming.d.ts.map +1 -1
- package/dist/naming/sql-naming.js.map +1 -1
- package/dist/outbox/outbox-builder.js.map +1 -1
- package/dist/outbox/outbox.d.ts +3 -1
- package/dist/outbox/outbox.d.ts.map +1 -1
- package/dist/outbox/outbox.js.map +1 -1
- package/dist/query/column-defaults.js.map +1 -1
- package/dist/query/condition-builder.d.ts +7 -1
- package/dist/query/condition-builder.d.ts.map +1 -1
- package/dist/query/condition-builder.js +5 -1
- package/dist/query/condition-builder.js.map +1 -1
- package/dist/query/cursor-client.d.ts +105 -0
- package/dist/query/cursor-client.d.ts.map +1 -0
- package/dist/query/cursor-client.js +165 -0
- package/dist/query/cursor-client.js.map +1 -0
- package/dist/query/cursor.d.ts.map +1 -1
- package/dist/query/cursor.js +7 -1
- package/dist/query/cursor.js.map +1 -1
- package/dist/query/db-now.d.ts +15 -1
- package/dist/query/db-now.d.ts.map +1 -1
- package/dist/query/db-now.js +30 -2
- package/dist/query/db-now.js.map +1 -1
- package/dist/query/orm/orm.js.map +1 -1
- package/dist/query/serialize/create-sql-serializer.js +2 -2
- package/dist/query/serialize/create-sql-serializer.js.map +1 -1
- package/dist/query/serialize/dialect/mysql-serializer.js.map +1 -1
- package/dist/query/serialize/dialect/postgres-serializer.js.map +1 -1
- package/dist/query/serialize/dialect/sqlite-serializer.js +6 -2
- package/dist/query/serialize/dialect/sqlite-serializer.js.map +1 -1
- package/dist/query/simple-query-interface.d.ts +7 -3
- package/dist/query/simple-query-interface.d.ts.map +1 -1
- package/dist/query/unit-of-work/execute-unit-of-work.d.ts +37 -2
- 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 +39 -18
- 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 +42 -16
- package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work/unit-of-work.js +50 -6
- package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
- package/dist/query/value-decoding.js +8 -1
- package/dist/query/value-decoding.js.map +1 -1
- package/dist/query/value-encoding.js.map +1 -1
- package/dist/schema/create.d.ts +69 -25
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +91 -16
- package/dist/schema/create.js.map +1 -1
- package/dist/schema/type-conversion/create-sql-type-mapper.js +1 -1
- package/dist/schema/type-conversion/create-sql-type-mapper.js.map +1 -1
- package/dist/schema/type-conversion/dialect/sqlite.js.map +1 -1
- package/dist/schema/validator.d.ts.map +1 -1
- package/dist/schema/validator.js.map +1 -1
- package/dist/schema-output/drizzle.d.ts.map +1 -1
- package/dist/schema-output/drizzle.js +8 -6
- package/dist/schema-output/drizzle.js.map +1 -1
- package/dist/schema-output/prisma.d.ts.map +1 -1
- package/dist/schema-output/prisma.js +21 -10
- package/dist/schema-output/prisma.js.map +1 -1
- package/dist/sql-driver/dialects/durable-object-dialect.js +3 -9
- package/dist/sql-driver/dialects/durable-object-dialect.js.map +1 -1
- package/dist/sql-driver/query-executor/default-query-executor.js.map +1 -1
- package/dist/sql-driver/query-executor/query-executor-base.js.map +1 -1
- package/dist/sql-driver/sql-driver-adapter.js.map +1 -1
- package/dist/sql-driver/sql.js.map +1 -1
- package/dist/sync/commands.d.ts +15 -0
- package/dist/sync/commands.d.ts.map +1 -0
- package/dist/sync/commands.js +27 -0
- package/dist/sync/commands.js.map +1 -0
- package/dist/sync/index.d.ts +4 -0
- package/dist/sync/index.js +4 -0
- package/dist/sync/read-tracking.d.ts +25 -0
- package/dist/sync/read-tracking.d.ts.map +1 -0
- package/dist/sync/read-tracking.js +148 -0
- package/dist/sync/read-tracking.js.map +1 -0
- package/dist/sync/submit.js +213 -0
- package/dist/sync/submit.js.map +1 -0
- package/dist/sync/types.d.ts +63 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/util/default-database-adapter.js +6 -1
- package/dist/util/default-database-adapter.js.map +1 -1
- package/dist/with-database.d.ts +3 -6
- package/dist/with-database.d.ts.map +1 -1
- package/dist/with-database.js +7 -15
- package/dist/with-database.js.map +1 -1
- package/package.json +33 -41
- package/src/adapters/adapters.ts +5 -4
- package/src/adapters/drizzle/migrate-drizzle.test.ts +46 -9
- package/src/adapters/drizzle/migration-parity-drizzle-kit.test.ts +5 -3
- package/src/adapters/drizzle/test-utils.ts +2 -1
- package/src/adapters/generic-sql/generic-sql-adapter.test.ts +5 -3
- package/src/adapters/generic-sql/generic-sql-adapter.ts +21 -24
- package/src/adapters/generic-sql/generic-sql-uow-executor.test.ts +1 -0
- package/src/adapters/generic-sql/generic-sql-uow-executor.ts +81 -15
- package/src/adapters/generic-sql/migration/adapter-migration-parity.test.ts +4 -2
- package/src/adapters/generic-sql/migration/cold-kysely.ts +1 -0
- package/src/adapters/generic-sql/migration/dialect/mysql.test.ts +3 -2
- package/src/adapters/generic-sql/migration/dialect/mysql.ts +1 -0
- package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +5 -4
- package/src/adapters/generic-sql/migration/dialect/postgres.ts +2 -1
- package/src/adapters/generic-sql/migration/dialect/sqlite.test.ts +795 -3
- package/src/adapters/generic-sql/migration/dialect/sqlite.ts +385 -57
- package/src/adapters/generic-sql/migration/executor.test.ts +52 -0
- package/src/adapters/generic-sql/migration/executor.ts +47 -4
- package/src/adapters/generic-sql/migration/prepared-migrations.test.ts +117 -14
- package/src/adapters/generic-sql/migration/prepared-migrations.ts +9 -8
- package/src/adapters/generic-sql/migration/sql-generator.ts +5 -3
- package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +3 -3
- package/src/adapters/generic-sql/query/cursor-utils.test.ts +3 -2
- package/src/adapters/generic-sql/query/cursor-utils.ts +1 -1
- package/src/adapters/generic-sql/query/db-now-sql.ts +49 -0
- package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.test.ts +144 -8
- package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +16 -17
- package/src/adapters/generic-sql/query/select-builder.test.ts +1 -0
- package/src/adapters/generic-sql/query/select-builder.ts +2 -2
- package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +24 -5
- package/src/adapters/generic-sql/query/sql-query-compiler.ts +83 -13
- package/src/adapters/generic-sql/query/where-builder.test.ts +7 -5
- package/src/adapters/generic-sql/query/where-builder.ts +48 -29
- package/src/adapters/generic-sql/sql-adapter-pglite-migrations.test.ts +6 -15
- package/src/adapters/generic-sql/sql-adapter-pglite-pagination.test.ts +52 -7
- package/src/adapters/generic-sql/sql-adapter-pglite-queries.test.ts +9 -6
- package/src/adapters/generic-sql/sql-adapter-sqlite3-driver.test.ts +273 -5
- package/src/adapters/generic-sql/sql-adapter-sqlite3-uow.test.ts +123 -6
- package/src/adapters/generic-sql/sql-adapter-sqlocal.test.ts +4 -2
- package/src/adapters/generic-sql/uow-decoder.test.ts +4 -3
- package/src/adapters/generic-sql/uow-decoder.ts +3 -3
- package/src/adapters/generic-sql/uow-encoder.test.ts +4 -2
- package/src/adapters/generic-sql/uow-encoder.ts +14 -18
- package/src/adapters/in-memory/condition-evaluator.test.ts +2 -1
- package/src/adapters/in-memory/condition-evaluator.ts +9 -4
- package/src/adapters/in-memory/in-memory-adapter.ts +155 -44
- package/src/adapters/in-memory/in-memory-uow.mutations.test.ts +50 -2
- package/src/adapters/in-memory/in-memory-uow.retrieval.test.ts +158 -3
- package/src/adapters/in-memory/in-memory-uow.ts +402 -26
- package/src/adapters/in-memory/options.test.ts +1 -0
- package/src/adapters/in-memory/options.ts +5 -1
- package/src/adapters/in-memory/outbox.test.ts +361 -0
- package/src/adapters/in-memory/reference-resolution.test.ts +3 -2
- package/src/adapters/in-memory/reference-resolution.ts +2 -2
- package/src/adapters/in-memory/sorted-array-index.test.ts +1 -0
- package/src/adapters/in-memory/store.test.ts +1 -0
- package/src/adapters/in-memory/store.ts +3 -3
- package/src/adapters/in-memory/value-normalization.test.ts +1 -0
- package/src/adapters/prisma/prisma-adapter-sqlite3.test.ts +51 -7
- package/src/adapters/shared/from-unit-of-work-compiler.ts +156 -46
- package/src/adapters/shared/uow-operation-compiler.ts +3 -3
- package/src/browser/mod.ts +64 -0
- package/src/client.ts +19 -0
- package/src/db-fragment-definition-builder.test.ts +821 -47
- package/src/db-fragment-definition-builder.ts +857 -110
- package/src/db-fragment-instantiator.test.ts +114 -90
- package/src/db-fragment-integration.test.ts +9 -6
- package/src/dispatchers/cloudflare-do/dispatcher.ts +204 -0
- package/src/dispatchers/cloudflare-do/index.test.ts +145 -12
- package/src/dispatchers/cloudflare-do/index.ts +49 -90
- package/src/dispatchers/node/dispatcher.ts +112 -0
- package/src/dispatchers/node/index.test.ts +43 -14
- package/src/dispatchers/node/index.ts +38 -75
- package/src/durable-hooks.test.ts +80 -0
- package/src/durable-hooks.ts +67 -0
- package/src/fragments/internal-fragment.routes.test.ts +570 -0
- package/src/fragments/internal-fragment.routes.ts +297 -5
- package/src/fragments/internal-fragment.schema.ts +45 -1
- package/src/fragments/internal-fragment.test.ts +223 -251
- package/src/fragments/internal-fragment.ts +278 -154
- package/src/hooks/durable-hooks-logger.ts +126 -0
- package/src/hooks/durable-hooks-processor.pglite.test.ts +87 -0
- package/src/hooks/durable-hooks-processor.test.ts +179 -14
- package/src/hooks/durable-hooks-processor.ts +120 -14
- package/src/hooks/durable-hooks-runtime.test.ts +65 -0
- package/src/hooks/durable-hooks-runtime.ts +81 -0
- package/src/hooks/hooks.test.ts +314 -53
- package/src/hooks/hooks.ts +360 -81
- package/src/id.test.ts +34 -0
- package/src/id.ts +1 -3
- package/src/internal/adapter-registry.test.ts +93 -0
- package/src/internal/adapter-registry.ts +239 -0
- package/src/internal/outbox-state.ts +43 -0
- package/src/migration-engine/auto-from-schema.test.ts +93 -0
- package/src/migration-engine/auto-from-schema.ts +360 -42
- package/src/migration-engine/create.test.ts +2 -1
- package/src/migration-engine/create.ts +1 -1
- package/src/migration-engine/generation-engine.test.ts +66 -9
- package/src/migration-engine/generation-engine.ts +31 -10
- package/src/migration-engine/shared.ts +13 -0
- package/src/mod.ts +45 -27
- package/src/naming/sql-naming.ts +1 -0
- package/src/outbox/outbox-builder.ts +2 -2
- package/src/outbox/outbox.test.ts +216 -45
- package/src/outbox/outbox.ts +3 -1
- package/src/query/column-defaults.ts +1 -1
- package/src/query/condition-builder.test.ts +15 -0
- package/src/query/condition-builder.ts +7 -0
- package/src/query/cursor-client.test.ts +70 -0
- package/src/query/cursor-client.ts +263 -0
- package/src/query/cursor.test.ts +3 -2
- package/src/query/cursor.ts +15 -3
- package/src/query/db-now.ts +69 -2
- package/src/query/orm/orm.ts +2 -2
- package/src/query/query-type.test.ts +2 -1
- package/src/query/serialize/create-sql-serializer.ts +3 -3
- package/src/query/serialize/dialect/mysql-serializer.ts +1 -1
- package/src/query/serialize/dialect/postgres-serializer.ts +1 -1
- package/src/query/serialize/dialect/sqlite-serializer.test.ts +39 -2
- package/src/query/serialize/dialect/sqlite-serializer.ts +18 -5
- package/src/query/simple-query-interface.ts +10 -4
- package/src/query/unit-of-work/execute-unit-of-work.test.ts +347 -9
- package/src/query/unit-of-work/execute-unit-of-work.ts +63 -20
- package/src/query/unit-of-work/retry-policy.test.ts +1 -0
- package/src/query/unit-of-work/tx-builder.test.ts +73 -1
- package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +5 -4
- package/src/query/unit-of-work/unit-of-work-types.test.ts +41 -11
- package/src/query/unit-of-work/unit-of-work.test.ts +28 -2
- package/src/query/unit-of-work/unit-of-work.ts +105 -19
- package/src/query/value-decoding.test.ts +50 -2
- package/src/query/value-decoding.ts +17 -4
- package/src/query/value-encoding.test.ts +1 -0
- package/src/query/value-encoding.ts +1 -1
- package/src/schema/create.test.ts +164 -5
- package/src/schema/create.ts +222 -24
- package/src/schema/generate-id.test.ts +1 -0
- package/src/schema/serialize.test.ts +4 -3
- package/src/schema/type-conversion/create-sql-type-mapper.ts +1 -1
- package/src/schema/type-conversion/dialect/sqlite.ts +2 -2
- package/src/schema/type-conversion/type-mapping.test.ts +2 -1
- package/src/schema/validator.test.ts +4 -2
- package/src/schema/validator.ts +1 -0
- package/src/schema-output/drizzle.test.ts +72 -19
- package/src/schema-output/drizzle.ts +24 -18
- package/src/schema-output/prisma.test.ts +172 -14
- package/src/schema-output/prisma.ts +34 -14
- package/src/sql-driver/better-sqlite3.test.ts +5 -3
- package/src/sql-driver/dialects/durable-object-dialect.ts +3 -8
- package/src/sql-driver/query-executor/default-query-executor.ts +1 -1
- package/src/sql-driver/query-executor/query-executor-base.ts +1 -1
- package/src/sql-driver/query-executor/query-executor.ts +1 -1
- package/src/sql-driver/sql-driver-adapter.ts +2 -2
- package/src/sql-driver/sql.ts +2 -1
- package/src/sql-driver/sqlocal.test.ts +4 -2
- package/src/sync/commands.test.ts +39 -0
- package/src/sync/commands.ts +51 -0
- package/src/sync/conflict-checker.test.ts +450 -0
- package/src/sync/conflict-checker.ts +248 -0
- package/src/sync/index.ts +14 -0
- package/src/sync/plan.ts +9 -0
- package/src/sync/read-tracking.test.ts +177 -0
- package/src/sync/read-tracking.ts +287 -0
- package/src/sync/submit.test.ts +205 -0
- package/src/sync/submit.ts +328 -0
- package/src/sync/types.ts +80 -0
- package/src/util/default-database-adapter.ts +15 -2
- package/src/with-database.ts +20 -50
- package/tsconfig.json +1 -1
- package/tsdown.config.ts +38 -26
- package/vitest.config.ts +1 -0
- package/dist/hooks/durable-hooks-processor.d.ts.map +0 -1
- package/dist/node_modules/.pnpm/rou3@0.7.12/node_modules/rou3/dist/index.js +0 -168
- package/dist/node_modules/.pnpm/rou3@0.7.12/node_modules/rou3/dist/index.js.map +0 -1
- package/dist/packages/fragno/dist/api/bind-services.js +0 -20
- package/dist/packages/fragno/dist/api/bind-services.js.map +0 -1
- package/dist/packages/fragno/dist/api/error.js +0 -48
- package/dist/packages/fragno/dist/api/error.js.map +0 -1
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js +0 -321
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +0 -1
- package/dist/packages/fragno/dist/api/fragment-instantiator.js +0 -669
- package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +0 -1
- package/dist/packages/fragno/dist/api/fragno-response.js +0 -73
- package/dist/packages/fragno/dist/api/fragno-response.js.map +0 -1
- package/dist/packages/fragno/dist/api/internal/response-stream.js +0 -81
- package/dist/packages/fragno/dist/api/internal/response-stream.js.map +0 -1
- package/dist/packages/fragno/dist/api/internal/route.js +0 -10
- package/dist/packages/fragno/dist/api/internal/route.js.map +0 -1
- package/dist/packages/fragno/dist/api/mutable-request-state.js +0 -97
- package/dist/packages/fragno/dist/api/mutable-request-state.js.map +0 -1
- package/dist/packages/fragno/dist/api/request-context-storage.js +0 -43
- package/dist/packages/fragno/dist/api/request-context-storage.js.map +0 -1
- package/dist/packages/fragno/dist/api/request-input-context.js +0 -185
- package/dist/packages/fragno/dist/api/request-input-context.js.map +0 -1
- package/dist/packages/fragno/dist/api/request-middleware.js +0 -83
- package/dist/packages/fragno/dist/api/request-middleware.js.map +0 -1
- package/dist/packages/fragno/dist/api/request-output-context.js +0 -119
- package/dist/packages/fragno/dist/api/request-output-context.js.map +0 -1
- package/dist/packages/fragno/dist/api/route.js +0 -30
- package/dist/packages/fragno/dist/api/route.js.map +0 -1
- package/dist/packages/fragno/dist/internal/symbols.js +0 -10
- package/dist/packages/fragno/dist/internal/symbols.js.map +0 -1
- package/dist/packages/fragno/dist/internal/trace-context.js +0 -12
- package/dist/packages/fragno/dist/internal/trace-context.js.map +0 -1
|
@@ -1,36 +1,107 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import type { DatabaseAdapter } from "./adapters/adapters";
|
|
4
|
-
import type { IUnitOfWork } from "./query/unit-of-work/unit-of-work";
|
|
1
|
+
import { FragnoApiError } from "@fragno-dev/core/api";
|
|
2
|
+
|
|
5
3
|
import type {
|
|
6
4
|
RequestThisContext,
|
|
7
5
|
FragnoPublicConfig,
|
|
8
|
-
|
|
6
|
+
AnyRouteOrFactory,
|
|
7
|
+
FragnoRouteConfig,
|
|
8
|
+
BoundServices,
|
|
9
9
|
} from "@fragno-dev/core";
|
|
10
10
|
import {
|
|
11
11
|
FragmentDefinitionBuilder,
|
|
12
12
|
type FragmentDefinition,
|
|
13
13
|
type ServiceConstructorFn,
|
|
14
14
|
} from "@fragno-dev/core";
|
|
15
|
+
|
|
16
|
+
import type { DatabaseAdapter, DatabaseContextStorage } from "./adapters/adapters";
|
|
17
|
+
import type { InternalFragmentInstance } from "./fragments/internal-fragment";
|
|
18
|
+
import { DurableHooksLogger } from "./hooks/durable-hooks-logger";
|
|
15
19
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
type ExecuteTxOptions,
|
|
21
|
-
} from "./query/unit-of-work/execute-unit-of-work";
|
|
20
|
+
getDurableHooksRuntimeByConfig,
|
|
21
|
+
getDurableHooksRuntimeByNamespace,
|
|
22
|
+
registerDurableHooksRuntime,
|
|
23
|
+
} from "./hooks/durable-hooks-runtime";
|
|
22
24
|
import {
|
|
23
25
|
prepareHookMutations,
|
|
24
26
|
type HooksMap,
|
|
25
27
|
type HookFn,
|
|
26
28
|
type HookContext,
|
|
27
29
|
type HookProcessorConfig,
|
|
30
|
+
type HookNotifier,
|
|
31
|
+
type HookNotifyContext,
|
|
28
32
|
type DurableHooksProcessingOptions,
|
|
29
|
-
|
|
33
|
+
createDurableHooksRunner,
|
|
30
34
|
} from "./hooks/hooks";
|
|
31
|
-
import type { InternalFragmentInstance } from "./fragments/internal-fragment";
|
|
32
|
-
import { resolveDatabaseAdapter } from "./util/default-database-adapter";
|
|
33
35
|
import { sanitizeNamespace } from "./naming/sql-naming";
|
|
36
|
+
import type { SimpleQueryInterface } from "./query/simple-query-interface";
|
|
37
|
+
import {
|
|
38
|
+
createServiceTxBuilder,
|
|
39
|
+
createHandlerTxBuilder,
|
|
40
|
+
ServiceTxBuilder,
|
|
41
|
+
HandlerTxBuilder,
|
|
42
|
+
type AwaitedPromisesInObject,
|
|
43
|
+
type ExtractServiceFinalResults,
|
|
44
|
+
type ExecuteTxOptions,
|
|
45
|
+
type TxResult,
|
|
46
|
+
} from "./query/unit-of-work/execute-unit-of-work";
|
|
47
|
+
import type { IUnitOfWork } from "./query/unit-of-work/unit-of-work";
|
|
48
|
+
import type { AnySchema } from "./schema/create";
|
|
49
|
+
import type { SyncCommandRegistry, SyncCommandTargetRegistration } from "./sync/types";
|
|
50
|
+
import { resolveDatabaseAdapter } from "./util/default-database-adapter";
|
|
51
|
+
type RegistrySchemaInfo = {
|
|
52
|
+
name: string;
|
|
53
|
+
namespace: string | null;
|
|
54
|
+
version: number;
|
|
55
|
+
tables: string[];
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
type RegistryFragmentMeta = {
|
|
59
|
+
name: string;
|
|
60
|
+
mountRoute: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
type ExtractServiceFinalResultOrSingle<T> = T extends readonly (
|
|
64
|
+
| TxResult<unknown, unknown>
|
|
65
|
+
| undefined
|
|
66
|
+
)[]
|
|
67
|
+
? AwaitedPromisesInObject<ExtractServiceFinalResults<T>>
|
|
68
|
+
: T extends undefined
|
|
69
|
+
? undefined
|
|
70
|
+
: AwaitedPromisesInObject<ExtractServiceFinalResults<readonly [T]>>[0];
|
|
71
|
+
|
|
72
|
+
type RegistryResolver = {
|
|
73
|
+
getRegistryForAdapterSync: <TUOWConfig>(adapter: DatabaseAdapter<TUOWConfig>) => {
|
|
74
|
+
registerSchema: (
|
|
75
|
+
schema: RegistrySchemaInfo,
|
|
76
|
+
fragment: RegistryFragmentMeta,
|
|
77
|
+
options?: { outboxEnabled?: boolean },
|
|
78
|
+
) => void;
|
|
79
|
+
registerSyncCommands: (registration: SyncCommandTargetRegistration) => void;
|
|
80
|
+
};
|
|
81
|
+
getInternalFragment: <TUOWConfig>(
|
|
82
|
+
adapter: DatabaseAdapter<TUOWConfig>,
|
|
83
|
+
) => InternalFragmentInstance;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
type HooksFactoryContext<TConfig> = {
|
|
87
|
+
config: TConfig;
|
|
88
|
+
options: FragnoPublicConfigWithDatabase;
|
|
89
|
+
deps: unknown;
|
|
90
|
+
services: unknown;
|
|
91
|
+
serviceDeps: unknown;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
type AnyHttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
|
|
95
|
+
|
|
96
|
+
type AnyFragnoRouteConfig = FragnoRouteConfig<
|
|
97
|
+
AnyHttpMethod,
|
|
98
|
+
string,
|
|
99
|
+
undefined,
|
|
100
|
+
undefined,
|
|
101
|
+
string,
|
|
102
|
+
string,
|
|
103
|
+
RequestThisContext
|
|
104
|
+
>;
|
|
34
105
|
|
|
35
106
|
/**
|
|
36
107
|
* Extended FragnoPublicConfig for database fragments.
|
|
@@ -39,6 +110,19 @@ import { sanitizeNamespace } from "./naming/sql-naming";
|
|
|
39
110
|
export type FragnoPublicConfigWithDatabase = FragnoPublicConfig & {
|
|
40
111
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
112
|
databaseAdapter?: DatabaseAdapter<any>;
|
|
113
|
+
/**
|
|
114
|
+
* Optional guard to limit database roundtrips per request (primarily for tests).
|
|
115
|
+
* When enabled, retrieve-only and mutate-only handlerTx().execute() calls are
|
|
116
|
+
* counted separately (one of each by default).
|
|
117
|
+
* Applied only for route handlers (not inContext).
|
|
118
|
+
*/
|
|
119
|
+
dbRoundtripGuard?: boolean | DbRoundtripGuardConfig;
|
|
120
|
+
/**
|
|
121
|
+
* Optional outbox configuration for this fragment.
|
|
122
|
+
*/
|
|
123
|
+
outbox?: {
|
|
124
|
+
enabled?: boolean;
|
|
125
|
+
};
|
|
42
126
|
/**
|
|
43
127
|
* Optional durable hooks processing configuration.
|
|
44
128
|
*/
|
|
@@ -51,16 +135,27 @@ export type FragnoPublicConfigWithDatabase = FragnoPublicConfig & {
|
|
|
51
135
|
databaseNamespace?: string | null;
|
|
52
136
|
};
|
|
53
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Configuration for limiting database roundtrips per request.
|
|
140
|
+
*/
|
|
141
|
+
export type DbRoundtripGuardConfig = {
|
|
142
|
+
/**
|
|
143
|
+
* Maximum allowed retrieve-only and mutate-only handlerTx().execute() calls per request.
|
|
144
|
+
* Each type is tracked separately.
|
|
145
|
+
* Defaults to 1 when the guard is enabled.
|
|
146
|
+
*/
|
|
147
|
+
maxRoundtrips?: number;
|
|
148
|
+
};
|
|
149
|
+
|
|
54
150
|
/**
|
|
55
151
|
* Implicit dependencies that database fragments get automatically.
|
|
56
152
|
* These are injected without requiring explicit configuration.
|
|
57
153
|
*/
|
|
58
154
|
export type ImplicitDatabaseDependencies<TSchema extends AnySchema> = {
|
|
59
155
|
/**
|
|
60
|
-
* Database
|
|
61
|
-
* @deprecated Prefer handlerTx/serviceTx instead of direct db usage.
|
|
156
|
+
* Database adapter instance.
|
|
62
157
|
*/
|
|
63
|
-
|
|
158
|
+
databaseAdapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
64
159
|
/**
|
|
65
160
|
* The schema definition for this fragment.
|
|
66
161
|
*/
|
|
@@ -137,19 +232,34 @@ export type DatabaseHandlerContext<THooks extends HooksMap = {}> = RequestThisCo
|
|
|
137
232
|
handlerTx(
|
|
138
233
|
options?: Omit<ExecuteTxOptions, "createUnitOfWork">,
|
|
139
234
|
): HandlerTxBuilder<readonly [], [], [], unknown, unknown, false, false, false, false, THooks>;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Execute multiple service calls in a handler context and return their final results.
|
|
238
|
+
*/
|
|
239
|
+
callServices<
|
|
240
|
+
TServiceCalls extends
|
|
241
|
+
| TxResult<unknown, unknown>
|
|
242
|
+
| undefined
|
|
243
|
+
| readonly (TxResult<unknown, unknown> | undefined)[],
|
|
244
|
+
>(
|
|
245
|
+
/**
|
|
246
|
+
* Factory to create service calls inside the active context.
|
|
247
|
+
*/
|
|
248
|
+
serviceCalls: () => TServiceCalls,
|
|
249
|
+
): Promise<ExtractServiceFinalResultOrSingle<TServiceCalls>>;
|
|
140
250
|
};
|
|
141
251
|
|
|
142
252
|
/**
|
|
143
253
|
* Database fragment context provided to user callbacks.
|
|
144
254
|
*/
|
|
145
|
-
export type DatabaseFragmentContext
|
|
255
|
+
export type DatabaseFragmentContext = {
|
|
146
256
|
/**
|
|
147
257
|
* Database adapter instance.
|
|
148
258
|
*/
|
|
149
259
|
databaseAdapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
type DatabaseFragmentContextInternal<TSchema extends AnySchema> = DatabaseFragmentContext & {
|
|
153
263
|
db: SimpleQueryInterface<TSchema>;
|
|
154
264
|
};
|
|
155
265
|
|
|
@@ -160,7 +270,7 @@ export type DatabaseFragmentContext<TSchema extends AnySchema> = {
|
|
|
160
270
|
function createDatabaseContext<TSchema extends AnySchema>(
|
|
161
271
|
options: FragnoPublicConfigWithDatabase,
|
|
162
272
|
schema: TSchema,
|
|
163
|
-
):
|
|
273
|
+
): DatabaseFragmentContextInternal<TSchema> {
|
|
164
274
|
const databaseAdapter = resolveDatabaseAdapter(options, schema);
|
|
165
275
|
|
|
166
276
|
const namespace = resolveDatabaseNamespace(options, schema);
|
|
@@ -177,6 +287,295 @@ function resolveDatabaseNamespace<TSchema extends AnySchema>(
|
|
|
177
287
|
return hasOverride ? (options.databaseNamespace ?? null) : sanitizeNamespace(schema.name);
|
|
178
288
|
}
|
|
179
289
|
|
|
290
|
+
function resolveMountRoute(name: string, mountRoute?: string): string {
|
|
291
|
+
const resolved = mountRoute ?? `/api/${name}`;
|
|
292
|
+
return resolved.endsWith("/") ? resolved.slice(0, -1) : resolved;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const dbRoundtripGuardStateSymbol = Symbol("fragno-db-roundtrip-guard");
|
|
296
|
+
const requestSourceSymbol = Symbol.for("fragno-request-source");
|
|
297
|
+
const requestRouteSymbol = Symbol.for("fragno-request-route");
|
|
298
|
+
const requestWaitUntilSymbol = Symbol.for("fragno-request-wait-until");
|
|
299
|
+
const roundtripGuardDocsUrl = "https://fragno.dev/docs/fragno/for-library-authors/rules-of-fragno";
|
|
300
|
+
|
|
301
|
+
type DbRoundtripGuardState = {
|
|
302
|
+
retrieveCount: number;
|
|
303
|
+
mutateCount: number;
|
|
304
|
+
maxRoundtrips: number;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
type DurableHooksLogContext = HookNotifyContext;
|
|
308
|
+
|
|
309
|
+
type AnyHandlerTxBuilder = HandlerTxBuilder<
|
|
310
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
311
|
+
any,
|
|
312
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
313
|
+
any,
|
|
314
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
315
|
+
any,
|
|
316
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
317
|
+
any,
|
|
318
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
319
|
+
any,
|
|
320
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
321
|
+
any,
|
|
322
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
323
|
+
any,
|
|
324
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
325
|
+
any,
|
|
326
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
327
|
+
any,
|
|
328
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
329
|
+
any
|
|
330
|
+
>;
|
|
331
|
+
|
|
332
|
+
function wrapHandlerTxBuilderWithRoundtripGuard<
|
|
333
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
334
|
+
TBuilder extends HandlerTxBuilder<any, any, any, any, any, any, any, any, any, any>,
|
|
335
|
+
>(builder: TBuilder, onExecute: () => void): TBuilder {
|
|
336
|
+
const wrappedBuilders = new WeakSet<AnyHandlerTxBuilder>();
|
|
337
|
+
|
|
338
|
+
const applyExecuteGuard = (target: TBuilder): TBuilder => {
|
|
339
|
+
if (wrappedBuilders.has(target)) {
|
|
340
|
+
return target;
|
|
341
|
+
}
|
|
342
|
+
wrappedBuilders.add(target);
|
|
343
|
+
const execute = target.execute.bind(target);
|
|
344
|
+
target.execute = () => {
|
|
345
|
+
onExecute();
|
|
346
|
+
return execute();
|
|
347
|
+
};
|
|
348
|
+
return target;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const wrap = (target: TBuilder): TBuilder => {
|
|
352
|
+
const guarded = applyExecuteGuard(target);
|
|
353
|
+
return new Proxy(guarded, {
|
|
354
|
+
get(obj, prop, receiver) {
|
|
355
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
356
|
+
if (typeof value !== "function") {
|
|
357
|
+
return value;
|
|
358
|
+
}
|
|
359
|
+
if (prop === "execute") {
|
|
360
|
+
return value;
|
|
361
|
+
}
|
|
362
|
+
return (...args: unknown[]) => {
|
|
363
|
+
const result = value.apply(obj, args);
|
|
364
|
+
if (result instanceof HandlerTxBuilder) {
|
|
365
|
+
return wrap(result as TBuilder);
|
|
366
|
+
}
|
|
367
|
+
return result;
|
|
368
|
+
};
|
|
369
|
+
},
|
|
370
|
+
}) as TBuilder;
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
return wrap(builder);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function buildDurableHooksLogContext(context?: DurableHooksLogContext) {
|
|
377
|
+
const logContext: Record<string, unknown> = {};
|
|
378
|
+
if (context?.route !== undefined) {
|
|
379
|
+
logContext["route"] = context.route;
|
|
380
|
+
}
|
|
381
|
+
if (context?.source !== undefined) {
|
|
382
|
+
logContext["source"] = context.source;
|
|
383
|
+
}
|
|
384
|
+
return logContext;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function notifyDurableHooks(
|
|
388
|
+
notifier: HookNotifier,
|
|
389
|
+
namespace: string,
|
|
390
|
+
notifyContext: HookNotifyContext,
|
|
391
|
+
logContextFields: Record<string, unknown>,
|
|
392
|
+
options?: { crossNamespace?: boolean },
|
|
393
|
+
) {
|
|
394
|
+
const crossNamespace = options?.crossNamespace ?? false;
|
|
395
|
+
const suffix = crossNamespace ? " (cross-namespace)" : "";
|
|
396
|
+
const notifyStart = Date.now();
|
|
397
|
+
DurableHooksLogger.debug(`Durable hooks notify requested${suffix}`, {
|
|
398
|
+
namespace,
|
|
399
|
+
fields: logContextFields,
|
|
400
|
+
});
|
|
401
|
+
const notifyPromise = Promise.resolve()
|
|
402
|
+
.then(() => notifier.notify(notifyContext))
|
|
403
|
+
.then((processed) => {
|
|
404
|
+
DurableHooksLogger.debug(`Durable hooks notify completed${suffix}`, {
|
|
405
|
+
namespace,
|
|
406
|
+
fields: {
|
|
407
|
+
result: processed === undefined ? null : processed,
|
|
408
|
+
ms: Date.now() - notifyStart,
|
|
409
|
+
...logContextFields,
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
})
|
|
413
|
+
.catch((error) => {
|
|
414
|
+
DurableHooksLogger.error(`Durable hooks notify failed${suffix}`, {
|
|
415
|
+
namespace,
|
|
416
|
+
fields: {
|
|
417
|
+
error: DurableHooksLogger.toErrorMessage(error),
|
|
418
|
+
...logContextFields,
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
if (notifyContext.waitUntil) {
|
|
424
|
+
notifyContext.waitUntil(notifyPromise);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
void notifyPromise;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function notifyDurableHooksAfterMutate<THooks extends HooksMap>({
|
|
431
|
+
uow,
|
|
432
|
+
hooksConfig,
|
|
433
|
+
internalFragment,
|
|
434
|
+
autoSchedule,
|
|
435
|
+
planMode,
|
|
436
|
+
logContext,
|
|
437
|
+
}: {
|
|
438
|
+
uow: IUnitOfWork;
|
|
439
|
+
hooksConfig?: HookProcessorConfig<THooks>;
|
|
440
|
+
internalFragment?: InternalFragmentInstance;
|
|
441
|
+
autoSchedule: boolean;
|
|
442
|
+
planMode: boolean;
|
|
443
|
+
logContext?: DurableHooksLogContext;
|
|
444
|
+
}) {
|
|
445
|
+
if (planMode) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const notifyContext: HookNotifyContext = {
|
|
450
|
+
source: logContext?.source ?? "request",
|
|
451
|
+
route: logContext?.route,
|
|
452
|
+
waitUntil: logContext?.waitUntil,
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
const logContextFields = buildDurableHooksLogContext(logContext);
|
|
456
|
+
const triggeredHooks = uow.getTriggeredHooks();
|
|
457
|
+
const ownNamespaceTriggeredCount = hooksConfig
|
|
458
|
+
? triggeredHooks.filter((hook) => hook.namespace === hooksConfig.namespace).length
|
|
459
|
+
: 0;
|
|
460
|
+
|
|
461
|
+
if (hooksConfig?.notifier && autoSchedule && ownNamespaceTriggeredCount > 0) {
|
|
462
|
+
notifyDurableHooks(
|
|
463
|
+
hooksConfig.notifier,
|
|
464
|
+
hooksConfig.namespace,
|
|
465
|
+
notifyContext,
|
|
466
|
+
logContextFields,
|
|
467
|
+
);
|
|
468
|
+
} else if (hooksConfig && !autoSchedule && ownNamespaceTriggeredCount > 0) {
|
|
469
|
+
DurableHooksLogger.debug("Durable hooks notify skipped (autoSchedule=false)", {
|
|
470
|
+
namespace: hooksConfig.namespace,
|
|
471
|
+
fields: {
|
|
472
|
+
queued: ownNamespaceTriggeredCount,
|
|
473
|
+
...logContextFields,
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
} else if (hooksConfig && !hooksConfig.notifier && ownNamespaceTriggeredCount > 0) {
|
|
477
|
+
DurableHooksLogger.debug("Durable hooks notify skipped (notifier missing)", {
|
|
478
|
+
namespace: hooksConfig.namespace,
|
|
479
|
+
fields: {
|
|
480
|
+
queued: ownNamespaceTriggeredCount,
|
|
481
|
+
...logContextFields,
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (triggeredHooks.length === 0) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const namespaces = new Set<string>();
|
|
491
|
+
const triggeredCountByNamespace = new Map<string, number>();
|
|
492
|
+
for (const hook of triggeredHooks) {
|
|
493
|
+
namespaces.add(hook.namespace);
|
|
494
|
+
triggeredCountByNamespace.set(
|
|
495
|
+
hook.namespace,
|
|
496
|
+
(triggeredCountByNamespace.get(hook.namespace) ?? 0) + 1,
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
if (hooksConfig) {
|
|
500
|
+
namespaces.delete(hooksConfig.namespace);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
for (const namespace of namespaces) {
|
|
504
|
+
if (!internalFragment) {
|
|
505
|
+
DurableHooksLogger.debug("Durable hooks notifier missing scope for namespace", {
|
|
506
|
+
namespace,
|
|
507
|
+
fields: logContextFields,
|
|
508
|
+
});
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const runtime = getDurableHooksRuntimeByNamespace(namespace, internalFragment);
|
|
513
|
+
if (!runtime) {
|
|
514
|
+
DurableHooksLogger.debug("Durable hooks notifier missing for namespace", {
|
|
515
|
+
namespace,
|
|
516
|
+
fields: logContextFields,
|
|
517
|
+
});
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
if (runtime.config.autoSchedule === false) {
|
|
521
|
+
DurableHooksLogger.debug("Durable hooks notify skipped (autoSchedule=false)", {
|
|
522
|
+
namespace,
|
|
523
|
+
fields: {
|
|
524
|
+
queued: triggeredCountByNamespace.get(namespace) ?? 0,
|
|
525
|
+
...logContextFields,
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
const notifier = runtime.config.notifier;
|
|
531
|
+
if (!notifier) {
|
|
532
|
+
DurableHooksLogger.debug("Durable hooks notifier missing for namespace", {
|
|
533
|
+
namespace,
|
|
534
|
+
fields: logContextFields,
|
|
535
|
+
});
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
notifyDurableHooks(notifier, namespace, notifyContext, logContextFields, {
|
|
539
|
+
crossNamespace: true,
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function resolveDbRoundtripGuard(
|
|
545
|
+
options: FragnoPublicConfigWithDatabase,
|
|
546
|
+
): DbRoundtripGuardState | null {
|
|
547
|
+
const guard = options.dbRoundtripGuard;
|
|
548
|
+
if (!guard) {
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (guard === true) {
|
|
553
|
+
return { retrieveCount: 0, mutateCount: 0, maxRoundtrips: 1 };
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return { retrieveCount: 0, mutateCount: 0, maxRoundtrips: guard.maxRoundtrips ?? 1 };
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function getDbRoundtripGuardState(
|
|
560
|
+
storage: DatabaseContextStorage,
|
|
561
|
+
guard: DbRoundtripGuardState,
|
|
562
|
+
): DbRoundtripGuardState {
|
|
563
|
+
const storageWithGuard = storage as DatabaseContextStorage & {
|
|
564
|
+
[dbRoundtripGuardStateSymbol]?: DbRoundtripGuardState;
|
|
565
|
+
};
|
|
566
|
+
if (!storageWithGuard[dbRoundtripGuardStateSymbol]) {
|
|
567
|
+
storageWithGuard[dbRoundtripGuardStateSymbol] = { ...guard };
|
|
568
|
+
} else {
|
|
569
|
+
storageWithGuard[dbRoundtripGuardStateSymbol]!.maxRoundtrips = guard.maxRoundtrips;
|
|
570
|
+
}
|
|
571
|
+
return storageWithGuard[dbRoundtripGuardStateSymbol]!;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function isRouteRequest(storage: DatabaseContextStorage): boolean {
|
|
575
|
+
const source = (storage as Record<symbol, unknown>)[requestSourceSymbol];
|
|
576
|
+
return source === "route";
|
|
577
|
+
}
|
|
578
|
+
|
|
180
579
|
/**
|
|
181
580
|
* Storage type for database fragments - stores the Unit of Work.
|
|
182
581
|
*/
|
|
@@ -201,7 +600,7 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
201
600
|
THooks extends HooksMap = {},
|
|
202
601
|
TServiceThisContext extends RequestThisContext = DatabaseHandlerContext,
|
|
203
602
|
THandlerThisContext extends RequestThisContext = DatabaseHandlerContext,
|
|
204
|
-
|
|
603
|
+
TInternalRoutes extends readonly AnyRouteOrFactory[] = readonly [],
|
|
205
604
|
> {
|
|
206
605
|
// Store the base builder - we'll replace its storage and context setup when building
|
|
207
606
|
#baseBuilder: FragmentDefinitionBuilder<
|
|
@@ -215,10 +614,12 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
215
614
|
TServiceThisContext,
|
|
216
615
|
THandlerThisContext,
|
|
217
616
|
DatabaseRequestStorage,
|
|
218
|
-
|
|
617
|
+
TInternalRoutes
|
|
219
618
|
>;
|
|
220
619
|
#schema: TSchema;
|
|
221
|
-
#hooksFactory?: (context:
|
|
620
|
+
#hooksFactory?: (context: HooksFactoryContext<TConfig>) => THooks;
|
|
621
|
+
#syncRegistry?: SyncCommandRegistry;
|
|
622
|
+
#registryResolver?: RegistryResolver;
|
|
222
623
|
|
|
223
624
|
constructor(
|
|
224
625
|
baseBuilder: FragmentDefinitionBuilder<
|
|
@@ -232,28 +633,28 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
232
633
|
TServiceThisContext,
|
|
233
634
|
THandlerThisContext,
|
|
234
635
|
DatabaseRequestStorage,
|
|
235
|
-
|
|
636
|
+
TInternalRoutes
|
|
236
637
|
>,
|
|
237
638
|
schema: TSchema,
|
|
238
|
-
hooksFactory?: (context:
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}) => THooks,
|
|
639
|
+
hooksFactory?: (context: HooksFactoryContext<TConfig>) => THooks,
|
|
640
|
+
syncRegistry?: SyncCommandRegistry,
|
|
641
|
+
registryResolver?: RegistryResolver,
|
|
242
642
|
) {
|
|
243
643
|
this.#baseBuilder = baseBuilder;
|
|
244
644
|
this.#schema = schema;
|
|
245
645
|
this.#hooksFactory = hooksFactory;
|
|
646
|
+
this.#syncRegistry = syncRegistry;
|
|
647
|
+
this.#registryResolver = registryResolver;
|
|
246
648
|
}
|
|
247
649
|
|
|
248
650
|
/**
|
|
249
651
|
* Define dependencies for this database fragment.
|
|
250
|
-
* The context includes database adapter
|
|
652
|
+
* The context includes the database adapter.
|
|
251
653
|
*/
|
|
252
654
|
withDependencies<TNewDeps>(
|
|
253
655
|
fn: (context: {
|
|
254
656
|
config: TConfig;
|
|
255
657
|
options: FragnoPublicConfigWithDatabase;
|
|
256
|
-
db: SimpleQueryInterface<TSchema>;
|
|
257
658
|
databaseAdapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
258
659
|
}) => TNewDeps,
|
|
259
660
|
): DatabaseFragmentDefinitionBuilder<
|
|
@@ -267,7 +668,7 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
267
668
|
THooks,
|
|
268
669
|
TServiceThisContext,
|
|
269
670
|
THandlerThisContext,
|
|
270
|
-
|
|
671
|
+
TInternalRoutes
|
|
271
672
|
> {
|
|
272
673
|
// Wrap user function to inject DB context
|
|
273
674
|
const wrappedFn = (context: { config: TConfig; options: FragnoPublicConfigWithDatabase }) => {
|
|
@@ -278,14 +679,13 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
278
679
|
const userDeps = fn({
|
|
279
680
|
config: context.config,
|
|
280
681
|
options: context.options,
|
|
281
|
-
db: dbContext.db,
|
|
282
682
|
databaseAdapter: dbContext.databaseAdapter,
|
|
283
683
|
});
|
|
284
684
|
|
|
285
685
|
// Create implicit dependencies
|
|
286
686
|
const createUow = () => dbContext.db.createUnitOfWork();
|
|
287
687
|
const implicitDeps: ImplicitDatabaseDependencies<TSchema> = {
|
|
288
|
-
|
|
688
|
+
databaseAdapter: dbContext.databaseAdapter,
|
|
289
689
|
schema: this.#schema,
|
|
290
690
|
namespace,
|
|
291
691
|
createUnitOfWork: createUow,
|
|
@@ -300,7 +700,13 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
300
700
|
// Create new base builder with wrapped function
|
|
301
701
|
const newBaseBuilder = this.#baseBuilder.withDependencies(wrappedFn);
|
|
302
702
|
|
|
303
|
-
return new DatabaseFragmentDefinitionBuilder(
|
|
703
|
+
return new DatabaseFragmentDefinitionBuilder(
|
|
704
|
+
newBaseBuilder,
|
|
705
|
+
this.#schema,
|
|
706
|
+
this.#hooksFactory,
|
|
707
|
+
this.#syncRegistry,
|
|
708
|
+
this.#registryResolver,
|
|
709
|
+
);
|
|
304
710
|
}
|
|
305
711
|
|
|
306
712
|
providesBaseService<TNewService>(
|
|
@@ -324,11 +730,17 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
324
730
|
THooks,
|
|
325
731
|
TServiceThisContext,
|
|
326
732
|
THandlerThisContext,
|
|
327
|
-
|
|
733
|
+
TInternalRoutes
|
|
328
734
|
> {
|
|
329
735
|
const newBaseBuilder = this.#baseBuilder.providesBaseService<TNewService>(fn);
|
|
330
736
|
|
|
331
|
-
return new DatabaseFragmentDefinitionBuilder(
|
|
737
|
+
return new DatabaseFragmentDefinitionBuilder(
|
|
738
|
+
newBaseBuilder,
|
|
739
|
+
this.#schema,
|
|
740
|
+
this.#hooksFactory,
|
|
741
|
+
this.#syncRegistry,
|
|
742
|
+
this.#registryResolver,
|
|
743
|
+
);
|
|
332
744
|
}
|
|
333
745
|
|
|
334
746
|
providesService<TServiceName extends string, TService>(
|
|
@@ -353,14 +765,20 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
353
765
|
THooks,
|
|
354
766
|
TServiceThisContext,
|
|
355
767
|
THandlerThisContext,
|
|
356
|
-
|
|
768
|
+
TInternalRoutes
|
|
357
769
|
> {
|
|
358
770
|
const newBaseBuilder = this.#baseBuilder.providesService<TServiceName, TService>(
|
|
359
771
|
serviceName,
|
|
360
772
|
fn,
|
|
361
773
|
);
|
|
362
774
|
|
|
363
|
-
return new DatabaseFragmentDefinitionBuilder(
|
|
775
|
+
return new DatabaseFragmentDefinitionBuilder(
|
|
776
|
+
newBaseBuilder,
|
|
777
|
+
this.#schema,
|
|
778
|
+
this.#hooksFactory,
|
|
779
|
+
this.#syncRegistry,
|
|
780
|
+
this.#registryResolver,
|
|
781
|
+
);
|
|
364
782
|
}
|
|
365
783
|
|
|
366
784
|
/**
|
|
@@ -392,14 +810,20 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
392
810
|
THooks,
|
|
393
811
|
TServiceThisContext,
|
|
394
812
|
THandlerThisContext,
|
|
395
|
-
|
|
813
|
+
TInternalRoutes
|
|
396
814
|
> {
|
|
397
815
|
const newBaseBuilder = this.#baseBuilder.providesPrivateService<TServiceName, TService>(
|
|
398
816
|
serviceName,
|
|
399
817
|
fn,
|
|
400
818
|
);
|
|
401
819
|
|
|
402
|
-
return new DatabaseFragmentDefinitionBuilder(
|
|
820
|
+
return new DatabaseFragmentDefinitionBuilder(
|
|
821
|
+
newBaseBuilder,
|
|
822
|
+
this.#schema,
|
|
823
|
+
this.#hooksFactory,
|
|
824
|
+
this.#syncRegistry,
|
|
825
|
+
this.#registryResolver,
|
|
826
|
+
);
|
|
403
827
|
}
|
|
404
828
|
|
|
405
829
|
/**
|
|
@@ -423,6 +847,9 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
423
847
|
fn: (context: {
|
|
424
848
|
config: TConfig;
|
|
425
849
|
options: FragnoPublicConfigWithDatabase;
|
|
850
|
+
deps: TDeps;
|
|
851
|
+
services: BoundServices<TBaseServices & TServices>;
|
|
852
|
+
serviceDeps: TServiceDependencies;
|
|
426
853
|
defineHook: <TPayload>(
|
|
427
854
|
hook: (this: HookContext, payload: TPayload) => void | Promise<void>,
|
|
428
855
|
) => HookFn<TPayload>;
|
|
@@ -438,7 +865,7 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
438
865
|
TNewHooks,
|
|
439
866
|
DatabaseServiceContext<TNewHooks>,
|
|
440
867
|
THandlerThisContext,
|
|
441
|
-
|
|
868
|
+
TInternalRoutes
|
|
442
869
|
> {
|
|
443
870
|
const defineHook = <TPayload>(
|
|
444
871
|
hook: (this: HookContext, payload: TPayload) => void | Promise<void>,
|
|
@@ -447,13 +874,13 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
447
874
|
};
|
|
448
875
|
|
|
449
876
|
// Store the hooks factory - it will be called in build() with config/options
|
|
450
|
-
const hooksFactory = (context: {
|
|
451
|
-
config: TConfig;
|
|
452
|
-
options: FragnoPublicConfigWithDatabase;
|
|
453
|
-
}) => {
|
|
877
|
+
const hooksFactory = (context: HooksFactoryContext<TConfig>) => {
|
|
454
878
|
return fn({
|
|
455
879
|
config: context.config,
|
|
456
880
|
options: context.options,
|
|
881
|
+
deps: context.deps as TDeps,
|
|
882
|
+
services: context.services as BoundServices<TBaseServices & TServices>,
|
|
883
|
+
serviceDeps: context.serviceDeps as TServiceDependencies,
|
|
457
884
|
defineHook,
|
|
458
885
|
});
|
|
459
886
|
};
|
|
@@ -463,6 +890,9 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
463
890
|
const newBuilder = new DatabaseFragmentDefinitionBuilder(
|
|
464
891
|
this.#baseBuilder,
|
|
465
892
|
this.#schema,
|
|
893
|
+
this.#hooksFactory,
|
|
894
|
+
this.#syncRegistry,
|
|
895
|
+
this.#registryResolver,
|
|
466
896
|
) as unknown as DatabaseFragmentDefinitionBuilder<
|
|
467
897
|
TSchema,
|
|
468
898
|
TConfig,
|
|
@@ -474,7 +904,7 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
474
904
|
TNewHooks,
|
|
475
905
|
DatabaseServiceContext<TNewHooks>,
|
|
476
906
|
THandlerThisContext,
|
|
477
|
-
|
|
907
|
+
TInternalRoutes
|
|
478
908
|
>;
|
|
479
909
|
|
|
480
910
|
newBuilder.#hooksFactory = hooksFactory;
|
|
@@ -482,6 +912,39 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
482
912
|
return newBuilder;
|
|
483
913
|
}
|
|
484
914
|
|
|
915
|
+
/**
|
|
916
|
+
* Register sync command definitions for this fragment.
|
|
917
|
+
*/
|
|
918
|
+
withSyncCommands(
|
|
919
|
+
registry: SyncCommandRegistry,
|
|
920
|
+
): DatabaseFragmentDefinitionBuilder<
|
|
921
|
+
TSchema,
|
|
922
|
+
TConfig,
|
|
923
|
+
TDeps,
|
|
924
|
+
TBaseServices,
|
|
925
|
+
TServices,
|
|
926
|
+
TServiceDependencies,
|
|
927
|
+
TPrivateServices,
|
|
928
|
+
THooks,
|
|
929
|
+
TServiceThisContext,
|
|
930
|
+
THandlerThisContext,
|
|
931
|
+
TInternalRoutes
|
|
932
|
+
> {
|
|
933
|
+
if (registry.schemaName !== this.#schema.name) {
|
|
934
|
+
throw new Error(
|
|
935
|
+
`Sync command registry schema name "${registry.schemaName}" does not match fragment schema "${this.#schema.name}".`,
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
return new DatabaseFragmentDefinitionBuilder(
|
|
940
|
+
this.#baseBuilder,
|
|
941
|
+
this.#schema,
|
|
942
|
+
this.#hooksFactory,
|
|
943
|
+
registry,
|
|
944
|
+
this.#registryResolver,
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
|
|
485
948
|
/**
|
|
486
949
|
* Declare that this fragment uses a required service provided by the runtime.
|
|
487
950
|
* Delegates to the base builder.
|
|
@@ -499,11 +962,17 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
499
962
|
THooks,
|
|
500
963
|
TServiceThisContext,
|
|
501
964
|
THandlerThisContext,
|
|
502
|
-
|
|
965
|
+
TInternalRoutes
|
|
503
966
|
> {
|
|
504
967
|
const newBaseBuilder = this.#baseBuilder.usesService<TServiceName, TService>(serviceName);
|
|
505
968
|
|
|
506
|
-
return new DatabaseFragmentDefinitionBuilder(
|
|
969
|
+
return new DatabaseFragmentDefinitionBuilder(
|
|
970
|
+
newBaseBuilder,
|
|
971
|
+
this.#schema,
|
|
972
|
+
this.#hooksFactory,
|
|
973
|
+
this.#syncRegistry,
|
|
974
|
+
this.#registryResolver,
|
|
975
|
+
);
|
|
507
976
|
}
|
|
508
977
|
|
|
509
978
|
/**
|
|
@@ -523,13 +992,19 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
523
992
|
THooks,
|
|
524
993
|
TServiceThisContext,
|
|
525
994
|
THandlerThisContext,
|
|
526
|
-
|
|
995
|
+
TInternalRoutes
|
|
527
996
|
> {
|
|
528
997
|
const newBaseBuilder = this.#baseBuilder.usesOptionalService<TServiceName, TService>(
|
|
529
998
|
serviceName,
|
|
530
999
|
);
|
|
531
1000
|
|
|
532
|
-
return new DatabaseFragmentDefinitionBuilder(
|
|
1001
|
+
return new DatabaseFragmentDefinitionBuilder(
|
|
1002
|
+
newBaseBuilder,
|
|
1003
|
+
this.#schema,
|
|
1004
|
+
this.#hooksFactory,
|
|
1005
|
+
this.#syncRegistry,
|
|
1006
|
+
this.#registryResolver,
|
|
1007
|
+
);
|
|
533
1008
|
}
|
|
534
1009
|
|
|
535
1010
|
/**
|
|
@@ -548,7 +1023,7 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
548
1023
|
DatabaseServiceContext<THooks>,
|
|
549
1024
|
DatabaseHandlerContext<THooks>,
|
|
550
1025
|
DatabaseRequestStorage,
|
|
551
|
-
|
|
1026
|
+
TInternalRoutes
|
|
552
1027
|
> {
|
|
553
1028
|
const baseDef = this.#baseBuilder.build();
|
|
554
1029
|
|
|
@@ -578,11 +1053,42 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
578
1053
|
}
|
|
579
1054
|
}
|
|
580
1055
|
|
|
581
|
-
const
|
|
1056
|
+
const dbContext = createDatabaseContext(context.options, this.#schema);
|
|
1057
|
+
const { db } = dbContext;
|
|
582
1058
|
const namespace = resolveDatabaseNamespace(context.options, this.#schema);
|
|
1059
|
+
const dryRun = process.env["FRAGNO_INIT_DRY_RUN"] === "true";
|
|
1060
|
+
const isInternalFragment = baseDef.name === "$fragno-internal-fragment";
|
|
1061
|
+
|
|
1062
|
+
if (!dryRun && !isInternalFragment && this.#registryResolver) {
|
|
1063
|
+
const registry = this.#registryResolver.getRegistryForAdapterSync(
|
|
1064
|
+
dbContext.databaseAdapter,
|
|
1065
|
+
);
|
|
1066
|
+
const outboxEnabled = context.options.outbox?.enabled ?? false;
|
|
1067
|
+
registry.registerSchema(
|
|
1068
|
+
{
|
|
1069
|
+
name: this.#schema.name,
|
|
1070
|
+
namespace,
|
|
1071
|
+
version: this.#schema.version,
|
|
1072
|
+
tables: Object.keys(this.#schema.tables).sort(),
|
|
1073
|
+
},
|
|
1074
|
+
{
|
|
1075
|
+
name: baseDef.name,
|
|
1076
|
+
mountRoute: resolveMountRoute(baseDef.name, context.options.mountRoute),
|
|
1077
|
+
},
|
|
1078
|
+
{ outboxEnabled },
|
|
1079
|
+
);
|
|
1080
|
+
if (this.#syncRegistry) {
|
|
1081
|
+
registry.registerSyncCommands({
|
|
1082
|
+
fragmentName: baseDef.name,
|
|
1083
|
+
schemaName: this.#syncRegistry.schemaName,
|
|
1084
|
+
namespace,
|
|
1085
|
+
commands: this.#syncRegistry.commands,
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
583
1089
|
|
|
584
1090
|
const implicitDeps: ImplicitDatabaseDependencies<TSchema> = {
|
|
585
|
-
|
|
1091
|
+
databaseAdapter: dbContext.databaseAdapter,
|
|
586
1092
|
schema: this.#schema,
|
|
587
1093
|
namespace,
|
|
588
1094
|
createUnitOfWork: () => db.createUnitOfWork(),
|
|
@@ -608,20 +1114,12 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
608
1114
|
// Create database context - needed here to create the UOW
|
|
609
1115
|
const dbContextForStorage = createDatabaseContext(options, this.#schema);
|
|
610
1116
|
|
|
611
|
-
|
|
612
|
-
const uow: IUnitOfWork = dbContextForStorage.db.createUnitOfWork();
|
|
1117
|
+
const uow = dbContextForStorage.db.createBaseUnitOfWork();
|
|
613
1118
|
|
|
614
1119
|
return { uow };
|
|
615
1120
|
},
|
|
616
1121
|
);
|
|
617
1122
|
|
|
618
|
-
// Get the internal fragment factory from linked fragments (added by withDatabase)
|
|
619
|
-
// Cast is safe: withDatabase() guarantees this fragment exists and has the correct type
|
|
620
|
-
const internalFragmentFactory = baseDef.linkedFragments?.["_fragno_internal"] as (context: {
|
|
621
|
-
config: TConfig;
|
|
622
|
-
options: FragnoPublicConfigWithDatabase;
|
|
623
|
-
}) => InternalFragmentInstance;
|
|
624
|
-
|
|
625
1123
|
// Cache per instantiated fragment (deps object is unique per instantiation).
|
|
626
1124
|
const hooksConfigCache = new WeakMap<object, HookProcessorConfig<THooks>>();
|
|
627
1125
|
|
|
@@ -629,6 +1127,8 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
629
1127
|
config: TConfig;
|
|
630
1128
|
options: FragnoPublicConfigWithDatabase;
|
|
631
1129
|
deps: TDeps;
|
|
1130
|
+
services?: BoundServices<TBaseServices & TServices>;
|
|
1131
|
+
serviceDeps?: TServiceDependencies;
|
|
632
1132
|
}) => {
|
|
633
1133
|
if (!this.#hooksFactory) {
|
|
634
1134
|
return undefined;
|
|
@@ -637,76 +1137,148 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
637
1137
|
typeof context.deps === "object" && context.deps !== null
|
|
638
1138
|
? (context.deps as object)
|
|
639
1139
|
: undefined;
|
|
1140
|
+
const namespace = resolveDatabaseNamespace(context.options, this.#schema);
|
|
1141
|
+
const namespaceKey = namespace ?? this.#schema.name;
|
|
1142
|
+
const durableHooksOptions = context.options.durableHooks;
|
|
1143
|
+
DurableHooksLogger.configure(durableHooksOptions?.logging, namespaceKey);
|
|
640
1144
|
const cachedHooksConfig = depsKey ? hooksConfigCache.get(depsKey) : undefined;
|
|
641
1145
|
if (cachedHooksConfig) {
|
|
1146
|
+
if (!cachedHooksConfig.hooks && context.services) {
|
|
1147
|
+
cachedHooksConfig.hooks = this.#hooksFactory({
|
|
1148
|
+
config: context.config,
|
|
1149
|
+
options: context.options,
|
|
1150
|
+
deps: context.deps,
|
|
1151
|
+
services: context.services,
|
|
1152
|
+
serviceDeps: context.serviceDeps ?? ({} as TServiceDependencies),
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
642
1155
|
return cachedHooksConfig;
|
|
643
1156
|
}
|
|
644
1157
|
|
|
645
|
-
const
|
|
646
|
-
const namespaceKey = namespace ?? this.#schema.name;
|
|
647
|
-
const durableHooksOptions = context.options.durableHooks;
|
|
1158
|
+
const autoSchedule = durableHooksOptions?.autoSchedule !== false;
|
|
648
1159
|
const baseAdapter = resolveDatabaseAdapter(context.options, this.#schema);
|
|
649
1160
|
const hookAdapter = baseAdapter.getHookProcessingAdapter?.() ?? baseAdapter;
|
|
650
1161
|
const hookOptions =
|
|
651
1162
|
hookAdapter === baseAdapter
|
|
652
1163
|
? context.options
|
|
653
1164
|
: { ...context.options, databaseAdapter: hookAdapter };
|
|
1165
|
+
const registryResolver = this.#registryResolver;
|
|
1166
|
+
if (!registryResolver) {
|
|
1167
|
+
throw new Error("Adapter registry resolver is missing for durable hooks.");
|
|
1168
|
+
}
|
|
654
1169
|
const dbContextForHooks = createDatabaseContext(hookOptions, this.#schema);
|
|
1170
|
+
const hookContextStorage = dbContextForHooks.databaseAdapter.contextStorage;
|
|
655
1171
|
const hooksConfig: HookProcessorConfig<THooks> = {
|
|
656
|
-
hooks:
|
|
1172
|
+
hooks: context.services
|
|
1173
|
+
? this.#hooksFactory({
|
|
1174
|
+
config: context.config,
|
|
1175
|
+
options: context.options,
|
|
1176
|
+
deps: context.deps,
|
|
1177
|
+
services: context.services,
|
|
1178
|
+
serviceDeps: context.serviceDeps ?? ({} as TServiceDependencies),
|
|
1179
|
+
})
|
|
1180
|
+
: undefined,
|
|
657
1181
|
namespace: namespaceKey,
|
|
658
|
-
internalFragment:
|
|
659
|
-
|
|
660
|
-
options: hookOptions,
|
|
661
|
-
}),
|
|
1182
|
+
internalFragment: registryResolver.getInternalFragment(hookAdapter),
|
|
1183
|
+
autoSchedule,
|
|
662
1184
|
handlerTx: (execOptions?: Omit<ExecuteTxOptions, "createUnitOfWork">) => {
|
|
663
1185
|
const userOnBeforeMutate = execOptions?.onBeforeMutate;
|
|
664
1186
|
const userOnAfterMutate = execOptions?.onAfterMutate;
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
)
|
|
673
|
-
|
|
674
|
-
},
|
|
675
|
-
onBeforeMutate: (uow) => {
|
|
676
|
-
prepareHookMutations(uow, hooksConfig);
|
|
677
|
-
if (userOnBeforeMutate) {
|
|
678
|
-
userOnBeforeMutate(uow);
|
|
679
|
-
}
|
|
680
|
-
},
|
|
681
|
-
onAfterMutate: async (uow) => {
|
|
682
|
-
void hooksConfig.scheduler?.schedule().catch((error) => {
|
|
683
|
-
console.error("Durable hooks processing failed", error);
|
|
684
|
-
});
|
|
685
|
-
if (userOnAfterMutate) {
|
|
686
|
-
await userOnAfterMutate(uow);
|
|
1187
|
+
const planMode = execOptions?.planMode ?? false;
|
|
1188
|
+
let storageRef: DatabaseContextStorage | null = null;
|
|
1189
|
+
const getHookWaitUntil = () => {
|
|
1190
|
+
if (!hookContextStorage.hasStore()) {
|
|
1191
|
+
return undefined;
|
|
1192
|
+
}
|
|
1193
|
+
return (
|
|
1194
|
+
hookContextStorage.getStore() as DatabaseContextStorage & {
|
|
1195
|
+
[requestWaitUntilSymbol]?: (promise: Promise<unknown>) => void;
|
|
687
1196
|
}
|
|
1197
|
+
)[requestWaitUntilSymbol];
|
|
1198
|
+
};
|
|
1199
|
+
return createHandlerTxBuilder<THooks>(
|
|
1200
|
+
{
|
|
1201
|
+
...execOptions,
|
|
1202
|
+
createUnitOfWork: () => {
|
|
1203
|
+
const baseUow = dbContextForHooks.db.createBaseUnitOfWork();
|
|
1204
|
+
baseUow.registerSchema(
|
|
1205
|
+
hooksConfig.internalFragment.$internal.deps.schema,
|
|
1206
|
+
hooksConfig.internalFragment.$internal.deps.namespace,
|
|
1207
|
+
);
|
|
1208
|
+
if (storageRef) {
|
|
1209
|
+
storageRef.uow = baseUow;
|
|
1210
|
+
}
|
|
1211
|
+
return baseUow;
|
|
1212
|
+
},
|
|
1213
|
+
onBeforeMutate: (uow) => {
|
|
1214
|
+
if (!planMode) {
|
|
1215
|
+
prepareHookMutations(
|
|
1216
|
+
uow,
|
|
1217
|
+
hooksConfig.internalFragment,
|
|
1218
|
+
hooksConfig.defaultRetryPolicy,
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
if (userOnBeforeMutate) {
|
|
1222
|
+
userOnBeforeMutate(uow);
|
|
1223
|
+
}
|
|
1224
|
+
},
|
|
1225
|
+
onAfterMutate: async (uow) => {
|
|
1226
|
+
notifyDurableHooksAfterMutate({
|
|
1227
|
+
uow,
|
|
1228
|
+
hooksConfig,
|
|
1229
|
+
internalFragment: hooksConfig.internalFragment,
|
|
1230
|
+
autoSchedule,
|
|
1231
|
+
planMode,
|
|
1232
|
+
logContext: { source: "hook", waitUntil: getHookWaitUntil() },
|
|
1233
|
+
});
|
|
1234
|
+
if (userOnAfterMutate) {
|
|
1235
|
+
await userOnAfterMutate(uow);
|
|
1236
|
+
}
|
|
1237
|
+
},
|
|
688
1238
|
},
|
|
689
|
-
|
|
1239
|
+
undefined,
|
|
1240
|
+
(run) =>
|
|
1241
|
+
hookContextStorage.runWithInitializer(() => {
|
|
1242
|
+
const inheritedWaitUntil = getHookWaitUntil();
|
|
1243
|
+
storageRef = { uow: null as unknown as DatabaseContextStorage["uow"] };
|
|
1244
|
+
if (inheritedWaitUntil) {
|
|
1245
|
+
(
|
|
1246
|
+
storageRef as DatabaseContextStorage & {
|
|
1247
|
+
[requestWaitUntilSymbol]?: (promise: Promise<unknown>) => void;
|
|
1248
|
+
}
|
|
1249
|
+
)[requestWaitUntilSymbol] = inheritedWaitUntil;
|
|
1250
|
+
}
|
|
1251
|
+
return storageRef;
|
|
1252
|
+
}, run),
|
|
1253
|
+
);
|
|
690
1254
|
},
|
|
691
1255
|
stuckProcessingTimeoutMinutes: durableHooksOptions?.stuckProcessingTimeoutMinutes,
|
|
692
1256
|
onStuckProcessingHooks: durableHooksOptions?.onStuckProcessingHooks,
|
|
693
1257
|
};
|
|
694
|
-
hooksConfig.
|
|
1258
|
+
hooksConfig.runner = createDurableHooksRunner(hooksConfig);
|
|
1259
|
+
registerDurableHooksRuntime(hooksConfig);
|
|
695
1260
|
if (depsKey) {
|
|
696
1261
|
hooksConfigCache.set(depsKey, hooksConfig);
|
|
697
1262
|
}
|
|
698
1263
|
return hooksConfig;
|
|
699
1264
|
};
|
|
700
1265
|
|
|
1266
|
+
const isInternalFragment = baseDef.name === "$fragno-internal-fragment";
|
|
1267
|
+
|
|
701
1268
|
const builderWithContext = builderWithStorage.withThisContext<
|
|
702
1269
|
DatabaseServiceContext<THooks>,
|
|
703
1270
|
DatabaseHandlerContext<THooks>
|
|
704
1271
|
>(({ storage, config, options, deps }) => {
|
|
705
1272
|
// Create hooks config if hooks factory is defined
|
|
706
1273
|
const hooksConfig = createHooksConfig({ config, options, deps });
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
1274
|
+
const autoSchedule = options.durableHooks?.autoSchedule !== false;
|
|
1275
|
+
const registryResolver = this.#registryResolver;
|
|
1276
|
+
const databaseAdapter =
|
|
1277
|
+
(deps as ImplicitDatabaseDependencies<TSchema>).databaseAdapter ??
|
|
1278
|
+
resolveDatabaseAdapter(options, this.#schema);
|
|
1279
|
+
const internalFragment = isInternalFragment
|
|
1280
|
+
? undefined
|
|
1281
|
+
: (hooksConfig?.internalFragment ?? registryResolver?.getInternalFragment(databaseAdapter));
|
|
710
1282
|
|
|
711
1283
|
// Builder API: serviceTx using createServiceTxBuilder
|
|
712
1284
|
function serviceTx<TSchema extends AnySchema>(schema: TSchema) {
|
|
@@ -734,8 +1306,75 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
734
1306
|
|
|
735
1307
|
const userOnBeforeMutate = execOptions?.onBeforeMutate;
|
|
736
1308
|
const userOnAfterMutate = execOptions?.onAfterMutate;
|
|
1309
|
+
const userOnAfterRetrieve = execOptions?.onAfterRetrieve;
|
|
1310
|
+
const planMode = execOptions?.planMode ?? false;
|
|
1311
|
+
const roundtripGuard = isRouteRequest(currentStorage)
|
|
1312
|
+
? resolveDbRoundtripGuard(options)
|
|
1313
|
+
: null;
|
|
1314
|
+
const roundtripState = roundtripGuard
|
|
1315
|
+
? getDbRoundtripGuardState(currentStorage, roundtripGuard)
|
|
1316
|
+
: null;
|
|
1317
|
+
const routeInfo = (
|
|
1318
|
+
currentStorage as DatabaseContextStorage & {
|
|
1319
|
+
[requestRouteSymbol]?: { method?: string; path?: string };
|
|
1320
|
+
}
|
|
1321
|
+
)[requestRouteSymbol];
|
|
1322
|
+
const routeWaitUntil = (
|
|
1323
|
+
currentStorage as DatabaseContextStorage & {
|
|
1324
|
+
[requestWaitUntilSymbol]?: (promise: Promise<unknown>) => void;
|
|
1325
|
+
}
|
|
1326
|
+
)[requestWaitUntilSymbol];
|
|
1327
|
+
const routeLabel =
|
|
1328
|
+
routeInfo && routeInfo.method && routeInfo.path
|
|
1329
|
+
? `${routeInfo.method} ${routeInfo.path}`
|
|
1330
|
+
: null;
|
|
1331
|
+
const routeSuffix = routeLabel ? ` (route: ${routeLabel})` : "";
|
|
1332
|
+
const buildRoundtripError = (kind: "retrieve" | "mutate") =>
|
|
1333
|
+
new FragnoApiError(
|
|
1334
|
+
{
|
|
1335
|
+
message:
|
|
1336
|
+
`[fragno-db] Fragment "${baseDef.name}" executed more than ` +
|
|
1337
|
+
`${roundtripState?.maxRoundtrips ?? 1} ${kind} ` +
|
|
1338
|
+
`database roundtrip(s) in a single request${routeSuffix}. ` +
|
|
1339
|
+
"Combine reads/writes into one handlerTx() or increase dbRoundtripGuard. " +
|
|
1340
|
+
`See ${roundtripGuardDocsUrl}`,
|
|
1341
|
+
code: "DB_ROUNDTRIP_LIMIT_EXCEEDED",
|
|
1342
|
+
},
|
|
1343
|
+
500,
|
|
1344
|
+
);
|
|
1345
|
+
const roundtripExecutionState = roundtripState
|
|
1346
|
+
? { countedRetrieve: false, countedMutate: false }
|
|
1347
|
+
: null;
|
|
737
1348
|
|
|
738
|
-
|
|
1349
|
+
const resetRoundtripExecutionState = () => {
|
|
1350
|
+
if (!roundtripExecutionState) {
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
roundtripExecutionState.countedRetrieve = false;
|
|
1354
|
+
roundtripExecutionState.countedMutate = false;
|
|
1355
|
+
};
|
|
1356
|
+
|
|
1357
|
+
const guardOnAfterRetrieve = roundtripState
|
|
1358
|
+
? async (uow: IUnitOfWork, results: unknown[]) => {
|
|
1359
|
+
if (
|
|
1360
|
+
roundtripExecutionState &&
|
|
1361
|
+
!roundtripExecutionState.countedRetrieve &&
|
|
1362
|
+
uow.getRetrievalOperations().length > 0
|
|
1363
|
+
) {
|
|
1364
|
+
roundtripExecutionState.countedRetrieve = true;
|
|
1365
|
+
roundtripState.retrieveCount += 1;
|
|
1366
|
+
if (roundtripState.retrieveCount > roundtripState.maxRoundtrips) {
|
|
1367
|
+
throw buildRoundtripError("retrieve");
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
if (userOnAfterRetrieve) {
|
|
1372
|
+
await userOnAfterRetrieve(uow, results);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
: userOnAfterRetrieve;
|
|
1376
|
+
|
|
1377
|
+
const builder = createHandlerTxBuilder<THooks>({
|
|
739
1378
|
...execOptions,
|
|
740
1379
|
createUnitOfWork: () => {
|
|
741
1380
|
currentStorage.uow.reset();
|
|
@@ -748,28 +1387,110 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
748
1387
|
return currentStorage.uow;
|
|
749
1388
|
},
|
|
750
1389
|
onBeforeMutate: (uow) => {
|
|
751
|
-
if (
|
|
752
|
-
prepareHookMutations(uow, hooksConfig);
|
|
1390
|
+
if (internalFragment && !planMode) {
|
|
1391
|
+
prepareHookMutations(uow, internalFragment, hooksConfig?.defaultRetryPolicy);
|
|
753
1392
|
}
|
|
754
1393
|
if (userOnBeforeMutate) {
|
|
755
1394
|
userOnBeforeMutate(uow);
|
|
756
1395
|
}
|
|
1396
|
+
if (
|
|
1397
|
+
roundtripState &&
|
|
1398
|
+
roundtripExecutionState &&
|
|
1399
|
+
!roundtripExecutionState.countedMutate &&
|
|
1400
|
+
uow.getMutationOperations().length > 0
|
|
1401
|
+
) {
|
|
1402
|
+
roundtripExecutionState.countedMutate = true;
|
|
1403
|
+
roundtripState.mutateCount += 1;
|
|
1404
|
+
if (roundtripState.mutateCount > roundtripState.maxRoundtrips) {
|
|
1405
|
+
throw buildRoundtripError("mutate");
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
757
1408
|
},
|
|
1409
|
+
onAfterRetrieve: guardOnAfterRetrieve,
|
|
758
1410
|
onAfterMutate: async (uow) => {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
1411
|
+
notifyDurableHooksAfterMutate({
|
|
1412
|
+
uow,
|
|
1413
|
+
hooksConfig,
|
|
1414
|
+
internalFragment,
|
|
1415
|
+
autoSchedule,
|
|
1416
|
+
planMode,
|
|
1417
|
+
logContext: { route: routeLabel, source: "request", waitUntil: routeWaitUntil },
|
|
1418
|
+
});
|
|
1419
|
+
if (hooksConfig && !planMode) {
|
|
1420
|
+
const runtimeState = getDurableHooksRuntimeByConfig(hooksConfig);
|
|
1421
|
+
if (
|
|
1422
|
+
runtimeState &&
|
|
1423
|
+
!runtimeState.dispatcherRegistered &&
|
|
1424
|
+
!runtimeState.dispatcherWarningEmitted
|
|
1425
|
+
) {
|
|
1426
|
+
const hasHooks = uow
|
|
1427
|
+
.getTriggeredHooks()
|
|
1428
|
+
.some((hook) => hook.namespace === hooksConfig.namespace);
|
|
1429
|
+
if (hasHooks) {
|
|
1430
|
+
runtimeState.dispatcherWarningEmitted = true;
|
|
1431
|
+
DurableHooksLogger.warn("Durable hooks dispatcher not configured for fragment", {
|
|
1432
|
+
namespace: hooksConfig.namespace,
|
|
1433
|
+
fields: {
|
|
1434
|
+
guidance:
|
|
1435
|
+
"Hooks will only run during requests; scheduled/retry hooks may stall. " +
|
|
1436
|
+
"Create a dispatcher with createDurableHooksProcessor([...]) from " +
|
|
1437
|
+
"`@fragno-dev/db/dispatchers/node` or `@fragno-dev/db/dispatchers/cloudflare-do`.",
|
|
1438
|
+
},
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
763
1442
|
}
|
|
764
1443
|
if (userOnAfterMutate) {
|
|
765
1444
|
await userOnAfterMutate(uow);
|
|
766
1445
|
}
|
|
767
1446
|
},
|
|
768
1447
|
});
|
|
1448
|
+
|
|
1449
|
+
if (roundtripState) {
|
|
1450
|
+
const guardedBuilder = wrapHandlerTxBuilderWithRoundtripGuard(
|
|
1451
|
+
builder,
|
|
1452
|
+
resetRoundtripExecutionState,
|
|
1453
|
+
);
|
|
1454
|
+
return guardedBuilder;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
return builder;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
function callServices<
|
|
1461
|
+
TServiceCalls extends
|
|
1462
|
+
| TxResult<unknown, unknown>
|
|
1463
|
+
| undefined
|
|
1464
|
+
| readonly (TxResult<unknown, unknown> | undefined)[],
|
|
1465
|
+
>(
|
|
1466
|
+
serviceCalls: () => TServiceCalls,
|
|
1467
|
+
): Promise<ExtractServiceFinalResultOrSingle<TServiceCalls>> {
|
|
1468
|
+
let callWasArray = false;
|
|
1469
|
+
const resultPromise = handlerTx()
|
|
1470
|
+
.withServiceCalls(() => {
|
|
1471
|
+
const calls = serviceCalls();
|
|
1472
|
+
callWasArray = Array.isArray(calls);
|
|
1473
|
+
return (callWasArray ? calls : [calls]) as readonly (
|
|
1474
|
+
| TxResult<unknown, unknown>
|
|
1475
|
+
| undefined
|
|
1476
|
+
)[];
|
|
1477
|
+
})
|
|
1478
|
+
.execute();
|
|
1479
|
+
|
|
1480
|
+
return resultPromise.then((result) =>
|
|
1481
|
+
callWasArray
|
|
1482
|
+
? (result as ExtractServiceFinalResultOrSingle<TServiceCalls>)
|
|
1483
|
+
: (
|
|
1484
|
+
result as AwaitedPromisesInObject<
|
|
1485
|
+
ExtractServiceFinalResults<readonly [TServiceCalls]>
|
|
1486
|
+
>
|
|
1487
|
+
)[0],
|
|
1488
|
+
) as Promise<ExtractServiceFinalResultOrSingle<TServiceCalls>>;
|
|
769
1489
|
}
|
|
770
1490
|
|
|
771
1491
|
const handlerContext: DatabaseHandlerContext<THooks> = {
|
|
772
1492
|
handlerTx,
|
|
1493
|
+
callServices,
|
|
773
1494
|
};
|
|
774
1495
|
|
|
775
1496
|
return { serviceContext, handlerContext };
|
|
@@ -778,13 +1499,39 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
778
1499
|
// Build the final definition
|
|
779
1500
|
const finalDef = builderWithContext.build();
|
|
780
1501
|
if (this.#hooksFactory) {
|
|
781
|
-
finalDef.internalDataFactory = ({ config, options, deps }) =>
|
|
782
|
-
|
|
1502
|
+
finalDef.internalDataFactory = ({ config, options, deps, services, serviceDeps }) => {
|
|
1503
|
+
const hooksConfig = createHooksConfig({
|
|
783
1504
|
config: config as TConfig,
|
|
784
1505
|
options: options as FragnoPublicConfigWithDatabase,
|
|
785
1506
|
deps: deps as TDeps,
|
|
786
|
-
|
|
787
|
-
|
|
1507
|
+
services: services as BoundServices<TBaseServices & TServices>,
|
|
1508
|
+
serviceDeps: serviceDeps as TServiceDependencies,
|
|
1509
|
+
});
|
|
1510
|
+
if (!hooksConfig) {
|
|
1511
|
+
return {};
|
|
1512
|
+
}
|
|
1513
|
+
return {
|
|
1514
|
+
durableHooksToken: registerDurableHooksRuntime(hooksConfig),
|
|
1515
|
+
};
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
if (this.#registryResolver) {
|
|
1519
|
+
const registryInternalRoutes = ({ deps }: { deps: TDeps }) => {
|
|
1520
|
+
const databaseAdapter = (deps as ImplicitDatabaseDependencies<TSchema>).databaseAdapter;
|
|
1521
|
+
if (!databaseAdapter) {
|
|
1522
|
+
throw new Error("Database adapter is missing for internal routes.");
|
|
1523
|
+
}
|
|
1524
|
+
const internalFragment = this.#registryResolver!.getInternalFragment(databaseAdapter);
|
|
1525
|
+
if (!internalFragment) {
|
|
1526
|
+
return [];
|
|
1527
|
+
}
|
|
1528
|
+
return (internalFragment.routes ?? []) as readonly AnyFragnoRouteConfig[];
|
|
1529
|
+
};
|
|
1530
|
+
const mergedInternalRoutes = [
|
|
1531
|
+
...(finalDef.internalRoutes ?? []),
|
|
1532
|
+
registryInternalRoutes,
|
|
1533
|
+
] as readonly AnyRouteOrFactory[];
|
|
1534
|
+
finalDef.internalRoutes = mergedInternalRoutes as TInternalRoutes;
|
|
788
1535
|
}
|
|
789
1536
|
|
|
790
1537
|
// Return the complete definition with proper typing and dependencies
|