@postxl/generator 0.73.5 → 0.74.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/dist/generator.js +23 -12
- package/dist/generators/indices/export/class.generator.js +7 -7
- package/dist/generators/models/route.generator.js +7 -3
- package/dist/generators/models/update/service.generator.js +51 -1
- package/dist/generators/models/view/service.generator.js +63 -10
- package/dist/lib/utils/string.d.ts +4 -0
- package/dist/lib/utils/string.js +12 -1
- package/dist/prisma/parse.js +54 -0
- package/package.json +1 -1
package/dist/generator.js
CHANGED
|
@@ -421,20 +421,31 @@ function generate(_a) {
|
|
|
421
421
|
}
|
|
422
422
|
// NOTE: Lastly we generate the log of the changes.
|
|
423
423
|
const log = lock_1.ConsoleUtils.getFilesChangelog(results.map((result) => {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
result.prevDisk.
|
|
433
|
-
|
|
424
|
+
switch (result.status) {
|
|
425
|
+
case 'write': {
|
|
426
|
+
if (result.prevDisk === undefined) {
|
|
427
|
+
return { path: result.path, status: 'new' };
|
|
428
|
+
}
|
|
429
|
+
if (result.disk === undefined) {
|
|
430
|
+
return { path: result.path, status: 'deleted' };
|
|
431
|
+
}
|
|
432
|
+
if (runtime_1.BufferUtils.equals(result.prevDisk.content, result.disk.content) &&
|
|
433
|
+
result.prevDisk.mode === result.disk.mode) {
|
|
434
|
+
return { path: result.path, status: 'unchanged' };
|
|
435
|
+
}
|
|
436
|
+
return { path: result.path, status: 'changed' };
|
|
434
437
|
}
|
|
435
|
-
|
|
438
|
+
case 'skip':
|
|
439
|
+
if (result.prevDisk &&
|
|
440
|
+
result.disk &&
|
|
441
|
+
runtime_1.BufferUtils.equals(result.prevDisk.content, result.disk.content) &&
|
|
442
|
+
result.prevDisk.mode === result.disk.mode) {
|
|
443
|
+
return { path: result.path, status: 'unchanged' };
|
|
444
|
+
}
|
|
445
|
+
return { path: result.path, status: 'skipped' };
|
|
446
|
+
default:
|
|
447
|
+
throw new types_2.ExhaustiveSwitchCheck(result);
|
|
436
448
|
}
|
|
437
|
-
return { path: result.path, status: 'skipped' };
|
|
438
449
|
}));
|
|
439
450
|
console.info(log);
|
|
440
451
|
const perfEnd = performance.now();
|
|
@@ -39,12 +39,12 @@ function generateExporterClass({ models, meta }) {
|
|
|
39
39
|
}
|
|
40
40
|
const linkedModelMeta = (0, meta_1.getModelMetadata)({ model: field.relationToModel });
|
|
41
41
|
if (field.isRequired) {
|
|
42
|
-
linkedItems.push(`await this.${linkedModelMeta.export.exportAddFunctionName}({id: item.${field.name}, includeChildren: false})`);
|
|
42
|
+
linkedItems.push(`await this.${linkedModelMeta.export.exportAddFunctionName}({id: item.${field.name}, includeChildren: false, user})`);
|
|
43
43
|
}
|
|
44
44
|
else {
|
|
45
45
|
linkedItems.push(`
|
|
46
46
|
if (item.${field.name}) {
|
|
47
|
-
await this.${linkedModelMeta.export.exportAddFunctionName}({id: item.${field.name}, includeChildren: false})
|
|
47
|
+
await this.${linkedModelMeta.export.exportAddFunctionName}({id: item.${field.name}, includeChildren: false, user})
|
|
48
48
|
}`);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -55,7 +55,7 @@ function generateExporterClass({ models, meta }) {
|
|
|
55
55
|
childItemCalls.push(`
|
|
56
56
|
// Add all ${linkedModelMeta.userFriendlyNamePlural} that are related to the ${modelMeta.userFriendlyName} via ${referencingField.name}
|
|
57
57
|
for (const ${referencingField.name} of await this.viewService.${linkedModelMeta.view.serviceVariableName}.data.${linkedFieldMeta.getByForeignKeyIdsMethodFnName}(id)) {
|
|
58
|
-
await this.${linkedModelMeta.export.exportAddFunctionName}({id: ${referencingField.name}, includeChildren})
|
|
58
|
+
await this.${linkedModelMeta.export.exportAddFunctionName}({id: ${referencingField.name}, includeChildren, user})
|
|
59
59
|
}
|
|
60
60
|
`);
|
|
61
61
|
}
|
|
@@ -65,7 +65,7 @@ function generateExporterClass({ models, meta }) {
|
|
|
65
65
|
${childItemCalls.join('\n')}
|
|
66
66
|
}`
|
|
67
67
|
: '';
|
|
68
|
-
const signature = `{id, includeChildren = true}: {id: ${model.brandedIdType}, includeChildren?: boolean}`;
|
|
68
|
+
const signature = `{id, includeChildren = true, user}: {id: ${model.brandedIdType}, includeChildren?: boolean, user: User}`;
|
|
69
69
|
addFunctions.push(`
|
|
70
70
|
/**
|
|
71
71
|
* Adds a ${modelMeta.userFriendlyName} and all related (and nested) dependencies to the export.
|
|
@@ -74,7 +74,7 @@ function generateExporterClass({ models, meta }) {
|
|
|
74
74
|
if (this.${modelMeta.internalPluralName}.has(id)) {
|
|
75
75
|
return
|
|
76
76
|
}
|
|
77
|
-
const item = await this.viewService.${modelMeta.view.serviceVariableName}.get(id)
|
|
77
|
+
const item = await this.viewService.${modelMeta.view.serviceVariableName}.get({ id, user })
|
|
78
78
|
if (!item) {
|
|
79
79
|
this.logger.error(\`Cannot find ${modelMeta.userFriendlyName} \${id}\`)
|
|
80
80
|
return
|
|
@@ -86,7 +86,7 @@ function generateExporterClass({ models, meta }) {
|
|
|
86
86
|
${childItems}
|
|
87
87
|
}
|
|
88
88
|
`);
|
|
89
|
-
addAllCalls.push(`this.${modelMeta.internalPluralName} = await this.viewService.${modelMeta.view.serviceVariableName}.getAll()`);
|
|
89
|
+
addAllCalls.push(`this.${modelMeta.internalPluralName} = await this.viewService.${modelMeta.view.serviceVariableName}.getAll(user)`);
|
|
90
90
|
}
|
|
91
91
|
return /* ts */ `
|
|
92
92
|
// For consistency, we include \`{ includeChildren = true }\` in the signature of every add function.
|
|
@@ -126,7 +126,7 @@ export class ${meta.export.exporterClass.name} {
|
|
|
126
126
|
/**
|
|
127
127
|
* Exports all data.
|
|
128
128
|
*/
|
|
129
|
-
public async exportAll(): Promise<${meta.export.encoder.encodedExcelDataTypeName}> {
|
|
129
|
+
public async exportAll(user: User): Promise<${meta.export.encoder.encodedExcelDataTypeName}> {
|
|
130
130
|
${addAllCalls.join('\n')}
|
|
131
131
|
|
|
132
132
|
return this.exportData()
|
|
@@ -35,10 +35,14 @@ export const ${meta.trpc.routerName} = router({
|
|
|
35
35
|
${defaultField ? defaultValueMethod : ''}
|
|
36
36
|
|
|
37
37
|
get: procedure
|
|
38
|
+
.use(authMiddleware)
|
|
38
39
|
.input(z.${idField.unbrandedTypeName}().transform(${meta.types.toBrandedIdTypeFnName}))
|
|
39
|
-
.query(({ input, ctx }) => ctx.view.${meta.data.dataServiceName}.get(input)),
|
|
40
|
-
getMap: procedure
|
|
40
|
+
.query(({ input, ctx }) => ctx.view.${meta.data.dataServiceName}.get({ id: input, user: ctx.user})),
|
|
41
|
+
getMap: procedure
|
|
42
|
+
.use(authMiddleware)
|
|
43
|
+
.query(({ ctx }) => ctx.view.${meta.data.dataServiceName}.getAll(ctx.user)),
|
|
41
44
|
getList: procedure
|
|
45
|
+
.use(authMiddleware)
|
|
42
46
|
.input(z.object({
|
|
43
47
|
cursor: ${meta.view.cursorDecoder}.optional(),
|
|
44
48
|
sort: z.object({
|
|
@@ -60,7 +64,7 @@ export const ${meta.trpc.routerName} = router({
|
|
|
60
64
|
.optional(),
|
|
61
65
|
}))
|
|
62
66
|
.query(({ input, ctx }) => {
|
|
63
|
-
return ctx.view.${meta.data.dataServiceName}.getList(input)
|
|
67
|
+
return ctx.view.${meta.data.dataServiceName}.getList({...input, user: ctx.user })
|
|
64
68
|
}),
|
|
65
69
|
|
|
66
70
|
create: procedure
|
|
@@ -22,7 +22,7 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
|
|
|
22
22
|
const { view, update, types, data } = meta;
|
|
23
23
|
const imports = imports_1.ImportsGenerator.from(meta.update.serviceClassLocation.path).addImports({
|
|
24
24
|
[data.repository.location.import]: data.repository.className,
|
|
25
|
-
[types.importPath]: [model.brandedIdType, types.typeName, types.toBrandedIdTypeFnName],
|
|
25
|
+
[types.importPath]: [model.brandedIdType, types.typeName, types.toBrandedIdTypeFnName, (0, types_1.toTypeName)('User')],
|
|
26
26
|
[view.serviceLocation.import]: [view.serviceClassName],
|
|
27
27
|
[schemaMeta.actions.execution.interfaceLocation.import]: [schemaMeta.actions.execution.interface],
|
|
28
28
|
[schemaMeta.actions.dispatcher.definitionLocation.import]: [schemaMeta.actions.dispatcher.definition],
|
|
@@ -47,6 +47,8 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
|
|
|
47
47
|
|
|
48
48
|
${imports.generate()}
|
|
49
49
|
|
|
50
|
+
import { TRPCError } from '@trpc/server'
|
|
51
|
+
|
|
50
52
|
export type Scope = "${meta.actions.actionScopeConstType}"
|
|
51
53
|
|
|
52
54
|
export type Actions = {
|
|
@@ -136,6 +138,15 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
|
|
|
136
138
|
data: CreatePayload;
|
|
137
139
|
execution: ${schemaMeta.actions.execution.interface}
|
|
138
140
|
}): Promise<${types.typeName}> {
|
|
141
|
+
// NOTE: User from execution.user should be authorized to create this item.
|
|
142
|
+
// eslint-disable-next-line no-constant-condition
|
|
143
|
+
if (false) {
|
|
144
|
+
throw new TRPCError({
|
|
145
|
+
code: 'UNAUTHORIZED',
|
|
146
|
+
message: \`You are not authorized to create a ${meta.userFriendlyName}\`,
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
139
150
|
return this.data.create({ item: data, execution })
|
|
140
151
|
}
|
|
141
152
|
|
|
@@ -144,6 +155,9 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
|
|
|
144
155
|
data: UpdatePayload;
|
|
145
156
|
execution: ${schemaMeta.actions.execution.interface}
|
|
146
157
|
}): Promise<${types.typeName}> {
|
|
158
|
+
// NOTE: We need to make sure that the user is authorized to access the item
|
|
159
|
+
await this.authorize(data.id, execution.user)
|
|
160
|
+
|
|
147
161
|
return this.data.update({ item: data, execution })
|
|
148
162
|
}
|
|
149
163
|
|
|
@@ -152,6 +166,18 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
|
|
|
152
166
|
data: UpsertPayload;
|
|
153
167
|
execution: ${schemaMeta.actions.execution.interface}
|
|
154
168
|
}): Promise<${types.typeName}> {
|
|
169
|
+
if ('id' in data) {
|
|
170
|
+
await this.authorize(data.id, execution.user)
|
|
171
|
+
} else {
|
|
172
|
+
// eslint-disable-next-line no-constant-condition
|
|
173
|
+
if (false) {
|
|
174
|
+
throw new TRPCError({
|
|
175
|
+
code: 'UNAUTHORIZED',
|
|
176
|
+
message: \`You are not authorized to create a ${meta.userFriendlyName}\`,
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
155
181
|
return this.data.upsert({ item: data, execution })
|
|
156
182
|
}
|
|
157
183
|
|
|
@@ -160,10 +186,34 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
|
|
|
160
186
|
data: ${model.brandedIdType};
|
|
161
187
|
execution: ${schemaMeta.actions.execution.interface}
|
|
162
188
|
}): Promise<${model.brandedIdType}> {
|
|
189
|
+
// NOTE: We need to make sure that the user is authorized to access the item
|
|
190
|
+
await this.authorize(data, execution.user)
|
|
191
|
+
|
|
163
192
|
return this.data.delete({ id: data, execution })
|
|
164
193
|
}
|
|
165
194
|
|
|
166
195
|
${cloneFn}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Authorizes access to ${meta.userFriendlyName}.
|
|
199
|
+
*/
|
|
200
|
+
public async authorize(id: ${model.brandedIdType}, user: User): Promise<void> {
|
|
201
|
+
// NOTE: Root user is always authorized.
|
|
202
|
+
if (this.viewService.users.data.rootUser.id === user.id) {
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
const item = await this.data.get(id)
|
|
206
|
+
if (!item) {
|
|
207
|
+
throw new Error(\`${meta.userFriendlyName} with id \${id} not found\`)
|
|
208
|
+
}
|
|
209
|
+
// eslint-disable-next-line no-constant-condition
|
|
210
|
+
if (false) {
|
|
211
|
+
throw new TRPCError({
|
|
212
|
+
code: 'UNAUTHORIZED',
|
|
213
|
+
message: \`You are not authorized to access ${meta.userFriendlyName} with id \${id}\`,
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
}
|
|
167
217
|
}
|
|
168
218
|
`;
|
|
169
219
|
}
|
|
@@ -15,7 +15,11 @@ function generateModelBusinessLogicView({ model, meta }) {
|
|
|
15
15
|
const imports = imports_1.ImportsGenerator.from(meta.view.serviceLocation.path);
|
|
16
16
|
imports.addImports({
|
|
17
17
|
[meta.data.repository.location.import]: meta.data.repository.className,
|
|
18
|
-
[meta.types.importPath]: [
|
|
18
|
+
[meta.types.importPath]: [
|
|
19
|
+
(0, types_1.toAnnotatedTypeName)(model.brandedIdType),
|
|
20
|
+
(0, types_1.toAnnotatedTypeName)(meta.types.typeName),
|
|
21
|
+
(0, types_1.toAnnotatedTypeName)((0, types_1.toTypeName)('User')),
|
|
22
|
+
],
|
|
19
23
|
[schemaMeta.view.serviceLocation.path]: schemaMeta.view.serviceClassName,
|
|
20
24
|
});
|
|
21
25
|
const compareFnBlock = (0, ast_1.createSwitchStatement)({
|
|
@@ -84,6 +88,9 @@ import { Inject, Injectable, forwardRef } from '@nestjs/common'
|
|
|
84
88
|
|
|
85
89
|
${imports.generate()}
|
|
86
90
|
|
|
91
|
+
import { TRPCError } from '@trpc/server'
|
|
92
|
+
|
|
93
|
+
|
|
87
94
|
/**
|
|
88
95
|
* Cursor decoder.
|
|
89
96
|
*/
|
|
@@ -123,15 +130,34 @@ export class ${meta.view.serviceClassName} {
|
|
|
123
130
|
* Returns the raw ${meta.userFriendlyName} with the given id or null if it does not exist.
|
|
124
131
|
* Raw: The ${meta.userFriendlyName} only contains linked Ids, not the linked items themselves.
|
|
125
132
|
*/
|
|
126
|
-
public async get(id: ${model.brandedIdType}): Promise<${meta.types.typeName} | null> {
|
|
127
|
-
|
|
133
|
+
public async get({ id, user }: { id: ${model.brandedIdType}, user: User }): Promise<${meta.types.typeName} | null> {
|
|
134
|
+
const item = await this.data.get(id)
|
|
135
|
+
if (!item) {
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// NOTE: Authorizing user to access the item.
|
|
140
|
+
await this.authorize({ item, user })
|
|
141
|
+
|
|
142
|
+
return item
|
|
128
143
|
}
|
|
129
144
|
|
|
130
145
|
/**
|
|
131
146
|
* Returns a map of all ${meta.userFriendlyNamePlural}.
|
|
132
147
|
*/
|
|
133
|
-
public async getAll(): Promise<Map<${meta.types.brandedIdType}, ${model.typeName}>> {
|
|
134
|
-
|
|
148
|
+
public async getAll(user: User): Promise<Map<${meta.types.brandedIdType}, ${model.typeName}>> {
|
|
149
|
+
const result = await this.data.getAll()
|
|
150
|
+
|
|
151
|
+
// NOTE: Removing non-authorized items from the result.
|
|
152
|
+
for (const [key, value] of result.entries()) {
|
|
153
|
+
try {
|
|
154
|
+
await this.authorize({ item: value, user })
|
|
155
|
+
} catch (e) {
|
|
156
|
+
result.delete(key)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return result
|
|
135
161
|
}
|
|
136
162
|
|
|
137
163
|
/**
|
|
@@ -141,24 +167,51 @@ export class ${meta.view.serviceClassName} {
|
|
|
141
167
|
filter,
|
|
142
168
|
sort,
|
|
143
169
|
cursor,
|
|
170
|
+
user,
|
|
144
171
|
}: {
|
|
145
172
|
filter?: { field: keyof ${model.typeName}; operator: FilterOperator; value: string | number }
|
|
146
173
|
sort?: { field: keyof ${model.typeName}; ascending: boolean }
|
|
147
174
|
cursor?: Cursor
|
|
148
|
-
|
|
149
|
-
|
|
175
|
+
user: User,
|
|
176
|
+
},
|
|
177
|
+
) {
|
|
178
|
+
const items = await this.data.getAll()
|
|
179
|
+
|
|
180
|
+
// NOTE: Removing non-authorized items from the result.
|
|
181
|
+
for (const [key, value] of items.entries()) {
|
|
182
|
+
try {
|
|
183
|
+
await this.authorize({ item: value, user })
|
|
184
|
+
} catch (e) {
|
|
185
|
+
items.delete(key)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const authorized = [...items.values()]
|
|
189
|
+
|
|
150
190
|
const filtered = !filter
|
|
151
|
-
?
|
|
152
|
-
:
|
|
191
|
+
? authorized
|
|
192
|
+
: authorized.filter((item) => filterFn(item, filter.field, filter.operator, filter.value))
|
|
153
193
|
|
|
154
194
|
const filteredAndSorted = !sort
|
|
155
195
|
? filtered
|
|
156
196
|
: filtered.sort((a, b) => (sort.ascending ? compare(a, b, sort.field) : -compare(a, b, sort.field)))
|
|
157
197
|
|
|
158
198
|
const paginated = !cursor ? filteredAndSorted : filteredAndSorted.slice(cursor.startRow, cursor.endRow)
|
|
159
|
-
return { rows: paginated, count:
|
|
199
|
+
return { rows: paginated, count: authorized.length }
|
|
160
200
|
}
|
|
161
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Authorizes access to ${meta.userFriendlyName}.
|
|
204
|
+
*/
|
|
205
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
206
|
+
public async authorize({ item, user}: { item: ${model.typeName}, user: User }): Promise<void> {
|
|
207
|
+
// eslint-disable-next-line no-constant-condition
|
|
208
|
+
if (false) {
|
|
209
|
+
throw new TRPCError({
|
|
210
|
+
code: 'UNAUTHORIZED',
|
|
211
|
+
message: \`You are not authorized to access ${meta.userFriendlyName} with id \${item.id}\`,
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
}
|
|
162
215
|
}
|
|
163
216
|
|
|
164
217
|
// Utility Functions
|
|
@@ -18,6 +18,10 @@ export declare const toPascalCase: (str: string) => string;
|
|
|
18
18
|
* Returns a pluralized version of the given string based on the count.
|
|
19
19
|
*/
|
|
20
20
|
export declare const pluralize: (s: string, count?: number) => string;
|
|
21
|
+
/**
|
|
22
|
+
* Returns true if the given string is already pluralized.
|
|
23
|
+
*/
|
|
24
|
+
export declare const isPlural: (s: string) => boolean;
|
|
21
25
|
/**
|
|
22
26
|
* Converts each line of a string to a commented line
|
|
23
27
|
*/
|
package/dist/lib/utils/string.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.conjugateNames = exports.commentLines = exports.pluralize = exports.toPascalCase = exports.toCamelCase = exports.capitalize = exports.uncapitalize = void 0;
|
|
3
|
+
exports.conjugateNames = exports.commentLines = exports.isPlural = exports.pluralize = exports.toPascalCase = exports.toCamelCase = exports.capitalize = exports.uncapitalize = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Returns the same string with a lowercase first letter.
|
|
6
6
|
*/
|
|
@@ -88,6 +88,9 @@ const pluralize = (s, count = 2) => {
|
|
|
88
88
|
return s.slice(0, -singular.length + 1) + plural.slice(1);
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
+
if (lower.endsWith('ss')) {
|
|
92
|
+
return s.slice(0, -2) + 'sses';
|
|
93
|
+
}
|
|
91
94
|
if (s.endsWith('y') &&
|
|
92
95
|
!s.endsWith('ay') &&
|
|
93
96
|
!s.endsWith('ey') &&
|
|
@@ -102,6 +105,14 @@ const pluralize = (s, count = 2) => {
|
|
|
102
105
|
return s + 's';
|
|
103
106
|
};
|
|
104
107
|
exports.pluralize = pluralize;
|
|
108
|
+
/**
|
|
109
|
+
* Returns true if the given string is already pluralized.
|
|
110
|
+
*/
|
|
111
|
+
const isPlural = (s) => {
|
|
112
|
+
const plural = (0, exports.pluralize)(s);
|
|
113
|
+
return plural === s;
|
|
114
|
+
};
|
|
115
|
+
exports.isPlural = isPlural;
|
|
105
116
|
/**
|
|
106
117
|
* Converts each line of a string to a commented line
|
|
107
118
|
*/
|
package/dist/prisma/parse.js
CHANGED
|
@@ -34,6 +34,7 @@ const attributes_1 = require("./attributes");
|
|
|
34
34
|
*/
|
|
35
35
|
function parsePrismaSchema({ datamodel: { enums: enumsRaw, models: modelsRaw }, config, }) {
|
|
36
36
|
ensurePXLSystemModelsExist(modelsRaw);
|
|
37
|
+
ensureConsistency({ models: modelsRaw, enums: enumsRaw });
|
|
37
38
|
// NOTE: We preprocess models and enums so that we can populate relationships.
|
|
38
39
|
const models = modelsRaw.map((dmmfModel) => parseModelCore({ dmmfModel, config }));
|
|
39
40
|
const enums = enumsRaw.map((dmmfEnum) => parseEnum({ dmmfEnum, config }));
|
|
@@ -72,6 +73,56 @@ function ensurePXLSystemModelsExist(models) {
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Validates:
|
|
78
|
+
* - That there are no duplicate model names
|
|
79
|
+
* - That model names are singular
|
|
80
|
+
* - That model attributes are valid
|
|
81
|
+
* - That field attributes are valid
|
|
82
|
+
* - That enum attributes are valid
|
|
83
|
+
*/
|
|
84
|
+
function ensureConsistency({ models, enums }) {
|
|
85
|
+
const errors = [];
|
|
86
|
+
const modelNames = models.map((m) => m.name);
|
|
87
|
+
const duplicateModelName = modelNames.find((name, i) => modelNames.indexOf(name) !== i);
|
|
88
|
+
if (duplicateModelName) {
|
|
89
|
+
errors.push(`Model ${duplicateModelName} is defined more than once.`);
|
|
90
|
+
}
|
|
91
|
+
for (const model of models) {
|
|
92
|
+
if ((0, string_1.isPlural)(model.name)) {
|
|
93
|
+
errors.push(`Model ${(0, logger_1.highlight)(model.name)} is plural. Please use singular names for models.`);
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
(0, attributes_1.getModelAttributes)(model);
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
errors.push(`Model ${(0, logger_1.highlight)(model.name)} has invalid model attributes: ${(0, error_1.extractError)(e)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
for (const model of models) {
|
|
103
|
+
for (const field of model.fields) {
|
|
104
|
+
try {
|
|
105
|
+
(0, attributes_1.getFieldAttributes)(field);
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
errors.push(`Model ${(0, logger_1.highlight)(model.name)} has invalid attributes for field ${(0, logger_1.highlight)(field.name)}:
|
|
109
|
+
${(0, error_1.extractError)(e)}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
for (const enumDef of enums) {
|
|
114
|
+
try {
|
|
115
|
+
(0, attributes_1.getEnumAttributes)(enumDef);
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
errors.push(`Enum ${(0, logger_1.highlight)(enumDef.name)} has invalid attributes:
|
|
119
|
+
${(0, error_1.extractError)(e)}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (errors.length > 0) {
|
|
123
|
+
(0, error_1.throwError)(`${errors.length} ${(0, string_1.pluralize)('issue', errors.length)} detected in schema:\n * ${errors.join('\n * ')}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
75
126
|
function isModelNotIgnored(model) {
|
|
76
127
|
return model !== undefined && !model.attributes.ignore;
|
|
77
128
|
}
|
|
@@ -79,6 +130,9 @@ function isModelNotIgnored(model) {
|
|
|
79
130
|
* Parses the core properties of a model without fields.
|
|
80
131
|
*/
|
|
81
132
|
function parseModelCore({ dmmfModel, config, }) {
|
|
133
|
+
if ((0, string_1.isPlural)(dmmfModel.name)) {
|
|
134
|
+
(0, error_1.throwError)(`Model ${dmmfModel.name} is plural. Please use singular names for models.`);
|
|
135
|
+
}
|
|
82
136
|
const attributes = (0, attributes_1.getModelAttributes)(dmmfModel);
|
|
83
137
|
return {
|
|
84
138
|
name: Types.toModelName((0, string_1.toPascalCase)(dmmfModel.name)),
|