metal-orm 1.0.24 → 1.0.25
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/README.md +2 -0
- package/dist/decorators/index.cjs +91 -7
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +24 -7
- package/dist/decorators/index.d.ts +24 -7
- package/dist/decorators/index.js +91 -7
- package/dist/decorators/index.js.map +1 -1
- package/package.json +4 -1
- package/scripts/generate-entities.mjs +76 -6
- package/src/decorators/column.ts +38 -4
- package/src/decorators/decorator-metadata.ts +53 -0
- package/src/decorators/entity.ts +37 -4
- package/src/decorators/relations.ts +34 -4
package/package.json
CHANGED
|
@@ -371,16 +371,86 @@ const renderEntityFile = (schema, options) => {
|
|
|
371
371
|
|
|
372
372
|
const relations = mapRelations(tables);
|
|
373
373
|
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
374
|
+
const usage = {
|
|
375
|
+
needsCol: false,
|
|
376
|
+
needsEntity: tables.length > 0,
|
|
377
|
+
needsColumnDecorator: false,
|
|
378
|
+
needsPrimaryKeyDecorator: false,
|
|
379
|
+
needsHasManyDecorator: false,
|
|
380
|
+
needsBelongsToDecorator: false,
|
|
381
|
+
needsBelongsToManyDecorator: false,
|
|
382
|
+
needsHasManyCollection: false,
|
|
383
|
+
needsManyToManyCollection: false
|
|
384
|
+
};
|
|
379
385
|
|
|
380
386
|
const lines = [];
|
|
381
387
|
lines.push('// AUTO-GENERATED by scripts/generate-entities.mjs');
|
|
382
388
|
lines.push('// Regenerate after schema changes.');
|
|
383
|
-
|
|
389
|
+
const imports = [];
|
|
390
|
+
|
|
391
|
+
for (const table of tables) {
|
|
392
|
+
for (const col of table.columns) {
|
|
393
|
+
usage.needsCol = true;
|
|
394
|
+
const rendered = renderColumnExpression(col, table.primaryKey);
|
|
395
|
+
if (rendered.decorator === 'PrimaryKey') {
|
|
396
|
+
usage.needsPrimaryKeyDecorator = true;
|
|
397
|
+
} else {
|
|
398
|
+
usage.needsColumnDecorator = true;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const rels = relations.get(table.name) || [];
|
|
403
|
+
for (const rel of rels) {
|
|
404
|
+
if (rel.kind === 'hasMany') {
|
|
405
|
+
usage.needsHasManyDecorator = true;
|
|
406
|
+
usage.needsHasManyCollection = true;
|
|
407
|
+
}
|
|
408
|
+
if (rel.kind === 'belongsTo') {
|
|
409
|
+
usage.needsBelongsToDecorator = true;
|
|
410
|
+
}
|
|
411
|
+
if (rel.kind === 'belongsToMany') {
|
|
412
|
+
usage.needsBelongsToManyDecorator = true;
|
|
413
|
+
usage.needsManyToManyCollection = true;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (usage.needsCol) {
|
|
419
|
+
imports.push("import { col } from 'metal-orm';");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const decoratorSet = new Set(['bootstrapEntities', 'getTableDefFromEntity']);
|
|
423
|
+
if (usage.needsEntity) decoratorSet.add('Entity');
|
|
424
|
+
if (usage.needsColumnDecorator) decoratorSet.add('Column');
|
|
425
|
+
if (usage.needsPrimaryKeyDecorator) decoratorSet.add('PrimaryKey');
|
|
426
|
+
if (usage.needsHasManyDecorator) decoratorSet.add('HasMany');
|
|
427
|
+
if (usage.needsBelongsToDecorator) decoratorSet.add('BelongsTo');
|
|
428
|
+
if (usage.needsBelongsToManyDecorator) decoratorSet.add('BelongsToMany');
|
|
429
|
+
const decoratorOrder = [
|
|
430
|
+
'Entity',
|
|
431
|
+
'Column',
|
|
432
|
+
'PrimaryKey',
|
|
433
|
+
'HasMany',
|
|
434
|
+
'BelongsTo',
|
|
435
|
+
'BelongsToMany',
|
|
436
|
+
'bootstrapEntities',
|
|
437
|
+
'getTableDefFromEntity'
|
|
438
|
+
];
|
|
439
|
+
const decoratorImports = decoratorOrder.filter(name => decoratorSet.has(name));
|
|
440
|
+
if (decoratorImports.length) {
|
|
441
|
+
imports.push(`import { ${decoratorImports.join(', ')} } from 'metal-orm/decorators';`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const ormTypes = [];
|
|
445
|
+
if (usage.needsHasManyCollection) ormTypes.push('HasManyCollection');
|
|
446
|
+
if (usage.needsManyToManyCollection) ormTypes.push('ManyToManyCollection');
|
|
447
|
+
if (ormTypes.length) {
|
|
448
|
+
imports.push(`import { ${ormTypes.join(', ')} } from 'metal-orm';`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (imports.length) {
|
|
452
|
+
lines.push(...imports, '');
|
|
453
|
+
}
|
|
384
454
|
|
|
385
455
|
for (const table of tables) {
|
|
386
456
|
const className = classNames.get(table.name);
|
package/src/decorators/column.ts
CHANGED
|
@@ -5,6 +5,13 @@ import {
|
|
|
5
5
|
ColumnDefLike,
|
|
6
6
|
ensureEntityMetadata
|
|
7
7
|
} from '../orm/entity-metadata.js';
|
|
8
|
+
import {
|
|
9
|
+
DualModePropertyDecorator,
|
|
10
|
+
getOrCreateMetadataBag,
|
|
11
|
+
isStandardDecoratorContext,
|
|
12
|
+
registerInitializer,
|
|
13
|
+
StandardDecoratorContext
|
|
14
|
+
} from './decorator-metadata.js';
|
|
8
15
|
|
|
9
16
|
export interface ColumnOptions {
|
|
10
17
|
type: ColumnType;
|
|
@@ -66,18 +73,45 @@ const registerColumn = (ctor: EntityConstructor, propertyName: string, column: C
|
|
|
66
73
|
addColumnMetadata(ctor, propertyName, column);
|
|
67
74
|
};
|
|
68
75
|
|
|
76
|
+
const registerColumnFromContext = (
|
|
77
|
+
context: StandardDecoratorContext,
|
|
78
|
+
column: ColumnDefLike
|
|
79
|
+
): void => {
|
|
80
|
+
if (!context.name) {
|
|
81
|
+
throw new Error('Column decorator requires a property name');
|
|
82
|
+
}
|
|
83
|
+
const propertyName = normalizePropertyName(context.name);
|
|
84
|
+
const bag = getOrCreateMetadataBag(context);
|
|
85
|
+
if (!bag.columns.some(entry => entry.propertyName === propertyName)) {
|
|
86
|
+
bag.columns.push({ propertyName, column: { ...column } });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
registerInitializer(context, function () {
|
|
90
|
+
const ctor = resolveConstructor(this);
|
|
91
|
+
if (!ctor) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
registerColumn(ctor, propertyName, column);
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
69
98
|
export function Column(definition: ColumnInput) {
|
|
70
99
|
const normalized = normalizeColumnInput(definition);
|
|
71
|
-
const decorator = (
|
|
72
|
-
|
|
73
|
-
|
|
100
|
+
const decorator: DualModePropertyDecorator = (targetOrValue, propertyKeyOrContext) => {
|
|
101
|
+
if (isStandardDecoratorContext(propertyKeyOrContext)) {
|
|
102
|
+
registerColumnFromContext(propertyKeyOrContext, normalized);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const propertyName = normalizePropertyName(propertyKeyOrContext);
|
|
107
|
+
const ctor = resolveConstructor(targetOrValue);
|
|
74
108
|
if (!ctor) {
|
|
75
109
|
throw new Error('Unable to resolve constructor when registering column metadata');
|
|
76
110
|
}
|
|
77
111
|
registerColumn(ctor, propertyName, { ...normalized });
|
|
78
112
|
};
|
|
79
113
|
|
|
80
|
-
return decorator
|
|
114
|
+
return decorator;
|
|
81
115
|
}
|
|
82
116
|
|
|
83
117
|
export function PrimaryKey(definition: ColumnInput) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ColumnDefLike, RelationMetadata } from '../orm/entity-metadata.js';
|
|
2
|
+
|
|
3
|
+
export interface StandardDecoratorContext {
|
|
4
|
+
kind: string;
|
|
5
|
+
name?: string | symbol;
|
|
6
|
+
metadata?: Record<PropertyKey, unknown>;
|
|
7
|
+
addInitializer?(initializer: (this: unknown) => void): void;
|
|
8
|
+
static?: boolean;
|
|
9
|
+
private?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DualModePropertyDecorator {
|
|
13
|
+
(target: object, propertyKey: string | symbol): void;
|
|
14
|
+
(value: unknown, context: StandardDecoratorContext): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DualModeClassDecorator {
|
|
18
|
+
<TFunction extends Function>(value: TFunction): void | TFunction;
|
|
19
|
+
<TFunction extends Function>(value: TFunction, context: StandardDecoratorContext): void | TFunction;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DecoratorMetadataBag {
|
|
23
|
+
columns: Array<{ propertyName: string; column: ColumnDefLike }>;
|
|
24
|
+
relations: Array<{ propertyName: string; relation: RelationMetadata }>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const METADATA_KEY = 'metal-orm:decorators';
|
|
28
|
+
|
|
29
|
+
export const isStandardDecoratorContext = (value: unknown): value is StandardDecoratorContext => {
|
|
30
|
+
return typeof value === 'object' && value !== null && 'kind' in (value as any);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const getOrCreateMetadataBag = (context: StandardDecoratorContext): DecoratorMetadataBag => {
|
|
34
|
+
const metadata = context.metadata || (context.metadata = {} as Record<PropertyKey, unknown>);
|
|
35
|
+
const existing = metadata[METADATA_KEY] as DecoratorMetadataBag | undefined;
|
|
36
|
+
if (existing) {
|
|
37
|
+
return existing;
|
|
38
|
+
}
|
|
39
|
+
const bag: DecoratorMetadataBag = { columns: [], relations: [] };
|
|
40
|
+
metadata[METADATA_KEY] = bag;
|
|
41
|
+
return bag;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const readMetadataBag = (context: StandardDecoratorContext): DecoratorMetadataBag | undefined => {
|
|
45
|
+
return context.metadata?.[METADATA_KEY] as DecoratorMetadataBag | undefined;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const registerInitializer = (
|
|
49
|
+
context: StandardDecoratorContext,
|
|
50
|
+
initializer: (this: unknown) => void
|
|
51
|
+
): void => {
|
|
52
|
+
context.addInitializer?.(initializer);
|
|
53
|
+
};
|
package/src/decorators/entity.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { TableHooks } from '../schema/table.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
addColumnMetadata,
|
|
4
|
+
addRelationMetadata,
|
|
5
|
+
EntityConstructor,
|
|
6
|
+
ensureEntityMetadata,
|
|
7
|
+
setEntityTableName
|
|
8
|
+
} from '../orm/entity-metadata.js';
|
|
9
|
+
import { DualModeClassDecorator, isStandardDecoratorContext, readMetadataBag } from './decorator-metadata.js';
|
|
3
10
|
|
|
4
11
|
export interface EntityOptions {
|
|
5
12
|
tableName?: string;
|
|
@@ -27,10 +34,36 @@ const deriveTableNameFromConstructor = (ctor: Function): string => {
|
|
|
27
34
|
};
|
|
28
35
|
|
|
29
36
|
export function Entity(options: EntityOptions = {}) {
|
|
30
|
-
const decorator
|
|
37
|
+
const decorator: DualModeClassDecorator = value => {
|
|
31
38
|
const tableName = options.tableName ?? deriveTableNameFromConstructor(value);
|
|
32
|
-
setEntityTableName(value, tableName, options.hooks);
|
|
39
|
+
setEntityTableName(value as EntityConstructor, tableName, options.hooks);
|
|
40
|
+
|
|
33
41
|
return value;
|
|
34
42
|
};
|
|
35
|
-
|
|
43
|
+
|
|
44
|
+
const decoratorWithContext: DualModeClassDecorator = (value, context?) => {
|
|
45
|
+
const ctor = value as EntityConstructor;
|
|
46
|
+
decorator(ctor);
|
|
47
|
+
|
|
48
|
+
if (context && isStandardDecoratorContext(context)) {
|
|
49
|
+
const bag = readMetadataBag(context);
|
|
50
|
+
if (bag) {
|
|
51
|
+
const meta = ensureEntityMetadata(ctor);
|
|
52
|
+
for (const entry of bag.columns) {
|
|
53
|
+
if (!meta.columns[entry.propertyName]) {
|
|
54
|
+
addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const entry of bag.relations) {
|
|
58
|
+
if (!meta.relations[entry.propertyName]) {
|
|
59
|
+
addRelationMetadata(ctor, entry.propertyName, entry.relation);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return ctor;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return decoratorWithContext;
|
|
36
69
|
}
|
|
@@ -5,6 +5,13 @@ import {
|
|
|
5
5
|
EntityOrTableTargetResolver,
|
|
6
6
|
RelationMetadata
|
|
7
7
|
} from '../orm/entity-metadata.js';
|
|
8
|
+
import {
|
|
9
|
+
DualModePropertyDecorator,
|
|
10
|
+
getOrCreateMetadataBag,
|
|
11
|
+
isStandardDecoratorContext,
|
|
12
|
+
registerInitializer,
|
|
13
|
+
StandardDecoratorContext
|
|
14
|
+
} from './decorator-metadata.js';
|
|
8
15
|
|
|
9
16
|
interface BaseRelationOptions {
|
|
10
17
|
target: EntityOrTableTargetResolver;
|
|
@@ -60,16 +67,39 @@ const registerRelation = (ctor: EntityConstructor, propertyName: string, metadat
|
|
|
60
67
|
const createFieldDecorator = (
|
|
61
68
|
metadataFactory: (propertyName: string) => RelationMetadata
|
|
62
69
|
) => {
|
|
63
|
-
const decorator = (
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
const decorator: DualModePropertyDecorator = (targetOrValue, propertyKeyOrContext) => {
|
|
71
|
+
if (isStandardDecoratorContext(propertyKeyOrContext)) {
|
|
72
|
+
const ctx = propertyKeyOrContext as StandardDecoratorContext;
|
|
73
|
+
if (!ctx.name) {
|
|
74
|
+
throw new Error('Relation decorator requires a property name');
|
|
75
|
+
}
|
|
76
|
+
const propertyName = normalizePropertyName(ctx.name);
|
|
77
|
+
const bag = getOrCreateMetadataBag(ctx);
|
|
78
|
+
const relationMetadata = metadataFactory(propertyName);
|
|
79
|
+
|
|
80
|
+
if (!bag.relations.some(entry => entry.propertyName === propertyName)) {
|
|
81
|
+
bag.relations.push({ propertyName, relation: relationMetadata });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
registerInitializer(ctx, function () {
|
|
85
|
+
const ctor = resolveConstructor(this);
|
|
86
|
+
if (!ctor) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
registerRelation(ctor, propertyName, relationMetadata);
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const propertyName = normalizePropertyName(propertyKeyOrContext);
|
|
95
|
+
const ctor = resolveConstructor(targetOrValue);
|
|
66
96
|
if (!ctor) {
|
|
67
97
|
throw new Error('Unable to resolve constructor when registering relation metadata');
|
|
68
98
|
}
|
|
69
99
|
registerRelation(ctor, propertyName, metadataFactory(propertyName));
|
|
70
100
|
};
|
|
71
101
|
|
|
72
|
-
return decorator
|
|
102
|
+
return decorator;
|
|
73
103
|
};
|
|
74
104
|
|
|
75
105
|
export function HasMany(options: HasManyOptions) {
|