zenstack 0.1.0 → 0.1.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 (55) hide show
  1. package/out/cli/index.js +4 -51
  2. package/out/cli/index.js.map +1 -1
  3. package/out/cli/package.template.json +10 -0
  4. package/out/cli/tsconfig.template.json +17 -0
  5. package/out/generator/constants.js +6 -0
  6. package/out/generator/constants.js.map +1 -0
  7. package/out/generator/index.js +76 -0
  8. package/out/generator/index.js.map +1 -0
  9. package/out/generator/next-auth/index.js +3 -3
  10. package/out/generator/package.template.json +9 -0
  11. package/out/generator/prisma/expression-writer.js +287 -0
  12. package/out/generator/prisma/expression-writer.js.map +1 -0
  13. package/out/generator/prisma/index.js +8 -182
  14. package/out/generator/prisma/index.js.map +1 -1
  15. package/out/generator/prisma/plain-expression-builder.js +69 -0
  16. package/out/generator/prisma/plain-expression-builder.js.map +1 -0
  17. package/out/generator/prisma/prisma-builder.js +1 -1
  18. package/out/generator/prisma/prisma-builder.js.map +1 -1
  19. package/out/generator/prisma/query-gard-generator.js +159 -0
  20. package/out/generator/prisma/query-gard-generator.js.map +1 -0
  21. package/out/generator/prisma/schema-generator.js +202 -0
  22. package/out/generator/prisma/schema-generator.js.map +1 -0
  23. package/out/generator/query-guard/index.js +2 -0
  24. package/out/generator/query-guard/index.js.map +1 -0
  25. package/out/generator/react-hooks/index.js +1 -1
  26. package/out/generator/react-hooks/index.js.map +1 -1
  27. package/out/generator/server/data/expression-writer.js +42 -36
  28. package/out/generator/server/data/expression-writer.js.map +1 -1
  29. package/out/generator/server/data/plain-expression-builder.js +18 -2
  30. package/out/generator/server/data/plain-expression-builder.js.map +1 -1
  31. package/out/generator/service/index.js +51 -1
  32. package/out/generator/service/index.js.map +1 -1
  33. package/out/generator/tsconfig.template.json +17 -0
  34. package/out/utils/indent-string.js +3 -19
  35. package/out/utils/indent-string.js.map +1 -1
  36. package/package.json +6 -3
  37. package/src/cli/index.ts +5 -33
  38. package/src/generator/constants.ts +2 -0
  39. package/src/generator/index.ts +59 -0
  40. package/src/generator/next-auth/index.ts +3 -3
  41. package/src/generator/package.template.json +9 -0
  42. package/src/generator/{server/data → prisma}/expression-writer.ts +65 -63
  43. package/src/generator/prisma/index.ts +10 -309
  44. package/src/generator/{server/data → prisma}/plain-expression-builder.ts +22 -3
  45. package/src/generator/prisma/prisma-builder.ts +1 -1
  46. package/src/generator/prisma/query-gard-generator.ts +208 -0
  47. package/src/generator/prisma/schema-generator.ts +295 -0
  48. package/src/generator/react-hooks/index.ts +2 -4
  49. package/src/generator/service/index.ts +54 -1
  50. package/src/generator/tsconfig.template.json +17 -0
  51. package/src/utils/indent-string.ts +3 -38
  52. package/src/generator/server/data/data-generator.ts +0 -483
  53. package/src/generator/server/function/function-generator.ts +0 -32
  54. package/src/generator/server/index.ts +0 -57
  55. package/src/generator/server/server-code-generator.ts +0 -6
@@ -1,25 +1,9 @@
1
1
  "use strict";
2
2
  // https://github.com/sindresorhus/indent-string
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- function indentString(string, count = 4, options = {}) {
5
- const { indent = ' ', includeEmptyLines = false } = options;
6
- if (typeof string !== 'string') {
7
- throw new TypeError(`Expected \`input\` to be a \`string\`, got \`${typeof string}\``);
8
- }
9
- if (typeof count !== 'number') {
10
- throw new TypeError(`Expected \`count\` to be a \`number\`, got \`${typeof count}\``);
11
- }
12
- if (count < 0) {
13
- throw new RangeError(`Expected \`count\` to be at least 0, got \`${count}\``);
14
- }
15
- if (typeof indent !== 'string') {
16
- throw new TypeError(`Expected \`options.indent\` to be a \`string\`, got \`${typeof indent}\``);
17
- }
18
- if (count === 0) {
19
- return string;
20
- }
21
- const regex = includeEmptyLines ? /^/gm : /^(?!\s*$)/gm;
22
- return string.replace(regex, indent.repeat(count));
4
+ function indentString(string, count = 4) {
5
+ const indent = ' ';
6
+ return string.replace(/^(?!\s*$)/gm, indent.repeat(count));
23
7
  }
24
8
  exports.default = indentString;
25
9
  //# sourceMappingURL=indent-string.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"indent-string.js","sourceRoot":"","sources":["../../src/utils/indent-string.ts"],"names":[],"mappings":";AAAA,gDAAgD;;AAEhD,SAAwB,YAAY,CAChC,MAAc,EACd,KAAK,GAAG,CAAC,EACT,UAA4D,EAAE;IAE9D,MAAM,EAAE,MAAM,GAAG,GAAG,EAAE,iBAAiB,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IAE5D,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;QAC5B,MAAM,IAAI,SAAS,CACf,gDAAgD,OAAO,MAAM,IAAI,CACpE,CAAC;KACL;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QAC3B,MAAM,IAAI,SAAS,CACf,gDAAgD,OAAO,KAAK,IAAI,CACnE,CAAC;KACL;IAED,IAAI,KAAK,GAAG,CAAC,EAAE;QACX,MAAM,IAAI,UAAU,CAChB,8CAA8C,KAAK,IAAI,CAC1D,CAAC;KACL;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;QAC5B,MAAM,IAAI,SAAS,CACf,yDAAyD,OAAO,MAAM,IAAI,CAC7E,CAAC;KACL;IAED,IAAI,KAAK,KAAK,CAAC,EAAE;QACb,OAAO,MAAM,CAAC;KACjB;IAED,MAAM,KAAK,GAAG,iBAAiB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC;IAExD,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACvD,CAAC;AAtCD,+BAsCC"}
1
+ {"version":3,"file":"indent-string.js","sourceRoot":"","sources":["../../src/utils/indent-string.ts"],"names":[],"mappings":";AAAA,gDAAgD;;AAEhD,SAAwB,YAAY,CAAC,MAAc,EAAE,KAAK,GAAG,CAAC;IAC1D,MAAM,MAAM,GAAG,GAAG,CAAC;IACnB,OAAO,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAC/D,CAAC;AAHD,+BAGC"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "zenstack",
3
3
  "displayName": "ZenStack CLI and Language Tools",
4
4
  "description": "ZenStack CLI and Language Tools",
5
- "version": "0.1.0",
5
+ "version": "0.1.1",
6
6
  "engines": {
7
7
  "vscode": "^1.56.0"
8
8
  },
@@ -48,11 +48,13 @@
48
48
  },
49
49
  "main": "./out/extension.js",
50
50
  "dependencies": {
51
+ "@zenstackhq/runtime": "0.1.1",
51
52
  "change-case": "^4.1.2",
52
53
  "chevrotain": "^9.1.0",
53
54
  "colors": "^1.4.0",
54
55
  "commander": "^8.0.0",
55
56
  "langium": "^0.4.0",
57
+ "prisma": "^4.4.0",
56
58
  "promisify": "^0.0.3",
57
59
  "ts-morph": "^16.0.0",
58
60
  "vscode-jsonrpc": "^8.0.2",
@@ -82,13 +84,14 @@
82
84
  },
83
85
  "scripts": {
84
86
  "vscode:prepublish": "npm run build && npm run lint",
85
- "build": "tsc && tsc-alias && cp src/language-server/stdlib.zmodel ./out/language-server/",
87
+ "build": "tsc && tsc-alias && cp src/language-server/stdlib.zmodel ./out/language-server/ && cp src/generator/*.template.json ./out/generator/",
86
88
  "ts:watch": "tsc --watch",
87
89
  "tsc-alias:watch": "tsc-alias --watch",
88
90
  "lint": "eslint src --ext ts",
89
91
  "langium:generate": "langium generate",
90
92
  "langium:watch": "langium generate --watch",
91
93
  "watch": "concurrently --kill-others \"npm:langium:watch\" \"npm:ts:watch\" \"npm:tsc-alias:watch\"",
92
- "test": "jest"
94
+ "test": "jest",
95
+ "npm-publish": "pnpm build && pnpm publish --no-git-checks --access public"
93
96
  }
94
97
  }
package/src/cli/index.ts CHANGED
@@ -4,14 +4,7 @@ import { ZModelLanguageMetaData } from '../language-server/generated/module';
4
4
  import { createZModelServices } from '../language-server/zmodel-module';
5
5
  import { extractAstNode } from './cli-util';
6
6
  import { Context } from '../generator/types';
7
- import * as path from 'path';
8
- import * as fs from 'fs';
9
- import colors from 'colors';
10
- import PrismaGenerator from '../generator/prisma';
11
- import ServiceGenerator from '../generator/service';
12
- import ReactHooksGenerator from '../generator/react-hooks';
13
- import NextAuthGenerator from '../generator/next-auth';
14
- import ServerGenerator from '../generator/server';
7
+ import { ZenStackGenerator } from '../generator';
15
8
 
16
9
  export const generateAction = async (
17
10
  fileName: string,
@@ -22,34 +15,14 @@ export const generateAction = async (
22
15
 
23
16
  const context: Context = {
24
17
  schema: model,
25
- outDir: path.resolve(opts.destination),
18
+ outDir: opts.destination || 'node_modules/.zenstack',
26
19
  };
27
20
 
28
- if (!fs.existsSync(context.outDir)) {
29
- fs.mkdirSync(context.outDir);
30
- }
31
-
32
- console.log(colors.bold('⌛️ Running ZenStack generators'));
33
-
34
- const generators = [
35
- new PrismaGenerator(),
36
- new ServiceGenerator(),
37
- new ReactHooksGenerator(),
38
- new ServerGenerator(),
39
- new NextAuthGenerator(),
40
- ];
41
-
42
- for (const generator of generators) {
43
- await generator.generate(context);
44
- }
45
-
46
- console.log(
47
- colors.green(colors.bold('👻 All generators completed successfully!'))
48
- );
21
+ await new ZenStackGenerator().generate(context);
49
22
  };
50
23
 
51
24
  export type GenerateOptions = {
52
- destination: string;
25
+ destination?: string;
53
26
  };
54
27
 
55
28
  export default function (): void {
@@ -68,8 +41,7 @@ export default function (): void {
68
41
  )
69
42
  .option(
70
43
  '-d, --destination <dir>',
71
- 'destination directory of generating',
72
- '.zenstack'
44
+ 'destination directory of generating'
73
45
  )
74
46
  .description(
75
47
  'generates JavaScript code that prints "Hello, {name}!" for each greeting in a source file'
@@ -0,0 +1,2 @@
1
+ export const RUNTIME_PACKAGE = '@zenstackhq/runtime';
2
+ export const GUARD_FIELD_NAME = 'zenstack_guard';
@@ -0,0 +1,59 @@
1
+ import { Context } from './types';
2
+ import * as fs from 'fs';
3
+ import colors from 'colors';
4
+ import PrismaGenerator from './prisma';
5
+ import ServiceGenerator from './service';
6
+ import ReactHooksGenerator from './react-hooks';
7
+ import NextAuthGenerator from './next-auth';
8
+ import path from 'path';
9
+ import { execSync } from 'child_process';
10
+
11
+ export class ZenStackGenerator {
12
+ async generate(context: Context) {
13
+ if (!fs.existsSync(context.outDir)) {
14
+ fs.mkdirSync(context.outDir);
15
+ }
16
+
17
+ console.log(colors.bold('⌛️ Running ZenStack generators'));
18
+
19
+ const generators = [
20
+ new PrismaGenerator(),
21
+ new ServiceGenerator(),
22
+ new ReactHooksGenerator(),
23
+ new NextAuthGenerator(),
24
+ ];
25
+
26
+ for (const generator of generators) {
27
+ await generator.generate(context);
28
+ }
29
+
30
+ // generate package.json
31
+ const packageJson = require(path.join(
32
+ __dirname,
33
+ 'package.template.json'
34
+ ));
35
+ fs.writeFileSync(
36
+ path.join(context.outDir, 'package.json'),
37
+ JSON.stringify(packageJson, undefined, 4)
38
+ );
39
+
40
+ // compile ts sources
41
+ const tsConfig = require(path.join(
42
+ __dirname,
43
+ 'tsconfig.template.json'
44
+ ));
45
+ fs.writeFileSync(
46
+ path.join(context.outDir, 'tsconfig.json'),
47
+ JSON.stringify(tsConfig, undefined, 4)
48
+ );
49
+
50
+ execSync(`npx tsc -p "${path.join(context.outDir, 'tsconfig.json')}"`);
51
+ console.log(colors.blue(' ✔️ Typescript source files transpiled'));
52
+
53
+ console.log(
54
+ colors.green(
55
+ colors.bold('👻 All generators completed successfully!')
56
+ )
57
+ );
58
+ }
59
+ }
@@ -30,9 +30,9 @@ export default class NextAuthGenerator implements Generator {
30
30
 
31
31
  generateAdapter(project: Project, context: Context) {
32
32
  const content = `
33
- import { ZenStackService } from '../service';
33
+ import { ZenStackService } from '..';
34
34
  import { Adapter } from 'next-auth/adapters';
35
- import { Prisma } from '@zenstack/.prisma';
35
+ import { Prisma } from '../.prisma';
36
36
 
37
37
  export function NextAuthAdapter(service: ZenStackService): Adapter {
38
38
  const db = service.db;
@@ -101,7 +101,7 @@ export default class NextAuthGenerator implements Generator {
101
101
 
102
102
  generateAuthorize(project: Project, context: Context) {
103
103
  const content = `
104
- import { ZenStackService } from '../service';
104
+ import { ZenStackService } from '..';
105
105
  import { hash, compare } from 'bcryptjs';
106
106
 
107
107
  async function hashPassword(password: string) {
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": ".zenstack",
3
+ "version": "1.0.0",
4
+ "description": "ZenStack generated code",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "author": "ZenStack",
8
+ "license": "MIT"
9
+ }
@@ -6,14 +6,14 @@ import {
6
6
  isEnumField,
7
7
  isMemberAccessExpr,
8
8
  isReferenceExpr,
9
+ isThisExpr,
9
10
  LiteralExpr,
10
11
  MemberAccessExpr,
11
12
  ReferenceExpr,
12
- ThisExpr,
13
13
  UnaryExpr,
14
14
  } from '@lang/generated/ast';
15
15
  import { CodeBlockWriter } from 'ts-morph';
16
- import { GeneratorError } from '../../types';
16
+ import { GeneratorError } from '../types';
17
17
  import { TypedNode } from '@lang/types';
18
18
  import PlainExpressionBuilder from './plain-expression-builder';
19
19
 
@@ -49,20 +49,17 @@ export default class ExpressionWriter {
49
49
  this.writeMemberAccess(expr as MemberAccessExpr);
50
50
  break;
51
51
 
52
- case ThisExpr:
53
- throw new Error('Not implemented');
54
-
55
52
  default:
56
53
  throw new Error(`Not implemented: ${expr.$type}`);
57
54
  }
58
55
  };
59
56
 
60
- this.writer.block(_write);
57
+ this.block(_write);
61
58
  }
62
59
 
63
60
  private writeReference(expr: ReferenceExpr) {
64
61
  if (isEnumField(expr.target.ref)) {
65
- throw new Error('Not implemented');
62
+ throw new Error('We should never get here');
66
63
  } else {
67
64
  this.writer.write(`${expr.target.ref!.name}: true`);
68
65
  }
@@ -72,7 +69,7 @@ export default class ExpressionWriter {
72
69
  this.writeFieldCondition(
73
70
  expr.operand,
74
71
  () => {
75
- this.writer.block(() => {
72
+ this.block(() => {
76
73
  this.writer.write(`${expr.member.ref?.name}: true`);
77
74
  });
78
75
  },
@@ -125,12 +122,17 @@ export default class ExpressionWriter {
125
122
  );
126
123
  }
127
124
 
128
- private isFieldRef(expr: Expression): expr is ReferenceExpr {
125
+ private isFieldAccess(expr: Expression): boolean {
126
+ if (isThisExpr(expr)) {
127
+ return true;
128
+ }
129
+ if (isMemberAccessExpr(expr)) {
130
+ return this.isFieldAccess(expr.operand);
131
+ }
129
132
  if (isReferenceExpr(expr) && isDataModelField(expr.target.ref)) {
130
133
  return true;
131
- } else {
132
- return false;
133
134
  }
135
+ return false;
134
136
  }
135
137
 
136
138
  private guard(write: () => void) {
@@ -143,11 +145,8 @@ export default class ExpressionWriter {
143
145
  }
144
146
 
145
147
  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);
148
+ const leftIsFieldAccess = this.isFieldAccess(expr.left);
149
+ const rightIsFieldAccess = this.isFieldAccess(expr.right);
151
150
 
152
151
  if (leftIsFieldAccess && rightIsFieldAccess) {
153
152
  throw new GeneratorError(
@@ -177,27 +176,30 @@ export default class ExpressionWriter {
177
176
  operator = this.negateOperator(operator);
178
177
  }
179
178
 
180
- const type = (fieldAccess as TypedNode).$resolvedType?.decl;
181
-
182
179
  this.writeFieldCondition(
183
180
  fieldAccess,
184
181
  () => {
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(() => {
182
+ this.block(
183
+ () => {
184
+ if (this.isModelTyped(fieldAccess)) {
185
+ // comparing with an object, conver to "id" comparison instead
186
+ this.writer.write('id: ');
187
+ this.block(() => {
188
+ this.writeOperator(operator, () => {
189
+ this.plain(operand);
190
+ this.writer.write('?.id');
191
+ });
192
+ });
193
+ } else {
190
194
  this.writeOperator(operator, () => {
191
195
  this.plain(operand);
192
- this.writer.write('?.id');
193
196
  });
194
- });
195
- } else {
196
- this.writeOperator(operator, () => {
197
- this.plain(operand);
198
- });
199
- }
200
- });
197
+ }
198
+ },
199
+ // "this" expression is compiled away (to .id access), so we should
200
+ // avoid generating a new layer
201
+ !isThisExpr(fieldAccess)
202
+ );
201
203
  },
202
204
  'is'
203
205
  );
@@ -210,7 +212,7 @@ export default class ExpressionWriter {
210
212
  if (operator === '!=') {
211
213
  // wrap a 'not'
212
214
  this.writer.write('not: ');
213
- this.writer.block(() => {
215
+ this.block(() => {
214
216
  this.writeOperator('==', writeOperand);
215
217
  });
216
218
  } else {
@@ -227,7 +229,11 @@ export default class ExpressionWriter {
227
229
  let selector: string;
228
230
  let operand: Expression | undefined;
229
231
 
230
- if (isReferenceExpr(fieldAccess)) {
232
+ if (isThisExpr(fieldAccess)) {
233
+ // pass on
234
+ writeCondition();
235
+ return;
236
+ } else if (isReferenceExpr(fieldAccess)) {
231
237
  selector = fieldAccess.target.ref?.name!;
232
238
  } else if (isMemberAccessExpr(fieldAccess)) {
233
239
  selector = fieldAccess.member.ref?.name!;
@@ -243,26 +249,31 @@ export default class ExpressionWriter {
243
249
  this.writeFieldCondition(
244
250
  operand,
245
251
  () => {
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
+ this.block(
253
+ () => {
254
+ this.writer.write(selector + ': ');
255
+ if (this.isModelTyped(fieldAccess)) {
256
+ // expression is resolved to a model, generate relation query
257
+ this.block(() => {
258
+ this.writer.write(`${relationOp}: `);
259
+ writeCondition();
260
+ });
261
+ } else {
262
+ // generate plain query
252
263
  writeCondition();
253
- });
254
- } else {
255
- // generate plain query
256
- writeCondition();
257
- }
258
- });
264
+ }
265
+ },
266
+ // if operand is "this", it doesn't really generate a new layer of query,
267
+ // so we should avoid generating a new block
268
+ !isThisExpr(operand)
269
+ );
259
270
  },
260
271
  'is'
261
272
  );
262
273
  } else if (this.isModelTyped(fieldAccess)) {
263
274
  // reference resolved to a model, generate relation query
264
275
  this.writer.write(selector + ': ');
265
- this.writer.block(() => {
276
+ this.block(() => {
266
277
  this.writer.write(`${relationOp}: `);
267
278
  writeCondition();
268
279
  });
@@ -273,25 +284,16 @@ export default class ExpressionWriter {
273
284
  }
274
285
  }
275
286
 
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;
287
+ private block(write: () => void, condition = true) {
288
+ if (condition) {
289
+ this.writer.block(write);
290
+ } else {
291
+ write();
292
292
  }
293
+ }
293
294
 
294
- return false;
295
+ private isModelTyped(expr: Expression) {
296
+ return isDataModel((expr as TypedNode).$resolvedType?.decl);
295
297
  }
296
298
 
297
299
  mapOperator(operator: '==' | '!=' | '>' | '>=' | '<' | '<=') {