@woltz/rich-domain 1.3.1 → 1.3.2
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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +9 -11
- package/eslint.config.js +0 -57
- package/jest.config.js +0 -21
- package/src/aggregate-changes.ts +0 -444
- package/src/base-entity.ts +0 -410
- package/src/change-tracker.ts +0 -1123
- package/src/constants.ts +0 -81
- package/src/criteria.ts +0 -521
- package/src/crypto.ts +0 -31
- package/src/domain-event-bus.ts +0 -152
- package/src/domain-event.ts +0 -49
- package/src/entity-changes.ts +0 -146
- package/src/entity-schema-registry.ts +0 -505
- package/src/entity.ts +0 -5
- package/src/exceptions.ts +0 -435
- package/src/id.ts +0 -98
- package/src/index.ts +0 -52
- package/src/mapper.ts +0 -6
- package/src/paginated-result.ts +0 -250
- package/src/repository/base-repository.ts +0 -33
- package/src/repository/index.ts +0 -3
- package/src/repository/unit-of-work.ts +0 -76
- package/src/types/change-tracker.ts +0 -264
- package/src/types/criteria.ts +0 -159
- package/src/types/domain-event.ts +0 -38
- package/src/types/domain.ts +0 -33
- package/src/types/index.ts +0 -7
- package/src/types/standard-schema.ts +0 -19
- package/src/types/unit-of-work.ts +0 -46
- package/src/types/utils.ts +0 -20
- package/src/utils/criteria-operator-validation.ts +0 -209
- package/src/utils/helpers.ts +0 -34
- package/src/validation-error.ts +0 -141
- package/src/value-object.ts +0 -249
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export { ValueObject } from "./value-object";
|
|
|
11
11
|
export { Mapper } from "./mapper";
|
|
12
12
|
export { EntitySchemaRegistry } from "./entity-schema-registry";
|
|
13
13
|
export { AggregateChanges } from "./aggregate-changes";
|
|
14
|
-
export type { DomainEventHandler, EntityHooks, Filter, EntityValidation, IDomainEvent, VOValidation, VOHooks, ValidationConfig, Primitive, TransactionContext, PaginationMeta, Pagination, OrderDirection, Order, IUnitOfWork, IDomainEventHandler, FieldPath, FilterOperator, Search, FilterValueFor, PathValue, OperatorsForType, DateOperators, NumberOperators, StringOperators, BooleanOperators, ArrayOperators, CriteriaOptions, } from "./types";
|
|
14
|
+
export type { DomainEventHandler, EntityHooks, Filter, EntityValidation, IDomainEvent, VOValidation, VOHooks, ValidationConfig, Primitive, TransactionContext, PaginationMeta, Pagination, OrderDirection, Order, IUnitOfWork, IDomainEventHandler, FieldPath, FilterOperator, Search, FilterValueFor, PathValue, OperatorsForType, DateOperators, NumberOperators, StringOperators, BooleanOperators, ArrayOperators, CriteriaOptions, DeepJsonResult, } from "./types";
|
|
15
15
|
export { ARRAY_OPERATORS, BOOLEAN_OPERATORS, DATE_OPERATORS, NUMBER_OPERATORS, STRING_OPERATORS, FILTER_OPERATORS, } from "./constants";
|
|
16
16
|
export { isValidOperatorForType } from "./utils/criteria-operator-validation";
|
|
17
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,MAAM,EACN,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,OAAO,EACP,gBAAgB,EAChB,SAAS,EACT,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,cAAc,EACd,KAAK,EACL,WAAW,EACX,mBAAmB,EACnB,SAAS,EACT,cAAc,EACd,MAAM,EACN,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,MAAM,EACN,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,OAAO,EACP,gBAAgB,EAChB,SAAS,EACT,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,cAAc,EACd,KAAK,EACL,WAAW,EACX,mBAAmB,EACnB,SAAS,EACT,cAAc,EACd,MAAM,EACN,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,cAAc,GACf,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAgCvD,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@woltz/rich-domain",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "Rich Domain Library with Standard Schema validation support",
|
|
5
5
|
"homepage": "https://woltz.mintlify.app",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
9
15
|
"author": "Tarcisio Andrade",
|
|
10
16
|
"license": "MIT",
|
|
11
17
|
"publishConfig": {
|
|
@@ -26,7 +32,7 @@
|
|
|
26
32
|
"check": "tsc --noEmit",
|
|
27
33
|
"coverage": "jest --coverage",
|
|
28
34
|
"lint": "eslint src",
|
|
29
|
-
"clean": "rm -rf dist coverage node_modules",
|
|
35
|
+
"clean": "rm -rf dist coverage node_modules tsconfig.tsbuildinfo",
|
|
30
36
|
"prepublishOnly": "npm run build",
|
|
31
37
|
"release": "standard-version",
|
|
32
38
|
"release:minor": "standard-version --release-as minor",
|
|
@@ -44,15 +50,7 @@
|
|
|
44
50
|
"typescript"
|
|
45
51
|
],
|
|
46
52
|
"files": [
|
|
47
|
-
"dist"
|
|
48
|
-
"src",
|
|
49
|
-
"**/*.js",
|
|
50
|
-
"**/*.mjs",
|
|
51
|
-
"**/*.cjs",
|
|
52
|
-
"**/*.d.ts",
|
|
53
|
-
"**/*.d.mts",
|
|
54
|
-
"**/*.d.cts",
|
|
55
|
-
"**/package.json"
|
|
53
|
+
"dist"
|
|
56
54
|
],
|
|
57
55
|
"devDependencies": {
|
|
58
56
|
"@faker-js/faker": "^9.9.0",
|
package/eslint.config.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import tsParser from "@typescript-eslint/parser";
|
|
2
|
-
import tsPlugin from "@typescript-eslint/eslint-plugin";
|
|
3
|
-
import { dirname } from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
-
const __dirname = dirname(__filename);
|
|
8
|
-
|
|
9
|
-
export default [
|
|
10
|
-
{
|
|
11
|
-
ignores: [
|
|
12
|
-
"dist/**",
|
|
13
|
-
"node_modules/**",
|
|
14
|
-
"coverage/**",
|
|
15
|
-
"**/*.test.ts",
|
|
16
|
-
"**/*.spec.ts",
|
|
17
|
-
],
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
files: ["src/**/*.ts"],
|
|
21
|
-
languageOptions: {
|
|
22
|
-
parser: tsParser,
|
|
23
|
-
parserOptions: {
|
|
24
|
-
ecmaVersion: 2020,
|
|
25
|
-
sourceType: "module",
|
|
26
|
-
project: "./tsconfig.json",
|
|
27
|
-
tsconfigRootDir: __dirname,
|
|
28
|
-
},
|
|
29
|
-
globals: {
|
|
30
|
-
console: "readonly",
|
|
31
|
-
process: "readonly",
|
|
32
|
-
Buffer: "readonly",
|
|
33
|
-
__dirname: "readonly",
|
|
34
|
-
__filename: "readonly",
|
|
35
|
-
module: "readonly",
|
|
36
|
-
require: "readonly",
|
|
37
|
-
exports: "readonly",
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
plugins: {
|
|
41
|
-
"@typescript-eslint": tsPlugin,
|
|
42
|
-
},
|
|
43
|
-
rules: {
|
|
44
|
-
...tsPlugin.configs.recommended.rules,
|
|
45
|
-
"@typescript-eslint/no-explicit-any": "off",
|
|
46
|
-
"@typescript-eslint/no-unused-vars": [
|
|
47
|
-
"warn",
|
|
48
|
-
{ argsIgnorePattern: "^_" },
|
|
49
|
-
],
|
|
50
|
-
"@typescript-eslint/explicit-function-return-type": "off",
|
|
51
|
-
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
52
|
-
"@typescript-eslint/no-this-alias": "off",
|
|
53
|
-
"@typescript-eslint/no-unsafe-function-type": "off",
|
|
54
|
-
"no-console": "off",
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
];
|
package/jest.config.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
|
2
|
-
export default {
|
|
3
|
-
preset: "ts-jest",
|
|
4
|
-
testEnvironment: "node",
|
|
5
|
-
roots: ["<rootDir>/tests", "<rootDir>/src"],
|
|
6
|
-
testMatch: ["**/*.test.ts", "**/*.spec.ts"],
|
|
7
|
-
transform: {
|
|
8
|
-
"^.+\\.tsx?$": "ts-jest",
|
|
9
|
-
},
|
|
10
|
-
coveragePathIgnorePatterns: [
|
|
11
|
-
"/node_modules/",
|
|
12
|
-
"/dist/",
|
|
13
|
-
"/tests/",
|
|
14
|
-
"**/examples/**",
|
|
15
|
-
],
|
|
16
|
-
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
|
17
|
-
collectCoverageFrom: ["src/**/*.ts", "!src/**/*.d.ts", "!src/index.ts"],
|
|
18
|
-
coverageDirectory: "coverage",
|
|
19
|
-
coverageReporters: ["text", "lcov", "clover"],
|
|
20
|
-
verbose: true,
|
|
21
|
-
};
|
package/src/aggregate-changes.ts
DELETED
|
@@ -1,444 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Operation,
|
|
3
|
-
CreateOperation,
|
|
4
|
-
UpdateOperation,
|
|
5
|
-
DeleteOperation,
|
|
6
|
-
BatchOperations,
|
|
7
|
-
BatchCreateItem,
|
|
8
|
-
BatchUpdateItem,
|
|
9
|
-
} from "./types/change-tracker";
|
|
10
|
-
import { EntityChanges } from "./entity-changes";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Manages and organizes the changes of an Aggregate.
|
|
14
|
-
*
|
|
15
|
-
* Responsibilities:
|
|
16
|
-
* - Stores all operations (create, update, delete)
|
|
17
|
-
* - Orders operations respecting FK dependencies
|
|
18
|
-
* - Groups operations by entity for batch execution
|
|
19
|
-
* - Provides query and iteration methods
|
|
20
|
-
* - Includes relationField, parentId, parentEntity for N:N support
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* ```typescript
|
|
24
|
-
* // Define an entity map for type-safe operations
|
|
25
|
-
* type UserEntities = {
|
|
26
|
-
* User: User;
|
|
27
|
-
* Post: Post;
|
|
28
|
-
* Comment: Comment;
|
|
29
|
-
* };
|
|
30
|
-
*
|
|
31
|
-
* // Getting changes with types
|
|
32
|
-
* const changes = user.getChanges<UserEntities>();
|
|
33
|
-
*
|
|
34
|
-
* // Filtering by entity with autocompletion
|
|
35
|
-
* const postChanges = changes.for('Post');
|
|
36
|
-
* postChanges.creates.forEach(post => {
|
|
37
|
-
* console.log(post.title);
|
|
38
|
-
* });
|
|
39
|
-
* ```
|
|
40
|
-
*/
|
|
41
|
-
export class AggregateChanges<TEntityMap = Record<string, any>> {
|
|
42
|
-
private ops: Operation[] = [];
|
|
43
|
-
|
|
44
|
-
constructor(operations: Operation[] = []) {
|
|
45
|
-
this.ops = [...operations];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Adds a create operation.
|
|
50
|
-
*
|
|
51
|
-
* @param entity - Entity name
|
|
52
|
-
* @param data - Entity data
|
|
53
|
-
* @param depth - Depth in the aggregate tree
|
|
54
|
-
* @param parentId - Parent entity ID (for FK)
|
|
55
|
-
* @param parentEntity - Parent entity name
|
|
56
|
-
* @param relationField - Name of the relation field in parent (e.g., 'tags', 'comments')
|
|
57
|
-
*/
|
|
58
|
-
addCreate<T>(
|
|
59
|
-
entity: string,
|
|
60
|
-
data: T,
|
|
61
|
-
depth: number,
|
|
62
|
-
parentId?: string,
|
|
63
|
-
parentEntity?: string,
|
|
64
|
-
relationField?: string
|
|
65
|
-
): void {
|
|
66
|
-
this.ops.push({
|
|
67
|
-
type: "create",
|
|
68
|
-
entity,
|
|
69
|
-
data,
|
|
70
|
-
depth,
|
|
71
|
-
parentId,
|
|
72
|
-
parentEntity,
|
|
73
|
-
relationField,
|
|
74
|
-
} as CreateOperation<T>);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Adds an update operation.
|
|
79
|
-
*/
|
|
80
|
-
addUpdate<T>(
|
|
81
|
-
entity: string,
|
|
82
|
-
id: string,
|
|
83
|
-
data: T,
|
|
84
|
-
changedFields: Record<string, any>,
|
|
85
|
-
depth: number
|
|
86
|
-
): void {
|
|
87
|
-
this.ops.push({
|
|
88
|
-
type: "update",
|
|
89
|
-
entity,
|
|
90
|
-
id,
|
|
91
|
-
data,
|
|
92
|
-
changedFields,
|
|
93
|
-
depth,
|
|
94
|
-
} as UpdateOperation<T>);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Adds a delete operation.
|
|
99
|
-
*
|
|
100
|
-
* @param entity - Entity name
|
|
101
|
-
* @param id - Entity ID
|
|
102
|
-
* @param data - Entity data (for reference)
|
|
103
|
-
* @param depth - Depth in the aggregate tree
|
|
104
|
-
* @param relationField - Name of the relation field in parent (e.g., 'tags', 'comments')
|
|
105
|
-
* @param parentId - Parent entity ID (for N:N disconnect)
|
|
106
|
-
* @param parentEntity - Parent entity name (for N:N disconnect)
|
|
107
|
-
*/
|
|
108
|
-
addDelete<T>(
|
|
109
|
-
entity: string,
|
|
110
|
-
id: string,
|
|
111
|
-
data: T,
|
|
112
|
-
depth: number,
|
|
113
|
-
relationField?: string,
|
|
114
|
-
parentId?: string,
|
|
115
|
-
parentEntity?: string
|
|
116
|
-
): void {
|
|
117
|
-
this.ops.push({
|
|
118
|
-
type: "delete",
|
|
119
|
-
entity,
|
|
120
|
-
id,
|
|
121
|
-
data,
|
|
122
|
-
depth,
|
|
123
|
-
relationField,
|
|
124
|
-
parentId,
|
|
125
|
-
parentEntity,
|
|
126
|
-
} as DeleteOperation<T>);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Returns all create operations, sorted by ascending depth (root → leaf).
|
|
131
|
-
*/
|
|
132
|
-
creates(): CreateOperation[] {
|
|
133
|
-
return this.ops
|
|
134
|
-
.filter((op): op is CreateOperation => op.type === "create")
|
|
135
|
-
.sort((a, b) => a.depth - b.depth);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Returns all update operations.
|
|
140
|
-
*/
|
|
141
|
-
updates(): UpdateOperation[] {
|
|
142
|
-
return this.ops.filter((op): op is UpdateOperation => op.type === "update");
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Returns all delete operations, sorted by descending depth (leaf → root).
|
|
147
|
-
*/
|
|
148
|
-
deletes(): DeleteOperation[] {
|
|
149
|
-
return this.ops
|
|
150
|
-
.filter((op): op is DeleteOperation => op.type === "delete")
|
|
151
|
-
.sort((a, b) => b.depth - a.depth);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Iterator that returns operations in the correct execution order:
|
|
156
|
-
* 1. Deletes (leaf → root)
|
|
157
|
-
* 2. Creates (root → leaf)
|
|
158
|
-
* 3. Updates
|
|
159
|
-
*/
|
|
160
|
-
*operations(): Generator<Operation> {
|
|
161
|
-
yield* this.deletes();
|
|
162
|
-
yield* this.creates();
|
|
163
|
-
yield* this.updates();
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Returns all operations as an array in execution order.
|
|
168
|
-
*/
|
|
169
|
-
toArray(): Operation[] {
|
|
170
|
-
return [...this.operations()];
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Converts the changes into BatchOperations for optimized execution.
|
|
175
|
-
*
|
|
176
|
-
* Groups operations by entity and sorts by depth:
|
|
177
|
-
* - Deletes: depth DESC (leaf → root), grouped by entity + relationField + parentId
|
|
178
|
-
* - Creates: depth ASC (root → leaf), grouped by entity + relationField
|
|
179
|
-
* - Updates: grouped by entity
|
|
180
|
-
*
|
|
181
|
-
* @example
|
|
182
|
-
* ```typescript
|
|
183
|
-
* const batch = changes.toBatchOperations();
|
|
184
|
-
*
|
|
185
|
-
* // Run deletes
|
|
186
|
-
* for (const del of batch.deletes) {
|
|
187
|
-
* if (registry.isReferenceCollection(del.parentEntity, del.relationField)) {
|
|
188
|
-
* // N:N - disconnect only
|
|
189
|
-
* await prisma[del.parentEntity].update({
|
|
190
|
-
* where: { id: del.parentId },
|
|
191
|
-
* data: { [del.relationField]: { disconnect: del.ids.map(id => ({ id })) } }
|
|
192
|
-
* });
|
|
193
|
-
* } else {
|
|
194
|
-
* // 1:N - delete entities
|
|
195
|
-
* await prisma[del.entity].deleteMany({ where: { id: { in: del.ids } } });
|
|
196
|
-
* }
|
|
197
|
-
* }
|
|
198
|
-
* ```
|
|
199
|
-
*/
|
|
200
|
-
toBatchOperations(): BatchOperations {
|
|
201
|
-
return {
|
|
202
|
-
deletes: this.groupDeletes(),
|
|
203
|
-
creates: this.groupCreates(),
|
|
204
|
-
updates: this.groupUpdates(),
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Groups deletes by entity + relationField + parentId, sorted by descending depth.
|
|
210
|
-
*
|
|
211
|
-
* For N:N relations, we need to group by parentId because disconnect
|
|
212
|
-
* operations are performed on the parent entity.
|
|
213
|
-
*/
|
|
214
|
-
private groupDeletes(): BatchOperations["deletes"] {
|
|
215
|
-
const deleteOps = this.deletes();
|
|
216
|
-
const grouped = new Map<
|
|
217
|
-
string,
|
|
218
|
-
{
|
|
219
|
-
depth: number;
|
|
220
|
-
ids: string[];
|
|
221
|
-
relationField?: string;
|
|
222
|
-
parentEntity?: string;
|
|
223
|
-
parentId?: string;
|
|
224
|
-
}
|
|
225
|
-
>();
|
|
226
|
-
|
|
227
|
-
for (const op of deleteOps) {
|
|
228
|
-
// Group by entity + relationField + parentId
|
|
229
|
-
// This ensures N:N disconnects are grouped per parent
|
|
230
|
-
const key = `${op.entity}:${op.relationField ?? ""}:${op.parentId ?? ""}`;
|
|
231
|
-
|
|
232
|
-
if (!grouped.has(key)) {
|
|
233
|
-
grouped.set(key, {
|
|
234
|
-
depth: op.depth,
|
|
235
|
-
ids: [],
|
|
236
|
-
relationField: op.relationField,
|
|
237
|
-
parentEntity: op.parentEntity,
|
|
238
|
-
parentId: op.parentId,
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
grouped.get(key)!.ids.push(op.id);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return Array.from(grouped.entries())
|
|
245
|
-
.map(([key, { depth, ids, relationField, parentEntity, parentId }]) => {
|
|
246
|
-
const entity = key.split(":")[0];
|
|
247
|
-
return { entity, depth, ids, parentId, relationField, parentEntity };
|
|
248
|
-
})
|
|
249
|
-
.sort((a, b) => b.depth - a.depth);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Groups creates by entity + relationField, sorted by ascending depth.
|
|
254
|
-
*
|
|
255
|
-
* Preserves parentEntity for N:N connect operations.
|
|
256
|
-
*/
|
|
257
|
-
private groupCreates(): BatchOperations["creates"] {
|
|
258
|
-
const createOps = this.creates();
|
|
259
|
-
const grouped = new Map<
|
|
260
|
-
string,
|
|
261
|
-
{
|
|
262
|
-
depth: number;
|
|
263
|
-
items: BatchCreateItem[];
|
|
264
|
-
relationField?: string;
|
|
265
|
-
parentEntity?: string;
|
|
266
|
-
}
|
|
267
|
-
>();
|
|
268
|
-
|
|
269
|
-
for (const op of createOps) {
|
|
270
|
-
const key = `${op.entity}:${op.relationField ?? ""}`;
|
|
271
|
-
|
|
272
|
-
if (!grouped.has(key)) {
|
|
273
|
-
grouped.set(key, {
|
|
274
|
-
depth: op.depth,
|
|
275
|
-
items: [],
|
|
276
|
-
relationField: op.relationField,
|
|
277
|
-
parentEntity: op.parentEntity,
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
grouped.get(key)!.items.push({
|
|
281
|
-
data: op.data,
|
|
282
|
-
parentId: op.parentId,
|
|
283
|
-
parentEntity: op.parentEntity,
|
|
284
|
-
relationField: op.relationField,
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return Array.from(grouped.entries())
|
|
289
|
-
.map(([key, { depth, items, relationField, parentEntity }]) => {
|
|
290
|
-
const entity = key.split(":")[0];
|
|
291
|
-
return { entity, depth, items, relationField, parentEntity };
|
|
292
|
-
})
|
|
293
|
-
.sort((a, b) => a.depth - b.depth);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Groups updates by entity.
|
|
298
|
-
*/
|
|
299
|
-
private groupUpdates(): BatchOperations["updates"] {
|
|
300
|
-
const updateOps = this.updates();
|
|
301
|
-
const grouped = new Map<string, BatchUpdateItem[]>();
|
|
302
|
-
|
|
303
|
-
for (const op of updateOps) {
|
|
304
|
-
if (!grouped.has(op.entity)) {
|
|
305
|
-
grouped.set(op.entity, []);
|
|
306
|
-
}
|
|
307
|
-
grouped.get(op.entity)!.push({
|
|
308
|
-
id: op.id,
|
|
309
|
-
changedFields: op.changedFields,
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return Array.from(grouped.entries()).map(([entity, items]) => ({
|
|
314
|
-
entity,
|
|
315
|
-
items,
|
|
316
|
-
}));
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Filters changes by entity name.
|
|
321
|
-
*
|
|
322
|
-
* @param entityName - Name of the entity (e.g., 'Post', 'Comment')
|
|
323
|
-
* @returns EntityChanges containing only the operations for this entity
|
|
324
|
-
*
|
|
325
|
-
* @example
|
|
326
|
-
* ```typescript
|
|
327
|
-
* const postChanges = changes.for('Post');
|
|
328
|
-
*
|
|
329
|
-
* if (postChanges.hasCreates()) {
|
|
330
|
-
* postChanges.creates.forEach(post => {
|
|
331
|
-
* console.log('New post:', post.title);
|
|
332
|
-
* });
|
|
333
|
-
* }
|
|
334
|
-
* ```
|
|
335
|
-
*/
|
|
336
|
-
for<K extends keyof TEntityMap>(entityName: K): EntityChanges<TEntityMap[K]> {
|
|
337
|
-
const filtered = this.ops.filter((op) => op.entity === entityName);
|
|
338
|
-
return new EntityChanges<TEntityMap[K]>(filtered);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Filters changes by relation field.
|
|
343
|
-
*
|
|
344
|
-
* @param relationField - Name of the relation field (e.g., 'tags', 'comments')
|
|
345
|
-
* @returns New AggregateChanges containing only operations for this relation
|
|
346
|
-
*
|
|
347
|
-
* @example
|
|
348
|
-
* ```typescript
|
|
349
|
-
* const tagChanges = changes.forRelation('tags');
|
|
350
|
-
* // Contains only creates/deletes for the 'tags' relation
|
|
351
|
-
* ```
|
|
352
|
-
*/
|
|
353
|
-
forRelation(relationField: string): AggregateChanges<TEntityMap> {
|
|
354
|
-
const filtered = this.ops.filter(
|
|
355
|
-
(op) => op.relationField === relationField
|
|
356
|
-
);
|
|
357
|
-
return new AggregateChanges<TEntityMap>(filtered);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Checks if there are create operations.
|
|
362
|
-
*/
|
|
363
|
-
hasCreates(): boolean {
|
|
364
|
-
return this.ops.some((op) => op.type === "create");
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Checks if there are update operations.
|
|
369
|
-
*/
|
|
370
|
-
hasUpdates(): boolean {
|
|
371
|
-
return this.ops.some((op) => op.type === "update");
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Checks if there are delete operations.
|
|
376
|
-
*/
|
|
377
|
-
hasDeletes(): boolean {
|
|
378
|
-
return this.ops.some((op) => op.type === "delete");
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Checks if there are any operations.
|
|
383
|
-
*/
|
|
384
|
-
hasChanges(): boolean {
|
|
385
|
-
return this.ops.length > 0;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Checks if there are no operations.
|
|
390
|
-
*/
|
|
391
|
-
isEmpty(): boolean {
|
|
392
|
-
return this.ops.length === 0;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Returns the total number of operations.
|
|
397
|
-
*/
|
|
398
|
-
get count(): number {
|
|
399
|
-
return this.ops.length;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Returns the raw operations (for debug/testing).
|
|
404
|
-
*/
|
|
405
|
-
get rawOperations(): Operation[] {
|
|
406
|
-
return [...this.ops];
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Lists all entities that have changes.
|
|
411
|
-
*/
|
|
412
|
-
getAffectedEntities(): string[] {
|
|
413
|
-
const entities = new Set<string>();
|
|
414
|
-
this.ops.forEach((op) => entities.add(op.entity));
|
|
415
|
-
return Array.from(entities);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Lists all relation fields that have changes.
|
|
420
|
-
*/
|
|
421
|
-
getAffectedRelations(): string[] {
|
|
422
|
-
const relations = new Set<string>();
|
|
423
|
-
this.ops.forEach((op) => {
|
|
424
|
-
if (op.relationField) {
|
|
425
|
-
relations.add(op.relationField);
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
return Array.from(relations);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Clears all operations.
|
|
433
|
-
*/
|
|
434
|
-
clear(): void {
|
|
435
|
-
this.ops = [];
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Creates a copy of the changes.
|
|
440
|
-
*/
|
|
441
|
-
clone(): AggregateChanges<TEntityMap> {
|
|
442
|
-
return new AggregateChanges<TEntityMap>([...this.ops]);
|
|
443
|
-
}
|
|
444
|
-
}
|