@fragno-dev/db 0.2.2 → 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 +404 -175
- package/CHANGELOG.md +109 -0
- package/README.md +54 -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 +24 -9
- package/dist/adapters/generic-sql/generic-sql-adapter.d.ts.map +1 -1
- package/dist/adapters/generic-sql/generic-sql-adapter.js +60 -22
- package/dist/adapters/generic-sql/generic-sql-adapter.js.map +1 -1
- package/dist/adapters/generic-sql/generic-sql-uow-executor.js +169 -3
- 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 +25 -6
- package/dist/adapters/generic-sql/migration/dialect/mysql.js.map +1 -1
- package/dist/adapters/generic-sql/migration/dialect/postgres.js +7 -6
- package/dist/adapters/generic-sql/migration/dialect/postgres.js.map +1 -1
- package/dist/adapters/generic-sql/migration/dialect/sqlite.js +193 -16
- 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 +9 -9
- package/dist/adapters/generic-sql/migration/prepared-migrations.js.map +1 -1
- package/dist/adapters/generic-sql/migration/sql-generator.js +75 -52
- package/dist/adapters/generic-sql/migration/sql-generator.js.map +1 -1
- package/dist/adapters/generic-sql/query/create-sql-query-compiler.js +7 -6
- 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/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 +32 -21
- 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 +49 -18
- package/dist/adapters/generic-sql/query/sql-query-compiler.js.map +1 -1
- package/dist/adapters/generic-sql/query/where-builder.js +43 -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 +6 -2
- package/dist/adapters/generic-sql/uow-decoder.js.map +1 -1
- package/dist/adapters/generic-sql/uow-encoder.js +27 -8
- package/dist/adapters/generic-sql/uow-encoder.js.map +1 -1
- package/dist/adapters/in-memory/condition-evaluator.js +135 -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 +196 -0
- package/dist/adapters/in-memory/in-memory-adapter.js.map +1 -0
- package/dist/adapters/in-memory/in-memory-uow.js +871 -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 +30 -0
- package/dist/adapters/in-memory/options.d.ts.map +1 -0
- package/dist/adapters/in-memory/options.js +62 -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 +51 -24
- 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/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 +101 -33
- package/dist/db-fragment-definition-builder.d.ts.map +1 -1
- package/dist/db-fragment-definition-builder.js +450 -60
- 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 +11 -0
- package/dist/dispatchers/cloudflare-do/index.d.ts.map +1 -0
- package/dist/dispatchers/cloudflare-do/index.js +31 -0
- package/dist/dispatchers/cloudflare-do/index.js.map +1 -0
- 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 +12 -0
- package/dist/dispatchers/node/index.d.ts.map +1 -0
- package/dist/dispatchers/node/index.js +27 -0
- package/dist/dispatchers/node/index.js.map +1 -0
- 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 +186 -8
- package/dist/fragments/internal-fragment.d.ts.map +1 -1
- package/dist/fragments/internal-fragment.js +203 -38
- package/dist/fragments/internal-fragment.js.map +1 -1
- package/dist/fragments/internal-fragment.routes.js +164 -0
- package/dist/fragments/internal-fragment.routes.js.map +1 -0
- package/dist/fragments/internal-fragment.schema.d.ts +15 -0
- package/dist/fragments/internal-fragment.schema.d.ts.map +1 -0
- package/dist/fragments/internal-fragment.schema.js +39 -0
- package/dist/fragments/internal-fragment.schema.js.map +1 -0
- 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 -0
- package/dist/hooks/durable-hooks-processor.js +80 -0
- package/dist/hooks/durable-hooks-processor.js.map +1 -0
- 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 +100 -1
- package/dist/hooks/hooks.d.ts.map +1 -1
- package/dist/hooks/hooks.js +254 -27
- 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 +223 -37
- 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 +86 -35
- 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 +20 -12
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +18 -12
- 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/outbox/outbox-builder.js +156 -0
- package/dist/outbox/outbox-builder.js.map +1 -0
- package/dist/outbox/outbox.d.ts +54 -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/query/column-defaults.js +20 -4
- 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 +3 -1
- package/dist/query/cursor.d.ts.map +1 -1
- package/dist/query/cursor.js +51 -14
- package/dist/query/cursor.js.map +1 -1
- package/dist/query/db-now.d.ts +22 -0
- package/dist/query/db-now.d.ts.map +1 -0
- package/dist/query/db-now.js +35 -0
- package/dist/query/db-now.js.map +1 -0
- package/dist/query/orm/orm.js.map +1 -1
- package/dist/query/serialize/create-sql-serializer.js +5 -4
- 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 +60 -12
- 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 +13 -4
- 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 +50 -24
- 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 +92 -30
- package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work/unit-of-work.js +136 -11
- package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
- package/dist/query/value-decoding.js +16 -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 +103 -35
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +172 -58
- 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 +4 -3
- 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} +88 -60
- 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 +307 -0
- package/dist/schema-output/prisma.js.map +1 -0
- 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 +66 -0
- package/dist/util/default-database-adapter.js.map +1 -0
- package/dist/with-database.d.ts +3 -6
- package/dist/with-database.d.ts.map +1 -1
- package/dist/with-database.js +8 -7
- package/dist/with-database.js.map +1 -1
- package/package.json +62 -55
- package/src/adapters/adapters.ts +33 -26
- package/src/adapters/drizzle/migrate-drizzle.test.ts +99 -41
- package/src/adapters/drizzle/migration-parity-drizzle-kit.test.ts +601 -0
- package/src/adapters/drizzle/test-utils.ts +13 -8
- package/src/adapters/generic-sql/driver-config.ts +38 -0
- package/src/adapters/generic-sql/generic-sql-adapter.test.ts +10 -8
- package/src/adapters/generic-sql/generic-sql-adapter.ts +117 -34
- package/src/adapters/generic-sql/generic-sql-uow-executor.test.ts +55 -0
- package/src/adapters/generic-sql/generic-sql-uow-executor.ts +297 -3
- package/src/adapters/generic-sql/migration/adapter-migration-parity.test.ts +120 -0
- package/src/adapters/generic-sql/migration/cold-kysely.ts +1 -0
- package/src/adapters/generic-sql/migration/dialect/mysql.test.ts +27 -8
- package/src/adapters/generic-sql/migration/dialect/mysql.ts +47 -8
- package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +28 -9
- package/src/adapters/generic-sql/migration/dialect/postgres.ts +9 -4
- package/src/adapters/generic-sql/migration/dialect/sqlite.test.ts +839 -8
- package/src/adapters/generic-sql/migration/dialect/sqlite.ts +396 -53
- 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 +238 -46
- package/src/adapters/generic-sql/migration/prepared-migrations.ts +21 -13
- package/src/adapters/generic-sql/migration/sql-generator.ts +145 -66
- package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +11 -8
- package/src/adapters/generic-sql/query/cursor-utils.test.ts +272 -0
- package/src/adapters/generic-sql/query/cursor-utils.ts +42 -7
- 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 +171 -35
- package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +53 -40
- package/src/adapters/generic-sql/query/select-builder.test.ts +16 -11
- package/src/adapters/generic-sql/query/select-builder.ts +7 -3
- package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +75 -6
- package/src/adapters/generic-sql/query/sql-query-compiler.ts +129 -24
- package/src/adapters/generic-sql/query/where-builder.test.ts +96 -20
- package/src/adapters/generic-sql/query/where-builder.ts +112 -41
- package/src/adapters/{kysely/kysely-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-migrations.test.ts} +11 -20
- package/src/adapters/generic-sql/sql-adapter-pglite-pagination.test.ts +851 -0
- package/src/adapters/{drizzle/drizzle-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-queries.test.ts} +18 -15
- package/src/adapters/generic-sql/{test/generic-drizzle-adapter-sqlite3.test.ts → sql-adapter-sqlite3-driver.test.ts} +282 -14
- package/src/adapters/{drizzle/drizzle-adapter-sqlite3.test.ts → generic-sql/sql-adapter-sqlite3-uow.test.ts} +129 -12
- package/src/adapters/{kysely/kysely-adapter-sqlocal.test.ts → generic-sql/sql-adapter-sqlocal.test.ts} +9 -7
- package/src/adapters/generic-sql/sqlite-storage.ts +20 -0
- package/src/adapters/generic-sql/uow-decoder.test.ts +5 -4
- package/src/adapters/generic-sql/uow-decoder.ts +23 -5
- package/src/adapters/generic-sql/uow-encoder.test.ts +36 -3
- package/src/adapters/generic-sql/uow-encoder.ts +48 -13
- package/src/adapters/in-memory/condition-evaluator.test.ts +194 -0
- package/src/adapters/in-memory/condition-evaluator.ts +280 -0
- package/src/adapters/in-memory/errors.ts +20 -0
- package/src/adapters/in-memory/in-memory-adapter.ts +388 -0
- package/src/adapters/in-memory/in-memory-uow.mutations.test.ts +344 -0
- package/src/adapters/in-memory/in-memory-uow.retrieval.test.ts +255 -0
- package/src/adapters/in-memory/in-memory-uow.ts +1724 -0
- package/src/adapters/in-memory/index.ts +3 -0
- package/src/adapters/in-memory/options.test.ts +42 -0
- package/src/adapters/in-memory/options.ts +91 -0
- package/src/adapters/in-memory/outbox.test.ts +361 -0
- package/src/adapters/in-memory/reference-resolution.test.ts +51 -0
- package/src/adapters/in-memory/reference-resolution.ts +67 -0
- package/src/adapters/in-memory/sorted-array-index.test.ts +124 -0
- package/src/adapters/in-memory/sorted-array-index.ts +228 -0
- package/src/adapters/in-memory/store.test.ts +69 -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 +58 -0
- package/src/adapters/prisma/prisma-adapter-sqlite3.test.ts +1207 -0
- package/src/adapters/shared/from-unit-of-work-compiler.ts +159 -47
- package/src/adapters/shared/uow-operation-compiler.ts +28 -18
- package/src/adapters/sql/index.ts +12 -0
- package/src/browser/mod.ts +64 -0
- package/src/client.ts +19 -0
- package/src/db-fragment-definition-builder.test.ts +845 -53
- package/src/db-fragment-definition-builder.ts +911 -95
- package/src/db-fragment-instantiator.test.ts +210 -94
- package/src/db-fragment-integration.test.ts +17 -12
- package/src/dispatchers/cloudflare-do/dispatcher.ts +204 -0
- package/src/dispatchers/cloudflare-do/index.test.ts +206 -0
- package/src/dispatchers/cloudflare-do/index.ts +63 -0
- package/src/dispatchers/node/dispatcher.ts +112 -0
- package/src/dispatchers/node/index.test.ts +120 -0
- package/src/dispatchers/node/index.ts +50 -0
- 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 +334 -0
- package/src/fragments/internal-fragment.schema.ts +95 -0
- package/src/fragments/internal-fragment.test.ts +505 -83
- package/src/fragments/internal-fragment.ts +453 -70
- 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 +282 -0
- package/src/hooks/durable-hooks-processor.ts +173 -0
- 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 +455 -34
- package/src/hooks/hooks.ts +501 -34
- 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 +107 -14
- package/src/migration-engine/auto-from-schema.ts +365 -44
- package/src/migration-engine/create.test.ts +4 -3
- package/src/migration-engine/create.ts +1 -1
- package/src/migration-engine/generation-engine.test.ts +292 -110
- package/src/migration-engine/generation-engine.ts +117 -66
- package/src/migration-engine/shared.ts +14 -0
- package/src/mod.ts +95 -39
- package/src/naming/sql-naming.ts +181 -0
- package/src/outbox/outbox-builder.ts +241 -0
- package/src/outbox/outbox.test.ts +424 -0
- package/src/outbox/outbox.ts +139 -0
- package/src/query/column-defaults.ts +42 -4
- package/src/query/condition-builder.test.ts +18 -3
- 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 +119 -20
- package/src/query/cursor.ts +88 -27
- package/src/query/db-now.ts +73 -0
- package/src/query/orm/orm.ts +2 -2
- package/src/query/query-type.test.ts +4 -3
- package/src/query/serialize/create-sql-serializer.ts +10 -5
- package/src/query/serialize/dialect/mysql-serializer.ts +13 -5
- package/src/query/serialize/dialect/postgres-serializer.ts +35 -5
- package/src/query/serialize/dialect/sqlite-serializer.test.ts +90 -3
- package/src/query/serialize/dialect/sqlite-serializer.ts +108 -12
- package/src/query/serialize/sql-serializer.ts +4 -4
- package/src/query/simple-query-interface.ts +15 -4
- package/src/query/unit-of-work/execute-unit-of-work.test.ts +372 -10
- package/src/query/unit-of-work/execute-unit-of-work.ts +87 -27
- 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 +17 -16
- package/src/query/unit-of-work/unit-of-work-types.test.ts +42 -12
- package/src/query/unit-of-work/unit-of-work.test.ts +196 -39
- package/src/query/unit-of-work/unit-of-work.ts +309 -38
- package/src/query/value-decoding.test.ts +63 -4
- package/src/query/value-decoding.ts +32 -6
- package/src/query/value-encoding.test.ts +86 -2
- package/src/query/value-encoding.ts +56 -6
- package/src/schema/create.test.ts +293 -47
- package/src/schema/create.ts +406 -70
- package/src/schema/generate-id.test.ts +3 -2
- package/src/schema/generate-id.ts +2 -2
- package/src/schema/serialize.test.ts +18 -5
- package/src/schema/type-conversion/create-sql-type-mapper.ts +8 -3
- package/src/schema/type-conversion/dialect/sqlite.ts +18 -0
- package/src/schema/type-conversion/type-mapping.test.ts +26 -1
- package/src/schema/validator.test.ts +199 -0
- package/src/schema/validator.ts +232 -0
- package/src/{adapters/drizzle/generate.test.ts → schema-output/drizzle.test.ts} +232 -129
- package/src/{adapters/drizzle/generate.ts → schema-output/drizzle.ts} +155 -99
- package/src/schema-output/prisma.test.ts +694 -0
- package/src/schema-output/prisma.ts +593 -0
- 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 +119 -0
- package/src/with-database.ts +20 -31
- package/tsconfig.json +1 -1
- package/tsdown.config.ts +38 -24
- package/vitest.config.ts +1 -0
- 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 +0 -165
- package/dist/node_modules/.pnpm/rou3@0.7.10/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 -320
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +0 -1
- package/dist/packages/fragno/dist/api/fragment-instantiator.js +0 -525
- 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 -118
- 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 -17
- 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/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
|
@@ -1,40 +1,150 @@
|
|
|
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
|
-
processHooks,
|
|
25
26
|
type HooksMap,
|
|
26
27
|
type HookFn,
|
|
27
28
|
type HookContext,
|
|
29
|
+
type HookProcessorConfig,
|
|
30
|
+
type HookNotifier,
|
|
31
|
+
type HookNotifyContext,
|
|
32
|
+
type DurableHooksProcessingOptions,
|
|
33
|
+
createDurableHooksRunner,
|
|
28
34
|
} from "./hooks/hooks";
|
|
29
|
-
import
|
|
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
|
+
>;
|
|
30
105
|
|
|
31
106
|
/**
|
|
32
|
-
* Extended FragnoPublicConfig
|
|
33
|
-
*
|
|
107
|
+
* Extended FragnoPublicConfig for database fragments.
|
|
108
|
+
* If databaseAdapter is omitted and better-sqlite3 is available, a default SQLite adapter is used.
|
|
34
109
|
*/
|
|
35
110
|
export type FragnoPublicConfigWithDatabase = FragnoPublicConfig & {
|
|
36
111
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
-
databaseAdapter
|
|
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
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Optional durable hooks processing configuration.
|
|
128
|
+
*/
|
|
129
|
+
durableHooks?: DurableHooksProcessingOptions;
|
|
130
|
+
/**
|
|
131
|
+
* Optional override for database namespace. If provided (including null), it is used as-is
|
|
132
|
+
* without sanitization — the caller is responsible for providing a valid namespace.
|
|
133
|
+
* When omitted, defaults to a sanitized version of schema.name.
|
|
134
|
+
*/
|
|
135
|
+
databaseNamespace?: string | null;
|
|
136
|
+
};
|
|
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;
|
|
38
148
|
};
|
|
39
149
|
|
|
40
150
|
/**
|
|
@@ -43,9 +153,9 @@ export type FragnoPublicConfigWithDatabase = FragnoPublicConfig & {
|
|
|
43
153
|
*/
|
|
44
154
|
export type ImplicitDatabaseDependencies<TSchema extends AnySchema> = {
|
|
45
155
|
/**
|
|
46
|
-
* Database
|
|
156
|
+
* Database adapter instance.
|
|
47
157
|
*/
|
|
48
|
-
|
|
158
|
+
databaseAdapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
49
159
|
/**
|
|
50
160
|
* The schema definition for this fragment.
|
|
51
161
|
*/
|
|
@@ -53,7 +163,7 @@ export type ImplicitDatabaseDependencies<TSchema extends AnySchema> = {
|
|
|
53
163
|
/**
|
|
54
164
|
* The database namespace for this fragment.
|
|
55
165
|
*/
|
|
56
|
-
namespace: string;
|
|
166
|
+
namespace: string | null;
|
|
57
167
|
/**
|
|
58
168
|
* Create a new Unit of Work for database operations.
|
|
59
169
|
*/
|
|
@@ -122,19 +232,34 @@ export type DatabaseHandlerContext<THooks extends HooksMap = {}> = RequestThisCo
|
|
|
122
232
|
handlerTx(
|
|
123
233
|
options?: Omit<ExecuteTxOptions, "createUnitOfWork">,
|
|
124
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>>;
|
|
125
250
|
};
|
|
126
251
|
|
|
127
252
|
/**
|
|
128
253
|
* Database fragment context provided to user callbacks.
|
|
129
254
|
*/
|
|
130
|
-
export type DatabaseFragmentContext
|
|
255
|
+
export type DatabaseFragmentContext = {
|
|
131
256
|
/**
|
|
132
257
|
* Database adapter instance.
|
|
133
258
|
*/
|
|
134
259
|
databaseAdapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
type DatabaseFragmentContextInternal<TSchema extends AnySchema> = DatabaseFragmentContext & {
|
|
138
263
|
db: SimpleQueryInterface<TSchema>;
|
|
139
264
|
};
|
|
140
265
|
|
|
@@ -145,19 +270,310 @@ export type DatabaseFragmentContext<TSchema extends AnySchema> = {
|
|
|
145
270
|
function createDatabaseContext<TSchema extends AnySchema>(
|
|
146
271
|
options: FragnoPublicConfigWithDatabase,
|
|
147
272
|
schema: TSchema,
|
|
273
|
+
): DatabaseFragmentContextInternal<TSchema> {
|
|
274
|
+
const databaseAdapter = resolveDatabaseAdapter(options, schema);
|
|
275
|
+
|
|
276
|
+
const namespace = resolveDatabaseNamespace(options, schema);
|
|
277
|
+
const db = databaseAdapter.createQueryEngine(schema, namespace);
|
|
278
|
+
|
|
279
|
+
return { databaseAdapter, db };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function resolveDatabaseNamespace<TSchema extends AnySchema>(
|
|
283
|
+
options: FragnoPublicConfigWithDatabase,
|
|
284
|
+
schema: TSchema,
|
|
285
|
+
): string | null {
|
|
286
|
+
const hasOverride = options.databaseNamespace !== undefined;
|
|
287
|
+
return hasOverride ? (options.databaseNamespace ?? null) : sanitizeNamespace(schema.name);
|
|
288
|
+
}
|
|
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,
|
|
148
389
|
namespace: string,
|
|
149
|
-
|
|
150
|
-
|
|
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
|
+
}
|
|
151
429
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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,
|
|
155
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
|
+
});
|
|
156
484
|
}
|
|
157
485
|
|
|
158
|
-
|
|
486
|
+
if (triggeredHooks.length === 0) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
159
489
|
|
|
160
|
-
|
|
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";
|
|
161
577
|
}
|
|
162
578
|
|
|
163
579
|
/**
|
|
@@ -171,7 +587,7 @@ export type DatabaseRequestStorage = {
|
|
|
171
587
|
* Builder for database fragments that wraps the core fragment builder
|
|
172
588
|
* and provides database-specific functionality.
|
|
173
589
|
*
|
|
174
|
-
* Database fragments
|
|
590
|
+
* Database fragments use FragnoPublicConfigWithDatabase and default the adapter when possible.
|
|
175
591
|
*/
|
|
176
592
|
export class DatabaseFragmentDefinitionBuilder<
|
|
177
593
|
TSchema extends AnySchema,
|
|
@@ -184,7 +600,7 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
184
600
|
THooks extends HooksMap = {},
|
|
185
601
|
TServiceThisContext extends RequestThisContext = DatabaseHandlerContext,
|
|
186
602
|
THandlerThisContext extends RequestThisContext = DatabaseHandlerContext,
|
|
187
|
-
|
|
603
|
+
TInternalRoutes extends readonly AnyRouteOrFactory[] = readonly [],
|
|
188
604
|
> {
|
|
189
605
|
// Store the base builder - we'll replace its storage and context setup when building
|
|
190
606
|
#baseBuilder: FragmentDefinitionBuilder<
|
|
@@ -198,11 +614,12 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
198
614
|
TServiceThisContext,
|
|
199
615
|
THandlerThisContext,
|
|
200
616
|
DatabaseRequestStorage,
|
|
201
|
-
|
|
617
|
+
TInternalRoutes
|
|
202
618
|
>;
|
|
203
619
|
#schema: TSchema;
|
|
204
|
-
#
|
|
205
|
-
#
|
|
620
|
+
#hooksFactory?: (context: HooksFactoryContext<TConfig>) => THooks;
|
|
621
|
+
#syncRegistry?: SyncCommandRegistry;
|
|
622
|
+
#registryResolver?: RegistryResolver;
|
|
206
623
|
|
|
207
624
|
constructor(
|
|
208
625
|
baseBuilder: FragmentDefinitionBuilder<
|
|
@@ -216,30 +633,28 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
216
633
|
TServiceThisContext,
|
|
217
634
|
THandlerThisContext,
|
|
218
635
|
DatabaseRequestStorage,
|
|
219
|
-
|
|
636
|
+
TInternalRoutes
|
|
220
637
|
>,
|
|
221
638
|
schema: TSchema,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
options: FragnoPublicConfigWithDatabase;
|
|
226
|
-
}) => THooks,
|
|
639
|
+
hooksFactory?: (context: HooksFactoryContext<TConfig>) => THooks,
|
|
640
|
+
syncRegistry?: SyncCommandRegistry,
|
|
641
|
+
registryResolver?: RegistryResolver,
|
|
227
642
|
) {
|
|
228
643
|
this.#baseBuilder = baseBuilder;
|
|
229
644
|
this.#schema = schema;
|
|
230
|
-
this.#namespace = namespace ?? baseBuilder.name;
|
|
231
645
|
this.#hooksFactory = hooksFactory;
|
|
646
|
+
this.#syncRegistry = syncRegistry;
|
|
647
|
+
this.#registryResolver = registryResolver;
|
|
232
648
|
}
|
|
233
649
|
|
|
234
650
|
/**
|
|
235
651
|
* Define dependencies for this database fragment.
|
|
236
|
-
* The context includes database adapter
|
|
652
|
+
* The context includes the database adapter.
|
|
237
653
|
*/
|
|
238
654
|
withDependencies<TNewDeps>(
|
|
239
655
|
fn: (context: {
|
|
240
656
|
config: TConfig;
|
|
241
657
|
options: FragnoPublicConfigWithDatabase;
|
|
242
|
-
db: SimpleQueryInterface<TSchema>;
|
|
243
658
|
databaseAdapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
244
659
|
}) => TNewDeps,
|
|
245
660
|
): DatabaseFragmentDefinitionBuilder<
|
|
@@ -253,26 +668,26 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
253
668
|
THooks,
|
|
254
669
|
TServiceThisContext,
|
|
255
670
|
THandlerThisContext,
|
|
256
|
-
|
|
671
|
+
TInternalRoutes
|
|
257
672
|
> {
|
|
258
673
|
// Wrap user function to inject DB context
|
|
259
674
|
const wrappedFn = (context: { config: TConfig; options: FragnoPublicConfigWithDatabase }) => {
|
|
260
|
-
const dbContext = createDatabaseContext(context.options, this.#schema
|
|
675
|
+
const dbContext = createDatabaseContext(context.options, this.#schema);
|
|
676
|
+
const namespace = resolveDatabaseNamespace(context.options, this.#schema);
|
|
261
677
|
|
|
262
678
|
// Call user function with enriched context
|
|
263
679
|
const userDeps = fn({
|
|
264
680
|
config: context.config,
|
|
265
681
|
options: context.options,
|
|
266
|
-
db: dbContext.db,
|
|
267
682
|
databaseAdapter: dbContext.databaseAdapter,
|
|
268
683
|
});
|
|
269
684
|
|
|
270
685
|
// Create implicit dependencies
|
|
271
686
|
const createUow = () => dbContext.db.createUnitOfWork();
|
|
272
687
|
const implicitDeps: ImplicitDatabaseDependencies<TSchema> = {
|
|
273
|
-
|
|
688
|
+
databaseAdapter: dbContext.databaseAdapter,
|
|
274
689
|
schema: this.#schema,
|
|
275
|
-
namespace
|
|
690
|
+
namespace,
|
|
276
691
|
createUnitOfWork: createUow,
|
|
277
692
|
};
|
|
278
693
|
|
|
@@ -288,8 +703,9 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
288
703
|
return new DatabaseFragmentDefinitionBuilder(
|
|
289
704
|
newBaseBuilder,
|
|
290
705
|
this.#schema,
|
|
291
|
-
this.#namespace,
|
|
292
706
|
this.#hooksFactory,
|
|
707
|
+
this.#syncRegistry,
|
|
708
|
+
this.#registryResolver,
|
|
293
709
|
);
|
|
294
710
|
}
|
|
295
711
|
|
|
@@ -314,15 +730,16 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
314
730
|
THooks,
|
|
315
731
|
TServiceThisContext,
|
|
316
732
|
THandlerThisContext,
|
|
317
|
-
|
|
733
|
+
TInternalRoutes
|
|
318
734
|
> {
|
|
319
735
|
const newBaseBuilder = this.#baseBuilder.providesBaseService<TNewService>(fn);
|
|
320
736
|
|
|
321
737
|
return new DatabaseFragmentDefinitionBuilder(
|
|
322
738
|
newBaseBuilder,
|
|
323
739
|
this.#schema,
|
|
324
|
-
this.#namespace,
|
|
325
740
|
this.#hooksFactory,
|
|
741
|
+
this.#syncRegistry,
|
|
742
|
+
this.#registryResolver,
|
|
326
743
|
);
|
|
327
744
|
}
|
|
328
745
|
|
|
@@ -348,7 +765,7 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
348
765
|
THooks,
|
|
349
766
|
TServiceThisContext,
|
|
350
767
|
THandlerThisContext,
|
|
351
|
-
|
|
768
|
+
TInternalRoutes
|
|
352
769
|
> {
|
|
353
770
|
const newBaseBuilder = this.#baseBuilder.providesService<TServiceName, TService>(
|
|
354
771
|
serviceName,
|
|
@@ -358,8 +775,9 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
358
775
|
return new DatabaseFragmentDefinitionBuilder(
|
|
359
776
|
newBaseBuilder,
|
|
360
777
|
this.#schema,
|
|
361
|
-
this.#namespace,
|
|
362
778
|
this.#hooksFactory,
|
|
779
|
+
this.#syncRegistry,
|
|
780
|
+
this.#registryResolver,
|
|
363
781
|
);
|
|
364
782
|
}
|
|
365
783
|
|
|
@@ -392,7 +810,7 @@ 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,
|
|
@@ -402,8 +820,9 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
402
820
|
return new DatabaseFragmentDefinitionBuilder(
|
|
403
821
|
newBaseBuilder,
|
|
404
822
|
this.#schema,
|
|
405
|
-
this.#namespace,
|
|
406
823
|
this.#hooksFactory,
|
|
824
|
+
this.#syncRegistry,
|
|
825
|
+
this.#registryResolver,
|
|
407
826
|
);
|
|
408
827
|
}
|
|
409
828
|
|
|
@@ -428,6 +847,9 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
428
847
|
fn: (context: {
|
|
429
848
|
config: TConfig;
|
|
430
849
|
options: FragnoPublicConfigWithDatabase;
|
|
850
|
+
deps: TDeps;
|
|
851
|
+
services: BoundServices<TBaseServices & TServices>;
|
|
852
|
+
serviceDeps: TServiceDependencies;
|
|
431
853
|
defineHook: <TPayload>(
|
|
432
854
|
hook: (this: HookContext, payload: TPayload) => void | Promise<void>,
|
|
433
855
|
) => HookFn<TPayload>;
|
|
@@ -443,7 +865,7 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
443
865
|
TNewHooks,
|
|
444
866
|
DatabaseServiceContext<TNewHooks>,
|
|
445
867
|
THandlerThisContext,
|
|
446
|
-
|
|
868
|
+
TInternalRoutes
|
|
447
869
|
> {
|
|
448
870
|
const defineHook = <TPayload>(
|
|
449
871
|
hook: (this: HookContext, payload: TPayload) => void | Promise<void>,
|
|
@@ -452,13 +874,13 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
452
874
|
};
|
|
453
875
|
|
|
454
876
|
// Store the hooks factory - it will be called in build() with config/options
|
|
455
|
-
const hooksFactory = (context: {
|
|
456
|
-
config: TConfig;
|
|
457
|
-
options: FragnoPublicConfigWithDatabase;
|
|
458
|
-
}) => {
|
|
877
|
+
const hooksFactory = (context: HooksFactoryContext<TConfig>) => {
|
|
459
878
|
return fn({
|
|
460
879
|
config: context.config,
|
|
461
880
|
options: context.options,
|
|
881
|
+
deps: context.deps as TDeps,
|
|
882
|
+
services: context.services as BoundServices<TBaseServices & TServices>,
|
|
883
|
+
serviceDeps: context.serviceDeps as TServiceDependencies,
|
|
462
884
|
defineHook,
|
|
463
885
|
});
|
|
464
886
|
};
|
|
@@ -468,7 +890,9 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
468
890
|
const newBuilder = new DatabaseFragmentDefinitionBuilder(
|
|
469
891
|
this.#baseBuilder,
|
|
470
892
|
this.#schema,
|
|
471
|
-
this.#
|
|
893
|
+
this.#hooksFactory,
|
|
894
|
+
this.#syncRegistry,
|
|
895
|
+
this.#registryResolver,
|
|
472
896
|
) as unknown as DatabaseFragmentDefinitionBuilder<
|
|
473
897
|
TSchema,
|
|
474
898
|
TConfig,
|
|
@@ -480,7 +904,7 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
480
904
|
TNewHooks,
|
|
481
905
|
DatabaseServiceContext<TNewHooks>,
|
|
482
906
|
THandlerThisContext,
|
|
483
|
-
|
|
907
|
+
TInternalRoutes
|
|
484
908
|
>;
|
|
485
909
|
|
|
486
910
|
newBuilder.#hooksFactory = hooksFactory;
|
|
@@ -488,6 +912,39 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
488
912
|
return newBuilder;
|
|
489
913
|
}
|
|
490
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
|
+
|
|
491
948
|
/**
|
|
492
949
|
* Declare that this fragment uses a required service provided by the runtime.
|
|
493
950
|
* Delegates to the base builder.
|
|
@@ -505,15 +962,16 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
505
962
|
THooks,
|
|
506
963
|
TServiceThisContext,
|
|
507
964
|
THandlerThisContext,
|
|
508
|
-
|
|
965
|
+
TInternalRoutes
|
|
509
966
|
> {
|
|
510
967
|
const newBaseBuilder = this.#baseBuilder.usesService<TServiceName, TService>(serviceName);
|
|
511
968
|
|
|
512
969
|
return new DatabaseFragmentDefinitionBuilder(
|
|
513
970
|
newBaseBuilder,
|
|
514
971
|
this.#schema,
|
|
515
|
-
this.#namespace,
|
|
516
972
|
this.#hooksFactory,
|
|
973
|
+
this.#syncRegistry,
|
|
974
|
+
this.#registryResolver,
|
|
517
975
|
);
|
|
518
976
|
}
|
|
519
977
|
|
|
@@ -534,7 +992,7 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
534
992
|
THooks,
|
|
535
993
|
TServiceThisContext,
|
|
536
994
|
THandlerThisContext,
|
|
537
|
-
|
|
995
|
+
TInternalRoutes
|
|
538
996
|
> {
|
|
539
997
|
const newBaseBuilder = this.#baseBuilder.usesOptionalService<TServiceName, TService>(
|
|
540
998
|
serviceName,
|
|
@@ -543,8 +1001,9 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
543
1001
|
return new DatabaseFragmentDefinitionBuilder(
|
|
544
1002
|
newBaseBuilder,
|
|
545
1003
|
this.#schema,
|
|
546
|
-
this.#namespace,
|
|
547
1004
|
this.#hooksFactory,
|
|
1005
|
+
this.#syncRegistry,
|
|
1006
|
+
this.#registryResolver,
|
|
548
1007
|
);
|
|
549
1008
|
}
|
|
550
1009
|
|
|
@@ -564,7 +1023,7 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
564
1023
|
DatabaseServiceContext<THooks>,
|
|
565
1024
|
DatabaseHandlerContext<THooks>,
|
|
566
1025
|
DatabaseRequestStorage,
|
|
567
|
-
|
|
1026
|
+
TInternalRoutes
|
|
568
1027
|
> {
|
|
569
1028
|
const baseDef = this.#baseBuilder.build();
|
|
570
1029
|
|
|
@@ -594,12 +1053,44 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
594
1053
|
}
|
|
595
1054
|
}
|
|
596
1055
|
|
|
597
|
-
const
|
|
1056
|
+
const dbContext = createDatabaseContext(context.options, this.#schema);
|
|
1057
|
+
const { db } = dbContext;
|
|
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
|
+
}
|
|
598
1089
|
|
|
599
1090
|
const implicitDeps: ImplicitDatabaseDependencies<TSchema> = {
|
|
600
|
-
|
|
1091
|
+
databaseAdapter: dbContext.databaseAdapter,
|
|
601
1092
|
schema: this.#schema,
|
|
602
|
-
namespace
|
|
1093
|
+
namespace,
|
|
603
1094
|
createUnitOfWork: () => db.createUnitOfWork(),
|
|
604
1095
|
};
|
|
605
1096
|
|
|
@@ -612,7 +1103,7 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
612
1103
|
// Use the adapter's shared context storage (all fragments using the same adapter share this storage)
|
|
613
1104
|
const builderWithExternalStorage = this.#baseBuilder.withExternalRequestStorage(
|
|
614
1105
|
({ options }) => {
|
|
615
|
-
const dbContext = createDatabaseContext(options, this.#schema
|
|
1106
|
+
const dbContext = createDatabaseContext(options, this.#schema);
|
|
616
1107
|
return dbContext.databaseAdapter.contextStorage;
|
|
617
1108
|
},
|
|
618
1109
|
);
|
|
@@ -621,34 +1112,173 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
621
1112
|
const builderWithStorage = builderWithExternalStorage.withRequestStorage(
|
|
622
1113
|
({ options }): DatabaseRequestStorage => {
|
|
623
1114
|
// Create database context - needed here to create the UOW
|
|
624
|
-
const dbContextForStorage = createDatabaseContext(options, this.#schema
|
|
1115
|
+
const dbContextForStorage = createDatabaseContext(options, this.#schema);
|
|
625
1116
|
|
|
626
|
-
|
|
627
|
-
const uow: IUnitOfWork = dbContextForStorage.db.createUnitOfWork();
|
|
1117
|
+
const uow = dbContextForStorage.db.createBaseUnitOfWork();
|
|
628
1118
|
|
|
629
1119
|
return { uow };
|
|
630
1120
|
},
|
|
631
1121
|
);
|
|
632
1122
|
|
|
633
|
-
//
|
|
634
|
-
|
|
635
|
-
|
|
1123
|
+
// Cache per instantiated fragment (deps object is unique per instantiation).
|
|
1124
|
+
const hooksConfigCache = new WeakMap<object, HookProcessorConfig<THooks>>();
|
|
1125
|
+
|
|
1126
|
+
const createHooksConfig = (context: {
|
|
636
1127
|
config: TConfig;
|
|
637
1128
|
options: FragnoPublicConfigWithDatabase;
|
|
638
|
-
|
|
1129
|
+
deps: TDeps;
|
|
1130
|
+
services?: BoundServices<TBaseServices & TServices>;
|
|
1131
|
+
serviceDeps?: TServiceDependencies;
|
|
1132
|
+
}) => {
|
|
1133
|
+
if (!this.#hooksFactory) {
|
|
1134
|
+
return undefined;
|
|
1135
|
+
}
|
|
1136
|
+
const depsKey =
|
|
1137
|
+
typeof context.deps === "object" && context.deps !== null
|
|
1138
|
+
? (context.deps as object)
|
|
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);
|
|
1144
|
+
const cachedHooksConfig = depsKey ? hooksConfigCache.get(depsKey) : undefined;
|
|
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
|
+
}
|
|
1155
|
+
return cachedHooksConfig;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
const autoSchedule = durableHooksOptions?.autoSchedule !== false;
|
|
1159
|
+
const baseAdapter = resolveDatabaseAdapter(context.options, this.#schema);
|
|
1160
|
+
const hookAdapter = baseAdapter.getHookProcessingAdapter?.() ?? baseAdapter;
|
|
1161
|
+
const hookOptions =
|
|
1162
|
+
hookAdapter === baseAdapter
|
|
1163
|
+
? context.options
|
|
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
|
+
}
|
|
1169
|
+
const dbContextForHooks = createDatabaseContext(hookOptions, this.#schema);
|
|
1170
|
+
const hookContextStorage = dbContextForHooks.databaseAdapter.contextStorage;
|
|
1171
|
+
const hooksConfig: HookProcessorConfig<THooks> = {
|
|
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,
|
|
1181
|
+
namespace: namespaceKey,
|
|
1182
|
+
internalFragment: registryResolver.getInternalFragment(hookAdapter),
|
|
1183
|
+
autoSchedule,
|
|
1184
|
+
handlerTx: (execOptions?: Omit<ExecuteTxOptions, "createUnitOfWork">) => {
|
|
1185
|
+
const userOnBeforeMutate = execOptions?.onBeforeMutate;
|
|
1186
|
+
const userOnAfterMutate = execOptions?.onAfterMutate;
|
|
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;
|
|
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
|
+
},
|
|
1238
|
+
},
|
|
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
|
+
);
|
|
1254
|
+
},
|
|
1255
|
+
stuckProcessingTimeoutMinutes: durableHooksOptions?.stuckProcessingTimeoutMinutes,
|
|
1256
|
+
onStuckProcessingHooks: durableHooksOptions?.onStuckProcessingHooks,
|
|
1257
|
+
};
|
|
1258
|
+
hooksConfig.runner = createDurableHooksRunner(hooksConfig);
|
|
1259
|
+
registerDurableHooksRuntime(hooksConfig);
|
|
1260
|
+
if (depsKey) {
|
|
1261
|
+
hooksConfigCache.set(depsKey, hooksConfig);
|
|
1262
|
+
}
|
|
1263
|
+
return hooksConfig;
|
|
1264
|
+
};
|
|
1265
|
+
|
|
1266
|
+
const isInternalFragment = baseDef.name === "$fragno-internal-fragment";
|
|
639
1267
|
|
|
640
1268
|
const builderWithContext = builderWithStorage.withThisContext<
|
|
641
1269
|
DatabaseServiceContext<THooks>,
|
|
642
1270
|
DatabaseHandlerContext<THooks>
|
|
643
|
-
>(({ storage, config, options }) => {
|
|
1271
|
+
>(({ storage, config, options, deps }) => {
|
|
644
1272
|
// Create hooks config if hooks factory is defined
|
|
645
|
-
const hooksConfig =
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
1273
|
+
const hooksConfig = createHooksConfig({ config, options, deps });
|
|
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));
|
|
652
1282
|
|
|
653
1283
|
// Builder API: serviceTx using createServiceTxBuilder
|
|
654
1284
|
function serviceTx<TSchema extends AnySchema>(schema: TSchema) {
|
|
@@ -676,40 +1306,191 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
676
1306
|
|
|
677
1307
|
const userOnBeforeMutate = execOptions?.onBeforeMutate;
|
|
678
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;
|
|
679
1348
|
|
|
680
|
-
|
|
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>({
|
|
681
1378
|
...execOptions,
|
|
682
1379
|
createUnitOfWork: () => {
|
|
683
1380
|
currentStorage.uow.reset();
|
|
684
|
-
if (
|
|
1381
|
+
if (internalFragment) {
|
|
685
1382
|
currentStorage.uow.registerSchema(
|
|
686
|
-
|
|
687
|
-
|
|
1383
|
+
internalFragment.$internal.deps.schema,
|
|
1384
|
+
internalFragment.$internal.deps.namespace,
|
|
688
1385
|
);
|
|
689
1386
|
}
|
|
690
1387
|
return currentStorage.uow;
|
|
691
1388
|
},
|
|
692
1389
|
onBeforeMutate: (uow) => {
|
|
693
|
-
if (
|
|
694
|
-
prepareHookMutations(uow, hooksConfig);
|
|
1390
|
+
if (internalFragment && !planMode) {
|
|
1391
|
+
prepareHookMutations(uow, internalFragment, hooksConfig?.defaultRetryPolicy);
|
|
695
1392
|
}
|
|
696
1393
|
if (userOnBeforeMutate) {
|
|
697
1394
|
userOnBeforeMutate(uow);
|
|
698
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
|
+
}
|
|
699
1408
|
},
|
|
1409
|
+
onAfterRetrieve: guardOnAfterRetrieve,
|
|
700
1410
|
onAfterMutate: async (uow) => {
|
|
701
|
-
|
|
702
|
-
|
|
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
|
+
}
|
|
703
1442
|
}
|
|
704
1443
|
if (userOnAfterMutate) {
|
|
705
1444
|
await userOnAfterMutate(uow);
|
|
706
1445
|
}
|
|
707
1446
|
},
|
|
708
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>>;
|
|
709
1489
|
}
|
|
710
1490
|
|
|
711
1491
|
const handlerContext: DatabaseHandlerContext<THooks> = {
|
|
712
1492
|
handlerTx,
|
|
1493
|
+
callServices,
|
|
713
1494
|
};
|
|
714
1495
|
|
|
715
1496
|
return { serviceContext, handlerContext };
|
|
@@ -717,6 +1498,41 @@ export class DatabaseFragmentDefinitionBuilder<
|
|
|
717
1498
|
|
|
718
1499
|
// Build the final definition
|
|
719
1500
|
const finalDef = builderWithContext.build();
|
|
1501
|
+
if (this.#hooksFactory) {
|
|
1502
|
+
finalDef.internalDataFactory = ({ config, options, deps, services, serviceDeps }) => {
|
|
1503
|
+
const hooksConfig = createHooksConfig({
|
|
1504
|
+
config: config as TConfig,
|
|
1505
|
+
options: options as FragnoPublicConfigWithDatabase,
|
|
1506
|
+
deps: deps as TDeps,
|
|
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;
|
|
1535
|
+
}
|
|
720
1536
|
|
|
721
1537
|
// Return the complete definition with proper typing and dependencies
|
|
722
1538
|
return {
|