@postxl/generator 0.0.1
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/README.md +3 -0
- package/dist/jest.config.d.ts +3 -0
- package/dist/src/generator.d.ts +12 -0
- package/dist/src/generator.js +164 -0
- package/dist/src/generators/enums/react.generator.d.ts +10 -0
- package/dist/src/generators/enums/react.generator.js +81 -0
- package/dist/src/generators/enums/types.generator.d.ts +10 -0
- package/dist/src/generators/enums/types.generator.js +18 -0
- package/dist/src/generators/indices/datamockmodule.generator.d.ts +9 -0
- package/dist/src/generators/indices/datamockmodule.generator.js +104 -0
- package/dist/src/generators/indices/datamodule.generator.d.ts +9 -0
- package/dist/src/generators/indices/datamodule.generator.js +128 -0
- package/dist/src/generators/indices/dataservice.generator.d.ts +9 -0
- package/dist/src/generators/indices/dataservice.generator.js +47 -0
- package/dist/src/generators/indices/repositories.generator.d.ts +9 -0
- package/dist/src/generators/indices/repositories.generator.js +17 -0
- package/dist/src/generators/indices/seed.generator.d.ts +9 -0
- package/dist/src/generators/indices/seed.generator.js +17 -0
- package/dist/src/generators/indices/stubs.generator.d.ts +9 -0
- package/dist/src/generators/indices/stubs.generator.js +17 -0
- package/dist/src/generators/indices/testdataservice.generator.d.ts +7 -0
- package/dist/src/generators/indices/testdataservice.generator.js +61 -0
- package/dist/src/generators/indices/types.generator.d.ts +10 -0
- package/dist/src/generators/indices/types.generator.js +21 -0
- package/dist/src/generators/models/react.generator/context.generator.d.ts +9 -0
- package/dist/src/generators/models/react.generator/context.generator.js +66 -0
- package/dist/src/generators/models/react.generator/index.d.ts +10 -0
- package/dist/src/generators/models/react.generator/index.js +32 -0
- package/dist/src/generators/models/react.generator/library.generator.d.ts +9 -0
- package/dist/src/generators/models/react.generator/library.generator.js +113 -0
- package/dist/src/generators/models/react.generator/lookup.generator.d.ts +9 -0
- package/dist/src/generators/models/react.generator/lookup.generator.js +97 -0
- package/dist/src/generators/models/react.generator/modals.generator.d.ts +23 -0
- package/dist/src/generators/models/react.generator/modals.generator.js +521 -0
- package/dist/src/generators/models/repository.generator.d.ts +9 -0
- package/dist/src/generators/models/repository.generator.js +282 -0
- package/dist/src/generators/models/route.generator.d.ts +16 -0
- package/dist/src/generators/models/route.generator.js +112 -0
- package/dist/src/generators/models/seed.generator.d.ts +20 -0
- package/dist/src/generators/models/seed.generator.js +185 -0
- package/dist/src/generators/models/stub.generator.d.ts +9 -0
- package/dist/src/generators/models/stub.generator.js +74 -0
- package/dist/src/generators/models/types.generator.d.ts +9 -0
- package/dist/src/generators/models/types.generator.js +116 -0
- package/dist/src/lib/attributes.d.ts +43 -0
- package/dist/src/lib/attributes.js +2 -0
- package/dist/src/lib/exports.d.ts +26 -0
- package/dist/src/lib/exports.js +38 -0
- package/dist/src/lib/imports.d.ts +35 -0
- package/dist/src/lib/imports.js +55 -0
- package/dist/src/lib/meta.d.ts +359 -0
- package/dist/src/lib/meta.js +195 -0
- package/dist/src/lib/schema/fields.d.ts +35 -0
- package/dist/src/lib/schema/fields.js +49 -0
- package/dist/src/lib/schema/schema.d.ts +275 -0
- package/dist/src/lib/schema/schema.js +2 -0
- package/dist/src/lib/schema/types.d.ts +72 -0
- package/dist/src/lib/schema/types.js +41 -0
- package/dist/src/lib/schema/zod.d.ts +8 -0
- package/dist/src/lib/schema/zod.js +44 -0
- package/dist/src/lib/serializer.d.ts +15 -0
- package/dist/src/lib/serializer.js +24 -0
- package/dist/src/lib/utils/error.d.ts +5 -0
- package/dist/src/lib/utils/error.js +13 -0
- package/dist/src/lib/utils/file.d.ts +10 -0
- package/dist/src/lib/utils/file.js +54 -0
- package/dist/src/lib/utils/logger.d.ts +11 -0
- package/dist/src/lib/utils/logger.js +2 -0
- package/dist/src/lib/utils/string.d.ts +29 -0
- package/dist/src/lib/utils/string.js +75 -0
- package/dist/src/lib/utils/types.d.ts +12 -0
- package/dist/src/lib/utils/types.js +2 -0
- package/dist/src/lib/vfs.d.ts +137 -0
- package/dist/src/lib/vfs.js +419 -0
- package/dist/src/prisma/attributes.d.ts +17 -0
- package/dist/src/prisma/attributes.js +80 -0
- package/dist/src/prisma/client-path.d.ts +7 -0
- package/dist/src/prisma/client-path.js +29 -0
- package/dist/src/prisma/parse.d.ts +12 -0
- package/dist/src/prisma/parse.js +276 -0
- package/dist/tests/attributes.test.d.ts +1 -0
- package/dist/tests/attributes.test.js +76 -0
- package/dist/tests/file.test.d.ts +1 -0
- package/dist/tests/file.test.js +26 -0
- package/dist/tests/utils/random.d.ts +3 -0
- package/dist/tests/utils/random.js +15 -0
- package/dist/tests/vfs.test.d.ts +1 -0
- package/dist/tests/vfs.test.js +74 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/jest.config.ts +18 -0
- package/package.json +42 -0
- package/tests/attributes.test.ts +91 -0
- package/tests/file.test.ts +32 -0
- package/tests/utils/random.ts +11 -0
- package/tests/vfs.test.ts +92 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateRepository = void 0;
|
|
4
|
+
const fields_1 = require("../../lib/schema/fields");
|
|
5
|
+
const string_1 = require("../../lib/utils/string");
|
|
6
|
+
const imports_1 = require("../../lib/imports");
|
|
7
|
+
/**
|
|
8
|
+
* Generates repository data structure for a given model.
|
|
9
|
+
*/
|
|
10
|
+
function generateRepository({ model, meta }) {
|
|
11
|
+
var _a;
|
|
12
|
+
const { idField, fields } = model;
|
|
13
|
+
const decoder = meta.data.repository.decoderFnName;
|
|
14
|
+
const uniqueStringFields = fields.filter(fields_1.isUniqueStringField);
|
|
15
|
+
const defaultValueInitFn = `
|
|
16
|
+
if (item.${(_a = model.defaultField) === null || _a === void 0 ? void 0 : _a.name}) {
|
|
17
|
+
if (this.defaultValue) {
|
|
18
|
+
console.warn(\`More than one default ${meta.userFriendlyName} found! \${this.defaultValue.id} and \${item.id}\`)
|
|
19
|
+
}
|
|
20
|
+
this.defaultValue = item
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
const defaultValueInitCheckFn = `
|
|
24
|
+
if (!this.db.isCLI && !this.defaultValue) {
|
|
25
|
+
throw new Error('No default ${meta.userFriendlyName} found!')
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
const { isGenerated } = idField;
|
|
29
|
+
const idIntInitFn = `this.currentMaxId = (await this.db.${meta.data.repository.getMethodFnName}.aggregate({ _max: { ${idField.sourceName}: true } }))._max.${idField.sourceName} ?? 0`;
|
|
30
|
+
const imports = imports_1.ImportsGenerator.from(meta.data.repoFilePath)
|
|
31
|
+
.addImport({
|
|
32
|
+
items: [model.typeName, model.brandedIdType, meta.types.toBrandedIdTypeFnName],
|
|
33
|
+
from: meta.types.importPath,
|
|
34
|
+
})
|
|
35
|
+
.addImport({ items: [meta.types.zodDecoderFnName], from: meta.types.importPath });
|
|
36
|
+
return `
|
|
37
|
+
import { Injectable, Logger } from '@nestjs/common'
|
|
38
|
+
import { DbService } from '@${model.schemaConfig.project}/db'
|
|
39
|
+
|
|
40
|
+
import { Repository } from '../repository.type'
|
|
41
|
+
|
|
42
|
+
${imports.generate()}
|
|
43
|
+
|
|
44
|
+
@Injectable()
|
|
45
|
+
export class ${meta.data.repositoryClassName} implements Repository<
|
|
46
|
+
${model.typeName},
|
|
47
|
+
${idField.unbrandedTypeName},
|
|
48
|
+
Omit<${model.typeName}, 'id'>
|
|
49
|
+
> {
|
|
50
|
+
protected data: Map<${model.brandedIdType}, ${model.typeName}> = new Map()
|
|
51
|
+
protected logger = new Logger(${meta.data.repositoryClassName}.name)
|
|
52
|
+
|
|
53
|
+
${model.defaultField ? `public defaultValue!: ${model.typeName}` : ''}
|
|
54
|
+
|
|
55
|
+
${isGenerated ? `protected currentMaxId = 0\n` : ''}
|
|
56
|
+
|
|
57
|
+
protected uniqueIds = {
|
|
58
|
+
${uniqueStringFields.map((f) => `'${f.name}': new Map<string, ${model.typeName}>()`).join(',\n')}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
constructor(protected db: DbService) {}
|
|
62
|
+
|
|
63
|
+
public async init() {
|
|
64
|
+
this.data.clear()
|
|
65
|
+
|
|
66
|
+
this.logger.log(\`Loading ${model.typeName} instances into repository...\`)
|
|
67
|
+
|
|
68
|
+
const data = await this.db.${meta.data.repository.getMethodFnName}.findMany({})
|
|
69
|
+
|
|
70
|
+
for (const rawItem of data) {
|
|
71
|
+
const item = this.${decoder}(rawItem)
|
|
72
|
+
this.data.set(item.id, item)
|
|
73
|
+
|
|
74
|
+
${model.defaultField ? defaultValueInitFn : ''}
|
|
75
|
+
${uniqueStringFields.map((f) => `this.uniqueIds.${f}.set(item.${f}, item)`).join('\n')}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
${isGenerated ? idIntInitFn : ''}
|
|
79
|
+
|
|
80
|
+
${model.defaultField ? defaultValueInitCheckFn : ''}
|
|
81
|
+
|
|
82
|
+
this.logger.log(\`Loaded \${this.data.size} instances of ${model.typeName} into repository!\`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public async reInit(data: ${model.typeName}[]): Promise<void> {
|
|
86
|
+
if (!this.db.useE2ETestDB) {
|
|
87
|
+
const errorMsg =
|
|
88
|
+
'ReInit() shall only be called in tests using MockRepositories or in DB configured for E2E tests!'
|
|
89
|
+
this.logger.error(errorMsg)
|
|
90
|
+
throw new Error(errorMsg)
|
|
91
|
+
}
|
|
92
|
+
await this.db.runOnlyOnTestDb(() => this.db.${meta.data.repository.getMethodFnName}.createMany({ data: data.map((i) => this.toCreateItem(i)) }))
|
|
93
|
+
return this.init()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public get(id: ${model.brandedIdType}| ${idField.unbrandedTypeName} | null): ${model.typeName} | null {
|
|
97
|
+
if (id === null) return null
|
|
98
|
+
return this.data.get(${meta.types.toBrandedIdTypeFnName}(id)) ?? null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public getAll(): Map<${model.brandedIdType}, ${model.typeName}> {
|
|
102
|
+
return this.data
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public getAllAsArray(): ${model.typeName}[] {
|
|
106
|
+
return Array.from(this.data.values())
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public filter(predicate: (item: ${model.typeName}) => boolean): ${model.typeName}[] {
|
|
110
|
+
return this.getAllAsArray().filter(predicate)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public findFirst(predicate: (item: ${model.typeName}) => boolean): ${model.typeName} | null {
|
|
114
|
+
return this.getAllAsArray().find(predicate) ?? null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public count(): number {
|
|
118
|
+
return this.data.size
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private toCreateItem(item: ${model.typeName}) {
|
|
122
|
+
${isGenerated
|
|
123
|
+
? `if (item.${idField.name} > ++this.currentMaxId) {
|
|
124
|
+
this.currentMaxId = item.${idField.name}
|
|
125
|
+
} else {
|
|
126
|
+
item.${idField.name} = this.currentMaxId as ${model.brandedIdType}
|
|
127
|
+
}`
|
|
128
|
+
: ''}
|
|
129
|
+
|
|
130
|
+
${uniqueStringFields.map((f) => `this.${getEnsureUniqueFnName(f)}(item)`).join('\n')}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
${[...model.fields.values()].map((f) => `${f.sourceName}: item.${f.name}`).join(',\n')}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
public async createWithId(item: Omit<${model.typeName}, '${idField.name}'> & {
|
|
137
|
+
id: ${model.brandedIdType} | ${idField.unbrandedTypeName} | undefined
|
|
138
|
+
}): Promise<${model.typeName}> {
|
|
139
|
+
if (item.${idField.name} === undefined) {
|
|
140
|
+
return this.create(item)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const newItem = this.${decoder}(
|
|
144
|
+
await this.db.${meta.data.repository.getMethodFnName}.create({
|
|
145
|
+
data: this.toCreateItem(item as ${model.typeName}),
|
|
146
|
+
}),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
${uniqueStringFields.map((f) => `this.uniqueIds.${f.name}.set(newItem.${f.name}, newItem)`).join('\n')}
|
|
150
|
+
|
|
151
|
+
this.data.set(newItem.id, newItem)
|
|
152
|
+
return newItem
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public async create(
|
|
156
|
+
item: Omit<${model.typeName}, 'id'>
|
|
157
|
+
): Promise<${model.typeName}> {
|
|
158
|
+
${isGenerated ? `const id = ++this.currentMaxId\n` : ''}
|
|
159
|
+
|
|
160
|
+
${uniqueStringFields.map((f) => `this.${getEnsureUniqueFnName(f)}(item)`).join('\n')}
|
|
161
|
+
|
|
162
|
+
const newItem = this.${decoder}(
|
|
163
|
+
await this.db.${meta.data.repository.getMethodFnName}.create({
|
|
164
|
+
data: {
|
|
165
|
+
${isGenerated ? `${idField.sourceName}: id,` : ''}
|
|
166
|
+
${[...model.fields.values()]
|
|
167
|
+
.filter((f) => f.kind !== 'id')
|
|
168
|
+
.map((f) => `${f.sourceName}: item.${f.name}`)
|
|
169
|
+
.join(',\n')}
|
|
170
|
+
},
|
|
171
|
+
}),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
${uniqueStringFields.map((f) => `this.uniqueIds.${f.name}.set(newItem.${f.name}, newItem)`).join('\n')}
|
|
175
|
+
|
|
176
|
+
this.data.set(newItem.id, newItem)
|
|
177
|
+
return newItem
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public async update(item: Partial<${model.typeName}> & {
|
|
181
|
+
id: ${model.brandedIdType} | ${idField.unbrandedTypeName}
|
|
182
|
+
}): Promise<${model.typeName}> {
|
|
183
|
+
const id = ${meta.types.toBrandedIdTypeFnName}(item.id)
|
|
184
|
+
const existingItem = this.get(id)
|
|
185
|
+
|
|
186
|
+
if (!existingItem) {
|
|
187
|
+
throw new Error(\`Could not update ${meta.userFriendlyName} with id \${id}. Not found!\`)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
${uniqueStringFields
|
|
191
|
+
.map((f) => {
|
|
192
|
+
return `
|
|
193
|
+
if (item.${f.name} !== undefined && existingItem.${f} !== item.${f}) {
|
|
194
|
+
this.${getEnsureUniqueFnName(f)}(item)
|
|
195
|
+
}
|
|
196
|
+
`;
|
|
197
|
+
})
|
|
198
|
+
.join('\n')}
|
|
199
|
+
|
|
200
|
+
const newItem = this.${decoder}(
|
|
201
|
+
await this.db.${meta.data.repository.getMethodFnName}.update({
|
|
202
|
+
where: {
|
|
203
|
+
${idField.sourceName}: item.${idField.name},
|
|
204
|
+
},
|
|
205
|
+
data: {
|
|
206
|
+
${[...model.fields.values()]
|
|
207
|
+
.filter((f) => f.kind !== 'id')
|
|
208
|
+
.map((f) => f.isRequired
|
|
209
|
+
? `${f.sourceName}: item.${f.name} ?? existingItem.${f.name}`
|
|
210
|
+
: `${f.sourceName}: item.${f.name}`)
|
|
211
|
+
.join(',\n')}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
},
|
|
215
|
+
}),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
${uniqueStringFields
|
|
219
|
+
.map((f) => {
|
|
220
|
+
return `
|
|
221
|
+
if (item.${f.name} !== undefined && existingItem.${f.name} !== item.${f.name}) {
|
|
222
|
+
this.uniqueIds.${f.name}.delete(existingItem.${f.name})
|
|
223
|
+
if (item.${f.name} !== null) {
|
|
224
|
+
this.uniqueIds.${f.name}.set(newItem.${f.name}, newItem)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
`;
|
|
228
|
+
})
|
|
229
|
+
.join('\n')}
|
|
230
|
+
|
|
231
|
+
this.data.set(newItem.id, newItem)
|
|
232
|
+
return newItem
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
public async delete(id: ${model.brandedIdType} | ${idField.unbrandedTypeName}): Promise<void> {
|
|
236
|
+
const existingItem = this.get(id)
|
|
237
|
+
if (!existingItem) {
|
|
238
|
+
throw new Error(\`Could not delete ${model.typeName} with id \${id}. Not found!\`)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
await this.db.${meta.data.repository.getMethodFnName}.delete({ where: { ${idField.sourceName}:id } })
|
|
242
|
+
|
|
243
|
+
${uniqueStringFields.map((f) => `this.uniqueIds.${f.name}.delete(existingItem.${f.name})`).join('\n')}
|
|
244
|
+
|
|
245
|
+
this.data.delete(${meta.types.toBrandedIdTypeFnName}(id))
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
${uniqueStringFields
|
|
249
|
+
.map((f) => {
|
|
250
|
+
const fName = f.name;
|
|
251
|
+
return `
|
|
252
|
+
private ${getEnsureUniqueFnName(f)}(item: { ${fName}?: string }) {
|
|
253
|
+
if (!item.${fName}) return
|
|
254
|
+
if (!this.uniqueIds.${fName}.has(item.${fName})) return
|
|
255
|
+
let counter = 1
|
|
256
|
+
let ${fName}: string
|
|
257
|
+
|
|
258
|
+
//TODO: Add attribute for max length of string
|
|
259
|
+
do {
|
|
260
|
+
${fName} = \`\${item.${fName}} (\${++counter})\`
|
|
261
|
+
} while (this.uniqueIds.${fName}.has(${fName}))
|
|
262
|
+
|
|
263
|
+
this.logger.log(\`Asset ${fName} \${item.${fName}} already exists. Renaming to \${${fName}})\`)
|
|
264
|
+
item.${fName} = ${fName}
|
|
265
|
+
}
|
|
266
|
+
`;
|
|
267
|
+
})
|
|
268
|
+
.join('\n\n')}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Utility function that converts a given Database object to a TypeScript model instance
|
|
272
|
+
*/
|
|
273
|
+
private ${decoder}(item: unknown): ${model.typeName} {
|
|
274
|
+
return ${meta.types.zodDecoderFnName}.parse(item)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
`;
|
|
278
|
+
}
|
|
279
|
+
exports.generateRepository = generateRepository;
|
|
280
|
+
function getEnsureUniqueFnName(field) {
|
|
281
|
+
return `ensureUnique${(0, string_1.toPascalCase)(field.name)}`;
|
|
282
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ModelMetaData, SchemaMetaData } from '../../lib/meta';
|
|
2
|
+
import { Model } from '../../lib/schema/schema';
|
|
3
|
+
/**
|
|
4
|
+
* Generates TRPC route for a given model.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateRoute({ model, meta }: {
|
|
7
|
+
model: Model;
|
|
8
|
+
meta: ModelMetaData;
|
|
9
|
+
}): string;
|
|
10
|
+
/**
|
|
11
|
+
* Generates the index file for all the routes.
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateRoutesIndex({ models, meta }: {
|
|
14
|
+
models: Model[];
|
|
15
|
+
meta: SchemaMetaData;
|
|
16
|
+
}): string;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateRoutesIndex = exports.generateRoute = void 0;
|
|
4
|
+
const meta_1 = require("../../lib/meta");
|
|
5
|
+
const fields_1 = require("../../lib/schema/fields");
|
|
6
|
+
const zod_1 = require("../../lib/schema/zod");
|
|
7
|
+
const imports_1 = require("../../lib/imports");
|
|
8
|
+
/**
|
|
9
|
+
* Generates TRPC route for a given model.
|
|
10
|
+
*/
|
|
11
|
+
function generateRoute({ model, meta }) {
|
|
12
|
+
const { idField, defaultField } = model;
|
|
13
|
+
const defaultValueMethod = `
|
|
14
|
+
getDefault: procedure.query(({ ctx }) => ctx.dataService.${meta.data.dataServiceName}.defaultValue),
|
|
15
|
+
`;
|
|
16
|
+
const createMethod = getCreateMethod({ model, meta });
|
|
17
|
+
const updateMethod = getUpdateMethod({ model, meta });
|
|
18
|
+
const deleteMethod = getDeleteMethod({ idField, meta });
|
|
19
|
+
const imports = imports_1.ImportsGenerator.from(meta.trpc.routerFilePath).addImport({
|
|
20
|
+
items: [meta.types.toBrandedIdTypeFnName],
|
|
21
|
+
from: meta.types.importPath,
|
|
22
|
+
});
|
|
23
|
+
for (const relation of (0, fields_1.getRelationFields)(model)) {
|
|
24
|
+
const depMeta = (0, meta_1.getModelMetadata)({ model: relation.relationToModel });
|
|
25
|
+
imports.addImport({
|
|
26
|
+
items: [depMeta.types.toBrandedIdTypeFnName],
|
|
27
|
+
from: depMeta.types.importPath,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return `
|
|
31
|
+
import { z } from 'zod'
|
|
32
|
+
import { procedure, router, undefinedToNull } from '../helper'
|
|
33
|
+
|
|
34
|
+
${imports.generate()}
|
|
35
|
+
|
|
36
|
+
export const ${meta.trpc.routerName} = router({
|
|
37
|
+
${defaultField ? defaultValueMethod : ''}
|
|
38
|
+
|
|
39
|
+
get: procedure
|
|
40
|
+
.input(z.${idField.unbrandedTypeName}().transform(${meta.types.toBrandedIdTypeFnName}))
|
|
41
|
+
.query(async ({ input, ctx }) => await ctx.dataService.${meta.data.dataServiceName}.get(input)),
|
|
42
|
+
getMap: procedure.query(({ ctx }) => ctx.dataService.${meta.data.dataServiceName}.getAll()),
|
|
43
|
+
|
|
44
|
+
${omit(createMethod, model.attributes.skipCreate)}
|
|
45
|
+
${omit(updateMethod, model.attributes.skipUpdate)}
|
|
46
|
+
${omit(deleteMethod, model.attributes.skipDelete)}
|
|
47
|
+
})
|
|
48
|
+
`;
|
|
49
|
+
}
|
|
50
|
+
exports.generateRoute = generateRoute;
|
|
51
|
+
function omit(lines, omitted) {
|
|
52
|
+
if (!omitted) {
|
|
53
|
+
return lines;
|
|
54
|
+
}
|
|
55
|
+
return lines
|
|
56
|
+
.split('\n')
|
|
57
|
+
.map((l) => `// ${l}`)
|
|
58
|
+
.join('\n');
|
|
59
|
+
}
|
|
60
|
+
function getCreateMethod({ model: { fields }, meta }) {
|
|
61
|
+
const parameters = fields
|
|
62
|
+
.filter((f) => f.kind !== 'id')
|
|
63
|
+
.map((field) => `${field.name}: z.${(0, zod_1.getZodDecoderDefinition)({ field })}`)
|
|
64
|
+
.join(',');
|
|
65
|
+
return `
|
|
66
|
+
create: procedure.input(z.object({
|
|
67
|
+
${parameters}
|
|
68
|
+
}))
|
|
69
|
+
.mutation(({ input, ctx }) => ctx.dataService.${meta.data.dataServiceName}.create(input)),
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
function getUpdateMethod({ model: { fields }, meta }) {
|
|
73
|
+
const parameters = fields
|
|
74
|
+
.map((field) => `${field.name}: z.${(0, zod_1.getZodDecoderDefinition)({ field, allowAnyOptionalField: field.kind !== 'id' })}`)
|
|
75
|
+
.join(',');
|
|
76
|
+
return `update: procedure
|
|
77
|
+
.input(
|
|
78
|
+
z.object({
|
|
79
|
+
${parameters}
|
|
80
|
+
})
|
|
81
|
+
)
|
|
82
|
+
.mutation(({ input, ctx }) => ctx.dataService.${meta.data.dataServiceName}.update(input)),
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
function getDeleteMethod({ idField, meta }) {
|
|
86
|
+
return `
|
|
87
|
+
delete: procedure
|
|
88
|
+
.input(z.${idField.unbrandedTypeName}().transform(${meta.types.toBrandedIdTypeFnName}))
|
|
89
|
+
.mutation(({ input, ctx }) => ctx.dataService.${meta.data.dataServiceName}.delete(input)),
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Generates the index file for all the routes.
|
|
94
|
+
*/
|
|
95
|
+
function generateRoutesIndex({ models, meta }) {
|
|
96
|
+
const mm = models.map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }));
|
|
97
|
+
const imports = imports_1.ImportsGenerator.from(meta.trpc.routesFilePath);
|
|
98
|
+
for (const { meta } of mm) {
|
|
99
|
+
imports.addImport({ items: [meta.trpc.routerName], from: meta.trpc.routerFilePath });
|
|
100
|
+
}
|
|
101
|
+
return `
|
|
102
|
+
${imports.generate()}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Object with all generated routes.
|
|
106
|
+
*/
|
|
107
|
+
export const routes = {
|
|
108
|
+
${mm.map(({ meta }) => `${meta.trpc.routerName}`).join(',\n')}
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
}
|
|
112
|
+
exports.generateRoutesIndex = generateRoutesIndex;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Model, SchemaConfig } from '../../lib/schema/schema';
|
|
2
|
+
import { ModelMetaData } from '../../lib/meta';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a seed file for a given model.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateSeedModel({ model, itemCount, meta, }: {
|
|
7
|
+
model: Model;
|
|
8
|
+
itemCount: number;
|
|
9
|
+
meta: ModelMetaData;
|
|
10
|
+
}): string;
|
|
11
|
+
/**
|
|
12
|
+
* Creates the seed file that exposes all seed data as single object.
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateSeeds({ models, config, }: {
|
|
15
|
+
models: {
|
|
16
|
+
model: Model;
|
|
17
|
+
meta: ModelMetaData;
|
|
18
|
+
}[];
|
|
19
|
+
config: SchemaConfig;
|
|
20
|
+
}): string;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateSeeds = exports.generateSeedModel = void 0;
|
|
7
|
+
const faker_1 = require("@faker-js/faker");
|
|
8
|
+
const assert_never_1 = __importDefault(require("assert-never"));
|
|
9
|
+
const serializer_1 = require("../../lib/serializer");
|
|
10
|
+
const string_1 = require("../../lib/utils/string");
|
|
11
|
+
const meta_1 = require("../../lib/meta");
|
|
12
|
+
const fields_1 = require("../../lib/schema/fields");
|
|
13
|
+
const imports_1 = require("../../lib/imports");
|
|
14
|
+
/**
|
|
15
|
+
* Creates a seed file for a given model.
|
|
16
|
+
*/
|
|
17
|
+
function generateSeedModel({ model, itemCount, meta, }) {
|
|
18
|
+
const imports = imports_1.ImportsGenerator.from(meta.seed.filePath).addImport({
|
|
19
|
+
items: [model.typeName, meta.types.toBrandedIdTypeFnName],
|
|
20
|
+
from: meta.types.importPath,
|
|
21
|
+
});
|
|
22
|
+
for (const relation of (0, fields_1.getRelationFields)(model)) {
|
|
23
|
+
const depMeta = (0, meta_1.getModelMetadata)({ model: relation.relationToModel });
|
|
24
|
+
imports.addImport({
|
|
25
|
+
items: [depMeta.types.toBrandedIdTypeFnName],
|
|
26
|
+
from: depMeta.types.importPath,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const exampleMode = getExampleMode({ model, maxItemCount: itemCount });
|
|
30
|
+
const seed = [];
|
|
31
|
+
for (let i = 1; i <= exampleMode.itemCount; i++) {
|
|
32
|
+
seed.push(generateSeedData({ model, index: i, exampleMode }));
|
|
33
|
+
}
|
|
34
|
+
return `
|
|
35
|
+
${imports.generate()}
|
|
36
|
+
|
|
37
|
+
export const ${meta.seed.constantName}: ${model.typeName}[] = [
|
|
38
|
+
${seed.join(',\n')}
|
|
39
|
+
]
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
exports.generateSeedModel = generateSeedModel;
|
|
43
|
+
/**
|
|
44
|
+
* In case of a model with examples, there are two options:
|
|
45
|
+
* 1. the number of examples per field is the same for all fields with examples -> we generate them as tuples
|
|
46
|
+
* 2. the number of examples per field is different -> we generate them as random permutations
|
|
47
|
+
* Here we determine which option to use - and the number of tuples/permutations available
|
|
48
|
+
*/
|
|
49
|
+
function getExampleMode({ model, maxItemCount }) {
|
|
50
|
+
let numberOfExamplesPerField = undefined;
|
|
51
|
+
let sameNumberOfExamplesPerField = true;
|
|
52
|
+
let examplePermutations = 0;
|
|
53
|
+
for (const field of model.fields) {
|
|
54
|
+
if (field.kind === 'scalar' && field.attributes.examples && field.attributes.examples.length > 0) {
|
|
55
|
+
if (numberOfExamplesPerField === undefined) {
|
|
56
|
+
numberOfExamplesPerField = field.attributes.examples.length;
|
|
57
|
+
examplePermutations = 1;
|
|
58
|
+
}
|
|
59
|
+
else if (numberOfExamplesPerField !== field.attributes.examples.length) {
|
|
60
|
+
sameNumberOfExamplesPerField = false;
|
|
61
|
+
}
|
|
62
|
+
examplePermutations *= field.attributes.examples.length;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (numberOfExamplesPerField === undefined) {
|
|
66
|
+
return { mode: 'NoExamples', itemCount: maxItemCount };
|
|
67
|
+
}
|
|
68
|
+
else if (sameNumberOfExamplesPerField) {
|
|
69
|
+
return {
|
|
70
|
+
mode: 'Tuples',
|
|
71
|
+
itemCount: numberOfExamplesPerField < maxItemCount ? numberOfExamplesPerField : maxItemCount,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
return { mode: 'Permutations', itemCount: examplePermutations < maxItemCount ? examplePermutations : maxItemCount };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function generateSeedData({ model, index, exampleMode, }) {
|
|
79
|
+
const data = new serializer_1.Serializer();
|
|
80
|
+
data.append('{');
|
|
81
|
+
for (const field of model.fields.values()) {
|
|
82
|
+
data.append(`${field.name}: ${generateFieldData({ field, model, index, exampleMode })},`);
|
|
83
|
+
}
|
|
84
|
+
data.append('}');
|
|
85
|
+
return data.print();
|
|
86
|
+
}
|
|
87
|
+
function generateFieldData({ field, model, index, exampleMode, }) {
|
|
88
|
+
switch (field.kind) {
|
|
89
|
+
case 'id':
|
|
90
|
+
return generateFieldDataId({ field, model, index });
|
|
91
|
+
case 'scalar':
|
|
92
|
+
return generateFieldDataScalar({ field, model, index, exampleMode });
|
|
93
|
+
case 'relation':
|
|
94
|
+
return generateFieldDataRelation({ field, model, index, itemCount: exampleMode.itemCount });
|
|
95
|
+
case 'enum':
|
|
96
|
+
return generateFieldDataEnum({ field });
|
|
97
|
+
default:
|
|
98
|
+
(0, assert_never_1.default)(field);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function generateFieldDataId({ field, model, index }) {
|
|
102
|
+
const idModelMeta = (0, meta_1.getModelMetadata)({ model: field.model });
|
|
103
|
+
return `${idModelMeta.types.toBrandedIdTypeFnName}(${field.unbrandedTypeName === 'string' ? `'${index}'` : index})`;
|
|
104
|
+
}
|
|
105
|
+
function generateFieldDataScalar({ field, model, index, exampleMode, }) {
|
|
106
|
+
const { hasExample, example } = getFieldExample({ field, model, index, exampleMode });
|
|
107
|
+
switch (field.typeName) {
|
|
108
|
+
case 'string':
|
|
109
|
+
return `'${hasExample ? example : generateFieldDataString({ field, model, index })}'`;
|
|
110
|
+
case 'number':
|
|
111
|
+
return hasExample ? example : generateFieldDataNumber({ field, model, index });
|
|
112
|
+
case 'boolean':
|
|
113
|
+
return hasExample ? example : generateFieldDataBoolean({ field, model, index });
|
|
114
|
+
case 'Date':
|
|
115
|
+
return hasExample ? example : generateFieldDataDate({ field, model, index });
|
|
116
|
+
default:
|
|
117
|
+
console.warn(`Unknown scalar type: ${field.typeName}`);
|
|
118
|
+
return '';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function getFieldExample({ field, model, index, exampleMode, }) {
|
|
122
|
+
if (exampleMode.mode === 'NoExamples')
|
|
123
|
+
return { hasExample: false, example: undefined };
|
|
124
|
+
if (!field.attributes.examples || field.attributes.examples.length === 0)
|
|
125
|
+
return { hasExample: false, example: undefined };
|
|
126
|
+
if (exampleMode.mode === 'Permutations') {
|
|
127
|
+
const example = faker_1.faker.helpers.arrayElement(field.attributes.examples);
|
|
128
|
+
return { hasExample: true, example };
|
|
129
|
+
}
|
|
130
|
+
else if (exampleMode.mode === 'Tuples') {
|
|
131
|
+
const example = field.attributes.examples[index % field.attributes.examples.length];
|
|
132
|
+
return { hasExample: true, example };
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
(0, assert_never_1.default)(exampleMode);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function generateFieldDataString({ field, model, index }) {
|
|
139
|
+
if (field.name === 'name')
|
|
140
|
+
return `${(0, string_1.toPascalCase)(model.name)} ${index}`;
|
|
141
|
+
if (field.name === 'email')
|
|
142
|
+
return faker_1.faker.internet.email();
|
|
143
|
+
return faker_1.faker.lorem.words(3);
|
|
144
|
+
}
|
|
145
|
+
function generateFieldDataNumber({}) {
|
|
146
|
+
return faker_1.faker.datatype.float({ precision: 0.1, min: 0, max: 1 }).toString();
|
|
147
|
+
}
|
|
148
|
+
function generateFieldDataBoolean({}) {
|
|
149
|
+
return faker_1.faker.datatype.boolean().toString();
|
|
150
|
+
}
|
|
151
|
+
function generateFieldDataDate({ field, model, index }) {
|
|
152
|
+
return `new Date('${faker_1.faker.date.past(3)}')`;
|
|
153
|
+
}
|
|
154
|
+
function generateFieldDataRelation({ field, model, index, itemCount, }) {
|
|
155
|
+
const referenceId = faker_1.faker.datatype.number({ min: 1, max: itemCount });
|
|
156
|
+
const refModelMeta = (0, meta_1.getModelMetadata)({ model: field.relationToModel });
|
|
157
|
+
const brandingFn = refModelMeta.types.toBrandedIdTypeFnName;
|
|
158
|
+
return `${brandingFn}(${field.unbrandedTypeName === 'string' ? `'${referenceId}'` : referenceId})`;
|
|
159
|
+
}
|
|
160
|
+
function generateFieldDataEnum({ field }) {
|
|
161
|
+
return `'${faker_1.faker.helpers.arrayElement(field.enumerator.values)}'`;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Creates the seed file that exposes all seed data as single object.
|
|
165
|
+
*/
|
|
166
|
+
function generateSeeds({ models, config, }) {
|
|
167
|
+
const imports = imports_1.ImportsGenerator.from(config.paths.seedPath);
|
|
168
|
+
for (const { meta } of models) {
|
|
169
|
+
imports.addImport({
|
|
170
|
+
items: [meta.seed.constantName],
|
|
171
|
+
from: meta.seed.filePath,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
const seeds = models.map(({ meta }) => `${meta.seed.constantName}`).join(',\n');
|
|
175
|
+
return `
|
|
176
|
+
import { MockData } from '${config.paths.seedPath}'
|
|
177
|
+
|
|
178
|
+
${imports.generate()}
|
|
179
|
+
|
|
180
|
+
export const seed: MockData = {
|
|
181
|
+
${seeds}
|
|
182
|
+
}
|
|
183
|
+
`;
|
|
184
|
+
}
|
|
185
|
+
exports.generateSeeds = generateSeeds;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Model } from '../../lib/schema/schema';
|
|
2
|
+
import { ModelMetaData } from '../../lib/meta';
|
|
3
|
+
/**
|
|
4
|
+
* Generates a stub definition file for a given model.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateStub({ model, meta }: {
|
|
7
|
+
model: Model;
|
|
8
|
+
meta: ModelMetaData;
|
|
9
|
+
}): string;
|