metal-orm 1.1.11 → 1.1.18

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.
Files changed (182) hide show
  1. package/README.md +758 -769
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/package.json +100 -96
  5. package/scripts/generate-entities/schema.mjs +122 -122
  6. package/scripts/generate-entities/tree-detection.mjs +92 -92
  7. package/scripts/inflection/pt-br.mjs +468 -468
  8. package/scripts/run-eslint.mjs +33 -34
  9. package/scripts/show-sql-seed.sql +102 -0
  10. package/scripts/show-sql.mjs +321 -325
  11. package/src/codegen/naming-strategy.ts +54 -54
  12. package/src/codegen/typescript.ts +482 -482
  13. package/src/core/ast/adapters.ts +1 -1
  14. package/src/core/ast/builders.ts +123 -123
  15. package/src/core/ast/expression-builders.ts +745 -745
  16. package/src/core/ast/expression-nodes.ts +304 -304
  17. package/src/core/ast/expression-visitor.ts +212 -212
  18. package/src/core/ast/expression.ts +20 -20
  19. package/src/core/ast/helpers.ts +14 -14
  20. package/src/core/ast/join.ts +18 -18
  21. package/src/core/ast/query.ts +200 -200
  22. package/src/core/ddl/dialects/base-schema-dialect.ts +1 -1
  23. package/src/core/ddl/dialects/mssql-schema-dialect.ts +1 -1
  24. package/src/core/ddl/dialects/mysql-schema-dialect.ts +1 -1
  25. package/src/core/ddl/dialects/postgres-schema-dialect.ts +1 -1
  26. package/src/core/ddl/dialects/render-reference.test.ts +70 -70
  27. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +1 -1
  28. package/src/core/ddl/introspect/catalogs/index.ts +5 -5
  29. package/src/core/ddl/introspect/catalogs/postgres.ts +146 -146
  30. package/src/core/ddl/introspect/context.ts +15 -15
  31. package/src/core/ddl/introspect/functions/postgres.ts +35 -35
  32. package/src/core/ddl/introspect/registry.ts +40 -40
  33. package/src/core/ddl/introspect/run-select.ts +35 -35
  34. package/src/core/ddl/introspect/utils.ts +56 -56
  35. package/src/core/ddl/naming-strategy.ts +16 -16
  36. package/src/core/ddl/schema-dialect.ts +56 -56
  37. package/src/core/ddl/schema-diff.ts +234 -234
  38. package/src/core/ddl/schema-generator.ts +1 -1
  39. package/src/core/ddl/schema-introspect.ts +25 -25
  40. package/src/core/ddl/sql-writing.ts +171 -171
  41. package/src/core/dialect/abstract.ts +541 -541
  42. package/src/core/dialect/base/cte-compiler.ts +33 -33
  43. package/src/core/dialect/base/function-table-formatter.ts +103 -103
  44. package/src/core/dialect/base/groupby-compiler.ts +21 -21
  45. package/src/core/dialect/base/join-compiler.ts +25 -25
  46. package/src/core/dialect/base/orderby-compiler.ts +35 -35
  47. package/src/core/dialect/base/pagination-strategy.ts +32 -32
  48. package/src/core/dialect/base/returning-strategy.ts +57 -57
  49. package/src/core/dialect/base/sql-dialect.ts +294 -294
  50. package/src/core/dialect/dialect-factory.ts +91 -91
  51. package/src/core/dialect/mssql/functions.ts +120 -120
  52. package/src/core/dialect/mssql/index.ts +317 -317
  53. package/src/core/dialect/mysql/functions.ts +103 -103
  54. package/src/core/dialect/mysql/index.ts +105 -105
  55. package/src/core/dialect/postgres/functions.ts +108 -108
  56. package/src/core/dialect/postgres/index.ts +91 -91
  57. package/src/core/dialect/sqlite/functions.ts +129 -129
  58. package/src/core/dialect/sqlite/index.ts +67 -67
  59. package/src/core/driver/database-driver.ts +19 -19
  60. package/src/core/driver/mssql-driver.ts +23 -23
  61. package/src/core/driver/mysql-driver.ts +23 -23
  62. package/src/core/driver/postgres-driver.ts +23 -23
  63. package/src/core/driver/sqlite-driver.ts +23 -23
  64. package/src/core/execution/db-executor.ts +92 -92
  65. package/src/core/execution/executors/better-sqlite3-executor.ts +90 -90
  66. package/src/core/execution/executors/mssql-executor.ts +184 -184
  67. package/src/core/execution/executors/mysql-executor.ts +88 -88
  68. package/src/core/execution/executors/postgres-executor.ts +26 -26
  69. package/src/core/execution/executors/sqlite-executor.ts +42 -42
  70. package/src/core/execution/pooling/pool-types.ts +30 -30
  71. package/src/core/execution/pooling/pool.ts +275 -275
  72. package/src/core/functions/array.ts +35 -35
  73. package/src/core/functions/control-flow.ts +79 -79
  74. package/src/core/functions/datetime.ts +237 -237
  75. package/src/core/functions/json.ts +70 -70
  76. package/src/core/functions/numeric.ts +297 -297
  77. package/src/core/functions/text.ts +334 -334
  78. package/src/core/functions/types.ts +33 -33
  79. package/src/core/hydration/types.ts +51 -51
  80. package/src/core/sql/sql-operator-config.ts +39 -39
  81. package/src/core/sql/sql.ts +152 -152
  82. package/src/decorators/column-decorator.ts +87 -87
  83. package/src/decorators/decorator-metadata.ts +143 -143
  84. package/src/decorators/entity.ts +93 -93
  85. package/src/decorators/index.ts +39 -39
  86. package/src/decorators/relations.ts +231 -231
  87. package/src/decorators/transformers/built-in/string-transformers.ts +231 -231
  88. package/src/decorators/transformers/transformer-decorators.ts +134 -134
  89. package/src/decorators/transformers/transformer-executor.ts +171 -171
  90. package/src/decorators/transformers/transformer-metadata.ts +73 -73
  91. package/src/decorators/validators/built-in/br-cep-validator.ts +55 -55
  92. package/src/decorators/validators/built-in/br-cnpj-validator.ts +105 -105
  93. package/src/decorators/validators/built-in/br-cpf-validator.ts +103 -103
  94. package/src/decorators/validators/country-validator-registry.ts +64 -64
  95. package/src/decorators/validators/country-validators-decorators.ts +203 -203
  96. package/src/decorators/validators/country-validators.ts +105 -105
  97. package/src/decorators/validators/validator-metadata.ts +59 -59
  98. package/src/dto/dto-types.ts +226 -226
  99. package/src/dto/filter-types.ts +141 -141
  100. package/src/dto/index.ts +95 -95
  101. package/src/dto/openapi/generators/base.ts +29 -29
  102. package/src/dto/openapi/generators/column.ts +37 -37
  103. package/src/dto/openapi/generators/dto.ts +91 -91
  104. package/src/dto/openapi/generators/filter.ts +75 -75
  105. package/src/dto/openapi/generators/nested-dto.ts +618 -618
  106. package/src/dto/openapi/generators/pagination.ts +111 -111
  107. package/src/dto/openapi/generators/relation-filter.ts +228 -228
  108. package/src/dto/openapi/index.ts +12 -12
  109. package/src/dto/openapi/types.ts +101 -101
  110. package/src/dto/openapi/utilities.ts +95 -95
  111. package/src/dto/pagination-utils.ts +150 -150
  112. package/src/dto/transform.ts +197 -197
  113. package/src/index.ts +86 -86
  114. package/src/orm/als.ts +58 -58
  115. package/src/orm/domain-event-bus.ts +119 -119
  116. package/src/orm/entity-context.ts +86 -86
  117. package/src/orm/entity-materializer.ts +159 -159
  118. package/src/orm/entity-meta.ts +97 -97
  119. package/src/orm/entity-registry.ts +39 -39
  120. package/src/orm/entity-relation-cache.ts +39 -39
  121. package/src/orm/execute.ts +194 -194
  122. package/src/orm/execution-context.ts +18 -18
  123. package/src/orm/hydration-context.ts +26 -26
  124. package/src/orm/hydration.ts +112 -112
  125. package/src/orm/identity-map.ts +61 -61
  126. package/src/orm/interceptor-pipeline.ts +16 -16
  127. package/src/orm/lazy-batch/belongs-to-many.ts +119 -119
  128. package/src/orm/lazy-batch/belongs-to.ts +108 -108
  129. package/src/orm/lazy-batch/has-many.ts +69 -69
  130. package/src/orm/lazy-batch/has-one.ts +68 -68
  131. package/src/orm/lazy-batch/shared.ts +125 -125
  132. package/src/orm/orm-session.ts +620 -620
  133. package/src/orm/orm.ts +34 -34
  134. package/src/orm/pooled-executor-factory.ts +121 -121
  135. package/src/orm/query-logger.ts +40 -40
  136. package/src/orm/relation-change-processor.ts +420 -420
  137. package/src/orm/relations/belongs-to.ts +143 -143
  138. package/src/orm/relations/has-many.ts +204 -204
  139. package/src/orm/relations/has-one.ts +174 -174
  140. package/src/orm/relations/many-to-many.ts +288 -288
  141. package/src/orm/relations/morph-many.ts +156 -156
  142. package/src/orm/relations/morph-one.ts +151 -151
  143. package/src/orm/relations/morph-to.ts +162 -162
  144. package/src/orm/runtime-types.ts +92 -92
  145. package/src/orm/save-graph.ts +561 -561
  146. package/src/orm/transaction-runner.ts +24 -24
  147. package/src/orm/unit-of-work.ts +426 -426
  148. package/src/query/index.ts +69 -69
  149. package/src/query/target.ts +46 -46
  150. package/src/query-builder/column-selector.ts +81 -81
  151. package/src/query-builder/delete.ts +141 -141
  152. package/src/query-builder/insert-query-state.ts +149 -149
  153. package/src/query-builder/insert.ts +117 -117
  154. package/src/query-builder/query-ast-service.ts +287 -287
  155. package/src/query-builder/query-resolution.ts +78 -78
  156. package/src/query-builder/raw-column-parser.ts +38 -38
  157. package/src/query-builder/relation-filter-utils.ts +153 -153
  158. package/src/query-builder/relation-join-planner.ts +10 -10
  159. package/src/query-builder/relation-manager.ts +87 -87
  160. package/src/query-builder/relation-projection-helper.ts +102 -102
  161. package/src/query-builder/relation-types.ts +9 -9
  162. package/src/query-builder/relation-utils.ts +15 -15
  163. package/src/query-builder/select/cte-facet.ts +40 -40
  164. package/src/query-builder/select/from-facet.ts +80 -80
  165. package/src/query-builder/select/join-facet.ts +62 -62
  166. package/src/query-builder/select/predicate-facet.ts +104 -104
  167. package/src/query-builder/select/projection-facet.ts +70 -70
  168. package/src/query-builder/select/relation-facet.ts +81 -81
  169. package/src/query-builder/select/setop-facet.ts +36 -36
  170. package/src/query-builder/select-helpers.ts +1 -1
  171. package/src/query-builder/select-query-builder-deps.ts +130 -130
  172. package/src/query-builder/select-query-state.ts +207 -207
  173. package/src/query-builder/update.ts +151 -151
  174. package/src/schema/column-types.ts +322 -322
  175. package/src/schema/table-guards.ts +37 -37
  176. package/src/schema/table.ts +281 -281
  177. package/src/tree/index.ts +13 -13
  178. package/src/tree/nested-set-strategy.ts +545 -545
  179. package/src/tree/tree-decorator.ts +397 -397
  180. package/src/tree/tree-manager.ts +754 -754
  181. package/src/tree/tree-query.ts +427 -427
  182. package/src/tree/tree-types.ts +299 -299
@@ -1,134 +1,134 @@
1
- import { resolveFieldDecoratorInfo } from '../decorator-metadata.js';
2
- import type { TransformerMetadata } from './transformer-metadata.js';
3
- import {
4
- TrimTransformer,
5
- CaseTransformer,
6
- AlphanumericValidator,
7
- EmailValidator,
8
- LengthValidator,
9
- PatternValidator
10
- } from './built-in/string-transformers.js';
11
-
12
- const registerTransformerMetadata = (
13
- targetOrValue: unknown,
14
- contextOrProperty: unknown,
15
- metadata: Partial<TransformerMetadata>
16
- ): void => {
17
- const { propertyName, bag } = resolveFieldDecoratorInfo(
18
- targetOrValue,
19
- contextOrProperty,
20
- 'Transformer'
21
- );
22
-
23
- // Find existing transformer metadata for this property
24
- let existing = bag.transformers.find(t => t.propertyName === propertyName);
25
-
26
- if (!existing) {
27
- existing = {
28
- propertyName,
29
- metadata: {
30
- propertyName,
31
- transformers: [],
32
- validators: [],
33
- sanitizers: [],
34
- executionOrder: 'both'
35
- }
36
- };
37
- bag.transformers.push(existing);
38
- }
39
-
40
- // Merge metadata
41
- if (metadata.transformers) {
42
- existing.metadata.transformers.push(...metadata.transformers);
43
- }
44
- if (metadata.validators) {
45
- existing.metadata.validators.push(...metadata.validators);
46
- }
47
- if (metadata.sanitizers) {
48
- existing.metadata.sanitizers.push(...metadata.sanitizers);
49
- }
50
- if (metadata.executionOrder) {
51
- existing.metadata.executionOrder = metadata.executionOrder;
52
- }
53
- };
54
-
55
- // Trim decorator
56
- export function Trim(options?: { trimStart?: boolean; trimEnd?: boolean; trimAll?: boolean }) {
57
- return function (targetOrValue: unknown, contextOrProperty: unknown) {
58
- registerTransformerMetadata(targetOrValue, contextOrProperty, {
59
- sanitizers: [new TrimTransformer(options)]
60
- });
61
- };
62
- }
63
-
64
- // Lower case decorator
65
- export function Lower() {
66
- return function (targetOrValue: unknown, contextOrProperty: unknown) {
67
- registerTransformerMetadata(targetOrValue, contextOrProperty, {
68
- sanitizers: [new CaseTransformer('lower')]
69
- });
70
- };
71
- }
72
-
73
- // Upper case decorator
74
- export function Upper() {
75
- return function (targetOrValue: unknown, contextOrProperty: unknown) {
76
- registerTransformerMetadata(targetOrValue, contextOrProperty, {
77
- sanitizers: [new CaseTransformer('upper')]
78
- });
79
- };
80
- }
81
-
82
- // Capitalize decorator
83
- export function Capitalize() {
84
- return function (targetOrValue: unknown, contextOrProperty: unknown) {
85
- registerTransformerMetadata(targetOrValue, contextOrProperty, {
86
- sanitizers: [new CaseTransformer('capitalize')]
87
- });
88
- };
89
- }
90
-
91
- // Title case decorator
92
- export function Title() {
93
- return function (targetOrValue: unknown, contextOrProperty: unknown) {
94
- registerTransformerMetadata(targetOrValue, contextOrProperty, {
95
- sanitizers: [new CaseTransformer('title')]
96
- });
97
- };
98
- }
99
-
100
- // Alphanumeric decorator
101
- export function Alphanumeric(options?: { allowSpaces?: boolean; allowUnderscores?: boolean; allowHyphens?: boolean }) {
102
- return function (targetOrValue: unknown, contextOrProperty: unknown) {
103
- registerTransformerMetadata(targetOrValue, contextOrProperty, {
104
- validators: [new AlphanumericValidator(options)]
105
- });
106
- };
107
- }
108
-
109
- // Email decorator
110
- export function Email(options?: { allowPlus?: boolean; requireTLD?: boolean }) {
111
- return function (targetOrValue: unknown, contextOrProperty: unknown) {
112
- registerTransformerMetadata(targetOrValue, contextOrProperty, {
113
- validators: [new EmailValidator(options)]
114
- });
115
- };
116
- }
117
-
118
- // Length decorator
119
- export function Length(options: { min?: number; max?: number; exact?: number }) {
120
- return function (targetOrValue: unknown, contextOrProperty: unknown) {
121
- registerTransformerMetadata(targetOrValue, contextOrProperty, {
122
- validators: [new LengthValidator(options)]
123
- });
124
- };
125
- }
126
-
127
- // Pattern decorator
128
- export function Pattern(options: { pattern: RegExp; flags?: string; errorMessage?: string; replacement?: string }) {
129
- return function (targetOrValue: unknown, contextOrProperty: unknown) {
130
- registerTransformerMetadata(targetOrValue, contextOrProperty, {
131
- validators: [new PatternValidator(options)]
132
- });
133
- };
134
- }
1
+ import { resolveFieldDecoratorInfo } from '../decorator-metadata.js';
2
+ import type { TransformerMetadata } from './transformer-metadata.js';
3
+ import {
4
+ TrimTransformer,
5
+ CaseTransformer,
6
+ AlphanumericValidator,
7
+ EmailValidator,
8
+ LengthValidator,
9
+ PatternValidator
10
+ } from './built-in/string-transformers.js';
11
+
12
+ const registerTransformerMetadata = (
13
+ targetOrValue: unknown,
14
+ contextOrProperty: unknown,
15
+ metadata: Partial<TransformerMetadata>
16
+ ): void => {
17
+ const { propertyName, bag } = resolveFieldDecoratorInfo(
18
+ targetOrValue,
19
+ contextOrProperty,
20
+ 'Transformer'
21
+ );
22
+
23
+ // Find existing transformer metadata for this property
24
+ let existing = bag.transformers.find(t => t.propertyName === propertyName);
25
+
26
+ if (!existing) {
27
+ existing = {
28
+ propertyName,
29
+ metadata: {
30
+ propertyName,
31
+ transformers: [],
32
+ validators: [],
33
+ sanitizers: [],
34
+ executionOrder: 'both'
35
+ }
36
+ };
37
+ bag.transformers.push(existing);
38
+ }
39
+
40
+ // Merge metadata
41
+ if (metadata.transformers) {
42
+ existing.metadata.transformers.push(...metadata.transformers);
43
+ }
44
+ if (metadata.validators) {
45
+ existing.metadata.validators.push(...metadata.validators);
46
+ }
47
+ if (metadata.sanitizers) {
48
+ existing.metadata.sanitizers.push(...metadata.sanitizers);
49
+ }
50
+ if (metadata.executionOrder) {
51
+ existing.metadata.executionOrder = metadata.executionOrder;
52
+ }
53
+ };
54
+
55
+ // Trim decorator
56
+ export function Trim(options?: { trimStart?: boolean; trimEnd?: boolean; trimAll?: boolean }) {
57
+ return function (targetOrValue: unknown, contextOrProperty: unknown) {
58
+ registerTransformerMetadata(targetOrValue, contextOrProperty, {
59
+ sanitizers: [new TrimTransformer(options)]
60
+ });
61
+ };
62
+ }
63
+
64
+ // Lower case decorator
65
+ export function Lower() {
66
+ return function (targetOrValue: unknown, contextOrProperty: unknown) {
67
+ registerTransformerMetadata(targetOrValue, contextOrProperty, {
68
+ sanitizers: [new CaseTransformer('lower')]
69
+ });
70
+ };
71
+ }
72
+
73
+ // Upper case decorator
74
+ export function Upper() {
75
+ return function (targetOrValue: unknown, contextOrProperty: unknown) {
76
+ registerTransformerMetadata(targetOrValue, contextOrProperty, {
77
+ sanitizers: [new CaseTransformer('upper')]
78
+ });
79
+ };
80
+ }
81
+
82
+ // Capitalize decorator
83
+ export function Capitalize() {
84
+ return function (targetOrValue: unknown, contextOrProperty: unknown) {
85
+ registerTransformerMetadata(targetOrValue, contextOrProperty, {
86
+ sanitizers: [new CaseTransformer('capitalize')]
87
+ });
88
+ };
89
+ }
90
+
91
+ // Title case decorator
92
+ export function Title() {
93
+ return function (targetOrValue: unknown, contextOrProperty: unknown) {
94
+ registerTransformerMetadata(targetOrValue, contextOrProperty, {
95
+ sanitizers: [new CaseTransformer('title')]
96
+ });
97
+ };
98
+ }
99
+
100
+ // Alphanumeric decorator
101
+ export function Alphanumeric(options?: { allowSpaces?: boolean; allowUnderscores?: boolean; allowHyphens?: boolean }) {
102
+ return function (targetOrValue: unknown, contextOrProperty: unknown) {
103
+ registerTransformerMetadata(targetOrValue, contextOrProperty, {
104
+ validators: [new AlphanumericValidator(options)]
105
+ });
106
+ };
107
+ }
108
+
109
+ // Email decorator
110
+ export function Email(options?: { allowPlus?: boolean; requireTLD?: boolean }) {
111
+ return function (targetOrValue: unknown, contextOrProperty: unknown) {
112
+ registerTransformerMetadata(targetOrValue, contextOrProperty, {
113
+ validators: [new EmailValidator(options)]
114
+ });
115
+ };
116
+ }
117
+
118
+ // Length decorator
119
+ export function Length(options: { min?: number; max?: number; exact?: number }) {
120
+ return function (targetOrValue: unknown, contextOrProperty: unknown) {
121
+ registerTransformerMetadata(targetOrValue, contextOrProperty, {
122
+ validators: [new LengthValidator(options)]
123
+ });
124
+ };
125
+ }
126
+
127
+ // Pattern decorator
128
+ export function Pattern(options: { pattern: RegExp; flags?: string; errorMessage?: string; replacement?: string }) {
129
+ return function (targetOrValue: unknown, contextOrProperty: unknown) {
130
+ registerTransformerMetadata(targetOrValue, contextOrProperty, {
131
+ validators: [new PatternValidator(options)]
132
+ });
133
+ };
134
+ }
@@ -1,171 +1,171 @@
1
- import { getEntityMetadata } from '../../orm/entity-metadata.js';
2
- import type { EntityConstructor, EntityMetadata } from '../../orm/entity-metadata.js';
3
- import type {
4
- TransformContext,
5
- TransformerConfig,
6
- PropertyTransformer,
7
- PropertyValidator,
8
- PropertySanitizer,
9
- TransformerMetadata
10
- } from './transformer-metadata.js';
11
-
12
- export class TransformerExecutor {
13
- private config: TransformerConfig = {
14
- auto: false,
15
- execute: 'both',
16
- stopOnFirstError: false
17
- };
18
-
19
- constructor(options?: TransformerConfig) {
20
- this.config = { ...this.config, ...options };
21
- }
22
-
23
- /**
24
- * Applies all transformers to an entity instance
25
- */
26
- async applyTransformers(entity: Record<string, unknown>, entityClass: EntityConstructor, context: Partial<TransformContext> = {}): Promise<void> {
27
- const meta = getEntityMetadata(entityClass);
28
- if (!meta || !meta.transformers) return;
29
-
30
- // Get column types from entity metadata
31
- const columnTypes = this.getColumnTypes(meta);
32
-
33
- for (const [propertyName, transformerMeta] of Object.entries(meta.transformers)) {
34
- const typedMeta = transformerMeta as unknown as TransformerMetadata;
35
-
36
- // Skip if transformer should not execute in current context
37
- if (!this.shouldExecute(typedMeta.executionOrder, context.isUpdate)) {
38
- continue;
39
- }
40
-
41
- // Get current value
42
- const value = entity[propertyName];
43
-
44
- // Skip if value is null or undefined
45
- if (value === null || value === undefined) continue;
46
-
47
- // Create transform context
48
- const transformContext: TransformContext = {
49
- entityName: entityClass.name,
50
- propertyName,
51
- columnType: columnTypes[propertyName] || 'VARCHAR',
52
- isUpdate: context.isUpdate || false,
53
- originalValue: context.originalValue,
54
- autoTransform: this.config.auto
55
- };
56
-
57
- try {
58
- // Apply sanitizers first
59
- let transformedValue = this.applySanitizers(value, typedMeta.sanitizers, transformContext);
60
-
61
- // Apply transformers
62
- transformedValue = this.applyTransformersToValue(transformedValue, typedMeta.transformers, transformContext);
63
-
64
- // Apply validation
65
- this.applyValidators(transformedValue, typedMeta.validators, transformContext);
66
-
67
- // Update the entity with the transformed value
68
- entity[propertyName] = transformedValue;
69
- } catch (_error) {
70
- // Handle errors based on config
71
- if (this.config.stopOnFirstError) {
72
- throw _error;
73
- }
74
- // Continue with other properties
75
- }
76
- }
77
- }
78
-
79
- private shouldExecute(executionOrder: 'before-save' | 'after-load' | 'both', isUpdate: boolean): boolean {
80
- if (executionOrder === 'both') return true;
81
- if (isUpdate && executionOrder === 'before-save') return true;
82
- if (!isUpdate && executionOrder === 'after-load') return true;
83
- return false;
84
- }
85
-
86
- private getColumnTypes(meta: EntityMetadata): Record<string, string> {
87
- if (!meta || !meta.columns) return {};
88
-
89
- const columnTypes: Record<string, string> = {};
90
- for (const [propertyName, columnDef] of Object.entries(meta.columns)) {
91
- columnTypes[propertyName] = columnDef.type || 'VARCHAR';
92
- }
93
- return columnTypes;
94
- }
95
-
96
- private applySanitizers(value: unknown, sanitizers: PropertySanitizer[], context: TransformContext): unknown {
97
- let result = value;
98
- for (const sanitizer of sanitizers) {
99
- try {
100
- result = sanitizer.sanitize(result, context);
101
- } catch (_error: unknown) {
102
- // Continue with next sanitizer if this one fails
103
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
104
- const error = _error;
105
- }
106
- }
107
- return result;
108
- }
109
-
110
- private applyTransformersToValue(value: unknown, transformers: PropertyTransformer[], context: TransformContext): unknown {
111
- let result = value;
112
- for (const transformer of transformers) {
113
- try {
114
- result = transformer.transform(result, context);
115
- } catch (_error: unknown) {
116
- // Continue with next transformer if this one fails
117
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
118
- const error = _error;
119
- }
120
- }
121
- return result;
122
- }
123
-
124
- private applyValidators(value: unknown, validators: PropertyValidator[], context: TransformContext): void {
125
- const errors: string[] = [];
126
-
127
- for (const validator of validators) {
128
- try {
129
- const result = validator.validate(value, context);
130
-
131
- if (!result.isValid) {
132
- if (context.autoTransform && 'autoTransform' in validator) {
133
- const autoResult = (validator as { autoTransform?: (v: unknown, c: TransformContext) => { success: boolean; correctedValue?: unknown; message?: string } }).autoTransform?.(value, context);
134
- if (typeof autoResult === 'object' && autoResult !== null && 'success' in autoResult && autoResult.success && 'correctedValue' in autoResult && autoResult.correctedValue !== undefined) {
135
- // Auto-correct the value
136
- (context as { correctedValue?: unknown }).correctedValue = autoResult.correctedValue;
137
- return; // Value is now valid
138
- }
139
- }
140
- errors.push(result.error || `Validation failed for ${validator.name}`);
141
-
142
- if (this.config.stopOnFirstError) {
143
- break;
144
- }
145
- }
146
- } catch (_error: unknown) {
147
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
148
- const error = _error;
149
- if (_error instanceof Error) {
150
- errors.push(_error.message);
151
- } else {
152
- errors.push('Validation failed');
153
- }
154
- if (this.config.stopOnFirstError) {
155
- break;
156
- }
157
- }
158
- }
159
-
160
- if (errors.length > 0) {
161
- throw new Error(errors.join(', '));
162
- }
163
- }
164
- }
165
-
166
- /**
167
- * Creates a transformer executor with specified config
168
- */
169
- export const createTransformerExecutor = (config?: TransformerConfig): TransformerExecutor => {
170
- return new TransformerExecutor(config);
171
- };
1
+ import { getEntityMetadata } from '../../orm/entity-metadata.js';
2
+ import type { EntityConstructor, EntityMetadata } from '../../orm/entity-metadata.js';
3
+ import type {
4
+ TransformContext,
5
+ TransformerConfig,
6
+ PropertyTransformer,
7
+ PropertyValidator,
8
+ PropertySanitizer,
9
+ TransformerMetadata
10
+ } from './transformer-metadata.js';
11
+
12
+ export class TransformerExecutor {
13
+ private config: TransformerConfig = {
14
+ auto: false,
15
+ execute: 'both',
16
+ stopOnFirstError: false
17
+ };
18
+
19
+ constructor(options?: TransformerConfig) {
20
+ this.config = { ...this.config, ...options };
21
+ }
22
+
23
+ /**
24
+ * Applies all transformers to an entity instance
25
+ */
26
+ async applyTransformers(entity: Record<string, unknown>, entityClass: EntityConstructor, context: Partial<TransformContext> = {}): Promise<void> {
27
+ const meta = getEntityMetadata(entityClass);
28
+ if (!meta || !meta.transformers) return;
29
+
30
+ // Get column types from entity metadata
31
+ const columnTypes = this.getColumnTypes(meta);
32
+
33
+ for (const [propertyName, transformerMeta] of Object.entries(meta.transformers)) {
34
+ const typedMeta = transformerMeta as unknown as TransformerMetadata;
35
+
36
+ // Skip if transformer should not execute in current context
37
+ if (!this.shouldExecute(typedMeta.executionOrder, context.isUpdate)) {
38
+ continue;
39
+ }
40
+
41
+ // Get current value
42
+ const value = entity[propertyName];
43
+
44
+ // Skip if value is null or undefined
45
+ if (value === null || value === undefined) continue;
46
+
47
+ // Create transform context
48
+ const transformContext: TransformContext = {
49
+ entityName: entityClass.name,
50
+ propertyName,
51
+ columnType: columnTypes[propertyName] || 'VARCHAR',
52
+ isUpdate: context.isUpdate || false,
53
+ originalValue: context.originalValue,
54
+ autoTransform: this.config.auto
55
+ };
56
+
57
+ try {
58
+ // Apply sanitizers first
59
+ let transformedValue = this.applySanitizers(value, typedMeta.sanitizers, transformContext);
60
+
61
+ // Apply transformers
62
+ transformedValue = this.applyTransformersToValue(transformedValue, typedMeta.transformers, transformContext);
63
+
64
+ // Apply validation
65
+ this.applyValidators(transformedValue, typedMeta.validators, transformContext);
66
+
67
+ // Update the entity with the transformed value
68
+ entity[propertyName] = transformedValue;
69
+ } catch (_error) {
70
+ // Handle errors based on config
71
+ if (this.config.stopOnFirstError) {
72
+ throw _error;
73
+ }
74
+ // Continue with other properties
75
+ }
76
+ }
77
+ }
78
+
79
+ private shouldExecute(executionOrder: 'before-save' | 'after-load' | 'both', isUpdate: boolean): boolean {
80
+ if (executionOrder === 'both') return true;
81
+ if (isUpdate && executionOrder === 'before-save') return true;
82
+ if (!isUpdate && executionOrder === 'after-load') return true;
83
+ return false;
84
+ }
85
+
86
+ private getColumnTypes(meta: EntityMetadata): Record<string, string> {
87
+ if (!meta || !meta.columns) return {};
88
+
89
+ const columnTypes: Record<string, string> = {};
90
+ for (const [propertyName, columnDef] of Object.entries(meta.columns)) {
91
+ columnTypes[propertyName] = columnDef.type || 'VARCHAR';
92
+ }
93
+ return columnTypes;
94
+ }
95
+
96
+ private applySanitizers(value: unknown, sanitizers: PropertySanitizer[], context: TransformContext): unknown {
97
+ let result = value;
98
+ for (const sanitizer of sanitizers) {
99
+ try {
100
+ result = sanitizer.sanitize(result, context);
101
+ } catch (_error: unknown) {
102
+ // Continue with next sanitizer if this one fails
103
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
104
+ const error = _error;
105
+ }
106
+ }
107
+ return result;
108
+ }
109
+
110
+ private applyTransformersToValue(value: unknown, transformers: PropertyTransformer[], context: TransformContext): unknown {
111
+ let result = value;
112
+ for (const transformer of transformers) {
113
+ try {
114
+ result = transformer.transform(result, context);
115
+ } catch (_error: unknown) {
116
+ // Continue with next transformer if this one fails
117
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
118
+ const error = _error;
119
+ }
120
+ }
121
+ return result;
122
+ }
123
+
124
+ private applyValidators(value: unknown, validators: PropertyValidator[], context: TransformContext): void {
125
+ const errors: string[] = [];
126
+
127
+ for (const validator of validators) {
128
+ try {
129
+ const result = validator.validate(value, context);
130
+
131
+ if (!result.isValid) {
132
+ if (context.autoTransform && 'autoTransform' in validator) {
133
+ const autoResult = (validator as { autoTransform?: (v: unknown, c: TransformContext) => { success: boolean; correctedValue?: unknown; message?: string } }).autoTransform?.(value, context);
134
+ if (typeof autoResult === 'object' && autoResult !== null && 'success' in autoResult && autoResult.success && 'correctedValue' in autoResult && autoResult.correctedValue !== undefined) {
135
+ // Auto-correct the value
136
+ (context as { correctedValue?: unknown }).correctedValue = autoResult.correctedValue;
137
+ return; // Value is now valid
138
+ }
139
+ }
140
+ errors.push(result.error || `Validation failed for ${validator.name}`);
141
+
142
+ if (this.config.stopOnFirstError) {
143
+ break;
144
+ }
145
+ }
146
+ } catch (_error: unknown) {
147
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
148
+ const error = _error;
149
+ if (_error instanceof Error) {
150
+ errors.push(_error.message);
151
+ } else {
152
+ errors.push('Validation failed');
153
+ }
154
+ if (this.config.stopOnFirstError) {
155
+ break;
156
+ }
157
+ }
158
+ }
159
+
160
+ if (errors.length > 0) {
161
+ throw new Error(errors.join(', '));
162
+ }
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Creates a transformer executor with specified config
168
+ */
169
+ export const createTransformerExecutor = (config?: TransformerConfig): TransformerExecutor => {
170
+ return new TransformerExecutor(config);
171
+ };