metal-orm 1.0.23 → 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/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
+ "engines": {
7
+ "node": ">=20.0.0"
8
+ },
6
9
  "bin": {
7
10
  "metal-orm-gen": "./scripts/generate-entities.mjs"
8
11
  },
@@ -168,9 +168,12 @@ const parseColumnType = colTypeRaw => {
168
168
 
169
169
  const base = type.replace(/\(.*\)/, '');
170
170
 
171
+ if (base === 'bit') return { factory: 'col.boolean()', ts: 'boolean' };
171
172
  if (base.includes('bigint')) return { factory: 'col.bigint()', ts: 'number' };
172
173
  if (base.includes('int')) return { factory: 'col.int()', ts: 'number' };
173
- if (base.includes('uuid')) return { factory: 'col.uuid()', ts: 'string' };
174
+ if (base.includes('uuid') || base.includes('uniqueidentifier')) return { factory: 'col.uuid()', ts: 'string' };
175
+ if (base === 'date') return { factory: 'col.date()', ts: 'Date' };
176
+ if (base.includes('datetime') || base === 'time') return { factory: 'col.datetime()', ts: 'Date' };
174
177
  if (base.includes('char') || base.includes('text')) {
175
178
  const lenArg = length ? `${length}` : '255';
176
179
  return { factory: `col.varchar(${lenArg})`, ts: 'string' };
@@ -368,16 +371,86 @@ const renderEntityFile = (schema, options) => {
368
371
 
369
372
  const relations = mapRelations(tables);
370
373
 
371
- const imports = [
372
- "import { col } from 'metal-orm';",
373
- "import { Entity, Column, PrimaryKey, HasMany, BelongsTo, BelongsToMany, bootstrapEntities, getTableDefFromEntity } from 'metal-orm/decorators';",
374
- "import { HasManyCollection, ManyToManyCollection } from 'metal-orm';"
375
- ];
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
+ };
376
385
 
377
386
  const lines = [];
378
387
  lines.push('// AUTO-GENERATED by scripts/generate-entities.mjs');
379
388
  lines.push('// Regenerate after schema changes.');
380
- lines.push(...imports, '');
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
+ }
381
454
 
382
455
  for (const table of tables) {
383
456
  const className = classNames.get(table.name);
@@ -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 = (target: object, propertyKey: string | symbol) => {
72
- const propertyName = normalizePropertyName(propertyKey);
73
- const ctor = resolveConstructor(target);
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 as PropertyDecorator;
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
+ };
@@ -1,5 +1,12 @@
1
1
  import { TableHooks } from '../schema/table.js';
2
- import { setEntityTableName } from '../orm/entity-metadata.js';
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 = <T extends new (...args: any[]) => any>(value: T) => {
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
- return decorator as unknown as ClassDecorator;
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 = (target: object, propertyKey: string | symbol) => {
64
- const propertyName = normalizePropertyName(propertyKey);
65
- const ctor = resolveConstructor(target);
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 as PropertyDecorator;
102
+ return decorator;
73
103
  };
74
104
 
75
105
  export function HasMany(options: HasManyOptions) {