zenstack 0.1.47 → 0.1.50

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 (169) hide show
  1. package/LICENSE.md +9 -0
  2. package/bin/cli +1 -1
  3. package/bundle/asset/logo-dark-256.png +0 -0
  4. package/bundle/asset/logo-dark.png +0 -0
  5. package/bundle/asset/logo-light-256.png +0 -0
  6. package/bundle/asset/logo-light.png +0 -0
  7. package/bundle/cli/index.js +6849 -0
  8. package/bundle/cli/index.js.map +7 -0
  9. package/bundle/extension.js +39 -0
  10. package/bundle/extension.js.map +7 -0
  11. package/bundle/language-server/main.js +6105 -0
  12. package/bundle/language-server/main.js.map +7 -0
  13. package/{out/generator → bundle/res}/package.template.json +0 -0
  14. package/bundle/res/stdlib.zmodel +101 -0
  15. package/{out/generator → bundle/res}/tsconfig.template.json +0 -0
  16. package/package.json +38 -14
  17. package/src/cli/cli-util.ts +71 -0
  18. package/src/cli/index.ts +182 -0
  19. package/src/extension.ts +76 -0
  20. package/src/generator/constants.ts +5 -0
  21. package/src/generator/index.ts +102 -0
  22. package/{out/generator/next-auth/index.js → src/generator/next-auth/index.ts} +49 -58
  23. package/src/generator/prisma/expression-writer.ts +360 -0
  24. package/src/generator/prisma/index.ts +35 -0
  25. package/src/generator/prisma/prisma-builder.ts +370 -0
  26. package/src/generator/prisma/query-gard-generator.ts +213 -0
  27. package/src/generator/prisma/schema-generator.ts +305 -0
  28. package/src/generator/prisma/typescript-expression-transformer.ts +108 -0
  29. package/src/generator/react-hooks/index.ts +184 -0
  30. package/src/generator/service/index.ts +110 -0
  31. package/src/generator/types.ts +17 -0
  32. package/src/generator/utils.ts +18 -0
  33. package/src/language-server/constants.ts +28 -0
  34. package/src/language-server/generated/ast.ts +616 -0
  35. package/{out/language-server/generated/grammar.js → src/language-server/generated/grammar.ts} +5 -8
  36. package/src/language-server/generated/module.ts +24 -0
  37. package/src/language-server/langium-ext.d.ts +10 -0
  38. package/src/language-server/lsp/zmodel-definition-provider.ts +87 -0
  39. package/src/language-server/main.ts +13 -0
  40. package/src/language-server/types.ts +25 -0
  41. package/src/language-server/validator/attribute-validator.ts +11 -0
  42. package/src/language-server/validator/datamodel-validator.ts +311 -0
  43. package/src/language-server/validator/datasource-validator.ts +102 -0
  44. package/src/language-server/validator/enum-validator.ts +14 -0
  45. package/src/language-server/validator/schema-validator.ts +31 -0
  46. package/src/language-server/validator/utils.ts +158 -0
  47. package/src/language-server/validator/zmodel-validator.ts +84 -0
  48. package/src/language-server/zmodel-linker.ts +446 -0
  49. package/src/language-server/zmodel-module.ts +136 -0
  50. package/src/language-server/zmodel-scope.ts +45 -0
  51. package/src/language-server/zmodel-workspace-manager.ts +23 -0
  52. package/src/language-server/zmodel.langium +197 -0
  53. package/{out/cli → src/res}/package.template.json +2 -3
  54. package/src/res/stdlib.zmodel +101 -0
  55. package/{out/cli → src/res}/tsconfig.template.json +1 -1
  56. package/src/utils/exec-utils.ts +8 -0
  57. package/src/utils/indent-string.ts +9 -0
  58. package/LICENSE +0 -21
  59. package/out/cli/cli-util.js +0 -64
  60. package/out/cli/cli-util.js.map +0 -1
  61. package/out/cli/generator.js +0 -1
  62. package/out/cli/generator.js.map +0 -1
  63. package/out/cli/index.js +0 -124
  64. package/out/cli/index.js.map +0 -1
  65. package/out/extension.js +0 -81
  66. package/out/extension.js.map +0 -1
  67. package/out/generator/constants.js +0 -9
  68. package/out/generator/constants.js.map +0 -1
  69. package/out/generator/data-server/index.js +0 -1
  70. package/out/generator/data-server/index.js.map +0 -1
  71. package/out/generator/index.js +0 -98
  72. package/out/generator/index.js.map +0 -1
  73. package/out/generator/next-auth/index.js.map +0 -1
  74. package/out/generator/prisma/expression-writer.js +0 -287
  75. package/out/generator/prisma/expression-writer.js.map +0 -1
  76. package/out/generator/prisma/index.js +0 -44
  77. package/out/generator/prisma/index.js.map +0 -1
  78. package/out/generator/prisma/plain-expression-builder.js +0 -69
  79. package/out/generator/prisma/plain-expression-builder.js.map +0 -1
  80. package/out/generator/prisma/prisma-builder.js +0 -307
  81. package/out/generator/prisma/prisma-builder.js.map +0 -1
  82. package/out/generator/prisma/query-gard-generator.js +0 -159
  83. package/out/generator/prisma/query-gard-generator.js.map +0 -1
  84. package/out/generator/prisma/schema-generator.js +0 -193
  85. package/out/generator/prisma/schema-generator.js.map +0 -1
  86. package/out/generator/query-guard/index.js +0 -2
  87. package/out/generator/query-guard/index.js.map +0 -1
  88. package/out/generator/react-hooks/index.js +0 -179
  89. package/out/generator/react-hooks/index.js.map +0 -1
  90. package/out/generator/server/data/data-generator.js +0 -376
  91. package/out/generator/server/data/data-generator.js.map +0 -1
  92. package/out/generator/server/data/expression-writer.js +0 -287
  93. package/out/generator/server/data/expression-writer.js.map +0 -1
  94. package/out/generator/server/data/plain-expression-builder.js +0 -69
  95. package/out/generator/server/data/plain-expression-builder.js.map +0 -1
  96. package/out/generator/server/data-generator.js +0 -82
  97. package/out/generator/server/data-generator.js.map +0 -1
  98. package/out/generator/server/expression-writer.js +0 -1
  99. package/out/generator/server/expression-writer.js.map +0 -1
  100. package/out/generator/server/function/function-generator.js +0 -50
  101. package/out/generator/server/function/function-generator.js.map +0 -1
  102. package/out/generator/server/function-generator.js +0 -13
  103. package/out/generator/server/function-generator.js.map +0 -1
  104. package/out/generator/server/index.js +0 -88
  105. package/out/generator/server/index.js.map +0 -1
  106. package/out/generator/server/js-expression-builder.js +0 -1
  107. package/out/generator/server/js-expression-builder.js.map +0 -1
  108. package/out/generator/server/plain-expression-builder.js +0 -1
  109. package/out/generator/server/plain-expression-builder.js.map +0 -1
  110. package/out/generator/server/server-code-generator.js +0 -3
  111. package/out/generator/server/server-code-generator.js.map +0 -1
  112. package/out/generator/server/server-code-writer.js +0 -1
  113. package/out/generator/server/server-code-writer.js.map +0 -1
  114. package/out/generator/service/index.js +0 -133
  115. package/out/generator/service/index.js.map +0 -1
  116. package/out/generator/types.js +0 -10
  117. package/out/generator/types.js.map +0 -1
  118. package/out/generator/utils.js +0 -10
  119. package/out/generator/utils.js.map +0 -1
  120. package/out/langium-ext.js +0 -3
  121. package/out/langium-ext.js.map +0 -1
  122. package/out/language-server/constants.js +0 -20
  123. package/out/language-server/constants.js.map +0 -1
  124. package/out/language-server/generated/ast.js +0 -390
  125. package/out/language-server/generated/ast.js.map +0 -1
  126. package/out/language-server/generated/grammar.js.map +0 -1
  127. package/out/language-server/generated/module.js +0 -23
  128. package/out/language-server/generated/module.js.map +0 -1
  129. package/out/language-server/langium-ext.js +0 -3
  130. package/out/language-server/langium-ext.js.map +0 -1
  131. package/out/language-server/main.js +0 -13
  132. package/out/language-server/main.js.map +0 -1
  133. package/out/language-server/stdlib.zmodel +0 -23
  134. package/out/language-server/types.js +0 -3
  135. package/out/language-server/types.js.map +0 -1
  136. package/out/language-server/validator/attribute-validator copy.js +0 -12
  137. package/out/language-server/validator/attribute-validator copy.js.map +0 -1
  138. package/out/language-server/validator/attribute-validator.js +0 -7
  139. package/out/language-server/validator/attribute-validator.js.map +0 -1
  140. package/out/language-server/validator/datamodel-validator.js +0 -199
  141. package/out/language-server/validator/datamodel-validator.js.map +0 -1
  142. package/out/language-server/validator/datasource-validator copy.js +0 -77
  143. package/out/language-server/validator/datasource-validator copy.js.map +0 -1
  144. package/out/language-server/validator/datasource-validator.js +0 -77
  145. package/out/language-server/validator/datasource-validator.js.map +0 -1
  146. package/out/language-server/validator/enum-validator.js +0 -10
  147. package/out/language-server/validator/enum-validator.js.map +0 -1
  148. package/out/language-server/validator/model-validator.js +0 -21
  149. package/out/language-server/validator/model-validator.js.map +0 -1
  150. package/out/language-server/validator/schema-validator.js +0 -21
  151. package/out/language-server/validator/schema-validator.js.map +0 -1
  152. package/out/language-server/validator/utils.js +0 -106
  153. package/out/language-server/validator/utils.js.map +0 -1
  154. package/out/language-server/validator/zmodel-validator.js +0 -52
  155. package/out/language-server/validator/zmodel-validator.js.map +0 -1
  156. package/out/language-server/zmodel-index.js +0 -11
  157. package/out/language-server/zmodel-index.js.map +0 -1
  158. package/out/language-server/zmodel-linker.js +0 -249
  159. package/out/language-server/zmodel-linker.js.map +0 -1
  160. package/out/language-server/zmodel-module.js +0 -46
  161. package/out/language-server/zmodel-module.js.map +0 -1
  162. package/out/language-server/zmodel-scope.js +0 -41
  163. package/out/language-server/zmodel-scope.js.map +0 -1
  164. package/out/language-server/zmodel-validator.js +0 -35
  165. package/out/language-server/zmodel-validator.js.map +0 -1
  166. package/out/utils/exec-utils.js +0 -9
  167. package/out/utils/exec-utils.js.map +0 -1
  168. package/out/utils/indent-string.js +0 -9
  169. package/out/utils/indent-string.js.map +0 -1
@@ -0,0 +1,370 @@
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 = "${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;
@@ -0,0 +1,213 @@
1
+ import {
2
+ DataModel,
3
+ isDataModel,
4
+ isEnum,
5
+ isLiteralExpr,
6
+ } from '@lang/generated/ast';
7
+ import { PolicyKind, PolicyOperationKind } from '@zenstackhq/internal';
8
+ import path from 'path';
9
+ import { Project, SourceFile, VariableDeclarationKind } from 'ts-morph';
10
+ import { GUARD_FIELD_NAME, INTERNAL_PACKAGE } from '../constants';
11
+ import { Context } from '../types';
12
+ import { resolved } from '../utils';
13
+ import ExpressionWriter from './expression-writer';
14
+
15
+ /**
16
+ * Generates source file that contains Prisma query guard objects used for injecting database queries
17
+ */
18
+ export default class QueryGuardGenerator {
19
+ constructor(private readonly context: Context) {}
20
+
21
+ async generate(): Promise<void> {
22
+ const project = new Project();
23
+ const sf = project.createSourceFile(
24
+ path.join(this.context.generatedCodeDir, 'src/query/guard.ts'),
25
+ undefined,
26
+ { overwrite: true }
27
+ );
28
+
29
+ sf.addImportDeclaration({
30
+ namedImports: [{ name: 'QueryContext' }],
31
+ moduleSpecifier: INTERNAL_PACKAGE,
32
+ isTypeOnly: true,
33
+ });
34
+
35
+ // import enums
36
+ for (const e of this.context.schema.declarations.filter((d) =>
37
+ isEnum(d)
38
+ )) {
39
+ sf.addImportDeclaration({
40
+ namedImports: [{ name: e.name }],
41
+ moduleSpecifier: '../../.prisma',
42
+ });
43
+ }
44
+
45
+ const models = this.context.schema.declarations.filter((d) =>
46
+ isDataModel(d)
47
+ ) as DataModel[];
48
+
49
+ this.generateFieldMapping(models, sf);
50
+
51
+ models.forEach((model) => this.generateQueryGuardForModel(model, sf));
52
+
53
+ sf.formatText({});
54
+ await project.save();
55
+ }
56
+
57
+ private generateFieldMapping(models: DataModel[], sourceFile: SourceFile) {
58
+ const mapping = Object.fromEntries(
59
+ models.map((m) => [
60
+ m.name,
61
+ Object.fromEntries(
62
+ m.fields
63
+ .filter((f) => isDataModel(f.type.reference?.ref))
64
+ .map((f) => [
65
+ f.name,
66
+ {
67
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
68
+ type: resolved(f.type.reference!).name,
69
+ isArray: f.type.array,
70
+ },
71
+ ])
72
+ ),
73
+ ])
74
+ );
75
+
76
+ sourceFile.addVariableStatement({
77
+ isExported: true,
78
+ declarationKind: VariableDeclarationKind.Const,
79
+ declarations: [
80
+ {
81
+ name: '_fieldMapping',
82
+ initializer: JSON.stringify(mapping),
83
+ },
84
+ ],
85
+ });
86
+ }
87
+
88
+ private getPolicyExpressions(
89
+ model: DataModel,
90
+ kind: PolicyKind,
91
+ operation: PolicyOperationKind
92
+ ) {
93
+ const attrs = model.attributes.filter(
94
+ (attr) => attr.decl.ref?.name === `@@${kind}`
95
+ );
96
+ return attrs
97
+ .filter((attr) => {
98
+ if (
99
+ !isLiteralExpr(attr.args[0].value) ||
100
+ typeof attr.args[0].value.value !== 'string'
101
+ ) {
102
+ return false;
103
+ }
104
+ const ops = attr.args[0].value.value
105
+ .split(',')
106
+ .map((s) => s.trim());
107
+ return ops.includes(operation) || ops.includes('all');
108
+ })
109
+ .map((attr) => attr.args[1].value);
110
+ }
111
+
112
+ private async generateQueryGuardForModel(
113
+ model: DataModel,
114
+ sourceFile: SourceFile
115
+ ) {
116
+ for (const kind of ['create', 'update', 'read', 'delete']) {
117
+ const func = sourceFile
118
+ .addFunction({
119
+ name: model.name + '_' + kind,
120
+ returnType: 'any',
121
+ parameters: [
122
+ {
123
+ name: 'context',
124
+ type: 'QueryContext',
125
+ },
126
+ ],
127
+ isExported: true,
128
+ })
129
+ .addBody();
130
+
131
+ func.addStatements('const { user } = context;');
132
+
133
+ // r = <guard object>;
134
+ func.addVariableStatement({
135
+ declarationKind: VariableDeclarationKind.Const,
136
+ declarations: [
137
+ {
138
+ name: 'r',
139
+ initializer: (writer) => {
140
+ const exprWriter = new ExpressionWriter(writer);
141
+ const denies = this.getPolicyExpressions(
142
+ model,
143
+ 'deny',
144
+ kind as PolicyOperationKind
145
+ );
146
+ const allows = this.getPolicyExpressions(
147
+ model,
148
+ 'allow',
149
+ kind as PolicyOperationKind
150
+ );
151
+
152
+ const writeDenies = () => {
153
+ writer.conditionalWrite(
154
+ denies.length > 1,
155
+ '{ AND: ['
156
+ );
157
+ denies.forEach((expr, i) => {
158
+ writer.block(() => {
159
+ writer.write('NOT: ');
160
+ exprWriter.write(expr);
161
+ });
162
+ writer.conditionalWrite(
163
+ i !== denies.length - 1,
164
+ ','
165
+ );
166
+ });
167
+ writer.conditionalWrite(
168
+ denies.length > 1,
169
+ ']}'
170
+ );
171
+ };
172
+
173
+ const writeAllows = () => {
174
+ writer.conditionalWrite(
175
+ allows.length > 1,
176
+ '{ OR: ['
177
+ );
178
+ allows.forEach((expr, i) => {
179
+ exprWriter.write(expr);
180
+ writer.conditionalWrite(
181
+ i !== allows.length - 1,
182
+ ','
183
+ );
184
+ });
185
+ writer.conditionalWrite(
186
+ allows.length > 1,
187
+ ']}'
188
+ );
189
+ };
190
+
191
+ if (allows.length > 0 && denies.length > 0) {
192
+ writer.writeLine('{ AND: [');
193
+ writeDenies();
194
+ writer.writeLine(',');
195
+ writeAllows();
196
+ writer.writeLine(']}');
197
+ } else if (denies.length > 0) {
198
+ writeDenies();
199
+ } else if (allows.length > 0) {
200
+ writeAllows();
201
+ } else {
202
+ // disallow any operation
203
+ writer.write(`{ ${GUARD_FIELD_NAME}: false }`);
204
+ }
205
+ },
206
+ },
207
+ ],
208
+ });
209
+
210
+ func.addStatements('return r;');
211
+ }
212
+ }
213
+ }