zenstack 0.5.0 → 0.6.0-pre.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.
Files changed (62) hide show
  1. package/{LICENSE.md → LICENSE} +0 -0
  2. package/bin/cli +1 -1
  3. package/package.json +18 -13
  4. package/bin/post-install.js +0 -0
  5. package/bundle/asset/logo-256-bg.png +0 -0
  6. package/bundle/asset/logo-dark-256.png +0 -0
  7. package/bundle/asset/logo-light-256.png +0 -0
  8. package/bundle/cli/index.js +0 -6952
  9. package/bundle/extension.js +0 -39
  10. package/bundle/language-server/main.js +0 -6208
  11. package/bundle/res/package.template.json +0 -9
  12. package/bundle/res/prism-zmodel.js +0 -22
  13. package/bundle/res/stdlib.zmodel +0 -218
  14. package/bundle/res/tsconfig.template.json +0 -17
  15. package/src/cli/cli-error.ts +0 -4
  16. package/src/cli/cli-util.ts +0 -214
  17. package/src/cli/index.ts +0 -246
  18. package/src/extension.ts +0 -76
  19. package/src/generator/ast-utils.ts +0 -18
  20. package/src/generator/constants.ts +0 -6
  21. package/src/generator/field-constraint/index.ts +0 -304
  22. package/src/generator/index.ts +0 -86
  23. package/src/generator/prisma/expression-writer.ts +0 -360
  24. package/src/generator/prisma/index.ts +0 -44
  25. package/src/generator/prisma/prisma-builder.ts +0 -370
  26. package/src/generator/prisma/query-guard-generator.ts +0 -249
  27. package/src/generator/prisma/schema-generator.ts +0 -313
  28. package/src/generator/prisma/typescript-expression-transformer.ts +0 -108
  29. package/src/generator/react-hooks/index.ts +0 -273
  30. package/src/generator/service/index.ts +0 -113
  31. package/src/generator/tsc/index.ts +0 -59
  32. package/src/generator/types.ts +0 -20
  33. package/src/global.d.ts +0 -3
  34. package/src/language-server/constants.ts +0 -29
  35. package/src/language-server/generated/ast.ts +0 -643
  36. package/src/language-server/generated/grammar.ts +0 -2492
  37. package/src/language-server/generated/module.ts +0 -24
  38. package/src/language-server/langium-ext.d.ts +0 -22
  39. package/src/language-server/main.ts +0 -13
  40. package/src/language-server/types.ts +0 -25
  41. package/src/language-server/utils.ts +0 -21
  42. package/src/language-server/validator/attribute-validator.ts +0 -11
  43. package/src/language-server/validator/datamodel-validator.ts +0 -426
  44. package/src/language-server/validator/datasource-validator.ts +0 -102
  45. package/src/language-server/validator/enum-validator.ts +0 -14
  46. package/src/language-server/validator/expression-validator.ts +0 -48
  47. package/src/language-server/validator/schema-validator.ts +0 -31
  48. package/src/language-server/validator/utils.ts +0 -158
  49. package/src/language-server/validator/zmodel-validator.ts +0 -91
  50. package/src/language-server/zmodel-linker.ts +0 -453
  51. package/src/language-server/zmodel-module.ts +0 -131
  52. package/src/language-server/zmodel-scope.ts +0 -45
  53. package/src/language-server/zmodel-workspace-manager.ts +0 -23
  54. package/src/language-server/zmodel.langium +0 -207
  55. package/src/res/package.template.json +0 -9
  56. package/src/res/prism-zmodel.js +0 -22
  57. package/src/res/stdlib.zmodel +0 -218
  58. package/src/res/tsconfig.template.json +0 -17
  59. package/src/telemetry.ts +0 -119
  60. package/src/utils/exec-utils.ts +0 -8
  61. package/src/utils/indent-string.ts +0 -9
  62. package/src/utils/pkg-utils.ts +0 -63
@@ -1,370 +0,0 @@
1
- import indentString from '../../utils/indent-string';
2
-
3
- /**
4
- * Prisma schema builder
5
- */
6
- export class PrismaModel {
7
- private datasources: DataSource[] = [];
8
- private generators: Generator[] = [];
9
- private models: Model[] = [];
10
- private enums: Enum[] = [];
11
-
12
- addDataSource(
13
- name: string,
14
- provider: string,
15
- url: DataSourceUrl,
16
- shadowDatabaseUrl?: DataSourceUrl
17
- ): DataSource {
18
- const ds = new DataSource(name, provider, url, shadowDatabaseUrl);
19
- this.datasources.push(ds);
20
- return ds;
21
- }
22
-
23
- addGenerator(
24
- name: string,
25
- provider: string,
26
- output: string,
27
- previewFeatures?: string[]
28
- ): Generator {
29
- const generator = new Generator(
30
- name,
31
- provider,
32
- output,
33
- previewFeatures
34
- );
35
- this.generators.push(generator);
36
- return generator;
37
- }
38
-
39
- addModel(name: string): Model {
40
- const model = new Model(name);
41
- this.models.push(model);
42
- return model;
43
- }
44
-
45
- addEnum(name: string, fields: string[]): Enum {
46
- const e = new Enum(name, fields);
47
- this.enums.push(e);
48
- return e;
49
- }
50
-
51
- toString(): string {
52
- return [
53
- ...this.datasources,
54
- ...this.generators,
55
- ...this.enums,
56
- ...this.models,
57
- ]
58
- .map((d) => d.toString())
59
- .join('\n\n');
60
- }
61
- }
62
-
63
- export class DataSource {
64
- constructor(
65
- public name: string,
66
- public provider: string,
67
- public url: DataSourceUrl,
68
- public shadowDatabaseUrl?: DataSourceUrl
69
- ) {}
70
-
71
- toString(): string {
72
- return (
73
- `datasource ${this.name} {\n` +
74
- indentString(`provider="${this.provider}"\n`) +
75
- indentString(`url=${this.url}\n`) +
76
- (this.shadowDatabaseUrl
77
- ? indentString(`shadowDatabaseurl=${this.shadowDatabaseUrl}\n`)
78
- : '') +
79
- `}`
80
- );
81
- }
82
- }
83
-
84
- export class DataSourceUrl {
85
- constructor(public value: string, public isEnv: boolean) {}
86
-
87
- toString(): string {
88
- return this.isEnv ? `env("${this.value}")` : `"${this.value}"`;
89
- }
90
- }
91
-
92
- export class Generator {
93
- constructor(
94
- public name: string,
95
- public provider: string,
96
- public output: string,
97
- public previewFeatures?: string[]
98
- ) {}
99
-
100
- toString(): string {
101
- return (
102
- `generator ${this.name} {\n` +
103
- indentString(`provider = "${this.provider}"\n`) +
104
- indentString(`output = ${JSON.stringify(this.output)}\n`) +
105
- (this.previewFeatures
106
- ? indentString(
107
- `previewFeatures = [${this.previewFeatures
108
- ?.map((f) => '"' + f + '"')
109
- .join(', ')}]\n`
110
- )
111
- : '') +
112
- `}`
113
- );
114
- }
115
- }
116
-
117
- export class Model {
118
- public fields: ModelField[] = [];
119
- public attributes: ModelAttribute[] = [];
120
- constructor(public name: string) {}
121
-
122
- addField(
123
- name: string,
124
- type: ModelFieldType | string,
125
- attributes: FieldAttribute[] = []
126
- ): ModelField {
127
- const field = new ModelField(name, type, attributes);
128
- this.fields.push(field);
129
- return field;
130
- }
131
-
132
- addAttribute(name: string, args: AttributeArg[] = []): ModelAttribute {
133
- const attr = new ModelAttribute(name, args);
134
- this.attributes.push(attr);
135
- return attr;
136
- }
137
-
138
- toString(): string {
139
- return (
140
- `model ${this.name} {\n` +
141
- indentString(
142
- [...this.fields, ...this.attributes]
143
- .map((d) => d.toString())
144
- .join('\n')
145
- ) +
146
- `\n}`
147
- );
148
- }
149
- }
150
-
151
- export type ScalarTypes =
152
- | 'String'
153
- | 'Boolean'
154
- | 'Int'
155
- | 'BigInt'
156
- | 'Float'
157
- | 'Decimal'
158
- | 'DateTime'
159
- | 'Json'
160
- | 'Bytes'
161
- | 'Unsupported';
162
-
163
- export class ModelFieldType {
164
- constructor(
165
- public type: ScalarTypes | string,
166
- public array?: boolean,
167
- public optional?: boolean
168
- ) {}
169
-
170
- toString(): string {
171
- return `${this.type}${this.array ? '[]' : ''}${
172
- this.optional ? '?' : ''
173
- }`;
174
- }
175
- }
176
-
177
- export class ModelField {
178
- constructor(
179
- public name: string,
180
- public type: ModelFieldType | string,
181
- public attributes: FieldAttribute[] = []
182
- ) {}
183
-
184
- addAttribute(name: string, args: AttributeArg[] = []): FieldAttribute {
185
- const attr = new FieldAttribute(name, args);
186
- this.attributes.push(attr);
187
- return attr;
188
- }
189
-
190
- toString(): string {
191
- return (
192
- `${this.name} ${this.type}` +
193
- (this.attributes.length > 0
194
- ? ' ' + this.attributes.map((a) => a.toString()).join(' ')
195
- : '')
196
- );
197
- }
198
- }
199
-
200
- export class FieldAttribute {
201
- constructor(public name: string, public args: AttributeArg[] = []) {}
202
-
203
- toString(): string {
204
- return (
205
- `${this.name}(` +
206
- this.args.map((a) => a.toString()).join(', ') +
207
- `)`
208
- );
209
- }
210
- }
211
-
212
- export class ModelAttribute {
213
- constructor(public name: string, public args: AttributeArg[] = []) {}
214
-
215
- toString(): string {
216
- return (
217
- `${this.name}(` +
218
- this.args.map((a) => a.toString()).join(', ') +
219
- `)`
220
- );
221
- }
222
- }
223
-
224
- export class AttributeArg {
225
- constructor(
226
- public name: string | undefined,
227
- public value: AttributeArgValue
228
- ) {}
229
-
230
- toString(): string {
231
- return this.name
232
- ? `${this.name}: ${this.value}`
233
- : this.value.toString();
234
- }
235
- }
236
-
237
- export class AttributeArgValue {
238
- constructor(
239
- public type:
240
- | 'String'
241
- | 'FieldReference'
242
- | 'Number'
243
- | 'Boolean'
244
- | 'Array'
245
- | 'FunctionCall',
246
- public value:
247
- | string
248
- | number
249
- | boolean
250
- | FieldReference
251
- | FunctionCall
252
- | AttributeArgValue[]
253
- ) {
254
- switch (type) {
255
- case 'String':
256
- if (typeof value !== 'string')
257
- throw new Error('Value must be string');
258
- break;
259
- case 'Number':
260
- if (typeof value !== 'number')
261
- throw new Error('Value must be number');
262
- break;
263
- case 'Boolean':
264
- if (typeof value !== 'boolean')
265
- throw new Error('Value must be boolean');
266
- break;
267
- case 'Array':
268
- if (!Array.isArray(value))
269
- throw new Error('Value must be array');
270
- break;
271
- case 'FieldReference':
272
- if (
273
- typeof value !== 'string' &&
274
- !(value instanceof FieldReference)
275
- )
276
- throw new Error('Value must be string or FieldReference');
277
- break;
278
- case 'FunctionCall':
279
- if (!(value instanceof FunctionCall))
280
- throw new Error('Value must be FunctionCall');
281
- break;
282
- }
283
- }
284
-
285
- toString(): string {
286
- switch (this.type) {
287
- case 'String':
288
- return `"${this.value}"`;
289
- case 'Number':
290
- return this.value.toString();
291
- case 'FieldReference': {
292
- if (typeof this.value === 'string') {
293
- return this.value;
294
- } else {
295
- const fr = this.value as FieldReference;
296
- let r = fr.field;
297
- if (fr.args.length > 0) {
298
- r +=
299
- '(' +
300
- fr.args.map((a) => a.toString()).join(',') +
301
- ')';
302
- }
303
- return r;
304
- }
305
- }
306
- case 'FunctionCall':
307
- return this.value.toString();
308
- case 'Boolean':
309
- return this.value ? 'true' : 'false';
310
- case 'Array':
311
- return (
312
- '[' +
313
- (this.value as AttributeArgValue[])
314
- .map((v) => v.toString())
315
- .join(', ') +
316
- ']'
317
- );
318
- default:
319
- throw new Error(`Unknown attribute value type ${this.type}`);
320
- }
321
- }
322
- }
323
-
324
- export class FieldReference {
325
- constructor(public field: string, public args: FieldReferenceArg[] = []) {}
326
- }
327
-
328
- export class FieldReferenceArg {
329
- constructor(public name: 'sort', public value: 'Asc' | 'Desc') {}
330
-
331
- toString(): string {
332
- return `${this.name}: ${this.value}`;
333
- }
334
- }
335
-
336
- export class FunctionCall {
337
- constructor(public func: string, public args: FunctionCallArg[] = []) {}
338
-
339
- toString(): string {
340
- return (
341
- `${this.func}` +
342
- '(' +
343
- this.args.map((a) => a.toString()).join(', ') +
344
- ')'
345
- );
346
- }
347
- }
348
-
349
- export class FunctionCallArg {
350
- // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
351
- constructor(public name: string | undefined, public value: any) {}
352
-
353
- toString(): string {
354
- return this.name ? `${this.name}: ${this.value}` : this.value;
355
- }
356
- }
357
-
358
- export class Enum {
359
- constructor(public name: string, public fields: EnumField[]) {}
360
-
361
- toString(): string {
362
- return (
363
- `enum ${this.name} {\n` +
364
- indentString(this.fields.join('\n')) +
365
- '\n}'
366
- );
367
- }
368
- }
369
-
370
- type EnumField = string;
@@ -1,249 +0,0 @@
1
- import {
2
- DataModel,
3
- DataModelField,
4
- isDataModel,
5
- isEnum,
6
- isLiteralExpr,
7
- } from '@lang/generated/ast';
8
- import {
9
- FieldInfo,
10
- PolicyKind,
11
- PolicyOperationKind,
12
- RuntimeAttribute,
13
- } from '@zenstackhq/runtime/server';
14
- import path from 'path';
15
- import { Project, SourceFile, VariableDeclarationKind } from 'ts-morph';
16
- import {
17
- GUARD_FIELD_NAME,
18
- RUNTIME_PACKAGE,
19
- UNKNOWN_USER_ID,
20
- } from '../constants';
21
- import { Context } from '../types';
22
- import { resolved } from '../ast-utils';
23
- import ExpressionWriter from './expression-writer';
24
-
25
- /**
26
- * Generates source file that contains Prisma query guard objects used for injecting database queries
27
- */
28
- export default class QueryGuardGenerator {
29
- constructor(private readonly context: Context) {}
30
-
31
- async generate(): Promise<void> {
32
- const project = new Project();
33
- const sf = project.createSourceFile(
34
- path.join(this.context.generatedCodeDir, 'src/query/guard.ts'),
35
- undefined,
36
- { overwrite: true }
37
- );
38
-
39
- sf.addImportDeclaration({
40
- namedImports: [{ name: 'QueryContext' }],
41
- moduleSpecifier: `${RUNTIME_PACKAGE}/server`,
42
- isTypeOnly: true,
43
- });
44
-
45
- // import enums
46
- for (const e of this.context.schema.declarations.filter((d) =>
47
- isEnum(d)
48
- )) {
49
- sf.addImportDeclaration({
50
- namedImports: [{ name: e.name }],
51
- moduleSpecifier: '../../.prisma',
52
- });
53
- }
54
-
55
- const models = this.context.schema.declarations.filter((d) =>
56
- isDataModel(d)
57
- ) as DataModel[];
58
-
59
- this.generateFieldMapping(models, sf);
60
-
61
- for (const model of models) {
62
- await this.generateQueryGuardForModel(model, sf);
63
- }
64
-
65
- sf.formatText({});
66
- await project.save();
67
- }
68
-
69
- private generateFieldMapping(models: DataModel[], sourceFile: SourceFile) {
70
- const mapping = Object.fromEntries(
71
- models.map((m) => [
72
- m.name,
73
- Object.fromEntries(
74
- m.fields.map((f) => {
75
- const fieldInfo: FieldInfo = {
76
- name: f.name,
77
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
78
- type: f.type.reference
79
- ? f.type.reference.$refText
80
- : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
81
- f.type.type!,
82
- isDataModel: isDataModel(f.type.reference?.ref),
83
- isArray: f.type.array,
84
- isOptional: f.type.optional,
85
- attributes: this.getFieldAttributes(f),
86
- };
87
- return [f.name, fieldInfo];
88
- })
89
- ),
90
- ])
91
- );
92
-
93
- sourceFile.addVariableStatement({
94
- isExported: true,
95
- declarationKind: VariableDeclarationKind.Const,
96
- declarations: [
97
- {
98
- name: '_fieldMapping',
99
- initializer: JSON.stringify(mapping),
100
- },
101
- ],
102
- });
103
- }
104
-
105
- private getFieldAttributes(field: DataModelField): RuntimeAttribute[] {
106
- return field.attributes
107
- .map((attr) => {
108
- const args: Array<{ name?: string; value: unknown }> = [];
109
- for (const arg of attr.args) {
110
- if (!isLiteralExpr(arg.value)) {
111
- // attributes with non-literal args are skipped
112
- return undefined;
113
- }
114
- args.push({ name: arg.name, value: arg.value.value });
115
- }
116
- return { name: resolved(attr.decl).name, args };
117
- })
118
- .filter((d): d is RuntimeAttribute => !!d);
119
- }
120
-
121
- private getPolicyExpressions(
122
- model: DataModel,
123
- kind: PolicyKind,
124
- operation: PolicyOperationKind
125
- ) {
126
- const attrs = model.attributes.filter(
127
- (attr) => attr.decl.ref?.name === `@@${kind}`
128
- );
129
- return attrs
130
- .filter((attr) => {
131
- if (
132
- !isLiteralExpr(attr.args[0].value) ||
133
- typeof attr.args[0].value.value !== 'string'
134
- ) {
135
- return false;
136
- }
137
- const ops = attr.args[0].value.value
138
- .split(',')
139
- .map((s) => s.trim());
140
- return ops.includes(operation) || ops.includes('all');
141
- })
142
- .map((attr) => attr.args[1].value);
143
- }
144
-
145
- private async generateQueryGuardForModel(
146
- model: DataModel,
147
- sourceFile: SourceFile
148
- ) {
149
- for (const kind of ['create', 'update', 'read', 'delete']) {
150
- const func = sourceFile
151
- .addFunction({
152
- name: model.name + '_' + kind,
153
- returnType: 'any',
154
- parameters: [
155
- {
156
- name: 'context',
157
- type: 'QueryContext',
158
- },
159
- ],
160
- isExported: true,
161
- })
162
- .addBody();
163
-
164
- func.addStatements(
165
- // make suer user id is always available
166
- `const user = context.user?.id ? context.user : { ...context.user, id: '${UNKNOWN_USER_ID}' };`
167
- );
168
-
169
- // r = <guard object>;
170
- func.addVariableStatement({
171
- declarationKind: VariableDeclarationKind.Const,
172
- declarations: [
173
- {
174
- name: 'r',
175
- initializer: (writer) => {
176
- const exprWriter = new ExpressionWriter(writer);
177
- const denies = this.getPolicyExpressions(
178
- model,
179
- 'deny',
180
- kind as PolicyOperationKind
181
- );
182
- const allows = this.getPolicyExpressions(
183
- model,
184
- 'allow',
185
- kind as PolicyOperationKind
186
- );
187
-
188
- const writeDenies = () => {
189
- writer.conditionalWrite(
190
- denies.length > 1,
191
- '{ AND: ['
192
- );
193
- denies.forEach((expr, i) => {
194
- writer.block(() => {
195
- writer.write('NOT: ');
196
- exprWriter.write(expr);
197
- });
198
- writer.conditionalWrite(
199
- i !== denies.length - 1,
200
- ','
201
- );
202
- });
203
- writer.conditionalWrite(
204
- denies.length > 1,
205
- ']}'
206
- );
207
- };
208
-
209
- const writeAllows = () => {
210
- writer.conditionalWrite(
211
- allows.length > 1,
212
- '{ OR: ['
213
- );
214
- allows.forEach((expr, i) => {
215
- exprWriter.write(expr);
216
- writer.conditionalWrite(
217
- i !== allows.length - 1,
218
- ','
219
- );
220
- });
221
- writer.conditionalWrite(
222
- allows.length > 1,
223
- ']}'
224
- );
225
- };
226
-
227
- if (allows.length > 0 && denies.length > 0) {
228
- writer.writeLine('{ AND: [');
229
- writeDenies();
230
- writer.writeLine(',');
231
- writeAllows();
232
- writer.writeLine(']}');
233
- } else if (denies.length > 0) {
234
- writeDenies();
235
- } else if (allows.length > 0) {
236
- writeAllows();
237
- } else {
238
- // disallow any operation
239
- writer.write(`{ ${GUARD_FIELD_NAME}: false }`);
240
- }
241
- },
242
- },
243
- ],
244
- });
245
-
246
- func.addStatements('return r;');
247
- }
248
- }
249
- }