@warlock.js/cascade 5.30.0 → 5.32.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 +17 -0
- package/README.md +1 -5
- package/cjs/index.cjs +104 -86
- package/cjs/index.cjs.map +1 -1
- package/esm/context/database-data-source-context.d.mts +2 -3
- package/esm/context/database-data-source-context.d.mts.map +1 -1
- package/esm/context/database-transaction-context.d.mts +2 -3
- package/esm/context/database-transaction-context.d.mts.map +1 -1
- package/esm/contracts/database-driver.contract.d.mts +56 -36
- package/esm/contracts/database-driver.contract.d.mts.map +1 -1
- package/esm/contracts/database-id-generator.contract.d.mts +7 -5
- package/esm/contracts/database-id-generator.contract.d.mts.map +1 -1
- package/esm/contracts/database-remover.contract.d.mts +3 -5
- package/esm/contracts/database-remover.contract.d.mts.map +1 -1
- package/esm/contracts/database-restorer.contract.d.mts +6 -6
- package/esm/contracts/database-restorer.contract.d.mts.map +1 -1
- package/esm/contracts/database-writer.contract.d.mts +4 -6
- package/esm/contracts/database-writer.contract.d.mts.map +1 -1
- package/esm/contracts/driver-blueprint.contract.d.mts +2 -3
- package/esm/contracts/driver-blueprint.contract.d.mts.map +1 -1
- package/esm/contracts/index.d.mts +2 -1
- package/esm/contracts/migration-driver.contract.d.mts +101 -53
- package/esm/contracts/migration-driver.contract.d.mts.map +1 -1
- package/esm/contracts/query-builder.contract.d.mts +51 -33
- package/esm/contracts/query-builder.contract.d.mts.map +1 -1
- package/esm/contracts/sync-adapter.contract.d.mts +25 -14
- package/esm/contracts/sync-adapter.contract.d.mts.map +1 -1
- package/esm/data-source/data-source-registry.d.mts +4 -5
- package/esm/data-source/data-source-registry.d.mts.map +1 -1
- package/esm/data-source/data-source-registry.mjs.map +1 -1
- package/esm/data-source/data-source.d.mts +8 -5
- package/esm/data-source/data-source.d.mts.map +1 -1
- package/esm/database-dirty-tracker.d.mts +2 -2
- package/esm/database-dirty-tracker.d.mts.map +1 -1
- package/esm/database-dirty-tracker.mjs +3 -2
- package/esm/database-dirty-tracker.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.d.mts +3 -4
- package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-id-generator.d.mts +2 -3
- package/esm/drivers/mongodb/mongodb-id-generator.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-id-generator.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.d.mts +1 -3
- package/esm/drivers/mongodb/mongodb-migration-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts +2 -3
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.mjs +8 -5
- package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-operations.d.mts +1 -3
- package/esm/drivers/mongodb/mongodb-query-operations.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-operations.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts +7 -6
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.mjs +2 -6
- package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-sync-adapter.d.mts +1 -3
- package/esm/drivers/mongodb/mongodb-sync-adapter.d.mts.map +1 -1
- package/esm/drivers/mongodb/types.d.mts +11 -9
- package/esm/drivers/mongodb/types.d.mts.map +1 -1
- package/esm/drivers/postgres/index.d.mts +2 -1
- package/esm/drivers/postgres/index.mjs +1 -1
- package/esm/drivers/postgres/naming.mjs +1 -1
- package/esm/drivers/postgres/naming.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-blueprint.d.mts +1 -3
- package/esm/drivers/postgres/postgres-blueprint.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.d.mts +4 -6
- package/esm/drivers/postgres/postgres-dialect.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-driver.d.mts +4 -5
- package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-migration-driver.d.mts +1 -3
- package/esm/drivers/postgres/postgres-migration-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-migration-driver.mjs +13 -10
- package/esm/drivers/postgres/postgres-migration-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.d.mts +4 -4
- package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.mjs +14 -10
- package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts +16 -11
- package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.mjs +1 -2
- package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-sql-serializer.mjs +13 -10
- package/esm/drivers/postgres/postgres-sql-serializer.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-ttl-purge.mjs +1 -1
- package/esm/drivers/postgres/postgres-ttl-purge.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-type-converters.mjs +2 -2
- package/esm/drivers/postgres/postgres-type-converters.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-update-validator.mjs +1 -1
- package/esm/drivers/postgres/postgres-update-validator.mjs.map +1 -1
- package/esm/drivers/postgres/types.d.mts +87 -45
- package/esm/drivers/postgres/types.d.mts.map +1 -1
- package/esm/drivers/sql/sql-dialect.contract.d.mts +1 -3
- package/esm/drivers/sql/sql-dialect.contract.d.mts.map +1 -1
- package/esm/drivers/sql/sql-types.d.mts +113 -59
- package/esm/drivers/sql/sql-types.d.mts.map +1 -1
- package/esm/errors/missing-data-source.error.d.mts +1 -2
- package/esm/errors/missing-data-source.error.d.mts.map +1 -1
- package/esm/errors/transaction-rollback.error.d.mts +1 -2
- package/esm/errors/transaction-rollback.error.d.mts.map +1 -1
- package/esm/errors/undefined-where-value.error.d.mts +1 -2
- package/esm/errors/undefined-where-value.error.d.mts.map +1 -1
- package/esm/errors/unsafe-filter.error.d.mts +1 -2
- package/esm/errors/unsafe-filter.error.d.mts.map +1 -1
- package/esm/errors/unsafe-raw-expression.error.d.mts +1 -2
- package/esm/errors/unsafe-raw-expression.error.d.mts.map +1 -1
- package/esm/errors/unsupported-lean-operation.error.d.mts +1 -2
- package/esm/errors/unsupported-lean-operation.error.d.mts.map +1 -1
- package/esm/errors/unsupported-lock-for-update.error.d.mts +1 -2
- package/esm/errors/unsupported-lock-for-update.error.d.mts.map +1 -1
- package/esm/errors/unsupported-query-operation.error.d.mts +1 -2
- package/esm/errors/unsupported-query-operation.error.d.mts.map +1 -1
- package/esm/errors/unsupported-query-operation.error.mjs.map +1 -1
- package/esm/errors/unsupported-update-operation.error.d.mts +1 -2
- package/esm/errors/unsupported-update-operation.error.d.mts.map +1 -1
- package/esm/events/model-events.d.mts +8 -6
- package/esm/events/model-events.d.mts.map +1 -1
- package/esm/expressions/aggregate-expressions.d.mts +7 -7
- package/esm/expressions/aggregate-expressions.d.mts.map +1 -1
- package/esm/expressions/column-expressions.d.mts +11 -11
- package/esm/expressions/column-expressions.d.mts.map +1 -1
- package/esm/expressions/column-expressions.mjs.map +1 -1
- package/esm/index.d.mts +7 -2
- package/esm/index.mjs +1 -1
- package/esm/migration/column-builder.d.mts +2 -3
- package/esm/migration/column-builder.d.mts.map +1 -1
- package/esm/migration/column-helpers.d.mts +49 -50
- package/esm/migration/column-helpers.d.mts.map +1 -1
- package/esm/migration/column-helpers.mjs +1 -1
- package/esm/migration/foreign-key-builder.d.mts +2 -3
- package/esm/migration/foreign-key-builder.d.mts.map +1 -1
- package/esm/migration/foreign-key-builder.mjs.map +1 -1
- package/esm/migration/index.d.mts +3 -2
- package/esm/migration/index.mjs +2 -2
- package/esm/migration/migration-runner.d.mts +7 -6
- package/esm/migration/migration-runner.d.mts.map +1 -1
- package/esm/migration/migration-runner.mjs +15 -11
- package/esm/migration/migration-runner.mjs.map +1 -1
- package/esm/migration/migration.d.mts +50 -32
- package/esm/migration/migration.d.mts.map +1 -1
- package/esm/migration/migration.mjs +1 -3
- package/esm/migration/migration.mjs.map +1 -1
- package/esm/migration/parse-created-at.mjs.map +1 -1
- package/esm/migration/sql-grammar.mjs.map +1 -1
- package/esm/migration/sql-serializer.d.mts +1 -3
- package/esm/migration/sql-serializer.d.mts.map +1 -1
- package/esm/migration/types.d.mts +7 -7
- package/esm/migration/types.d.mts.map +1 -1
- package/esm/model/generate-model-id.mjs.map +1 -1
- package/esm/model/methods/accessor-methods.mjs +1 -1
- package/esm/model/methods/accessor-methods.mjs.map +1 -1
- package/esm/model/methods/dirty-methods.mjs.map +1 -1
- package/esm/model/methods/hydration-methods.mjs.map +1 -1
- package/esm/model/methods/meta-methods.mjs +4 -2
- package/esm/model/methods/meta-methods.mjs.map +1 -1
- package/esm/model/methods/query-methods.mjs +1 -1
- package/esm/model/methods/query-methods.mjs.map +1 -1
- package/esm/model/methods/scope-methods.mjs.map +1 -1
- package/esm/model/methods/static-event-methods.mjs.map +1 -1
- package/esm/model/methods/write-methods.d.mts +3 -2
- package/esm/model/methods/write-methods.d.mts.map +1 -1
- package/esm/model/methods/write-methods.mjs +5 -2
- package/esm/model/methods/write-methods.mjs.map +1 -1
- package/esm/model/model.d.mts +9 -9
- package/esm/model/model.d.mts.map +1 -1
- package/esm/model/model.mjs.map +1 -1
- package/esm/model/model.types.d.mts +7 -7
- package/esm/model/model.types.d.mts.map +1 -1
- package/esm/model/register-model.d.mts +13 -15
- package/esm/model/register-model.d.mts.map +1 -1
- package/esm/model/register-model.mjs.map +1 -1
- package/esm/model/relation-decorators.d.mts +6 -8
- package/esm/model/relation-decorators.d.mts.map +1 -1
- package/esm/operations/database.d.mts +13 -8
- package/esm/operations/database.d.mts.map +1 -1
- package/esm/operations/index.d.mts +2 -1
- package/esm/operations/index.mjs +1 -1
- package/esm/operations/migrations.d.mts +18 -14
- package/esm/operations/migrations.d.mts.map +1 -1
- package/esm/operations/migrations.mjs.map +1 -1
- package/esm/query-builder/query-builder.d.mts +2 -4
- package/esm/query-builder/query-builder.d.mts.map +1 -1
- package/esm/query-builder/query-builder.mjs +12 -11
- package/esm/query-builder/query-builder.mjs.map +1 -1
- package/esm/relations/index.d.mts +2 -1
- package/esm/relations/index.mjs +1 -1
- package/esm/relations/key-conventions.mjs +2 -1
- package/esm/relations/key-conventions.mjs.map +1 -1
- package/esm/relations/pivot-operations.d.mts +3 -5
- package/esm/relations/pivot-operations.d.mts.map +1 -1
- package/esm/relations/pivot-operations.mjs.map +1 -1
- package/esm/relations/relation-hydrator.d.mts +3 -5
- package/esm/relations/relation-hydrator.d.mts.map +1 -1
- package/esm/relations/relation-loader.d.mts +2 -4
- package/esm/relations/relation-loader.d.mts.map +1 -1
- package/esm/relations/relation-loader.mjs +4 -5
- package/esm/relations/relation-loader.mjs.map +1 -1
- package/esm/relations/types.d.mts +14 -15
- package/esm/relations/types.d.mts.map +1 -1
- package/esm/remover/database-remover.d.mts +1 -3
- package/esm/remover/database-remover.d.mts.map +1 -1
- package/esm/remover/database-remover.mjs.map +1 -1
- package/esm/restorer/database-restorer.d.mts +1 -3
- package/esm/restorer/database-restorer.d.mts.map +1 -1
- package/esm/restorer/database-restorer.mjs +2 -1
- package/esm/restorer/database-restorer.mjs.map +1 -1
- package/esm/sql-database-dirty-tracker.d.mts +1 -3
- package/esm/sql-database-dirty-tracker.d.mts.map +1 -1
- package/esm/sync/model-events.mjs +1 -1
- package/esm/sync/model-sync-operation.d.mts +2 -4
- package/esm/sync/model-sync-operation.d.mts.map +1 -1
- package/esm/sync/model-sync.d.mts +3 -4
- package/esm/sync/model-sync.d.mts.map +1 -1
- package/esm/sync/sync-context.d.mts +2 -4
- package/esm/sync/sync-context.d.mts.map +1 -1
- package/esm/sync/sync-context.mjs.map +1 -1
- package/esm/sync/sync-fanout.mjs +1 -1
- package/esm/sync/sync-fanout.mjs.map +1 -1
- package/esm/sync/sync-manager.d.mts +2 -4
- package/esm/sync/sync-manager.d.mts.map +1 -1
- package/esm/sync/sync-manager.mjs.map +1 -1
- package/esm/sync/types.d.mts +121 -64
- package/esm/sync/types.d.mts.map +1 -1
- package/esm/transactions/after-commit.d.mts +2 -2
- package/esm/transactions/after-commit.d.mts.map +1 -1
- package/esm/types.d.mts +7 -8
- package/esm/types.d.mts.map +1 -1
- package/esm/utils/connect-to-database.d.mts +7 -8
- package/esm/utils/connect-to-database.d.mts.map +1 -1
- package/esm/utils/database-writer.utils.d.mts +3 -4
- package/esm/utils/database-writer.utils.d.mts.map +1 -1
- package/esm/utils/database-writer.utils.mjs.map +1 -1
- package/esm/utils/define-model.d.mts +3 -5
- package/esm/utils/define-model.d.mts.map +1 -1
- package/esm/utils/escape-regex.d.mts +3 -4
- package/esm/utils/escape-regex.d.mts.map +1 -1
- package/esm/utils/is-valid-date-value.mjs.map +1 -1
- package/esm/utils/once-connected.d.mts +2 -4
- package/esm/utils/once-connected.d.mts.map +1 -1
- package/esm/utils/sanitize-filter.d.mts +3 -4
- package/esm/utils/sanitize-filter.d.mts.map +1 -1
- package/esm/validation/database-seal-plugins.d.mts +2 -1
- package/esm/validation/database-writer-validation-error.d.mts +1 -3
- package/esm/validation/database-writer-validation-error.d.mts.map +1 -1
- package/esm/validation/index.d.mts +3 -1
- package/esm/validation/index.mjs +1 -1
- package/esm/validation/plugins/database-rules-plugin.d.mts +2 -3
- package/esm/validation/plugins/database-rules-plugin.d.mts.map +1 -1
- package/esm/validation/plugins/embed-validator-plugin.d.mts +4 -3
- package/esm/validation/plugins/embed-validator-plugin.d.mts.map +1 -1
- package/esm/validation/types.d.mts +14 -10
- package/esm/validation/types.d.mts.map +1 -1
- package/esm/validation/validators/embed-validator.d.mts +2 -3
- package/esm/validation/validators/embed-validator.d.mts.map +1 -1
- package/esm/writer/database-writer.d.mts +1 -3
- package/esm/writer/database-writer.d.mts.map +1 -1
- package/esm/writer/database-writer.mjs +1 -1
- package/esm/writer/database-writer.mjs.map +1 -1
- package/llms-full.txt +541 -434
- package/package.json +5 -5
- package/skills/aggregate-data/SKILL.md +28 -17
- package/skills/alter-migration/SKILL.md +325 -233
- package/skills/cascade-basics/SKILL.md +19 -19
- package/skills/configure-delete-strategy/SKILL.md +11 -11
- package/skills/define-model/SKILL.md +19 -17
- package/skills/define-relations/SKILL.md +13 -18
- package/skills/index.md +3 -0
- package/skills/manage-transactions/SKILL.md +18 -12
- package/skills/paginate-results/SKILL.md +6 -8
- package/skills/perform-atomic-ops/SKILL.md +26 -21
- package/skills/query-data/SKILL.md +27 -33
- package/skills/run-cascade-cli/SKILL.md +11 -10
- package/skills/search-by-vector/SKILL.md +8 -5
- package/skills/subscribe-to-model-events/SKILL.md +17 -17
- package/skills/track-changes/SKILL.md +9 -9
- package/skills/write-migration/SKILL.md +4 -4
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ValidationResult } from "@warlock.js/seal";
|
|
2
|
-
|
|
3
2
|
//#region ../../packages/cascade/src/validation/database-writer-validation-error.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Error thrown when model validation fails during database write operations.
|
|
@@ -24,7 +23,7 @@ import { ValidationResult } from "@warlock.js/seal";
|
|
|
24
23
|
* }
|
|
25
24
|
* ```
|
|
26
25
|
*/
|
|
27
|
-
declare class DatabaseWriterValidationError extends Error {
|
|
26
|
+
export declare class DatabaseWriterValidationError extends Error {
|
|
28
27
|
/**
|
|
29
28
|
* Array of validation errors from @warlock.js/seal.
|
|
30
29
|
*
|
|
@@ -97,5 +96,4 @@ declare class DatabaseWriterValidationError extends Error {
|
|
|
97
96
|
hasFieldError(fieldPath: string): boolean;
|
|
98
97
|
}
|
|
99
98
|
//#endregion
|
|
100
|
-
export { DatabaseWriterValidationError };
|
|
101
99
|
//# sourceMappingURL=database-writer-validation-error.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-writer-validation-error.d.mts","names":[],"sources":["../../../../../../../../packages/cascade/src/validation/database-writer-validation-error.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"database-writer-validation-error.d.mts","names":[],"sources":["../../../../../../../../packages/cascade/src/validation/database-writer-validation-error.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;qBA0Ba,sCAAsC;;;;;;;;;;WAUjC,QAAQ;;;;;;;;;;;;;;EAexB,YAAmB,iBAAiB,QAAQ;;;;;;;;;;;;;;;;;;EA0CrC;;;;;;;;;;;;;;EAkEA,eAAe,oBAAoB;;;;;;;;;;;;;;EAiBnC,cAAc"}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import { DatabaseWriterValidationError } from "./database-writer-validation-error.mjs";
|
|
2
|
-
import { BaseQueryRuleOptions, BaseUniqueRuleOptions, ExistsRuleOptions, UniqueRuleOptions } from "./types.mjs";
|
|
2
|
+
import { BaseQueryRuleOptions, BaseUniqueRuleOptions, ExistsRuleOptions, UniqueRuleOptions } from "./types.mjs";
|
|
3
|
+
import "./database-seal-plugins.mjs";
|
|
4
|
+
export { type BaseQueryRuleOptions, type BaseUniqueRuleOptions, DatabaseWriterValidationError, type ExistsRuleOptions, type UniqueRuleOptions };
|
package/esm/validation/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ChildModel } from "../../model/model.types.mjs";
|
|
2
2
|
import { Model } from "../../model/model.mjs";
|
|
3
3
|
import { ExistsRuleOptions, UniqueRuleOptions } from "../types.mjs";
|
|
4
|
+
import "@warlock.js/seal";
|
|
4
5
|
//#region ../../packages/cascade/src/validation/plugins/database-rules-plugin.d.ts
|
|
5
6
|
declare module "@warlock.js/seal" {
|
|
6
7
|
interface ScalarValidator {
|
|
@@ -30,7 +31,5 @@ declare module "@warlock.js/seal" {
|
|
|
30
31
|
}): this;
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
-
* Cascade's database-rules plugin for Seal.
|
|
35
|
-
*/
|
|
34
|
+
//#endregion
|
|
36
35
|
//# sourceMappingURL=database-rules-plugin.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-rules-plugin.d.mts","names":[],"sources":["../../../../../../../../../packages/cascade/src/validation/plugins/database-rules-plugin.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"database-rules-plugin.d.mts","names":[],"sources":["../../../../../../../../../packages/cascade/src/validation/plugins/database-rules-plugin.ts"],"mappings":";;;;;;YAmBY;;IAER,OACE,OAAO,WAAW,iBAClB,cAAc,QAAQ;MACpB;;;IAKJ,OACE,OAAO,WAAW,iBAClB,cAAc,QAAQ;MACpB;;;YAKI;IACR,OACE,OAAO,WAAW,iBAClB,cAAc,QAAQ;MACpB;;IAGJ,OACE,OAAO,WAAW,iBAClB,cAAc,QAAQ;MACpB;;;YAKI;IACR,OACE,OAAO,WAAW,iBAClB,cAAc,QAAQ;MACpB;;IAGJ,OACE,OAAO,WAAW,iBAClB,cAAc,QAAQ;MACpB"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ChildModel } from "../../model/model.types.mjs";
|
|
2
|
+
import "../../model/model.mjs";
|
|
2
3
|
import { EmbedModelValidator } from "../validators/embed-validator.mjs";
|
|
4
|
+
import "@warlock.js/seal";
|
|
3
5
|
//#region ../../packages/cascade/src/validation/plugins/embed-validator-plugin.d.ts
|
|
4
6
|
type EmbedOptions = {
|
|
5
7
|
errorMessage?: string;
|
|
@@ -11,7 +13,6 @@ declare module "@warlock.js/seal" {
|
|
|
11
13
|
embedMany(model?: ChildModel<any> | string, options?: EmbedOptions): EmbedModelValidator;
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
*/
|
|
16
|
+
//#endregion
|
|
17
|
+
export {};
|
|
17
18
|
//# sourceMappingURL=embed-validator-plugin.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embed-validator-plugin.d.mts","names":[],"sources":["../../../../../../../../../packages/cascade/src/validation/plugins/embed-validator-plugin.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"embed-validator-plugin.d.mts","names":[],"sources":["../../../../../../../../../packages/cascade/src/validation/plugins/embed-validator-plugin.ts"],"mappings":";;;;;KAWK;EACH;EACA;;;YAIU;IACR,MAAM,QAAQ,0BAA0B,UAAU,eAAe;IACjE,UAAU,QAAQ,0BAA0B,UAAU,eAAe"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ChildModel } from "../model/model.types.mjs";
|
|
2
2
|
import { Model } from "../model/model.mjs";
|
|
3
3
|
import { QueryBuilderContract } from "../contracts/query-builder.contract.mjs";
|
|
4
|
-
|
|
5
4
|
//#region ../../packages/cascade/src/validation/types.d.ts
|
|
6
5
|
/**
|
|
7
6
|
* Base options shared by all database-backed validation rules.
|
|
@@ -9,19 +8,22 @@ import { QueryBuilderContract } from "../contracts/query-builder.contract.mjs";
|
|
|
9
8
|
* Identifies the model to query, the column to filter on, and an optional
|
|
10
9
|
* hook to customise the query before the existence/uniqueness check runs.
|
|
11
10
|
*/
|
|
12
|
-
type BaseQueryRuleOptions = {
|
|
13
|
-
/** The Model to query against */
|
|
11
|
+
export type BaseQueryRuleOptions = {
|
|
12
|
+
/** The Model to query against */
|
|
13
|
+
Model: ChildModel<Model> | string;
|
|
14
|
+
/** Callback to customise the query before it executes */
|
|
14
15
|
query?: (options: {
|
|
15
16
|
query: QueryBuilderContract;
|
|
16
17
|
value: any;
|
|
17
18
|
allValues: any;
|
|
18
|
-
}) => void | Promise<void>;
|
|
19
|
+
}) => void | Promise<void>;
|
|
20
|
+
/** The column to filter by (defaults to the validated key) */
|
|
19
21
|
column?: string;
|
|
20
22
|
};
|
|
21
23
|
/**
|
|
22
24
|
* Base options for uniqueness validation rules.
|
|
23
25
|
*/
|
|
24
|
-
type BaseUniqueRuleOptions = BaseQueryRuleOptions;
|
|
26
|
+
export type BaseUniqueRuleOptions = BaseQueryRuleOptions;
|
|
25
27
|
/**
|
|
26
28
|
* Options for the `unique` validation rule.
|
|
27
29
|
*
|
|
@@ -29,15 +31,17 @@ type BaseUniqueRuleOptions = BaseQueryRuleOptions;
|
|
|
29
31
|
* input's value", or an explicit `exceptColumnName` / `exceptValue` pair
|
|
30
32
|
* used by request-aware wrappers (e.g. core's `uniqueExceptCurrentUser`).
|
|
31
33
|
*/
|
|
32
|
-
type UniqueRuleOptions = BaseUniqueRuleOptions & {
|
|
33
|
-
/** Sibling input key to exclude from the uniqueness check */
|
|
34
|
-
|
|
34
|
+
export type UniqueRuleOptions = BaseUniqueRuleOptions & {
|
|
35
|
+
/** Sibling input key to exclude from the uniqueness check */
|
|
36
|
+
except?: string;
|
|
37
|
+
/** Column name to exclude (paired with `exceptValue`) */
|
|
38
|
+
exceptColumnName?: string;
|
|
39
|
+
/** Value to exclude under `exceptColumnName` */
|
|
35
40
|
exceptValue?: any;
|
|
36
41
|
};
|
|
37
42
|
/**
|
|
38
43
|
* Options for the `exists` validation rule.
|
|
39
44
|
*/
|
|
40
|
-
type ExistsRuleOptions = BaseQueryRuleOptions;
|
|
45
|
+
export type ExistsRuleOptions = BaseQueryRuleOptions;
|
|
41
46
|
//#endregion
|
|
42
|
-
export { BaseQueryRuleOptions, BaseUniqueRuleOptions, ExistsRuleOptions, UniqueRuleOptions };
|
|
43
47
|
//# sourceMappingURL=types.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../../packages/cascade/src/validation/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../../packages/cascade/src/validation/types.ts"],"mappings":";;;;;;;;;;YASY;;EAEV,OAAO,WAAW;;EAElB,SAAS;IACP,OAAO;IACP;IACA;eACW;;EAEb;;;;;YAMU,wBAAwB;;;;;;;;YASxB,oBAAoB;;EAE9B;;EAEA;;EAEA;;;;;YAMU,oBAAoB"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ChildModel } from "../../model/model.types.mjs";
|
|
2
|
+
import "../../model/model.mjs";
|
|
2
3
|
import { BaseValidator } from "@warlock.js/seal";
|
|
3
|
-
|
|
4
4
|
//#region ../../packages/cascade/src/validation/validators/embed-validator.d.ts
|
|
5
|
-
declare class EmbedModelValidator extends BaseValidator {
|
|
5
|
+
export declare class EmbedModelValidator extends BaseValidator {
|
|
6
6
|
/**
|
|
7
7
|
* Check if value is a string type
|
|
8
8
|
*/
|
|
@@ -21,5 +21,4 @@ declare class EmbedModelValidator extends BaseValidator {
|
|
|
21
21
|
embed(embed?: string | string[]): EmbedModelValidator;
|
|
22
22
|
}
|
|
23
23
|
//#endregion
|
|
24
|
-
export { EmbedModelValidator };
|
|
25
24
|
//# sourceMappingURL=embed-validator.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embed-validator.d.mts","names":[],"sources":["../../../../../../../../../packages/cascade/src/validation/validators/embed-validator.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"embed-validator.d.mts","names":[],"sources":["../../../../../../../../../packages/cascade/src/validation/validators/embed-validator.ts"],"mappings":";;;;qBAMa,4BAA4B;;;;EAIhC,YAAY;;;;EAYZ,MAAM,OAAO,0BAA0B,wBAAwB;;;;EAa/D,OAAO,OAAO,0BAA0B,wBAAwB;;;;EAiBhE,MAAM,4BAA4B"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Model } from "../model/model.mjs";
|
|
2
2
|
import { WriterContract, WriterOptions, WriterResult } from "../contracts/database-writer.contract.mjs";
|
|
3
|
-
|
|
4
3
|
//#region ../../packages/cascade/src/writer/database-writer.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* Database writer service that orchestrates model persistence.
|
|
@@ -36,7 +35,7 @@ import { WriterContract, WriterOptions, WriterResult } from "../contracts/databa
|
|
|
36
35
|
* await writer.save({ skipEvents: true });
|
|
37
36
|
* ```
|
|
38
37
|
*/
|
|
39
|
-
declare class DatabaseWriter implements WriterContract {
|
|
38
|
+
export declare class DatabaseWriter implements WriterContract {
|
|
40
39
|
/** The model instance being persisted */
|
|
41
40
|
private readonly model;
|
|
42
41
|
/** Dirty columns an update validation dropped (strip mode); never written. */
|
|
@@ -180,5 +179,4 @@ declare class DatabaseWriter implements WriterContract {
|
|
|
180
179
|
private triggerSync;
|
|
181
180
|
}
|
|
182
181
|
//#endregion
|
|
183
|
-
export { DatabaseWriter };
|
|
184
182
|
//# sourceMappingURL=database-writer.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-writer.d.mts","names":[],"sources":["../../../../../../../../packages/cascade/src/writer/database-writer.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"database-writer.d.mts","names":[],"sources":["../../../../../../../../packages/cascade/src/writer/database-writer.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAyDa,0BAA0B;;mBAEpB;;mBAGA;;mBAGA;;mBAGA;;mBAGA;;mBAGA;;mBAGA;;mBAGA;;mBAGA;;;;;;;;;;;;;EAcjB,YAAmB,OAAO;;;;;;;;EAkBb,KAAK,UAAS,gBAAqB,QAAQ;;;;;;;;;;;UAmE1C;;;;;;;EAyHD,oBAAoB;;;;;;;;UAYnB;;;;;;;;;;;;EAmCD,iBAAiB,UAAS,gBAAqB,QAAQ;;;;;;;;UA4CtD;;;;;;UAgDN;;;;;;;;;;;;UAsDA;;;;;;EASK,kBAAkB;;;;;;;;;;;;;;;;;;;;;;UAmCvB;;;;;;;;;;UAmFA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-writer.mjs","names":[],"sources":["../../../../../../../../packages/cascade/src/writer/database-writer.ts"],"sourcesContent":["import { when } from \"@mongez/reinforcements\";\nimport { getSealConfig, v, type ObjectValidator } from \"@warlock.js/seal\";\nimport type {\n DriverContract,\n InsertResult,\n UpdateOperations,\n UpdateResult,\n} from \"../contracts/database-driver.contract\";\nimport type {\n WriterContract,\n WriterOptions,\n WriterResult,\n} from \"../contracts/database-writer.contract\";\nimport { mergeDriverFields } from \"../model/methods/accessor-methods\";\nimport { generateModelId } from \"../model/generate-model-id\";\nimport type { ChildModel, Model } from \"../model/model\";\nimport { enqueueSyncFanout } from \"../sync/sync-fanout\";\nimport { triggerModelEvent } from \"../sync/model-events\";\nimport type { StrictMode } from \"../types\";\nimport { DatabaseWriterValidationError } from \"../validation\";\nimport { afterCommit } from \"../transactions/after-commit\";\nimport type { DataSource } from \"./../data-source/data-source\";\n\n/**\n * Database writer service that orchestrates model persistence.\n *\n * Handles the complete save pipeline:\n * 1. Check for changes (skip if no changes and not new)\n * 2. Emit `saving` event (for data enrichment)\n * 3. Emit `validating` event\n * 4. Validate and cast data via @warlock.js/seal schema\n * 5. Emit `validated` event\n * 6. Generate ID (for new NoSQL records)\n * 7. Emit `creating`/`updating` events\n * 8. Execute insert or update via driver\n * 9. Merge returned data into model\n * 10. Reset dirty tracker and update `isNew` flag\n * 11. Emit `saved` and `created`/`updated` events\n *\n * @example\n * ```typescript\n * const user = new User({ name: \"Alice\", email: \"alice@example.com\" });\n * const writer = new DatabaseWriter(user);\n * await writer.save();\n *\n * console.log(user.get(\"id\")); // 1 (auto-generated)\n * console.log(user.get(\"_id\")); // ObjectId(\"...\")\n *\n * // Update existing record\n * user.set(\"name\", \"Alice Smith\");\n * await writer.save();\n * // Only updates the \"name\" field (partial update)\n *\n * // Silent save (no events)\n * await writer.save({ skipEvents: true });\n * ```\n */\nexport class DatabaseWriter implements WriterContract {\n /** The model instance being persisted */\n private readonly model: Model;\n\n /** Dirty columns an update validation dropped (strip mode); never written. */\n private readonly unvalidatedColumns = new Set<string>();\n\n /** Model constructor reference */\n private readonly ctor: ChildModel<Model>;\n\n /** Data source containing driver and ID generator */\n private readonly dataSource: DataSource;\n\n /** Database driver for executing queries */\n private readonly driver: DriverContract;\n\n /** Table/collection name */\n private readonly table: string;\n\n /** Primary key field name */\n private readonly primaryKey: string;\n\n /** Validation schema (if defined) */\n private readonly schema?: ObjectValidator;\n\n /** Strict mode configuration */\n private readonly strictMode: StrictMode;\n\n /**\n * Create a new writer instance for a model.\n *\n * @param model - The model instance to persist\n *\n * @example\n * ```typescript\n * const user = new User({ name: \"Alice\" });\n * const writer = new DatabaseWriter(user);\n * await writer.save();\n * ```\n */\n public constructor(model: Model) {\n this.model = model;\n this.ctor = model.constructor as ChildModel<Model>;\n this.dataSource = this.ctor.getDataSource();\n this.driver = this.dataSource.driver;\n this.table = this.ctor.table;\n this.primaryKey = this.ctor.primaryKey;\n this.schema = this.ctor.schema;\n this.strictMode = this.ctor.strictMode;\n }\n\n /**\n * Save the model instance to the database.\n *\n * @param options - Save options\n * @returns Result with success status, document, and metadata\n * @throws {ValidationError} If validation fails\n */\n public async save(options: WriterOptions = {}): Promise<WriterResult> {\n const isInsert = this.model.isNew;\n\n // 1. Check if model has changes (skip if no changes and not new)\n if (!isInsert && !this.model.hasChanges()) {\n return {\n success: true,\n document: this.model.data,\n isNew: false,\n modifiedCount: 0,\n };\n }\n\n // 2. Emit saving event (before validation for data enrichment)\n if (!options.skipEvents) {\n await this.model.emitEvent(\"saving\", {\n isInsert,\n options,\n mode: isInsert ? \"insert\" : \"update\",\n });\n }\n\n // 3-4. Execute insert or update (validation happens inside each path)\n let result: InsertResult | UpdateResult;\n\n if (isInsert) {\n result = await this.performInsert(options);\n } else {\n await this.validateAndCast(false, options);\n result = await this.performUpdate(options);\n }\n\n // 5. Reset dirty tracker and update isNew flag\n const changedFields = isInsert ? [] : this.model.getDirtyColumns();\n this.model.dirtyTracker.reset();\n this.model.isNew = false;\n\n // 6. Emit post-save events\n if (!options.skipEvents) {\n await this.model.emitEvent(\"saved\");\n await this.model.emitEvent(isInsert ? \"created\" : \"updated\");\n }\n\n // 7. Trigger sync operations after COMMIT (inside a transaction they are\n // queued, so a rolled-back write never fans out). Failures are logged.\n if (!options.skipSync && !isInsert) {\n afterCommit(() => this.triggerSync(changedFields));\n }\n\n return {\n success: true,\n document: this.model.data,\n isNew: isInsert,\n modifiedCount: isInsert\n ? undefined\n : (result as UpdateResult).modifiedCount,\n };\n }\n\n /**\n * Validate and cast model data using the schema.\n *\n * Updates the model's data in-place with validated/casted values.\n *\n * @param isInsert - Whether this is an insert operation\n * @param options - Save options\n * @throws {ValidationError} If validation fails\n * @private\n */\n private async validateAndCast(\n isInsert: boolean,\n options: WriterOptions,\n ): Promise<void> {\n // Emit validating event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"validating\", {\n isInsert,\n options,\n mode: isInsert ? \"insert\" : \"update\",\n });\n }\n\n // Skip validation if requested or no schema defined\n if (options.skipValidation || !this.schema) {\n return;\n }\n\n // Whitelist the framework-managed system columns so a model carrying them\n // (id / _id / timestamps / soft-delete deletedAt) validates cleanly instead\n // of being stripped (strictMode \"strip\") or rejected (strictMode \"fail\").\n // `when(...)` adds each timestamp/soft-delete column only when configured\n // (truthy) — the lazy factory means a disabled column (`false`) never even\n // builds a bogus schema key.\n //\n // The whitelist must apply to BOTH insert and update: on insert a caller may\n // supply a backdated createdAt (e.g. data migrations / imports), and without\n // the whitelist strictMode \"strip\" silently drops it before it reaches the\n // writer, while \"fail\" rejects the whole insert.\n const systemColumns = {\n id: v.scalar().optional(),\n _id: v.any().optional(),\n ...when(this.ctor.createdAtColumn, () => ({\n [this.ctor.createdAtColumn as string]: v.date().optional(),\n })),\n ...when(this.ctor.updatedAtColumn, () => ({\n [this.ctor.updatedAtColumn as string]: v.date().optional(),\n })),\n ...when(this.ctor.deletedAtColumn, () => ({\n [this.ctor.deletedAtColumn as string]: v.date().optional(),\n })),\n };\n\n // Clone full schema for insert, partial (dirty keys only) for updates.\n //\n // An update validates ONLY the dirty columns and merges back only those\n // keys: columns the schema does not list (migration-added, withCount, ...)\n // are never touched, so they can no longer be turned into `$unset`.\n const dataToValidate: Record<string, unknown> = isInsert\n ? this.model.data\n : Object.fromEntries(\n this.model\n .getDirtyColumns()\n .filter(column => column in this.model.data)\n .map(column => [column, this.model.data[column]]),\n );\n\n const validationSchema = isInsert\n ? this.schema.clone().extend(systemColumns)\n : this.schema.clone(Object.keys(dataToValidate)).extend(systemColumns);\n\n // Apply strict mode\n if (this.strictMode === \"strip\") {\n validationSchema.stripUnknown();\n } else if (this.strictMode === \"fail\") {\n validationSchema.allowUnknown(false);\n } else if (this.strictMode === \"allow\") {\n validationSchema.allowUnknown(true);\n }\n\n // Run validation\n const result = await v.validate(validationSchema, dataToValidate, {\n context: {\n model: this.model,\n },\n ...getSealConfig(),\n });\n\n if (!result.isValid) {\n const error = new DatabaseWriterValidationError(\n `[${this.model.constructor.name} Model] ${isInsert ? \"Insert\" : \"Update\"} Validation failed`,\n result.errors,\n );\n if (!options.skipEvents) {\n await this.model.emitEvent(\"validated\", { result, error });\n }\n throw error;\n }\n\n // Update model data with validated/casted data\n if (isInsert) {\n this.model.replaceData(result.data);\n } else {\n const validated = result.data as Record<string, unknown>;\n\n for (const column of Object.keys(dataToValidate)) {\n if (column in validated) {\n this.model.set(column, validated[column]);\n } else if (dataToValidate[column] === null && column in validationSchema.schema) {\n // An explicit null on a KNOWN column that passed validation: an\n // optional, non-nullable leaf coalesces null to \"absent\" in seal, so\n // it is missing from the result. It is a deliberate \"clear this\n // column\", not an unknown one to strip (Real-Estate #18). A required\n // column never gets here: its rule rejects the null and validation throws.\n this.model.set(column, null);\n } else {\n // Dropped by strictMode \"strip\": never write an unvalidated column.\n this.unvalidatedColumns.add(column);\n }\n }\n }\n\n // Emit validated event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"validated\", { result });\n }\n }\n\n /**\n * Validate, cast and id-generate a not-yet-persisted model for `upsert()`.\n *\n * Runs the insert-mode pipeline (schema, casts, strictMode, Mongo `id`) but\n * does not write; the caller sends the resulting data through the driver.\n */\n public async prepareForUpsert(): Promise<void> {\n await this.validateAndCast(true, {});\n await this.generateNextId();\n }\n\n /**\n * Perform an insert operation.\n *\n * @param options - Save options\n * @returns Insert result\n * @private\n */\n private async performInsert(options: WriterOptions): Promise<InsertResult> {\n const dataToInsert = await this.prepareForInsert(options);\n\n // INSERT: use full validated data\n let result: InsertResult;\n\n try {\n result = await this.driver.insert(this.table, dataToInsert);\n } catch (error) {\n throw this.mapUniqueViolation(error, true);\n }\n\n // Merge returned data (e.g., generated _id, timestamps)\n // Note: We use merge here because the result might not include all fields\n // (e.g., our generated 'id' field), and we don't want to lose them\n mergeDriverFields(this.model, result.document as Record<string, unknown>);\n\n // Reset dirty tracker immediately after merge to prevent\n // database-generated fields (like _id) from being marked as dirty\n this.model.dirtyTracker.reset();\n\n return result;\n }\n\n /**\n * Run the insert pipeline (validation, casting, id generation, timestamps,\n * `validating`/`validated`/`creating` hooks) WITHOUT writing.\n *\n * `save()` uses this for its insert path, so bulk inserts and single inserts\n * cannot drift. The caller is responsible for sending the returned document\n * to the driver.\n *\n * @param options - Save options (e.g. `skipEvents`, `skipValidation`)\n * @returns The prepared document to insert\n */\n public async prepareForInsert(options: WriterOptions = {}): Promise<Record<string, unknown>> {\n // Validate and cast data\n await this.validateAndCast(true, options);\n\n // Generate ID if needed (NoSQL only)\n await this.generateNextId();\n\n // Get data to insert (already validated and casted)\n const dataToInsert = this.model.data;\n\n // Add createdAt and updatedAt to the data (using resolved column names)\n // The column names are already resolved through the hierarchy:\n // Model static property > Database config > Driver defaults > undefined\n //\n // Only stamp createdAt when the caller did NOT supply one, so a backdated\n // value (e.g. data migrations / imports) is honored instead of overwritten.\n // This mirrors the upsert path's guard. updatedAt is always stamped to\n // reflect the moment the record is persisted.\n const createdAtColumn = this.ctor.createdAtColumn;\n\n if (createdAtColumn && dataToInsert[createdAtColumn] == null) {\n dataToInsert[createdAtColumn] = new Date();\n }\n\n const updatedAtColumn = this.ctor.updatedAtColumn;\n if (updatedAtColumn) {\n dataToInsert[updatedAtColumn] = new Date();\n }\n\n // Emit creating event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"creating\");\n }\n\n return dataToInsert;\n }\n\n /**\n * Perform an update operation.\n *\n * @param options - Save options\n * @returns Update result\n * @private\n */\n private async performUpdate(options: WriterOptions): Promise<UpdateResult> {\n // Emit updating event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"updating\");\n }\n\n // Update the updatedAt timestamp (using resolved column name)\n const updatedAtColumn = this.ctor.updatedAtColumn;\n if (updatedAtColumn) {\n this.model.set(updatedAtColumn, new Date());\n }\n\n if (options.replace) {\n const document = await this.driver.replace(\n this.table,\n this.buildPrimaryKeyFilter(),\n this.model.data,\n );\n\n if (document) {\n this.model.replaceData(document as Record<string, unknown>);\n }\n\n return { modifiedCount: document ? 1 : 0 };\n }\n\n // Build operations from dirty tracker\n const operations = this.buildUpdateOperations();\n\n // Nothing left to write once identity columns are excluded — don't hand the\n // driver an empty update document (MongoDB rejects one).\n if (Object.keys(operations).length === 0) {\n return { modifiedCount: 0 };\n }\n\n // Execute update with operations\n try {\n return await this.driver.update(\n this.table,\n this.buildPrimaryKeyFilter(),\n operations,\n );\n } catch (error) {\n throw this.mapUniqueViolation(error, false);\n }\n }\n\n /**\n * Map a driver unique-constraint violation (Postgres 23505, MongoDB 11000)\n * to a field validation error, so a lost `unique` race never surfaces as a\n * 500. Any other error is returned untouched.\n */\n private mapUniqueViolation(error: unknown, isInsert: boolean): unknown {\n const err = error as\n | {\n code?: string | number;\n detail?: string;\n keyPattern?: Record<string, unknown>;\n message?: string;\n }\n | undefined;\n\n if (err?.code !== \"23505\" && err?.code !== 11000 && err?.code !== \"11000\") {\n return error;\n }\n\n let field: string | undefined;\n\n if (err.keyPattern) {\n field = Object.keys(err.keyPattern)[0];\n } else {\n // Postgres: \"Key (email)=(x) already exists.\"\n field = /Key \\(([^)]+)\\)=/.exec(err.detail ?? \"\")?.[1]?.split(\",\")[0]?.trim();\n }\n\n if (!field) {\n // Mongo: \"index: email_1 dup key\"\n field = /index: (\\S+?)_-?1\\b/.exec(err.message ?? \"\")?.[1];\n }\n\n return new DatabaseWriterValidationError(\n `[${this.model.constructor.name} Model] ${isInsert ? \"Insert\" : \"Update\"} Validation failed`,\n [\n {\n type: \"unique\",\n error: `The ${field ?? \"value\"} must be unique`,\n input: field ?? \"id\",\n },\n ],\n );\n }\n\n /**\n * Build the filter that pins a write to the row this model was loaded from.\n *\n * It reads `model.trustedPrimaryKey` — the value captured when the instance\n * became persisted — NOT the current value in `model.data`. The current value\n * is reachable by mass assignment (`model.merge(req.body)`), so deriving the\n * filter from it let a request body redirect the UPDATE to another document.\n *\n * @returns Filter matching the originally loaded record\n * @private\n */\n private buildPrimaryKeyFilter(): Record<string, unknown> {\n return { [this.primaryKey]: this.model.trustedPrimaryKey };\n }\n\n /**\n * Generate ID for the model if auto-generation is enabled.\n *\n * @private\n */\n public async generateNextId(): Promise<void> {\n if (!this.ctor.autoGenerateId || this.model.get(\"id\")) {\n return;\n }\n\n const id = await generateModelId(this.ctor);\n\n if (id === undefined) {\n return;\n }\n\n this.model.set(\"id\", id);\n }\n\n /**\n * Build update operations from the model's dirty tracker.\n *\n * Handles both modified fields ($set) and removed fields ($unset).\n *\n * @returns Update operations for the driver\n * @private\n *\n * @example\n * ```typescript\n * // Model with changes\n * user.set(\"name\", \"Alice\");\n * user.unset(\"tempField\");\n *\n * const operations = this.buildUpdateOperations();\n * // {\n * // $set: { name: \"Alice\" },\n * // $unset: { tempField: 1 }\n * // }\n * ```\n */\n private buildUpdateOperations(): UpdateOperations {\n const operations: UpdateOperations = {};\n\n // Identity columns are never written by an update. The filter pins the row\n // by its captured primary key, so a dirty `id`/`_id` can only come from\n // mass assignment or an explicit set() — either way, rewriting the key of\n // an existing row corrupts identity (and `_id` is immutable in MongoDB).\n // Changing a primary key is a deliberate operation: use the atomic/raw APIs.\n const identityColumns = new Set([this.primaryKey, \"id\", \"_id\"]);\n\n // Top-level fields assigned/unset as a whole (`set(\"nested\", obj)`,\n // `unset(\"nested\")`) are written as ONE value. Per-leaf operations can only\n // report the leaves that changed or vanished, so a nested object replaced with\n // one that has fewer keys would keep its removed keys' empty parents in the\n // stored document. Dotted-path sets (`set(\"nested.a\", v)`) and merges are\n // partial by intent and keep going through the per-leaf path below.\n const replacedColumns = this.model.dirtyTracker\n .getReplacedColumns()\n .filter(column => !identityColumns.has(column) && !this.unvalidatedColumns.has(column));\n\n // A leaf under a whole-written field must not be sent too: MongoDB rejects\n // an update touching both `nested` and `nested.a`.\n const isUnderReplaced = (column: string): boolean =>\n replacedColumns.some(root => column === root || column.startsWith(`${root}.`));\n\n // Get dirty columns (modified fields)\n const dirtyColumns = this.model\n .getDirtyColumns()\n .filter(\n column =>\n !identityColumns.has(column) &&\n !this.unvalidatedColumns.has(column) &&\n !isUnderReplaced(column),\n );\n\n if (dirtyColumns.length > 0) {\n operations.$set = {};\n for (const column of dirtyColumns) {\n const value = this.model.get(column);\n if (value === undefined) continue;\n\n operations.$set[column] = this.model.get(column);\n }\n }\n\n // Get removed columns, collapsed to the highest path that is actually gone so\n // a parent emptied by an unset is removed too instead of left behind as `{}`.\n const removedColumns = this.model.dirtyTracker\n .getRemovedPaths()\n .filter(column => !identityColumns.has(column) && !isUnderReplaced(column));\n\n if (removedColumns.length > 0) {\n operations.$unset = {};\n for (const column of removedColumns) {\n operations.$unset[column] = 1;\n }\n }\n\n for (const column of replacedColumns) {\n const value = this.model.get(column);\n\n if (value === undefined) {\n operations.$unset ??= {};\n operations.$unset[column] = 1;\n continue;\n }\n\n operations.$set ??= {};\n operations.$set[column] = value;\n }\n\n return operations;\n }\n\n /**\n * Trigger sync operations after successful save.\n *\n * Emits a model.updated event that ModelSyncOperation listens to.\n * The sync is handled by registered sync operations, not directly here.\n *\n * @param changedFields - Fields that were changed (for filtering)\n * @private\n */\n private triggerSync(changedFields: string[]): void {\n enqueueSyncFanout({\n sourceModel: this.ctor.name,\n operation: \"update\",\n execute: () => triggerModelEvent(this.ctor, \"updated\", this.model, changedFields),\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,IAAa,iBAAb,MAAsD;;CAEpD,AAAiB;;CAGjB,AAAiB,qCAAqB,IAAI,IAAY;;CAGtD,AAAiB;;CAGjB,AAAiB;;CAGjB,AAAiB;;CAGjB,AAAiB;;CAGjB,AAAiB;;CAGjB,AAAiB;;CAGjB,AAAiB;;;;;;;;;;;;;CAcjB,AAAO,YAAY,OAAc;EAC/B,KAAK,QAAQ;EACb,KAAK,OAAO,MAAM;EAClB,KAAK,aAAa,KAAK,KAAK,cAAc;EAC1C,KAAK,SAAS,KAAK,WAAW;EAC9B,KAAK,QAAQ,KAAK,KAAK;EACvB,KAAK,aAAa,KAAK,KAAK;EAC5B,KAAK,SAAS,KAAK,KAAK;EACxB,KAAK,aAAa,KAAK,KAAK;CAC9B;;;;;;;;CASA,MAAa,KAAK,UAAyB,CAAC,GAA0B;EACpE,MAAM,WAAW,KAAK,MAAM;EAG5B,IAAI,CAAC,YAAY,CAAC,KAAK,MAAM,WAAW,GACtC,OAAO;GACL,SAAS;GACT,UAAU,KAAK,MAAM;GACrB,OAAO;GACP,eAAe;EACjB;EAIF,IAAI,CAAC,QAAQ,YACX,MAAM,KAAK,MAAM,UAAU,UAAU;GACnC;GACA;GACA,MAAM,WAAW,WAAW;EAC9B,CAAC;EAIH,IAAI;EAEJ,IAAI,UACF,SAAS,MAAM,KAAK,cAAc,OAAO;OACpC;GACL,MAAM,KAAK,gBAAgB,OAAO,OAAO;GACzC,SAAS,MAAM,KAAK,cAAc,OAAO;EAC3C;EAGA,MAAM,gBAAgB,WAAW,CAAC,IAAI,KAAK,MAAM,gBAAgB;EACjE,KAAK,MAAM,aAAa,MAAM;EAC9B,KAAK,MAAM,QAAQ;EAGnB,IAAI,CAAC,QAAQ,YAAY;GACvB,MAAM,KAAK,MAAM,UAAU,OAAO;GAClC,MAAM,KAAK,MAAM,UAAU,WAAW,YAAY,SAAS;EAC7D;EAIA,IAAI,CAAC,QAAQ,YAAY,CAAC,UACxB,kBAAkB,KAAK,YAAY,aAAa,CAAC;EAGnD,OAAO;GACL,SAAS;GACT,UAAU,KAAK,MAAM;GACrB,OAAO;GACP,eAAe,WACX,SACC,OAAwB;EAC/B;CACF;;;;;;;;;;;CAYA,MAAc,gBACZ,UACA,SACe;EAEf,IAAI,CAAC,QAAQ,YACX,MAAM,KAAK,MAAM,UAAU,cAAc;GACvC;GACA;GACA,MAAM,WAAW,WAAW;EAC9B,CAAC;EAIH,IAAI,QAAQ,kBAAkB,CAAC,KAAK,QAClC;EAcF,MAAM,gBAAgB;GACpB,IAAI,EAAE,OAAO,CAAC,CAAC,SAAS;GACxB,KAAK,EAAE,IAAI,CAAC,CAAC,SAAS;GACtB,GAAG,KAAK,KAAK,KAAK,wBAAwB,GACvC,KAAK,KAAK,kBAA4B,EAAE,KAAK,CAAC,CAAC,SAAS,EAC3D,EAAE;GACF,GAAG,KAAK,KAAK,KAAK,wBAAwB,GACvC,KAAK,KAAK,kBAA4B,EAAE,KAAK,CAAC,CAAC,SAAS,EAC3D,EAAE;GACF,GAAG,KAAK,KAAK,KAAK,wBAAwB,GACvC,KAAK,KAAK,kBAA4B,EAAE,KAAK,CAAC,CAAC,SAAS,EAC3D,EAAE;EACJ;EAOA,MAAM,iBAA0C,WAC5C,KAAK,MAAM,OACX,OAAO,YACL,KAAK,MACF,gBAAgB,CAAC,CACjB,QAAO,WAAU,UAAU,KAAK,MAAM,IAAI,CAAC,CAC3C,KAAI,WAAU,CAAC,QAAQ,KAAK,MAAM,KAAK,OAAO,CAAC,CACpD;EAEJ,MAAM,mBAAmB,WACrB,KAAK,OAAO,MAAM,CAAC,CAAC,OAAO,aAAa,IACxC,KAAK,OAAO,MAAM,OAAO,KAAK,cAAc,CAAC,CAAC,CAAC,OAAO,aAAa;EAGvE,IAAI,KAAK,eAAe,SACtB,iBAAiB,aAAa;OACzB,IAAI,KAAK,eAAe,QAC7B,iBAAiB,aAAa,KAAK;OAC9B,IAAI,KAAK,eAAe,SAC7B,iBAAiB,aAAa,IAAI;EAIpC,MAAM,SAAS,MAAM,EAAE,SAAS,kBAAkB,gBAAgB;GAChE,SAAS,EACP,OAAO,KAAK,MACd;GACA,GAAG,cAAc;EACnB,CAAC;EAED,IAAI,CAAC,OAAO,SAAS;GACnB,MAAM,QAAQ,IAAI,8BAChB,IAAI,KAAK,MAAM,YAAY,KAAK,UAAU,WAAW,WAAW,SAAS,qBACzE,OAAO,MACT;GACA,IAAI,CAAC,QAAQ,YACX,MAAM,KAAK,MAAM,UAAU,aAAa;IAAE;IAAQ;GAAM,CAAC;GAE3D,MAAM;EACR;EAGA,IAAI,UACF,KAAK,MAAM,YAAY,OAAO,IAAI;OAC7B;GACL,MAAM,YAAY,OAAO;GAEzB,KAAK,MAAM,UAAU,OAAO,KAAK,cAAc,GAC7C,IAAI,UAAU,WACZ,KAAK,MAAM,IAAI,QAAQ,UAAU,OAAO;QACnC,IAAI,eAAe,YAAY,QAAQ,UAAU,iBAAiB,QAMvE,KAAK,MAAM,IAAI,QAAQ,IAAI;QAG3B,KAAK,mBAAmB,IAAI,MAAM;EAGxC;EAGA,IAAI,CAAC,QAAQ,YACX,MAAM,KAAK,MAAM,UAAU,aAAa,EAAE,OAAO,CAAC;CAEtD;;;;;;;CAQA,MAAa,mBAAkC;EAC7C,MAAM,KAAK,gBAAgB,MAAM,CAAC,CAAC;EACnC,MAAM,KAAK,eAAe;CAC5B;;;;;;;;CASA,MAAc,cAAc,SAA+C;EACzE,MAAM,eAAe,MAAM,KAAK,iBAAiB,OAAO;EAGxD,IAAI;EAEJ,IAAI;GACF,SAAS,MAAM,KAAK,OAAO,OAAO,KAAK,OAAO,YAAY;EAC5D,SAAS,OAAO;GACd,MAAM,KAAK,mBAAmB,OAAO,IAAI;EAC3C;EAKA,kBAAkB,KAAK,OAAO,OAAO,QAAmC;EAIxE,KAAK,MAAM,aAAa,MAAM;EAE9B,OAAO;CACT;;;;;;;;;;;;CAaA,MAAa,iBAAiB,UAAyB,CAAC,GAAqC;EAE3F,MAAM,KAAK,gBAAgB,MAAM,OAAO;EAGxC,MAAM,KAAK,eAAe;EAG1B,MAAM,eAAe,KAAK,MAAM;EAUhC,MAAM,kBAAkB,KAAK,KAAK;EAElC,IAAI,mBAAmB,aAAa,oBAAoB,MACtD,aAAa,mCAAmB,IAAI,KAAK;EAG3C,MAAM,kBAAkB,KAAK,KAAK;EAClC,IAAI,iBACF,aAAa,mCAAmB,IAAI,KAAK;EAI3C,IAAI,CAAC,QAAQ,YACX,MAAM,KAAK,MAAM,UAAU,UAAU;EAGvC,OAAO;CACT;;;;;;;;CASA,MAAc,cAAc,SAA+C;EAEzE,IAAI,CAAC,QAAQ,YACX,MAAM,KAAK,MAAM,UAAU,UAAU;EAIvC,MAAM,kBAAkB,KAAK,KAAK;EAClC,IAAI,iBACF,KAAK,MAAM,IAAI,iCAAiB,IAAI,KAAK,CAAC;EAG5C,IAAI,QAAQ,SAAS;GACnB,MAAM,WAAW,MAAM,KAAK,OAAO,QACjC,KAAK,OACL,KAAK,sBAAsB,GAC3B,KAAK,MAAM,IACb;GAEA,IAAI,UACF,KAAK,MAAM,YAAY,QAAmC;GAG5D,OAAO,EAAE,eAAe,WAAW,IAAI,EAAE;EAC3C;EAGA,MAAM,aAAa,KAAK,sBAAsB;EAI9C,IAAI,OAAO,KAAK,UAAU,CAAC,CAAC,WAAW,GACrC,OAAO,EAAE,eAAe,EAAE;EAI5B,IAAI;GACF,OAAO,MAAM,KAAK,OAAO,OACvB,KAAK,OACL,KAAK,sBAAsB,GAC3B,UACF;EACF,SAAS,OAAO;GACd,MAAM,KAAK,mBAAmB,OAAO,KAAK;EAC5C;CACF;;;;;;CAOA,AAAQ,mBAAmB,OAAgB,UAA4B;EACrE,MAAM,MAAM;EASZ,IAAI,KAAK,SAAS,WAAW,KAAK,SAAS,QAAS,KAAK,SAAS,SAChE,OAAO;EAGT,IAAI;EAEJ,IAAI,IAAI,YACN,QAAQ,OAAO,KAAK,IAAI,UAAU,CAAC,CAAC;OAGpC,QAAQ,mBAAmB,KAAK,IAAI,UAAU,EAAE,CAAC,GAAG,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK;EAG9E,IAAI,CAAC,OAEH,QAAQ,sBAAsB,KAAK,IAAI,WAAW,EAAE,CAAC,GAAG;EAG1D,OAAO,IAAI,8BACT,IAAI,KAAK,MAAM,YAAY,KAAK,UAAU,WAAW,WAAW,SAAS,qBACzE,CACE;GACE,MAAM;GACN,OAAO,OAAO,SAAS,QAAQ;GAC/B,OAAO,SAAS;EAClB,CACF,CACF;CACF;;;;;;;;;;;;CAaA,AAAQ,wBAAiD;EACvD,OAAO,GAAG,KAAK,aAAa,KAAK,MAAM,kBAAkB;CAC3D;;;;;;CAOA,MAAa,iBAAgC;EAC3C,IAAI,CAAC,KAAK,KAAK,kBAAkB,KAAK,MAAM,IAAI,IAAI,GAClD;EAGF,MAAM,KAAK,MAAM,gBAAgB,KAAK,IAAI;EAE1C,IAAI,OAAO,QACT;EAGF,KAAK,MAAM,IAAI,MAAM,EAAE;CACzB;;;;;;;;;;;;;;;;;;;;;;CAuBA,AAAQ,wBAA0C;EAChD,MAAM,aAA+B,CAAC;EAOtC,MAAM,kBAAkB,IAAI,IAAI;GAAC,KAAK;GAAY;GAAM;EAAK,CAAC;EAQ9D,MAAM,kBAAkB,KAAK,MAAM,aAChC,mBAAmB,CAAC,CACpB,QAAO,WAAU,CAAC,gBAAgB,IAAI,MAAM,KAAK,CAAC,KAAK,mBAAmB,IAAI,MAAM,CAAC;EAIxF,MAAM,mBAAmB,WACvB,gBAAgB,MAAK,SAAQ,WAAW,QAAQ,OAAO,WAAW,GAAG,KAAK,EAAE,CAAC;EAG/E,MAAM,eAAe,KAAK,MACvB,gBAAgB,CAAC,CACjB,QACC,WACE,CAAC,gBAAgB,IAAI,MAAM,KAC3B,CAAC,KAAK,mBAAmB,IAAI,MAAM,KACnC,CAAC,gBAAgB,MAAM,CAC3B;EAEF,IAAI,aAAa,SAAS,GAAG;GAC3B,WAAW,OAAO,CAAC;GACnB,KAAK,MAAM,UAAU,cAAc;IAEjC,IADc,KAAK,MAAM,IAAI,MACrB,MAAM,QAAW;IAEzB,WAAW,KAAK,UAAU,KAAK,MAAM,IAAI,MAAM;GACjD;EACF;EAIA,MAAM,iBAAiB,KAAK,MAAM,aAC/B,gBAAgB,CAAC,CACjB,QAAO,WAAU,CAAC,gBAAgB,IAAI,MAAM,KAAK,CAAC,gBAAgB,MAAM,CAAC;EAE5E,IAAI,eAAe,SAAS,GAAG;GAC7B,WAAW,SAAS,CAAC;GACrB,KAAK,MAAM,UAAU,gBACnB,WAAW,OAAO,UAAU;EAEhC;EAEA,KAAK,MAAM,UAAU,iBAAiB;GACpC,MAAM,QAAQ,KAAK,MAAM,IAAI,MAAM;GAEnC,IAAI,UAAU,QAAW;IACvB,WAAW,WAAW,CAAC;IACvB,WAAW,OAAO,UAAU;IAC5B;GACF;GAEA,WAAW,SAAS,CAAC;GACrB,WAAW,KAAK,UAAU;EAC5B;EAEA,OAAO;CACT;;;;;;;;;;CAWA,AAAQ,YAAY,eAA+B;EACjD,kBAAkB;GAChB,aAAa,KAAK,KAAK;GACvB,WAAW;GACX,eAAe,kBAAkB,KAAK,MAAM,WAAW,KAAK,OAAO,aAAa;EAClF,CAAC;CACH;AACF"}
|
|
1
|
+
{"version":3,"file":"database-writer.mjs","names":[],"sources":["../../../../../../../../packages/cascade/src/writer/database-writer.ts"],"sourcesContent":["import { when } from \"@mongez/reinforcements\";\nimport { getSealConfig, v, type ObjectValidator } from \"@warlock.js/seal\";\nimport type {\n DriverContract,\n InsertResult,\n UpdateOperations,\n UpdateResult,\n} from \"../contracts/database-driver.contract\";\nimport type {\n WriterContract,\n WriterOptions,\n WriterResult,\n} from \"../contracts/database-writer.contract\";\nimport { mergeDriverFields } from \"../model/methods/accessor-methods\";\nimport { generateModelId } from \"../model/generate-model-id\";\nimport type { ChildModel, Model } from \"../model/model\";\nimport { enqueueSyncFanout } from \"../sync/sync-fanout\";\nimport { triggerModelEvent } from \"../sync/model-events\";\nimport type { StrictMode } from \"../types\";\nimport { DatabaseWriterValidationError } from \"../validation\";\nimport { afterCommit } from \"../transactions/after-commit\";\nimport type { DataSource } from \"./../data-source/data-source\";\n\n/**\n * Database writer service that orchestrates model persistence.\n *\n * Handles the complete save pipeline:\n * 1. Check for changes (skip if no changes and not new)\n * 2. Emit `saving` event (for data enrichment)\n * 3. Emit `validating` event\n * 4. Validate and cast data via @warlock.js/seal schema\n * 5. Emit `validated` event\n * 6. Generate ID (for new NoSQL records)\n * 7. Emit `creating`/`updating` events\n * 8. Execute insert or update via driver\n * 9. Merge returned data into model\n * 10. Reset dirty tracker and update `isNew` flag\n * 11. Emit `saved` and `created`/`updated` events\n *\n * @example\n * ```typescript\n * const user = new User({ name: \"Alice\", email: \"alice@example.com\" });\n * const writer = new DatabaseWriter(user);\n * await writer.save();\n *\n * console.log(user.get(\"id\")); // 1 (auto-generated)\n * console.log(user.get(\"_id\")); // ObjectId(\"...\")\n *\n * // Update existing record\n * user.set(\"name\", \"Alice Smith\");\n * await writer.save();\n * // Only updates the \"name\" field (partial update)\n *\n * // Silent save (no events)\n * await writer.save({ skipEvents: true });\n * ```\n */\nexport class DatabaseWriter implements WriterContract {\n /** The model instance being persisted */\n private readonly model: Model;\n\n /** Dirty columns an update validation dropped (strip mode); never written. */\n private readonly unvalidatedColumns = new Set<string>();\n\n /** Model constructor reference */\n private readonly ctor: ChildModel<Model>;\n\n /** Data source containing driver and ID generator */\n private readonly dataSource: DataSource;\n\n /** Database driver for executing queries */\n private readonly driver: DriverContract;\n\n /** Table/collection name */\n private readonly table: string;\n\n /** Primary key field name */\n private readonly primaryKey: string;\n\n /** Validation schema (if defined) */\n private readonly schema?: ObjectValidator;\n\n /** Strict mode configuration */\n private readonly strictMode: StrictMode;\n\n /**\n * Create a new writer instance for a model.\n *\n * @param model - The model instance to persist\n *\n * @example\n * ```typescript\n * const user = new User({ name: \"Alice\" });\n * const writer = new DatabaseWriter(user);\n * await writer.save();\n * ```\n */\n public constructor(model: Model) {\n this.model = model;\n this.ctor = model.constructor as ChildModel<Model>;\n this.dataSource = this.ctor.getDataSource();\n this.driver = this.dataSource.driver;\n this.table = this.ctor.table;\n this.primaryKey = this.ctor.primaryKey;\n this.schema = this.ctor.schema;\n this.strictMode = this.ctor.strictMode;\n }\n\n /**\n * Save the model instance to the database.\n *\n * @param options - Save options\n * @returns Result with success status, document, and metadata\n * @throws {ValidationError} If validation fails\n */\n public async save(options: WriterOptions = {}): Promise<WriterResult> {\n const isInsert = this.model.isNew;\n\n // 1. Check if model has changes (skip if no changes and not new)\n if (!isInsert && !this.model.hasChanges()) {\n return {\n success: true,\n document: this.model.data,\n isNew: false,\n modifiedCount: 0,\n };\n }\n\n // 2. Emit saving event (before validation for data enrichment)\n if (!options.skipEvents) {\n await this.model.emitEvent(\"saving\", {\n isInsert,\n options,\n mode: isInsert ? \"insert\" : \"update\",\n });\n }\n\n // 3-4. Execute insert or update (validation happens inside each path)\n let result: InsertResult | UpdateResult;\n\n if (isInsert) {\n result = await this.performInsert(options);\n } else {\n await this.validateAndCast(false, options);\n result = await this.performUpdate(options);\n }\n\n // 5. Reset dirty tracker and update isNew flag\n const changedFields = isInsert ? [] : this.model.getDirtyColumns();\n this.model.dirtyTracker.reset();\n this.model.isNew = false;\n\n // 6. Emit post-save events\n if (!options.skipEvents) {\n await this.model.emitEvent(\"saved\");\n await this.model.emitEvent(isInsert ? \"created\" : \"updated\");\n }\n\n // 7. Trigger sync operations after COMMIT (inside a transaction they are\n // queued, so a rolled-back write never fans out). Failures are logged.\n if (!options.skipSync && !isInsert) {\n afterCommit(() => this.triggerSync(changedFields));\n }\n\n return {\n success: true,\n document: this.model.data,\n isNew: isInsert,\n modifiedCount: isInsert ? undefined : (result as UpdateResult).modifiedCount,\n };\n }\n\n /**\n * Validate and cast model data using the schema.\n *\n * Updates the model's data in-place with validated/casted values.\n *\n * @param isInsert - Whether this is an insert operation\n * @param options - Save options\n * @throws {ValidationError} If validation fails\n * @private\n */\n private async validateAndCast(isInsert: boolean, options: WriterOptions): Promise<void> {\n // Emit validating event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"validating\", {\n isInsert,\n options,\n mode: isInsert ? \"insert\" : \"update\",\n });\n }\n\n // Skip validation if requested or no schema defined\n if (options.skipValidation || !this.schema) {\n return;\n }\n\n // Whitelist the framework-managed system columns so a model carrying them\n // (id / _id / timestamps / soft-delete deletedAt) validates cleanly instead\n // of being stripped (strictMode \"strip\") or rejected (strictMode \"fail\").\n // `when(...)` adds each timestamp/soft-delete column only when configured\n // (truthy) — the lazy factory means a disabled column (`false`) never even\n // builds a bogus schema key.\n //\n // The whitelist must apply to BOTH insert and update: on insert a caller may\n // supply a backdated createdAt (e.g. data migrations / imports), and without\n // the whitelist strictMode \"strip\" silently drops it before it reaches the\n // writer, while \"fail\" rejects the whole insert.\n const systemColumns = {\n id: v.scalar().optional(),\n _id: v.any().optional(),\n ...when(this.ctor.createdAtColumn, () => ({\n [this.ctor.createdAtColumn as string]: v.date().optional(),\n })),\n ...when(this.ctor.updatedAtColumn, () => ({\n [this.ctor.updatedAtColumn as string]: v.date().optional(),\n })),\n ...when(this.ctor.deletedAtColumn, () => ({\n [this.ctor.deletedAtColumn as string]: v.date().optional(),\n })),\n };\n\n // Clone full schema for insert, partial (dirty keys only) for updates.\n //\n // An update validates ONLY the dirty columns and merges back only those\n // keys: columns the schema does not list (migration-added, withCount, ...)\n // are never touched, so they can no longer be turned into `$unset`.\n const dataToValidate: Record<string, unknown> = isInsert\n ? this.model.data\n : Object.fromEntries(\n this.model\n .getDirtyColumns()\n .filter((column) => column in this.model.data)\n .map((column) => [column, this.model.data[column]]),\n );\n\n const validationSchema = isInsert\n ? this.schema.clone().extend(systemColumns)\n : this.schema.clone(Object.keys(dataToValidate)).extend(systemColumns);\n\n // Apply strict mode\n if (this.strictMode === \"strip\") {\n validationSchema.stripUnknown();\n } else if (this.strictMode === \"fail\") {\n validationSchema.allowUnknown(false);\n } else if (this.strictMode === \"allow\") {\n validationSchema.allowUnknown(true);\n }\n\n // Run validation\n const result = await v.validate(validationSchema, dataToValidate, {\n context: {\n model: this.model,\n },\n ...getSealConfig(),\n });\n\n if (!result.isValid) {\n const error = new DatabaseWriterValidationError(\n `[${this.model.constructor.name} Model] ${isInsert ? \"Insert\" : \"Update\"} Validation failed`,\n result.errors,\n );\n if (!options.skipEvents) {\n await this.model.emitEvent(\"validated\", { result, error });\n }\n throw error;\n }\n\n // Update model data with validated/casted data\n if (isInsert) {\n this.model.replaceData(result.data);\n } else {\n const validated = result.data as Record<string, unknown>;\n\n for (const column of Object.keys(dataToValidate)) {\n if (column in validated) {\n this.model.set(column, validated[column]);\n } else if (dataToValidate[column] === null && column in validationSchema.schema) {\n // An explicit null on a KNOWN column that passed validation: an\n // optional, non-nullable leaf coalesces null to \"absent\" in seal, so\n // it is missing from the result. It is a deliberate \"clear this\n // column\", not an unknown one to strip (Real-Estate #18). A required\n // column never gets here: its rule rejects the null and validation throws.\n this.model.set(column, null);\n } else {\n // Dropped by strictMode \"strip\": never write an unvalidated column.\n this.unvalidatedColumns.add(column);\n }\n }\n }\n\n // Emit validated event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"validated\", { result });\n }\n }\n\n /**\n * Validate, cast and id-generate a not-yet-persisted model for `upsert()`.\n *\n * Runs the insert-mode pipeline (schema, casts, strictMode, Mongo `id`) but\n * does not write; the caller sends the resulting data through the driver.\n */\n public async prepareForUpsert(): Promise<void> {\n await this.validateAndCast(true, {});\n await this.generateNextId();\n }\n\n /**\n * Perform an insert operation.\n *\n * @param options - Save options\n * @returns Insert result\n * @private\n */\n private async performInsert(options: WriterOptions): Promise<InsertResult> {\n const dataToInsert = await this.prepareForInsert(options);\n\n // INSERT: use full validated data\n let result: InsertResult;\n\n try {\n result = await this.driver.insert(this.table, dataToInsert);\n } catch (error) {\n throw this.mapUniqueViolation(error, true);\n }\n\n // Merge returned data (e.g., generated _id, timestamps)\n // Note: We use merge here because the result might not include all fields\n // (e.g., our generated 'id' field), and we don't want to lose them\n mergeDriverFields(this.model, result.document as Record<string, unknown>);\n\n // Reset dirty tracker immediately after merge to prevent\n // database-generated fields (like _id) from being marked as dirty\n this.model.dirtyTracker.reset();\n\n return result;\n }\n\n /**\n * Run the insert pipeline (validation, casting, id generation, timestamps,\n * `validating`/`validated`/`creating` hooks) WITHOUT writing.\n *\n * `save()` uses this for its insert path, so bulk inserts and single inserts\n * cannot drift. The caller is responsible for sending the returned document\n * to the driver.\n *\n * @param options - Save options (e.g. `skipEvents`, `skipValidation`)\n * @returns The prepared document to insert\n */\n public async prepareForInsert(options: WriterOptions = {}): Promise<Record<string, unknown>> {\n // Validate and cast data\n await this.validateAndCast(true, options);\n\n // Generate ID if needed (NoSQL only)\n await this.generateNextId();\n\n // Get data to insert (already validated and casted)\n const dataToInsert = this.model.data;\n\n // Add createdAt and updatedAt to the data (using resolved column names)\n // The column names are already resolved through the hierarchy:\n // Model static property > Database config > Driver defaults > undefined\n //\n // Only stamp createdAt when the caller did NOT supply one, so a backdated\n // value (e.g. data migrations / imports) is honored instead of overwritten.\n // This mirrors the upsert path's guard. updatedAt is always stamped to\n // reflect the moment the record is persisted.\n const createdAtColumn = this.ctor.createdAtColumn;\n\n if (createdAtColumn && dataToInsert[createdAtColumn] == null) {\n dataToInsert[createdAtColumn] = new Date();\n }\n\n const updatedAtColumn = this.ctor.updatedAtColumn;\n if (updatedAtColumn) {\n dataToInsert[updatedAtColumn] = new Date();\n }\n\n // Emit creating event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"creating\");\n }\n\n return dataToInsert;\n }\n\n /**\n * Perform an update operation.\n *\n * @param options - Save options\n * @returns Update result\n * @private\n */\n private async performUpdate(options: WriterOptions): Promise<UpdateResult> {\n // Emit updating event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"updating\");\n }\n\n // Update the updatedAt timestamp (using resolved column name)\n const updatedAtColumn = this.ctor.updatedAtColumn;\n if (updatedAtColumn) {\n this.model.set(updatedAtColumn, new Date());\n }\n\n if (options.replace) {\n const document = await this.driver.replace(\n this.table,\n this.buildPrimaryKeyFilter(),\n this.model.data,\n );\n\n if (document) {\n this.model.replaceData(document as Record<string, unknown>);\n }\n\n return { modifiedCount: document ? 1 : 0 };\n }\n\n // Build operations from dirty tracker\n const operations = this.buildUpdateOperations();\n\n // Nothing left to write once identity columns are excluded — don't hand the\n // driver an empty update document (MongoDB rejects one).\n if (Object.keys(operations).length === 0) {\n return { modifiedCount: 0 };\n }\n\n // Execute update with operations\n try {\n return await this.driver.update(this.table, this.buildPrimaryKeyFilter(), operations);\n } catch (error) {\n throw this.mapUniqueViolation(error, false);\n }\n }\n\n /**\n * Map a driver unique-constraint violation (Postgres 23505, MongoDB 11000)\n * to a field validation error, so a lost `unique` race never surfaces as a\n * 500. Any other error is returned untouched.\n */\n private mapUniqueViolation(error: unknown, isInsert: boolean): unknown {\n const err = error as\n | {\n code?: string | number;\n detail?: string;\n keyPattern?: Record<string, unknown>;\n message?: string;\n }\n | undefined;\n\n if (err?.code !== \"23505\" && err?.code !== 11000 && err?.code !== \"11000\") {\n return error;\n }\n\n let field: string | undefined;\n\n if (err.keyPattern) {\n field = Object.keys(err.keyPattern)[0];\n } else {\n // Postgres: \"Key (email)=(x) already exists.\"\n field = /Key \\(([^)]+)\\)=/\n .exec(err.detail ?? \"\")?.[1]\n ?.split(\",\")[0]\n ?.trim();\n }\n\n if (!field) {\n // Mongo: \"index: email_1 dup key\"\n field = /index: (\\S+?)_-?1\\b/.exec(err.message ?? \"\")?.[1];\n }\n\n return new DatabaseWriterValidationError(\n `[${this.model.constructor.name} Model] ${isInsert ? \"Insert\" : \"Update\"} Validation failed`,\n [\n {\n type: \"unique\",\n error: `The ${field ?? \"value\"} must be unique`,\n input: field ?? \"id\",\n },\n ],\n );\n }\n\n /**\n * Build the filter that pins a write to the row this model was loaded from.\n *\n * It reads `model.trustedPrimaryKey` — the value captured when the instance\n * became persisted — NOT the current value in `model.data`. The current value\n * is reachable by mass assignment (`model.merge(req.body)`), so deriving the\n * filter from it let a request body redirect the UPDATE to another document.\n *\n * @returns Filter matching the originally loaded record\n * @private\n */\n private buildPrimaryKeyFilter(): Record<string, unknown> {\n return { [this.primaryKey]: this.model.trustedPrimaryKey };\n }\n\n /**\n * Generate ID for the model if auto-generation is enabled.\n *\n * @private\n */\n public async generateNextId(): Promise<void> {\n if (!this.ctor.autoGenerateId || this.model.get(\"id\")) {\n return;\n }\n\n const id = await generateModelId(this.ctor);\n\n if (id === undefined) {\n return;\n }\n\n this.model.set(\"id\", id);\n }\n\n /**\n * Build update operations from the model's dirty tracker.\n *\n * Handles both modified fields ($set) and removed fields ($unset).\n *\n * @returns Update operations for the driver\n * @private\n *\n * @example\n * ```typescript\n * // Model with changes\n * user.set(\"name\", \"Alice\");\n * user.unset(\"tempField\");\n *\n * const operations = this.buildUpdateOperations();\n * // {\n * // $set: { name: \"Alice\" },\n * // $unset: { tempField: 1 }\n * // }\n * ```\n */\n private buildUpdateOperations(): UpdateOperations {\n const operations: UpdateOperations = {};\n\n // Identity columns are never written by an update. The filter pins the row\n // by its captured primary key, so a dirty `id`/`_id` can only come from\n // mass assignment or an explicit set() — either way, rewriting the key of\n // an existing row corrupts identity (and `_id` is immutable in MongoDB).\n // Changing a primary key is a deliberate operation: use the atomic/raw APIs.\n const identityColumns = new Set([this.primaryKey, \"id\", \"_id\"]);\n\n // Top-level fields assigned/unset as a whole (`set(\"nested\", obj)`,\n // `unset(\"nested\")`) are written as ONE value. Per-leaf operations can only\n // report the leaves that changed or vanished, so a nested object replaced with\n // one that has fewer keys would keep its removed keys' empty parents in the\n // stored document. Dotted-path sets (`set(\"nested.a\", v)`) and merges are\n // partial by intent and keep going through the per-leaf path below.\n const replacedColumns = this.model.dirtyTracker\n .getReplacedColumns()\n .filter((column) => !identityColumns.has(column) && !this.unvalidatedColumns.has(column));\n\n // A leaf under a whole-written field must not be sent too: MongoDB rejects\n // an update touching both `nested` and `nested.a`.\n const isUnderReplaced = (column: string): boolean =>\n replacedColumns.some((root) => column === root || column.startsWith(`${root}.`));\n\n // Get dirty columns (modified fields)\n const dirtyColumns = this.model\n .getDirtyColumns()\n .filter(\n (column) =>\n !identityColumns.has(column) &&\n !this.unvalidatedColumns.has(column) &&\n !isUnderReplaced(column),\n );\n\n if (dirtyColumns.length > 0) {\n operations.$set = {};\n for (const column of dirtyColumns) {\n const value = this.model.get(column);\n if (value === undefined) continue;\n\n operations.$set[column] = this.model.get(column);\n }\n }\n\n // Get removed columns, collapsed to the highest path that is actually gone so\n // a parent emptied by an unset is removed too instead of left behind as `{}`.\n const removedColumns = this.model.dirtyTracker\n .getRemovedPaths()\n .filter((column) => !identityColumns.has(column) && !isUnderReplaced(column));\n\n if (removedColumns.length > 0) {\n operations.$unset = {};\n for (const column of removedColumns) {\n operations.$unset[column] = 1;\n }\n }\n\n for (const column of replacedColumns) {\n const value = this.model.get(column);\n\n if (value === undefined) {\n operations.$unset ??= {};\n operations.$unset[column] = 1;\n continue;\n }\n\n operations.$set ??= {};\n operations.$set[column] = value;\n }\n\n return operations;\n }\n\n /**\n * Trigger sync operations after successful save.\n *\n * Emits a model.updated event that ModelSyncOperation listens to.\n * The sync is handled by registered sync operations, not directly here.\n *\n * @param changedFields - Fields that were changed (for filtering)\n * @private\n */\n private triggerSync(changedFields: string[]): void {\n enqueueSyncFanout({\n sourceModel: this.ctor.name,\n operation: \"update\",\n execute: () => triggerModelEvent(this.ctor, \"updated\", this.model, changedFields),\n });\n }\n}\n"],"mappings":"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"}
|