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
package/src/cli/index.ts DELETED
@@ -1,246 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { paramCase } from 'change-case';
3
- import colors from 'colors';
4
- import { Command, Option } from 'commander';
5
- import path from 'path';
6
- import { PackageManagers } from '../utils/pkg-utils';
7
- import { ZModelLanguageMetaData } from '../language-server/generated/module';
8
- import telemetry from '../telemetry';
9
- import { execSync } from '../utils/exec-utils';
10
- import { CliError } from './cli-error';
11
- import { initProject, runGenerator } from './cli-util';
12
-
13
- export const initAction = async (
14
- projectPath: string,
15
- options: {
16
- packageManager: PackageManagers | undefined;
17
- }
18
- ): Promise<void> => {
19
- await telemetry.trackSpan(
20
- 'cli:command:start',
21
- 'cli:command:complete',
22
- 'cli:command:error',
23
- { command: 'init' },
24
- () => initProject(projectPath, options.packageManager)
25
- );
26
- };
27
-
28
- export const generateAction = async (options: {
29
- schema: string;
30
- packageManager: PackageManagers | undefined;
31
- }): Promise<void> => {
32
- await telemetry.trackSpan(
33
- 'cli:command:start',
34
- 'cli:command:complete',
35
- 'cli:command:error',
36
- { command: 'generate' },
37
- () => runGenerator(options)
38
- );
39
- };
40
-
41
- function prismaAction(prismaCmd: string): (...args: any[]) => Promise<void> {
42
- return async (options: any, command: Command) => {
43
- await telemetry.trackSpan(
44
- 'cli:command:start',
45
- 'cli:command:complete',
46
- 'cli:command:error',
47
- {
48
- command: prismaCmd
49
- ? prismaCmd + ' ' + command.name()
50
- : command.name(),
51
- },
52
- async () => {
53
- const optStr = Array.from(Object.entries<any>(options))
54
- .map(([k, v]) => {
55
- let optVal = v;
56
- if (k === 'schema') {
57
- optVal = path.join(
58
- path.dirname(v),
59
- 'schema.prisma'
60
- );
61
- }
62
- return (
63
- '--' +
64
- paramCase(k) +
65
- (typeof optVal === 'string' ? ` ${optVal}` : '')
66
- );
67
- })
68
- .join(' ');
69
-
70
- // regenerate prisma schema first
71
- await runGenerator(options, ['prisma'], false);
72
-
73
- const prismaExec = `npx prisma ${prismaCmd} ${command.name()} ${optStr}`;
74
- console.log(prismaExec);
75
- try {
76
- execSync(prismaExec);
77
- } catch {
78
- telemetry.track('cli:command:error', {
79
- command: prismaCmd,
80
- });
81
- console.error(
82
- colors.red(
83
- 'Prisma command failed to execute. See errors above.'
84
- )
85
- );
86
- throw new CliError('prisma command run error');
87
- }
88
- }
89
- );
90
- };
91
- }
92
-
93
- export default async function (): Promise<void> {
94
- await telemetry.trackSpan(
95
- 'cli:start',
96
- 'cli:complete',
97
- 'cli:error',
98
- { args: process.argv },
99
- async () => {
100
- const program = new Command('zenstack');
101
-
102
- program.version(
103
- // eslint-disable-next-line @typescript-eslint/no-var-requires
104
- require('../../package.json').version,
105
- '-v --version',
106
- 'display CLI version'
107
- );
108
-
109
- const schemaExtensions =
110
- ZModelLanguageMetaData.fileExtensions.join(', ');
111
-
112
- program
113
- .description(
114
- `${colors.bold.blue(
115
- 'ζ'
116
- )} ZenStack is a toolkit for building secure CRUD apps with Next.js + Typescript.\n\nDocumentation: https://zenstack.dev.`
117
- )
118
- .showHelpAfterError()
119
- .showSuggestionAfterError();
120
-
121
- const schemaOption = new Option(
122
- '--schema <file>',
123
- `schema file (with extension ${schemaExtensions})`
124
- ).default('./zenstack/schema.zmodel');
125
-
126
- const pmOption = new Option(
127
- '-p, --package-manager <pm>',
128
- 'package manager to use'
129
- ).choices(['npm', 'yarn', 'pnpm']);
130
-
131
- //#region wraps Prisma commands
132
-
133
- program
134
- .command('init')
135
- .description('Set up a new ZenStack project.')
136
- .addOption(pmOption)
137
- .argument('[path]', 'project path', '.')
138
- .action(initAction);
139
-
140
- program
141
- .command('generate')
142
- .description(
143
- 'Generates RESTful API and Typescript client for your data model.'
144
- )
145
- .addOption(schemaOption)
146
- .addOption(pmOption)
147
- .action(generateAction);
148
-
149
- const migrate = program
150
- .command('migrate')
151
- .description(
152
- `Updates the database schema with migrations\nAlias for ${colors.cyan(
153
- 'prisma migrate'
154
- )}.`
155
- );
156
-
157
- migrate
158
- .command('dev')
159
- .description(
160
- `Creates a migration, apply it to the database, generate db client\nAlias for ${colors.cyan(
161
- 'prisma migrate dev'
162
- )}.`
163
- )
164
- .addOption(schemaOption)
165
- .option(
166
- '--create-only',
167
- 'Create a migration without applying it'
168
- )
169
- .option('-n --name <name>', 'Name the migration')
170
- .option('--skip-seed', 'Skip triggering seed')
171
- .action(prismaAction('migrate'));
172
-
173
- migrate
174
- .command('reset')
175
- .description(
176
- `Resets your database and apply all migrations\nAlias for ${colors.cyan(
177
- 'prisma migrate reset'
178
- )}.`
179
- )
180
- .addOption(schemaOption)
181
- .option('--force', 'Skip the confirmation prompt')
182
- .action(prismaAction('migrate'));
183
-
184
- migrate
185
- .command('deploy')
186
- .description(
187
- `Applies pending migrations to the database in production/staging\nAlias for ${colors.cyan(
188
- 'prisma migrate deploy'
189
- )}.`
190
- )
191
- .addOption(schemaOption)
192
- .action(prismaAction('migrate'));
193
-
194
- migrate
195
- .command('status')
196
- .description(
197
- `Checks the status of migrations in the production/staging database\nAlias for ${colors.cyan(
198
- 'prisma migrate status'
199
- )}.`
200
- )
201
- .addOption(schemaOption)
202
- .action(prismaAction('migrate'));
203
-
204
- const db = program
205
- .command('db')
206
- .description(
207
- `Manages your database schema and lifecycle during development\nAlias for ${colors.cyan(
208
- 'prisma db'
209
- )}.`
210
- );
211
-
212
- db.command('push')
213
- .description(
214
- `Pushes the Prisma schema state to the database\nAlias for ${colors.cyan(
215
- 'prisma db push'
216
- )}.`
217
- )
218
- .addOption(schemaOption)
219
- .option('--accept-data-loss', 'Ignore data loss warnings')
220
- .action(prismaAction('db'));
221
-
222
- program
223
- .command('studio')
224
- .description(
225
- `Browses your data with Prisma Studio\nAlias for ${colors.cyan(
226
- 'prisma studio'
227
- )}.`
228
- )
229
- .addOption(schemaOption)
230
- .option('-p --port <port>', 'Port to start Studio in')
231
- .option('-b --browser <browser>', 'Browser to open Studio in')
232
- .option(
233
- '-n --hostname',
234
- 'Hostname to bind the Express server to'
235
- )
236
- .action(prismaAction(''));
237
-
238
- //#endregion
239
-
240
- // handle errors explicitly to ensure telemetry
241
- program.exitOverride();
242
-
243
- await program.parseAsync(process.argv);
244
- }
245
- );
246
- }
package/src/extension.ts DELETED
@@ -1,76 +0,0 @@
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('bundle', '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
- }
@@ -1,18 +0,0 @@
1
- import { DataModel, isDataModel, Model } from '@lang/generated/ast';
2
- import { AstNode, Reference } from 'langium';
3
- import { GeneratorError } from './types';
4
-
5
- export function extractDataModelsWithAllowRules(model: Model): DataModel[] {
6
- return model.declarations.filter(
7
- (d) =>
8
- isDataModel(d) &&
9
- !!d.attributes.find((attr) => attr.decl.ref?.name === '@@allow')
10
- ) as DataModel[];
11
- }
12
-
13
- export function resolved<T extends AstNode>(ref: Reference<T>): T {
14
- if (!ref.ref) {
15
- throw new GeneratorError(`Reference not resolved: ${ref.$refText}`);
16
- }
17
- return ref.ref;
18
- }
@@ -1,6 +0,0 @@
1
- export const RUNTIME_PACKAGE = '@zenstackhq/runtime';
2
- export const GUARD_FIELD_NAME = 'zenstack_guard';
3
- export const TRANSACTION_FIELD_NAME = 'zenstack_transaction';
4
- export const API_ROUTE_NAME = 'zenstack';
5
- export const GENERATED_CODE_PATH = 'node_modules/.zenstack';
6
- export const UNKNOWN_USER_ID = 'zenstack_unknown_user';
@@ -1,304 +0,0 @@
1
- import {
2
- DataModel,
3
- DataModelField,
4
- DataModelFieldAttribute,
5
- isDataModel,
6
- isLiteralExpr,
7
- LiteralExpr,
8
- } from '@lang/generated/ast';
9
- import * as path from 'path';
10
- import { Project, SourceFile } from 'ts-morph';
11
- import { Context, Generator } from '../types';
12
-
13
- /**
14
- * Generates field constraint validators (run on both client and server side)
15
- */
16
- export default class FieldConstraintGenerator implements Generator {
17
- get name() {
18
- return 'field-constraint';
19
- }
20
-
21
- get startMessage() {
22
- return 'Generating field constraints...';
23
- }
24
-
25
- get successMessage() {
26
- return 'Successfully generated field constraints';
27
- }
28
-
29
- async generate(context: Context) {
30
- const project = new Project();
31
- const sf = project.createSourceFile(
32
- path.join(
33
- context.generatedCodeDir,
34
- 'src/field-constraint/index.ts'
35
- ),
36
- undefined,
37
- { overwrite: true }
38
- );
39
-
40
- sf.addStatements([`import { z } from "zod";`]);
41
-
42
- context.schema.declarations
43
- .filter((d): d is DataModel => isDataModel(d))
44
- .forEach((model) => {
45
- this.generateConstraints(sf, model);
46
- });
47
-
48
- sf.formatText();
49
- await project.save();
50
-
51
- return [];
52
- }
53
-
54
- private generateConstraints(sf: SourceFile, model: DataModel) {
55
- sf.addStatements(`
56
- export const ${this.validator(
57
- model.name,
58
- 'create'
59
- )}: z.ZodType = z.lazy(() => z.object({
60
- ${model.fields
61
- .map((f) => ({
62
- field: f,
63
- schema: this.makeFieldValidator(f, 'create'),
64
- }))
65
- .filter(({ schema }) => !!schema)
66
- .map(({ field, schema }) => field.name + ': ' + schema)
67
- .join(',\n')}
68
- }));
69
-
70
- export const ${this.validator(
71
- model.name,
72
- 'update'
73
- )}: z.ZodType = z.lazy(() => z.object({
74
- ${model.fields
75
- .map((f) => ({
76
- field: f,
77
- schema: this.makeFieldValidator(f, 'update'),
78
- }))
79
- .filter(({ schema }) => !!schema)
80
- .map(({ field, schema }) => field.name + ': ' + schema)
81
- .join(',\n')}
82
- }).partial());
83
- `);
84
- }
85
-
86
- private makeFieldValidator(
87
- field: DataModelField,
88
- mode: 'create' | 'update'
89
- ) {
90
- const baseSchema = this.makeZodSchema(field, mode);
91
- let zodSchema = baseSchema;
92
-
93
- // translate field constraint attributes to zod schema
94
- for (const attr of field.attributes) {
95
- switch (attr.decl.ref?.name) {
96
- case '@length': {
97
- const min = this.getAttrLiteralArg<number>(attr, 'min');
98
- if (min) {
99
- zodSchema += `.min(${min})`;
100
- }
101
- const max = this.getAttrLiteralArg<number>(attr, 'max');
102
- if (max) {
103
- zodSchema += `.max(${max})`;
104
- }
105
- break;
106
- }
107
- case '@regex': {
108
- const expr = this.getAttrLiteralArg<string>(attr, 'regex');
109
- if (expr) {
110
- zodSchema += `.regex(/${expr}/)`;
111
- }
112
- break;
113
- }
114
- case '@startsWith': {
115
- const text = this.getAttrLiteralArg<string>(attr, 'text');
116
- if (text) {
117
- zodSchema += `.startsWith(${JSON.stringify(text)})`;
118
- }
119
- break;
120
- }
121
- case '@endsWith': {
122
- const text = this.getAttrLiteralArg<string>(attr, 'text');
123
- if (text) {
124
- zodSchema += `.endsWith(${JSON.stringify(text)})`;
125
- }
126
- break;
127
- }
128
- case '@email': {
129
- zodSchema += `.email()`;
130
- break;
131
- }
132
- case '@url': {
133
- zodSchema += `.url()`;
134
- break;
135
- }
136
- case '@datetime': {
137
- zodSchema += `.datetime({ offset: true })`;
138
- break;
139
- }
140
- case '@gt': {
141
- const value = this.getAttrLiteralArg<number>(attr, 'value');
142
- if (value !== undefined) {
143
- zodSchema += `.gt(${value})`;
144
- }
145
- break;
146
- }
147
- case '@gte': {
148
- const value = this.getAttrLiteralArg<number>(attr, 'value');
149
- if (value !== undefined) {
150
- zodSchema += `.gte(${value})`;
151
- }
152
- break;
153
- }
154
- case '@lt': {
155
- const value = this.getAttrLiteralArg<number>(attr, 'value');
156
- if (value !== undefined) {
157
- zodSchema += `.lt(${value})`;
158
- }
159
- break;
160
- }
161
- case '@lte': {
162
- const value = this.getAttrLiteralArg<number>(attr, 'value');
163
- if (value !== undefined) {
164
- zodSchema += `.lte(${value})`;
165
- }
166
- break;
167
- }
168
- }
169
- }
170
-
171
- if (
172
- !isDataModel(field.type.reference?.ref) &&
173
- zodSchema === baseSchema
174
- ) {
175
- // empty schema, skip
176
- return undefined;
177
- }
178
-
179
- if (field.type.optional) {
180
- zodSchema = this.optional(zodSchema);
181
- }
182
-
183
- return zodSchema;
184
- }
185
-
186
- private getAttrLiteralArg<T extends string | number>(
187
- attr: DataModelFieldAttribute,
188
- paramName: string
189
- ) {
190
- const arg = attr.args.find(
191
- (arg) => arg.$resolvedParam?.name === paramName
192
- );
193
- if (!arg || !isLiteralExpr(arg.value)) {
194
- return undefined;
195
- }
196
- return (arg.value as LiteralExpr).value as T;
197
- }
198
-
199
- private makeZodSchema(field: DataModelField, mode: 'create' | 'update') {
200
- const type = field.type;
201
- let schema = '';
202
- if (type.reference && isDataModel(type.reference.ref)) {
203
- const modelType = type.reference.ref.name;
204
- const create = this.validator(modelType, 'create');
205
- const update = this.validator(modelType, 'update');
206
-
207
- // list all possible action fields in write playload:
208
- // create/createMany/connectOrCreate/update/updateMany/upsert
209
-
210
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
211
- let fields: any = {
212
- create: this.optional(this.enumerable(create)),
213
- createMany: this.optional(this.enumerable(create)),
214
- connectOrCreate: this.optional(
215
- this.enumerable(this.object({ create }))
216
- ),
217
- };
218
-
219
- if (mode === 'update') {
220
- fields = {
221
- ...fields,
222
- update: this.optional(
223
- this.enumerable(
224
- type.array ? this.object({ data: update }) : update
225
- )
226
- ),
227
- updateMany: this.optional(
228
- this.enumerable(this.object({ data: update }))
229
- ),
230
- upsert: this.optional(
231
- type.array
232
- ? this.enumerable(
233
- this.object({
234
- create,
235
- update,
236
- })
237
- )
238
- : this.object({
239
- create,
240
- update,
241
- })
242
- ),
243
- };
244
- }
245
-
246
- schema = this.optional(this.object(fields));
247
- } else {
248
- switch (type.type) {
249
- case 'Int':
250
- case 'Float':
251
- case 'Decimal':
252
- schema = 'z.number()';
253
- break;
254
- case 'BigInt':
255
- schema = 'z.bigint()';
256
- break;
257
- case 'String':
258
- schema = 'z.string()';
259
- break;
260
- case 'Boolean':
261
- schema = 'z.boolean()';
262
- break;
263
- case 'DateTime':
264
- schema = 'z.date()';
265
- break;
266
- default:
267
- schema = 'z.any()';
268
- break;
269
- }
270
-
271
- if (type.array) {
272
- schema = this.array(schema);
273
- }
274
- }
275
-
276
- return schema;
277
- }
278
-
279
- private union(...schemas: string[]) {
280
- return `z.union([${schemas.join(', ')}])`;
281
- }
282
-
283
- private optional(schema: string) {
284
- return `z.optional(${schema})`;
285
- }
286
-
287
- private array(schema: string) {
288
- return `z.array(${schema})`;
289
- }
290
-
291
- private enumerable(schema: string) {
292
- return this.union(schema, this.array(schema));
293
- }
294
-
295
- private object(fields: Record<string, string>) {
296
- return `z.object({ ${Object.entries(fields)
297
- .map(([k, v]) => k + ': ' + v)
298
- .join(',\n')} })`;
299
- }
300
-
301
- private validator(modelName: string, mode: 'create' | 'update') {
302
- return `${modelName}_${mode}_validator`;
303
- }
304
- }