@mikro-orm/entity-generator 7.2.0-dev.2 → 7.2.0-dev.21
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/DefineEntitySourceFile.js +3 -0
- package/EntityGenerator.d.ts +2 -0
- package/EntityGenerator.js +35 -2
- package/EntitySchemaSourceFile.js +5 -2
- package/NativeEnumSourceFile.js +6 -3
- package/RoutineSourceFile.js +3 -1
- package/SourceFile.d.ts +5 -1
- package/SourceFile.js +82 -17
- package/package.json +4 -4
|
@@ -38,6 +38,9 @@ export class DefineEntitySourceFile extends EntitySchemaSourceFile {
|
|
|
38
38
|
if (this.meta.uniques.length > 0) {
|
|
39
39
|
entitySchemaOptions.uniques = this.meta.uniques.map(index => this.getUniqueOptions(index));
|
|
40
40
|
}
|
|
41
|
+
if (this.meta.checks.length > 0) {
|
|
42
|
+
entitySchemaOptions.checks = this.meta.checks.map(check => this.getCheckOptions(check));
|
|
43
|
+
}
|
|
41
44
|
entitySchemaOptions.properties = Object.fromEntries(Object.entries(this.meta.properties).map(([name, prop]) => [name, this.getPropertyBuilder(prop)]));
|
|
42
45
|
// Force top level and properties to be indented, regardless of line length
|
|
43
46
|
entitySchemaOptions[Config] = true;
|
package/EntityGenerator.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export declare class EntityGenerator {
|
|
|
7
7
|
static register(orm: MikroORM): void;
|
|
8
8
|
generate(options?: GenerateOptions): Promise<string[]>;
|
|
9
9
|
private getEntityMetadata;
|
|
10
|
+
/** Carries introspected RLS state (policies + enablement) from the table onto the entity metadata. */
|
|
11
|
+
private applyRowLevelSecurity;
|
|
10
12
|
private cleanUpReferentialIntegrityRules;
|
|
11
13
|
private matchName;
|
|
12
14
|
private detectManyToManyRelations;
|
package/EntityGenerator.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EntitySchema, ReferenceKind, types, Utils, } from '@mikro-orm/core';
|
|
2
2
|
import { DatabaseSchema, } from '@mikro-orm/sql';
|
|
3
3
|
import { fs } from '@mikro-orm/core/fs-utils';
|
|
4
|
-
import { dirname, join } from 'node:path';
|
|
4
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
5
5
|
import { writeFile } from 'node:fs/promises';
|
|
6
6
|
import { DefineEntitySourceFile } from './DefineEntitySourceFile.js';
|
|
7
7
|
import { EntitySchemaSourceFile } from './EntitySchemaSourceFile.js';
|
|
@@ -62,6 +62,15 @@ export class EntityGenerator {
|
|
|
62
62
|
}
|
|
63
63
|
const files = this.#sources.map(file => [file.getBaseName(), file.generate()]);
|
|
64
64
|
if (options.save) {
|
|
65
|
+
// generated files may only land in the project folder, or under the configured path when
|
|
66
|
+
// that points elsewhere, so a file name can never reach an arbitrary filesystem location
|
|
67
|
+
const allowedRoots = [resolve(this.#config.get('baseDir')), resolve(baseDir)];
|
|
68
|
+
for (const [fileName] of files) {
|
|
69
|
+
const target = resolve(baseDir, fileName);
|
|
70
|
+
if (!allowedRoots.some(root => !relative(root, target).startsWith('..'))) {
|
|
71
|
+
throw new Error(`Cannot generate '${fileName}', it resolves outside of the project folder`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
65
74
|
fs.ensureDir(baseDir);
|
|
66
75
|
const promises = [];
|
|
67
76
|
for (const [fileName, data] of files) {
|
|
@@ -91,7 +100,9 @@ export class EntityGenerator {
|
|
|
91
100
|
}
|
|
92
101
|
}
|
|
93
102
|
}
|
|
94
|
-
|
|
103
|
+
const meta = table.getEntityDeclaration(this.#namingStrategy, this.#helper, options.scalarPropertiesForRelations);
|
|
104
|
+
this.applyRowLevelSecurity(meta, table);
|
|
105
|
+
return meta;
|
|
95
106
|
});
|
|
96
107
|
for (const meta of metadata) {
|
|
97
108
|
for (const prop of meta.relations) {
|
|
@@ -150,6 +161,28 @@ export class EntityGenerator {
|
|
|
150
161
|
await options.onProcessedMetadata?.(metadata, this.#platform);
|
|
151
162
|
return metadata;
|
|
152
163
|
}
|
|
164
|
+
/** Carries introspected RLS state (policies + enablement) from the table onto the entity metadata. */
|
|
165
|
+
applyRowLevelSecurity(meta, table) {
|
|
166
|
+
meta.policies = table.getPolicies().map(policy => ({
|
|
167
|
+
name: policy.name,
|
|
168
|
+
command: policy.command,
|
|
169
|
+
type: policy.type,
|
|
170
|
+
roles: policy.roles,
|
|
171
|
+
...(policy.using != null ? { using: policy.using } : {}),
|
|
172
|
+
...(policy.check != null ? { check: policy.check } : {}),
|
|
173
|
+
}));
|
|
174
|
+
if (table.rlsForced) {
|
|
175
|
+
meta.rowLevelSecurity = 'force';
|
|
176
|
+
}
|
|
177
|
+
else if (table.rlsEnabled) {
|
|
178
|
+
meta.rowLevelSecurity = true;
|
|
179
|
+
}
|
|
180
|
+
else if (meta.policies.length > 0) {
|
|
181
|
+
// policies staged but RLS disabled — record it explicitly so a reload does not re-enable RLS via the
|
|
182
|
+
// policies-imply-RLS default
|
|
183
|
+
meta.rowLevelSecurity = false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
153
186
|
cleanUpReferentialIntegrityRules(metadata) {
|
|
154
187
|
// Clear FK rules that match defaults for:
|
|
155
188
|
// 1. FK-as-PK entities (all PKs are FKs) - cascade for both update and delete
|
|
@@ -33,6 +33,9 @@ export class EntitySchemaSourceFile extends SourceFile {
|
|
|
33
33
|
if (this.meta.uniques.length > 0) {
|
|
34
34
|
entitySchemaOptions.uniques = this.meta.uniques.map(index => this.getUniqueOptions(index));
|
|
35
35
|
}
|
|
36
|
+
if (this.meta.checks.length > 0) {
|
|
37
|
+
entitySchemaOptions.checks = this.meta.checks.map(check => this.getCheckOptions(check));
|
|
38
|
+
}
|
|
36
39
|
entitySchemaOptions.properties = Object.fromEntries(Object.entries(this.meta.properties).map(([name, prop]) => [name, this.getPropertyOptions(prop)]));
|
|
37
40
|
// Force top level and properties to be indented, regardless of line length
|
|
38
41
|
entitySchemaOptions[Config] = true;
|
|
@@ -64,7 +67,7 @@ export class EntitySchemaSourceFile extends SourceFile {
|
|
|
64
67
|
}
|
|
65
68
|
}
|
|
66
69
|
if (primaryProps.length > 0) {
|
|
67
|
-
const primaryPropNames = primaryProps.map(prop =>
|
|
70
|
+
const primaryPropNames = primaryProps.map(prop => this.quoteKey(prop.name));
|
|
68
71
|
if (primaryProps.length > 1) {
|
|
69
72
|
classBody += `${' '.repeat(2)}[${this.referenceCoreImport('PrimaryKeyProp')}]?: [${primaryPropNames.join(', ')}];\n`;
|
|
70
73
|
}
|
|
@@ -73,7 +76,7 @@ export class EntitySchemaSourceFile extends SourceFile {
|
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
78
|
if (eagerProperties.length > 0) {
|
|
76
|
-
const eagerPropertyNames = eagerProperties.map(prop =>
|
|
79
|
+
const eagerPropertyNames = eagerProperties.map(prop => this.quoteKey(prop.name)).sort();
|
|
77
80
|
classBody += `${' '.repeat(2)}[${this.referenceCoreImport('EagerProps')}]?: ${eagerPropertyNames.join(' | ')};\n`;
|
|
78
81
|
}
|
|
79
82
|
classBody += props.join('');
|
package/NativeEnumSourceFile.js
CHANGED
|
@@ -24,10 +24,10 @@ export class NativeEnumSourceFile extends SourceFile {
|
|
|
24
24
|
for (const enumValue of enumValues) {
|
|
25
25
|
const enumName = this.namingStrategy.enumValueToEnumProperty(enumValue, this.nativeEnum.name, '', this.nativeEnum.schema);
|
|
26
26
|
if (enumMode === 'dictionary') {
|
|
27
|
-
ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.
|
|
27
|
+
ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quoteKey(enumName)}: ${this.quote(enumValue)},\n`;
|
|
28
28
|
}
|
|
29
29
|
else {
|
|
30
|
-
ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.
|
|
30
|
+
ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quoteKey(enumName)} = ${this.quote(enumValue)},\n`;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
if (enumMode === 'dictionary') {
|
|
@@ -42,6 +42,9 @@ export class NativeEnumSourceFile extends SourceFile {
|
|
|
42
42
|
return ret;
|
|
43
43
|
}
|
|
44
44
|
getBaseName(extension = '.ts') {
|
|
45
|
-
|
|
45
|
+
// the enum name comes from the database and ends up in a path, so path separators
|
|
46
|
+
// (and anything else that cannot appear in a file name) collapse to `_`
|
|
47
|
+
const name = this.nativeEnum.name.replaceAll(/[^\p{L}\p{N}$_-]+/gu, '_');
|
|
48
|
+
return `${this.options.fileName(name)}${extension}`;
|
|
46
49
|
}
|
|
47
50
|
}
|
package/RoutineSourceFile.js
CHANGED
|
@@ -20,7 +20,9 @@ function quoteMultiline(val) {
|
|
|
20
20
|
}
|
|
21
21
|
function toPascalCase(name) {
|
|
22
22
|
const pascal = name
|
|
23
|
-
|
|
23
|
+
// anything that cannot appear in an identifier acts as a word separator, which keeps
|
|
24
|
+
// path separators out of both the emitted const name and the generated file name
|
|
25
|
+
.split(/[^\p{L}\p{N}$]+/u)
|
|
24
26
|
.filter(Boolean)
|
|
25
27
|
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
26
28
|
.join('');
|
package/SourceFile.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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';
|
|
1
|
+
import { type CheckConstraint, 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
|
*/
|
|
@@ -21,10 +21,13 @@ export declare class SourceFile {
|
|
|
21
21
|
private getColumnOptions;
|
|
22
22
|
protected getIndexOptions(index: EntityMetadata['indexes'][number], isAtEntityLevel?: boolean): IndexOptions<Dictionary, string>;
|
|
23
23
|
protected getUniqueOptions(index: EntityMetadata['uniques'][number], isAtEntityLevel?: boolean): UniqueOptions<Dictionary, string>;
|
|
24
|
+
protected getCheckOptions(check: EntityMetadata['checks'][number]): CheckConstraint<Dictionary>;
|
|
24
25
|
protected generateImports(): string;
|
|
25
26
|
protected getEntityClass(classBody: string): string;
|
|
26
27
|
getBaseName(extension?: string): string;
|
|
27
28
|
protected quote(val: string): string;
|
|
29
|
+
/** For positions that reject a template literal: object keys, import bindings/specifiers, string literal types. */
|
|
30
|
+
protected quoteKey(val: string): string;
|
|
28
31
|
protected getPropertyDefinition(prop: EntityProperty, padLeft: number): string;
|
|
29
32
|
protected getEnumClassDefinition(prop: EntityProperty, padLeft: number): string;
|
|
30
33
|
protected serializeObject(options: {}, wordwrap?: number, spaces?: number, level?: number): string;
|
|
@@ -32,6 +35,7 @@ export declare class SourceFile {
|
|
|
32
35
|
protected getEntityDeclOptions(): EntityOptions<unknown>;
|
|
33
36
|
private isDiscriminatorPropertyUserDefined;
|
|
34
37
|
protected getPartitionByDecl(partitionBy: EntityPartitionBy): Dictionary;
|
|
38
|
+
protected getPolicyDecl(policy: EntityMetadata['policies'][number]): Dictionary;
|
|
35
39
|
protected getEmbeddableDeclOptions(): EmbeddableOptions<unknown>;
|
|
36
40
|
private getCollectionDecl;
|
|
37
41
|
private getPropertyDecorator;
|
package/SourceFile.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Cascade, Config, DecimalType, ReferenceKind, SCALAR_TYPES, UnknownType, Utils, inspect, } from '@mikro-orm/core';
|
|
2
|
+
import { DatabaseTable } from '@mikro-orm/sql';
|
|
2
3
|
import { parse, relative } from 'node:path';
|
|
3
4
|
import { POSSIBLE_TYPE_IMPORTS } from './CoreImportsHelper.js';
|
|
4
5
|
/**
|
|
@@ -26,11 +27,13 @@ export class SourceFile {
|
|
|
26
27
|
if (this.meta.embeddable || this.meta.collection) {
|
|
27
28
|
if (this.meta.embeddable) {
|
|
28
29
|
const options = this.getEmbeddableDeclOptions();
|
|
29
|
-
|
|
30
|
+
const decl = `@${this.referenceDecoratorImport('Embeddable')}(`;
|
|
31
|
+
ret += `${decl}${Utils.hasObjectKeys(options) ? this.serializeObject(options, 80 - decl.length, 0) : ''})\n`;
|
|
30
32
|
}
|
|
31
33
|
else {
|
|
32
34
|
const options = this.getEntityDeclOptions();
|
|
33
|
-
|
|
35
|
+
const decl = `@${this.referenceDecoratorImport('Entity')}(`;
|
|
36
|
+
ret += `${decl}${Utils.hasObjectKeys(options) ? this.serializeObject(options, 80 - decl.length, 0) : ''})\n`;
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
39
|
for (const index of this.meta.indexes) {
|
|
@@ -45,6 +48,9 @@ export class SourceFile {
|
|
|
45
48
|
}
|
|
46
49
|
ret += `@${this.referenceDecoratorImport('Unique')}(${this.serializeObject(this.getUniqueOptions(index))})\n`;
|
|
47
50
|
}
|
|
51
|
+
for (const check of this.meta.checks) {
|
|
52
|
+
ret += `@${this.referenceDecoratorImport('Check')}(${this.serializeObject(this.getCheckOptions(check))})\n`;
|
|
53
|
+
}
|
|
48
54
|
let classHead = '';
|
|
49
55
|
if (this.meta.className === this.options.customBaseEntityName) {
|
|
50
56
|
const defineConfigTypeSettings = {};
|
|
@@ -79,7 +85,7 @@ export class SourceFile {
|
|
|
79
85
|
}
|
|
80
86
|
});
|
|
81
87
|
if (primaryProps.length > 0) {
|
|
82
|
-
const primaryPropNames = primaryProps.map(prop =>
|
|
88
|
+
const primaryPropNames = primaryProps.map(prop => this.quoteKey(prop.name));
|
|
83
89
|
if (primaryProps.length > 1) {
|
|
84
90
|
classHead += `\n${' '.repeat(2)}[${this.referenceCoreImport('PrimaryKeyProp')}]?: [${primaryPropNames.join(', ')}];\n`;
|
|
85
91
|
}
|
|
@@ -88,7 +94,7 @@ export class SourceFile {
|
|
|
88
94
|
}
|
|
89
95
|
}
|
|
90
96
|
if (eagerProperties.length > 0) {
|
|
91
|
-
const eagerPropertyNames = eagerProperties.map(prop =>
|
|
97
|
+
const eagerPropertyNames = eagerProperties.map(prop => this.quoteKey(prop.name)).sort();
|
|
92
98
|
classHead += `\n${' '.repeat(2)}[${this.referenceCoreImport('EagerProps')}]?: ${eagerPropertyNames.join(' | ')};\n`;
|
|
93
99
|
}
|
|
94
100
|
ret += this.getEntityClass(classBody ? `${classHead}\n${classBody}` : classHead);
|
|
@@ -198,6 +204,14 @@ export class SourceFile {
|
|
|
198
204
|
}
|
|
199
205
|
return uniqueOpt;
|
|
200
206
|
}
|
|
207
|
+
getCheckOptions(check) {
|
|
208
|
+
const checkOpt = {};
|
|
209
|
+
if (typeof check.name === 'string') {
|
|
210
|
+
checkOpt.name = this.quote(check.name);
|
|
211
|
+
}
|
|
212
|
+
checkOpt.expression = this.quote(check.expression);
|
|
213
|
+
return checkOpt;
|
|
214
|
+
}
|
|
201
215
|
generateImports() {
|
|
202
216
|
const imports = new Set();
|
|
203
217
|
if (this.coreImports.size > 0) {
|
|
@@ -242,22 +256,22 @@ export class SourceFile {
|
|
|
242
256
|
if (file.name === '') {
|
|
243
257
|
continue;
|
|
244
258
|
}
|
|
245
|
-
importMap.set(file.path, `import ${this.
|
|
259
|
+
importMap.set(file.path, `import ${this.quoteKey(file.name)};`);
|
|
246
260
|
continue;
|
|
247
261
|
}
|
|
248
262
|
if (file.name === '') {
|
|
249
|
-
importMap.set(file.path, `import * as ${entity} from ${this.
|
|
263
|
+
importMap.set(file.path, `import * as ${entity} from ${this.quoteKey(file.path)};`);
|
|
250
264
|
continue;
|
|
251
265
|
}
|
|
252
266
|
if (file.name === 'default') {
|
|
253
|
-
importMap.set(file.path, `import ${entity} from ${this.
|
|
267
|
+
importMap.set(file.path, `import ${entity} from ${this.quoteKey(file.path)};`);
|
|
254
268
|
continue;
|
|
255
269
|
}
|
|
256
270
|
if (file.name === entity) {
|
|
257
|
-
importMap.set(file.path, `import { ${entity} } from ${this.
|
|
271
|
+
importMap.set(file.path, `import { ${entity} } from ${this.quoteKey(file.path)};`);
|
|
258
272
|
continue;
|
|
259
273
|
}
|
|
260
|
-
importMap.set(file.path, `import { ${identifierRegex.test(file.name) ? file.name : this.
|
|
274
|
+
importMap.set(file.path, `import { ${identifierRegex.test(file.name) ? file.name : this.quoteKey(file.name)} as ${entity} } from ${this.quoteKey(file.path)};`);
|
|
261
275
|
}
|
|
262
276
|
if (this.enumImports.size) {
|
|
263
277
|
for (const [name, exports] of this.enumImports.entries()) {
|
|
@@ -265,7 +279,7 @@ export class SourceFile {
|
|
|
265
279
|
path: `${basePath}/${this.options.fileName(name)}${extension}`,
|
|
266
280
|
name,
|
|
267
281
|
};
|
|
268
|
-
importMap.set(file.path, `import { ${exports.join(', ')} } from ${this.
|
|
282
|
+
importMap.set(file.path, `import { ${exports.join(', ')} } from ${this.quoteKey(file.path)};`);
|
|
269
283
|
}
|
|
270
284
|
}
|
|
271
285
|
for (const key of [...importMap.keys()].sort()) {
|
|
@@ -294,12 +308,26 @@ export class SourceFile {
|
|
|
294
308
|
}
|
|
295
309
|
quote(val) {
|
|
296
310
|
const backtick = val.startsWith(`'`) || val.includes('\n');
|
|
297
|
-
|
|
298
|
-
|
|
311
|
+
// Backslashes first, so the escapes added below don't get neutralized by a preceding `\`.
|
|
312
|
+
// A raw `\r` is a syntax error in a string literal and silently normalizes to `\n` in a template one.
|
|
313
|
+
const escaped = val.replaceAll('\\', '\\\\').replaceAll('\r', '\\r');
|
|
314
|
+
return backtick
|
|
315
|
+
? `\`${escaped.replaceAll('`', '\\`').replaceAll('${', '\\${')}\``
|
|
316
|
+
: `'${escaped.replaceAll(`'`, `\\'`)}'`;
|
|
317
|
+
}
|
|
318
|
+
/** For positions that reject a template literal: object keys, import bindings/specifiers, string literal types. */
|
|
319
|
+
quoteKey(val) {
|
|
320
|
+
const escaped = val
|
|
321
|
+
.replaceAll('\\', '\\\\')
|
|
322
|
+
.replaceAll('\r', '\\r')
|
|
323
|
+
.replaceAll('\n', '\\n')
|
|
324
|
+
.replaceAll('\t', '\\t')
|
|
325
|
+
.replaceAll(`'`, `\\'`);
|
|
326
|
+
return `'${escaped}'`;
|
|
299
327
|
}
|
|
300
328
|
getPropertyDefinition(prop, padLeft) {
|
|
301
329
|
const padding = ' '.repeat(padLeft);
|
|
302
|
-
const propName = identifierRegex.test(prop.name) ? prop.name : this.
|
|
330
|
+
const propName = identifierRegex.test(prop.name) ? prop.name : this.quoteKey(prop.name);
|
|
303
331
|
const enumMode = this.options.enumMode;
|
|
304
332
|
let hiddenType = '';
|
|
305
333
|
if (prop.hidden) {
|
|
@@ -419,10 +447,10 @@ export class SourceFile {
|
|
|
419
447
|
for (const enumValue of enumValues) {
|
|
420
448
|
const enumName = this.namingStrategy.enumValueToEnumProperty(enumValue, prop.fieldNames[0], this.meta.collection, this.meta.schema);
|
|
421
449
|
if (enumMode === 'dictionary') {
|
|
422
|
-
ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.
|
|
450
|
+
ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quoteKey(enumName)}: ${formatValue(enumValue)},\n`;
|
|
423
451
|
}
|
|
424
452
|
else {
|
|
425
|
-
ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.
|
|
453
|
+
ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quoteKey(enumName)} = ${formatValue(enumValue)},\n`;
|
|
426
454
|
}
|
|
427
455
|
}
|
|
428
456
|
if (enumMode === 'dictionary') {
|
|
@@ -452,7 +480,7 @@ export class SourceFile {
|
|
|
452
480
|
const entries = Object.entries(options);
|
|
453
481
|
return `{${doIndent ? `\n${' '.repeat(spaces)}` : ' '}${entries
|
|
454
482
|
.map(([opt, val]) => {
|
|
455
|
-
const key = identifierRegex.test(opt) ? opt : this.
|
|
483
|
+
const key = identifierRegex.test(opt) ? opt : this.quoteKey(opt);
|
|
456
484
|
return `${doIndent ? ' '.repeat(level * 2 + (spaces + 2)) : ''}${key}: ${this.serializeValue(val, typeof nextWordwrap === 'number' ? nextWordwrap - key.length - 2 /* ': '.length*/ : undefined, doIndent ? spaces : undefined, level + 1)}`;
|
|
457
485
|
})
|
|
458
486
|
.join(sep)}${doIndent ? `${entries.length > 0 ? ',\n' : ''}${' '.repeat(spaces + level * 2)}` : ' '}}`;
|
|
@@ -487,6 +515,21 @@ export class SourceFile {
|
|
|
487
515
|
if (this.meta.partitionBy) {
|
|
488
516
|
options.partitionBy = this.getPartitionByDecl(this.meta.partitionBy);
|
|
489
517
|
}
|
|
518
|
+
// policies imply RLS on reload, so only emit `rowLevelSecurity` when it adds information:
|
|
519
|
+
// `'force'` always, plain `true` only for a deny-all table (enabled with no policies),
|
|
520
|
+
// and explicit `false` only when policies are staged but RLS is disabled
|
|
521
|
+
if (this.meta.rowLevelSecurity === 'force') {
|
|
522
|
+
options.rowLevelSecurity = this.quote('force');
|
|
523
|
+
}
|
|
524
|
+
else if (this.meta.rowLevelSecurity === true && this.meta.policies.length === 0) {
|
|
525
|
+
options.rowLevelSecurity = true;
|
|
526
|
+
}
|
|
527
|
+
else if (this.meta.rowLevelSecurity === false && this.meta.policies.length > 0) {
|
|
528
|
+
options.rowLevelSecurity = false;
|
|
529
|
+
}
|
|
530
|
+
if (this.meta.policies.length > 0) {
|
|
531
|
+
options.policies = this.meta.policies.map(policy => this.getPolicyDecl(policy));
|
|
532
|
+
}
|
|
490
533
|
if (this.meta.readonly && !this.meta.virtual) {
|
|
491
534
|
options.readonly = this.meta.readonly;
|
|
492
535
|
}
|
|
@@ -537,6 +580,27 @@ export class SourceFile {
|
|
|
537
580
|
}
|
|
538
581
|
return result;
|
|
539
582
|
}
|
|
583
|
+
getPolicyDecl(policy) {
|
|
584
|
+
// introspected names are always explicit; the rest is emitted only when it differs from the default
|
|
585
|
+
const result = { name: this.quote(policy.name) };
|
|
586
|
+
if (policy.command && policy.command !== 'all') {
|
|
587
|
+
result.command = this.quote(policy.command);
|
|
588
|
+
}
|
|
589
|
+
if (policy.type && policy.type !== 'permissive') {
|
|
590
|
+
result.type = this.quote(policy.type);
|
|
591
|
+
}
|
|
592
|
+
const roles = policy.roles;
|
|
593
|
+
if (roles && !DatabaseTable.isDefaultPolicyRoles(roles)) {
|
|
594
|
+
result.roles = roles.map(role => this.quote(role));
|
|
595
|
+
}
|
|
596
|
+
if (typeof policy.using === 'string') {
|
|
597
|
+
result.using = this.quote(policy.using);
|
|
598
|
+
}
|
|
599
|
+
if (typeof policy.check === 'string') {
|
|
600
|
+
result.check = this.quote(policy.check);
|
|
601
|
+
}
|
|
602
|
+
return result;
|
|
603
|
+
}
|
|
540
604
|
getEmbeddableDeclOptions() {
|
|
541
605
|
const options = {};
|
|
542
606
|
return this.getCollectionDecl(options, 'discriminator');
|
|
@@ -673,7 +737,8 @@ export class SourceFile {
|
|
|
673
737
|
prop.defaultRaw !== 'null' &&
|
|
674
738
|
prop.defaultRaw !== '' &&
|
|
675
739
|
prop.defaultRaw !== (typeof prop.default === 'string' ? this.quote(prop.default) : `${prop.default}`)) {
|
|
676
|
-
|
|
740
|
+
// kept as a template literal for readability, but raw SQL may contain `${`, which would otherwise interpolate
|
|
741
|
+
options.defaultRaw = `\`${prop.defaultRaw.replaceAll('\\', '\\\\').replaceAll('`', '\\`').replaceAll('${', '\\${')}\``;
|
|
677
742
|
}
|
|
678
743
|
else if (!(typeof prop.default === 'undefined' || prop.default === null) &&
|
|
679
744
|
(prop.ref ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/entity-generator",
|
|
3
|
-
"version": "7.2.0-dev.
|
|
3
|
+
"version": "7.2.0-dev.21",
|
|
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.2.0-dev.
|
|
50
|
+
"@mikro-orm/sql": "7.2.0-dev.21"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@mikro-orm/core": "^7.1.
|
|
53
|
+
"@mikro-orm/core": "^7.1.15"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@mikro-orm/core": "7.2.0-dev.
|
|
56
|
+
"@mikro-orm/core": "7.2.0-dev.21"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">= 22.17.0"
|