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,350 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BinaryExpr,
|
|
3
|
+
Expression,
|
|
4
|
+
isDataModel,
|
|
5
|
+
isDataModelField,
|
|
6
|
+
isEnumField,
|
|
7
|
+
isMemberAccessExpr,
|
|
8
|
+
isReferenceExpr,
|
|
9
|
+
LiteralExpr,
|
|
10
|
+
MemberAccessExpr,
|
|
11
|
+
ReferenceExpr,
|
|
12
|
+
ThisExpr,
|
|
13
|
+
UnaryExpr,
|
|
14
|
+
} from '@lang/generated/ast';
|
|
15
|
+
import { CodeBlockWriter } from 'ts-morph';
|
|
16
|
+
import { GeneratorError } from '../../types';
|
|
17
|
+
import { TypedNode } from '@lang/types';
|
|
18
|
+
import PlainExpressionBuilder from './plain-expression-builder';
|
|
19
|
+
|
|
20
|
+
const AUX_GUARD_FIELD = 'zenstack_guard';
|
|
21
|
+
|
|
22
|
+
type ComparisonOperator = '==' | '!=' | '>' | '>=' | '<' | '<=';
|
|
23
|
+
|
|
24
|
+
export default class ExpressionWriter {
|
|
25
|
+
private readonly plainExprBuilder = new PlainExpressionBuilder();
|
|
26
|
+
|
|
27
|
+
constructor(private readonly writer: CodeBlockWriter) {}
|
|
28
|
+
|
|
29
|
+
write(expr: Expression) {
|
|
30
|
+
const _write = () => {
|
|
31
|
+
switch (expr.$type) {
|
|
32
|
+
case LiteralExpr:
|
|
33
|
+
this.writeLiteral(expr as LiteralExpr);
|
|
34
|
+
break;
|
|
35
|
+
|
|
36
|
+
case UnaryExpr:
|
|
37
|
+
this.writeUnary(expr as UnaryExpr);
|
|
38
|
+
break;
|
|
39
|
+
|
|
40
|
+
case BinaryExpr:
|
|
41
|
+
this.writeBinary(expr as BinaryExpr);
|
|
42
|
+
break;
|
|
43
|
+
|
|
44
|
+
case ReferenceExpr:
|
|
45
|
+
this.writeReference(expr as ReferenceExpr);
|
|
46
|
+
break;
|
|
47
|
+
|
|
48
|
+
case MemberAccessExpr:
|
|
49
|
+
this.writeMemberAccess(expr as MemberAccessExpr);
|
|
50
|
+
break;
|
|
51
|
+
|
|
52
|
+
case ThisExpr:
|
|
53
|
+
throw new Error('Not implemented');
|
|
54
|
+
|
|
55
|
+
default:
|
|
56
|
+
throw new Error(`Not implemented: ${expr.$type}`);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
this.writer.block(_write);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private writeReference(expr: ReferenceExpr) {
|
|
64
|
+
if (isEnumField(expr.target.ref)) {
|
|
65
|
+
throw new Error('Not implemented');
|
|
66
|
+
} else {
|
|
67
|
+
this.writer.write(`${expr.target.ref!.name}: true`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private writeMemberAccess(expr: MemberAccessExpr) {
|
|
72
|
+
this.writeFieldCondition(
|
|
73
|
+
expr.operand,
|
|
74
|
+
() => {
|
|
75
|
+
this.writer.block(() => {
|
|
76
|
+
this.writer.write(`${expr.member.ref?.name}: true`);
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
'is'
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private writeExprList(exprs: Expression[]) {
|
|
84
|
+
this.writer.writeLine('[');
|
|
85
|
+
for (let i = 0; i < exprs.length; i++) {
|
|
86
|
+
this.write(exprs[i]);
|
|
87
|
+
if (i !== exprs.length - 1) {
|
|
88
|
+
this.writer.writeLine(',');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
this.writer.writeLine(']');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private writeBinary(expr: BinaryExpr) {
|
|
95
|
+
switch (expr.operator) {
|
|
96
|
+
case '&&':
|
|
97
|
+
case '||':
|
|
98
|
+
this.writeLogical(expr, expr.operator);
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
case '==':
|
|
102
|
+
case '!=':
|
|
103
|
+
case '>':
|
|
104
|
+
case '>=':
|
|
105
|
+
case '<':
|
|
106
|
+
case '<=':
|
|
107
|
+
this.writeComparison(expr, expr.operator);
|
|
108
|
+
break;
|
|
109
|
+
|
|
110
|
+
case '?':
|
|
111
|
+
case '!':
|
|
112
|
+
case '^':
|
|
113
|
+
this.writeCollectionPredicate(expr, expr.operator);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private writeCollectionPredicate(expr: BinaryExpr, operator: string) {
|
|
119
|
+
this.writeFieldCondition(
|
|
120
|
+
expr.left,
|
|
121
|
+
() => {
|
|
122
|
+
this.write(expr.right);
|
|
123
|
+
},
|
|
124
|
+
operator === '?' ? 'some' : operator === '!' ? 'every' : 'none'
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private isFieldRef(expr: Expression): expr is ReferenceExpr {
|
|
129
|
+
if (isReferenceExpr(expr) && isDataModelField(expr.target.ref)) {
|
|
130
|
+
return true;
|
|
131
|
+
} else {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private guard(write: () => void) {
|
|
137
|
+
this.writer.write(`${AUX_GUARD_FIELD}: `);
|
|
138
|
+
write();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private plain(expr: Expression) {
|
|
142
|
+
this.writer.write(this.plainExprBuilder.build(expr));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private writeComparison(expr: BinaryExpr, operator: ComparisonOperator) {
|
|
146
|
+
const leftIsFieldAccess =
|
|
147
|
+
this.isFieldRef(expr.left) || this.isRelationFieldAccess(expr.left);
|
|
148
|
+
const rightIsFieldAccess =
|
|
149
|
+
this.isFieldRef(expr.right) ||
|
|
150
|
+
this.isRelationFieldAccess(expr.right);
|
|
151
|
+
|
|
152
|
+
if (leftIsFieldAccess && rightIsFieldAccess) {
|
|
153
|
+
throw new GeneratorError(
|
|
154
|
+
`Comparison between fields are not supported yet`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!leftIsFieldAccess && !rightIsFieldAccess) {
|
|
159
|
+
// compile down to a plain expression
|
|
160
|
+
this.guard(() => {
|
|
161
|
+
this.plain(expr.left);
|
|
162
|
+
this.writer.write(' ' + operator + ' ');
|
|
163
|
+
this.plain(expr.right);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let fieldAccess: Expression;
|
|
170
|
+
let operand: Expression;
|
|
171
|
+
if (leftIsFieldAccess) {
|
|
172
|
+
fieldAccess = expr.left;
|
|
173
|
+
operand = expr.right;
|
|
174
|
+
} else {
|
|
175
|
+
fieldAccess = expr.right;
|
|
176
|
+
operand = expr.left;
|
|
177
|
+
operator = this.negateOperator(operator);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const type = (fieldAccess as TypedNode).$resolvedType?.decl;
|
|
181
|
+
|
|
182
|
+
this.writeFieldCondition(
|
|
183
|
+
fieldAccess,
|
|
184
|
+
() => {
|
|
185
|
+
this.writer.block(() => {
|
|
186
|
+
if (isDataModel(type)) {
|
|
187
|
+
// comparing with an object, conver to "id" comparison instead
|
|
188
|
+
this.writer.write('id: ');
|
|
189
|
+
this.writer.block(() => {
|
|
190
|
+
this.writeOperator(operator, () => {
|
|
191
|
+
this.plain(operand);
|
|
192
|
+
this.writer.write('?.id');
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
} else {
|
|
196
|
+
this.writeOperator(operator, () => {
|
|
197
|
+
this.plain(operand);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
},
|
|
202
|
+
'is'
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private writeOperator(
|
|
207
|
+
operator: ComparisonOperator,
|
|
208
|
+
writeOperand: () => void
|
|
209
|
+
) {
|
|
210
|
+
if (operator === '!=') {
|
|
211
|
+
// wrap a 'not'
|
|
212
|
+
this.writer.write('not: ');
|
|
213
|
+
this.writer.block(() => {
|
|
214
|
+
this.writeOperator('==', writeOperand);
|
|
215
|
+
});
|
|
216
|
+
} else {
|
|
217
|
+
this.writer.write(`${this.mapOperator(operator)}: `);
|
|
218
|
+
writeOperand();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private writeFieldCondition(
|
|
223
|
+
fieldAccess: Expression,
|
|
224
|
+
writeCondition: () => void,
|
|
225
|
+
relationOp: 'is' | 'some' | 'every' | 'none'
|
|
226
|
+
) {
|
|
227
|
+
let selector: string;
|
|
228
|
+
let operand: Expression | undefined;
|
|
229
|
+
|
|
230
|
+
if (isReferenceExpr(fieldAccess)) {
|
|
231
|
+
selector = fieldAccess.target.ref?.name!;
|
|
232
|
+
} else if (isMemberAccessExpr(fieldAccess)) {
|
|
233
|
+
selector = fieldAccess.member.ref?.name!;
|
|
234
|
+
operand = fieldAccess.operand;
|
|
235
|
+
} else {
|
|
236
|
+
throw new GeneratorError(
|
|
237
|
+
`Unsupported expression type: ${fieldAccess.$type}`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (operand) {
|
|
242
|
+
// member access expression
|
|
243
|
+
this.writeFieldCondition(
|
|
244
|
+
operand,
|
|
245
|
+
() => {
|
|
246
|
+
this.writer.block(() => {
|
|
247
|
+
this.writer.write(selector + ': ');
|
|
248
|
+
if (this.isModelTyped(fieldAccess)) {
|
|
249
|
+
// expression is resolved to a model, generate relation query
|
|
250
|
+
this.writer.block(() => {
|
|
251
|
+
this.writer.write(`${relationOp}: `);
|
|
252
|
+
writeCondition();
|
|
253
|
+
});
|
|
254
|
+
} else {
|
|
255
|
+
// generate plain query
|
|
256
|
+
writeCondition();
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
'is'
|
|
261
|
+
);
|
|
262
|
+
} else if (this.isModelTyped(fieldAccess)) {
|
|
263
|
+
// reference resolved to a model, generate relation query
|
|
264
|
+
this.writer.write(selector + ': ');
|
|
265
|
+
this.writer.block(() => {
|
|
266
|
+
this.writer.write(`${relationOp}: `);
|
|
267
|
+
writeCondition();
|
|
268
|
+
});
|
|
269
|
+
} else {
|
|
270
|
+
// generate a plain query
|
|
271
|
+
this.writer.write(selector + ': ');
|
|
272
|
+
writeCondition();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private isModelTyped(expr: Expression) {
|
|
277
|
+
return isDataModel((expr as TypedNode).$resolvedType?.decl);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private isRelationFieldAccess(expr: Expression): boolean {
|
|
281
|
+
if (isMemberAccessExpr(expr)) {
|
|
282
|
+
return this.isRelationFieldAccess(expr.operand);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (
|
|
286
|
+
isReferenceExpr(expr) &&
|
|
287
|
+
isDataModelField(expr.target.ref) &&
|
|
288
|
+
expr.target.ref.type.reference &&
|
|
289
|
+
isDataModel(expr.target.ref.type.reference.ref)
|
|
290
|
+
) {
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
mapOperator(operator: '==' | '!=' | '>' | '>=' | '<' | '<=') {
|
|
298
|
+
switch (operator) {
|
|
299
|
+
case '==':
|
|
300
|
+
return 'equals';
|
|
301
|
+
case '!=':
|
|
302
|
+
throw new Error('Operation != should have been compiled away');
|
|
303
|
+
case '>':
|
|
304
|
+
return 'gt';
|
|
305
|
+
case '>=':
|
|
306
|
+
return 'ge';
|
|
307
|
+
case '<':
|
|
308
|
+
return 'lt';
|
|
309
|
+
case '<=':
|
|
310
|
+
return 'le';
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private negateOperator(operator: '==' | '!=' | '>' | '>=' | '<' | '<=') {
|
|
315
|
+
switch (operator) {
|
|
316
|
+
case '>':
|
|
317
|
+
return '<=';
|
|
318
|
+
case '<':
|
|
319
|
+
return '>=';
|
|
320
|
+
case '>=':
|
|
321
|
+
return '<';
|
|
322
|
+
case '<=':
|
|
323
|
+
return '>';
|
|
324
|
+
default:
|
|
325
|
+
return operator;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private writeLogical(expr: BinaryExpr, operator: '&&' | '||') {
|
|
330
|
+
this.writer.writeLine(`${operator === '&&' ? 'AND' : 'OR'}: `);
|
|
331
|
+
this.writeExprList([expr.left, expr.right]);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private writeUnary(expr: UnaryExpr) {
|
|
335
|
+
if (expr.operator !== '!') {
|
|
336
|
+
throw new GeneratorError(
|
|
337
|
+
`Unary operator "${expr.operator}" is not supported`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
this.writer.writeLine('NOT: ');
|
|
342
|
+
this.write(expr.operand);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private writeLiteral(expr: LiteralExpr) {
|
|
346
|
+
this.guard(() => {
|
|
347
|
+
this.plain(expr);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { GeneratorError } from '../../types';
|
|
2
|
+
import {
|
|
3
|
+
ArrayExpr,
|
|
4
|
+
Expression,
|
|
5
|
+
InvocationExpr,
|
|
6
|
+
LiteralExpr,
|
|
7
|
+
MemberAccessExpr,
|
|
8
|
+
NullExpr,
|
|
9
|
+
ReferenceExpr,
|
|
10
|
+
} from '@lang/generated/ast';
|
|
11
|
+
|
|
12
|
+
export default class PlainExpressionBuilder {
|
|
13
|
+
build(expr: Expression): string {
|
|
14
|
+
switch (expr.$type) {
|
|
15
|
+
case LiteralExpr:
|
|
16
|
+
return this.literal(expr as LiteralExpr);
|
|
17
|
+
|
|
18
|
+
case ArrayExpr:
|
|
19
|
+
return this.array(expr as ArrayExpr);
|
|
20
|
+
|
|
21
|
+
case NullExpr:
|
|
22
|
+
return this.null();
|
|
23
|
+
|
|
24
|
+
case ReferenceExpr:
|
|
25
|
+
return this.reference(expr as ReferenceExpr);
|
|
26
|
+
|
|
27
|
+
case InvocationExpr:
|
|
28
|
+
return this.invocation(expr as InvocationExpr);
|
|
29
|
+
|
|
30
|
+
case MemberAccessExpr:
|
|
31
|
+
return this.memberAccess(expr as MemberAccessExpr);
|
|
32
|
+
|
|
33
|
+
default:
|
|
34
|
+
throw new GeneratorError(
|
|
35
|
+
`Unsupported expression type: ${expr.$type}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private memberAccess(expr: MemberAccessExpr) {
|
|
41
|
+
return `${this.build(expr.operand)}?.${expr.member.ref!.name}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private invocation(expr: InvocationExpr) {
|
|
45
|
+
if (expr.function.ref?.name !== 'auth') {
|
|
46
|
+
throw new GeneratorError(
|
|
47
|
+
`Function invocation is not supported: ${expr.function.ref?.name}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return 'user';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private reference(expr: ReferenceExpr) {
|
|
54
|
+
return expr.target.ref!.name;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private null() {
|
|
58
|
+
return 'null';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private array(expr: ArrayExpr) {
|
|
62
|
+
return `[${expr.items.map((item) => this.build(item)).join(', ')}]`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private literal(expr: LiteralExpr) {
|
|
66
|
+
if (typeof expr.value === 'string') {
|
|
67
|
+
return `'${expr.value}'`;
|
|
68
|
+
} else {
|
|
69
|
+
return expr.value.toString();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Context } from '../../types';
|
|
2
|
+
import { Project } from 'ts-morph';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { ServerCodeGenerator } from '../server-code-generator';
|
|
5
|
+
|
|
6
|
+
export default class FunctionServerGenerator implements ServerCodeGenerator {
|
|
7
|
+
generate(project: Project, context: Context) {
|
|
8
|
+
this.generateIndex(project, context);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
private generateIndex(project: Project, context: Context) {
|
|
12
|
+
const content = `
|
|
13
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
14
|
+
import { RequestHandlerOptions } from '..';
|
|
15
|
+
|
|
16
|
+
export default async function (
|
|
17
|
+
req: NextApiRequest,
|
|
18
|
+
res: NextApiResponse,
|
|
19
|
+
path: string[],
|
|
20
|
+
options: RequestHandlerOptions
|
|
21
|
+
) {
|
|
22
|
+
throw new Error('Not implemented');
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
const sf = project.createSourceFile(
|
|
26
|
+
path.join(context.outDir, 'server/function/index.ts'),
|
|
27
|
+
content,
|
|
28
|
+
{ overwrite: true }
|
|
29
|
+
);
|
|
30
|
+
sf.formatText();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Project } from 'ts-morph';
|
|
2
|
+
import { Context, Generator } from '../types';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import DataServerGenerator from './data/data-generator';
|
|
5
|
+
import FunctionServerGenerator from './function/function-generator';
|
|
6
|
+
|
|
7
|
+
export default class ServerGenerator implements Generator {
|
|
8
|
+
async generate(context: Context) {
|
|
9
|
+
const project = new Project();
|
|
10
|
+
|
|
11
|
+
this.generateIndex(project, context);
|
|
12
|
+
|
|
13
|
+
new DataServerGenerator().generate(project, context);
|
|
14
|
+
new FunctionServerGenerator().generate(project, context);
|
|
15
|
+
|
|
16
|
+
await project.save();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
generateIndex(project: Project, context: Context) {
|
|
20
|
+
const content = `
|
|
21
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
22
|
+
import dataHandler from './data';
|
|
23
|
+
import functionHandler from './function';
|
|
24
|
+
|
|
25
|
+
export type AuthUser = { id: string } & Record<string, any>;
|
|
26
|
+
|
|
27
|
+
export type RequestHandlerOptions = {
|
|
28
|
+
getServerUser: (
|
|
29
|
+
req: NextApiRequest,
|
|
30
|
+
res: NextApiResponse
|
|
31
|
+
) => Promise<AuthUser | undefined>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function RequestHandler(options: RequestHandlerOptions) {
|
|
35
|
+
return async (req: NextApiRequest, res: NextApiResponse) => {
|
|
36
|
+
const [route, ...rest] = req.query.path as string[];
|
|
37
|
+
switch (route) {
|
|
38
|
+
case 'data':
|
|
39
|
+
return dataHandler(req, res, rest, options);
|
|
40
|
+
|
|
41
|
+
case 'function':
|
|
42
|
+
return functionHandler(req, res, rest, options);
|
|
43
|
+
|
|
44
|
+
default:
|
|
45
|
+
res.status(404).json({ error: 'Unknown route: ' + route });
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
const sf = project.createSourceFile(
|
|
51
|
+
path.join(context.outDir, 'server/index.ts'),
|
|
52
|
+
content,
|
|
53
|
+
{ overwrite: true }
|
|
54
|
+
);
|
|
55
|
+
sf.formatText();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Context, Generator } from '../types';
|
|
2
|
+
import { Project, StructureKind } from 'ts-morph';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import colors from 'colors';
|
|
5
|
+
|
|
6
|
+
export default class ServiceGenerator implements Generator {
|
|
7
|
+
async generate(context: Context) {
|
|
8
|
+
const project = new Project();
|
|
9
|
+
const sf = project.createSourceFile(
|
|
10
|
+
path.join(context.outDir, 'service.ts'),
|
|
11
|
+
undefined,
|
|
12
|
+
{ overwrite: true }
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
sf.addImportDeclaration({
|
|
16
|
+
namedImports: ['PrismaClient'],
|
|
17
|
+
moduleSpecifier: './.prisma',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const cls = sf.addClass({
|
|
21
|
+
name: 'ZenStackService',
|
|
22
|
+
isExported: true,
|
|
23
|
+
});
|
|
24
|
+
cls.addMember({
|
|
25
|
+
kind: StructureKind.Property,
|
|
26
|
+
name: 'private readonly _prisma',
|
|
27
|
+
initializer: 'new PrismaClient()',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
cls.addGetAccessor({
|
|
31
|
+
name: 'db',
|
|
32
|
+
})
|
|
33
|
+
.addBody()
|
|
34
|
+
.setBodyText('return this._prisma;');
|
|
35
|
+
|
|
36
|
+
sf.addStatements(['export default new ZenStackService();']);
|
|
37
|
+
|
|
38
|
+
sf.formatText();
|
|
39
|
+
await project.save();
|
|
40
|
+
|
|
41
|
+
console.log(colors.blue(` ✔️ ZenStack service generated`));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Model } from '@lang/generated/ast';
|
|
2
|
+
|
|
3
|
+
export interface Context {
|
|
4
|
+
schema: Model;
|
|
5
|
+
outDir: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface Generator {
|
|
9
|
+
generate(context: Context): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class GeneratorError extends Error {
|
|
13
|
+
constructor(message: string) {
|
|
14
|
+
super(message);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DataModel, isDataModel, Model } from '@lang/generated/ast';
|
|
2
|
+
|
|
3
|
+
export function extractDataModelsWithAllowRules(model: Model) {
|
|
4
|
+
return model.declarations.filter(
|
|
5
|
+
(d) =>
|
|
6
|
+
isDataModel(d) &&
|
|
7
|
+
!!d.attributes.find((attr) => attr.decl.ref?.name === 'allow')
|
|
8
|
+
) as DataModel[];
|
|
9
|
+
}
|