metal-orm 1.1.7 → 1.1.9

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.
@@ -1,85 +1,90 @@
1
- import { TableHooks } from '../schema/table.js';
2
- import { RelationKinds } from '../schema/relation.js';
1
+ import { TableHooks } from '../schema/table.js';
2
+ import { RelationKinds } from '../schema/relation.js';
3
3
  import {
4
4
  addColumnMetadata,
5
5
  addRelationMetadata,
6
6
  EntityConstructor,
7
- ensureEntityMetadata,
7
+ ensureEntityMetadata,
8
8
  setEntityTableName
9
9
  } from '../orm/entity-metadata.js';
10
10
  import { readMetadataBag } from './decorator-metadata.js';
11
-
12
- /**
13
- * Options for defining an entity.
14
- */
15
- export interface EntityOptions {
16
- tableName?: string;
17
- hooks?: TableHooks;
18
- /** Entity type: 'table' (default) or 'view'. Views are read-only. */
19
- type?: 'table' | 'view';
20
- }
21
-
22
- const toSnakeCase = (value: string): string => {
23
- return value
24
- .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
25
- .replace(/[^a-z0-9_]+/gi, '_')
26
- .replace(/__+/g, '_')
27
- .replace(/^_|_$/g, '')
28
- .toLowerCase();
29
- };
30
-
31
- const deriveTableNameFromConstructor = (ctor: EntityConstructor<unknown>): string => {
32
- const fallback = 'unknown';
33
- const rawName = ctor.name || fallback;
34
- const strippedName = rawName.replace(/Entity$/i, '');
35
- const normalized = toSnakeCase(strippedName || rawName);
36
- if (!normalized) {
37
- return fallback;
38
- }
39
- return normalized.endsWith('s') ? normalized : `${normalized}s`;
40
- };
41
-
42
- /**
43
- * Class decorator to mark a class as an entity and configure its table mapping.
44
- * @param options - Configuration options for the entity.
45
- * @returns A class decorator that registers the entity metadata.
46
- */
47
- export function Entity(options: EntityOptions = {}) {
48
- return function <T extends EntityConstructor>(value: T, context: ClassDecoratorContext): T {
49
- const ctor = value;
50
- const tableName = options.tableName ?? deriveTableNameFromConstructor(ctor);
51
- setEntityTableName(ctor, tableName, options.hooks, options.type);
52
-
53
- const bag = readMetadataBag(context);
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>(value: T, context: ClassDecoratorContext): T {
50
+ const ctor = value;
51
+ const tableName = options.tableName ?? deriveTableNameFromConstructor(ctor);
52
+ setEntityTableName(ctor, tableName, options.hooks, options.type);
53
+
54
+ const bag = readMetadataBag(context);
54
55
  if (bag) {
55
56
  const meta = ensureEntityMetadata(ctor);
56
57
  for (const entry of bag.columns) {
57
- if (meta.columns[entry.propertyName]) {
58
- throw new Error(
59
- `Column '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
60
- );
61
- }
62
- addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
63
- }
64
- for (const entry of bag.relations) {
65
- if (meta.relations[entry.propertyName]) {
66
- throw new Error(
67
- `Relation '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
68
- );
69
- }
70
- const relationCopy =
71
- entry.relation.kind === RelationKinds.BelongsToMany
72
- ? {
73
- ...entry.relation,
74
- defaultPivotColumns: entry.relation.defaultPivotColumns
75
- ? [...entry.relation.defaultPivotColumns]
76
- : undefined
77
- }
78
- : { ...entry.relation };
58
+ if (meta.columns[entry.propertyName]) {
59
+ throw new Error(
60
+ `Column '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
61
+ );
62
+ }
63
+ addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
64
+ }
65
+ for (const entry of bag.relations) {
66
+ if (meta.relations[entry.propertyName]) {
67
+ throw new Error(
68
+ `Relation '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
69
+ );
70
+ }
71
+ const relationCopy =
72
+ entry.relation.kind === RelationKinds.BelongsToMany
73
+ ? {
74
+ ...entry.relation,
75
+ defaultPivotColumns: entry.relation.defaultPivotColumns
76
+ ? [...entry.relation.defaultPivotColumns]
77
+ : undefined
78
+ }
79
+ : { ...entry.relation };
79
80
  addRelationMetadata(ctor, entry.propertyName, relationCopy);
80
81
  }
81
82
  }
82
83
 
84
+ // Supports both decorator orders:
85
+ // @Entity() above @Tree() and @Tree() above @Entity().
86
+ syncTreeEntityMetadata(ctor, bag?.tree);
87
+
83
88
  return ctor;
84
89
  };
85
90
  }