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.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/bin/cli +3 -0
  3. package/out/cli/cli-util.js +64 -0
  4. package/out/cli/cli-util.js.map +1 -0
  5. package/out/cli/generator.js +1 -0
  6. package/out/cli/generator.js.map +1 -0
  7. package/out/cli/index.js +90 -0
  8. package/out/cli/index.js.map +1 -0
  9. package/out/extension.js +81 -0
  10. package/out/extension.js.map +1 -0
  11. package/out/generator/data-server/index.js +1 -0
  12. package/out/generator/data-server/index.js.map +1 -0
  13. package/out/generator/next-auth/index.js +196 -0
  14. package/out/generator/next-auth/index.js.map +1 -0
  15. package/out/generator/prisma/index.js +212 -0
  16. package/out/generator/prisma/index.js.map +1 -0
  17. package/out/generator/prisma/prisma-builder.js +307 -0
  18. package/out/generator/prisma/prisma-builder.js.map +1 -0
  19. package/out/generator/react-hooks/index.js +258 -0
  20. package/out/generator/react-hooks/index.js.map +1 -0
  21. package/out/generator/server/data/data-generator.js +376 -0
  22. package/out/generator/server/data/data-generator.js.map +1 -0
  23. package/out/generator/server/data/expression-writer.js +281 -0
  24. package/out/generator/server/data/expression-writer.js.map +1 -0
  25. package/out/generator/server/data/plain-expression-builder.js +53 -0
  26. package/out/generator/server/data/plain-expression-builder.js.map +1 -0
  27. package/out/generator/server/data-generator.js +82 -0
  28. package/out/generator/server/data-generator.js.map +1 -0
  29. package/out/generator/server/expression-writer.js +1 -0
  30. package/out/generator/server/expression-writer.js.map +1 -0
  31. package/out/generator/server/function/function-generator.js +50 -0
  32. package/out/generator/server/function/function-generator.js.map +1 -0
  33. package/out/generator/server/function-generator.js +13 -0
  34. package/out/generator/server/function-generator.js.map +1 -0
  35. package/out/generator/server/index.js +88 -0
  36. package/out/generator/server/index.js.map +1 -0
  37. package/out/generator/server/js-expression-builder.js +1 -0
  38. package/out/generator/server/js-expression-builder.js.map +1 -0
  39. package/out/generator/server/plain-expression-builder.js +1 -0
  40. package/out/generator/server/plain-expression-builder.js.map +1 -0
  41. package/out/generator/server/server-code-generator.js +3 -0
  42. package/out/generator/server/server-code-generator.js.map +1 -0
  43. package/out/generator/server/server-code-writer.js +1 -0
  44. package/out/generator/server/server-code-writer.js.map +1 -0
  45. package/out/generator/service/index.js +72 -0
  46. package/out/generator/service/index.js.map +1 -0
  47. package/out/generator/types.js +10 -0
  48. package/out/generator/types.js.map +1 -0
  49. package/out/generator/utils.js +10 -0
  50. package/out/generator/utils.js.map +1 -0
  51. package/out/language-server/generated/ast.js +386 -0
  52. package/out/language-server/generated/ast.js.map +1 -0
  53. package/out/language-server/generated/grammar.js +2193 -0
  54. package/out/language-server/generated/grammar.js.map +1 -0
  55. package/out/language-server/generated/module.js +23 -0
  56. package/out/language-server/generated/module.js.map +1 -0
  57. package/out/language-server/main.js +12 -0
  58. package/out/language-server/main.js.map +1 -0
  59. package/out/language-server/stdlib.zmodel +21 -0
  60. package/out/language-server/types.js +3 -0
  61. package/out/language-server/types.js.map +1 -0
  62. package/out/language-server/zmodel-index.js +38 -0
  63. package/out/language-server/zmodel-index.js.map +1 -0
  64. package/out/language-server/zmodel-linker.js +239 -0
  65. package/out/language-server/zmodel-linker.js.map +1 -0
  66. package/out/language-server/zmodel-module.js +51 -0
  67. package/out/language-server/zmodel-module.js.map +1 -0
  68. package/out/language-server/zmodel-scope.js +30 -0
  69. package/out/language-server/zmodel-scope.js.map +1 -0
  70. package/out/language-server/zmodel-validator.js +25 -0
  71. package/out/language-server/zmodel-validator.js.map +1 -0
  72. package/out/utils/indent-string.js +25 -0
  73. package/out/utils/indent-string.js.map +1 -0
  74. package/package.json +94 -0
  75. package/src/cli/cli-util.ts +80 -0
  76. package/src/cli/index.ts +80 -0
  77. package/src/extension.ts +76 -0
  78. package/src/generator/next-auth/index.ts +183 -0
  79. package/src/generator/prisma/index.ts +323 -0
  80. package/src/generator/prisma/prisma-builder.ts +366 -0
  81. package/src/generator/react-hooks/index.ts +267 -0
  82. package/src/generator/server/data/data-generator.ts +483 -0
  83. package/src/generator/server/data/expression-writer.ts +350 -0
  84. package/src/generator/server/data/plain-expression-builder.ts +72 -0
  85. package/src/generator/server/function/function-generator.ts +32 -0
  86. package/src/generator/server/index.ts +57 -0
  87. package/src/generator/server/server-code-generator.ts +6 -0
  88. package/src/generator/service/index.ts +43 -0
  89. package/src/generator/types.ts +16 -0
  90. package/src/generator/utils.ts +9 -0
  91. package/src/language-server/generated/ast.ts +603 -0
  92. package/src/language-server/generated/grammar.ts +2190 -0
  93. package/src/language-server/generated/module.ts +24 -0
  94. package/src/language-server/main.ts +12 -0
  95. package/src/language-server/stdlib.zmodel +21 -0
  96. package/src/language-server/types.ts +9 -0
  97. package/src/language-server/zmodel-index.ts +33 -0
  98. package/src/language-server/zmodel-linker.ts +407 -0
  99. package/src/language-server/zmodel-module.ts +90 -0
  100. package/src/language-server/zmodel-scope.ts +21 -0
  101. package/src/language-server/zmodel-validator.ts +35 -0
  102. package/src/language-server/zmodel.langium +186 -0
  103. package/src/utils/indent-string.ts +41 -0
@@ -0,0 +1,80 @@
1
+ import colors from 'colors';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { AstNode, LangiumDocument, LangiumServices } from 'langium';
5
+ import { URI } from 'vscode-uri';
6
+
7
+ export async function extractDocument(
8
+ fileName: string,
9
+ services: LangiumServices
10
+ ): Promise<LangiumDocument> {
11
+ const extensions = services.LanguageMetaData.fileExtensions;
12
+ if (!extensions.includes(path.extname(fileName))) {
13
+ console.error(
14
+ colors.yellow(
15
+ `Please choose a file with one of these extensions: ${extensions}.`
16
+ )
17
+ );
18
+ process.exit(1);
19
+ }
20
+
21
+ if (!fs.existsSync(fileName)) {
22
+ console.error(colors.red(`File ${fileName} does not exist.`));
23
+ process.exit(1);
24
+ }
25
+
26
+ const stdLib =
27
+ services.shared.workspace.LangiumDocuments.getOrCreateDocument(
28
+ URI.file(path.join(__dirname, '../language-server/stdlib.zmodel'))
29
+ );
30
+ const document =
31
+ services.shared.workspace.LangiumDocuments.getOrCreateDocument(
32
+ URI.file(path.resolve(fileName))
33
+ );
34
+ await services.shared.workspace.DocumentBuilder.build([stdLib, document], {
35
+ validationChecks: 'all',
36
+ });
37
+
38
+ const validationErrors = (document.diagnostics ?? []).filter(
39
+ (e) => e.severity === 1
40
+ );
41
+ if (validationErrors.length > 0) {
42
+ console.error(colors.red('There are validation errors:'));
43
+ for (const validationError of validationErrors) {
44
+ console.error(
45
+ colors.red(
46
+ `line ${validationError.range.start.line + 1}: ${
47
+ validationError.message
48
+ } [${document.textDocument.getText(validationError.range)}]`
49
+ )
50
+ );
51
+ }
52
+ process.exit(1);
53
+ }
54
+
55
+ return document;
56
+ }
57
+
58
+ export async function extractAstNode<T extends AstNode>(
59
+ fileName: string,
60
+ services: LangiumServices
61
+ ): Promise<T> {
62
+ return (await extractDocument(fileName, services)).parseResult?.value as T;
63
+ }
64
+
65
+ interface FilePathData {
66
+ destination: string;
67
+ name: string;
68
+ }
69
+
70
+ export function extractDestinationAndName(
71
+ filePath: string,
72
+ destination: string | undefined
73
+ ): FilePathData {
74
+ filePath = filePath.replace(/\..*$/, '').replace(/[.-]/g, '');
75
+ return {
76
+ destination:
77
+ destination ?? path.join(path.dirname(filePath), 'generated'),
78
+ name: path.basename(filePath),
79
+ };
80
+ }
@@ -0,0 +1,80 @@
1
+ import { Command } from 'commander';
2
+ import { Model } from '../language-server/generated/ast';
3
+ import { ZModelLanguageMetaData } from '../language-server/generated/module';
4
+ import { createZModelServices } from '../language-server/zmodel-module';
5
+ import { extractAstNode } from './cli-util';
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';
15
+
16
+ export const generateAction = async (
17
+ fileName: string,
18
+ opts: GenerateOptions
19
+ ): Promise<void> => {
20
+ const services = createZModelServices().ZModel;
21
+ const model = await extractAstNode<Model>(fileName, services);
22
+
23
+ const context: Context = {
24
+ schema: model,
25
+ outDir: path.resolve(opts.destination),
26
+ };
27
+
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
+ );
49
+ };
50
+
51
+ export type GenerateOptions = {
52
+ destination: string;
53
+ };
54
+
55
+ export default function (): void {
56
+ const program = new Command();
57
+
58
+ program
59
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
60
+ .version(require('../../package.json').version);
61
+
62
+ const fileExtensions = ZModelLanguageMetaData.fileExtensions.join(', ');
63
+ program
64
+ .command('generate')
65
+ .argument(
66
+ '<file>',
67
+ `source file (possible file extensions: ${fileExtensions})`
68
+ )
69
+ .option(
70
+ '-d, --destination <dir>',
71
+ 'destination directory of generating',
72
+ '.zenstack'
73
+ )
74
+ .description(
75
+ 'generates JavaScript code that prints "Hello, {name}!" for each greeting in a source file'
76
+ )
77
+ .action(generateAction);
78
+
79
+ program.parse(process.argv);
80
+ }
@@ -0,0 +1,76 @@
1
+ import * as vscode from 'vscode';
2
+ import * as path from 'path';
3
+ import {
4
+ LanguageClient,
5
+ LanguageClientOptions,
6
+ ServerOptions,
7
+ TransportKind,
8
+ } from 'vscode-languageclient/node';
9
+
10
+ let client: LanguageClient;
11
+
12
+ // This function is called when the extension is activated.
13
+ export function activate(context: vscode.ExtensionContext): void {
14
+ client = startLanguageClient(context);
15
+ }
16
+
17
+ // This function is called when the extension is deactivated.
18
+ export function deactivate(): Thenable<void> | undefined {
19
+ if (client) {
20
+ return client.stop();
21
+ }
22
+ return undefined;
23
+ }
24
+
25
+ function startLanguageClient(context: vscode.ExtensionContext): LanguageClient {
26
+ const serverModule = context.asAbsolutePath(
27
+ path.join('out', 'language-server', 'main')
28
+ );
29
+ // The debug options for the server
30
+ // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging.
31
+ // By setting `process.env.DEBUG_BREAK` to a truthy value, the language server will wait until a debugger is attached.
32
+ const debugOptions = {
33
+ execArgv: [
34
+ '--nolazy',
35
+ `--inspect${process.env.DEBUG_BREAK ? '-brk' : ''}=${
36
+ process.env.DEBUG_SOCKET || '6009'
37
+ }`,
38
+ ],
39
+ };
40
+
41
+ // If the extension is launched in debug mode then the debug server options are used
42
+ // Otherwise the run options are used
43
+ const serverOptions: ServerOptions = {
44
+ run: { module: serverModule, transport: TransportKind.ipc },
45
+ debug: {
46
+ module: serverModule,
47
+ transport: TransportKind.ipc,
48
+ options: debugOptions,
49
+ },
50
+ };
51
+
52
+ const fileSystemWatcher =
53
+ vscode.workspace.createFileSystemWatcher('**/*.zmodel');
54
+ context.subscriptions.push(fileSystemWatcher);
55
+
56
+ // Options to control the language client
57
+ const clientOptions: LanguageClientOptions = {
58
+ documentSelector: [{ scheme: 'file', language: 'zmodel' }],
59
+ synchronize: {
60
+ // Notify the server about file changes to files contained in the workspace
61
+ fileEvents: fileSystemWatcher,
62
+ },
63
+ };
64
+
65
+ // Create the language client and start the client.
66
+ const client = new LanguageClient(
67
+ 'zmodel',
68
+ 'ZenStack Model',
69
+ serverOptions,
70
+ clientOptions
71
+ );
72
+
73
+ // Start the client. This will also launch the server
74
+ client.start();
75
+ return client;
76
+ }
@@ -0,0 +1,183 @@
1
+ import { Context, Generator } from '../types';
2
+ import { Project } from 'ts-morph';
3
+ import * as path from 'path';
4
+
5
+ export default class NextAuthGenerator implements Generator {
6
+ async generate(context: Context) {
7
+ const project = new Project();
8
+
9
+ this.generateIndex(project, context);
10
+ this.generateAdapter(project, context);
11
+ this.generateAuthorize(project, context);
12
+
13
+ await project.save();
14
+ }
15
+
16
+ generateIndex(project: Project, context: Context) {
17
+ const sf = project.createSourceFile(
18
+ path.join(context.outDir, 'auth/index.ts'),
19
+ undefined,
20
+ { overwrite: true }
21
+ );
22
+
23
+ sf.addStatements([
24
+ `export * from './next-auth-adapter';`,
25
+ `export * from './authorize';`,
26
+ ]);
27
+
28
+ sf.formatText();
29
+ }
30
+
31
+ generateAdapter(project: Project, context: Context) {
32
+ const content = `
33
+ import { ZenStackService } from '../service';
34
+ import { Adapter } from 'next-auth/adapters';
35
+ import { Prisma } from '@zenstack/.prisma';
36
+
37
+ export function NextAuthAdapter(service: ZenStackService): Adapter {
38
+ const db = service.db;
39
+ return {
40
+ createUser: (data) => db.user.create({ data: data as Prisma.UserCreateInput }),
41
+ getUser: (id) => db.user.findUnique({ where: { id } }),
42
+ getUserByEmail: (email) => db.user.findUnique({ where: { email } }),
43
+ async getUserByAccount(provider_providerAccountId) {
44
+ const account = await db.account.findUnique({
45
+ where: { provider_providerAccountId },
46
+ select: { user: true },
47
+ });
48
+ return account?.user ?? null;
49
+ },
50
+ updateUser: (data) => db.user.update({ where: { id: data.id }, data: data as Prisma.UserUpdateInput }),
51
+ deleteUser: (id) => db.user.delete({ where: { id } }),
52
+ linkAccount: (data) => db.account.create({ data }) as any,
53
+ unlinkAccount: (provider_providerAccountId) =>
54
+ db.account.delete({ where: { provider_providerAccountId } }) as any,
55
+ async getSessionAndUser(sessionToken) {
56
+ const userAndSession = await db.session.findUnique({
57
+ where: { sessionToken },
58
+ include: { user: true },
59
+ });
60
+ if (!userAndSession) return null;
61
+ const { user, ...session } = userAndSession;
62
+ return { user, session };
63
+ },
64
+ createSession: (data) => db.session.create({ data }),
65
+ updateSession: (data) =>
66
+ db.session.update({
67
+ data,
68
+ where: { sessionToken: data.sessionToken },
69
+ }),
70
+ deleteSession: (sessionToken) =>
71
+ db.session.delete({ where: { sessionToken } }),
72
+ createVerificationToken: (data) => db.verificationToken.create({ data }),
73
+ async useVerificationToken(identifier_token) {
74
+ try {
75
+ return await db.verificationToken.delete({
76
+ where: { identifier_token },
77
+ });
78
+ } catch (error) {
79
+ // If token already used/deleted, just return null
80
+ // https://www.prisma.io/docs/reference/api-reference/error-reference#p2025
81
+ if (
82
+ (error as Prisma.PrismaClientKnownRequestError).code ===
83
+ 'P2025'
84
+ )
85
+ return null;
86
+ throw error;
87
+ }
88
+ },
89
+ };
90
+ }
91
+ `;
92
+
93
+ const sf = project.createSourceFile(
94
+ path.join(context.outDir, 'auth/next-auth-adapter.ts'),
95
+ content,
96
+ { overwrite: true }
97
+ );
98
+
99
+ sf.formatText();
100
+ }
101
+
102
+ generateAuthorize(project: Project, context: Context) {
103
+ const content = `
104
+ import { ZenStackService } from '../service';
105
+ import { hash, compare } from 'bcryptjs';
106
+
107
+ async function hashPassword(password: string) {
108
+ const hashedPassword = await hash(password, 12);
109
+ return hashedPassword;
110
+ }
111
+
112
+ async function verifyPassword(password: string, hashedPassword: string) {
113
+ const isValid = await compare(password, hashedPassword);
114
+ return isValid;
115
+ }
116
+
117
+ export function authorize(service: ZenStackService) {
118
+ return async (
119
+ credentials: Record<'email' | 'password', string> | undefined
120
+ ) => {
121
+ try {
122
+ let maybeUser = await service.db.user.findFirst({
123
+ where: {
124
+ email: credentials!.email,
125
+ },
126
+ select: {
127
+ id: true,
128
+ email: true,
129
+ password: true,
130
+ name: true,
131
+ },
132
+ });
133
+
134
+ if (!maybeUser) {
135
+ if (!credentials!.password || !credentials!.email) {
136
+ throw new Error('Invalid Credentials');
137
+ }
138
+
139
+ maybeUser = await service.db.user.create({
140
+ data: {
141
+ email: credentials!.email,
142
+ password: await hashPassword(credentials!.password),
143
+ },
144
+ select: {
145
+ id: true,
146
+ email: true,
147
+ password: true,
148
+ name: true,
149
+ },
150
+ });
151
+ } else {
152
+ const isValid = await verifyPassword(
153
+ credentials!.password,
154
+ maybeUser.password
155
+ );
156
+
157
+ if (!isValid) {
158
+ throw new Error('Invalid Credentials');
159
+ }
160
+ }
161
+
162
+ return {
163
+ id: maybeUser.id,
164
+ email: maybeUser.email,
165
+ name: maybeUser.name,
166
+ };
167
+ } catch (error) {
168
+ console.log(error);
169
+ throw error;
170
+ }
171
+ };
172
+ }
173
+ `;
174
+
175
+ const sf = project.createSourceFile(
176
+ path.join(context.outDir, 'auth/authorize.ts'),
177
+ content,
178
+ { overwrite: true }
179
+ );
180
+
181
+ sf.formatText();
182
+ }
183
+ }