metal-orm 1.1.11 → 1.1.16

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,87 +1,87 @@
1
- import { ColumnDef, ColumnType } from '../schema/column-types.js';
2
- import { ColumnDefLike } from '../orm/entity-metadata.js';
3
- import { resolveFieldDecoratorInfo } from './decorator-metadata.js';
4
-
5
- /**
6
- * Options for defining a column in an entity.
7
- */
8
- export interface ColumnOptions {
9
- type: ColumnType;
10
- args?: ColumnDef['args'];
11
- dialectTypes?: ColumnDef['dialectTypes'];
12
- notNull?: boolean;
13
- primary?: boolean;
14
- tsType?: ColumnDef['tsType'];
15
- name?: string;
16
- }
17
-
18
- /**
19
- * Input type for column definitions, either as options object or direct ColumnDef.
20
- */
21
- export type ColumnInput = ColumnOptions | ColumnDef;
22
-
23
- const normalizeColumnInput = (input: ColumnInput): ColumnDefLike => {
24
- const asOptions = input as ColumnOptions;
25
- const asDefinition = input as ColumnDef;
26
- const column: ColumnDefLike = {
27
- type: asOptions.type ?? asDefinition.type,
28
- args: asOptions.args ?? asDefinition.args,
29
- dialectTypes: asOptions.dialectTypes ?? asDefinition.dialectTypes,
30
- notNull: asOptions.notNull ?? asDefinition.notNull,
31
- primary: asOptions.primary ?? asDefinition.primary,
32
- tsType: asDefinition.tsType ?? asOptions.tsType,
33
- unique: asDefinition.unique,
34
- default: asDefinition.default,
35
- autoIncrement: asDefinition.autoIncrement,
36
- generated: asDefinition.generated,
37
- check: asDefinition.check,
38
- references: asDefinition.references,
39
- comment: asDefinition.comment,
40
- name: asOptions.name ?? asDefinition.name
41
- };
42
-
43
- if (!column.type) {
44
- throw new Error('Column decorator requires a column type');
45
- }
46
-
47
- return column;
48
- };
49
-
50
- const registerColumnFromContext = (
51
- targetOrValue: unknown,
52
- contextOrProperty: unknown,
53
- column: ColumnDefLike
54
- ): void => {
55
- const { propertyName, bag } = resolveFieldDecoratorInfo(
56
- targetOrValue,
57
- contextOrProperty,
58
- 'Column'
59
- );
60
- if (!bag.columns.some(entry => entry.propertyName === propertyName)) {
61
- bag.columns.push({ propertyName, column: { ...column } });
62
- }
63
- };
64
-
65
- /**
66
- * Decorator to define a column on an entity property.
67
- * @param definition - The column definition or options.
68
- * @returns A property decorator that registers the column metadata.
69
- */
70
- export function Column(definition: ColumnInput) {
71
- const normalized = normalizeColumnInput(definition);
72
- return function (targetOrValue: unknown, contextOrProperty: unknown) {
73
- registerColumnFromContext(targetOrValue, contextOrProperty, normalized);
74
- };
75
- }
76
-
77
- /**
78
- * Decorator to define a primary key column on an entity property.
79
- * Sets the primary flag to true and delegates to Column decorator.
80
- * @param definition - The column definition or options.
81
- * @returns A property decorator that registers the primary key column metadata.
82
- */
83
- export function PrimaryKey(definition: ColumnInput) {
84
- const normalized = normalizeColumnInput(definition);
85
- normalized.primary = true;
86
- return Column(normalized);
87
- }
1
+ import { ColumnDef, ColumnType } from '../schema/column-types.js';
2
+ import { ColumnDefLike } from '../orm/entity-metadata.js';
3
+ import { resolveFieldDecoratorInfo } from './decorator-metadata.js';
4
+
5
+ /**
6
+ * Options for defining a column in an entity.
7
+ */
8
+ export interface ColumnOptions {
9
+ type: ColumnType;
10
+ args?: ColumnDef['args'];
11
+ dialectTypes?: ColumnDef['dialectTypes'];
12
+ notNull?: boolean;
13
+ primary?: boolean;
14
+ tsType?: ColumnDef['tsType'];
15
+ name?: string;
16
+ }
17
+
18
+ /**
19
+ * Input type for column definitions, either as options object or direct ColumnDef.
20
+ */
21
+ export type ColumnInput = ColumnOptions | ColumnDef;
22
+
23
+ const normalizeColumnInput = (input: ColumnInput): ColumnDefLike => {
24
+ const asOptions = input as ColumnOptions;
25
+ const asDefinition = input as ColumnDef;
26
+ const column: ColumnDefLike = {
27
+ type: asOptions.type ?? asDefinition.type,
28
+ args: asOptions.args ?? asDefinition.args,
29
+ dialectTypes: asOptions.dialectTypes ?? asDefinition.dialectTypes,
30
+ notNull: asOptions.notNull ?? asDefinition.notNull,
31
+ primary: asOptions.primary ?? asDefinition.primary,
32
+ tsType: asDefinition.tsType ?? asOptions.tsType,
33
+ unique: asDefinition.unique,
34
+ default: asDefinition.default,
35
+ autoIncrement: asDefinition.autoIncrement,
36
+ generated: asDefinition.generated,
37
+ check: asDefinition.check,
38
+ references: asDefinition.references,
39
+ comment: asDefinition.comment,
40
+ name: asOptions.name ?? asDefinition.name
41
+ };
42
+
43
+ if (!column.type) {
44
+ throw new Error('Column decorator requires a column type');
45
+ }
46
+
47
+ return column;
48
+ };
49
+
50
+ const registerColumnFromContext = (
51
+ targetOrValue: unknown,
52
+ contextOrProperty: unknown,
53
+ column: ColumnDefLike
54
+ ): void => {
55
+ const { propertyName, bag } = resolveFieldDecoratorInfo(
56
+ targetOrValue,
57
+ contextOrProperty,
58
+ 'Column'
59
+ );
60
+ if (!bag.columns.some(entry => entry.propertyName === propertyName)) {
61
+ bag.columns.push({ propertyName, column: { ...column } });
62
+ }
63
+ };
64
+
65
+ /**
66
+ * Decorator to define a column on an entity property.
67
+ * @param definition - The column definition or options.
68
+ * @returns A property decorator that registers the column metadata.
69
+ */
70
+ export function Column(definition: ColumnInput) {
71
+ const normalized = normalizeColumnInput(definition);
72
+ return function (targetOrValue: unknown, contextOrProperty: unknown) {
73
+ registerColumnFromContext(targetOrValue, contextOrProperty, normalized);
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Decorator to define a primary key column on an entity property.
79
+ * Sets the primary flag to true and delegates to Column decorator.
80
+ * @param definition - The column definition or options.
81
+ * @returns A property decorator that registers the primary key column metadata.
82
+ */
83
+ export function PrimaryKey(definition: ColumnInput) {
84
+ const normalized = normalizeColumnInput(definition);
85
+ normalized.primary = true;
86
+ return Column(normalized);
87
+ }
@@ -1,143 +1,143 @@
1
- import { ColumnDefLike, RelationMetadata } from '../orm/entity-metadata.js';
2
- import type { TransformerMetadata } from './transformers/transformer-metadata.js';
3
-
4
- /**
5
- * Bag for storing decorator metadata during the decoration phase.
6
- */
7
- export interface DecoratorTreeMetadata {
8
- parentProperty?: string;
9
- childrenProperty?: string;
10
- }
11
-
12
- export interface DecoratorMetadataBag {
13
- columns: Array<{ propertyName: string; column: ColumnDefLike }>;
14
- relations: Array<{ propertyName: string; relation: RelationMetadata }>;
15
- transformers: Array<{ propertyName: string; metadata: TransformerMetadata }>;
16
- tree?: DecoratorTreeMetadata;
17
- }
18
-
19
- const METADATA_KEY = 'metal-orm:decorators';
20
- const LEGACY_METADATA_KEY = Symbol.for('metal-orm:decorators:legacy');
21
-
22
- type MetadataCarrier = {
23
- metadata?: Record<PropertyKey, unknown>;
24
- };
25
-
26
- type LegacyMetadataCarrier = {
27
- constructor?: object;
28
- };
29
-
30
- /**
31
- * Gets or creates a metadata bag for the given decorator context.
32
- * @param context - The decorator context with metadata support.
33
- * @returns The metadata bag.
34
- */
35
- export const getOrCreateMetadataBag = (context: MetadataCarrier): DecoratorMetadataBag => {
36
- const metadata = context.metadata || (context.metadata = {} as Record<PropertyKey, unknown>);
37
- let bag = metadata[METADATA_KEY] as DecoratorMetadataBag | undefined;
38
- if (!bag) {
39
- bag = { columns: [], relations: [], transformers: [] };
40
- metadata[METADATA_KEY] = bag;
41
- }
42
- return bag;
43
- };
44
-
45
- const getOrCreateMetadataBagOnConstructor = (ctor: object): DecoratorMetadataBag => {
46
- const carrier = ctor as Record<PropertyKey, unknown>;
47
- let bag = carrier[LEGACY_METADATA_KEY] as DecoratorMetadataBag | undefined;
48
- if (!bag) {
49
- bag = { columns: [], relations: [], transformers: [] };
50
- carrier[LEGACY_METADATA_KEY] = bag;
51
- }
52
- return bag;
53
- };
54
-
55
- /**
56
- * Reads the metadata bag from the given decorator context.
57
- * @param context - The decorator context with metadata support.
58
- * @returns The metadata bag if present.
59
- */
60
- export const readMetadataBag = (context: MetadataCarrier): DecoratorMetadataBag | undefined => {
61
- return context.metadata?.[METADATA_KEY] as DecoratorMetadataBag | undefined;
62
- };
63
-
64
- /**
65
- * Reads the metadata bag from a decorated constructor when using standard decorators.
66
- * @param ctor - The entity constructor.
67
- * @returns The metadata bag if present.
68
- */
69
- export const readMetadataBagFromConstructor = (ctor: object): DecoratorMetadataBag | undefined => {
70
- const metadataSymbol = (Symbol as { metadata?: symbol }).metadata;
71
- if (metadataSymbol) {
72
- const metadata = Reflect.get(ctor, metadataSymbol) as Record<PropertyKey, unknown> | undefined;
73
- const stage3Bag = metadata?.[METADATA_KEY] as DecoratorMetadataBag | undefined;
74
- if (stage3Bag) {
75
- return stage3Bag;
76
- }
77
- }
78
- return (ctor as Record<PropertyKey, unknown>)[LEGACY_METADATA_KEY] as DecoratorMetadataBag | undefined;
79
- };
80
-
81
- /**
82
- * Public helper to read decorator metadata from a class constructor.
83
- * @param ctor - The entity constructor.
84
- * @returns The metadata bag if present.
85
- */
86
- export const getDecoratorMetadata = (ctor: object): DecoratorMetadataBag | undefined =>
87
- readMetadataBagFromConstructor(ctor);
88
-
89
- const normalizePropertyName = (name: string | symbol): string => {
90
- if (typeof name === 'symbol') {
91
- return name.description ?? name.toString();
92
- }
93
- return name;
94
- };
95
-
96
- const isStage3FieldContext = (value: unknown): value is ClassFieldDecoratorContext => {
97
- return (
98
- typeof value === 'object' &&
99
- value !== null &&
100
- 'name' in value &&
101
- 'private' in value &&
102
- 'metadata' in value
103
- );
104
- };
105
-
106
- export interface ResolvedFieldDecoratorInfo {
107
- bag: DecoratorMetadataBag;
108
- propertyName: string;
109
- }
110
-
111
- export const resolveFieldDecoratorInfo = (
112
- targetOrValue: unknown,
113
- contextOrProperty: unknown,
114
- decoratorName: string
115
- ): ResolvedFieldDecoratorInfo => {
116
- if (isStage3FieldContext(contextOrProperty)) {
117
- if (!contextOrProperty.name) {
118
- throw new Error(`${decoratorName} decorator requires a property name`);
119
- }
120
- if (contextOrProperty.private) {
121
- throw new Error(`${decoratorName} decorator does not support private fields`);
122
- }
123
- return {
124
- propertyName: normalizePropertyName(contextOrProperty.name),
125
- bag: getOrCreateMetadataBag(contextOrProperty)
126
- };
127
- }
128
-
129
- if (typeof contextOrProperty === 'string' || typeof contextOrProperty === 'symbol') {
130
- const legacyTarget = targetOrValue as LegacyMetadataCarrier | undefined;
131
- const ctor =
132
- typeof legacyTarget === 'function' ? legacyTarget : legacyTarget?.constructor;
133
- if (!ctor || (typeof ctor !== 'function' && typeof ctor !== 'object')) {
134
- throw new Error(`${decoratorName} decorator requires a class field target`);
135
- }
136
- return {
137
- propertyName: normalizePropertyName(contextOrProperty),
138
- bag: getOrCreateMetadataBagOnConstructor(ctor)
139
- };
140
- }
141
-
142
- throw new Error(`${decoratorName} decorator received an unsupported decorator context`);
143
- };
1
+ import { ColumnDefLike, RelationMetadata } from '../orm/entity-metadata.js';
2
+ import type { TransformerMetadata } from './transformers/transformer-metadata.js';
3
+
4
+ /**
5
+ * Bag for storing decorator metadata during the decoration phase.
6
+ */
7
+ export interface DecoratorTreeMetadata {
8
+ parentProperty?: string;
9
+ childrenProperty?: string;
10
+ }
11
+
12
+ export interface DecoratorMetadataBag {
13
+ columns: Array<{ propertyName: string; column: ColumnDefLike }>;
14
+ relations: Array<{ propertyName: string; relation: RelationMetadata }>;
15
+ transformers: Array<{ propertyName: string; metadata: TransformerMetadata }>;
16
+ tree?: DecoratorTreeMetadata;
17
+ }
18
+
19
+ const METADATA_KEY = 'metal-orm:decorators';
20
+ const LEGACY_METADATA_KEY = Symbol.for('metal-orm:decorators:legacy');
21
+
22
+ type MetadataCarrier = {
23
+ metadata?: Record<PropertyKey, unknown>;
24
+ };
25
+
26
+ type LegacyMetadataCarrier = {
27
+ constructor?: object;
28
+ };
29
+
30
+ /**
31
+ * Gets or creates a metadata bag for the given decorator context.
32
+ * @param context - The decorator context with metadata support.
33
+ * @returns The metadata bag.
34
+ */
35
+ export const getOrCreateMetadataBag = (context: MetadataCarrier): DecoratorMetadataBag => {
36
+ const metadata = context.metadata || (context.metadata = {} as Record<PropertyKey, unknown>);
37
+ let bag = metadata[METADATA_KEY] as DecoratorMetadataBag | undefined;
38
+ if (!bag) {
39
+ bag = { columns: [], relations: [], transformers: [] };
40
+ metadata[METADATA_KEY] = bag;
41
+ }
42
+ return bag;
43
+ };
44
+
45
+ const getOrCreateMetadataBagOnConstructor = (ctor: object): DecoratorMetadataBag => {
46
+ const carrier = ctor as Record<PropertyKey, unknown>;
47
+ let bag = carrier[LEGACY_METADATA_KEY] as DecoratorMetadataBag | undefined;
48
+ if (!bag) {
49
+ bag = { columns: [], relations: [], transformers: [] };
50
+ carrier[LEGACY_METADATA_KEY] = bag;
51
+ }
52
+ return bag;
53
+ };
54
+
55
+ /**
56
+ * Reads the metadata bag from the given decorator context.
57
+ * @param context - The decorator context with metadata support.
58
+ * @returns The metadata bag if present.
59
+ */
60
+ export const readMetadataBag = (context: MetadataCarrier): DecoratorMetadataBag | undefined => {
61
+ return context.metadata?.[METADATA_KEY] as DecoratorMetadataBag | undefined;
62
+ };
63
+
64
+ /**
65
+ * Reads the metadata bag from a decorated constructor when using standard decorators.
66
+ * @param ctor - The entity constructor.
67
+ * @returns The metadata bag if present.
68
+ */
69
+ export const readMetadataBagFromConstructor = (ctor: object): DecoratorMetadataBag | undefined => {
70
+ const metadataSymbol = (Symbol as { metadata?: symbol }).metadata;
71
+ if (metadataSymbol) {
72
+ const metadata = Reflect.get(ctor, metadataSymbol) as Record<PropertyKey, unknown> | undefined;
73
+ const stage3Bag = metadata?.[METADATA_KEY] as DecoratorMetadataBag | undefined;
74
+ if (stage3Bag) {
75
+ return stage3Bag;
76
+ }
77
+ }
78
+ return (ctor as Record<PropertyKey, unknown>)[LEGACY_METADATA_KEY] as DecoratorMetadataBag | undefined;
79
+ };
80
+
81
+ /**
82
+ * Public helper to read decorator metadata from a class constructor.
83
+ * @param ctor - The entity constructor.
84
+ * @returns The metadata bag if present.
85
+ */
86
+ export const getDecoratorMetadata = (ctor: object): DecoratorMetadataBag | undefined =>
87
+ readMetadataBagFromConstructor(ctor);
88
+
89
+ const normalizePropertyName = (name: string | symbol): string => {
90
+ if (typeof name === 'symbol') {
91
+ return name.description ?? name.toString();
92
+ }
93
+ return name;
94
+ };
95
+
96
+ const isStage3FieldContext = (value: unknown): value is ClassFieldDecoratorContext => {
97
+ return (
98
+ typeof value === 'object' &&
99
+ value !== null &&
100
+ 'name' in value &&
101
+ 'private' in value &&
102
+ 'metadata' in value
103
+ );
104
+ };
105
+
106
+ export interface ResolvedFieldDecoratorInfo {
107
+ bag: DecoratorMetadataBag;
108
+ propertyName: string;
109
+ }
110
+
111
+ export const resolveFieldDecoratorInfo = (
112
+ targetOrValue: unknown,
113
+ contextOrProperty: unknown,
114
+ decoratorName: string
115
+ ): ResolvedFieldDecoratorInfo => {
116
+ if (isStage3FieldContext(contextOrProperty)) {
117
+ if (!contextOrProperty.name) {
118
+ throw new Error(`${decoratorName} decorator requires a property name`);
119
+ }
120
+ if (contextOrProperty.private) {
121
+ throw new Error(`${decoratorName} decorator does not support private fields`);
122
+ }
123
+ return {
124
+ propertyName: normalizePropertyName(contextOrProperty.name),
125
+ bag: getOrCreateMetadataBag(contextOrProperty)
126
+ };
127
+ }
128
+
129
+ if (typeof contextOrProperty === 'string' || typeof contextOrProperty === 'symbol') {
130
+ const legacyTarget = targetOrValue as LegacyMetadataCarrier | undefined;
131
+ const ctor =
132
+ typeof legacyTarget === 'function' ? legacyTarget : legacyTarget?.constructor;
133
+ if (!ctor || (typeof ctor !== 'function' && typeof ctor !== 'object')) {
134
+ throw new Error(`${decoratorName} decorator requires a class field target`);
135
+ }
136
+ return {
137
+ propertyName: normalizePropertyName(contextOrProperty),
138
+ bag: getOrCreateMetadataBagOnConstructor(ctor)
139
+ };
140
+ }
141
+
142
+ throw new Error(`${decoratorName} decorator received an unsupported decorator context`);
143
+ };
@@ -1,93 +1,93 @@
1
- import { TableHooks } from '../schema/table.js';
2
- import { RelationKinds } from '../schema/relation.js';
3
- import {
4
- addColumnMetadata,
5
- addRelationMetadata,
6
- EntityConstructor,
7
- ensureEntityMetadata,
8
- setEntityTableName
9
- } from '../orm/entity-metadata.js';
10
- import { readMetadataBag, readMetadataBagFromConstructor } from './decorator-metadata.js';
11
- import { syncTreeEntityMetadata } from '../tree/tree-decorator.js';
12
-
13
- /**
14
- * Options for defining an entity.
15
- */
16
- export interface EntityOptions {
17
- tableName?: string;
18
- hooks?: TableHooks;
19
- /** Entity type: 'table' (default) or 'view'. Views are read-only. */
20
- type?: 'table' | 'view';
21
- }
22
-
23
- const toSnakeCase = (value: string): string => {
24
- return value
25
- .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
26
- .replace(/[^a-z0-9_]+/gi, '_')
27
- .replace(/__+/g, '_')
28
- .replace(/^_|_$/g, '')
29
- .toLowerCase();
30
- };
31
-
32
- const deriveTableNameFromConstructor = (ctor: EntityConstructor<unknown>): string => {
33
- const fallback = 'unknown';
34
- const rawName = ctor.name || fallback;
35
- const strippedName = rawName.replace(/Entity$/i, '');
36
- const normalized = toSnakeCase(strippedName || rawName);
37
- if (!normalized) {
38
- return fallback;
39
- }
40
- return normalized.endsWith('s') ? normalized : `${normalized}s`;
41
- };
42
-
43
- /**
44
- * Class decorator to mark a class as an entity and configure its table mapping.
45
- * @param options - Configuration options for the entity.
46
- * @returns A class decorator that registers the entity metadata.
47
- */
48
- export function Entity(options: EntityOptions = {}) {
49
- return function <T extends EntityConstructor>(
50
- value: T,
51
- context?: ClassDecoratorContext
52
- ): T {
53
- const ctor = value;
54
- const tableName = options.tableName ?? deriveTableNameFromConstructor(ctor);
55
- setEntityTableName(ctor, tableName, options.hooks, options.type);
56
-
57
- const bag = context ? readMetadataBag(context) : readMetadataBagFromConstructor(ctor);
58
- if (bag) {
59
- const meta = ensureEntityMetadata(ctor);
60
- for (const entry of bag.columns) {
61
- if (meta.columns[entry.propertyName]) {
62
- throw new Error(
63
- `Column '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
64
- );
65
- }
66
- addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
67
- }
68
- for (const entry of bag.relations) {
69
- if (meta.relations[entry.propertyName]) {
70
- throw new Error(
71
- `Relation '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
72
- );
73
- }
74
- const relationCopy =
75
- entry.relation.kind === RelationKinds.BelongsToMany
76
- ? {
77
- ...entry.relation,
78
- defaultPivotColumns: entry.relation.defaultPivotColumns
79
- ? [...entry.relation.defaultPivotColumns]
80
- : undefined
81
- }
82
- : { ...entry.relation };
83
- addRelationMetadata(ctor, entry.propertyName, relationCopy);
84
- }
85
- }
86
-
87
- // Supports both decorator orders:
88
- // @Entity() above @Tree() and @Tree() above @Entity().
89
- syncTreeEntityMetadata(ctor, bag?.tree);
90
-
91
- return ctor;
92
- };
93
- }
1
+ import { TableHooks } from '../schema/table.js';
2
+ import { RelationKinds } from '../schema/relation.js';
3
+ import {
4
+ addColumnMetadata,
5
+ addRelationMetadata,
6
+ EntityConstructor,
7
+ ensureEntityMetadata,
8
+ setEntityTableName
9
+ } from '../orm/entity-metadata.js';
10
+ import { readMetadataBag, readMetadataBagFromConstructor } from './decorator-metadata.js';
11
+ import { syncTreeEntityMetadata } from '../tree/tree-decorator.js';
12
+
13
+ /**
14
+ * Options for defining an entity.
15
+ */
16
+ export interface EntityOptions {
17
+ tableName?: string;
18
+ hooks?: TableHooks;
19
+ /** Entity type: 'table' (default) or 'view'. Views are read-only. */
20
+ type?: 'table' | 'view';
21
+ }
22
+
23
+ const toSnakeCase = (value: string): string => {
24
+ return value
25
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
26
+ .replace(/[^a-z0-9_]+/gi, '_')
27
+ .replace(/__+/g, '_')
28
+ .replace(/^_|_$/g, '')
29
+ .toLowerCase();
30
+ };
31
+
32
+ const deriveTableNameFromConstructor = (ctor: EntityConstructor<unknown>): string => {
33
+ const fallback = 'unknown';
34
+ const rawName = ctor.name || fallback;
35
+ const strippedName = rawName.replace(/Entity$/i, '');
36
+ const normalized = toSnakeCase(strippedName || rawName);
37
+ if (!normalized) {
38
+ return fallback;
39
+ }
40
+ return normalized.endsWith('s') ? normalized : `${normalized}s`;
41
+ };
42
+
43
+ /**
44
+ * Class decorator to mark a class as an entity and configure its table mapping.
45
+ * @param options - Configuration options for the entity.
46
+ * @returns A class decorator that registers the entity metadata.
47
+ */
48
+ export function Entity(options: EntityOptions = {}) {
49
+ return function <T extends EntityConstructor>(
50
+ value: T,
51
+ context?: ClassDecoratorContext
52
+ ): T {
53
+ const ctor = value;
54
+ const tableName = options.tableName ?? deriveTableNameFromConstructor(ctor);
55
+ setEntityTableName(ctor, tableName, options.hooks, options.type);
56
+
57
+ const bag = context ? readMetadataBag(context) : readMetadataBagFromConstructor(ctor);
58
+ if (bag) {
59
+ const meta = ensureEntityMetadata(ctor);
60
+ for (const entry of bag.columns) {
61
+ if (meta.columns[entry.propertyName]) {
62
+ throw new Error(
63
+ `Column '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
64
+ );
65
+ }
66
+ addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
67
+ }
68
+ for (const entry of bag.relations) {
69
+ if (meta.relations[entry.propertyName]) {
70
+ throw new Error(
71
+ `Relation '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
72
+ );
73
+ }
74
+ const relationCopy =
75
+ entry.relation.kind === RelationKinds.BelongsToMany
76
+ ? {
77
+ ...entry.relation,
78
+ defaultPivotColumns: entry.relation.defaultPivotColumns
79
+ ? [...entry.relation.defaultPivotColumns]
80
+ : undefined
81
+ }
82
+ : { ...entry.relation };
83
+ addRelationMetadata(ctor, entry.propertyName, relationCopy);
84
+ }
85
+ }
86
+
87
+ // Supports both decorator orders:
88
+ // @Entity() above @Tree() and @Tree() above @Entity().
89
+ syncTreeEntityMetadata(ctor, bag?.tree);
90
+
91
+ return ctor;
92
+ };
93
+ }