@fragno-dev/db 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +202 -140
- package/CHANGELOG.md +35 -0
- package/README.md +30 -9
- package/dist/adapters/adapters.d.ts +23 -21
- package/dist/adapters/adapters.d.ts.map +1 -1
- package/dist/adapters/adapters.js.map +1 -1
- package/dist/adapters/generic-sql/driver-config.d.ts +16 -1
- package/dist/adapters/generic-sql/driver-config.d.ts.map +1 -1
- package/dist/adapters/generic-sql/driver-config.js +23 -1
- package/dist/adapters/generic-sql/driver-config.js.map +1 -1
- package/dist/adapters/generic-sql/generic-sql-adapter.d.ts +27 -9
- package/dist/adapters/generic-sql/generic-sql-adapter.d.ts.map +1 -1
- package/dist/adapters/generic-sql/generic-sql-adapter.js +55 -16
- package/dist/adapters/generic-sql/generic-sql-adapter.js.map +1 -1
- package/dist/adapters/generic-sql/generic-sql-uow-executor.js +129 -3
- package/dist/adapters/generic-sql/generic-sql-uow-executor.js.map +1 -1
- package/dist/adapters/generic-sql/migration/dialect/mysql.js +24 -5
- package/dist/adapters/generic-sql/migration/dialect/mysql.js.map +1 -1
- package/dist/adapters/generic-sql/migration/dialect/postgres.js +6 -5
- package/dist/adapters/generic-sql/migration/dialect/postgres.js.map +1 -1
- package/dist/adapters/generic-sql/migration/dialect/sqlite.js +21 -10
- package/dist/adapters/generic-sql/migration/dialect/sqlite.js.map +1 -1
- package/dist/adapters/generic-sql/migration/prepared-migrations.d.ts.map +1 -1
- package/dist/adapters/generic-sql/migration/prepared-migrations.js +8 -8
- package/dist/adapters/generic-sql/migration/prepared-migrations.js.map +1 -1
- package/dist/adapters/generic-sql/migration/sql-generator.js +74 -51
- package/dist/adapters/generic-sql/migration/sql-generator.js.map +1 -1
- package/dist/adapters/generic-sql/query/create-sql-query-compiler.js +6 -5
- package/dist/adapters/generic-sql/query/create-sql-query-compiler.js.map +1 -1
- package/dist/adapters/generic-sql/query/cursor-utils.js +42 -4
- package/dist/adapters/generic-sql/query/cursor-utils.js.map +1 -1
- package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js +25 -17
- package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js.map +1 -1
- package/dist/adapters/generic-sql/query/select-builder.js +5 -3
- package/dist/adapters/generic-sql/query/select-builder.js.map +1 -1
- package/dist/adapters/generic-sql/query/sql-query-compiler.js +15 -12
- package/dist/adapters/generic-sql/query/sql-query-compiler.js.map +1 -1
- package/dist/adapters/generic-sql/query/where-builder.js +39 -29
- package/dist/adapters/generic-sql/query/where-builder.js.map +1 -1
- package/dist/adapters/generic-sql/sqlite-storage.d.ts +13 -0
- package/dist/adapters/generic-sql/sqlite-storage.d.ts.map +1 -0
- package/dist/adapters/generic-sql/sqlite-storage.js +15 -0
- package/dist/adapters/generic-sql/sqlite-storage.js.map +1 -0
- package/dist/adapters/generic-sql/uow-decoder.js +7 -3
- package/dist/adapters/generic-sql/uow-decoder.js.map +1 -1
- package/dist/adapters/generic-sql/uow-encoder.js +28 -8
- package/dist/adapters/generic-sql/uow-encoder.js.map +1 -1
- package/dist/adapters/in-memory/condition-evaluator.js +131 -0
- package/dist/adapters/in-memory/condition-evaluator.js.map +1 -0
- package/dist/adapters/in-memory/errors.d.ts +13 -0
- package/dist/adapters/in-memory/errors.d.ts.map +1 -0
- package/dist/adapters/in-memory/errors.js +23 -0
- package/dist/adapters/in-memory/errors.js.map +1 -0
- package/dist/adapters/in-memory/in-memory-adapter.d.ts +27 -0
- package/dist/adapters/in-memory/in-memory-adapter.d.ts.map +1 -0
- package/dist/adapters/in-memory/in-memory-adapter.js +176 -0
- package/dist/adapters/in-memory/in-memory-adapter.js.map +1 -0
- package/dist/adapters/in-memory/in-memory-uow.js +648 -0
- package/dist/adapters/in-memory/in-memory-uow.js.map +1 -0
- package/dist/adapters/in-memory/index.d.ts +4 -0
- package/dist/adapters/in-memory/index.js +4 -0
- package/dist/adapters/in-memory/options.d.ts +28 -0
- package/dist/adapters/in-memory/options.d.ts.map +1 -0
- package/dist/adapters/in-memory/options.js +61 -0
- package/dist/adapters/in-memory/options.js.map +1 -0
- package/dist/adapters/in-memory/reference-resolution.js +26 -0
- package/dist/adapters/in-memory/reference-resolution.js.map +1 -0
- package/dist/adapters/in-memory/sorted-array-index.js +129 -0
- package/dist/adapters/in-memory/sorted-array-index.js.map +1 -0
- package/dist/adapters/in-memory/store.js +71 -0
- package/dist/adapters/in-memory/store.js.map +1 -0
- package/dist/adapters/in-memory/value-comparison.js +28 -0
- package/dist/adapters/in-memory/value-comparison.js.map +1 -0
- package/dist/adapters/shared/from-unit-of-work-compiler.js.map +1 -1
- package/dist/adapters/shared/uow-operation-compiler.js +11 -11
- package/dist/adapters/shared/uow-operation-compiler.js.map +1 -1
- package/dist/adapters/sql/index.d.ts +5 -0
- package/dist/adapters/sql/index.js +4 -0
- package/dist/db-fragment-definition-builder.d.ts +18 -7
- package/dist/db-fragment-definition-builder.d.ts.map +1 -1
- package/dist/db-fragment-definition-builder.js +116 -54
- package/dist/db-fragment-definition-builder.js.map +1 -1
- package/dist/dispatchers/cloudflare-do/index.d.ts +26 -0
- package/dist/dispatchers/cloudflare-do/index.d.ts.map +1 -0
- package/dist/dispatchers/cloudflare-do/index.js +63 -0
- package/dist/dispatchers/cloudflare-do/index.js.map +1 -0
- package/dist/dispatchers/node/index.d.ts +17 -0
- package/dist/dispatchers/node/index.d.ts.map +1 -0
- package/dist/dispatchers/node/index.js +59 -0
- package/dist/dispatchers/node/index.js.map +1 -0
- package/dist/fragments/internal-fragment.d.ts +79 -2
- package/dist/fragments/internal-fragment.d.ts.map +1 -1
- package/dist/fragments/internal-fragment.js +150 -32
- package/dist/fragments/internal-fragment.js.map +1 -1
- package/dist/fragments/internal-fragment.routes.js +29 -0
- package/dist/fragments/internal-fragment.routes.js.map +1 -0
- package/dist/fragments/internal-fragment.schema.d.ts +9 -0
- package/dist/fragments/internal-fragment.schema.d.ts.map +1 -0
- package/dist/fragments/internal-fragment.schema.js +22 -0
- package/dist/fragments/internal-fragment.schema.js.map +1 -0
- package/dist/hooks/durable-hooks-processor.d.ts +14 -0
- package/dist/hooks/durable-hooks-processor.d.ts.map +1 -0
- package/dist/hooks/durable-hooks-processor.js +32 -0
- package/dist/hooks/durable-hooks-processor.js.map +1 -0
- package/dist/hooks/hooks.d.ts +42 -1
- package/dist/hooks/hooks.d.ts.map +1 -1
- package/dist/hooks/hooks.js +72 -6
- package/dist/hooks/hooks.js.map +1 -1
- package/dist/migration-engine/auto-from-schema.js +14 -11
- package/dist/migration-engine/auto-from-schema.js.map +1 -1
- package/dist/migration-engine/generation-engine.d.ts +16 -10
- package/dist/migration-engine/generation-engine.d.ts.map +1 -1
- package/dist/migration-engine/generation-engine.js +72 -33
- package/dist/migration-engine/generation-engine.js.map +1 -1
- package/dist/migration-engine/shared.js.map +1 -1
- package/dist/mod.d.ts +15 -8
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +14 -8
- package/dist/mod.js.map +1 -1
- package/dist/naming/sql-naming.d.ts +19 -0
- package/dist/naming/sql-naming.d.ts.map +1 -0
- package/dist/naming/sql-naming.js +116 -0
- package/dist/naming/sql-naming.js.map +1 -0
- package/dist/node_modules/.pnpm/{rou3@0.7.10 → rou3@0.7.12}/node_modules/rou3/dist/index.js +8 -5
- package/dist/node_modules/.pnpm/rou3@0.7.12/node_modules/rou3/dist/index.js.map +1 -0
- package/dist/outbox/outbox-builder.js +156 -0
- package/dist/outbox/outbox-builder.js.map +1 -0
- package/dist/outbox/outbox.d.ts +52 -0
- package/dist/outbox/outbox.d.ts.map +1 -0
- package/dist/outbox/outbox.js +37 -0
- package/dist/outbox/outbox.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js +3 -2
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +1 -1
- package/dist/packages/fragno/dist/api/fragment-instantiator.js +164 -20
- package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +1 -1
- package/dist/packages/fragno/dist/api/request-input-context.js +67 -0
- package/dist/packages/fragno/dist/api/request-input-context.js.map +1 -1
- package/dist/packages/fragno/dist/api/route.js +14 -1
- package/dist/packages/fragno/dist/api/route.js.map +1 -1
- package/dist/packages/fragno/dist/internal/trace-context.js +12 -0
- package/dist/packages/fragno/dist/internal/trace-context.js.map +1 -0
- package/dist/query/column-defaults.js +20 -4
- package/dist/query/column-defaults.js.map +1 -1
- package/dist/query/cursor.d.ts +3 -1
- package/dist/query/cursor.d.ts.map +1 -1
- package/dist/query/cursor.js +45 -14
- package/dist/query/cursor.js.map +1 -1
- package/dist/query/db-now.d.ts +8 -0
- package/dist/query/db-now.d.ts.map +1 -0
- package/dist/query/db-now.js +7 -0
- package/dist/query/db-now.js.map +1 -0
- package/dist/query/serialize/create-sql-serializer.js +3 -2
- package/dist/query/serialize/create-sql-serializer.js.map +1 -1
- package/dist/query/serialize/dialect/mysql-serializer.js +12 -6
- package/dist/query/serialize/dialect/mysql-serializer.js.map +1 -1
- package/dist/query/serialize/dialect/postgres-serializer.js +25 -7
- package/dist/query/serialize/dialect/postgres-serializer.js.map +1 -1
- package/dist/query/serialize/dialect/sqlite-serializer.js +55 -11
- package/dist/query/serialize/dialect/sqlite-serializer.js.map +1 -1
- package/dist/query/serialize/sql-serializer.js +2 -2
- package/dist/query/serialize/sql-serializer.js.map +1 -1
- package/dist/query/simple-query-interface.d.ts +6 -1
- package/dist/query/simple-query-interface.d.ts.map +1 -1
- package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work/execute-unit-of-work.js +11 -6
- package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -1
- package/dist/query/unit-of-work/unit-of-work.d.ts +50 -14
- package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work/unit-of-work.js +86 -5
- package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
- package/dist/query/value-decoding.js +9 -6
- package/dist/query/value-decoding.js.map +1 -1
- package/dist/query/value-encoding.js +29 -9
- package/dist/query/value-encoding.js.map +1 -1
- package/dist/schema/create.d.ts +38 -14
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +81 -42
- package/dist/schema/create.js.map +1 -1
- package/dist/schema/generate-id.js +2 -2
- package/dist/schema/generate-id.js.map +1 -1
- package/dist/schema/type-conversion/create-sql-type-mapper.js +3 -2
- package/dist/schema/type-conversion/create-sql-type-mapper.js.map +1 -1
- package/dist/schema/type-conversion/dialect/sqlite.js +9 -0
- package/dist/schema/type-conversion/dialect/sqlite.js.map +1 -1
- package/dist/schema/validator.d.ts +10 -0
- package/dist/schema/validator.d.ts.map +1 -0
- package/dist/schema/validator.js +123 -0
- package/dist/schema/validator.js.map +1 -0
- package/dist/schema-output/drizzle.d.ts +30 -0
- package/dist/schema-output/drizzle.d.ts.map +1 -0
- package/dist/{adapters/drizzle/generate.js → schema-output/drizzle.js} +82 -56
- package/dist/schema-output/drizzle.js.map +1 -0
- package/dist/schema-output/prisma.d.ts +17 -0
- package/dist/schema-output/prisma.d.ts.map +1 -0
- package/dist/schema-output/prisma.js +296 -0
- package/dist/schema-output/prisma.js.map +1 -0
- package/dist/util/default-database-adapter.js +61 -0
- package/dist/util/default-database-adapter.js.map +1 -0
- package/dist/with-database.d.ts +1 -1
- package/dist/with-database.d.ts.map +1 -1
- package/dist/with-database.js +12 -3
- package/dist/with-database.js.map +1 -1
- package/package.json +43 -28
- package/src/adapters/adapters.ts +30 -24
- package/src/adapters/drizzle/migrate-drizzle.test.ts +54 -33
- package/src/adapters/drizzle/migration-parity-drizzle-kit.test.ts +599 -0
- package/src/adapters/drizzle/test-utils.ts +12 -8
- package/src/adapters/generic-sql/driver-config.ts +38 -0
- package/src/adapters/generic-sql/generic-sql-adapter.test.ts +5 -5
- package/src/adapters/generic-sql/generic-sql-adapter.ts +110 -24
- package/src/adapters/generic-sql/generic-sql-uow-executor.test.ts +54 -0
- package/src/adapters/generic-sql/generic-sql-uow-executor.ts +231 -3
- package/src/adapters/generic-sql/migration/adapter-migration-parity.test.ts +118 -0
- package/src/adapters/generic-sql/migration/dialect/mysql.test.ts +26 -8
- package/src/adapters/generic-sql/migration/dialect/mysql.ts +46 -8
- package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +25 -7
- package/src/adapters/generic-sql/migration/dialect/postgres.ts +8 -4
- package/src/adapters/generic-sql/migration/dialect/sqlite.test.ts +47 -8
- package/src/adapters/generic-sql/migration/dialect/sqlite.ts +27 -12
- package/src/adapters/generic-sql/migration/prepared-migrations.test.ts +128 -39
- package/src/adapters/generic-sql/migration/prepared-migrations.ts +15 -8
- package/src/adapters/generic-sql/migration/sql-generator.ts +142 -65
- package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +9 -6
- package/src/adapters/generic-sql/query/cursor-utils.test.ts +271 -0
- package/src/adapters/generic-sql/query/cursor-utils.ts +41 -6
- package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.test.ts +27 -27
- package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +38 -24
- package/src/adapters/generic-sql/query/select-builder.test.ts +15 -11
- package/src/adapters/generic-sql/query/select-builder.ts +6 -2
- package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +52 -2
- package/src/adapters/generic-sql/query/sql-query-compiler.ts +50 -15
- package/src/adapters/generic-sql/query/where-builder.test.ts +91 -17
- package/src/adapters/generic-sql/query/where-builder.ts +90 -38
- package/src/adapters/{kysely/kysely-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-migrations.test.ts} +6 -6
- package/src/adapters/generic-sql/sql-adapter-pglite-pagination.test.ts +806 -0
- package/src/adapters/{drizzle/drizzle-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-queries.test.ts} +11 -11
- package/src/adapters/generic-sql/{test/generic-drizzle-adapter-sqlite3.test.ts → sql-adapter-sqlite3-driver.test.ts} +10 -10
- package/src/adapters/{drizzle/drizzle-adapter-sqlite3.test.ts → generic-sql/sql-adapter-sqlite3-uow.test.ts} +7 -7
- package/src/adapters/{kysely/kysely-adapter-sqlocal.test.ts → generic-sql/sql-adapter-sqlocal.test.ts} +6 -6
- package/src/adapters/generic-sql/sqlite-storage.ts +20 -0
- package/src/adapters/generic-sql/uow-decoder.test.ts +1 -1
- package/src/adapters/generic-sql/uow-decoder.ts +21 -3
- package/src/adapters/generic-sql/uow-encoder.test.ts +33 -2
- package/src/adapters/generic-sql/uow-encoder.ts +50 -11
- package/src/adapters/in-memory/condition-evaluator.test.ts +193 -0
- package/src/adapters/in-memory/condition-evaluator.ts +275 -0
- package/src/adapters/in-memory/errors.ts +20 -0
- package/src/adapters/in-memory/in-memory-adapter.ts +277 -0
- package/src/adapters/in-memory/in-memory-uow.mutations.test.ts +296 -0
- package/src/adapters/in-memory/in-memory-uow.retrieval.test.ts +100 -0
- package/src/adapters/in-memory/in-memory-uow.ts +1348 -0
- package/src/adapters/in-memory/index.ts +3 -0
- package/src/adapters/in-memory/options.test.ts +41 -0
- package/src/adapters/in-memory/options.ts +87 -0
- package/src/adapters/in-memory/reference-resolution.test.ts +50 -0
- package/src/adapters/in-memory/reference-resolution.ts +67 -0
- package/src/adapters/in-memory/sorted-array-index.test.ts +123 -0
- package/src/adapters/in-memory/sorted-array-index.ts +228 -0
- package/src/adapters/in-memory/store.test.ts +68 -0
- package/src/adapters/in-memory/store.ts +145 -0
- package/src/adapters/in-memory/value-comparison.ts +53 -0
- package/src/adapters/in-memory/value-normalization.test.ts +57 -0
- package/src/adapters/prisma/prisma-adapter-sqlite3.test.ts +1163 -0
- package/src/adapters/shared/from-unit-of-work-compiler.ts +3 -1
- package/src/adapters/shared/uow-operation-compiler.ts +26 -16
- package/src/adapters/sql/index.ts +12 -0
- package/src/db-fragment-definition-builder.test.ts +30 -12
- package/src/db-fragment-definition-builder.ts +142 -73
- package/src/db-fragment-instantiator.test.ts +105 -13
- package/src/db-fragment-integration.test.ts +9 -7
- package/src/dispatchers/cloudflare-do/index.test.ts +73 -0
- package/src/dispatchers/cloudflare-do/index.ts +104 -0
- package/src/dispatchers/node/index.test.ts +91 -0
- package/src/dispatchers/node/index.ts +87 -0
- package/src/fragments/internal-fragment.routes.ts +42 -0
- package/src/fragments/internal-fragment.schema.ts +51 -0
- package/src/fragments/internal-fragment.test.ts +458 -8
- package/src/fragments/internal-fragment.ts +322 -63
- package/src/hooks/durable-hooks-processor.test.ts +117 -0
- package/src/hooks/durable-hooks-processor.ts +67 -0
- package/src/hooks/hooks.test.ts +165 -5
- package/src/hooks/hooks.ts +197 -9
- package/src/migration-engine/auto-from-schema.test.ts +14 -14
- package/src/migration-engine/auto-from-schema.ts +5 -2
- package/src/migration-engine/create.test.ts +2 -2
- package/src/migration-engine/generation-engine.test.ts +229 -104
- package/src/migration-engine/generation-engine.ts +94 -64
- package/src/migration-engine/shared.ts +1 -0
- package/src/mod.ts +64 -26
- package/src/naming/sql-naming.ts +180 -0
- package/src/outbox/outbox-builder.ts +241 -0
- package/src/outbox/outbox.test.ts +253 -0
- package/src/outbox/outbox.ts +137 -0
- package/src/query/column-defaults.ts +41 -3
- package/src/query/condition-builder.test.ts +3 -3
- package/src/query/cursor.test.ts +116 -18
- package/src/query/cursor.ts +75 -26
- package/src/query/db-now.ts +6 -0
- package/src/query/query-type.test.ts +2 -2
- package/src/query/serialize/create-sql-serializer.ts +7 -2
- package/src/query/serialize/dialect/mysql-serializer.ts +12 -4
- package/src/query/serialize/dialect/postgres-serializer.ts +34 -4
- package/src/query/serialize/dialect/sqlite-serializer.test.ts +51 -1
- package/src/query/serialize/dialect/sqlite-serializer.ts +92 -9
- package/src/query/serialize/sql-serializer.ts +4 -4
- package/src/query/simple-query-interface.ts +5 -0
- package/src/query/unit-of-work/execute-unit-of-work.test.ts +25 -1
- package/src/query/unit-of-work/execute-unit-of-work.ts +25 -8
- package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +12 -12
- package/src/query/unit-of-work/unit-of-work-types.test.ts +1 -1
- package/src/query/unit-of-work/unit-of-work.test.ts +168 -37
- package/src/query/unit-of-work/unit-of-work.ts +203 -18
- package/src/query/value-decoding.test.ts +13 -2
- package/src/query/value-decoding.ts +17 -4
- package/src/query/value-encoding.test.ts +85 -2
- package/src/query/value-encoding.ts +56 -6
- package/src/schema/create.test.ts +129 -42
- package/src/schema/create.ts +185 -47
- package/src/schema/generate-id.test.ts +2 -2
- package/src/schema/generate-id.ts +2 -2
- package/src/schema/serialize.test.ts +14 -2
- package/src/schema/type-conversion/create-sql-type-mapper.ts +7 -2
- package/src/schema/type-conversion/dialect/sqlite.ts +18 -0
- package/src/schema/type-conversion/type-mapping.test.ts +25 -1
- package/src/schema/validator.test.ts +197 -0
- package/src/schema/validator.ts +231 -0
- package/src/{adapters/drizzle/generate.test.ts → schema-output/drizzle.test.ts} +179 -129
- package/src/{adapters/drizzle/generate.ts → schema-output/drizzle.ts} +143 -93
- package/src/schema-output/prisma.test.ts +536 -0
- package/src/schema-output/prisma.ts +573 -0
- package/src/util/default-database-adapter.ts +106 -0
- package/src/with-database.ts +22 -3
- package/tsdown.config.ts +6 -4
- package/dist/adapters/drizzle/drizzle-adapter.d.ts +0 -20
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +0 -1
- package/dist/adapters/drizzle/drizzle-adapter.js +0 -27
- package/dist/adapters/drizzle/drizzle-adapter.js.map +0 -1
- package/dist/adapters/drizzle/generate.d.ts +0 -30
- package/dist/adapters/drizzle/generate.d.ts.map +0 -1
- package/dist/adapters/drizzle/generate.js.map +0 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts +0 -19
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +0 -1
- package/dist/adapters/kysely/kysely-adapter.js +0 -17
- package/dist/adapters/kysely/kysely-adapter.js.map +0 -1
- package/dist/adapters/shared/table-name-mapper.d.ts +0 -12
- package/dist/adapters/shared/table-name-mapper.d.ts.map +0 -1
- package/dist/adapters/shared/table-name-mapper.js +0 -43
- package/dist/adapters/shared/table-name-mapper.js.map +0 -1
- package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js.map +0 -1
- package/dist/schema-generator/schema-generator.d.ts +0 -15
- package/dist/schema-generator/schema-generator.d.ts.map +0 -1
- package/src/adapters/drizzle/drizzle-adapter.ts +0 -39
- package/src/adapters/kysely/kysely-adapter.ts +0 -27
- package/src/adapters/shared/table-name-mapper.ts +0 -50
- package/src/schema-generator/schema-generator.ts +0 -12
package/dist/hooks/hooks.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { ExponentialBackoffRetryPolicy } from "../query/unit-of-work/retry-policy.js";
|
|
2
2
|
|
|
3
3
|
//#region src/hooks/hooks.ts
|
|
4
|
+
const DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES = 10;
|
|
5
|
+
function resolveStuckProcessingTimeoutMinutes(value) {
|
|
6
|
+
if (value === false) return false;
|
|
7
|
+
if (typeof value === "number") return value > 0 ? value : false;
|
|
8
|
+
return DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES;
|
|
9
|
+
}
|
|
4
10
|
/**
|
|
5
11
|
* Add hook events as mutation operations to the UOW.
|
|
6
12
|
* This should be called before executeMutations() so hook records are created
|
|
@@ -15,6 +21,7 @@ function prepareHookMutations(uow, config) {
|
|
|
15
21
|
const internalUow = uow.forSchema(internalSchema);
|
|
16
22
|
for (const hook of triggeredHooks) {
|
|
17
23
|
const maxAttempts = (hook.options?.retryPolicy ?? retryPolicy).shouldRetry(4) ? 5 : 1;
|
|
24
|
+
const nextRetryAt = (hook.options?.processAt ? new Date(hook.options.processAt) : null) ?? null;
|
|
18
25
|
internalUow.create("fragno_hooks", {
|
|
19
26
|
namespace,
|
|
20
27
|
hookName: hook.hookName,
|
|
@@ -23,7 +30,7 @@ function prepareHookMutations(uow, config) {
|
|
|
23
30
|
attempts: 0,
|
|
24
31
|
maxAttempts,
|
|
25
32
|
lastAttemptAt: null,
|
|
26
|
-
nextRetryAt
|
|
33
|
+
nextRetryAt,
|
|
27
34
|
error: null,
|
|
28
35
|
nonce: uow.idempotencyKey
|
|
29
36
|
});
|
|
@@ -36,10 +43,32 @@ function prepareHookMutations(uow, config) {
|
|
|
36
43
|
async function processHooks(config) {
|
|
37
44
|
const { hooks, namespace, internalFragment, defaultRetryPolicy } = config;
|
|
38
45
|
const retryPolicy = defaultRetryPolicy ?? new ExponentialBackoffRetryPolicy({ maxRetries: 5 });
|
|
46
|
+
const stuckProcessingTimeoutMinutes = resolveStuckProcessingTimeoutMinutes(config.stuckProcessingTimeoutMinutes);
|
|
47
|
+
const getDbNow = async () => {
|
|
48
|
+
const services = internalFragment.services;
|
|
49
|
+
if (services.getDbNow) return services.getDbNow();
|
|
50
|
+
return /* @__PURE__ */ new Date();
|
|
51
|
+
};
|
|
52
|
+
const dbNow = await getDbNow();
|
|
53
|
+
if (stuckProcessingTimeoutMinutes !== false) {
|
|
54
|
+
const staleBefore = /* @__PURE__ */ new Date(dbNow.getTime() - stuckProcessingTimeoutMinutes * 6e4);
|
|
55
|
+
const stuckEvents = await internalFragment.inContext(async function() {
|
|
56
|
+
return await this.handlerTx().withServiceCalls(() => [internalFragment.services.hookService.requeueStuckProcessingHooks(namespace, staleBefore)]).transform(({ serviceResult: [events] }) => events).execute();
|
|
57
|
+
});
|
|
58
|
+
if (stuckEvents.length > 0) try {
|
|
59
|
+
config.onStuckProcessingHooks?.({
|
|
60
|
+
namespace,
|
|
61
|
+
timeoutMinutes: stuckProcessingTimeoutMinutes,
|
|
62
|
+
events: stuckEvents
|
|
63
|
+
});
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error("Error calling onStuckProcessingHooks", error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
39
68
|
const pendingEvents = await internalFragment.inContext(async function() {
|
|
40
|
-
return await this.handlerTx().withServiceCalls(() => [internalFragment.services.hookService.
|
|
69
|
+
return await this.handlerTx().withServiceCalls(() => [internalFragment.services.hookService.claimPendingHookEvents(namespace)]).transform(({ serviceResult: [events] }) => events).execute();
|
|
41
70
|
});
|
|
42
|
-
if (pendingEvents.length === 0) return;
|
|
71
|
+
if (pendingEvents.length === 0) return 0;
|
|
43
72
|
const processedEvents = await Promise.allSettled(pendingEvents.map(async (event) => {
|
|
44
73
|
const hookFn = hooks[event.hookName];
|
|
45
74
|
if (!hookFn) return {
|
|
@@ -50,7 +79,10 @@ async function processHooks(config) {
|
|
|
50
79
|
maxAttempts: event.maxAttempts
|
|
51
80
|
};
|
|
52
81
|
try {
|
|
53
|
-
const hookContext = {
|
|
82
|
+
const hookContext = {
|
|
83
|
+
idempotencyKey: event.idempotencyKey,
|
|
84
|
+
handlerTx: config.handlerTx
|
|
85
|
+
};
|
|
54
86
|
await hookFn.call(hookContext, event.payload);
|
|
55
87
|
return {
|
|
56
88
|
eventId: event.id,
|
|
@@ -76,14 +108,48 @@ async function processHooks(config) {
|
|
|
76
108
|
if (status === "completed") txResults.push(internalFragment.services.hookService.markHookCompleted(eventId));
|
|
77
109
|
else if (status === "failed") {
|
|
78
110
|
const { error, attempts } = processedEvent.value;
|
|
79
|
-
txResults.push(internalFragment.services.hookService.markHookFailed(eventId, error, attempts, retryPolicy));
|
|
111
|
+
txResults.push(internalFragment.services.hookService.markHookFailed(eventId, error, attempts, retryPolicy, dbNow));
|
|
80
112
|
}
|
|
81
113
|
}
|
|
82
114
|
return txResults;
|
|
83
115
|
}).execute();
|
|
84
116
|
});
|
|
117
|
+
return processedEvents.reduce((count, result) => count + (result.status === "fulfilled" ? 1 : 0), 0);
|
|
118
|
+
}
|
|
119
|
+
function createHookScheduler(config) {
|
|
120
|
+
let processing = false;
|
|
121
|
+
let queued = false;
|
|
122
|
+
let currentPromise = null;
|
|
123
|
+
const schedule = async () => {
|
|
124
|
+
if (processing) {
|
|
125
|
+
queued = true;
|
|
126
|
+
return currentPromise ?? Promise.resolve(0);
|
|
127
|
+
}
|
|
128
|
+
processing = true;
|
|
129
|
+
currentPromise = (async () => {
|
|
130
|
+
let lastCount = 0;
|
|
131
|
+
try {
|
|
132
|
+
do {
|
|
133
|
+
queued = false;
|
|
134
|
+
lastCount = await processHooks(config);
|
|
135
|
+
} while (queued);
|
|
136
|
+
return lastCount;
|
|
137
|
+
} finally {
|
|
138
|
+
processing = false;
|
|
139
|
+
queued = false;
|
|
140
|
+
}
|
|
141
|
+
})();
|
|
142
|
+
return currentPromise;
|
|
143
|
+
};
|
|
144
|
+
const drain = async () => {
|
|
145
|
+
while (true) if (await schedule() === 0) return;
|
|
146
|
+
};
|
|
147
|
+
return {
|
|
148
|
+
schedule,
|
|
149
|
+
drain
|
|
150
|
+
};
|
|
85
151
|
}
|
|
86
152
|
|
|
87
153
|
//#endregion
|
|
88
|
-
export {
|
|
154
|
+
export { createHookScheduler, prepareHookMutations };
|
|
89
155
|
//# sourceMappingURL=hooks.js.map
|
package/dist/hooks/hooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.js","names":["hookContext: HookContext","txResults: TxResult<void>[]"],"sources":["../../src/hooks/hooks.ts"],"sourcesContent":["import type { RetryPolicy } from \"../query/unit-of-work/retry-policy\";\nimport { ExponentialBackoffRetryPolicy } from \"../query/unit-of-work/retry-policy\";\nimport type { IUnitOfWork } from \"../query/unit-of-work/unit-of-work\";\nimport type { InternalFragmentInstance } from \"../fragments/internal-fragment\";\nimport type { TxResult } from \"../query/unit-of-work/execute-unit-of-work\";\n\n/**\n * Context available in hook functions via `this`.\n * Contains the idempotency key for idempotency and database access.\n */\nexport interface HookContext {\n /**\n * Unique idempotency key for this transaction.\n * Use this for idempotency checks in your hook implementation.\n */\n idempotencyKey: string;\n}\n\n/**\n * A hook function signature.\n * Hooks receive a typed payload and access context via `this`.\n */\nexport type HookFn<TPayload = unknown> = (payload: TPayload) => void | Promise<void>;\n\n/**\n * Map of hook names to hook functions.\n * Used for type-safe hook definitions and triggering.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type HooksMap = Record<string, HookFn<any>>;\n\n/**\n * Extract the payload type from a hook function.\n */\nexport type HookPayload<T> = T extends HookFn<infer P> ? P : never;\n\n/**\n * Options for triggering a hook.\n */\nexport interface TriggerHookOptions {\n /**\n * Optional retry policy override for this specific hook trigger.\n * If not provided, uses the default retry policy.\n */\n retryPolicy?: RetryPolicy;\n}\n\n/**\n * Internal representation of a triggered hook.\n * Stored in the Unit of Work before execution.\n */\nexport interface TriggeredHook {\n hookName: string;\n payload: unknown;\n options?: TriggerHookOptions;\n}\n\n/**\n * Configuration for hook processing.\n */\nexport interface HookProcessorConfig {\n hooks: HooksMap;\n namespace: string;\n internalFragment: InternalFragmentInstance;\n defaultRetryPolicy?: RetryPolicy;\n}\n\n/**\n * Add hook events as mutation operations to the UOW.\n * This should be called before executeMutations() so hook records are created\n * in the same transaction as the user's mutations.\n */\nexport function prepareHookMutations(uow: IUnitOfWork, config: HookProcessorConfig): void {\n const { namespace, internalFragment, defaultRetryPolicy } = config;\n const retryPolicy = defaultRetryPolicy ?? new ExponentialBackoffRetryPolicy({ maxRetries: 5 });\n\n const triggeredHooks = uow.getTriggeredHooks();\n\n if (triggeredHooks.length === 0) {\n return;\n }\n\n const internalSchema = internalFragment.$internal.deps.schema;\n const internalUow = uow.forSchema(internalSchema);\n\n for (const hook of triggeredHooks) {\n const hookRetryPolicy = hook.options?.retryPolicy ?? retryPolicy;\n const maxAttempts = hookRetryPolicy.shouldRetry(4) ? 5 : 1;\n internalUow.create(\"fragno_hooks\", {\n namespace,\n hookName: hook.hookName,\n payload: hook.payload,\n status: \"pending\",\n attempts: 0,\n maxAttempts,\n lastAttemptAt: null,\n nextRetryAt: null,\n error: null,\n nonce: uow.idempotencyKey,\n });\n }\n}\n\n/**\n * Process pending hook events after the transaction has committed.\n * This should be called in the onSuccess callback after executeMutations().\n */\nexport async function processHooks(config: HookProcessorConfig): Promise<void> {\n const { hooks, namespace, internalFragment, defaultRetryPolicy } = config;\n const retryPolicy = defaultRetryPolicy ?? new ExponentialBackoffRetryPolicy({ maxRetries: 5 });\n\n // Get pending events\n const pendingEvents = await internalFragment.inContext(async function () {\n return await this.handlerTx()\n .withServiceCalls(\n () => [internalFragment.services.hookService.getPendingHookEvents(namespace)] as const,\n )\n .transform(({ serviceResult: [events] }) => events)\n .execute();\n });\n\n if (pendingEvents.length === 0) {\n return;\n }\n\n // Process events (async work outside transaction)\n const processedEvents = await Promise.allSettled(\n pendingEvents.map(async (event) => {\n const hookFn = hooks[event.hookName];\n if (!hookFn) {\n return {\n eventId: event.id,\n status: \"failed\" as const,\n error: `Hook '${event.hookName}' not found in hooks map`,\n attempts: event.attempts,\n maxAttempts: event.maxAttempts,\n };\n }\n\n try {\n const hookContext: HookContext = { idempotencyKey: event.idempotencyKey };\n await hookFn.call(hookContext, event.payload);\n return {\n eventId: event.id,\n status: \"completed\" as const,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n eventId: event.id,\n status: \"failed\" as const,\n error: errorMessage,\n attempts: event.attempts,\n maxAttempts: event.maxAttempts,\n };\n }\n }),\n );\n\n // Mark events as completed/failed\n await internalFragment.inContext(async function () {\n await this.handlerTx()\n .withServiceCalls(() => {\n const txResults: TxResult<void>[] = [];\n for (const processedEvent of processedEvents) {\n if (processedEvent.status === \"rejected\") {\n continue;\n }\n\n const { eventId, status } = processedEvent.value;\n\n if (status === \"completed\") {\n txResults.push(internalFragment.services.hookService.markHookCompleted(eventId));\n } else if (status === \"failed\") {\n const { error, attempts } = processedEvent.value;\n txResults.push(\n internalFragment.services.hookService.markHookFailed(\n eventId,\n error,\n attempts,\n retryPolicy,\n ),\n );\n }\n }\n return txResults;\n })\n .execute();\n });\n}\n"],"mappings":";;;;;;;;AAwEA,SAAgB,qBAAqB,KAAkB,QAAmC;CACxF,MAAM,EAAE,WAAW,kBAAkB,uBAAuB;CAC5D,MAAM,cAAc,sBAAsB,IAAI,8BAA8B,EAAE,YAAY,GAAG,CAAC;CAE9F,MAAM,iBAAiB,IAAI,mBAAmB;AAE9C,KAAI,eAAe,WAAW,EAC5B;CAGF,MAAM,iBAAiB,iBAAiB,UAAU,KAAK;CACvD,MAAM,cAAc,IAAI,UAAU,eAAe;AAEjD,MAAK,MAAM,QAAQ,gBAAgB;EAEjC,MAAM,eADkB,KAAK,SAAS,eAAe,aACjB,YAAY,EAAE,GAAG,IAAI;AACzD,cAAY,OAAO,gBAAgB;GACjC;GACA,UAAU,KAAK;GACf,SAAS,KAAK;GACd,QAAQ;GACR,UAAU;GACV;GACA,eAAe;GACf,aAAa;GACb,OAAO;GACP,OAAO,IAAI;GACZ,CAAC;;;;;;;AAQN,eAAsB,aAAa,QAA4C;CAC7E,MAAM,EAAE,OAAO,WAAW,kBAAkB,uBAAuB;CACnE,MAAM,cAAc,sBAAsB,IAAI,8BAA8B,EAAE,YAAY,GAAG,CAAC;CAG9F,MAAM,gBAAgB,MAAM,iBAAiB,UAAU,iBAAkB;AACvE,SAAO,MAAM,KAAK,WAAW,CAC1B,uBACO,CAAC,iBAAiB,SAAS,YAAY,qBAAqB,UAAU,CAAC,CAC9E,CACA,WAAW,EAAE,eAAe,CAAC,cAAc,OAAO,CAClD,SAAS;GACZ;AAEF,KAAI,cAAc,WAAW,EAC3B;CAIF,MAAM,kBAAkB,MAAM,QAAQ,WACpC,cAAc,IAAI,OAAO,UAAU;EACjC,MAAM,SAAS,MAAM,MAAM;AAC3B,MAAI,CAAC,OACH,QAAO;GACL,SAAS,MAAM;GACf,QAAQ;GACR,OAAO,SAAS,MAAM,SAAS;GAC/B,UAAU,MAAM;GAChB,aAAa,MAAM;GACpB;AAGH,MAAI;GACF,MAAMA,cAA2B,EAAE,gBAAgB,MAAM,gBAAgB;AACzE,SAAM,OAAO,KAAK,aAAa,MAAM,QAAQ;AAC7C,UAAO;IACL,SAAS,MAAM;IACf,QAAQ;IACT;WACM,OAAO;GACd,MAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAC3E,UAAO;IACL,SAAS,MAAM;IACf,QAAQ;IACR,OAAO;IACP,UAAU,MAAM;IAChB,aAAa,MAAM;IACpB;;GAEH,CACH;AAGD,OAAM,iBAAiB,UAAU,iBAAkB;AACjD,QAAM,KAAK,WAAW,CACnB,uBAAuB;GACtB,MAAMC,YAA8B,EAAE;AACtC,QAAK,MAAM,kBAAkB,iBAAiB;AAC5C,QAAI,eAAe,WAAW,WAC5B;IAGF,MAAM,EAAE,SAAS,WAAW,eAAe;AAE3C,QAAI,WAAW,YACb,WAAU,KAAK,iBAAiB,SAAS,YAAY,kBAAkB,QAAQ,CAAC;aACvE,WAAW,UAAU;KAC9B,MAAM,EAAE,OAAO,aAAa,eAAe;AAC3C,eAAU,KACR,iBAAiB,SAAS,YAAY,eACpC,SACA,OACA,UACA,YACD,CACF;;;AAGL,UAAO;IACP,CACD,SAAS;GACZ"}
|
|
1
|
+
{"version":3,"file":"hooks.js","names":["hookContext: HookContext","txResults: TxResult<void>[]","currentPromise: Promise<number> | null"],"sources":["../../src/hooks/hooks.ts"],"sourcesContent":["import type {\n ExecuteTxOptions,\n HandlerTxBuilder,\n} from \"../query/unit-of-work/execute-unit-of-work\";\nimport type { RetryPolicy } from \"../query/unit-of-work/retry-policy\";\nimport { ExponentialBackoffRetryPolicy } from \"../query/unit-of-work/retry-policy\";\nimport type { IUnitOfWork } from \"../query/unit-of-work/unit-of-work\";\nimport type { InternalFragmentInstance } from \"../fragments/internal-fragment\";\nimport type { TxResult } from \"../query/unit-of-work/execute-unit-of-work\";\nimport type { FragnoId } from \"../schema/create\";\n\n/**\n * Context available in hook functions via `this`.\n * Contains the idempotency key for idempotency and database access.\n */\nexport interface HookContext {\n /**\n * Unique idempotency key for this transaction.\n * Use this for idempotency checks in your hook implementation.\n */\n idempotencyKey: string;\n /**\n * Create a handler transaction builder to run atomic operations.\n */\n handlerTx: HookHandlerTx;\n}\n\n/**\n * A hook function signature.\n * Hooks receive a typed payload and access context via `this`.\n */\nexport type HookFn<TPayload = unknown> = (payload: TPayload) => void | Promise<void>;\n\n/**\n * Map of hook names to hook functions.\n * Used for type-safe hook definitions and triggering.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type HooksMap = Record<string, HookFn<any>>;\n\n/**\n * Extract the payload type from a hook function.\n */\nexport type HookPayload<T> = T extends HookFn<infer P> ? P : never;\n\n/**\n * Options for triggering a hook.\n */\nexport interface TriggerHookOptions {\n /**\n * Optional retry policy override for this specific hook trigger.\n * If not provided, uses the default retry policy.\n */\n retryPolicy?: RetryPolicy;\n /**\n * Absolute time for the first attempt. If in the future, the hook is\n * scheduled for that time; if in the past, it runs immediately.\n */\n processAt?: Date;\n}\n\n/**\n * Internal representation of a triggered hook.\n * Stored in the Unit of Work before execution.\n */\nexport interface TriggeredHook {\n hookName: string;\n payload: unknown;\n options?: TriggerHookOptions;\n}\n\nexport type HookScheduler = {\n schedule: () => Promise<number>;\n drain: () => Promise<void>;\n};\n\n/**\n * Configuration for hook processing.\n */\nexport interface HookProcessorConfig<THooks extends HooksMap = HooksMap> {\n hooks: THooks;\n namespace: string;\n internalFragment: InternalFragmentInstance;\n handlerTx: HookHandlerTx;\n /**\n * Internal hook scheduler used to coordinate processing/drain.\n */\n scheduler?: HookScheduler;\n defaultRetryPolicy?: RetryPolicy;\n /**\n * Re-queue hooks that have been in `processing` for at least this many minutes.\n * Use `false` to disable stuck-processing recovery entirely.\n * Values <= 0 are treated as `false`.\n *\n * Default: 10 minutes.\n */\n stuckProcessingTimeoutMinutes?: StuckHookProcessingTimeoutMinutes;\n /**\n * Called when stuck processing hooks are detected and re-queued.\n * Invoked after the hooks are moved back to `pending`.\n */\n onStuckProcessingHooks?: (info: StuckHookProcessingInfo) => void;\n}\n\nexport type StuckHookProcessingTimeoutMinutes = number | false;\n\nexport type StuckHookProcessingEvent = {\n id: FragnoId;\n hookName: string;\n attempts: number;\n maxAttempts: number;\n lastAttemptAt: Date | null;\n nextRetryAt: Date | null;\n};\n\nexport type StuckHookProcessingInfo = {\n namespace: string;\n timeoutMinutes: number;\n events: StuckHookProcessingEvent[];\n};\n\nexport type DurableHooksProcessingOptions = {\n /**\n * Re-queue hooks that have been in `processing` for at least this many minutes.\n * Use `false` to disable stuck-processing recovery entirely.\n * Values <= 0 are treated as `false`.\n *\n * Default: 10 minutes.\n */\n stuckProcessingTimeoutMinutes?: StuckHookProcessingTimeoutMinutes;\n /**\n * Called when stuck processing hooks are detected and re-queued.\n * Invoked after the hooks are moved back to `pending`.\n */\n onStuckProcessingHooks?: (info: StuckHookProcessingInfo) => void;\n};\n\nexport type HookHandlerTx = (\n execOptions?: Omit<ExecuteTxOptions, \"createUnitOfWork\">,\n) => HandlerTxBuilder<readonly [], [], [], unknown, unknown, false, false, false, false, HooksMap>;\n\nconst DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES = 10;\n\nfunction resolveStuckProcessingTimeoutMinutes(\n value: StuckHookProcessingTimeoutMinutes | undefined,\n): number | false {\n if (value === false) {\n return false;\n }\n if (typeof value === \"number\") {\n return value > 0 ? value : false;\n }\n return DEFAULT_STUCK_PROCESSING_TIMEOUT_MINUTES;\n}\n\n/**\n * Add hook events as mutation operations to the UOW.\n * This should be called before executeMutations() so hook records are created\n * in the same transaction as the user's mutations.\n */\nexport function prepareHookMutations<THooks extends HooksMap>(\n uow: IUnitOfWork,\n config: HookProcessorConfig<THooks>,\n): void {\n const { namespace, internalFragment, defaultRetryPolicy } = config;\n const retryPolicy = defaultRetryPolicy ?? new ExponentialBackoffRetryPolicy({ maxRetries: 5 });\n\n const triggeredHooks = uow.getTriggeredHooks();\n\n if (triggeredHooks.length === 0) {\n return;\n }\n\n const internalSchema = internalFragment.$internal.deps.schema;\n const internalUow = uow.forSchema(internalSchema);\n\n for (const hook of triggeredHooks) {\n const hookRetryPolicy = hook.options?.retryPolicy ?? retryPolicy;\n const maxAttempts = hookRetryPolicy.shouldRetry(4) ? 5 : 1;\n const processAt = hook.options?.processAt ? new Date(hook.options.processAt) : null;\n const nextRetryAt = processAt ?? null;\n internalUow.create(\"fragno_hooks\", {\n namespace,\n hookName: hook.hookName,\n payload: hook.payload,\n status: \"pending\",\n attempts: 0,\n maxAttempts,\n lastAttemptAt: null,\n nextRetryAt,\n error: null,\n nonce: uow.idempotencyKey,\n });\n }\n}\n\n/**\n * Process pending hook events after the transaction has committed.\n * This should be called in the onSuccess callback after executeMutations().\n */\nexport async function processHooks<THooks extends HooksMap>(\n config: HookProcessorConfig<THooks>,\n): Promise<number> {\n const { hooks, namespace, internalFragment, defaultRetryPolicy } = config;\n const retryPolicy = defaultRetryPolicy ?? new ExponentialBackoffRetryPolicy({ maxRetries: 5 });\n const stuckProcessingTimeoutMinutes = resolveStuckProcessingTimeoutMinutes(\n config.stuckProcessingTimeoutMinutes,\n );\n const getDbNow = async () => {\n const services = internalFragment.services as { getDbNow?: () => Promise<Date> };\n if (services.getDbNow) {\n return services.getDbNow();\n }\n return new Date();\n };\n const dbNow = await getDbNow();\n\n if (stuckProcessingTimeoutMinutes !== false) {\n const staleBefore = new Date(dbNow.getTime() - stuckProcessingTimeoutMinutes * 60_000);\n const stuckEvents = await internalFragment.inContext(async function () {\n return await this.handlerTx()\n .withServiceCalls(\n () =>\n [\n internalFragment.services.hookService.requeueStuckProcessingHooks(\n namespace,\n staleBefore,\n ),\n ] as const,\n )\n .transform(({ serviceResult: [events] }) => events)\n .execute();\n });\n\n if (stuckEvents.length > 0) {\n try {\n config.onStuckProcessingHooks?.({\n namespace,\n timeoutMinutes: stuckProcessingTimeoutMinutes,\n events: stuckEvents,\n });\n } catch (error) {\n console.error(\"Error calling onStuckProcessingHooks\", error);\n }\n }\n }\n\n // Claim pending events in the same transaction to avoid double-processing.\n const pendingEvents = await internalFragment.inContext(async function () {\n return await this.handlerTx()\n .withServiceCalls(\n () => [internalFragment.services.hookService.claimPendingHookEvents(namespace)] as const,\n )\n .transform(({ serviceResult: [events] }) => events)\n .execute();\n });\n\n if (pendingEvents.length === 0) {\n return 0;\n }\n\n // Process events (async work outside transaction)\n const processedEvents = await Promise.allSettled(\n pendingEvents.map(async (event) => {\n const hookFn = hooks[event.hookName];\n if (!hookFn) {\n return {\n eventId: event.id,\n status: \"failed\" as const,\n error: `Hook '${event.hookName}' not found in hooks map`,\n attempts: event.attempts,\n maxAttempts: event.maxAttempts,\n };\n }\n\n try {\n const hookContext: HookContext = {\n idempotencyKey: event.idempotencyKey,\n handlerTx: config.handlerTx,\n };\n await hookFn.call(hookContext, event.payload);\n return {\n eventId: event.id,\n status: \"completed\" as const,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n eventId: event.id,\n status: \"failed\" as const,\n error: errorMessage,\n attempts: event.attempts,\n maxAttempts: event.maxAttempts,\n };\n }\n }),\n );\n\n // Mark events as completed/failed\n await internalFragment.inContext(async function () {\n await this.handlerTx()\n .withServiceCalls(() => {\n const txResults: TxResult<void>[] = [];\n for (const processedEvent of processedEvents) {\n if (processedEvent.status === \"rejected\") {\n continue;\n }\n\n const { eventId, status } = processedEvent.value;\n\n if (status === \"completed\") {\n txResults.push(internalFragment.services.hookService.markHookCompleted(eventId));\n } else if (status === \"failed\") {\n const { error, attempts } = processedEvent.value;\n txResults.push(\n internalFragment.services.hookService.markHookFailed(\n eventId,\n error,\n attempts,\n retryPolicy,\n dbNow,\n ),\n );\n }\n }\n return txResults;\n })\n .execute();\n });\n\n const processedCount = processedEvents.reduce(\n (count, result) => count + (result.status === \"fulfilled\" ? 1 : 0),\n 0,\n );\n\n return processedCount;\n}\n\nexport function createHookScheduler(config: HookProcessorConfig): HookScheduler {\n let processing = false;\n let queued = false;\n let currentPromise: Promise<number> | null = null;\n\n const schedule = async () => {\n if (processing) {\n queued = true;\n return currentPromise ?? Promise.resolve(0);\n }\n\n processing = true;\n currentPromise = (async () => {\n let lastCount = 0;\n try {\n do {\n queued = false;\n lastCount = await processHooks(config);\n } while (queued);\n return lastCount;\n } finally {\n processing = false;\n queued = false;\n }\n })();\n\n return currentPromise;\n };\n\n const drain = async () => {\n while (true) {\n const processed = await schedule();\n if (processed === 0) {\n return;\n }\n }\n };\n\n return { schedule, drain };\n}\n"],"mappings":"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"}
|
|
@@ -16,7 +16,7 @@ import "../schema/create.js";
|
|
|
16
16
|
*
|
|
17
17
|
* @example
|
|
18
18
|
* ```ts
|
|
19
|
-
* const mySchema = schema(s => s
|
|
19
|
+
* const mySchema = schema("my", s => s
|
|
20
20
|
* .addTable("users", t => t.addColumn("id", idColumn())) // version 1
|
|
21
21
|
* .addTable("posts", t => t.addColumn("id", idColumn())) // version 2
|
|
22
22
|
* );
|
|
@@ -92,16 +92,19 @@ function generateMigrationFromSchema(targetSchema, fromVersion, toVersion) {
|
|
|
92
92
|
columns: subOp.columns,
|
|
93
93
|
unique: subOp.unique
|
|
94
94
|
});
|
|
95
|
-
} else if (op.type === "add-reference")
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
95
|
+
} else if (op.type === "add-reference") {
|
|
96
|
+
if (!op.referenceName || op.referenceName.trim().length === 0) throw new Error(`referenceName is required for add-reference on ${op.tableName}`);
|
|
97
|
+
migrationOperations.push({
|
|
98
|
+
type: "add-foreign-key",
|
|
99
|
+
table: op.tableName,
|
|
100
|
+
value: {
|
|
101
|
+
name: op.referenceName,
|
|
102
|
+
columns: [op.config.from.column],
|
|
103
|
+
referencedTable: op.config.to.table,
|
|
104
|
+
referencedColumns: [op.config.to.column]
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
105
108
|
return migrationOperations;
|
|
106
109
|
}
|
|
107
110
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-from-schema.js","names":["migrationOperations: MigrationOperation[]","columns: ColumnInfo[]"],"sources":["../../src/migration-engine/auto-from-schema.ts"],"sourcesContent":["import { type AnySchema } from \"../schema/create\";\nimport type { MigrationOperation, ColumnInfo } from \"./shared\";\n\n/**\n * Generate migration operations from a schema's operation history\n *\n * The schema version number represents the cumulative number of operations applied.\n * This function takes operations from the schema between fromVersion and toVersion,\n * and converts them into migration operations.\n *\n * @param targetSchema - The schema containing the operations history\n * @param fromVersion - The current database version (e.g., 0)\n * @param toVersion - The target version to migrate to (e.g., 5)\n * @param options - Migration generation options\n * @returns Array of migration operations to apply\n *\n * @example\n * ```ts\n * const mySchema = schema(s => s\n * .addTable(\"users\", t => t.addColumn(\"id\", idColumn())) // version 1\n * .addTable(\"posts\", t => t.addColumn(\"id\", idColumn())) // version 2\n * );\n *\n * // Generate operations from version 0 to 1 (only creates users table)\n * const operations = generateMigrationFromSchema(mySchema, 0, 1);\n * ```\n */\nexport function generateMigrationFromSchema(\n targetSchema: AnySchema,\n fromVersion: number,\n toVersion: number,\n): MigrationOperation[] {\n if (fromVersion < 0) {\n throw new Error(`fromVersion cannot be negative: ${fromVersion}`);\n }\n\n if (fromVersion > targetSchema.version) {\n throw new Error(\n `fromVersion (${fromVersion}) exceeds schema version (${targetSchema.version})`,\n );\n }\n\n if (toVersion > targetSchema.version) {\n throw new Error(`toVersion (${toVersion}) exceeds schema version (${targetSchema.version})`);\n }\n\n if (toVersion < fromVersion) {\n throw new Error(\n `Cannot migrate backwards: toVersion (${toVersion}) < fromVersion (${fromVersion})`,\n );\n }\n\n // Get operations between fromVersion and toVersion\n // Operations are 1-indexed (operation 0 is version 0→1)\n const relevantOperations = targetSchema.operations.slice(fromVersion, toVersion);\n\n // Convert schema operations to migration operations\n const migrationOperations: MigrationOperation[] = [];\n\n for (const op of relevantOperations) {\n if (op.type === \"add-table\") {\n // Collect columns for create-table operation\n const columns: ColumnInfo[] = [];\n\n for (const subOp of op.operations) {\n if (subOp.type === \"add-column\") {\n const col = subOp.column;\n columns.push({\n name: subOp.columnName,\n type: col.type,\n isNullable: col.isNullable,\n role: col.role,\n default: col.default\n ? \"value\" in col.default\n ? { value: col.default.value }\n : \"dbSpecial\" in col.default\n ? { dbSpecial: col.default.dbSpecial }\n : \"runtime\" in col.default && typeof col.default.runtime === \"string\"\n ? { runtime: col.default.runtime }\n : undefined\n : undefined,\n });\n }\n }\n\n migrationOperations.push({\n type: \"create-table\",\n name: op.tableName,\n columns,\n });\n\n // Add indexes and foreign keys as separate operations\n for (const subOp of op.operations) {\n if (subOp.type === \"add-index\") {\n migrationOperations.push({\n type: \"add-index\",\n table: op.tableName,\n name: subOp.name,\n columns: subOp.columns,\n unique: subOp.unique,\n });\n } else if (subOp.type === \"add-foreign-key\") {\n migrationOperations.push({\n type: \"add-foreign-key\",\n table: op.tableName,\n value: {\n name: subOp.name,\n columns: subOp.columns,\n referencedTable: subOp.referencedTable,\n referencedColumns: subOp.referencedColumns,\n },\n });\n }\n }\n } else if (op.type === \"alter-table\") {\n const columnOps = op.operations.filter((o) => o.type === \"add-column\");\n\n if (columnOps.length > 0) {\n migrationOperations.push({\n type: \"alter-table\",\n name: op.tableName,\n value: columnOps.map((o) => {\n const col = o.column;\n return {\n type: \"create-column\" as const,\n value: {\n name: o.columnName,\n type: col.type,\n isNullable: col.isNullable,\n role: col.role,\n default: col.default\n ? \"value\" in col.default\n ? { value: col.default.value }\n : \"dbSpecial\" in col.default\n ? { dbSpecial: col.default.dbSpecial }\n : \"runtime\" in col.default && typeof col.default.runtime === \"string\"\n ? { runtime: col.default.runtime }\n : undefined\n : undefined,\n },\n };\n }),\n });\n }\n\n // Add indexes as separate operations\n for (const subOp of op.operations) {\n if (subOp.type === \"add-index\") {\n migrationOperations.push({\n type: \"add-index\",\n table: op.tableName,\n name: subOp.name,\n columns: subOp.columns,\n unique: subOp.unique,\n });\n }\n }\n } else if (op.type === \"add-reference\") {\n migrationOperations.push({\n type: \"add-foreign-key\",\n table: op.tableName,\n value: {\n name:
|
|
1
|
+
{"version":3,"file":"auto-from-schema.js","names":["migrationOperations: MigrationOperation[]","columns: ColumnInfo[]"],"sources":["../../src/migration-engine/auto-from-schema.ts"],"sourcesContent":["import { type AnySchema } from \"../schema/create\";\nimport type { MigrationOperation, ColumnInfo } from \"./shared\";\n\n/**\n * Generate migration operations from a schema's operation history\n *\n * The schema version number represents the cumulative number of operations applied.\n * This function takes operations from the schema between fromVersion and toVersion,\n * and converts them into migration operations.\n *\n * @param targetSchema - The schema containing the operations history\n * @param fromVersion - The current database version (e.g., 0)\n * @param toVersion - The target version to migrate to (e.g., 5)\n * @param options - Migration generation options\n * @returns Array of migration operations to apply\n *\n * @example\n * ```ts\n * const mySchema = schema(\"my\", s => s\n * .addTable(\"users\", t => t.addColumn(\"id\", idColumn())) // version 1\n * .addTable(\"posts\", t => t.addColumn(\"id\", idColumn())) // version 2\n * );\n *\n * // Generate operations from version 0 to 1 (only creates users table)\n * const operations = generateMigrationFromSchema(mySchema, 0, 1);\n * ```\n */\nexport function generateMigrationFromSchema(\n targetSchema: AnySchema,\n fromVersion: number,\n toVersion: number,\n): MigrationOperation[] {\n if (fromVersion < 0) {\n throw new Error(`fromVersion cannot be negative: ${fromVersion}`);\n }\n\n if (fromVersion > targetSchema.version) {\n throw new Error(\n `fromVersion (${fromVersion}) exceeds schema version (${targetSchema.version})`,\n );\n }\n\n if (toVersion > targetSchema.version) {\n throw new Error(`toVersion (${toVersion}) exceeds schema version (${targetSchema.version})`);\n }\n\n if (toVersion < fromVersion) {\n throw new Error(\n `Cannot migrate backwards: toVersion (${toVersion}) < fromVersion (${fromVersion})`,\n );\n }\n\n // Get operations between fromVersion and toVersion\n // Operations are 1-indexed (operation 0 is version 0→1)\n const relevantOperations = targetSchema.operations.slice(fromVersion, toVersion);\n\n // Convert schema operations to migration operations\n const migrationOperations: MigrationOperation[] = [];\n\n for (const op of relevantOperations) {\n if (op.type === \"add-table\") {\n // Collect columns for create-table operation\n const columns: ColumnInfo[] = [];\n\n for (const subOp of op.operations) {\n if (subOp.type === \"add-column\") {\n const col = subOp.column;\n columns.push({\n name: subOp.columnName,\n type: col.type,\n isNullable: col.isNullable,\n role: col.role,\n default: col.default\n ? \"value\" in col.default\n ? { value: col.default.value }\n : \"dbSpecial\" in col.default\n ? { dbSpecial: col.default.dbSpecial }\n : \"runtime\" in col.default && typeof col.default.runtime === \"string\"\n ? { runtime: col.default.runtime }\n : undefined\n : undefined,\n });\n }\n }\n\n migrationOperations.push({\n type: \"create-table\",\n name: op.tableName,\n columns,\n });\n\n // Add indexes and foreign keys as separate operations\n for (const subOp of op.operations) {\n if (subOp.type === \"add-index\") {\n migrationOperations.push({\n type: \"add-index\",\n table: op.tableName,\n name: subOp.name,\n columns: subOp.columns,\n unique: subOp.unique,\n });\n } else if (subOp.type === \"add-foreign-key\") {\n migrationOperations.push({\n type: \"add-foreign-key\",\n table: op.tableName,\n value: {\n name: subOp.name,\n columns: subOp.columns,\n referencedTable: subOp.referencedTable,\n referencedColumns: subOp.referencedColumns,\n },\n });\n }\n }\n } else if (op.type === \"alter-table\") {\n const columnOps = op.operations.filter((o) => o.type === \"add-column\");\n\n if (columnOps.length > 0) {\n migrationOperations.push({\n type: \"alter-table\",\n name: op.tableName,\n value: columnOps.map((o) => {\n const col = o.column;\n return {\n type: \"create-column\" as const,\n value: {\n name: o.columnName,\n type: col.type,\n isNullable: col.isNullable,\n role: col.role,\n default: col.default\n ? \"value\" in col.default\n ? { value: col.default.value }\n : \"dbSpecial\" in col.default\n ? { dbSpecial: col.default.dbSpecial }\n : \"runtime\" in col.default && typeof col.default.runtime === \"string\"\n ? { runtime: col.default.runtime }\n : undefined\n : undefined,\n },\n };\n }),\n });\n }\n\n // Add indexes as separate operations\n for (const subOp of op.operations) {\n if (subOp.type === \"add-index\") {\n migrationOperations.push({\n type: \"add-index\",\n table: op.tableName,\n name: subOp.name,\n columns: subOp.columns,\n unique: subOp.unique,\n });\n }\n }\n } else if (op.type === \"add-reference\") {\n if (!op.referenceName || op.referenceName.trim().length === 0) {\n throw new Error(`referenceName is required for add-reference on ${op.tableName}`);\n }\n migrationOperations.push({\n type: \"add-foreign-key\",\n table: op.tableName,\n value: {\n name: op.referenceName,\n columns: [op.config.from.column],\n referencedTable: op.config.to.table,\n referencedColumns: [op.config.to.column],\n },\n });\n }\n }\n\n return migrationOperations;\n}\n"],"mappings":"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"}
|
|
@@ -5,26 +5,32 @@ import { FragnoDatabase } from "../mod.js";
|
|
|
5
5
|
interface GenerationEngineResult {
|
|
6
6
|
schema: string;
|
|
7
7
|
path: string;
|
|
8
|
-
namespace: string;
|
|
8
|
+
namespace: string | null;
|
|
9
|
+
}
|
|
10
|
+
type SchemaOutputFormat = "sql" | "drizzle" | "prisma";
|
|
11
|
+
interface GenerateSchemaOptions {
|
|
12
|
+
format?: SchemaOutputFormat;
|
|
13
|
+
path?: string;
|
|
14
|
+
toVersion?: number;
|
|
15
|
+
fromVersion?: number;
|
|
9
16
|
}
|
|
10
17
|
interface GenerationInternalResult {
|
|
11
18
|
schema: string;
|
|
12
19
|
path: string;
|
|
13
|
-
namespace: string;
|
|
20
|
+
namespace: string | null;
|
|
21
|
+
namespaceKey: string;
|
|
22
|
+
schemaName: string;
|
|
23
|
+
isSettings: boolean;
|
|
14
24
|
fromVersion: number;
|
|
15
25
|
toVersion: number;
|
|
16
26
|
}
|
|
17
27
|
interface ExecuteMigrationResult {
|
|
18
|
-
namespace: string;
|
|
28
|
+
namespace: string | null;
|
|
19
29
|
didMigrate: boolean;
|
|
20
30
|
fromVersion: number;
|
|
21
31
|
toVersion: number;
|
|
22
32
|
}
|
|
23
|
-
declare function
|
|
24
|
-
path?: string;
|
|
25
|
-
toVersion?: number;
|
|
26
|
-
fromVersion?: number;
|
|
27
|
-
}): Promise<GenerationEngineResult[]>;
|
|
33
|
+
declare function generateSchemaArtifacts<const TDatabases extends FragnoDatabase<AnySchema, any>[]>(databases: TDatabases, options?: GenerateSchemaOptions): Promise<GenerationEngineResult[]>;
|
|
28
34
|
/**
|
|
29
35
|
* Execute migrations for all fragments in the correct order.
|
|
30
36
|
* Migrates settings table first, then fragments alphabetically.
|
|
@@ -36,7 +42,7 @@ declare function executeMigrations<const TDatabases extends FragnoDatabase<AnySc
|
|
|
36
42
|
/**
|
|
37
43
|
* Post-processes migration files to add ordering and standardize naming.
|
|
38
44
|
*
|
|
39
|
-
* Sorts files with settings namespace first, then alphabetically by namespace,
|
|
45
|
+
* Sorts files with settings namespace first, then alphabetically by namespace key,
|
|
40
46
|
* and assigns ordering numbers. Transforms filenames to format:
|
|
41
47
|
* `<date>_<n>_f<from>_t<to>_<namespace>.sql`
|
|
42
48
|
*
|
|
@@ -45,5 +51,5 @@ declare function executeMigrations<const TDatabases extends FragnoDatabase<AnySc
|
|
|
45
51
|
*/
|
|
46
52
|
declare function postProcessMigrationFilenames(files: GenerationInternalResult[]): GenerationEngineResult[];
|
|
47
53
|
//#endregion
|
|
48
|
-
export { ExecuteMigrationResult, GenerationEngineResult, GenerationInternalResult, executeMigrations,
|
|
54
|
+
export { ExecuteMigrationResult, GenerateSchemaOptions, GenerationEngineResult, GenerationInternalResult, SchemaOutputFormat, executeMigrations, generateSchemaArtifacts, postProcessMigrationFilenames };
|
|
49
55
|
//# sourceMappingURL=generation-engine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generation-engine.d.ts","names":[],"sources":["../../src/migration-engine/generation-engine.ts"],"sourcesContent":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"generation-engine.d.ts","names":[],"sources":["../../src/migration-engine/generation-engine.ts"],"sourcesContent":[],"mappings":";;;;UAaiB,sBAAA;;EAAA,IAAA,EAAA,MAAA;EAML,SAAA,EAAA,MAAA,GAAA,IAAkB;AAE9B;AAOiB,KATL,kBAAA,GAS6B,KAAA,GAAA,SAAA,GAAA,QAAA;AAWxB,UAlBA,qBAAA,CAkBsB;EAajB,MAAA,CAAA,EA9BX,kBA8BkC;EAEH,IAAA,CAAA,EAAA,MAAA;EAAf,SAAA,CAAA,EAAA,MAAA;EACd,WAAA,CAAA,EAAA,MAAA;;AAAsD,UA3BlD,wBAAA,CA2BkD;EAAR,MAAA,EAAA,MAAA;EAAO,IAAA,EAAA,MAAA;EA4J5C,SAAA,EAAA,MAAA,GAAiB,IAAA;EAAyC,YAAA,EAAA,MAAA;EAAf,UAAA,EAAA,MAAA;EACpD,UAAA,EAAA,OAAA;EACF,WAAA,EAAA,MAAA;EAAR,SAAA,EAAA,MAAA;;AAwJa,UAtUC,sBAAA,CAsU4B;;;;;;iBAzTvB,iDAEK,eAAe,8BAC7B,sBAAsB,wBAAwB,QAAQ;;;;;;;;iBA4J7C,2CAA2C,eAAe,yBACnE,aACV,QAAQ;;;;;;;;;;;iBAwJK,6BAAA,QACP,6BACN"}
|
|
@@ -1,32 +1,53 @@
|
|
|
1
1
|
import { instantiate } from "../packages/fragno/dist/api/fragment-instantiator.js";
|
|
2
|
-
import { SETTINGS_NAMESPACE, getSchemaVersionFromDatabase, internalFragmentDef, internalSchema } from "../fragments/internal-fragment.js";
|
|
3
2
|
import { fragnoDatabaseAdapterNameFakeSymbol, fragnoDatabaseAdapterVersionFakeSymbol } from "../adapters/adapters.js";
|
|
3
|
+
import { SETTINGS_TABLE_NAME, internalSchema } from "../fragments/internal-fragment.schema.js";
|
|
4
|
+
import { supportedDatabases } from "../adapters/generic-sql/driver-config.js";
|
|
5
|
+
import { getSchemaVersionFromDatabase, internalFragmentDef } from "../fragments/internal-fragment.js";
|
|
6
|
+
import { generateDrizzleSchema } from "../schema-output/drizzle.js";
|
|
7
|
+
import { generatePrismaSchema } from "../schema-output/prisma.js";
|
|
4
8
|
|
|
5
9
|
//#region src/migration-engine/generation-engine.ts
|
|
6
|
-
|
|
10
|
+
const DEFAULT_DRIZZLE_PATH = "fragno-schema.ts";
|
|
11
|
+
const DEFAULT_PRISMA_PATH = "fragno.prisma";
|
|
12
|
+
const isSupportedDatabase = (value) => supportedDatabases.includes(value);
|
|
13
|
+
async function generateSchemaArtifacts(databases, options) {
|
|
7
14
|
if (databases.length === 0) throw new Error("No databases provided for schema generation");
|
|
8
15
|
const firstDb = databases[0];
|
|
9
16
|
const adapter = firstDb.adapter;
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
const format = options?.format ?? "sql";
|
|
18
|
+
if (format !== "sql") {
|
|
19
|
+
if (options?.toVersion !== void 0 || options?.fromVersion !== void 0) throw new Error("--from and --to are only supported when generating SQL migrations.");
|
|
20
|
+
const databaseType = adapter.adapterMetadata?.databaseType;
|
|
21
|
+
if (!databaseType || !isSupportedDatabase(databaseType)) throw new Error("Adapter does not expose databaseType metadata required for schema output generation.");
|
|
12
22
|
const fragmentsMap = /* @__PURE__ */ new Map();
|
|
13
|
-
fragmentsMap.set(
|
|
23
|
+
fragmentsMap.set(internalSchema.name, {
|
|
14
24
|
schema: internalSchema,
|
|
15
|
-
namespace:
|
|
16
|
-
});
|
|
17
|
-
for (const db of databases) if (!fragmentsMap.has(db.namespace)) fragmentsMap.set(db.namespace, {
|
|
18
|
-
schema: db.schema,
|
|
19
|
-
namespace: db.namespace
|
|
25
|
+
namespace: null
|
|
20
26
|
});
|
|
27
|
+
for (const db of databases) {
|
|
28
|
+
const namespaceKey = db.namespace ?? db.schema.name;
|
|
29
|
+
if (!fragmentsMap.has(namespaceKey)) fragmentsMap.set(namespaceKey, {
|
|
30
|
+
schema: db.schema,
|
|
31
|
+
namespace: db.namespace
|
|
32
|
+
});
|
|
33
|
+
}
|
|
21
34
|
const allFragments = Array.from(fragmentsMap.values());
|
|
35
|
+
const defaultPath = format === "drizzle" ? DEFAULT_DRIZZLE_PATH : DEFAULT_PRISMA_PATH;
|
|
22
36
|
return [{
|
|
23
|
-
|
|
37
|
+
schema: format === "drizzle" ? generateDrizzleSchema(allFragments, databaseType, { namingStrategy: adapter.namingStrategy }) : generatePrismaSchema(allFragments, databaseType, {
|
|
38
|
+
sqliteStorageMode: adapter.adapterMetadata?.sqliteStorageMode,
|
|
39
|
+
namingStrategy: adapter.namingStrategy
|
|
40
|
+
}),
|
|
41
|
+
path: options?.path ?? defaultPath,
|
|
24
42
|
namespace: firstDb.namespace
|
|
25
43
|
}];
|
|
26
44
|
}
|
|
27
|
-
if (!adapter.prepareMigrations) throw new Error("Adapter does not support migration
|
|
45
|
+
if (!adapter.prepareMigrations) throw new Error("Adapter does not support migration generation. Ensure your adapter implements prepareMigrations.");
|
|
28
46
|
if (!await adapter.isConnectionHealthy()) throw new Error("Database connection is not healthy. Please check your database connection and try again.");
|
|
29
|
-
const settingsSourceVersion = await getSchemaVersionFromDatabase(instantiate(internalFragmentDef).withConfig({}).withOptions({
|
|
47
|
+
const settingsSourceVersion = await getSchemaVersionFromDatabase(instantiate(internalFragmentDef).withConfig({}).withOptions({
|
|
48
|
+
databaseAdapter: adapter,
|
|
49
|
+
databaseNamespace: null
|
|
50
|
+
}).build(), "");
|
|
30
51
|
const generatedFiles = [];
|
|
31
52
|
const settingsPreparedMigrations = adapter.prepareMigrations(internalSchema, "");
|
|
32
53
|
const settingsTargetVersion = internalSchema.version;
|
|
@@ -34,13 +55,16 @@ async function generateMigrationsOrSchema(databases, options) {
|
|
|
34
55
|
if (settingsSql.trim()) generatedFiles.push({
|
|
35
56
|
schema: settingsSql,
|
|
36
57
|
path: "settings-migration.sql",
|
|
37
|
-
namespace:
|
|
58
|
+
namespace: null,
|
|
59
|
+
namespaceKey: SETTINGS_TABLE_NAME,
|
|
60
|
+
schemaName: internalSchema.name,
|
|
61
|
+
isSettings: true,
|
|
38
62
|
fromVersion: settingsSourceVersion,
|
|
39
63
|
toVersion: settingsTargetVersion
|
|
40
64
|
});
|
|
41
65
|
for (const db of databases) {
|
|
42
66
|
const dbAdapter = db.adapter;
|
|
43
|
-
if (!dbAdapter.prepareMigrations) throw new Error(`Adapter for ${db.namespace} does not support
|
|
67
|
+
if (!dbAdapter.prepareMigrations) throw new Error(`Adapter for ${db.namespace ?? db.schema.name} does not support migration generation. Ensure your adapter implements prepareMigrations.`);
|
|
44
68
|
const preparedMigrations = dbAdapter.prepareMigrations(db.schema, db.namespace);
|
|
45
69
|
const targetVersion = options?.toVersion ?? db.schema.version;
|
|
46
70
|
const sourceVersion = options?.fromVersion ?? 0;
|
|
@@ -49,6 +73,9 @@ async function generateMigrationsOrSchema(databases, options) {
|
|
|
49
73
|
schema: sql,
|
|
50
74
|
path: "schema.sql",
|
|
51
75
|
namespace: db.namespace,
|
|
76
|
+
namespaceKey: db.namespace ?? db.schema.name,
|
|
77
|
+
schemaName: db.schema.name,
|
|
78
|
+
isSettings: false,
|
|
52
79
|
fromVersion: sourceVersion,
|
|
53
80
|
toVersion: targetVersion
|
|
54
81
|
});
|
|
@@ -65,7 +92,7 @@ async function generateMigrationsOrSchema(databases, options) {
|
|
|
65
92
|
async function executeMigrations(databases) {
|
|
66
93
|
if (databases.length === 0) throw new Error("No databases provided for migration");
|
|
67
94
|
const adapter = databases[0].adapter;
|
|
68
|
-
if (!adapter.prepareMigrations) throw new Error("Adapter does not support running migrations. The adapter only supports schema generation.\nTry using '
|
|
95
|
+
if (!adapter.prepareMigrations) throw new Error("Adapter does not support running migrations. The adapter only supports schema generation.\nTry using 'generateSchemaArtifacts' instead to generate schema files.");
|
|
69
96
|
const firstAdapterName = adapter[fragnoDatabaseAdapterNameFakeSymbol];
|
|
70
97
|
const firstAdapterVersion = adapter[fragnoDatabaseAdapterVersionFakeSymbol];
|
|
71
98
|
for (const db of databases) {
|
|
@@ -76,32 +103,40 @@ async function executeMigrations(databases) {
|
|
|
76
103
|
if (!await adapter.isConnectionHealthy()) throw new Error("Database connection is not healthy. Please check your database connection and try again.");
|
|
77
104
|
const results = [];
|
|
78
105
|
const migrationsToExecute = [];
|
|
79
|
-
const internalFragment = instantiate(internalFragmentDef).withConfig({}).withOptions({
|
|
80
|
-
|
|
106
|
+
const internalFragment = instantiate(internalFragmentDef).withConfig({}).withOptions({
|
|
107
|
+
databaseAdapter: adapter,
|
|
108
|
+
databaseNamespace: null
|
|
109
|
+
}).build();
|
|
110
|
+
const settingsSourceVersion = await getSchemaVersionFromDatabase(internalFragment, "");
|
|
81
111
|
const settingsPreparedMigrations = adapter.prepareMigrations(internalSchema, "");
|
|
82
112
|
const settingsTargetVersion = internalSchema.version;
|
|
83
113
|
if (settingsSourceVersion < settingsTargetVersion) {
|
|
84
114
|
if (settingsPreparedMigrations.compile(settingsSourceVersion, settingsTargetVersion, { updateVersionInMigration: true }).statements.length > 0) migrationsToExecute.push({
|
|
85
|
-
namespace:
|
|
115
|
+
namespace: null,
|
|
116
|
+
namespaceKey: SETTINGS_TABLE_NAME,
|
|
86
117
|
fromVersion: settingsSourceVersion,
|
|
87
118
|
toVersion: settingsTargetVersion,
|
|
88
119
|
execute: () => settingsPreparedMigrations.execute(settingsSourceVersion, settingsTargetVersion, { updateVersionInMigration: true })
|
|
89
120
|
});
|
|
90
121
|
}
|
|
91
|
-
const
|
|
122
|
+
const getNamespaceKey = (db) => db.namespace ?? db.schema.name;
|
|
123
|
+
const sortedDatabases = [...databases].sort((a, b) => getNamespaceKey(a).localeCompare(getNamespaceKey(b)));
|
|
92
124
|
for (const fragnoDb of sortedDatabases) {
|
|
125
|
+
const namespaceKey = getNamespaceKey(fragnoDb);
|
|
93
126
|
const preparedMigrations = adapter.prepareMigrations(fragnoDb.schema, fragnoDb.namespace);
|
|
94
|
-
const currentVersion = await getSchemaVersionFromDatabase(internalFragment,
|
|
127
|
+
const currentVersion = await getSchemaVersionFromDatabase(internalFragment, namespaceKey);
|
|
95
128
|
const targetVersion = fragnoDb.schema.version;
|
|
96
129
|
if (currentVersion < targetVersion) {
|
|
97
130
|
if (preparedMigrations.compile(currentVersion, targetVersion, { updateVersionInMigration: true }).statements.length > 0) migrationsToExecute.push({
|
|
98
131
|
namespace: fragnoDb.namespace,
|
|
132
|
+
namespaceKey,
|
|
99
133
|
fromVersion: currentVersion,
|
|
100
134
|
toVersion: targetVersion,
|
|
101
135
|
execute: () => preparedMigrations.execute(currentVersion, targetVersion, { updateVersionInMigration: true })
|
|
102
136
|
});
|
|
103
137
|
}
|
|
104
138
|
}
|
|
139
|
+
const executedNamespaceKeys = /* @__PURE__ */ new Set();
|
|
105
140
|
for (const migration of migrationsToExecute) {
|
|
106
141
|
await migration.execute();
|
|
107
142
|
results.push({
|
|
@@ -110,19 +145,23 @@ async function executeMigrations(databases) {
|
|
|
110
145
|
fromVersion: migration.fromVersion,
|
|
111
146
|
toVersion: migration.toVersion
|
|
112
147
|
});
|
|
148
|
+
executedNamespaceKeys.add(migration.namespaceKey);
|
|
149
|
+
}
|
|
150
|
+
for (const fragnoDb of databases) {
|
|
151
|
+
const namespaceKey = getNamespaceKey(fragnoDb);
|
|
152
|
+
if (!executedNamespaceKeys.has(namespaceKey)) results.push({
|
|
153
|
+
namespace: fragnoDb.namespace,
|
|
154
|
+
didMigrate: false,
|
|
155
|
+
fromVersion: fragnoDb.schema.version,
|
|
156
|
+
toVersion: fragnoDb.schema.version
|
|
157
|
+
});
|
|
113
158
|
}
|
|
114
|
-
for (const fragnoDb of databases) if (!results.find((r) => r.namespace === fragnoDb.namespace)) results.push({
|
|
115
|
-
namespace: fragnoDb.namespace,
|
|
116
|
-
didMigrate: false,
|
|
117
|
-
fromVersion: fragnoDb.schema.version,
|
|
118
|
-
toVersion: fragnoDb.schema.version
|
|
119
|
-
});
|
|
120
159
|
return results;
|
|
121
160
|
}
|
|
122
161
|
/**
|
|
123
162
|
* Post-processes migration files to add ordering and standardize naming.
|
|
124
163
|
*
|
|
125
|
-
* Sorts files with settings namespace first, then alphabetically by namespace,
|
|
164
|
+
* Sorts files with settings namespace first, then alphabetically by namespace key,
|
|
126
165
|
* and assigns ordering numbers. Transforms filenames to format:
|
|
127
166
|
* `<date>_<n>_f<from>_t<to>_<namespace>.sql`
|
|
128
167
|
*
|
|
@@ -132,15 +171,15 @@ async function executeMigrations(databases) {
|
|
|
132
171
|
function postProcessMigrationFilenames(files) {
|
|
133
172
|
if (files.length === 0) return [];
|
|
134
173
|
const sortedFiles = [...files].sort((a, b) => {
|
|
135
|
-
if (a.
|
|
136
|
-
if (b.
|
|
137
|
-
return a.
|
|
174
|
+
if (a.isSettings) return -1;
|
|
175
|
+
if (b.isSettings) return 1;
|
|
176
|
+
return a.namespaceKey.localeCompare(b.namespaceKey);
|
|
138
177
|
});
|
|
139
178
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0].replace(/-/g, "");
|
|
140
179
|
return sortedFiles.map((file, index) => {
|
|
141
180
|
const fromVersion = file.fromVersion ?? 0;
|
|
142
181
|
const toVersion = file.toVersion ?? 0;
|
|
143
|
-
const newPath = `${date}_${(index + 1).toString().padStart(3, "0")}_f${fromVersion.toString().padStart(3, "0")}_t${toVersion.toString().padStart(3, "0")}_${file.
|
|
182
|
+
const newPath = `${date}_${(index + 1).toString().padStart(3, "0")}_f${fromVersion.toString().padStart(3, "0")}_t${toVersion.toString().padStart(3, "0")}_${file.namespaceKey.replace(/[^a-z0-9-]/gi, "_")}.sql`;
|
|
144
183
|
return {
|
|
145
184
|
schema: file.schema,
|
|
146
185
|
path: newPath,
|
|
@@ -150,5 +189,5 @@ function postProcessMigrationFilenames(files) {
|
|
|
150
189
|
}
|
|
151
190
|
|
|
152
191
|
//#endregion
|
|
153
|
-
export { executeMigrations,
|
|
192
|
+
export { executeMigrations, generateSchemaArtifacts, postProcessMigrationFilenames };
|
|
154
193
|
//# sourceMappingURL=generation-engine.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generation-engine.js","names":["generatedFiles: GenerationInternalResult[]","results: ExecuteMigrationResult[]","migrationsToExecute: Array<{\n namespace: string;\n fromVersion: number;\n toVersion: number;\n execute: () => Promise<void>;\n }>"],"sources":["../../src/migration-engine/generation-engine.ts"],"sourcesContent":["import type { FragnoDatabase } from \"../mod\";\nimport type { AnySchema } from \"../schema/create\";\nimport {\n fragnoDatabaseAdapterNameFakeSymbol,\n fragnoDatabaseAdapterVersionFakeSymbol,\n} from \"../adapters/adapters\";\nimport {\n internalFragmentDef,\n internalSchema,\n SETTINGS_NAMESPACE,\n getSchemaVersionFromDatabase,\n} from \"../fragments/internal-fragment\";\nimport { instantiate } from \"@fragno-dev/core\";\n\nexport interface GenerationEngineResult {\n schema: string;\n path: string;\n namespace: string;\n}\n\nexport interface GenerationInternalResult {\n schema: string;\n path: string;\n namespace: string;\n fromVersion: number;\n toVersion: number;\n}\n\nexport interface ExecuteMigrationResult {\n namespace: string;\n didMigrate: boolean;\n fromVersion: number;\n toVersion: number;\n}\n\nexport async function generateMigrationsOrSchema<\n // oxlint-disable-next-line no-explicit-any\n const TDatabases extends FragnoDatabase<AnySchema, any>[],\n>(\n databases: TDatabases,\n options?: {\n path?: string;\n toVersion?: number;\n fromVersion?: number;\n },\n): Promise<GenerationEngineResult[]> {\n if (databases.length === 0) {\n throw new Error(\"No databases provided for schema generation\");\n }\n\n const firstDb = databases[0];\n const adapter = firstDb.adapter;\n\n // If adapter has createSchemaGenerator, use it for combined generation (e.g., Drizzle)\n if (adapter.createSchemaGenerator) {\n if (options?.toVersion !== undefined || options?.fromVersion !== undefined) {\n console.warn(\n \"⚠️ Warning: --from and --to version options are not supported when generating schemas for multiple fragments and will be ignored.\",\n );\n }\n\n // Collect all schemas, de-duplicating by namespace.\n // The internal fragment (settings schema) is always included first since all database\n // fragments automatically link to it via withDatabase().\n const fragmentsMap = new Map<string, { schema: AnySchema; namespace: string }>();\n\n // Include internal fragment first with empty namespace (settings table has no prefix)\n fragmentsMap.set(\"\", {\n schema: internalSchema,\n namespace: \"\",\n });\n\n // Add user fragments, de-duplicating by namespace\n // Each FragnoDatabase has a unique namespace, so this prevents duplicate schema generation\n for (const db of databases) {\n if (!fragmentsMap.has(db.namespace)) {\n fragmentsMap.set(db.namespace, {\n schema: db.schema,\n namespace: db.namespace,\n });\n }\n }\n\n const allFragments = Array.from(fragmentsMap.values());\n const generator = adapter.createSchemaGenerator(allFragments, {\n path: options?.path,\n });\n\n return [\n {\n ...generator.generateSchema(),\n namespace: firstDb.namespace,\n },\n ];\n }\n\n // Otherwise, use migration engine for individual generation (e.g., Kysely, GenericSQL)\n if (!adapter.prepareMigrations) {\n throw new Error(\n \"Adapter does not support migration-based schema generation. Ensure your adapter implements prepareMigrations.\",\n );\n }\n\n if (!(await adapter.isConnectionHealthy())) {\n throw new Error(\n \"Database connection is not healthy. Please check your database connection and try again.\",\n );\n }\n\n // Use the internal fragment for settings management\n const internalFragment = instantiate(internalFragmentDef)\n .withConfig({})\n .withOptions({ databaseAdapter: adapter })\n .build();\n\n const settingsSourceVersion = await getSchemaVersionFromDatabase(\n internalFragment,\n SETTINGS_NAMESPACE,\n );\n\n const generatedFiles: GenerationInternalResult[] = [];\n\n // Use empty namespace for settings (SETTINGS_NAMESPACE is for prefixing keys, not the database namespace)\n const settingsPreparedMigrations = adapter.prepareMigrations(internalSchema, \"\");\n const settingsTargetVersion = internalSchema.version;\n\n // Generate settings table migration\n const settingsSql = settingsPreparedMigrations.getSQL(\n settingsSourceVersion,\n settingsTargetVersion,\n );\n\n if (settingsSql.trim()) {\n generatedFiles.push({\n schema: settingsSql,\n path: \"settings-migration.sql\", // Placeholder, will be renamed in post-processing\n namespace: \"\", // Empty namespace for settings table\n fromVersion: settingsSourceVersion,\n toVersion: settingsTargetVersion,\n });\n }\n\n // Generate migration for each fragment\n for (const db of databases) {\n const dbAdapter = db.adapter;\n\n // Use migration engine\n if (!dbAdapter.prepareMigrations) {\n throw new Error(\n `Adapter for ${db.namespace} does not support schema generation. ` +\n `Ensure your adapter implements either createSchemaGenerator or prepareMigrations.`,\n );\n }\n\n const preparedMigrations = dbAdapter.prepareMigrations(db.schema, db.namespace);\n const targetVersion = options?.toVersion ?? db.schema.version;\n const sourceVersion = options?.fromVersion ?? 0;\n\n // Generate migration from source to target version\n const sql = preparedMigrations.getSQL(sourceVersion, targetVersion);\n\n // If no migrations needed, skip this fragment\n if (sql.trim()) {\n generatedFiles.push({\n schema: sql,\n path: \"schema.sql\", // Placeholder, will be renamed in post-processing\n namespace: db.namespace,\n fromVersion: sourceVersion,\n toVersion: targetVersion,\n });\n }\n }\n\n // Post-process filenames with ordering\n return postProcessMigrationFilenames(generatedFiles);\n}\n\n/**\n * Execute migrations for all fragments in the correct order.\n * Migrates settings table first, then fragments alphabetically.\n *\n * @param databases - Array of FragnoDatabase instances to migrate\n * @returns Array of execution results for each migration\n */\nexport async function executeMigrations<const TDatabases extends FragnoDatabase<AnySchema>[]>(\n databases: TDatabases,\n): Promise<ExecuteMigrationResult[]> {\n if (databases.length === 0) {\n throw new Error(\"No databases provided for migration\");\n }\n\n const firstDb = databases[0];\n const adapter = firstDb.adapter;\n\n // Validate adapter supports migrations\n if (!adapter.prepareMigrations) {\n throw new Error(\n \"Adapter does not support running migrations. The adapter only supports schema generation.\\n\" +\n \"Try using 'generateMigrationsOrSchema' instead to generate schema files.\",\n );\n }\n\n // Validate all use same adapter name and version\n const firstAdapterName = adapter[fragnoDatabaseAdapterNameFakeSymbol];\n const firstAdapterVersion = adapter[fragnoDatabaseAdapterVersionFakeSymbol];\n\n for (const db of databases) {\n const dbAdapterName = db.adapter[fragnoDatabaseAdapterNameFakeSymbol];\n const dbAdapterVersion = db.adapter[fragnoDatabaseAdapterVersionFakeSymbol];\n\n if (dbAdapterName !== firstAdapterName || dbAdapterVersion !== firstAdapterVersion) {\n throw new Error(\n `All fragments must use the same database adapter. ` +\n `Found: ${firstAdapterName}@${firstAdapterVersion} and ${dbAdapterName}@${dbAdapterVersion}`,\n );\n }\n }\n\n if (!(await adapter.isConnectionHealthy())) {\n throw new Error(\n \"Database connection is not healthy. Please check your database connection and try again.\",\n );\n }\n\n const results: ExecuteMigrationResult[] = [];\n const migrationsToExecute: Array<{\n namespace: string;\n fromVersion: number;\n toVersion: number;\n execute: () => Promise<void>;\n }> = [];\n\n // 1. Prepare settings table migration\n // Use the internal fragment for settings management\n const internalFragment = instantiate(internalFragmentDef)\n .withConfig({})\n .withOptions({ databaseAdapter: adapter })\n .build();\n\n const settingsSourceVersion = await getSchemaVersionFromDatabase(\n internalFragment,\n SETTINGS_NAMESPACE,\n );\n\n // Use empty namespace for settings (SETTINGS_NAMESPACE is for prefixing keys, not the database namespace)\n const settingsPreparedMigrations = adapter.prepareMigrations(internalSchema, \"\");\n const settingsTargetVersion = internalSchema.version;\n\n if (settingsSourceVersion < settingsTargetVersion) {\n const compiledMigration = settingsPreparedMigrations.compile(\n settingsSourceVersion,\n settingsTargetVersion,\n { updateVersionInMigration: true },\n );\n\n if (compiledMigration.statements.length > 0) {\n migrationsToExecute.push({\n namespace: \"\", // Empty namespace for settings table\n fromVersion: settingsSourceVersion,\n toVersion: settingsTargetVersion,\n execute: () =>\n settingsPreparedMigrations.execute(settingsSourceVersion, settingsTargetVersion, {\n updateVersionInMigration: true,\n }),\n });\n }\n }\n\n // 2. Prepare fragment migrations (sorted alphabetically)\n const sortedDatabases = [...databases].sort((a, b) => a.namespace.localeCompare(b.namespace));\n\n for (const fragnoDb of sortedDatabases) {\n const preparedMigrations = adapter.prepareMigrations(fragnoDb.schema, fragnoDb.namespace);\n const currentVersion = await getSchemaVersionFromDatabase(internalFragment, fragnoDb.namespace);\n const targetVersion = fragnoDb.schema.version;\n\n if (currentVersion < targetVersion) {\n const compiledMigration = preparedMigrations.compile(currentVersion, targetVersion, {\n updateVersionInMigration: true,\n });\n\n if (compiledMigration.statements.length > 0) {\n migrationsToExecute.push({\n namespace: fragnoDb.namespace,\n fromVersion: currentVersion,\n toVersion: targetVersion,\n execute: () =>\n preparedMigrations.execute(currentVersion, targetVersion, {\n updateVersionInMigration: true,\n }),\n });\n }\n }\n }\n\n // 3. Execute all migrations in order\n for (const migration of migrationsToExecute) {\n await migration.execute();\n results.push({\n namespace: migration.namespace,\n didMigrate: true,\n fromVersion: migration.fromVersion,\n toVersion: migration.toVersion,\n });\n }\n\n // 4. Add skipped migrations (already up-to-date)\n for (const fragnoDb of databases) {\n if (!results.find((r) => r.namespace === fragnoDb.namespace)) {\n results.push({\n namespace: fragnoDb.namespace,\n didMigrate: false,\n fromVersion: fragnoDb.schema.version,\n toVersion: fragnoDb.schema.version,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Post-processes migration files to add ordering and standardize naming.\n *\n * Sorts files with settings namespace first, then alphabetically by namespace,\n * and assigns ordering numbers. Transforms filenames to format:\n * `<date>_<n>_f<from>_t<to>_<namespace>.sql`\n *\n * @param files - Array of generated migration files with version information\n * @returns Array of files with standardized paths and ordering\n */\nexport function postProcessMigrationFilenames(\n files: GenerationInternalResult[],\n): GenerationEngineResult[] {\n if (files.length === 0) {\n return [];\n }\n\n // Sort files: settings namespace first (empty string), then alphabetically by namespace\n const sortedFiles = [...files].sort((a, b) => {\n // Settings table has empty namespace - sort it first\n if (a.namespace === \"\") {\n return -1;\n }\n if (b.namespace === \"\") {\n return 1;\n }\n return a.namespace.localeCompare(b.namespace);\n });\n\n // Generate date prefix for filenames\n const date = new Date().toISOString().split(\"T\")[0].replace(/-/g, \"\");\n\n // Rename files with ordering\n return sortedFiles.map((file, index) => {\n const fromVersion = file.fromVersion ?? 0;\n const toVersion = file.toVersion ?? 0;\n\n // Create new filename with ordering\n const orderNum = (index + 1).toString().padStart(3, \"0\");\n const fromPadded = fromVersion.toString().padStart(3, \"0\");\n const toPadded = toVersion.toString().padStart(3, \"0\");\n\n // For settings table (empty namespace), use \"fragno_db_settings\" in the filename\n // For other tables, use their namespace\n const safeName =\n file.namespace === \"\" ? \"fragno_db_settings\" : file.namespace.replace(/[^a-z0-9-]/gi, \"_\");\n const newPath = `${date}_${orderNum}_f${fromPadded}_t${toPadded}_${safeName}.sql`;\n\n return {\n schema: file.schema,\n path: newPath,\n namespace: file.namespace,\n };\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generation-engine.js","names":["generatedFiles: GenerationInternalResult[]","results: ExecuteMigrationResult[]","migrationsToExecute: Array<{\n namespace: string | null;\n namespaceKey: string;\n fromVersion: number;\n toVersion: number;\n execute: () => Promise<void>;\n }>"],"sources":["../../src/migration-engine/generation-engine.ts"],"sourcesContent":["import type { FragnoDatabase } from \"../mod\";\nimport type { AnySchema } from \"../schema/create\";\nimport {\n fragnoDatabaseAdapterNameFakeSymbol,\n fragnoDatabaseAdapterVersionFakeSymbol,\n} from \"../adapters/adapters\";\nimport { generateDrizzleSchema } from \"../schema-output/drizzle\";\nimport { generatePrismaSchema } from \"../schema-output/prisma\";\nimport { internalFragmentDef, getSchemaVersionFromDatabase } from \"../fragments/internal-fragment\";\nimport { internalSchema, SETTINGS_TABLE_NAME } from \"../fragments/internal-fragment.schema\";\nimport { instantiate } from \"@fragno-dev/core\";\nimport { supportedDatabases, type SupportedDatabase } from \"../adapters/generic-sql/driver-config\";\n\nexport interface GenerationEngineResult {\n schema: string;\n path: string;\n namespace: string | null;\n}\n\nexport type SchemaOutputFormat = \"sql\" | \"drizzle\" | \"prisma\";\n\nexport interface GenerateSchemaOptions {\n format?: SchemaOutputFormat;\n path?: string;\n toVersion?: number;\n fromVersion?: number;\n}\n\nexport interface GenerationInternalResult {\n schema: string;\n path: string;\n namespace: string | null;\n namespaceKey: string;\n schemaName: string;\n isSettings: boolean;\n fromVersion: number;\n toVersion: number;\n}\n\nexport interface ExecuteMigrationResult {\n namespace: string | null;\n didMigrate: boolean;\n fromVersion: number;\n toVersion: number;\n}\n\nconst DEFAULT_DRIZZLE_PATH = \"fragno-schema.ts\";\nconst DEFAULT_PRISMA_PATH = \"fragno.prisma\";\n\nconst isSupportedDatabase = (value: string): value is SupportedDatabase =>\n supportedDatabases.includes(value as SupportedDatabase);\n\nexport async function generateSchemaArtifacts<\n // oxlint-disable-next-line no-explicit-any\n const TDatabases extends FragnoDatabase<AnySchema, any>[],\n>(databases: TDatabases, options?: GenerateSchemaOptions): Promise<GenerationEngineResult[]> {\n if (databases.length === 0) {\n throw new Error(\"No databases provided for schema generation\");\n }\n\n const firstDb = databases[0];\n const adapter = firstDb.adapter;\n const format = options?.format ?? \"sql\";\n\n if (format !== \"sql\") {\n if (options?.toVersion !== undefined || options?.fromVersion !== undefined) {\n throw new Error(\"--from and --to are only supported when generating SQL migrations.\");\n }\n\n const databaseType = adapter.adapterMetadata?.databaseType;\n if (!databaseType || !isSupportedDatabase(databaseType)) {\n throw new Error(\n \"Adapter does not expose databaseType metadata required for schema output generation.\",\n );\n }\n\n // Collect all schemas, de-duplicating by namespace.\n // The internal fragment (settings schema) is always included first since all database\n // fragments automatically link to it via withDatabase().\n const fragmentsMap = new Map<string, { schema: AnySchema; namespace: string | null }>();\n\n // Include internal fragment first with empty namespace (settings table has no prefix)\n fragmentsMap.set(internalSchema.name, {\n schema: internalSchema,\n namespace: null,\n });\n\n // Add user fragments, de-duplicating by namespace\n // Each FragnoDatabase has a unique namespace, so this prevents duplicate schema generation\n for (const db of databases) {\n const namespaceKey = db.namespace ?? db.schema.name;\n if (!fragmentsMap.has(namespaceKey)) {\n fragmentsMap.set(namespaceKey, {\n schema: db.schema,\n namespace: db.namespace,\n });\n }\n }\n\n const allFragments = Array.from(fragmentsMap.values());\n const defaultPath = format === \"drizzle\" ? DEFAULT_DRIZZLE_PATH : DEFAULT_PRISMA_PATH;\n const schema =\n format === \"drizzle\"\n ? generateDrizzleSchema(allFragments, databaseType, {\n namingStrategy: adapter.namingStrategy,\n })\n : generatePrismaSchema(allFragments, databaseType, {\n sqliteStorageMode: adapter.adapterMetadata?.sqliteStorageMode,\n namingStrategy: adapter.namingStrategy,\n });\n\n return [\n {\n schema,\n path: options?.path ?? defaultPath,\n namespace: firstDb.namespace,\n },\n ];\n }\n\n // Otherwise, use migration engine for SQL migration generation.\n if (!adapter.prepareMigrations) {\n throw new Error(\n \"Adapter does not support migration generation. Ensure your adapter implements prepareMigrations.\",\n );\n }\n\n if (!(await adapter.isConnectionHealthy())) {\n throw new Error(\n \"Database connection is not healthy. Please check your database connection and try again.\",\n );\n }\n\n // Use the internal fragment for settings management\n const internalFragment = instantiate(internalFragmentDef)\n .withConfig({})\n .withOptions({ databaseAdapter: adapter, databaseNamespace: null })\n .build();\n\n const settingsSourceVersion = await getSchemaVersionFromDatabase(internalFragment, \"\");\n\n const generatedFiles: GenerationInternalResult[] = [];\n\n // Internal fragment uses empty-string namespace: no table suffix, version key is \".schema_version\"\n const settingsPreparedMigrations = adapter.prepareMigrations(internalSchema, \"\");\n const settingsTargetVersion = internalSchema.version;\n\n // Generate settings table migration\n const settingsSql = settingsPreparedMigrations.getSQL(\n settingsSourceVersion,\n settingsTargetVersion,\n );\n\n if (settingsSql.trim()) {\n generatedFiles.push({\n schema: settingsSql,\n path: \"settings-migration.sql\", // Placeholder, will be renamed in post-processing\n namespace: null,\n namespaceKey: SETTINGS_TABLE_NAME,\n schemaName: internalSchema.name,\n isSettings: true,\n fromVersion: settingsSourceVersion,\n toVersion: settingsTargetVersion,\n });\n }\n\n // Generate migration for each fragment\n for (const db of databases) {\n const dbAdapter = db.adapter;\n\n // Use migration engine\n if (!dbAdapter.prepareMigrations) {\n throw new Error(\n `Adapter for ${db.namespace ?? db.schema.name} does not support migration generation. ` +\n `Ensure your adapter implements prepareMigrations.`,\n );\n }\n\n const preparedMigrations = dbAdapter.prepareMigrations(db.schema, db.namespace);\n const targetVersion = options?.toVersion ?? db.schema.version;\n const sourceVersion = options?.fromVersion ?? 0;\n\n // Generate migration from source to target version\n const sql = preparedMigrations.getSQL(sourceVersion, targetVersion);\n\n // If no migrations needed, skip this fragment\n if (sql.trim()) {\n generatedFiles.push({\n schema: sql,\n path: \"schema.sql\", // Placeholder, will be renamed in post-processing\n namespace: db.namespace,\n namespaceKey: db.namespace ?? db.schema.name,\n schemaName: db.schema.name,\n isSettings: false,\n fromVersion: sourceVersion,\n toVersion: targetVersion,\n });\n }\n }\n\n // Post-process filenames with ordering\n return postProcessMigrationFilenames(generatedFiles);\n}\n\n/**\n * Execute migrations for all fragments in the correct order.\n * Migrates settings table first, then fragments alphabetically.\n *\n * @param databases - Array of FragnoDatabase instances to migrate\n * @returns Array of execution results for each migration\n */\nexport async function executeMigrations<const TDatabases extends FragnoDatabase<AnySchema>[]>(\n databases: TDatabases,\n): Promise<ExecuteMigrationResult[]> {\n if (databases.length === 0) {\n throw new Error(\"No databases provided for migration\");\n }\n\n const firstDb = databases[0];\n const adapter = firstDb.adapter;\n\n // Validate adapter supports migrations\n if (!adapter.prepareMigrations) {\n throw new Error(\n \"Adapter does not support running migrations. The adapter only supports schema generation.\\n\" +\n \"Try using 'generateSchemaArtifacts' instead to generate schema files.\",\n );\n }\n\n // Validate all use same adapter name and version\n const firstAdapterName = adapter[fragnoDatabaseAdapterNameFakeSymbol];\n const firstAdapterVersion = adapter[fragnoDatabaseAdapterVersionFakeSymbol];\n\n for (const db of databases) {\n const dbAdapterName = db.adapter[fragnoDatabaseAdapterNameFakeSymbol];\n const dbAdapterVersion = db.adapter[fragnoDatabaseAdapterVersionFakeSymbol];\n\n if (dbAdapterName !== firstAdapterName || dbAdapterVersion !== firstAdapterVersion) {\n throw new Error(\n `All fragments must use the same database adapter. ` +\n `Found: ${firstAdapterName}@${firstAdapterVersion} and ${dbAdapterName}@${dbAdapterVersion}`,\n );\n }\n }\n\n if (!(await adapter.isConnectionHealthy())) {\n throw new Error(\n \"Database connection is not healthy. Please check your database connection and try again.\",\n );\n }\n\n const results: ExecuteMigrationResult[] = [];\n const migrationsToExecute: Array<{\n namespace: string | null;\n namespaceKey: string;\n fromVersion: number;\n toVersion: number;\n execute: () => Promise<void>;\n }> = [];\n\n // 1. Prepare settings table migration\n // Use the internal fragment for settings management\n const internalFragment = instantiate(internalFragmentDef)\n .withConfig({})\n .withOptions({ databaseAdapter: adapter, databaseNamespace: null })\n .build();\n\n const settingsSourceVersion = await getSchemaVersionFromDatabase(internalFragment, \"\");\n\n // Internal fragment uses empty-string namespace: no table suffix, version key is \".schema_version\"\n const settingsPreparedMigrations = adapter.prepareMigrations(internalSchema, \"\");\n const settingsTargetVersion = internalSchema.version;\n\n if (settingsSourceVersion < settingsTargetVersion) {\n const compiledMigration = settingsPreparedMigrations.compile(\n settingsSourceVersion,\n settingsTargetVersion,\n { updateVersionInMigration: true },\n );\n\n if (compiledMigration.statements.length > 0) {\n migrationsToExecute.push({\n namespace: null,\n namespaceKey: SETTINGS_TABLE_NAME,\n fromVersion: settingsSourceVersion,\n toVersion: settingsTargetVersion,\n execute: () =>\n settingsPreparedMigrations.execute(settingsSourceVersion, settingsTargetVersion, {\n updateVersionInMigration: true,\n }),\n });\n }\n }\n\n // 2. Prepare fragment migrations (sorted alphabetically)\n const getNamespaceKey = (db: FragnoDatabase<AnySchema>) => db.namespace ?? db.schema.name;\n const sortedDatabases = [...databases].sort((a, b) =>\n getNamespaceKey(a).localeCompare(getNamespaceKey(b)),\n );\n\n for (const fragnoDb of sortedDatabases) {\n const namespaceKey = getNamespaceKey(fragnoDb);\n const preparedMigrations = adapter.prepareMigrations(fragnoDb.schema, fragnoDb.namespace);\n const currentVersion = await getSchemaVersionFromDatabase(internalFragment, namespaceKey);\n const targetVersion = fragnoDb.schema.version;\n\n if (currentVersion < targetVersion) {\n const compiledMigration = preparedMigrations.compile(currentVersion, targetVersion, {\n updateVersionInMigration: true,\n });\n\n if (compiledMigration.statements.length > 0) {\n migrationsToExecute.push({\n namespace: fragnoDb.namespace,\n namespaceKey,\n fromVersion: currentVersion,\n toVersion: targetVersion,\n execute: () =>\n preparedMigrations.execute(currentVersion, targetVersion, {\n updateVersionInMigration: true,\n }),\n });\n }\n }\n }\n\n // 3. Execute all migrations in order\n const executedNamespaceKeys = new Set<string>();\n for (const migration of migrationsToExecute) {\n await migration.execute();\n results.push({\n namespace: migration.namespace,\n didMigrate: true,\n fromVersion: migration.fromVersion,\n toVersion: migration.toVersion,\n });\n executedNamespaceKeys.add(migration.namespaceKey);\n }\n\n // 4. Add skipped migrations (already up-to-date)\n for (const fragnoDb of databases) {\n const namespaceKey = getNamespaceKey(fragnoDb);\n if (!executedNamespaceKeys.has(namespaceKey)) {\n results.push({\n namespace: fragnoDb.namespace,\n didMigrate: false,\n fromVersion: fragnoDb.schema.version,\n toVersion: fragnoDb.schema.version,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Post-processes migration files to add ordering and standardize naming.\n *\n * Sorts files with settings namespace first, then alphabetically by namespace key,\n * and assigns ordering numbers. Transforms filenames to format:\n * `<date>_<n>_f<from>_t<to>_<namespace>.sql`\n *\n * @param files - Array of generated migration files with version information\n * @returns Array of files with standardized paths and ordering\n */\nexport function postProcessMigrationFilenames(\n files: GenerationInternalResult[],\n): GenerationEngineResult[] {\n if (files.length === 0) {\n return [];\n }\n\n // Sort files: settings first, then alphabetically by namespace key\n const sortedFiles = [...files].sort((a, b) => {\n if (a.isSettings) {\n return -1;\n }\n if (b.isSettings) {\n return 1;\n }\n return a.namespaceKey.localeCompare(b.namespaceKey);\n });\n\n // Generate date prefix for filenames\n const date = new Date().toISOString().split(\"T\")[0].replace(/-/g, \"\");\n\n // Rename files with ordering\n return sortedFiles.map((file, index) => {\n const fromVersion = file.fromVersion ?? 0;\n const toVersion = file.toVersion ?? 0;\n\n // Create new filename with ordering\n const orderNum = (index + 1).toString().padStart(3, \"0\");\n const fromPadded = fromVersion.toString().padStart(3, \"0\");\n const toPadded = toVersion.toString().padStart(3, \"0\");\n\n const safeName = file.namespaceKey.replace(/[^a-z0-9-]/gi, \"_\");\n const newPath = `${date}_${orderNum}_f${fromPadded}_t${toPadded}_${safeName}.sql`;\n\n return {\n schema: file.schema,\n path: newPath,\n namespace: file.namespace,\n };\n });\n}\n"],"mappings":"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"}
|