metal-orm 1.0.24 → 1.0.27

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.24",
3
+ "version": "1.0.27",
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
  },
@@ -371,16 +371,86 @@ const renderEntityFile = (schema, options) => {
371
371
 
372
372
  const relations = mapRelations(tables);
373
373
 
374
- const imports = [
375
- "import { col } from 'metal-orm';",
376
- "import { Entity, Column, PrimaryKey, HasMany, BelongsTo, BelongsToMany, bootstrapEntities, getTableDefFromEntity } from 'metal-orm/decorators';",
377
- "import { HasManyCollection, ManyToManyCollection } from 'metal-orm';"
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
- 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
+ }
384
454
 
385
455
  for (const table of tables) {
386
456
  const className = classNames.get(table.name);
@@ -37,3 +37,142 @@ export function createMssqlExecutor(
37
37
  },
38
38
  };
39
39
  }
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Tedious integration helper (driver adapter)
43
+ // ---------------------------------------------------------------------------
44
+
45
+ export interface TediousColumn {
46
+ metadata: { colName: string };
47
+ value: unknown;
48
+ }
49
+
50
+ export interface TediousRequest {
51
+ addParameter(name: string, type: unknown, value: unknown): void;
52
+ on(event: 'row', listener: (columns: TediousColumn[]) => void): void;
53
+ }
54
+
55
+ export interface TediousRequestCtor {
56
+ new (sql: string, callback: (err?: Error | null) => void): TediousRequest;
57
+ }
58
+
59
+ export interface TediousTypes {
60
+ NVarChar: unknown;
61
+ Int: unknown;
62
+ Float: unknown;
63
+ BigInt: unknown;
64
+ Bit: unknown;
65
+ DateTime: unknown;
66
+ VarBinary: unknown;
67
+ [key: string]: unknown;
68
+ }
69
+
70
+ export interface TediousModule {
71
+ Request: TediousRequestCtor;
72
+ TYPES: TediousTypes;
73
+ }
74
+
75
+ export interface TediousConnectionLike {
76
+ execSql(request: TediousRequest): void;
77
+ beginTransaction?(cb: (err?: Error | null) => void): void;
78
+ commitTransaction?(cb: (err?: Error | null) => void): void;
79
+ rollbackTransaction?(cb: (err?: Error | null) => void): void;
80
+ }
81
+
82
+ export interface CreateTediousClientOptions {
83
+ inferType?(value: unknown, TYPES: TediousTypes): unknown;
84
+ }
85
+
86
+ const defaultInferType = (value: unknown, TYPES: TediousTypes): unknown => {
87
+ if (value === null || value === undefined) return TYPES.NVarChar;
88
+ if (typeof value === 'number') {
89
+ return Number.isInteger(value) ? TYPES.Int : TYPES.Float;
90
+ }
91
+ if (typeof value === 'bigint') return TYPES.BigInt;
92
+ if (typeof value === 'boolean') return TYPES.Bit;
93
+ if (value instanceof Date) return TYPES.DateTime;
94
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
95
+ return TYPES.VarBinary;
96
+ }
97
+ return TYPES.NVarChar;
98
+ };
99
+
100
+ export function createTediousMssqlClient(
101
+ connection: TediousConnectionLike,
102
+ { Request, TYPES }: TediousModule,
103
+ options?: CreateTediousClientOptions
104
+ ): MssqlClientLike {
105
+ const inferType = options?.inferType ?? defaultInferType;
106
+
107
+ return {
108
+ async query(sql: string, params: unknown[] = []) {
109
+ const rows = await new Promise<Array<Record<string, unknown>>>(
110
+ (resolve, reject) => {
111
+ const collected: Record<string, unknown>[] = [];
112
+
113
+ const request = new Request(sql, err => {
114
+ if (err) return reject(err);
115
+ resolve(collected);
116
+ });
117
+
118
+ params.forEach((value, idx) => {
119
+ const sqlType = inferType(value, TYPES);
120
+ request.addParameter(
121
+ `p${idx + 1}`,
122
+ sqlType,
123
+ value as unknown
124
+ );
125
+ });
126
+
127
+ request.on('row', cols => {
128
+ const row: Record<string, unknown> = {};
129
+ for (const col of cols) {
130
+ row[col.metadata.colName] = col.value;
131
+ }
132
+ collected.push(row);
133
+ });
134
+
135
+ connection.execSql(request);
136
+ }
137
+ );
138
+
139
+ return { recordset: rows };
140
+ },
141
+
142
+ beginTransaction: connection.beginTransaction
143
+ ? () =>
144
+ new Promise<void>((resolve, reject) => {
145
+ connection.beginTransaction!(err =>
146
+ err ? reject(err) : resolve()
147
+ );
148
+ })
149
+ : undefined,
150
+
151
+ commit: connection.commitTransaction
152
+ ? () =>
153
+ new Promise<void>((resolve, reject) => {
154
+ connection.commitTransaction!(err =>
155
+ err ? reject(err) : resolve()
156
+ );
157
+ })
158
+ : undefined,
159
+
160
+ rollback: connection.rollbackTransaction
161
+ ? () =>
162
+ new Promise<void>((resolve, reject) => {
163
+ connection.rollbackTransaction!(err =>
164
+ err ? reject(err) : resolve()
165
+ );
166
+ })
167
+ : undefined,
168
+ };
169
+ }
170
+
171
+ export function createTediousExecutor(
172
+ connection: TediousConnectionLike,
173
+ module: TediousModule,
174
+ options?: CreateTediousClientOptions
175
+ ): DbExecutor {
176
+ const client = createTediousMssqlClient(connection, module, options);
177
+ return createMssqlExecutor(client);
178
+ }
@@ -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) {
package/src/index.ts CHANGED
@@ -2,11 +2,11 @@ export * from './schema/table.js';
2
2
  export * from './schema/column.js';
3
3
  export * from './schema/relation.js';
4
4
  export * from './schema/types.js';
5
- export * from './query-builder/select.js';
6
- export * from './query-builder/select-helpers.js';
7
- export * from './query-builder/insert.js';
8
- export * from './query-builder/update.js';
9
- export * from './query-builder/delete.js';
5
+ export * from './query-builder/select.js';
6
+ export * from './query-builder/select-helpers.js';
7
+ export * from './query-builder/insert.js';
8
+ export * from './query-builder/update.js';
9
+ export * from './query-builder/delete.js';
10
10
  export * from './core/ast/expression.js';
11
11
  export * from './core/hydration/types.js';
12
12
  export * from './core/dialect/mysql/index.js';