@mikro-orm/entity-generator 7.0.17 → 7.0.18-dev.0
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/EntityGenerator.js +6 -0
- package/README.md +2 -1
- package/RoutineSourceFile.d.ts +17 -0
- package/RoutineSourceFile.js +136 -0
- package/SourceFile.d.ts +2 -1
- package/SourceFile.js +46 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +3 -3
package/EntityGenerator.js
CHANGED
|
@@ -6,6 +6,7 @@ import { writeFile } from 'node:fs/promises';
|
|
|
6
6
|
import { DefineEntitySourceFile } from './DefineEntitySourceFile.js';
|
|
7
7
|
import { EntitySchemaSourceFile } from './EntitySchemaSourceFile.js';
|
|
8
8
|
import { NativeEnumSourceFile } from './NativeEnumSourceFile.js';
|
|
9
|
+
import { RoutineSourceFile } from './RoutineSourceFile.js';
|
|
9
10
|
import { SourceFile } from './SourceFile.js';
|
|
10
11
|
/** Generates entity source files by introspecting an existing database schema. */
|
|
11
12
|
export class EntityGenerator {
|
|
@@ -33,6 +34,8 @@ export class EntityGenerator {
|
|
|
33
34
|
async generate(options = {}) {
|
|
34
35
|
options = Utils.mergeConfig({}, this.#config.get('entityGenerator'), options);
|
|
35
36
|
const schema = await DatabaseSchema.create(this.#connection, this.#platform, this.#config, undefined, undefined, options.takeTables, options.skipTables);
|
|
37
|
+
// The generator emits whatever it can find, so always load routines here.
|
|
38
|
+
await schema.loadRoutines(this.#connection, this.#platform);
|
|
36
39
|
const metadata = await this.getEntityMetadata(schema, options);
|
|
37
40
|
const defaultPath = `${this.#config.get('baseDir')}/generated-entities`;
|
|
38
41
|
const baseDir = fs.normalizePath(options.path ?? defaultPath);
|
|
@@ -54,6 +57,9 @@ export class EntityGenerator {
|
|
|
54
57
|
for (const nativeEnum of Object.values(schema.getNativeEnums())) {
|
|
55
58
|
this.#sources.push(new NativeEnumSourceFile({}, this.#namingStrategy, this.#platform, options, nativeEnum));
|
|
56
59
|
}
|
|
60
|
+
for (const routine of schema.getRoutines()) {
|
|
61
|
+
this.#sources.push(new RoutineSourceFile(routine, this.#namingStrategy, this.#platform, options));
|
|
62
|
+
}
|
|
57
63
|
const files = this.#sources.map(file => [file.getBaseName(), file.generate()]);
|
|
58
64
|
if (options.save) {
|
|
59
65
|
fs.ensureDir(baseDir);
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<a href="https://mikro-orm.io"><img src="https://raw.githubusercontent.com/mikro-orm/mikro-orm/master/docs/static/img/logo-readme.svg?sanitize=true" alt="MikroORM" /></a>
|
|
3
3
|
</h1>
|
|
4
4
|
|
|
5
|
-
TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-orm.io/docs/unit-of-work/) and [Identity Map](https://mikro-orm.io/docs/identity-map/) patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL, SQLite (including libSQL), MSSQL and Oracle databases.
|
|
5
|
+
TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-orm.io/docs/unit-of-work/) and [Identity Map](https://mikro-orm.io/docs/identity-map/) patterns. Supports MongoDB, MySQL, MariaDB, PostgreSQL (including CockroachDB and PGlite), SQLite (including libSQL), MSSQL and Oracle databases.
|
|
6
6
|
|
|
7
7
|
> Heavily inspired by [Doctrine](https://www.doctrine-project.org/) and [Hibernate](https://hibernate.org/).
|
|
8
8
|
|
|
@@ -19,6 +19,7 @@ Install a driver package for your database:
|
|
|
19
19
|
|
|
20
20
|
```sh
|
|
21
21
|
npm install @mikro-orm/postgresql # PostgreSQL
|
|
22
|
+
npm install @mikro-orm/pglite # PGlite (embedded PostgreSQL in WASM)
|
|
22
23
|
npm install @mikro-orm/mysql # MySQL
|
|
23
24
|
npm install @mikro-orm/mariadb # MariaDB
|
|
24
25
|
npm install @mikro-orm/sqlite # SQLite
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { GenerateOptions, NamingStrategy, Platform } from '@mikro-orm/core';
|
|
2
|
+
import type { SqlRoutineDef } from '@mikro-orm/sql';
|
|
3
|
+
/** Always emits the class-less `new Routine(...)` form; there is no decorator form for routines. */
|
|
4
|
+
export declare class RoutineSourceFile {
|
|
5
|
+
private readonly routine;
|
|
6
|
+
private readonly namingStrategy;
|
|
7
|
+
private readonly platform;
|
|
8
|
+
private readonly options;
|
|
9
|
+
constructor(routine: SqlRoutineDef, namingStrategy: NamingStrategy, platform: Platform, options: GenerateOptions);
|
|
10
|
+
generate(): string;
|
|
11
|
+
getBaseName(extension?: string): string;
|
|
12
|
+
getClassName(): string;
|
|
13
|
+
private buildConfig;
|
|
14
|
+
private formatParams;
|
|
15
|
+
private formatReturns;
|
|
16
|
+
private emitRoutine;
|
|
17
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const identifierRegex = /^(?:[$_\p{ID_Start}])(?:[$\p{ID_Continue}])*$/u;
|
|
2
|
+
function quote(val) {
|
|
3
|
+
// Backslashes first so subsequent escapes don't double up the `\` we just added.
|
|
4
|
+
const escaped = val
|
|
5
|
+
.replaceAll('\\', '\\\\')
|
|
6
|
+
.replaceAll('\r', '\\r')
|
|
7
|
+
.replaceAll('\n', '\\n')
|
|
8
|
+
.replaceAll('\t', '\\t')
|
|
9
|
+
.replaceAll(`'`, `\\'`);
|
|
10
|
+
return `'${escaped}'`;
|
|
11
|
+
}
|
|
12
|
+
function safeKey(name) {
|
|
13
|
+
return identifierRegex.test(name) ? name : quote(name);
|
|
14
|
+
}
|
|
15
|
+
function quoteMultiline(val) {
|
|
16
|
+
if (val.includes('\n') || val.includes('`') || val.includes('${')) {
|
|
17
|
+
return `\`${val.replaceAll('\\', '\\\\').replaceAll('`', '\\`').replaceAll('${', '\\${')}\``;
|
|
18
|
+
}
|
|
19
|
+
return quote(val);
|
|
20
|
+
}
|
|
21
|
+
function toPascalCase(name) {
|
|
22
|
+
const pascal = name
|
|
23
|
+
.split(/[_\s-]+/)
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
26
|
+
.join('');
|
|
27
|
+
// A routine name starting with a digit (e.g. `2fa_check`) would yield an invalid identifier;
|
|
28
|
+
// prefix `_` so the emitted `export const ...` parses.
|
|
29
|
+
return /^[A-Za-z_$]/.test(pascal) ? pascal : `_${pascal}`;
|
|
30
|
+
}
|
|
31
|
+
// `satisfies` ties this list to `RoutineRuntimeType` so the runtime set can't drift from the union.
|
|
32
|
+
const ROUTINE_RUNTIME_TYPES = [
|
|
33
|
+
'string',
|
|
34
|
+
'number',
|
|
35
|
+
'boolean',
|
|
36
|
+
'bigint',
|
|
37
|
+
'Buffer',
|
|
38
|
+
'Date',
|
|
39
|
+
'object',
|
|
40
|
+
'any',
|
|
41
|
+
];
|
|
42
|
+
const ROUTINE_RUNTIME_TYPE_SET = new Set(ROUTINE_RUNTIME_TYPES);
|
|
43
|
+
function narrowRuntimeType(runtimeType) {
|
|
44
|
+
return runtimeType && ROUTINE_RUNTIME_TYPE_SET.has(runtimeType) ? runtimeType : 'any';
|
|
45
|
+
}
|
|
46
|
+
/** Always emits the class-less `new Routine(...)` form; there is no decorator form for routines. */
|
|
47
|
+
export class RoutineSourceFile {
|
|
48
|
+
routine;
|
|
49
|
+
namingStrategy;
|
|
50
|
+
platform;
|
|
51
|
+
options;
|
|
52
|
+
constructor(routine, namingStrategy, platform, options) {
|
|
53
|
+
this.routine = routine;
|
|
54
|
+
this.namingStrategy = namingStrategy;
|
|
55
|
+
this.platform = platform;
|
|
56
|
+
this.options = options;
|
|
57
|
+
}
|
|
58
|
+
generate() {
|
|
59
|
+
return this.emitRoutine(this.getClassName(), this.buildConfig());
|
|
60
|
+
}
|
|
61
|
+
getBaseName(extension = '.ts') {
|
|
62
|
+
return `${this.options.fileName(this.getClassName())}${extension}`;
|
|
63
|
+
}
|
|
64
|
+
getClassName() {
|
|
65
|
+
return toPascalCase(this.routine.name);
|
|
66
|
+
}
|
|
67
|
+
buildConfig() {
|
|
68
|
+
const lines = [];
|
|
69
|
+
lines.push(` name: ${quote(this.routine.name)},`);
|
|
70
|
+
lines.push(` type: ${quote(this.routine.type)},`);
|
|
71
|
+
if (this.routine.schema) {
|
|
72
|
+
lines.push(` schema: ${quote(this.routine.schema)},`);
|
|
73
|
+
}
|
|
74
|
+
if (this.routine.language) {
|
|
75
|
+
lines.push(` language: ${quote(this.routine.language)},`);
|
|
76
|
+
}
|
|
77
|
+
if (this.routine.security) {
|
|
78
|
+
lines.push(` security: ${quote(this.routine.security)},`);
|
|
79
|
+
}
|
|
80
|
+
if (this.routine.deterministic != null) {
|
|
81
|
+
lines.push(` deterministic: ${this.routine.deterministic},`);
|
|
82
|
+
}
|
|
83
|
+
if (this.routine.comment) {
|
|
84
|
+
lines.push(` comment: ${quote(this.routine.comment)},`);
|
|
85
|
+
}
|
|
86
|
+
if (this.routine.params.length > 0) {
|
|
87
|
+
lines.push(` params: ${this.formatParams(this.routine.params)},`);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
lines.push(` params: {},`);
|
|
91
|
+
}
|
|
92
|
+
if (this.routine.returns) {
|
|
93
|
+
lines.push(` returns: ${this.formatReturns(this.routine.returns)},`);
|
|
94
|
+
}
|
|
95
|
+
if (this.routine.body) {
|
|
96
|
+
lines.push(` body: ${quoteMultiline(this.routine.body)},`);
|
|
97
|
+
}
|
|
98
|
+
else if (this.routine.expression) {
|
|
99
|
+
lines.push(` expression: ${quoteMultiline(this.routine.expression)},`);
|
|
100
|
+
}
|
|
101
|
+
return `{\n${lines.join('\n')}\n}`;
|
|
102
|
+
}
|
|
103
|
+
formatParams(params) {
|
|
104
|
+
const lines = params.map(p => {
|
|
105
|
+
const parts = [`type: ${quote(p.type)}`];
|
|
106
|
+
if (p.direction !== 'in') {
|
|
107
|
+
parts.push(`direction: ${quote(p.direction)}`);
|
|
108
|
+
}
|
|
109
|
+
if (p.direction === 'out' || p.direction === 'inout') {
|
|
110
|
+
parts.push(`ref: true`);
|
|
111
|
+
}
|
|
112
|
+
if (p.nullable) {
|
|
113
|
+
parts.push(`nullable: true`);
|
|
114
|
+
}
|
|
115
|
+
if (p.defaultRaw) {
|
|
116
|
+
parts.push(`defaultRaw: ${quote(p.defaultRaw)}`);
|
|
117
|
+
}
|
|
118
|
+
return ` ${safeKey(p.name)}: { ${parts.join(', ')} },`;
|
|
119
|
+
});
|
|
120
|
+
return `{\n${lines.join('\n')}\n }`;
|
|
121
|
+
}
|
|
122
|
+
formatReturns(returns) {
|
|
123
|
+
const parts = [];
|
|
124
|
+
// Narrow to `RoutineRuntimeType`; unrecognised types collapse to `'any'`.
|
|
125
|
+
const inferred = returns.runtimeType ?? this.platform.getMappedType(returns.type)?.runtimeType;
|
|
126
|
+
parts.push(`runtimeType: ${quote(narrowRuntimeType(inferred))}`);
|
|
127
|
+
parts.push(`columnType: ${quote(returns.type)}`);
|
|
128
|
+
if (returns.nullable) {
|
|
129
|
+
parts.push(`nullable: true`);
|
|
130
|
+
}
|
|
131
|
+
return `{ ${parts.join(', ')} }`;
|
|
132
|
+
}
|
|
133
|
+
emitRoutine(className, config) {
|
|
134
|
+
return `import { Routine } from '@mikro-orm/core';\n\nexport const ${className} = new Routine(${config});\n`;
|
|
135
|
+
}
|
|
136
|
+
}
|
package/SourceFile.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Dictionary, type EmbeddableOptions, type EntityMetadata, type EntityOptions, type EntityProperty, type GenerateOptions, type IndexOptions, type NamingStrategy, type OneToOneOptions, type Platform, type UniqueOptions } from '@mikro-orm/core';
|
|
1
|
+
import { type Dictionary, type EmbeddableOptions, type EntityMetadata, type EntityOptions, type EntityPartitionBy, type EntityProperty, type GenerateOptions, type IndexOptions, type NamingStrategy, type OneToOneOptions, type Platform, type UniqueOptions } from '@mikro-orm/core';
|
|
2
2
|
/**
|
|
3
3
|
* @see https://github.com/tc39/proposal-regexp-unicode-property-escapes#other-examples
|
|
4
4
|
*/
|
|
@@ -30,6 +30,7 @@ export declare class SourceFile {
|
|
|
30
30
|
protected serializeObject(options: {}, wordwrap?: number, spaces?: number, level?: number): string;
|
|
31
31
|
protected serializeValue(val: unknown, wordwrap?: number, spaces?: number, level?: number): unknown;
|
|
32
32
|
protected getEntityDeclOptions(): EntityOptions<unknown>;
|
|
33
|
+
protected getPartitionByDecl(partitionBy: EntityPartitionBy): Dictionary;
|
|
33
34
|
protected getEmbeddableDeclOptions(): EmbeddableOptions<unknown>;
|
|
34
35
|
private getCollectionDecl;
|
|
35
36
|
private getPropertyDecorator;
|
package/SourceFile.js
CHANGED
|
@@ -130,6 +130,9 @@ export class SourceFile {
|
|
|
130
130
|
else if (typeof index.expression === 'function') {
|
|
131
131
|
indexOpt.expression = `${index.expression}`.replace(')=>`', ') => `');
|
|
132
132
|
}
|
|
133
|
+
if (typeof index.where === 'string') {
|
|
134
|
+
indexOpt.where = this.quote(index.where);
|
|
135
|
+
}
|
|
133
136
|
if (isAtEntityLevel && index.properties) {
|
|
134
137
|
indexOpt.properties = Utils.asArray(index.properties).map(prop => this.quote('' + prop));
|
|
135
138
|
}
|
|
@@ -170,6 +173,9 @@ export class SourceFile {
|
|
|
170
173
|
else if (typeof index.expression === 'function') {
|
|
171
174
|
uniqueOpt.expression = `${index.expression}`.replace(')=>`', ') => `');
|
|
172
175
|
}
|
|
176
|
+
if (typeof index.where === 'string') {
|
|
177
|
+
uniqueOpt.where = this.quote(index.where);
|
|
178
|
+
}
|
|
173
179
|
if (isAtEntityLevel && index.properties) {
|
|
174
180
|
uniqueOpt.properties = Utils.asArray(index.properties).map(prop => this.quote('' + prop));
|
|
175
181
|
}
|
|
@@ -478,6 +484,9 @@ export class SourceFile {
|
|
|
478
484
|
if (this.meta.comment) {
|
|
479
485
|
options.comment = this.quote(this.meta.comment);
|
|
480
486
|
}
|
|
487
|
+
if (this.meta.partitionBy) {
|
|
488
|
+
options.partitionBy = this.getPartitionByDecl(this.meta.partitionBy);
|
|
489
|
+
}
|
|
481
490
|
if (this.meta.readonly && !this.meta.virtual) {
|
|
482
491
|
options.readonly = this.meta.readonly;
|
|
483
492
|
}
|
|
@@ -486,6 +495,40 @@ export class SourceFile {
|
|
|
486
495
|
}
|
|
487
496
|
return this.getCollectionDecl(options);
|
|
488
497
|
}
|
|
498
|
+
getPartitionByDecl(partitionBy) {
|
|
499
|
+
const result = { type: this.quote(partitionBy.type) };
|
|
500
|
+
// Introspected metadata from `toEntityPartitionBy` always emits a string or string[];
|
|
501
|
+
// callback-form expressions only exist in hand-written entity metadata. Fail loud if a
|
|
502
|
+
// callback ever reaches the generator — stringifying `fn.toString()` would produce source
|
|
503
|
+
// that re-imports nothing and will not compile.
|
|
504
|
+
if (typeof partitionBy.expression === 'function') {
|
|
505
|
+
throw new Error(`Cannot emit entity source for ${this.meta.className}: partitionBy.expression is a callback. ` +
|
|
506
|
+
`Entity generator expects string or string[] expressions from catalog introspection.`);
|
|
507
|
+
}
|
|
508
|
+
const expression = partitionBy.expression;
|
|
509
|
+
if (Array.isArray(expression)) {
|
|
510
|
+
result.expression = expression.map(key => this.quote(String(key)));
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
result.expression = this.quote(String(expression));
|
|
514
|
+
}
|
|
515
|
+
if (partitionBy.type === 'hash') {
|
|
516
|
+
result.partitions = Array.isArray(partitionBy.partitions)
|
|
517
|
+
? partitionBy.partitions.map(name => this.quote(String(name)))
|
|
518
|
+
: partitionBy.partitions;
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
result.partitions = partitionBy.partitions.map(partition => {
|
|
522
|
+
const entry = {};
|
|
523
|
+
if (partition.name) {
|
|
524
|
+
entry.name = this.quote(partition.name);
|
|
525
|
+
}
|
|
526
|
+
entry.values = this.quote(partition.values);
|
|
527
|
+
return entry;
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
return result;
|
|
531
|
+
}
|
|
489
532
|
getEmbeddableDeclOptions() {
|
|
490
533
|
const options = {};
|
|
491
534
|
const result = this.getCollectionDecl(options);
|
|
@@ -618,6 +661,9 @@ export class SourceFile {
|
|
|
618
661
|
if (typeof prop.comment === 'string') {
|
|
619
662
|
options.comment = this.quote(prop.comment);
|
|
620
663
|
}
|
|
664
|
+
if (typeof prop.collation === 'string') {
|
|
665
|
+
options.collation = this.quote(prop.collation);
|
|
666
|
+
}
|
|
621
667
|
// TODO: Composite FKs with default values require additions to default/defaultRaw that are not yet supported.
|
|
622
668
|
if (prop.fieldNames?.length <= 1) {
|
|
623
669
|
if (typeof prop.defaultRaw !== 'undefined' &&
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/entity-generator",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.18-dev.0",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
"copy": "node ../../scripts/copy.mjs"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@mikro-orm/sql": "7.0.
|
|
50
|
+
"@mikro-orm/sql": "7.0.18-dev.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@mikro-orm/core": "^7.0.17"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.0.
|
|
56
|
+
"@mikro-orm/core": "7.0.18-dev.0"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|