zenstack 0.1.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/LICENSE +21 -0
- package/bin/cli +3 -0
- package/out/cli/cli-util.js +64 -0
- package/out/cli/cli-util.js.map +1 -0
- package/out/cli/generator.js +1 -0
- package/out/cli/generator.js.map +1 -0
- package/out/cli/index.js +90 -0
- package/out/cli/index.js.map +1 -0
- package/out/extension.js +81 -0
- package/out/extension.js.map +1 -0
- package/out/generator/data-server/index.js +1 -0
- package/out/generator/data-server/index.js.map +1 -0
- package/out/generator/next-auth/index.js +196 -0
- package/out/generator/next-auth/index.js.map +1 -0
- package/out/generator/prisma/index.js +212 -0
- package/out/generator/prisma/index.js.map +1 -0
- package/out/generator/prisma/prisma-builder.js +307 -0
- package/out/generator/prisma/prisma-builder.js.map +1 -0
- package/out/generator/react-hooks/index.js +258 -0
- package/out/generator/react-hooks/index.js.map +1 -0
- package/out/generator/server/data/data-generator.js +376 -0
- package/out/generator/server/data/data-generator.js.map +1 -0
- package/out/generator/server/data/expression-writer.js +281 -0
- package/out/generator/server/data/expression-writer.js.map +1 -0
- package/out/generator/server/data/plain-expression-builder.js +53 -0
- package/out/generator/server/data/plain-expression-builder.js.map +1 -0
- package/out/generator/server/data-generator.js +82 -0
- package/out/generator/server/data-generator.js.map +1 -0
- package/out/generator/server/expression-writer.js +1 -0
- package/out/generator/server/expression-writer.js.map +1 -0
- package/out/generator/server/function/function-generator.js +50 -0
- package/out/generator/server/function/function-generator.js.map +1 -0
- package/out/generator/server/function-generator.js +13 -0
- package/out/generator/server/function-generator.js.map +1 -0
- package/out/generator/server/index.js +88 -0
- package/out/generator/server/index.js.map +1 -0
- package/out/generator/server/js-expression-builder.js +1 -0
- package/out/generator/server/js-expression-builder.js.map +1 -0
- package/out/generator/server/plain-expression-builder.js +1 -0
- package/out/generator/server/plain-expression-builder.js.map +1 -0
- package/out/generator/server/server-code-generator.js +3 -0
- package/out/generator/server/server-code-generator.js.map +1 -0
- package/out/generator/server/server-code-writer.js +1 -0
- package/out/generator/server/server-code-writer.js.map +1 -0
- package/out/generator/service/index.js +72 -0
- package/out/generator/service/index.js.map +1 -0
- package/out/generator/types.js +10 -0
- package/out/generator/types.js.map +1 -0
- package/out/generator/utils.js +10 -0
- package/out/generator/utils.js.map +1 -0
- package/out/language-server/generated/ast.js +386 -0
- package/out/language-server/generated/ast.js.map +1 -0
- package/out/language-server/generated/grammar.js +2193 -0
- package/out/language-server/generated/grammar.js.map +1 -0
- package/out/language-server/generated/module.js +23 -0
- package/out/language-server/generated/module.js.map +1 -0
- package/out/language-server/main.js +12 -0
- package/out/language-server/main.js.map +1 -0
- package/out/language-server/stdlib.zmodel +21 -0
- package/out/language-server/types.js +3 -0
- package/out/language-server/types.js.map +1 -0
- package/out/language-server/zmodel-index.js +38 -0
- package/out/language-server/zmodel-index.js.map +1 -0
- package/out/language-server/zmodel-linker.js +239 -0
- package/out/language-server/zmodel-linker.js.map +1 -0
- package/out/language-server/zmodel-module.js +51 -0
- package/out/language-server/zmodel-module.js.map +1 -0
- package/out/language-server/zmodel-scope.js +30 -0
- package/out/language-server/zmodel-scope.js.map +1 -0
- package/out/language-server/zmodel-validator.js +25 -0
- package/out/language-server/zmodel-validator.js.map +1 -0
- package/out/utils/indent-string.js +25 -0
- package/out/utils/indent-string.js.map +1 -0
- package/package.json +94 -0
- package/src/cli/cli-util.ts +80 -0
- package/src/cli/index.ts +80 -0
- package/src/extension.ts +76 -0
- package/src/generator/next-auth/index.ts +183 -0
- package/src/generator/prisma/index.ts +323 -0
- package/src/generator/prisma/prisma-builder.ts +366 -0
- package/src/generator/react-hooks/index.ts +267 -0
- package/src/generator/server/data/data-generator.ts +483 -0
- package/src/generator/server/data/expression-writer.ts +350 -0
- package/src/generator/server/data/plain-expression-builder.ts +72 -0
- package/src/generator/server/function/function-generator.ts +32 -0
- package/src/generator/server/index.ts +57 -0
- package/src/generator/server/server-code-generator.ts +6 -0
- package/src/generator/service/index.ts +43 -0
- package/src/generator/types.ts +16 -0
- package/src/generator/utils.ts +9 -0
- package/src/language-server/generated/ast.ts +603 -0
- package/src/language-server/generated/grammar.ts +2190 -0
- package/src/language-server/generated/module.ts +24 -0
- package/src/language-server/main.ts +12 -0
- package/src/language-server/stdlib.zmodel +21 -0
- package/src/language-server/types.ts +9 -0
- package/src/language-server/zmodel-index.ts +33 -0
- package/src/language-server/zmodel-linker.ts +407 -0
- package/src/language-server/zmodel-module.ts +90 -0
- package/src/language-server/zmodel-scope.ts +21 -0
- package/src/language-server/zmodel-validator.ts +35 -0
- package/src/language-server/zmodel.langium +186 -0
- package/src/utils/indent-string.ts +41 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { writeFile } from 'fs/promises';
|
|
2
|
+
import { AstNode } from 'langium';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import colors from 'colors';
|
|
5
|
+
import {
|
|
6
|
+
AttributeArg,
|
|
7
|
+
DataModel,
|
|
8
|
+
DataModelAttribute,
|
|
9
|
+
DataModelField,
|
|
10
|
+
DataModelFieldAttribute,
|
|
11
|
+
DataSource,
|
|
12
|
+
Enum,
|
|
13
|
+
Expression,
|
|
14
|
+
InvocationExpr,
|
|
15
|
+
isArrayExpr,
|
|
16
|
+
isInvocationExpr,
|
|
17
|
+
isLiteralExpr,
|
|
18
|
+
isReferenceExpr,
|
|
19
|
+
LiteralExpr,
|
|
20
|
+
} from '@lang/generated/ast';
|
|
21
|
+
import { Context, Generator, GeneratorError } from '../types';
|
|
22
|
+
import {
|
|
23
|
+
AttributeArg as PrismaAttributeArg,
|
|
24
|
+
AttributeArgValue as PrismaAttributeArgValue,
|
|
25
|
+
DataSourceUrl as PrismaDataSourceUrl,
|
|
26
|
+
FieldAttribute as PrismaFieldAttribute,
|
|
27
|
+
ModelAttribute as PrismaModelAttribute,
|
|
28
|
+
Model as PrismaDataModel,
|
|
29
|
+
FieldReference as PrismaFieldReference,
|
|
30
|
+
FieldReferenceArg as PrismaFieldReferenceArg,
|
|
31
|
+
FunctionCall as PrismaFunctionCall,
|
|
32
|
+
FunctionCallArg as PrismaFunctionCallArg,
|
|
33
|
+
PrismaModel,
|
|
34
|
+
ModelFieldType,
|
|
35
|
+
} from './prisma-builder';
|
|
36
|
+
import { execSync } from 'child_process';
|
|
37
|
+
|
|
38
|
+
const supportedProviders = ['postgresql', 'mysql', 'sqlite', 'sqlserver'];
|
|
39
|
+
const supportedAttrbutes = [
|
|
40
|
+
'id',
|
|
41
|
+
'index',
|
|
42
|
+
'relation',
|
|
43
|
+
'default',
|
|
44
|
+
'createdAt',
|
|
45
|
+
'updatedAt',
|
|
46
|
+
'unique',
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
export default class PrismaGenerator implements Generator {
|
|
50
|
+
async generate(context: Context) {
|
|
51
|
+
const { schema } = context;
|
|
52
|
+
const prisma = new PrismaModel();
|
|
53
|
+
|
|
54
|
+
for (const decl of schema.declarations) {
|
|
55
|
+
switch (decl.$type) {
|
|
56
|
+
case DataSource:
|
|
57
|
+
this.generateDataSource(
|
|
58
|
+
context,
|
|
59
|
+
prisma,
|
|
60
|
+
decl as DataSource
|
|
61
|
+
);
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case Enum:
|
|
65
|
+
this.generateEnum(context, prisma, decl as Enum);
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case DataModel:
|
|
69
|
+
this.generateModel(context, prisma, decl as DataModel);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.generateGenerator(context, prisma);
|
|
75
|
+
|
|
76
|
+
const outFile = path.join(context.outDir, 'schema.prisma');
|
|
77
|
+
await writeFile(outFile, prisma.toString());
|
|
78
|
+
console.log(colors.blue(` ✔️ Prisma schema generated`));
|
|
79
|
+
|
|
80
|
+
// run prisma generate and install @prisma/client
|
|
81
|
+
await this.generatePrismaClient(outFile);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async generatePrismaClient(schemaFile: string) {
|
|
85
|
+
try {
|
|
86
|
+
execSync('npx prisma');
|
|
87
|
+
} catch (err) {
|
|
88
|
+
execSync(`npm i prisma @prisma/client`);
|
|
89
|
+
console.log(colors.blue(' ✔️ Prisma package installed'));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
execSync(`npx prisma generate --schema "${schemaFile}"`);
|
|
93
|
+
console.log(colors.blue(' ✔️ Prisma client generated'));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private isStringLiteral(node: AstNode): node is LiteralExpr {
|
|
97
|
+
return isLiteralExpr(node) && typeof node.value === 'string';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private generateDataSource(
|
|
101
|
+
context: Context,
|
|
102
|
+
prisma: PrismaModel,
|
|
103
|
+
dataSource: DataSource
|
|
104
|
+
) {
|
|
105
|
+
let provider: string | undefined = undefined;
|
|
106
|
+
let url: PrismaDataSourceUrl | undefined = undefined;
|
|
107
|
+
let shadowDatabaseUrl: PrismaDataSourceUrl | undefined = undefined;
|
|
108
|
+
|
|
109
|
+
for (const f of dataSource.fields) {
|
|
110
|
+
switch (f.name) {
|
|
111
|
+
case 'provider': {
|
|
112
|
+
if (this.isStringLiteral(f.value)) {
|
|
113
|
+
provider = f.value.value as string;
|
|
114
|
+
} else {
|
|
115
|
+
throw new GeneratorError(
|
|
116
|
+
'Datasource provider must be set to a string'
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (!supportedProviders.includes(provider)) {
|
|
120
|
+
throw new GeneratorError(
|
|
121
|
+
`Provider ${provider} is not supported. Supported providers: ${supportedProviders.join(
|
|
122
|
+
', '
|
|
123
|
+
)}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
case 'url': {
|
|
130
|
+
const r = this.extractDataSourceUrl(f.value);
|
|
131
|
+
if (!r) {
|
|
132
|
+
throw new GeneratorError(
|
|
133
|
+
'Invalid value for datasource url'
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
url = r;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
case 'shadowDatabaseUrl': {
|
|
141
|
+
const r = this.extractDataSourceUrl(f.value);
|
|
142
|
+
if (!r) {
|
|
143
|
+
throw new GeneratorError(
|
|
144
|
+
'Invalid value for datasource url'
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
shadowDatabaseUrl = r;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!provider) {
|
|
154
|
+
throw new GeneratorError('Datasource is missing "provider" field');
|
|
155
|
+
}
|
|
156
|
+
if (!url) {
|
|
157
|
+
throw new GeneratorError('Datasource is missing "url" field');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
prisma.addDataSource(dataSource.name, provider, url, shadowDatabaseUrl);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private extractDataSourceUrl(fieldValue: LiteralExpr | InvocationExpr) {
|
|
164
|
+
if (this.isStringLiteral(fieldValue)) {
|
|
165
|
+
return new PrismaDataSourceUrl(fieldValue.value as string, false);
|
|
166
|
+
} else if (
|
|
167
|
+
isInvocationExpr(fieldValue) &&
|
|
168
|
+
fieldValue.function.ref?.name === 'env' &&
|
|
169
|
+
fieldValue.args.length === 1 &&
|
|
170
|
+
this.isStringLiteral(fieldValue.args[0].value)
|
|
171
|
+
) {
|
|
172
|
+
return new PrismaDataSourceUrl(
|
|
173
|
+
fieldValue.args[0].value.value as string,
|
|
174
|
+
true
|
|
175
|
+
);
|
|
176
|
+
} else {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private generateGenerator(context: Context, prisma: PrismaModel) {
|
|
182
|
+
prisma.addGenerator(
|
|
183
|
+
'client',
|
|
184
|
+
'prisma-client-js',
|
|
185
|
+
path.join(context.outDir, '.prisma'),
|
|
186
|
+
['fieldReference']
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private generateModel(
|
|
191
|
+
context: Context,
|
|
192
|
+
prisma: PrismaModel,
|
|
193
|
+
decl: DataModel
|
|
194
|
+
) {
|
|
195
|
+
const model = prisma.addModel(decl.name);
|
|
196
|
+
for (const field of decl.fields) {
|
|
197
|
+
this.generateModelField(model, field);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// add an "zenstack_guard" field for dealing with pure auth() related conditions
|
|
201
|
+
model.addField('zenstack_guard', 'Boolean', [
|
|
202
|
+
new PrismaFieldAttribute('default', [
|
|
203
|
+
new PrismaAttributeArg(
|
|
204
|
+
undefined,
|
|
205
|
+
new PrismaAttributeArgValue('Boolean', true)
|
|
206
|
+
),
|
|
207
|
+
]),
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
for (const attr of decl.attributes.filter((attr) =>
|
|
211
|
+
supportedAttrbutes.includes(attr.decl.ref?.name!)
|
|
212
|
+
)) {
|
|
213
|
+
this.generateModelAttribute(model, attr);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private generateModelField(model: PrismaDataModel, field: DataModelField) {
|
|
218
|
+
const type = new ModelFieldType(
|
|
219
|
+
(field.type.type || field.type.reference?.ref?.name)!,
|
|
220
|
+
field.type.array,
|
|
221
|
+
field.type.optional
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const attributes = field.attributes
|
|
225
|
+
.filter((attr) => supportedAttrbutes.includes(attr.decl.ref?.name!))
|
|
226
|
+
.map((attr) => this.makeFieldAttribute(attr));
|
|
227
|
+
model.addField(field.name, type, attributes);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private makeFieldAttribute(attr: DataModelFieldAttribute) {
|
|
231
|
+
return new PrismaFieldAttribute(
|
|
232
|
+
attr.decl.ref?.name!,
|
|
233
|
+
attr.args.map((arg) => this.makeAttributeArg(arg))
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
makeAttributeArg(arg: AttributeArg): PrismaAttributeArg {
|
|
238
|
+
return new PrismaAttributeArg(
|
|
239
|
+
arg.name,
|
|
240
|
+
this.makeAttributeArgValue(arg.value)
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
makeAttributeArgValue(node: Expression): PrismaAttributeArgValue {
|
|
245
|
+
if (isLiteralExpr(node)) {
|
|
246
|
+
switch (typeof node.value) {
|
|
247
|
+
case 'string':
|
|
248
|
+
return new PrismaAttributeArgValue('String', node.value);
|
|
249
|
+
case 'number':
|
|
250
|
+
return new PrismaAttributeArgValue('Number', node.value);
|
|
251
|
+
case 'boolean':
|
|
252
|
+
return new PrismaAttributeArgValue('Boolean', node.value);
|
|
253
|
+
default:
|
|
254
|
+
throw new GeneratorError(
|
|
255
|
+
`Unexpected literal type: ${typeof node.value}`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
} else if (isArrayExpr(node)) {
|
|
259
|
+
return new PrismaAttributeArgValue(
|
|
260
|
+
'Array',
|
|
261
|
+
new Array(
|
|
262
|
+
...node.items.map((item) =>
|
|
263
|
+
this.makeAttributeArgValue(item)
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
);
|
|
267
|
+
} else if (isReferenceExpr(node)) {
|
|
268
|
+
return new PrismaAttributeArgValue(
|
|
269
|
+
'FieldReference',
|
|
270
|
+
new PrismaFieldReference(
|
|
271
|
+
node.target.ref?.name!,
|
|
272
|
+
node.args.map(
|
|
273
|
+
(arg) =>
|
|
274
|
+
new PrismaFieldReferenceArg(arg.name, arg.value)
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
);
|
|
278
|
+
} else if (isInvocationExpr(node)) {
|
|
279
|
+
// invocation
|
|
280
|
+
return new PrismaAttributeArgValue(
|
|
281
|
+
'FunctionCall',
|
|
282
|
+
this.makeFunctionCall(node)
|
|
283
|
+
);
|
|
284
|
+
} else {
|
|
285
|
+
throw new GeneratorError(
|
|
286
|
+
`Unsupported attribute argument expression type: ${node.$type}`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
makeFunctionCall(node: InvocationExpr): PrismaFunctionCall {
|
|
292
|
+
return new PrismaFunctionCall(
|
|
293
|
+
node.function.ref?.name!,
|
|
294
|
+
node.args.map((arg) => {
|
|
295
|
+
if (!isLiteralExpr(arg.value)) {
|
|
296
|
+
throw new GeneratorError(
|
|
297
|
+
'Function call argument must be literal'
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
return new PrismaFunctionCallArg(arg.name, arg.value.value);
|
|
301
|
+
})
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private generateModelAttribute(
|
|
306
|
+
model: PrismaDataModel,
|
|
307
|
+
attr: DataModelAttribute
|
|
308
|
+
) {
|
|
309
|
+
model.attributes.push(
|
|
310
|
+
new PrismaModelAttribute(
|
|
311
|
+
attr.decl.ref?.name!,
|
|
312
|
+
attr.args.map((arg) => this.makeAttributeArg(arg))
|
|
313
|
+
)
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private generateEnum(context: Context, prisma: PrismaModel, decl: Enum) {
|
|
318
|
+
prisma.addEnum(
|
|
319
|
+
decl.name,
|
|
320
|
+
decl.fields.map((f) => f.name)
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import indentString from '../../utils/indent-string';
|
|
2
|
+
|
|
3
|
+
export class PrismaModel {
|
|
4
|
+
private datasources: DataSource[] = [];
|
|
5
|
+
private generators: Generator[] = [];
|
|
6
|
+
private models: Model[] = [];
|
|
7
|
+
private enums: Enum[] = [];
|
|
8
|
+
|
|
9
|
+
addDataSource(
|
|
10
|
+
name: string,
|
|
11
|
+
provider: string,
|
|
12
|
+
url: DataSourceUrl,
|
|
13
|
+
shadowDatabaseUrl?: DataSourceUrl
|
|
14
|
+
) {
|
|
15
|
+
const ds = new DataSource(name, provider, url, shadowDatabaseUrl);
|
|
16
|
+
this.datasources.push(ds);
|
|
17
|
+
return ds;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
addGenerator(
|
|
21
|
+
name: string,
|
|
22
|
+
provider: string,
|
|
23
|
+
output: string,
|
|
24
|
+
previewFeatures?: string[]
|
|
25
|
+
) {
|
|
26
|
+
const generator = new Generator(
|
|
27
|
+
name,
|
|
28
|
+
provider,
|
|
29
|
+
output,
|
|
30
|
+
previewFeatures
|
|
31
|
+
);
|
|
32
|
+
this.generators.push(generator);
|
|
33
|
+
return generator;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
addModel(name: string) {
|
|
37
|
+
const model = new Model(name);
|
|
38
|
+
this.models.push(model);
|
|
39
|
+
return model;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
addEnum(name: string, fields: string[]) {
|
|
43
|
+
const e = new Enum(name, fields);
|
|
44
|
+
this.enums.push(e);
|
|
45
|
+
return e;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
toString() {
|
|
49
|
+
return [
|
|
50
|
+
...this.datasources,
|
|
51
|
+
...this.generators,
|
|
52
|
+
...this.enums,
|
|
53
|
+
...this.models,
|
|
54
|
+
]
|
|
55
|
+
.map((d) => d.toString())
|
|
56
|
+
.join('\n\n');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class DataSource {
|
|
61
|
+
constructor(
|
|
62
|
+
public name: string,
|
|
63
|
+
public provider: string,
|
|
64
|
+
public url: DataSourceUrl,
|
|
65
|
+
public shadowDatabaseUrl?: DataSourceUrl
|
|
66
|
+
) {}
|
|
67
|
+
|
|
68
|
+
toString() {
|
|
69
|
+
return (
|
|
70
|
+
`datasource ${this.name} {\n` +
|
|
71
|
+
indentString(`provider="${this.provider}"\n`) +
|
|
72
|
+
indentString(`url=${this.url}\n`) +
|
|
73
|
+
(this.shadowDatabaseUrl
|
|
74
|
+
? indentString(`shadowDatabaseurl=${this.shadowDatabaseUrl}\n`)
|
|
75
|
+
: '') +
|
|
76
|
+
`}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export class DataSourceUrl {
|
|
82
|
+
constructor(public value: string, public isEnv: boolean) {}
|
|
83
|
+
|
|
84
|
+
toString() {
|
|
85
|
+
return this.isEnv ? `env("${this.value}")` : `"${this.value}"`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export class Generator {
|
|
90
|
+
constructor(
|
|
91
|
+
public name: string,
|
|
92
|
+
public provider: string,
|
|
93
|
+
public output: string,
|
|
94
|
+
public previewFeatures?: string[]
|
|
95
|
+
) {}
|
|
96
|
+
|
|
97
|
+
toString() {
|
|
98
|
+
return (
|
|
99
|
+
`generator ${this.name} {\n` +
|
|
100
|
+
indentString(`provider = "${this.provider}"\n`) +
|
|
101
|
+
indentString(`output = "${this.output}"\n`) +
|
|
102
|
+
(this.previewFeatures
|
|
103
|
+
? indentString(
|
|
104
|
+
`previewFeatures = [${this.previewFeatures
|
|
105
|
+
?.map((f) => '"' + f + '"')
|
|
106
|
+
.join(',')}]\n`
|
|
107
|
+
)
|
|
108
|
+
: '') +
|
|
109
|
+
`}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export class Model {
|
|
115
|
+
public fields: ModelField[] = [];
|
|
116
|
+
public attributes: ModelAttribute[] = [];
|
|
117
|
+
constructor(public name: string) {}
|
|
118
|
+
|
|
119
|
+
addField(
|
|
120
|
+
name: string,
|
|
121
|
+
type: ModelFieldType | string,
|
|
122
|
+
attributes: FieldAttribute[] = []
|
|
123
|
+
) {
|
|
124
|
+
const field = new ModelField(name, type, attributes);
|
|
125
|
+
this.fields.push(field);
|
|
126
|
+
return field;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
addAttribute(name: string, args: AttributeArg[] = []) {
|
|
130
|
+
const attr = new ModelAttribute(name, args);
|
|
131
|
+
this.attributes.push(attr);
|
|
132
|
+
return attr;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
toString() {
|
|
136
|
+
return (
|
|
137
|
+
`model ${this.name} {\n` +
|
|
138
|
+
indentString(
|
|
139
|
+
[...this.fields, ...this.attributes]
|
|
140
|
+
.map((d) => d.toString())
|
|
141
|
+
.join('\n')
|
|
142
|
+
) +
|
|
143
|
+
`\n}`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export type ScalarTypes =
|
|
149
|
+
| 'String'
|
|
150
|
+
| 'Boolean'
|
|
151
|
+
| 'Int'
|
|
152
|
+
| 'BigInt'
|
|
153
|
+
| 'Float'
|
|
154
|
+
| 'Decimal'
|
|
155
|
+
| 'DateTime'
|
|
156
|
+
| 'Json'
|
|
157
|
+
| 'Bytes'
|
|
158
|
+
| 'Unsupported';
|
|
159
|
+
|
|
160
|
+
export class ModelFieldType {
|
|
161
|
+
constructor(
|
|
162
|
+
public type: ScalarTypes | string,
|
|
163
|
+
public array?: boolean,
|
|
164
|
+
public optional?: boolean
|
|
165
|
+
) {}
|
|
166
|
+
|
|
167
|
+
toString() {
|
|
168
|
+
return `${this.type}${this.array ? '[]' : ''}${
|
|
169
|
+
this.optional ? '?' : ''
|
|
170
|
+
}`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export class ModelField {
|
|
175
|
+
constructor(
|
|
176
|
+
public name: string,
|
|
177
|
+
public type: ModelFieldType | string,
|
|
178
|
+
public attributes: FieldAttribute[] = []
|
|
179
|
+
) {}
|
|
180
|
+
|
|
181
|
+
addAttribute(name: string, args: AttributeArg[] = []) {
|
|
182
|
+
const attr = new FieldAttribute(name, args);
|
|
183
|
+
this.attributes.push(attr);
|
|
184
|
+
return attr;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
toString() {
|
|
188
|
+
return (
|
|
189
|
+
`${this.name} ${this.type}` +
|
|
190
|
+
(this.attributes.length > 0
|
|
191
|
+
? ' ' + this.attributes.map((a) => a.toString()).join(' ')
|
|
192
|
+
: '')
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export class FieldAttribute {
|
|
198
|
+
constructor(public name: string, public args: AttributeArg[] = []) {}
|
|
199
|
+
|
|
200
|
+
toString() {
|
|
201
|
+
return (
|
|
202
|
+
`@${this.name}(` +
|
|
203
|
+
this.args.map((a) => a.toString()).join(', ') +
|
|
204
|
+
`)`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export class ModelAttribute {
|
|
210
|
+
constructor(public name: string, public args: AttributeArg[] = []) {}
|
|
211
|
+
|
|
212
|
+
toString() {
|
|
213
|
+
return (
|
|
214
|
+
`@@${this.name}(` +
|
|
215
|
+
this.args.map((a) => a.toString()).join(', ') +
|
|
216
|
+
`)`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export class AttributeArg {
|
|
222
|
+
constructor(
|
|
223
|
+
public name: string | undefined,
|
|
224
|
+
public value: AttributeArgValue
|
|
225
|
+
) {}
|
|
226
|
+
|
|
227
|
+
toString() {
|
|
228
|
+
return this.name
|
|
229
|
+
? `${this.name}: ${this.value}`
|
|
230
|
+
: this.value.toString();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export class AttributeArgValue {
|
|
235
|
+
constructor(
|
|
236
|
+
public type:
|
|
237
|
+
| 'String'
|
|
238
|
+
| 'FieldReference'
|
|
239
|
+
| 'Number'
|
|
240
|
+
| 'Boolean'
|
|
241
|
+
| 'Array'
|
|
242
|
+
| 'FunctionCall',
|
|
243
|
+
public value:
|
|
244
|
+
| string
|
|
245
|
+
| number
|
|
246
|
+
| boolean
|
|
247
|
+
| FieldReference
|
|
248
|
+
| FunctionCall
|
|
249
|
+
| AttributeArgValue[]
|
|
250
|
+
) {
|
|
251
|
+
switch (type) {
|
|
252
|
+
case 'String':
|
|
253
|
+
if (typeof value !== 'string')
|
|
254
|
+
throw new Error('Value must be string');
|
|
255
|
+
break;
|
|
256
|
+
case 'Number':
|
|
257
|
+
if (typeof value !== 'number')
|
|
258
|
+
throw new Error('Value must be number');
|
|
259
|
+
break;
|
|
260
|
+
case 'Boolean':
|
|
261
|
+
if (typeof value !== 'boolean')
|
|
262
|
+
throw new Error('Value must be boolean');
|
|
263
|
+
break;
|
|
264
|
+
case 'Array':
|
|
265
|
+
if (!Array.isArray(value))
|
|
266
|
+
throw new Error('Value must be array');
|
|
267
|
+
break;
|
|
268
|
+
case 'FieldReference':
|
|
269
|
+
if (
|
|
270
|
+
typeof value !== 'string' &&
|
|
271
|
+
!(value instanceof FieldReference)
|
|
272
|
+
)
|
|
273
|
+
throw new Error('Value must be string or FieldReference');
|
|
274
|
+
break;
|
|
275
|
+
case 'FunctionCall':
|
|
276
|
+
if (!(value instanceof FunctionCall))
|
|
277
|
+
throw new Error('Value must be FunctionCall');
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
toString(): string {
|
|
283
|
+
switch (this.type) {
|
|
284
|
+
case 'String':
|
|
285
|
+
return `"${this.value}"`;
|
|
286
|
+
case 'Number':
|
|
287
|
+
return this.value.toString();
|
|
288
|
+
case 'FieldReference': {
|
|
289
|
+
if (typeof this.value === 'string') {
|
|
290
|
+
return this.value;
|
|
291
|
+
} else {
|
|
292
|
+
const fr = this.value as FieldReference;
|
|
293
|
+
let r = fr.field;
|
|
294
|
+
if (fr.args.length > 0) {
|
|
295
|
+
r +=
|
|
296
|
+
'(' +
|
|
297
|
+
fr.args.map((a) => a.toString()).join(',') +
|
|
298
|
+
')';
|
|
299
|
+
}
|
|
300
|
+
return r;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
case 'FunctionCall':
|
|
304
|
+
return this.value.toString();
|
|
305
|
+
case 'Boolean':
|
|
306
|
+
return this.value ? 'true' : 'false';
|
|
307
|
+
case 'Array':
|
|
308
|
+
return (
|
|
309
|
+
'[' +
|
|
310
|
+
(this.value as AttributeArgValue[])
|
|
311
|
+
.map((v) => v.toString())
|
|
312
|
+
.join(', ') +
|
|
313
|
+
']'
|
|
314
|
+
);
|
|
315
|
+
default:
|
|
316
|
+
throw new Error(`Unknown attribute value type ${this.type}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export class FieldReference {
|
|
322
|
+
constructor(public field: string, public args: FieldReferenceArg[] = []) {}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export class FieldReferenceArg {
|
|
326
|
+
constructor(public name: 'sort', public value: 'Asc' | 'Desc') {}
|
|
327
|
+
|
|
328
|
+
toString() {
|
|
329
|
+
return `${this.name}: ${this.value}`;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export class FunctionCall {
|
|
334
|
+
constructor(public func: string, public args: FunctionCallArg[] = []) {}
|
|
335
|
+
|
|
336
|
+
toString() {
|
|
337
|
+
return (
|
|
338
|
+
`${this.func}` +
|
|
339
|
+
'(' +
|
|
340
|
+
this.args.map((a) => a.toString()).join(', ') +
|
|
341
|
+
')'
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export class FunctionCallArg {
|
|
347
|
+
constructor(public name: string | undefined, public value: any) {}
|
|
348
|
+
|
|
349
|
+
toString() {
|
|
350
|
+
return this.name ? `${this.name}: ${this.value}` : this.value;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export class Enum {
|
|
355
|
+
constructor(public name: string, public fields: EnumField[]) {}
|
|
356
|
+
|
|
357
|
+
toString() {
|
|
358
|
+
return (
|
|
359
|
+
`enum ${this.name} {\n` +
|
|
360
|
+
indentString(this.fields.join('\n')) +
|
|
361
|
+
'\n}'
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
type EnumField = String;
|