@warlock.js/cascade 5.19.1 → 5.21.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/CHANGELOG.md +36 -1
- package/cjs/index.cjs +2356 -1039
- package/cjs/index.cjs.map +1 -1
- package/esm/context/database-transaction-context.d.mts +10 -0
- package/esm/context/database-transaction-context.d.mts.map +1 -1
- package/esm/context/database-transaction-context.mjs +17 -0
- package/esm/context/database-transaction-context.mjs.map +1 -1
- package/esm/contracts/query-builder.contract.d.mts +30 -3
- package/esm/contracts/query-builder.contract.d.mts.map +1 -1
- package/esm/data-source/data-source-registry.d.mts +33 -1
- package/esm/data-source/data-source-registry.d.mts.map +1 -1
- package/esm/data-source/data-source-registry.mjs +75 -5
- package/esm/data-source/data-source-registry.mjs.map +1 -1
- package/esm/database-dirty-tracker.d.mts +5 -0
- package/esm/database-dirty-tracker.d.mts.map +1 -1
- package/esm/database-dirty-tracker.mjs +16 -0
- package/esm/database-dirty-tracker.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.d.mts +12 -0
- package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.mjs +58 -19
- package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.d.mts +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.mjs +15 -18
- package/esm/drivers/mongodb/mongodb-migration-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts +13 -3
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.mjs +72 -19
- package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts +6 -0
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.mjs +128 -30
- package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/index.d.mts +2 -3
- package/esm/drivers/postgres/index.mjs +1 -2
- package/esm/drivers/postgres/postgres-blueprint.mjs +4 -4
- package/esm/drivers/postgres/postgres-blueprint.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.d.mts +5 -1
- package/esm/drivers/postgres/postgres-dialect.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.mjs +38 -8
- package/esm/drivers/postgres/postgres-dialect.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-driver.d.mts +52 -29
- package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-driver.mjs +196 -58
- package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-migration-driver.d.mts +2 -1
- package/esm/drivers/postgres/postgres-migration-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-migration-driver.mjs +8 -6
- package/esm/drivers/postgres/postgres-migration-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.d.mts +18 -7
- package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.mjs +146 -31
- package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts +23 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.mjs +160 -43
- package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-sql-serializer.mjs +3 -2
- package/esm/drivers/postgres/postgres-sql-serializer.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-type-converters.mjs +57 -0
- package/esm/drivers/postgres/postgres-type-converters.mjs.map +1 -0
- package/esm/events/model-events.d.mts +1 -1
- package/esm/events/model-events.d.mts.map +1 -1
- package/esm/events/model-events.mjs.map +1 -1
- package/esm/index.d.mts +6 -6
- package/esm/index.mjs +5 -5
- package/esm/migration/column-builder.d.mts.map +1 -1
- package/esm/migration/column-builder.mjs +2 -2
- package/esm/migration/column-builder.mjs.map +1 -1
- package/esm/migration/index.d.mts +2 -2
- package/esm/migration/index.mjs +1 -1
- package/esm/migration/migration-order.mjs +5 -2
- package/esm/migration/migration-order.mjs.map +1 -1
- package/esm/migration/migration-runner.d.mts +50 -5
- package/esm/migration/migration-runner.d.mts.map +1 -1
- package/esm/migration/migration-runner.mjs +370 -147
- package/esm/migration/migration-runner.mjs.map +1 -1
- package/esm/migration/migration.d.mts +36 -3
- package/esm/migration/migration.d.mts.map +1 -1
- package/esm/migration/migration.mjs +62 -5
- package/esm/migration/migration.mjs.map +1 -1
- package/esm/model/methods/accessor-methods.mjs +1 -3
- package/esm/model/methods/accessor-methods.mjs.map +1 -1
- package/esm/model/methods/delete-methods.mjs +6 -2
- package/esm/model/methods/delete-methods.mjs.map +1 -1
- package/esm/model/methods/hydration-methods.mjs +16 -2
- package/esm/model/methods/hydration-methods.mjs.map +1 -1
- package/esm/model/methods/instance-event-methods.mjs +2 -1
- package/esm/model/methods/instance-event-methods.mjs.map +1 -1
- package/esm/model/methods/meta-methods.mjs +53 -16
- package/esm/model/methods/meta-methods.mjs.map +1 -1
- package/esm/model/methods/query-methods.mjs +86 -15
- package/esm/model/methods/query-methods.mjs.map +1 -1
- package/esm/model/methods/static-event-methods.mjs +16 -1
- package/esm/model/methods/static-event-methods.mjs.map +1 -1
- package/esm/model/methods/write-methods.d.mts.map +1 -1
- package/esm/model/methods/write-methods.mjs +50 -50
- package/esm/model/methods/write-methods.mjs.map +1 -1
- package/esm/model/model.d.mts +16 -1
- package/esm/model/model.d.mts.map +1 -1
- package/esm/model/model.mjs +39 -5
- package/esm/model/model.mjs.map +1 -1
- package/esm/model/register-model.d.mts +6 -1
- package/esm/model/register-model.d.mts.map +1 -1
- package/esm/model/register-model.mjs +16 -1
- package/esm/model/register-model.mjs.map +1 -1
- package/esm/query-builder/query-builder.d.mts +21 -2
- package/esm/query-builder/query-builder.d.mts.map +1 -1
- package/esm/query-builder/query-builder.mjs +97 -20
- package/esm/query-builder/query-builder.mjs.map +1 -1
- package/esm/relations/key-conventions.mjs +13 -3
- package/esm/relations/key-conventions.mjs.map +1 -1
- package/esm/relations/pivot-operations.mjs +1 -1
- package/esm/relations/relation-loader.d.mts +15 -5
- package/esm/relations/relation-loader.d.mts.map +1 -1
- package/esm/relations/relation-loader.mjs +51 -16
- package/esm/relations/relation-loader.mjs.map +1 -1
- package/esm/remover/database-remover.d.mts.map +1 -1
- package/esm/remover/database-remover.mjs +21 -7
- package/esm/remover/database-remover.mjs.map +1 -1
- package/esm/restorer/database-restorer.d.mts +12 -2
- package/esm/restorer/database-restorer.d.mts.map +1 -1
- package/esm/restorer/database-restorer.mjs +43 -13
- package/esm/restorer/database-restorer.mjs.map +1 -1
- package/esm/sync/model-events.mjs +63 -3
- package/esm/sync/model-events.mjs.map +1 -1
- package/esm/sync/model-sync-operation.d.mts.map +1 -1
- package/esm/sync/model-sync-operation.mjs +3 -2
- package/esm/sync/model-sync-operation.mjs.map +1 -1
- package/esm/sync/sync-manager.d.mts +3 -42
- package/esm/sync/sync-manager.d.mts.map +1 -1
- package/esm/sync/sync-manager.mjs +37 -126
- package/esm/sync/sync-manager.mjs.map +1 -1
- package/esm/transactions/after-commit.d.mts +24 -0
- package/esm/transactions/after-commit.d.mts.map +1 -0
- package/esm/transactions/after-commit.mjs +47 -0
- package/esm/transactions/after-commit.mjs.map +1 -0
- package/esm/utils/connect-to-database.d.mts.map +1 -1
- package/esm/utils/connect-to-database.mjs +4 -4
- package/esm/utils/connect-to-database.mjs.map +1 -1
- package/esm/utils/database-writer.utils.d.mts +1 -1
- package/esm/utils/database-writer.utils.d.mts.map +1 -1
- package/esm/utils/database-writer.utils.mjs +1 -0
- package/esm/utils/database-writer.utils.mjs.map +1 -1
- package/esm/utils/define-model.d.mts.map +1 -1
- package/esm/utils/define-model.mjs +2 -10
- package/esm/utils/define-model.mjs.map +1 -1
- package/esm/utils/is-valid-date-value.mjs +3 -2
- package/esm/utils/is-valid-date-value.mjs.map +1 -1
- package/esm/utils/once-connected.d.mts.map +1 -1
- package/esm/utils/once-connected.mjs +12 -2
- package/esm/utils/once-connected.mjs.map +1 -1
- package/esm/validation/mutators/embed-mutator.mjs +19 -11
- package/esm/validation/mutators/embed-mutator.mjs.map +1 -1
- package/esm/validation/plugins/embed-validator-plugin.d.mts +2 -2
- package/esm/validation/plugins/embed-validator-plugin.d.mts.map +1 -1
- package/esm/validation/plugins/embed-validator-plugin.mjs +8 -2
- package/esm/validation/plugins/embed-validator-plugin.mjs.map +1 -1
- package/esm/validation/rules/database-model-rule.mjs +6 -5
- package/esm/validation/rules/database-model-rule.mjs.map +1 -1
- package/esm/validation/rules/exists-rule.mjs +6 -4
- package/esm/validation/rules/exists-rule.mjs.map +1 -1
- package/esm/validation/rules/unique-rule.mjs +5 -2
- package/esm/validation/rules/unique-rule.mjs.map +1 -1
- package/esm/validation/validators/embed-validator.d.mts.map +1 -1
- package/esm/validation/validators/embed-validator.mjs +1 -1
- package/esm/validation/validators/embed-validator.mjs.map +1 -1
- package/esm/writer/database-writer.d.mts +27 -0
- package/esm/writer/database-writer.d.mts.map +1 -1
- package/esm/writer/database-writer.mjs +84 -17
- package/esm/writer/database-writer.mjs.map +1 -1
- package/llms-full.txt +2783 -2766
- package/llms.txt +15 -15
- package/package.json +6 -6
- package/skills/aggregate-data/SKILL.md +165 -165
- package/skills/alter-migration/SKILL.md +531 -531
- package/skills/configure-delete-strategy/SKILL.md +130 -130
- package/skills/define-model/SKILL.md +205 -205
- package/skills/define-relations/SKILL.md +171 -171
- package/skills/index.md +23 -0
- package/skills/manage-data-sources/SKILL.md +140 -140
- package/skills/manage-transactions/SKILL.md +208 -191
- package/skills/paginate-results/SKILL.md +122 -122
- package/skills/perform-atomic-ops/SKILL.md +156 -156
- package/skills/query-data/SKILL.md +213 -213
- package/skills/run-cascade-cli/SKILL.md +125 -125
- package/skills/search-by-vector/SKILL.md +127 -127
- package/skills/subscribe-to-model-events/SKILL.md +148 -148
- package/skills/track-changes/SKILL.md +127 -127
- package/skills/write-migration/SKILL.md +206 -206
- package/esm/drivers/postgres/postgres-sync-adapter.d.mts +0 -79
- package/esm/drivers/postgres/postgres-sync-adapter.d.mts.map +0 -1
- package/esm/drivers/postgres/postgres-sync-adapter.mjs +0 -162
- package/esm/drivers/postgres/postgres-sync-adapter.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"once-connected.mjs","names":[],"sources":["../../../../../../../cascade/src/utils/once-connected.ts"],"sourcesContent":["import type { DataSource } from \"../data-source/data-source\";\nimport { dataSourceRegistry } from \"../data-source/data-source-registry\";\n\n/**\n * Execute a callback once the driver is connected.\n *\n * If the driver is already connected, the callback is executed immediately.\n * Otherwise, it waits for the \"connected\" event.\n *\n * **Use Case:**\n * Useful for ensuring database-dependent operations only run after connection\n * is established, especially in small to medium projects with a single data source.\n *\n * @param dataSourceOrNameOrCallback - Data source instance, name, or callback function\n * @param callback - Function to execute once connected (optional if first param is callback)\n *\n * @example\n * ```typescript\n * // With default data source (just callback)\n * await connectToDatabase({ database: \"myapp\" });\n *\n * onceConnected((ds) => {\n * console.log(\"Database is ready!\");\n * console.log(\"Connected to:\", ds.name);\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With data source name\n * onceConnected(\"primary\", (ds) => {\n * console.log(\"Primary database is ready!\");\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With data source instance\n * const dataSource = await connectToDatabase({ database: \"myapp\" });\n *\n * onceConnected(dataSource, (ds) => {\n * console.log(\"Database is ready!\");\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With driver instance\n * const driver = new MongoDbDriver(config);\n * const dataSource = new DataSource({ name: \"primary\", driver });\n *\n * onceConnected(dataSource, () => {\n * // Start application server\n * app.listen(3000);\n * });\n *\n * // Connect after setting up the callback\n * await driver.connect();\n * ```\n *\n * @example\n * ```typescript\n * // Chaining operations\n * onceConnected(async (ds) => {\n * // Seed database\n * await seedDatabase();\n *\n * // Run migrations\n * await runMigrations();\n *\n * console.log(\"Database ready!\");\n * });\n * ```\n */\nexport function onceConnected(\n dataSourceOrNameOrCallback: DataSource | string | ((dataSource: DataSource) => void),\n callback?: (dataSource: DataSource) => void,\n): void {\n // Determine if first parameter is a callback\n let targetDataSource: DataSource | string;\n let actualCallback: (dataSource: DataSource) => void;\n\n if (typeof dataSourceOrNameOrCallback === \"function\") {\n // First param is callback, use default data source\n actualCallback = dataSourceOrNameOrCallback;\n targetDataSource = \"default\";\n } else {\n // First param is data source name or instance\n if (!callback) {\n throw new Error(\"Callback is required when providing a data source name or instance.\");\n }\n actualCallback = callback;\n targetDataSource = dataSourceOrNameOrCallback;\n }\n\n // Try to get the data source if it's a string\n let resolvedDataSource: DataSource | undefined;\n if (typeof targetDataSource === \"string\") {\n try {\n resolvedDataSource =\n targetDataSource === \"default\"\n ? dataSourceRegistry.get()\n : dataSourceRegistry.get(targetDataSource);\n } catch {\n // Data source not registered yet, will wait for event\n }\n } else {\n resolvedDataSource = targetDataSource;\n }\n\n // If data source is resolved and already connected, fire immediately\n if (resolvedDataSource && resolvedDataSource.driver.isConnected) {\n actualCallback(resolvedDataSource);\n return;\n }\n\n // Otherwise, wait for the connected event\n const listener = (ds: DataSource) => {\n const matches =\n typeof targetDataSource === \"string\"\n ? targetDataSource === \"default\"\n ? ds
|
|
1
|
+
{"version":3,"file":"once-connected.mjs","names":[],"sources":["../../../../../../../cascade/src/utils/once-connected.ts"],"sourcesContent":["import type { DataSource } from \"../data-source/data-source\";\nimport { dataSourceRegistry } from \"../data-source/data-source-registry\";\n\n/**\n * Whether the given source is the registry's actual default (not just the `isDefault` option flag).\n */\nfunction isRegistryDefault(ds: DataSource): boolean {\n try {\n return dataSourceRegistry.get() === ds;\n } catch {\n return false;\n }\n}\n\n/**\n * Execute a callback once the driver is connected.\n *\n * If the driver is already connected, the callback is executed immediately.\n * Otherwise, it waits for the \"connected\" event.\n *\n * **Use Case:**\n * Useful for ensuring database-dependent operations only run after connection\n * is established, especially in small to medium projects with a single data source.\n *\n * @param dataSourceOrNameOrCallback - Data source instance, name, or callback function\n * @param callback - Function to execute once connected (optional if first param is callback)\n *\n * @example\n * ```typescript\n * // With default data source (just callback)\n * await connectToDatabase({ database: \"myapp\" });\n *\n * onceConnected((ds) => {\n * console.log(\"Database is ready!\");\n * console.log(\"Connected to:\", ds.name);\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With data source name\n * onceConnected(\"primary\", (ds) => {\n * console.log(\"Primary database is ready!\");\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With data source instance\n * const dataSource = await connectToDatabase({ database: \"myapp\" });\n *\n * onceConnected(dataSource, (ds) => {\n * console.log(\"Database is ready!\");\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With driver instance\n * const driver = new MongoDbDriver(config);\n * const dataSource = new DataSource({ name: \"primary\", driver });\n *\n * onceConnected(dataSource, () => {\n * // Start application server\n * app.listen(3000);\n * });\n *\n * // Connect after setting up the callback\n * await driver.connect();\n * ```\n *\n * @example\n * ```typescript\n * // Chaining operations\n * onceConnected(async (ds) => {\n * // Seed database\n * await seedDatabase();\n *\n * // Run migrations\n * await runMigrations();\n *\n * console.log(\"Database ready!\");\n * });\n * ```\n */\nexport function onceConnected(\n dataSourceOrNameOrCallback: DataSource | string | ((dataSource: DataSource) => void),\n callback?: (dataSource: DataSource) => void,\n): void {\n // Determine if first parameter is a callback\n let targetDataSource: DataSource | string;\n let actualCallback: (dataSource: DataSource) => void;\n\n if (typeof dataSourceOrNameOrCallback === \"function\") {\n // First param is callback, use default data source\n actualCallback = dataSourceOrNameOrCallback;\n targetDataSource = \"default\";\n } else {\n // First param is data source name or instance\n if (!callback) {\n throw new Error(\"Callback is required when providing a data source name or instance.\");\n }\n actualCallback = callback;\n targetDataSource = dataSourceOrNameOrCallback;\n }\n\n // Try to get the data source if it's a string\n let resolvedDataSource: DataSource | undefined;\n if (typeof targetDataSource === \"string\") {\n try {\n resolvedDataSource =\n targetDataSource === \"default\"\n ? dataSourceRegistry.get()\n : dataSourceRegistry.get(targetDataSource);\n } catch {\n // Data source not registered yet, will wait for event\n }\n } else {\n resolvedDataSource = targetDataSource;\n }\n\n // If data source is resolved and already connected, fire immediately\n if (resolvedDataSource && resolvedDataSource.driver.isConnected) {\n actualCallback(resolvedDataSource);\n return;\n }\n\n // Otherwise, wait for the connected event\n const listener = (ds: DataSource) => {\n const matches =\n typeof targetDataSource === \"string\"\n ? targetDataSource === \"default\"\n ? isRegistryDefault(ds)\n : ds.name === targetDataSource\n : ds === targetDataSource;\n\n if (matches) {\n // Remove the listener once our condition is met\n dataSourceRegistry.off(\"connected\", listener);\n actualCallback(ds);\n }\n // If it doesn't match, we do nothing and just wait for the next \"connected\" event targeting our DB\n };\n\n dataSourceRegistry.on(\"connected\", listener);\n}\n\n/**\n * Execute a callback once the driver is disconnected.\n *\n * If the driver is already disconnected, the callback is executed immediately.\n * Otherwise, it waits for the \"disconnected\" event.\n *\n * **Use Case:**\n * Useful for cleanup operations, graceful shutdown, or reconnection logic.\n *\n * @param dataSourceOrNameOrCallback - Data source instance, name, or callback function\n * @param callback - Function to execute once disconnected (optional if first param is callback)\n *\n * @example\n * ```typescript\n * // With default data source (just callback)\n * await connectToDatabase({ database: \"myapp\" });\n *\n * onceDisconnected((ds) => {\n * console.log(\"Database disconnected!\");\n * console.log(\"Attempting reconnection...\");\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With data source name\n * onceDisconnected(\"primary\", (ds) => {\n * console.log(\"Primary database disconnected!\");\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With data source instance\n * const dataSource = await connectToDatabase({ database: \"myapp\" });\n *\n * onceDisconnected(dataSource, (ds) => {\n * console.log(\"Database disconnected!\");\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Graceful shutdown with default data source\n * process.on(\"SIGTERM\", async () => {\n * console.log(\"Shutting down...\");\n *\n * onceDisconnected(() => {\n * console.log(\"Database closed, exiting process\");\n * process.exit(0);\n * });\n *\n * const dataSource = DataSourceRegistry.getDefault();\n * await dataSource?.driver.disconnect();\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Cleanup resources on disconnect\n * onceDisconnected(async (ds) => {\n * // Close file handles\n * await closeFileHandles();\n *\n * // Clear caches\n * clearCaches();\n *\n * console.log(\"Cleanup complete\");\n * });\n * ```\n */\nexport function onceDisconnected(\n dataSourceOrNameOrCallback: DataSource | string | ((dataSource: DataSource) => void),\n callback?: (dataSource: DataSource) => void,\n): void {\n // Determine if first parameter is a callback\n let targetDataSource: DataSource | string;\n let actualCallback: (dataSource: DataSource) => void;\n\n if (typeof dataSourceOrNameOrCallback === \"function\") {\n // First param is callback, use default data source\n actualCallback = dataSourceOrNameOrCallback;\n targetDataSource = \"default\";\n } else {\n // First param is data source name or instance\n if (!callback) {\n throw new Error(\"Callback is required when providing a data source name or instance.\");\n }\n actualCallback = callback;\n targetDataSource = dataSourceOrNameOrCallback;\n }\n\n // Try to get the data source if it's a string\n let resolvedDataSource: DataSource | undefined;\n if (typeof targetDataSource === \"string\") {\n try {\n resolvedDataSource =\n targetDataSource === \"default\"\n ? dataSourceRegistry.get()\n : dataSourceRegistry.get(targetDataSource);\n } catch {\n // Data source not registered yet, will wait for event\n }\n } else {\n resolvedDataSource = targetDataSource;\n }\n\n // If data source is resolved and already disconnected, fire immediately\n if (resolvedDataSource && !resolvedDataSource.driver.isConnected) {\n actualCallback(resolvedDataSource);\n return;\n }\n\n // Otherwise, wait for the disconnected event\n const listener = (ds: DataSource) => {\n const matches =\n typeof targetDataSource === \"string\"\n ? targetDataSource === \"default\"\n ? isRegistryDefault(ds)\n : ds.name === targetDataSource\n : ds === targetDataSource;\n\n if (matches) {\n actualCallback(ds);\n } else {\n // Not the one we're looking for, keep listening\n dataSourceRegistry.once(\"disconnected\", listener);\n }\n };\n\n dataSourceRegistry.once(\"disconnected\", listener);\n}\n"],"mappings":";;;;;;AAMA,SAAS,kBAAkB,IAAyB;CAClD,IAAI;EACF,OAAO,mBAAmB,IAAI,MAAM;CACtC,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyEA,SAAgB,cACd,4BACA,UACM;CAEN,IAAI;CACJ,IAAI;CAEJ,IAAI,OAAO,+BAA+B,YAAY;EAEpD,iBAAiB;EACjB,mBAAmB;CACrB,OAAO;EAEL,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,qEAAqE;EAEvF,iBAAiB;EACjB,mBAAmB;CACrB;CAGA,IAAI;CACJ,IAAI,OAAO,qBAAqB,UAC9B,IAAI;EACF,qBACE,qBAAqB,YACjB,mBAAmB,IAAI,IACvB,mBAAmB,IAAI,gBAAgB;CAC/C,QAAQ,CAER;MAEA,qBAAqB;CAIvB,IAAI,sBAAsB,mBAAmB,OAAO,aAAa;EAC/D,eAAe,kBAAkB;EACjC;CACF;CAGA,MAAM,YAAY,OAAmB;EAQnC,IANE,OAAO,qBAAqB,WACxB,qBAAqB,YACnB,kBAAkB,EAAE,IACpB,GAAG,SAAS,mBACd,OAAO,kBAEA;GAEX,mBAAmB,IAAI,aAAa,QAAQ;GAC5C,eAAe,EAAE;EACnB;CAEF;CAEA,mBAAmB,GAAG,aAAa,QAAQ;AAC7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyEA,SAAgB,iBACd,4BACA,UACM;CAEN,IAAI;CACJ,IAAI;CAEJ,IAAI,OAAO,+BAA+B,YAAY;EAEpD,iBAAiB;EACjB,mBAAmB;CACrB,OAAO;EAEL,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,qEAAqE;EAEvF,iBAAiB;EACjB,mBAAmB;CACrB;CAGA,IAAI;CACJ,IAAI,OAAO,qBAAqB,UAC9B,IAAI;EACF,qBACE,qBAAqB,YACjB,mBAAmB,IAAI,IACvB,mBAAmB,IAAI,gBAAgB;CAC/C,QAAQ,CAER;MAEA,qBAAqB;CAIvB,IAAI,sBAAsB,CAAC,mBAAmB,OAAO,aAAa;EAChE,eAAe,kBAAkB;EACjC;CACF;CAGA,MAAM,YAAY,OAAmB;EAQnC,IANE,OAAO,qBAAqB,WACxB,qBAAqB,YACnB,kBAAkB,EAAE,IACpB,GAAG,SAAS,mBACd,OAAO,kBAGX,eAAe,EAAE;OAGjB,mBAAmB,KAAK,gBAAgB,QAAQ;CAEpD;CAEA,mBAAmB,KAAK,gBAAgB,QAAQ;AAClD"}
|
|
@@ -1,24 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { requireModelClass } from "../../model/register-model.mjs";
|
|
2
2
|
import { Model } from "../../model/model.mjs";
|
|
3
3
|
|
|
4
4
|
//#region ../cascade/src/validation/mutators/embed-mutator.ts
|
|
5
5
|
const databaseModelMutator = async (value, context) => {
|
|
6
|
-
|
|
7
|
-
if (
|
|
8
|
-
|
|
6
|
+
const { model } = context?.options || {};
|
|
7
|
+
if (!model) throw new Error("Model option is required");
|
|
8
|
+
const ModelClass = requireModelClass(model);
|
|
9
9
|
if (value instanceof Model) return value;
|
|
10
|
-
if (typeof value === "object"
|
|
11
|
-
|
|
10
|
+
if (value && typeof value === "object") {
|
|
11
|
+
const key = ModelClass.primaryKey || "id";
|
|
12
|
+
value = value[key] ?? value.id;
|
|
13
|
+
}
|
|
14
|
+
if (typeof value !== "number" && typeof value !== "string") return value;
|
|
15
|
+
if (value === "") return value;
|
|
12
16
|
return await ModelClass.find(value);
|
|
13
17
|
};
|
|
14
18
|
const databaseModelsMutator = async (value, context) => {
|
|
15
19
|
if (!Array.isArray(value)) return value;
|
|
16
|
-
|
|
17
|
-
if (
|
|
18
|
-
|
|
20
|
+
const { model } = context?.options || {};
|
|
21
|
+
if (!model) throw new Error("Model option is required");
|
|
22
|
+
const ModelClass = requireModelClass(model);
|
|
19
23
|
if (value.every((item) => item instanceof Model)) return value;
|
|
20
|
-
const
|
|
21
|
-
|
|
24
|
+
const key = ModelClass.primaryKey || "id";
|
|
25
|
+
const ids = value.map((item) => item && typeof item === "object" ? item[key] ?? item.id : item).filter((item) => item !== void 0 && item !== null);
|
|
26
|
+
const uniqueIds = [...new Set(ids)];
|
|
27
|
+
const found = await ModelClass.query().whereIn(key, uniqueIds).get();
|
|
28
|
+
if (found.length !== uniqueIds.length) return value;
|
|
29
|
+
return found;
|
|
22
30
|
};
|
|
23
31
|
|
|
24
32
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embed-mutator.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/mutators/embed-mutator.ts"],"sourcesContent":["import type { Mutator } from \"@warlock.js/seal\";\nimport { type ChildModel, Model } from \"../../model/model\";\nimport {
|
|
1
|
+
{"version":3,"file":"embed-mutator.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/mutators/embed-mutator.ts"],"sourcesContent":["import type { Mutator } from \"@warlock.js/seal\";\nimport { type ChildModel, Model } from \"../../model/model\";\nimport { requireModelClass } from \"../../model/register-model\";\n\ntype DatabaseModelMutatorOptions = {\n model: ChildModel<any> | string;\n};\n\nexport const databaseModelMutator: Mutator<DatabaseModelMutatorOptions> = async (\n value,\n context,\n) => {\n const { model } = context?.options || {};\n\n if (!model) {\n throw new Error(\"Model option is required\");\n }\n\n const ModelClass = requireModelClass(model);\n\n if (value instanceof Model) return value;\n\n if (value && typeof value === \"object\") {\n const key = ModelClass.primaryKey || \"id\";\n value = value[key] ?? value.id;\n }\n\n if (typeof value !== \"number\" && typeof value !== \"string\") return value;\n\n if (value === \"\") return value;\n\n return await ModelClass.find(value);\n};\n\nexport const databaseModelsMutator: Mutator<DatabaseModelMutatorOptions> = async (\n value,\n context,\n) => {\n if (!Array.isArray(value)) return value;\n\n const { model } = context?.options || {};\n\n if (!model) {\n throw new Error(\"Model option is required\");\n }\n\n const ModelClass = requireModelClass(model);\n\n // first, if all values are list of models, then return them.\n if (value.every((item) => item instanceof Model)) return value;\n\n const key = ModelClass.primaryKey || \"id\";\n\n const ids = value\n .map((item) => (item && typeof item === \"object\" ? (item[key] ?? item.id) : item))\n .filter((item) => item !== undefined && item !== null);\n\n const uniqueIds = [...new Set(ids)];\n\n const found = await ModelClass.query().whereIn(key, uniqueIds).get();\n\n // a missing id must fail validation instead of being silently dropped;\n // returning the raw value makes the models rule reject it\n if (found.length !== uniqueIds.length) return value;\n\n return found;\n};\n"],"mappings":";;;;AAQA,MAAa,uBAA6D,OACxE,OACA,YACG;CACH,MAAM,EAAE,UAAU,SAAS,WAAW,CAAC;CAEvC,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,0BAA0B;CAG5C,MAAM,aAAa,kBAAkB,KAAK;CAE1C,IAAI,iBAAiB,OAAO,OAAO;CAEnC,IAAI,SAAS,OAAO,UAAU,UAAU;EACtC,MAAM,MAAM,WAAW,cAAc;EACrC,QAAQ,MAAM,QAAQ,MAAM;CAC9B;CAEA,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU,OAAO;CAEnE,IAAI,UAAU,IAAI,OAAO;CAEzB,OAAO,MAAM,WAAW,KAAK,KAAK;AACpC;AAEA,MAAa,wBAA8D,OACzE,OACA,YACG;CACH,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO;CAElC,MAAM,EAAE,UAAU,SAAS,WAAW,CAAC;CAEvC,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,0BAA0B;CAG5C,MAAM,aAAa,kBAAkB,KAAK;CAG1C,IAAI,MAAM,OAAO,SAAS,gBAAgB,KAAK,GAAG,OAAO;CAEzD,MAAM,MAAM,WAAW,cAAc;CAErC,MAAM,MAAM,MACT,KAAK,SAAU,QAAQ,OAAO,SAAS,WAAY,KAAK,QAAQ,KAAK,KAAM,IAAK,CAAC,CACjF,QAAQ,SAAS,SAAS,UAAa,SAAS,IAAI;CAEvD,MAAM,YAAY,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC;CAElC,MAAM,QAAQ,MAAM,WAAW,MAAM,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,IAAI;CAInE,IAAI,MAAM,WAAW,UAAU,QAAQ,OAAO;CAE9C,OAAO;AACT"}
|
|
@@ -7,8 +7,8 @@ type EmbedOptions = {
|
|
|
7
7
|
};
|
|
8
8
|
declare module "@warlock.js/seal" {
|
|
9
9
|
interface ValidatorV {
|
|
10
|
-
embed(model
|
|
11
|
-
embedMany(model
|
|
10
|
+
embed(model?: ChildModel<any> | string, options?: EmbedOptions): EmbedModelValidator;
|
|
11
|
+
embedMany(model?: ChildModel<any> | string, options?: EmbedOptions): EmbedModelValidator;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embed-validator-plugin.d.mts","names":[],"sources":["../../../../../../../../cascade/src/validation/plugins/embed-validator-plugin.ts"],"mappings":";;;KAWK,YAAA;EACH,YAAA;EACA,KAAK;AAAA;AAAA;EAAA,UAIK,UAAA;IACR,KAAA,CAAM,KAAA,
|
|
1
|
+
{"version":3,"file":"embed-validator-plugin.d.mts","names":[],"sources":["../../../../../../../../cascade/src/validation/plugins/embed-validator-plugin.ts"],"mappings":";;;KAWK,YAAA;EACH,YAAA;EACA,KAAK;AAAA;AAAA;EAAA,UAIK,UAAA;IACR,KAAA,CAAM,KAAA,GAAQ,UAAA,gBAA0B,OAAA,GAAU,YAAA,GAAe,mBAAA;IACjE,SAAA,CAAU,KAAA,GAAQ,UAAA,gBAA0B,OAAA,GAAU,YAAA,GAAe,mBAAA;EAAA;AAAA"}
|
|
@@ -10,8 +10,14 @@ const embedValidator = {
|
|
|
10
10
|
version: "1.0.0",
|
|
11
11
|
description: "Adds embed validation (v.embed())",
|
|
12
12
|
install() {
|
|
13
|
-
v.embed = (model, options) =>
|
|
14
|
-
|
|
13
|
+
v.embed = (model, options) => {
|
|
14
|
+
const validator = new EmbedModelValidator();
|
|
15
|
+
return (model ? validator.model(model, options?.errorMessage) : validator).embed(options?.embed);
|
|
16
|
+
};
|
|
17
|
+
v.embedMany = (model, options) => {
|
|
18
|
+
const validator = new EmbedModelValidator();
|
|
19
|
+
return (model ? validator.models(model, options?.errorMessage) : validator).embed(options?.embed);
|
|
20
|
+
};
|
|
15
21
|
}
|
|
16
22
|
};
|
|
17
23
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embed-validator-plugin.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/plugins/embed-validator-plugin.ts"],"sourcesContent":["/**\n * Embed Validator Plugin\n *\n * Adds embed validation to Seal v factory\n */\n\nimport type { SealPlugin } from \"@warlock.js/seal\";\nimport { v } from \"@warlock.js/seal\";\nimport type { ChildModel } from \"../../model/model\";\nimport { EmbedModelValidator } from \"../validators/embed-validator\";\n\ntype EmbedOptions = {\n errorMessage?: string;\n embed?: string | string[];\n};\n\ndeclare module \"@warlock.js/seal\" {\n interface ValidatorV {\n embed(model
|
|
1
|
+
{"version":3,"file":"embed-validator-plugin.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/plugins/embed-validator-plugin.ts"],"sourcesContent":["/**\n * Embed Validator Plugin\n *\n * Adds embed validation to Seal v factory\n */\n\nimport type { SealPlugin } from \"@warlock.js/seal\";\nimport { v } from \"@warlock.js/seal\";\nimport type { ChildModel } from \"../../model/model\";\nimport { EmbedModelValidator } from \"../validators/embed-validator\";\n\ntype EmbedOptions = {\n errorMessage?: string;\n embed?: string | string[];\n};\n\ndeclare module \"@warlock.js/seal\" {\n interface ValidatorV {\n embed(model?: ChildModel<any> | string, options?: EmbedOptions): EmbedModelValidator;\n embedMany(model?: ChildModel<any> | string, options?: EmbedOptions): EmbedModelValidator;\n }\n}\n\n/**\n * File validation plugin for Seal\n */\nexport const embedValidator: SealPlugin = {\n name: \"embed\",\n version: \"1.0.0\",\n description: \"Adds embed validation (v.embed())\",\n\n install() {\n // Inject embed() method into v factory\n // without a model, a bare validator is returned (call .model()/.models() later)\n v.embed = (model?: ChildModel<any> | string, options?: EmbedOptions) => {\n const validator = new EmbedModelValidator();\n\n return (model ? validator.model(model, options?.errorMessage) : validator).embed(\n options?.embed,\n );\n };\n v.embedMany = (model?: ChildModel<any> | string, options?: EmbedOptions) => {\n const validator = new EmbedModelValidator();\n\n return (model ? validator.models(model, options?.errorMessage) : validator).embed(\n options?.embed,\n );\n };\n },\n};\n"],"mappings":";;;;;;;AA0BA,MAAa,iBAA6B;CACxC,MAAM;CACN,SAAS;CACT,aAAa;CAEb,UAAU;EAGR,EAAE,SAAS,OAAkC,YAA2B;GACtE,MAAM,YAAY,IAAI,oBAAoB;GAE1C,QAAQ,QAAQ,UAAU,MAAM,OAAO,SAAS,YAAY,IAAI,UAAS,CAAE,MACzE,SAAS,KACX;EACF;EACA,EAAE,aAAa,OAAkC,YAA2B;GAC1E,MAAM,YAAY,IAAI,oBAAoB;GAE1C,QAAQ,QAAQ,UAAU,OAAO,OAAO,SAAS,YAAY,IAAI,UAAS,CAAE,MAC1E,SAAS,KACX;EACF;CACF;AACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { requireModelClass } from "../../model/register-model.mjs";
|
|
2
2
|
import { Model } from "../../model/model.mjs";
|
|
3
3
|
import { VALID_RULE, invalidRule } from "@warlock.js/seal";
|
|
4
4
|
|
|
@@ -8,7 +8,8 @@ const databaseModelRule = {
|
|
|
8
8
|
defaultErrorMessage: "The :input must be a valid :model model",
|
|
9
9
|
async validate(value, context) {
|
|
10
10
|
if (value instanceof Model === false) {
|
|
11
|
-
|
|
11
|
+
const { model } = this.context.options;
|
|
12
|
+
this.context.attributesList.model = typeof model === "string" ? model : model?.name;
|
|
12
13
|
return invalidRule(this, context);
|
|
13
14
|
}
|
|
14
15
|
return VALID_RULE;
|
|
@@ -18,9 +19,9 @@ const databaseModelsRule = {
|
|
|
18
19
|
name: "databaseModels",
|
|
19
20
|
defaultErrorMessage: "The :input must be a list of valid :model",
|
|
20
21
|
async validate(value, context) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.context.attributesList.model = model.name;
|
|
22
|
+
const { model } = this.context.options;
|
|
23
|
+
const ModelClass = requireModelClass(model);
|
|
24
|
+
this.context.attributesList.model = typeof model === "string" ? model : ModelClass.name;
|
|
24
25
|
if (!Array.isArray(value)) return invalidRule(this, context);
|
|
25
26
|
if (value.every((item) => item instanceof Model)) return VALID_RULE;
|
|
26
27
|
return invalidRule(this, context);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-model-rule.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/rules/database-model-rule.ts"],"sourcesContent":["import { invalidRule, VALID_RULE, type SchemaRule } from \"@warlock.js/seal\";\
|
|
1
|
+
{"version":3,"file":"database-model-rule.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/rules/database-model-rule.ts"],"sourcesContent":["import { invalidRule, VALID_RULE, type SchemaRule } from \"@warlock.js/seal\";\nimport { type ChildModel, Model } from \"./../../model/model\";\nimport { requireModelClass } from \"./../../model/register-model\";\n\nexport const databaseModelRule: SchemaRule = {\n name: \"databaseModel\",\n defaultErrorMessage: \"The :input must be a valid :model model\",\n async validate(value, context) {\n if (value instanceof Model === false) {\n const { model } = this.context.options;\n\n this.context.attributesList.model = typeof model === \"string\" ? model : model?.name;\n return invalidRule(this, context);\n }\n\n return VALID_RULE;\n },\n};\n\nexport const databaseModelsRule: SchemaRule<{ model: ChildModel<any> | string }> = {\n name: \"databaseModels\",\n defaultErrorMessage: \"The :input must be a list of valid :model\",\n async validate(value, context) {\n const { model } = this.context.options;\n\n // keep the original name for messages; throws when a string ref is unregistered\n const ModelClass = requireModelClass(model);\n\n this.context.attributesList.model = typeof model === \"string\" ? model : ModelClass.name;\n\n if (!Array.isArray(value)) return invalidRule(this, context);\n\n if (value.every((item) => item instanceof Model)) return VALID_RULE;\n\n return invalidRule(this, context);\n },\n};\n"],"mappings":";;;;;AAIA,MAAa,oBAAgC;CAC3C,MAAM;CACN,qBAAqB;CACrB,MAAM,SAAS,OAAO,SAAS;EAC7B,IAAI,iBAAiB,UAAU,OAAO;GACpC,MAAM,EAAE,UAAU,KAAK,QAAQ;GAE/B,KAAK,QAAQ,eAAe,QAAQ,OAAO,UAAU,WAAW,QAAQ,OAAO;GAC/E,OAAO,YAAY,MAAM,OAAO;EAClC;EAEA,OAAO;CACT;AACF;AAEA,MAAa,qBAAsE;CACjF,MAAM;CACN,qBAAqB;CACrB,MAAM,SAAS,OAAO,SAAS;EAC7B,MAAM,EAAE,UAAU,KAAK,QAAQ;EAG/B,MAAM,aAAa,kBAAkB,KAAK;EAE1C,KAAK,QAAQ,eAAe,QAAQ,OAAO,UAAU,WAAW,QAAQ,WAAW;EAEnF,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,YAAY,MAAM,OAAO;EAE3D,IAAI,MAAM,OAAO,SAAS,gBAAgB,KAAK,GAAG,OAAO;EAEzD,OAAO,YAAY,MAAM,OAAO;CAClC;AACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { requireModelClass } from "../../model/register-model.mjs";
|
|
2
2
|
import { VALID_RULE, invalidRule } from "@warlock.js/seal";
|
|
3
3
|
|
|
4
4
|
//#region ../cascade/src/validation/rules/exists-rule.ts
|
|
@@ -12,9 +12,11 @@ const existsRule = {
|
|
|
12
12
|
name: "exists",
|
|
13
13
|
defaultErrorMessage: "The :input must exist",
|
|
14
14
|
async validate(value, context) {
|
|
15
|
-
const { Model, query, column
|
|
16
|
-
const
|
|
17
|
-
|
|
15
|
+
const { Model, query, column } = this.context.options;
|
|
16
|
+
const ResolvedModelClass = requireModelClass(Model);
|
|
17
|
+
const lookupColumn = column ?? ResolvedModelClass.primaryKey;
|
|
18
|
+
const dbQuery = ResolvedModelClass.query();
|
|
19
|
+
dbQuery.where(lookupColumn, value);
|
|
18
20
|
if (query) await query({
|
|
19
21
|
query: dbQuery,
|
|
20
22
|
value,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exists-rule.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/rules/exists-rule.ts"],"sourcesContent":["import { invalidRule, VALID_RULE, type SchemaRule } from \"@warlock.js/seal\";\nimport {
|
|
1
|
+
{"version":3,"file":"exists-rule.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/rules/exists-rule.ts"],"sourcesContent":["import { invalidRule, VALID_RULE, type SchemaRule } from \"@warlock.js/seal\";\nimport { requireModelClass } from \"../../model/register-model\";\nimport type { ExistsRuleOptions } from \"../types\";\n\n/**\n * Validates that a record exists in the database for the given column/value.\n *\n * @example\n * v.string().exists(Organization, { column: \"id\" });\n */\nexport const existsRule: SchemaRule<ExistsRuleOptions> = {\n name: \"exists\",\n defaultErrorMessage: \"The :input must exist\",\n async validate(value: any, context) {\n const { Model, query, column } = this.context.options;\n\n const ResolvedModelClass = requireModelClass(Model);\n\n // Explicit column wins, otherwise look up by the related model's primary key\n const lookupColumn = column ?? ResolvedModelClass.primaryKey;\n\n const dbQuery = ResolvedModelClass.query();\n\n dbQuery.where(lookupColumn, value);\n\n if (query) {\n await query({\n query: dbQuery,\n value,\n allValues: context.allValues,\n });\n }\n\n const document = await dbQuery.first();\n\n return document ? VALID_RULE : invalidRule(this, context);\n },\n};\n"],"mappings":";;;;;;;;;;AAUA,MAAa,aAA4C;CACvD,MAAM;CACN,qBAAqB;CACrB,MAAM,SAAS,OAAY,SAAS;EAClC,MAAM,EAAE,OAAO,OAAO,WAAW,KAAK,QAAQ;EAE9C,MAAM,qBAAqB,kBAAkB,KAAK;EAGlD,MAAM,eAAe,UAAU,mBAAmB;EAElD,MAAM,UAAU,mBAAmB,MAAM;EAEzC,QAAQ,MAAM,cAAc,KAAK;EAEjC,IAAI,OACF,MAAM,MAAM;GACV,OAAO;GACP;GACA,WAAW,QAAQ;EACrB,CAAC;EAKH,OAAO,MAFgB,QAAQ,MAAM,IAEnB,aAAa,YAAY,MAAM,OAAO;CAC1D;AACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { requireModelClass } from "../../model/register-model.mjs";
|
|
2
2
|
import { get } from "@mongez/reinforcements";
|
|
3
3
|
import { VALID_RULE, invalidRule } from "@warlock.js/seal";
|
|
4
4
|
|
|
@@ -22,12 +22,15 @@ const uniqueRule = {
|
|
|
22
22
|
defaultErrorMessage: "The :input must be unique",
|
|
23
23
|
async validate(value, context) {
|
|
24
24
|
const { Model, except, column = context.key, exceptColumnName, exceptValue, query } = this.context.options;
|
|
25
|
-
const
|
|
25
|
+
const ResolvedModelClass = requireModelClass(Model);
|
|
26
|
+
const dbQuery = ResolvedModelClass.query();
|
|
26
27
|
dbQuery.where(column, value);
|
|
27
28
|
if (except) {
|
|
28
29
|
const exceptVal = get(context.allValues, except);
|
|
29
30
|
if (exceptVal !== void 0) dbQuery.where(except, "!=", exceptVal);
|
|
30
31
|
}
|
|
32
|
+
const currentModel = context.rootContext?.model;
|
|
33
|
+
if (currentModel && currentModel.isNew === false && currentModel.constructor === ResolvedModelClass && currentModel.trustedPrimaryKey !== void 0) dbQuery.where(ResolvedModelClass.primaryKey || "id", "!=", currentModel.trustedPrimaryKey);
|
|
31
34
|
if (exceptColumnName !== void 0) dbQuery.where(exceptColumnName, "!=", exceptValue);
|
|
32
35
|
if (query) await query({
|
|
33
36
|
query: dbQuery,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unique-rule.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/rules/unique-rule.ts"],"sourcesContent":["import { get } from \"@mongez/reinforcements\";\nimport { invalidRule, VALID_RULE, type SchemaRule } from \"@warlock.js/seal\";\nimport {
|
|
1
|
+
{"version":3,"file":"unique-rule.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/rules/unique-rule.ts"],"sourcesContent":["import { get } from \"@mongez/reinforcements\";\nimport { invalidRule, VALID_RULE, type SchemaRule } from \"@warlock.js/seal\";\nimport { requireModelClass } from \"../../model/register-model\";\nimport type { UniqueRuleOptions } from \"../types\";\n\n/**\n * Validates that a value is unique against a database column.\n *\n * Supports three exclusion modes:\n * - `except`: read a sibling input's value and exclude rows where that\n * column equals the sibling's value\n * - `exceptColumnName` + `exceptValue`: exclude rows where the named\n * column equals the supplied value (used by request-aware wrappers)\n * - Custom `query` callback for anything more involved\n *\n * @example\n * v.email().unique(\"User\");\n * v.string().unique(\"User\", { except: \"id\" });\n */\nexport const uniqueRule: SchemaRule<UniqueRuleOptions> = {\n name: \"unique\",\n defaultErrorMessage: \"The :input must be unique\",\n async validate(value: any, context) {\n const {\n Model,\n except,\n column = context.key,\n exceptColumnName,\n exceptValue,\n query,\n } = this.context.options;\n\n const ResolvedModelClass = requireModelClass(Model);\n\n const dbQuery = ResolvedModelClass.query();\n\n dbQuery.where(column, value);\n\n if (except) {\n const exceptVal = get(context.allValues, except);\n\n if (exceptVal !== undefined) {\n dbQuery.where(except, \"!=\", exceptVal);\n }\n }\n\n // A persisted model never collides with its own row on update.\n const currentModel = context.rootContext?.model;\n\n if (\n currentModel &&\n currentModel.isNew === false &&\n currentModel.constructor === ResolvedModelClass &&\n currentModel.trustedPrimaryKey !== undefined\n ) {\n dbQuery.where(\n (ResolvedModelClass as any).primaryKey || \"id\",\n \"!=\",\n currentModel.trustedPrimaryKey,\n );\n }\n\n if (exceptColumnName !== undefined) {\n dbQuery.where(exceptColumnName, \"!=\", exceptValue);\n }\n\n if (query) {\n await query({\n query: dbQuery,\n value,\n allValues: context.allValues,\n });\n }\n\n const document = await dbQuery.first();\n\n return document ? invalidRule(this, context) : VALID_RULE;\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmBA,MAAa,aAA4C;CACvD,MAAM;CACN,qBAAqB;CACrB,MAAM,SAAS,OAAY,SAAS;EAClC,MAAM,EACJ,OACA,QACA,SAAS,QAAQ,KACjB,kBACA,aACA,UACE,KAAK,QAAQ;EAEjB,MAAM,qBAAqB,kBAAkB,KAAK;EAElD,MAAM,UAAU,mBAAmB,MAAM;EAEzC,QAAQ,MAAM,QAAQ,KAAK;EAE3B,IAAI,QAAQ;GACV,MAAM,YAAY,IAAI,QAAQ,WAAW,MAAM;GAE/C,IAAI,cAAc,QAChB,QAAQ,MAAM,QAAQ,MAAM,SAAS;EAEzC;EAGA,MAAM,eAAe,QAAQ,aAAa;EAE1C,IACE,gBACA,aAAa,UAAU,SACvB,aAAa,gBAAgB,sBAC7B,aAAa,sBAAsB,QAEnC,QAAQ,MACL,mBAA2B,cAAc,MAC1C,MACA,aAAa,iBACf;EAGF,IAAI,qBAAqB,QACvB,QAAQ,MAAM,kBAAkB,MAAM,WAAW;EAGnD,IAAI,OACF,MAAM,MAAM;GACV,OAAO;GACP;GACA,WAAW,QAAQ;EACrB,CAAC;EAKH,OAAO,MAFgB,QAAQ,MAAM,IAEnB,YAAY,MAAM,OAAO,IAAI;CACjD;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embed-validator.d.mts","names":[],"sources":["../../../../../../../../cascade/src/validation/validators/embed-validator.ts"],"mappings":";;;;cAMa,mBAAA,SAA4B,aAAA;;;AAAzC;EAIS,WAAA,CAAY,KAAA;;;;
|
|
1
|
+
{"version":3,"file":"embed-validator.d.mts","names":[],"sources":["../../../../../../../../cascade/src/validation/validators/embed-validator.ts"],"mappings":";;;;cAMa,mBAAA,SAA4B,aAAA;;;AAAzC;EAIS,WAAA,CAAY,KAAA;;;;EAYZ,KAAA,CAAM,KAAA,EAAO,UAAA,gBAA0B,YAAA,YAAwB,mBAAA;EAaC;;;EAAhE,MAAA,CAAO,KAAA,EAAO,UAAA,gBAA0B,YAAA,YAAwB,mBAAA;EA7BnB;;;EA8C7C,KAAA,CAAM,KAAA,uBAA4B,mBAAA;AAAA"}
|
|
@@ -10,7 +10,7 @@ var EmbedModelValidator = class extends BaseValidator {
|
|
|
10
10
|
* Check if value is a string type
|
|
11
11
|
*/
|
|
12
12
|
matchesType(value) {
|
|
13
|
-
return value instanceof Model || typeof value === "number" || Array.isArray(value) && value.every((item) => item instanceof Model);
|
|
13
|
+
return value instanceof Model || typeof value === "number" || typeof value === "string" || Array.isArray(value) && value.every((item) => item instanceof Model);
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* Mutate the value to be a model, also fail if the mutated value is not a valid model
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embed-validator.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/validators/embed-validator.ts"],"sourcesContent":["import { arrayRule, BaseValidator } from \"@warlock.js/seal\";\nimport { Model, type ChildModel } from \"../../model/model\";\nimport { databaseModelMutator, databaseModelsMutator } from \"../mutators/embed-mutator\";\nimport { databaseModelRule, databaseModelsRule } from \"../rules/database-model-rule\";\nimport { databaseModelTransformer } from \"../transformers/embed-model-transformer\";\n\nexport class EmbedModelValidator extends BaseValidator {\n /**\n * Check if value is a string type\n */\n public matchesType(value: any): boolean {\n return (\n value instanceof Model ||\n typeof value === \"number\" ||\n (Array.isArray(value) && value.every((item) => item instanceof Model))\n );\n }\n\n /**\n * Mutate the value to be a model, also fail if the mutated value is not a valid model\n */\n public model(model: ChildModel<any> | string, errorMessage?: string): EmbedModelValidator {\n this.addMutator(databaseModelMutator, {\n model,\n });\n\n return this.addRule(databaseModelRule, errorMessage, {\n model,\n });\n }\n\n /**\n * Validate the value is a list of models\n */\n public models(model: ChildModel<any> | string, errorMessage?: string): EmbedModelValidator {\n const instance = this.instance;\n instance.addMutator(databaseModelsMutator, {\n model,\n });\n\n instance.addMutableRule(arrayRule);\n instance.addMutableRule(databaseModelsRule, errorMessage, {\n model,\n });\n\n return instance;\n }\n\n /**\n * Determine how the data will be stored as an embedded document\n */\n public embed(embed?: string | string[]): EmbedModelValidator {\n return this.addTransformer(databaseModelTransformer, {\n embed,\n });\n }\n}\n"],"mappings":";;;;;;;AAMA,IAAa,sBAAb,cAAyC,cAAc;;;;CAIrD,AAAO,YAAY,OAAqB;EACtC,OACE,iBAAiB,SACjB,OAAO,UAAU,YAChB,MAAM,QAAQ,KAAK,KAAK,MAAM,OAAO,SAAS,gBAAgB,KAAK;CAExE;;;;CAKA,AAAO,MAAM,OAAiC,cAA4C;EACxF,KAAK,WAAW,sBAAsB,EACpC,MACF,CAAC;EAED,OAAO,KAAK,QAAQ,mBAAmB,cAAc,EACnD,MACF,CAAC;CACH;;;;CAKA,AAAO,OAAO,OAAiC,cAA4C;EACzF,MAAM,WAAW,KAAK;EACtB,SAAS,WAAW,uBAAuB,EACzC,MACF,CAAC;EAED,SAAS,eAAe,SAAS;EACjC,SAAS,eAAe,oBAAoB,cAAc,EACxD,MACF,CAAC;EAED,OAAO;CACT;;;;CAKA,AAAO,MAAM,OAAgD;EAC3D,OAAO,KAAK,eAAe,0BAA0B,EACnD,MACF,CAAC;CACH;AACF"}
|
|
1
|
+
{"version":3,"file":"embed-validator.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/validators/embed-validator.ts"],"sourcesContent":["import { arrayRule, BaseValidator } from \"@warlock.js/seal\";\nimport { Model, type ChildModel } from \"../../model/model\";\nimport { databaseModelMutator, databaseModelsMutator } from \"../mutators/embed-mutator\";\nimport { databaseModelRule, databaseModelsRule } from \"../rules/database-model-rule\";\nimport { databaseModelTransformer } from \"../transformers/embed-model-transformer\";\n\nexport class EmbedModelValidator extends BaseValidator {\n /**\n * Check if value is a string type\n */\n public matchesType(value: any): boolean {\n return (\n value instanceof Model ||\n typeof value === \"number\" ||\n typeof value === \"string\" ||\n (Array.isArray(value) && value.every((item) => item instanceof Model))\n );\n }\n\n /**\n * Mutate the value to be a model, also fail if the mutated value is not a valid model\n */\n public model(model: ChildModel<any> | string, errorMessage?: string): EmbedModelValidator {\n this.addMutator(databaseModelMutator, {\n model,\n });\n\n return this.addRule(databaseModelRule, errorMessage, {\n model,\n });\n }\n\n /**\n * Validate the value is a list of models\n */\n public models(model: ChildModel<any> | string, errorMessage?: string): EmbedModelValidator {\n const instance = this.instance;\n instance.addMutator(databaseModelsMutator, {\n model,\n });\n\n instance.addMutableRule(arrayRule);\n instance.addMutableRule(databaseModelsRule, errorMessage, {\n model,\n });\n\n return instance;\n }\n\n /**\n * Determine how the data will be stored as an embedded document\n */\n public embed(embed?: string | string[]): EmbedModelValidator {\n return this.addTransformer(databaseModelTransformer, {\n embed,\n });\n }\n}\n"],"mappings":";;;;;;;AAMA,IAAa,sBAAb,cAAyC,cAAc;;;;CAIrD,AAAO,YAAY,OAAqB;EACtC,OACE,iBAAiB,SACjB,OAAO,UAAU,YACjB,OAAO,UAAU,YAChB,MAAM,QAAQ,KAAK,KAAK,MAAM,OAAO,SAAS,gBAAgB,KAAK;CAExE;;;;CAKA,AAAO,MAAM,OAAiC,cAA4C;EACxF,KAAK,WAAW,sBAAsB,EACpC,MACF,CAAC;EAED,OAAO,KAAK,QAAQ,mBAAmB,cAAc,EACnD,MACF,CAAC;CACH;;;;CAKA,AAAO,OAAO,OAAiC,cAA4C;EACzF,MAAM,WAAW,KAAK;EACtB,SAAS,WAAW,uBAAuB,EACzC,MACF,CAAC;EAED,SAAS,eAAe,SAAS;EACjC,SAAS,eAAe,oBAAoB,cAAc,EACxD,MACF,CAAC;EAED,OAAO;CACT;;;;CAKA,AAAO,MAAM,OAAgD;EAC3D,OAAO,KAAK,eAAe,0BAA0B,EACnD,MACF,CAAC;CACH;AACF"}
|
|
@@ -39,6 +39,8 @@ import { WriterContract, WriterOptions, WriterResult } from "../contracts/databa
|
|
|
39
39
|
declare class DatabaseWriter implements WriterContract {
|
|
40
40
|
/** The model instance being persisted */
|
|
41
41
|
private readonly model;
|
|
42
|
+
/** Dirty columns an update validation dropped (strip mode); never written. */
|
|
43
|
+
private readonly unvalidatedColumns;
|
|
42
44
|
/** Model constructor reference */
|
|
43
45
|
private readonly ctor;
|
|
44
46
|
/** Data source containing driver and ID generator */
|
|
@@ -85,6 +87,13 @@ declare class DatabaseWriter implements WriterContract {
|
|
|
85
87
|
* @private
|
|
86
88
|
*/
|
|
87
89
|
private validateAndCast;
|
|
90
|
+
/**
|
|
91
|
+
* Validate, cast and id-generate a not-yet-persisted model for `upsert()`.
|
|
92
|
+
*
|
|
93
|
+
* Runs the insert-mode pipeline (schema, casts, strictMode, Mongo `id`) but
|
|
94
|
+
* does not write; the caller sends the resulting data through the driver.
|
|
95
|
+
*/
|
|
96
|
+
prepareForUpsert(): Promise<void>;
|
|
88
97
|
/**
|
|
89
98
|
* Perform an insert operation.
|
|
90
99
|
*
|
|
@@ -93,6 +102,18 @@ declare class DatabaseWriter implements WriterContract {
|
|
|
93
102
|
* @private
|
|
94
103
|
*/
|
|
95
104
|
private performInsert;
|
|
105
|
+
/**
|
|
106
|
+
* Run the insert pipeline (validation, casting, id generation, timestamps,
|
|
107
|
+
* `validating`/`validated`/`creating` hooks) WITHOUT writing.
|
|
108
|
+
*
|
|
109
|
+
* `save()` uses this for its insert path, so bulk inserts and single inserts
|
|
110
|
+
* cannot drift. The caller is responsible for sending the returned document
|
|
111
|
+
* to the driver.
|
|
112
|
+
*
|
|
113
|
+
* @param options - Save options (e.g. `skipEvents`, `skipValidation`)
|
|
114
|
+
* @returns The prepared document to insert
|
|
115
|
+
*/
|
|
116
|
+
prepareForInsert(options?: WriterOptions): Promise<Record<string, unknown>>;
|
|
96
117
|
/**
|
|
97
118
|
* Perform an update operation.
|
|
98
119
|
*
|
|
@@ -101,6 +122,12 @@ declare class DatabaseWriter implements WriterContract {
|
|
|
101
122
|
* @private
|
|
102
123
|
*/
|
|
103
124
|
private performUpdate;
|
|
125
|
+
/**
|
|
126
|
+
* Map a driver unique-constraint violation (Postgres 23505, MongoDB 11000)
|
|
127
|
+
* to a field validation error, so a lost `unique` race never surfaces as a
|
|
128
|
+
* 500. Any other error is returned untouched.
|
|
129
|
+
*/
|
|
130
|
+
private mapUniqueViolation;
|
|
104
131
|
/**
|
|
105
132
|
* Build the filter that pins a write to the row this model was loaded from.
|
|
106
133
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-writer.d.mts","names":[],"sources":["../../../../../../../cascade/src/writer/database-writer.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"database-writer.d.mts","names":[],"sources":["../../../../../../../cascade/src/writer/database-writer.ts"],"mappings":";;;;;;AAwDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,cAAA,YAA0B,cAAA;EA+HvB;EAAA,iBA7HG,KAAA;EAkPgB;EAAA,iBA/OhB,kBAAA;EA8RJ;EAAA,iBA3RI,IAAA;EA2Ra;EAAA,iBAxRb,UAAA;EAwRmD;EAAA,iBArRnD,MAAA;EAqXT;EAAA,iBAlXS,KAAA;EA8aJ;EAAA,iBA3aI,UAAA;EAwdT;EAAA,iBArdS,MAAA;EAiiBT;EAAA,iBA9hBS,UAAA;EAikBH;;AAAW;;;;;;;;;;cAnjBN,KAAA,EAAO,KAAA;;;;;;;;EAkBb,IAAA,CAAK,OAAA,GAAS,aAAA,GAAqB,OAAA,CAAQ,YAAA;;;;;;;;;;;UAqE1C,eAAA;;;;;;;EAqHD,gBAAA,IAAoB,OAAA;;;;;;;;UAYnB,aAAA;;;;;;;;;;;;EAmCD,gBAAA,CAAiB,OAAA,GAAS,aAAA,GAAqB,OAAA,CAAQ,MAAA;;;;;;;;UA4CtD,aAAA;;;;;;UAoDN,kBAAA;;;;;;;;;;;;UAmDA,qBAAA;;;;;;EASK,cAAA,IAAkB,OAAA;;;;;;;;;;;;;;;;;;;;;;UA6CvB,qBAAA;;;;;;;;;;;;UAmDA,gBAAA;;;;;;;;;;;;UAyBA,kBAAA;;;;;;;;;UAsBA,SAAA;;;;;;;;;;UAaM,WAAA;AAAA"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { afterCommit } from "../transactions/after-commit.mjs";
|
|
2
|
+
import { triggerModelEvent } from "../sync/model-events.mjs";
|
|
2
3
|
import { mergeDriverFields } from "../model/methods/accessor-methods.mjs";
|
|
3
4
|
import { DatabaseWriterValidationError } from "../validation/database-writer-validation-error.mjs";
|
|
4
5
|
import "../validation/index.mjs";
|
|
6
|
+
import { log } from "@warlock.js/logger";
|
|
5
7
|
import { when } from "@mongez/reinforcements";
|
|
6
|
-
import events from "@mongez/events";
|
|
7
8
|
import { getSealConfig, v } from "@warlock.js/seal";
|
|
8
9
|
|
|
9
10
|
//#region ../cascade/src/writer/database-writer.ts
|
|
@@ -44,6 +45,8 @@ import { getSealConfig, v } from "@warlock.js/seal";
|
|
|
44
45
|
var DatabaseWriter = class {
|
|
45
46
|
/** The model instance being persisted */
|
|
46
47
|
model;
|
|
48
|
+
/** Dirty columns an update validation dropped (strip mode); never written. */
|
|
49
|
+
unvalidatedColumns = /* @__PURE__ */ new Set();
|
|
47
50
|
/** Model constructor reference */
|
|
48
51
|
ctor;
|
|
49
52
|
/** Data source containing driver and ID generator */
|
|
@@ -100,10 +103,12 @@ var DatabaseWriter = class {
|
|
|
100
103
|
options,
|
|
101
104
|
mode: isInsert ? "insert" : "update"
|
|
102
105
|
});
|
|
103
|
-
await this.validateAndCast(isInsert, options);
|
|
104
106
|
let result;
|
|
105
107
|
if (isInsert) result = await this.performInsert(options);
|
|
106
|
-
else
|
|
108
|
+
else {
|
|
109
|
+
await this.validateAndCast(false, options);
|
|
110
|
+
result = await this.performUpdate(options);
|
|
111
|
+
}
|
|
107
112
|
const changedFields = isInsert ? [] : this.model.getDirtyColumns();
|
|
108
113
|
this.model.dirtyTracker.reset();
|
|
109
114
|
this.model.isNew = false;
|
|
@@ -111,7 +116,7 @@ var DatabaseWriter = class {
|
|
|
111
116
|
await this.model.emitEvent("saved");
|
|
112
117
|
await this.model.emitEvent(isInsert ? "created" : "updated");
|
|
113
118
|
}
|
|
114
|
-
if (!options.skipSync && !isInsert) this.triggerSync(changedFields);
|
|
119
|
+
if (!options.skipSync && !isInsert) afterCommit(() => this.triggerSync(changedFields));
|
|
115
120
|
return {
|
|
116
121
|
success: true,
|
|
117
122
|
document: this.model.data,
|
|
@@ -143,16 +148,16 @@ var DatabaseWriter = class {
|
|
|
143
148
|
...when(this.ctor.updatedAtColumn, () => ({ [this.ctor.updatedAtColumn]: v.date().optional() })),
|
|
144
149
|
...when(this.ctor.deletedAtColumn, () => ({ [this.ctor.deletedAtColumn]: v.date().optional() }))
|
|
145
150
|
};
|
|
146
|
-
const
|
|
151
|
+
const dataToValidate = isInsert ? this.model.data : Object.fromEntries(this.model.getDirtyColumns().filter((column) => column in this.model.data).map((column) => [column, this.model.data[column]]));
|
|
152
|
+
const validationSchema = isInsert ? this.schema.clone().extend(systemColumns) : this.schema.clone(Object.keys(dataToValidate)).extend(systemColumns);
|
|
147
153
|
if (this.strictMode === "strip") validationSchema.stripUnknown();
|
|
148
154
|
else if (this.strictMode === "fail") validationSchema.allowUnknown(false);
|
|
149
155
|
else if (this.strictMode === "allow") validationSchema.allowUnknown(true);
|
|
150
|
-
const result = await v.validate(validationSchema,
|
|
156
|
+
const result = await v.validate(validationSchema, dataToValidate, {
|
|
151
157
|
context: { model: this.model },
|
|
152
158
|
...getSealConfig()
|
|
153
159
|
});
|
|
154
160
|
if (!result.isValid) {
|
|
155
|
-
console.trace(result.errors);
|
|
156
161
|
const error = new DatabaseWriterValidationError(`[${this.model.constructor.name} Model] ${isInsert ? "Insert" : "Update"} Validation failed`, result.errors);
|
|
157
162
|
if (!options.skipEvents) await this.model.emitEvent("validated", {
|
|
158
163
|
result,
|
|
@@ -160,10 +165,25 @@ var DatabaseWriter = class {
|
|
|
160
165
|
});
|
|
161
166
|
throw error;
|
|
162
167
|
}
|
|
163
|
-
this.model.replaceData(result.data);
|
|
168
|
+
if (isInsert) this.model.replaceData(result.data);
|
|
169
|
+
else {
|
|
170
|
+
const validated = result.data;
|
|
171
|
+
for (const column of Object.keys(dataToValidate)) if (column in validated) this.model.set(column, validated[column]);
|
|
172
|
+
else this.unvalidatedColumns.add(column);
|
|
173
|
+
}
|
|
164
174
|
if (!options.skipEvents) await this.model.emitEvent("validated", { result });
|
|
165
175
|
}
|
|
166
176
|
/**
|
|
177
|
+
* Validate, cast and id-generate a not-yet-persisted model for `upsert()`.
|
|
178
|
+
*
|
|
179
|
+
* Runs the insert-mode pipeline (schema, casts, strictMode, Mongo `id`) but
|
|
180
|
+
* does not write; the caller sends the resulting data through the driver.
|
|
181
|
+
*/
|
|
182
|
+
async prepareForUpsert() {
|
|
183
|
+
await this.validateAndCast(true, {});
|
|
184
|
+
await this.generateNextId();
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
167
187
|
* Perform an insert operation.
|
|
168
188
|
*
|
|
169
189
|
* @param options - Save options
|
|
@@ -171,6 +191,30 @@ var DatabaseWriter = class {
|
|
|
171
191
|
* @private
|
|
172
192
|
*/
|
|
173
193
|
async performInsert(options) {
|
|
194
|
+
const dataToInsert = await this.prepareForInsert(options);
|
|
195
|
+
let result;
|
|
196
|
+
try {
|
|
197
|
+
result = await this.driver.insert(this.table, dataToInsert);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
throw this.mapUniqueViolation(error, true);
|
|
200
|
+
}
|
|
201
|
+
mergeDriverFields(this.model, result.document);
|
|
202
|
+
this.model.dirtyTracker.reset();
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Run the insert pipeline (validation, casting, id generation, timestamps,
|
|
207
|
+
* `validating`/`validated`/`creating` hooks) WITHOUT writing.
|
|
208
|
+
*
|
|
209
|
+
* `save()` uses this for its insert path, so bulk inserts and single inserts
|
|
210
|
+
* cannot drift. The caller is responsible for sending the returned document
|
|
211
|
+
* to the driver.
|
|
212
|
+
*
|
|
213
|
+
* @param options - Save options (e.g. `skipEvents`, `skipValidation`)
|
|
214
|
+
* @returns The prepared document to insert
|
|
215
|
+
*/
|
|
216
|
+
async prepareForInsert(options = {}) {
|
|
217
|
+
await this.validateAndCast(true, options);
|
|
174
218
|
await this.generateNextId();
|
|
175
219
|
const dataToInsert = this.model.data;
|
|
176
220
|
const createdAtColumn = this.ctor.createdAtColumn;
|
|
@@ -178,10 +222,7 @@ var DatabaseWriter = class {
|
|
|
178
222
|
const updatedAtColumn = this.ctor.updatedAtColumn;
|
|
179
223
|
if (updatedAtColumn) dataToInsert[updatedAtColumn] = /* @__PURE__ */ new Date();
|
|
180
224
|
if (!options.skipEvents) await this.model.emitEvent("creating");
|
|
181
|
-
|
|
182
|
-
mergeDriverFields(this.model, result.document);
|
|
183
|
-
this.model.dirtyTracker.reset();
|
|
184
|
-
return result;
|
|
225
|
+
return dataToInsert;
|
|
185
226
|
}
|
|
186
227
|
/**
|
|
187
228
|
* Perform an update operation.
|
|
@@ -201,7 +242,29 @@ var DatabaseWriter = class {
|
|
|
201
242
|
}
|
|
202
243
|
const operations = this.buildUpdateOperations();
|
|
203
244
|
if (Object.keys(operations).length === 0) return { modifiedCount: 0 };
|
|
204
|
-
|
|
245
|
+
try {
|
|
246
|
+
return await this.driver.update(this.table, this.buildPrimaryKeyFilter(), operations);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
throw this.mapUniqueViolation(error, false);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Map a driver unique-constraint violation (Postgres 23505, MongoDB 11000)
|
|
253
|
+
* to a field validation error, so a lost `unique` race never surfaces as a
|
|
254
|
+
* 500. Any other error is returned untouched.
|
|
255
|
+
*/
|
|
256
|
+
mapUniqueViolation(error, isInsert) {
|
|
257
|
+
const err = error;
|
|
258
|
+
if (err?.code !== "23505" && err?.code !== 11e3 && err?.code !== "11000") return error;
|
|
259
|
+
let field;
|
|
260
|
+
if (err.keyPattern) field = Object.keys(err.keyPattern)[0];
|
|
261
|
+
else field = /Key \(([^)]+)\)=/.exec(err.detail ?? "")?.[1]?.split(",")[0]?.trim();
|
|
262
|
+
if (!field) field = /index: (\S+?)_-?1\b/.exec(err.message ?? "")?.[1];
|
|
263
|
+
return new DatabaseWriterValidationError(`[${this.model.constructor.name} Model] ${isInsert ? "Insert" : "Update"} Validation failed`, [{
|
|
264
|
+
type: "unique",
|
|
265
|
+
error: `The ${field ?? "value"} must be unique`,
|
|
266
|
+
input: field ?? "id"
|
|
267
|
+
}]);
|
|
205
268
|
}
|
|
206
269
|
/**
|
|
207
270
|
* Build the filter that pins a write to the row this model was loaded from.
|
|
@@ -263,7 +326,7 @@ var DatabaseWriter = class {
|
|
|
263
326
|
"id",
|
|
264
327
|
"_id"
|
|
265
328
|
]);
|
|
266
|
-
const dirtyColumns = this.model.getDirtyColumns().filter((column) => !identityColumns.has(column));
|
|
329
|
+
const dirtyColumns = this.model.getDirtyColumns().filter((column) => !identityColumns.has(column) && !this.unvalidatedColumns.has(column));
|
|
267
330
|
if (dirtyColumns.length > 0) {
|
|
268
331
|
operations.$set = {};
|
|
269
332
|
for (const column of dirtyColumns) {
|
|
@@ -306,8 +369,8 @@ var DatabaseWriter = class {
|
|
|
306
369
|
* @private
|
|
307
370
|
*/
|
|
308
371
|
resolveIncrementBy() {
|
|
309
|
-
if (this.ctor.incrementIdBy) return this.ctor.incrementIdBy;
|
|
310
372
|
if (this.ctor.randomIncrement) return typeof this.ctor.randomIncrement === "function" ? this.ctor.randomIncrement() : this.randomInt(1, 10);
|
|
373
|
+
if (this.ctor.incrementIdBy) return this.ctor.incrementIdBy;
|
|
311
374
|
return 1;
|
|
312
375
|
}
|
|
313
376
|
/**
|
|
@@ -331,7 +394,11 @@ var DatabaseWriter = class {
|
|
|
331
394
|
* @private
|
|
332
395
|
*/
|
|
333
396
|
async triggerSync(changedFields) {
|
|
334
|
-
|
|
397
|
+
try {
|
|
398
|
+
await triggerModelEvent(this.ctor, "updated", this.model, changedFields);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
log.error("database", "sync.failed", `[cascade] sync failed for ${this.ctor.name}: ${error}`);
|
|
401
|
+
}
|
|
335
402
|
}
|
|
336
403
|
};
|
|
337
404
|
|