Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs

name: Build Test CI

on:
push:
branches: ['dev', 'main']
pull_request:
branches: ['dev', 'main']

jobs:
build:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v3
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: ^7.15.0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm run build
- run: pnpm run test
35 changes: 0 additions & 35 deletions .github/workflows/node.js.yml

This file was deleted.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<a href="https://www.npmjs.com/package/zenstack">
<img src="https://img.shields.io/npm/v/zenstack">
</a>
<a href="https://twitter.com/intent/tweet?text=Wow%20%40zenstackhq">
<a href="https://twitter.com/zenstackhq">
<img src="https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Fgithub.com%2Fzenstackhq%2Fzenstack">
</a>
<a href="https://go.zenstack.dev/chat">
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "zenstack-monorepo",
"version": "0.3.2",
"version": "0.3.4",
"description": "",
"scripts": {
"build": "pnpm -r build",
"test": "pnpm -r test",
"test": "pnpm -r run test --silent",
"lint": "pnpm -r lint",
"publish-all": "pnpm --filter \"./packages/**\" -r publish"
},
Expand Down
6 changes: 4 additions & 2 deletions packages/internal/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/internal",
"version": "0.3.2",
"version": "0.3.4",
"displayName": "ZenStack Internal Library",
"description": "ZenStack internal runtime library. This package is for supporting runtime functionality of ZenStack and not supposed to be used directly.",
"repository": {
Expand Down Expand Up @@ -30,7 +30,9 @@
"cuid": "^2.1.8",
"decimal.js": "^10.4.2",
"deepcopy": "^2.1.0",
"swr": "^1.3.0"
"swr": "^1.3.0",
"zod": "^3.19.1",
"zod-validation-error": "^0.2.1"
},
"peerDependencies": {
"@prisma/client": "^4.4.0",
Expand Down
1 change: 1 addition & 0 deletions packages/internal/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { ServerErrorCode } from './types';
export * as request from './request';
export * from './validation';
15 changes: 14 additions & 1 deletion packages/internal/src/handler/data/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ServerErrorCode,
Service,
} from '../../types';
import { ValidationError } from '../../validation';
import { RequestHandler, RequestHandlerError } from '../types';
import {
and,
Expand Down Expand Up @@ -131,6 +132,14 @@ export default class DataHandler<DbClient extends DbClientContract>
ServerErrorCode.INVALID_REQUEST_PARAMS
),
});
} else if (err instanceof ValidationError) {
this.service.warn(
`Field constraint validation error for model "${model}": ${err.message}`
);
res.status(400).send({
code: ServerErrorCode.INVALID_REQUEST_PARAMS,
message: err.message,
});
} else {
// generic errors
this.service.error(
Expand All @@ -140,7 +149,7 @@ export default class DataHandler<DbClient extends DbClientContract>
this.service.error(err.stack);
}
res.status(500).send({
error: ServerErrorCode.UNKNOWN,
code: ServerErrorCode.UNKNOWN,
message: getServerErrorMessage(ServerErrorCode.UNKNOWN),
});
}
Expand Down Expand Up @@ -207,6 +216,8 @@ export default class DataHandler<DbClient extends DbClientContract>
);
}

await this.service.validateModelPayload(model, 'create', args.data);

// preprocess payload to modify fields as required by attribute like @password
await preprocessWritePayload(model, args, this.service);

Expand Down Expand Up @@ -322,6 +333,8 @@ export default class DataHandler<DbClient extends DbClientContract>
);
}

await this.service.validateModelPayload(model, 'update', args.data);

// preprocess payload to modify fields as required by attribute like @password
await preprocessWritePayload(model, args, this.service);

Expand Down
2 changes: 1 addition & 1 deletion packages/internal/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ export * from './types';
export * from './config';
export * from './service';
export * from './request-handler';
export * as request from './request';
export * from './validation';
24 changes: 24 additions & 0 deletions packages/internal/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
Service,
} from './types';
import colors from 'colors';
import { validate } from './validation';
import { z } from 'zod';

export abstract class DefaultService<
DbClient extends {
Expand All @@ -31,6 +33,9 @@ export abstract class DefaultService<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private guardModule: any;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
private fieldConstraintModule: any;

private readonly prismaLogLevels: LogLevel[] = [
'query',
'info',
Expand Down Expand Up @@ -155,6 +160,22 @@ export abstract class DefaultService<
return provider(context);
}

async validateModelPayload(
model: string,
mode: 'create' | 'update',
payload: unknown
) {
if (!this.fieldConstraintModule) {
this.fieldConstraintModule = await this.loadFieldConstraintModule();
}
const validator = this.fieldConstraintModule[
`${model}_${mode}_validator`
] as z.ZodType;
if (validator) {
validate(validator, payload);
}
}

verbose(message: string): void {
this.handleLog('verbose', message);
}
Expand All @@ -179,4 +200,7 @@ export abstract class DefaultService<

// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected abstract loadGuardModule(): Promise<any>;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected abstract loadFieldConstraintModule(): Promise<any>;
}
6 changes: 6 additions & 0 deletions packages/internal/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ export interface Service<DbClient = any> {
context: QueryContext
): Promise<unknown>;

validateModelPayload(
model: string,
mode: 'create' | 'update',
payload: unknown
): Promise<void>;

/**
* Generates a log message with verbose level.
*/
Expand Down
20 changes: 20 additions & 0 deletions packages/internal/src/validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { z } from 'zod';
import { fromZodError } from 'zod-validation-error';

/**
* Error indicating violations of field-level constraints
*/
export class ValidationError {
constructor(public readonly message: string) {}
}

/**
* Validate the given data with the given zod schema (for field-level constraints)
*/
export function validate(validator: z.ZodType, data: unknown) {
try {
validator.parse(data);
} catch (err) {
throw new ValidationError(fromZodError(err as z.ZodError).message);
}
}
2 changes: 1 addition & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/runtime",
"displayName": "ZenStack Runtime Library",
"version": "0.3.2",
"version": "0.3.4",
"description": "This package contains runtime library for consuming client and server side code generated by ZenStack.",
"repository": {
"type": "git",
Expand Down
4 changes: 2 additions & 2 deletions packages/schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"publisher": "zenstack",
"displayName": "ZenStack Language Tools",
"description": "A toolkit for modeling data and access policies in full-stack development with Next.js and Typescript",
"version": "0.3.2",
"version": "0.3.4",
"author": {
"name": "ZenStack Team"
},
Expand Down Expand Up @@ -70,7 +70,7 @@
"vscode:package": "vsce package --no-dependencies",
"clean": "rimraf bundle",
"build": "pnpm langium:generate && tsc --noEmit && pnpm bundle && cp -r src/res/* bundle/res/",
"bundle": "npm run clean && node build/bundle.js --minify",
"bundle": "npm run clean && node build/bundle.js",
"bundle-watch": "node build/bundle.js --watch",
"ts:watch": "tsc --watch --noEmit",
"tsc-alias:watch": "tsc-alias --watch",
Expand Down
Loading