From 331d87a55813285d6ec5cf0d87af6ebd9bb9c07c Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 4 Nov 2022 10:39:54 +0800 Subject: [PATCH 01/13] chore: add @password @omit field attributes documentation (#47) --- .../learning-the-zmodel-language.md | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/get-started/learning-the-zmodel-language.md b/docs/get-started/learning-the-zmodel-language.md index d1793e306..43a55bb9b 100644 --- a/docs/get-started/learning-the-zmodel-language.md +++ b/docs/get-started/learning-the-zmodel-language.md @@ -89,7 +89,39 @@ model Post { } ``` -Please refer to [Prisma's documentation](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#attributes) for an exhaustive list of attributes and functions. +ZenStack inherits most attributes and functions from Prisma, and added a number of new ones: + +- `@password` + + A field attribute that marks a field as a password. Before storing such a field, ZenStack hashes its value with [bcryptjs](https://github.com/dcodeIO/bcrypt.js), with a default salt length of 12. + + You can override the default setting with the `saltLength` or `salt` named parameters, like: + +```prisma +model User { + password String @password(saltLength: 16) +} + +model User { + password String @password(salt: "mysalt") +} +``` + +If both `saltLength` and `salt` parameters are provided, `salt` is used. + +- `@omit` + + A field attribute that marks a field to be excluded when model entities are read from the generated service. This is often used to prevent sensitive information from being returned to the front-end code (e.g., password, even hashed). + + E.g.: + +```prisma +model User { + password String @password @omit +} +``` + +For attributes and functions inherited from Prisma, please refer to [Prisma's documentation](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#attributes) for details. ## Relations From 3e7604d01ccabcffa278b74c4cf12da61879b4bc Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 7 Nov 2022 09:59:00 +0800 Subject: [PATCH 02/13] fix: internal error 500 is returned when unique constraint violation happens (#49) We can't check Prisma's error type against PrismaClientKnownError class because we can't reference generated Prisma classes from the internal package. Use a weakly-typed checking instead. --- package.json | 2 +- packages/internal/package.json | 3 +- packages/internal/src/handler/data/handler.ts | 38 ++++++++++--------- packages/internal/src/handler/types.ts | 7 +++- packages/internal/src/types.ts | 28 ++++++++++++++ packages/runtime/package.json | 2 +- packages/schema/package.json | 2 +- samples/todo/package.json | 2 +- 8 files changed, 58 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index d7dcdc11f..a7e2d3088 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "0.2.4", + "version": "0.2.8", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/internal/package.json b/packages/internal/package.json index 01022a0e8..3a1755157 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/internal", - "version": "0.2.4", + "version": "0.2.8", "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": { @@ -12,7 +12,6 @@ "scripts": { "build": "tsc", "watch": "tsc --watch", - "test": "jest", "lint": "eslint src --ext ts", "prepublishOnly": "pnpm build" }, diff --git a/packages/internal/src/handler/data/handler.ts b/packages/internal/src/handler/data/handler.ts index cc9213fd1..916c4958c 100644 --- a/packages/internal/src/handler/data/handler.ts +++ b/packages/internal/src/handler/data/handler.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { PrismaClientKnownRequestError } from '@prisma/client/runtime'; import cuid from 'cuid'; import { NextApiRequest, NextApiResponse } from 'next'; import { TRANSACTION_FIELD_NAME } from '../../constants'; @@ -7,6 +6,7 @@ import { RequestHandlerOptions } from '../../request-handler'; import { DbClientContract, DbOperations, + getServerErrorMessage, QueryContext, ServerErrorCode, Service, @@ -97,12 +97,14 @@ export default class DataHandler message: err.message, }); } - } else if (err instanceof PrismaClientKnownRequestError) { + } else if (this.isPrismaClientKnownRequestError(err)) { // errors thrown by Prisma, try mapping to a known error if (PRISMA_ERROR_MAPPING[err.code]) { res.status(400).send({ code: PRISMA_ERROR_MAPPING[err.code], - message: 'database access error', + message: getServerErrorMessage( + PRISMA_ERROR_MAPPING[err.code] + ), }); } else { res.status(400).send({ @@ -118,7 +120,10 @@ export default class DataHandler if (err instanceof Error) { console.error(err.stack); } - res.status(500).send({ error: ServerErrorCode.UNKNOWN }); + res.status(500).send({ + error: ServerErrorCode.UNKNOWN, + message: getServerErrorMessage(ServerErrorCode.UNKNOWN), + }); } } } @@ -146,10 +151,7 @@ export default class DataHandler ); if (result.length === 0) { - throw new RequestHandlerError( - ServerErrorCode.ENTITY_NOT_FOUND, - 'not found' - ); + throw new RequestHandlerError(ServerErrorCode.ENTITY_NOT_FOUND); } res.status(200).send(result[0]); } else { @@ -261,8 +263,7 @@ export default class DataHandler ); if (result.length === 0) { throw new RequestHandlerError( - ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED, - `create result could not be read back due to policy check` + ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED ); } res.status(201).send(result[0]); @@ -272,8 +273,7 @@ export default class DataHandler err.code === ServerErrorCode.DENIED_BY_POLICY ) { throw new RequestHandlerError( - ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED, - `create result could not be read back due to policy check` + ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED ); } else { throw err; @@ -375,8 +375,7 @@ export default class DataHandler ); if (result.length === 0) { throw new RequestHandlerError( - ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED, - `update result could not be read back due to policy check` + ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED ); } res.status(200).send(result[0]); @@ -386,8 +385,7 @@ export default class DataHandler err.code === ServerErrorCode.DENIED_BY_POLICY ) { throw new RequestHandlerError( - ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED, - `update result could not be read back due to policy check` + ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED ); } else { throw err; @@ -460,9 +458,13 @@ export default class DataHandler res.status(200).send(r); } else { throw new RequestHandlerError( - ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED, - `delete result could not be read back due to policy check` + ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED ); } } + + private isPrismaClientKnownRequestError(err: any): err is { code: string } { + // we can't reference Prisma generated types so need to weakly check error type + return !!err.clientVersion && typeof err.code === 'string'; + } } diff --git a/packages/internal/src/handler/types.ts b/packages/internal/src/handler/types.ts index 757aa4f2c..b481f6208 100644 --- a/packages/internal/src/handler/types.ts +++ b/packages/internal/src/handler/types.ts @@ -1,5 +1,5 @@ import { NextApiRequest, NextApiResponse } from 'next'; -import { ServerErrorCode } from '../types'; +import { getServerErrorMessage, ServerErrorCode } from '../types'; /** * Defines contract for a Next.js API endpoint handler. @@ -23,7 +23,10 @@ export interface RequestHandler { * Error thrown during request handling */ export class RequestHandlerError extends Error { - constructor(public readonly code: ServerErrorCode, message: string) { + constructor(public readonly code: ServerErrorCode, message?: string) { + message = message + ? `${getServerErrorMessage(code)}: ${message}` + : getServerErrorMessage(code); super(message); } diff --git a/packages/internal/src/types.ts b/packages/internal/src/types.ts index e841423d9..9f0a01b69 100644 --- a/packages/internal/src/types.ts +++ b/packages/internal/src/types.ts @@ -134,3 +134,31 @@ export enum ServerErrorCode { */ UNKNOWN = 'UNKNOWN', } + +export function getServerErrorMessage(code: ServerErrorCode): string { + switch (code) { + case ServerErrorCode.ENTITY_NOT_FOUND: + return 'the requested entity is not found'; + + case ServerErrorCode.INVALID_REQUEST_PARAMS: + return 'request parameters are invalid'; + + case ServerErrorCode.DENIED_BY_POLICY: + return 'the request was denied due to access policy violation'; + + case ServerErrorCode.UNIQUE_CONSTRAINT_VIOLATION: + return 'the request failed because of database unique constraint violation'; + + case ServerErrorCode.REFERENCE_CONSTRAINT_VIOLATION: + return 'the request failed because of database foreign key constraint violation'; + + case ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED: + return 'the write operation succeeded, but the data cannot be read back due to access policy violation'; + + case ServerErrorCode.UNKNOWN: + return 'an unknown error occurred'; + + default: + return `generic error: ${code}`; + } +} diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 1e3951913..c7da9dc11 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "0.2.4", + "version": "0.2.8", "description": "This package contains runtime library for consuming client and server side code generated by ZenStack.", "repository": { "type": "git", diff --git a/packages/schema/package.json b/packages/schema/package.json index f117429fb..984c6fe8f 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "ZenStack is a toolkit that simplifies full-stack development", - "version": "0.2.4", + "version": "0.2.8", "author": { "name": "ZenStack Team" }, diff --git a/samples/todo/package.json b/samples/todo/package.json index 53daf9bf4..8a89ca097 100644 --- a/samples/todo/package.json +++ b/samples/todo/package.json @@ -1,6 +1,6 @@ { "name": "todo", - "version": "0.2.4", + "version": "0.2.8", "private": true, "scripts": { "dev": "next dev", From 244bd0903062905015f1f5516b69390eb5351b3c Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 7 Nov 2022 14:52:52 +0800 Subject: [PATCH 03/13] feat: configurable logging (#50) - following Prisma's logging paradigm, make logging configuration with several levels to stdout or event emitting - moving some logic from generated code to the DefaultService abstract base class in runtime --- README.md | 4 +- docs/ref/setup-logging.md | 75 +++++ package.json | 2 +- packages/internal/jest.config.ts | 3 + packages/internal/package.json | 3 +- packages/internal/src/config.ts | 16 ++ packages/internal/src/handler/data/handler.ts | 50 +++- .../internal/src/handler/data/policy-utils.ts | 10 +- packages/internal/src/index.ts | 2 + packages/internal/src/request-handler.ts | 3 +- packages/internal/src/request.ts | 1 - packages/internal/src/service.ts | 182 +++++++++++++ packages/internal/src/types.ts | 45 +++ packages/internal/tsconfig.json | 1 + packages/runtime/package.json | 2 +- packages/schema/package.json | 6 +- .../schema/src/generator/next-auth/index.ts | 3 +- .../schema/src/generator/service/index.ts | 99 ++----- pnpm-lock.yaml | 67 ++++- samples/todo/package-lock.json | 164 +++++------ samples/todo/package.json | 12 +- samples/todo/tsconfig.json | 4 +- tests/integration/jest.config.ts | 5 +- tests/integration/package.json | 3 +- tests/integration/tests/logging.test.ts | 256 ++++++++++++++++++ 25 files changed, 809 insertions(+), 209 deletions(-) create mode 100644 docs/ref/setup-logging.md create mode 100644 packages/internal/src/config.ts create mode 100644 packages/internal/src/service.ts create mode 100644 tests/integration/tests/logging.test.ts diff --git a/README.md b/README.md index 49ec30f2f..2316cd15b 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,7 @@ export const getServerSideProps: GetServerSideProps = async () => { **Please note** that server-side database access is not protected by access policies. This is by-design so as to provide a way of bypassing the policies. Please make sure you implement authorization properly. -## What's next? +## Learning more ### [Learning the ZModel language](/docs/get-started/learning-the-zmodel-language.md) @@ -272,6 +272,8 @@ export const getServerSideProps: GetServerSideProps = async () => { ### [Database hosting considerations](/docs/ref/database-hosting-considerations.md) +### [Setup logging](/docs/ref/setup-logging.md) + ## Reach out to us for issues, feedback and ideas! [Discord](https://discord.gg/dbuC9ZWc) [Twitter](https://twitter.com/zenstackhq) diff --git a/docs/ref/setup-logging.md b/docs/ref/setup-logging.md new file mode 100644 index 000000000..7e5194e27 --- /dev/null +++ b/docs/ref/setup-logging.md @@ -0,0 +1,75 @@ +# Setup Logging + +ZenStack uses the following levels to control server-side logging: + +1. error + + Error level logging + +1. warn + + Warning level logging + +1. info + + Info level logging + +1. verbose + + Verbose level logging + +1. query + + Detailed database query logging + +By default, ZenStack prints `error` and `warn` level of logging with `console.error` and `console.log`, respectively. You can also control the logging behavior by providing a `zenstack.config.json` file at the root of your project. + +You can turn log levels on and off in `zenstack.config.json`: + +```json +{ + "log": ["verbose", "info", "warn"] +} +``` + +You can also configure ZenStack to emit log as event instead of dumping to stdout, like: + +```json +{ + "log": [ + { + "level": "info", + "emit": "event" + } + ] +} +``` + +To consume the events: + +```ts +import service from '@zenstackhq/runtime'; + +service.$on('info', (event) => { + console.log(event.timestamp, event.message); +}); +``` + +You can also mix and match stdout output with event emitting, like: + +```json +{ + "log": [ + { + "level": "info", + "emit": "stdout" + }, + { + "level": "info", + "emit": "event" + } + ] +} +``` + +The settings in `zenstack.config.json` controls logging of both ZenStack and the underlying Prisma instance. diff --git a/package.json b/package.json index a7e2d3088..5f9c2d7fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "0.2.8", + "version": "0.2.9", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/internal/jest.config.ts b/packages/internal/jest.config.ts index 5fde9b5bf..a04ad4290 100644 --- a/packages/internal/jest.config.ts +++ b/packages/internal/jest.config.ts @@ -10,6 +10,9 @@ export default { // Automatically clear mock calls, instances, contexts and results before every test clearMocks: true, + // Automatically reset mock state before every test + resetMocks: true, + // Indicates whether the coverage information should be collected while executing the test collectCoverage: true, diff --git a/packages/internal/package.json b/packages/internal/package.json index 3a1755157..895906c38 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/internal", - "version": "0.2.8", + "version": "0.2.9", "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": { @@ -25,6 +25,7 @@ ], "dependencies": { "bcryptjs": "^2.4.3", + "colors": "^1.4.0", "cuid": "^2.1.8", "deepcopy": "^2.1.0", "swr": "^1.3.0" diff --git a/packages/internal/src/config.ts b/packages/internal/src/config.ts new file mode 100644 index 000000000..dd50cd1ae --- /dev/null +++ b/packages/internal/src/config.ts @@ -0,0 +1,16 @@ +import { LogLevel } from './types'; + +/** + * Logging config definition + */ +export type LogDefinition = { + level: LogLevel; + emit: 'stdout' | 'event'; +}; + +/** + * Service configuration + */ +export interface ServiceConfig { + log?: Array; +} diff --git a/packages/internal/src/handler/data/handler.ts b/packages/internal/src/handler/data/handler.ts index 916c4958c..ee664e292 100644 --- a/packages/internal/src/handler/data/handler.ts +++ b/packages/internal/src/handler/data/handler.ts @@ -49,6 +49,11 @@ export default class DataHandler const context = { user: await this.options.getServerUser(req, res) }; + this.service.verbose(`Data request: ${method} ${path}`); + if (req.body) { + this.service.verbose(`Request body: ${JSON.stringify(req.body)}`); + } + try { switch (method) { case 'GET': @@ -68,12 +73,12 @@ export default class DataHandler break; default: - console.warn(`Unhandled method: ${method}`); + this.service.warn(`Unhandled method: ${method}`); res.status(200).send({}); break; } } catch (err: unknown) { - console.log(`Error handling ${method} ${model}: ${err}`); + this.service.error(`${method} ${model}: ${err}`); if (err instanceof RequestHandlerError) { // in case of errors thrown directly by ZenStack @@ -85,12 +90,14 @@ export default class DataHandler message: err.message, }); break; + case ServerErrorCode.ENTITY_NOT_FOUND: res.status(404).send({ code: err.code, message: err.message, }); break; + default: res.status(400).send({ code: err.code, @@ -112,13 +119,21 @@ export default class DataHandler message: 'an unhandled Prisma error occurred', }); } + } else if (this.isPrismaClientValidationError(err)) { + // prisma validation error + res.status(400).send({ + code: ServerErrorCode.INVALID_REQUEST_PARAMS, + message: getServerErrorMessage( + ServerErrorCode.INVALID_REQUEST_PARAMS + ), + }); } else { // generic errors - console.error( + this.service.error( `An unknown error occurred: ${JSON.stringify(err)}` ); - if (err instanceof Error) { - console.error(err.stack); + if (err instanceof Error && err.stack) { + this.service.error(err.stack); } res.status(500).send({ error: ServerErrorCode.UNKNOWN, @@ -206,7 +221,7 @@ export default class DataHandler ); // conduct the create - console.log( + this.service.verbose( `Conducting create: ${model}:\n${JSON.stringify(args)}` ); const createResult = (await tx[model].create(args)) as { @@ -229,7 +244,7 @@ export default class DataHandler const createdIds = await queryIds(model, tx, { [TRANSACTION_FIELD_NAME]: `${transactionId}:create`, }); - console.log( + this.service.verbose( `Validating nestedly created entities: ${model}#[${createdIds.join( ', ' )}]` @@ -332,7 +347,7 @@ export default class DataHandler ); // conduct the update - console.log( + this.service.verbose( `Conducting update: ${model}:\n${JSON.stringify(args)}` ); await tx[model].update(args); @@ -343,7 +358,7 @@ export default class DataHandler const createdIds = await queryIds(model, tx, { [TRANSACTION_FIELD_NAME]: `${transactionId}:create`, }); - console.log( + this.service.verbose( `Validating nestedly created entities: ${model}#[${createdIds.join( ', ' )}]` @@ -445,7 +460,7 @@ export default class DataHandler } // conduct the deletion - console.log( + this.service.verbose( `Conducting delete ${model}:\n${JSON.stringify(args)}` ); await tx[model].delete(args); @@ -463,8 +478,17 @@ export default class DataHandler } } - private isPrismaClientKnownRequestError(err: any): err is { code: string } { - // we can't reference Prisma generated types so need to weakly check error type - return !!err.clientVersion && typeof err.code === 'string'; + private isPrismaClientKnownRequestError( + err: any + ): err is { code: string; message: string } { + return ( + err.__proto__.constructor.name === 'PrismaClientKnownRequestError' + ); + } + + private isPrismaClientValidationError( + err: any + ): err is { message: string } { + return err.__proto__.constructor.name === 'PrismaClientValidationError'; } } diff --git a/packages/internal/src/handler/data/policy-utils.ts b/packages/internal/src/handler/data/policy-utils.ts index ff22c15e5..051c02f86 100644 --- a/packages/internal/src/handler/data/policy-utils.ts +++ b/packages/internal/src/handler/data/policy-utils.ts @@ -102,7 +102,7 @@ export async function readWithCheck( // recursively inject read guard conditions into the query args await injectNestedReadConditions(model, args, service, context); - console.log( + service.verbose( `Reading with validation for ${model}: ${JSON.stringify(args)}` ); const result = await db[model].findMany(args); @@ -207,7 +207,7 @@ async function postProcessForRead( continue; } - console.log( + service.verbose( `Validating read of to-one relation: ${fieldInfo.type}#${entityData[field].id}` ); @@ -378,7 +378,7 @@ export async function checkPolicyForIds( context: QueryContext, db: Record ) { - console.log( + service.verbose( `Checking policy for ${model}#[${ids.join(', ')}] for ${operation}` ); @@ -427,7 +427,7 @@ async function checkPolicyForSelectionPath( // build a Prisma query for the path const query = buildChainedSelectQuery(id, selectionPath); - console.log( + service.verbose( `Query for selection path: model ${model}, path ${JSON.stringify( selectionPath )}, query ${JSON.stringify(query)}` @@ -436,7 +436,7 @@ async function checkPolicyForSelectionPath( // collect ids at the end of the path const ids: string[] = collectTerminalEntityIds(selectionPath, r); - console.log(`Collected leaf ids: ${JSON.stringify(ids)}`); + service.verbose(`Collected leaf ids: ${JSON.stringify(ids)}`); if (ids.length === 0) { return; diff --git a/packages/internal/src/index.ts b/packages/internal/src/index.ts index e50b82227..27aef584c 100644 --- a/packages/internal/src/index.ts +++ b/packages/internal/src/index.ts @@ -1,3 +1,5 @@ export * from './types'; +export * from './config'; +export * from './service'; export * from './request-handler'; export * as request from './request'; diff --git a/packages/internal/src/request-handler.ts b/packages/internal/src/request-handler.ts index 4c173c7a9..d182e6557 100644 --- a/packages/internal/src/request-handler.ts +++ b/packages/internal/src/request-handler.ts @@ -40,7 +40,8 @@ export function requestHandler( return dataHandler.handle(req, res, rest); default: - res.status(404).json({ error: 'Unknown route: ' + route }); + service.warn(`Unknown route: ${route}`); + res.status(404).json({ error: `Unknown route: ${route}` }); } }; } diff --git a/packages/internal/src/request.ts b/packages/internal/src/request.ts index adea8a1e7..41ea2224a 100644 --- a/packages/internal/src/request.ts +++ b/packages/internal/src/request.ts @@ -113,7 +113,6 @@ export function getMutate(): Mutator { const keys = Array.from(cache.keys()).filter( (k) => typeof k === 'string' && k.startsWith(key) ) as string[]; - console.log('Mutating keys:', JSON.stringify(keys)); const mutations = keys.map((key) => mutate(key, data, opts)); return Promise.all(mutations); }; diff --git a/packages/internal/src/service.ts b/packages/internal/src/service.ts new file mode 100644 index 000000000..994075106 --- /dev/null +++ b/packages/internal/src/service.ts @@ -0,0 +1,182 @@ +import * as fs from 'fs'; +import { EventEmitter } from 'stream'; +import { ServiceConfig } from './config'; +import { + FieldInfo, + LogEvent, + LogLevel, + PolicyOperationKind, + QueryContext, + Service, +} from './types'; +import colors from 'colors'; + +export abstract class DefaultService< + DbClient extends { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + $on: (eventType: any, handler: (event: any) => void) => void; + } +> implements Service +{ + protected config: ServiceConfig; + private prisma: DbClient; + protected readonly logEmitter = new EventEmitter(); + private readonly logSettings = { + query: { stdout: false, emit: false }, + verbose: { stdout: false, emit: false }, + info: { stdout: true, emit: false }, + warn: { stdout: true, emit: false }, + error: { stdout: true, emit: false }, + }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private guardModule: any; + + private readonly prismaLogLevels: LogLevel[] = [ + 'query', + 'info', + 'warn', + 'error', + ]; + + constructor() { + this.initialize(); + } + + private initialize() { + this.config = this.loadConfig(); + + // initialize log sink mapping + if (this.config.log) { + // reset all levels + for (const key of Object.keys(this.logSettings)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (this.logSettings as any)[key] = { stdout: false, emit: false }; + } + + for (const entry of this.config.log) { + const level = typeof entry === 'string' ? entry : entry.level; + if (!Object.keys(this.logSettings).includes(level)) { + console.error(`Unknown log level "${level}"`); + continue; + } + if (typeof entry === 'string') { + this.logSettings[level].stdout = true; + } else if (entry.emit === 'stdout') { + this.logSettings[level].stdout = true; + } else { + this.logSettings[level].emit = true; + } + } + } + + console.log( + 'Initializing ZenStack service with config:', + JSON.stringify(this.config) + ); + + this.prisma = this.initializePrisma(); + + for (const level of this.prismaLogLevels) { + if (this.logSettings[level].emit) { + this.verbose(`Hooking prisma log level ${level}`); + this.prisma.$on(level, (e) => { + this.logEmitter.emit(level, e); + }); + } + } + } + + $on(level: LogLevel, callback: (event: LogEvent) => void): void { + this.logEmitter.on(level, callback); + } + + private handleLog(level: LogLevel, message: string): void { + if (this.logSettings[level].stdout) { + switch (level) { + case 'verbose': + console.log(colors.blue(`zenstack:${level}`), message); + break; + case 'info': + console.log(colors.cyan(`zenstack:${level}`), message); + break; + case 'warn': + console.warn(colors.yellow(`zenstack:${level}`), message); + break; + case 'error': + console.error(colors.red(`zenstack:${level}`), message); + break; + } + } + if (this.logSettings[level].emit) { + this.logEmitter.emit(level, { timestamp: new Date(), message }); + } + } + + private loadConfig(): ServiceConfig { + const configFile = './zenstack.config.json'; + if (fs.existsSync(configFile)) { + try { + const config = JSON.parse( + fs.readFileSync(configFile).toString('utf-8') + ); + return config as ServiceConfig; + } catch (err) { + console.error('Failed to load zenstack.config.json', err); + } + } + return {}; + } + + get db(): DbClient { + return this.prisma; + } + + async resolveField( + model: string, + field: string + ): Promise { + if (!this.guardModule) { + this.guardModule = await this.loadGuardModule(); + } + return this.guardModule._fieldMapping?.[model]?.[field]; + } + + async buildQueryGuard( + model: string, + operation: PolicyOperationKind, + context: QueryContext + ): Promise { + if (!this.guardModule) { + this.guardModule = await this.loadGuardModule(); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const provider: (context: QueryContext) => any = + this.guardModule[model + '_' + operation]; + return provider(context); + } + + verbose(message: string): void { + this.handleLog('verbose', message); + } + + info(message: string): void { + this.handleLog('info', message); + } + + warn(message: string): void { + this.handleLog('warn', message); + } + + error(message: string): void { + this.handleLog('error', message); + } + + reinitialize(): void { + this.initialize(); + } + + protected abstract initializePrisma(): DbClient; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + protected abstract loadGuardModule(): Promise; +} diff --git a/packages/internal/src/types.ts b/packages/internal/src/types.ts index 9f0a01b69..214ab49b6 100644 --- a/packages/internal/src/types.ts +++ b/packages/internal/src/types.ts @@ -62,6 +62,11 @@ export type DbClientContract = Record & { ) => Promise; }; +/** + * Logging levels + */ +export type LogLevel = 'verbose' | 'info' | 'query' | 'warn' | 'error'; + /** * The main service of ZenStack. Implementation of this interface is automatically generated. */ @@ -93,6 +98,31 @@ export interface Service { operation: PolicyOperationKind, context: QueryContext ): Promise; + + /** + * Generates a log message with verbose level. + */ + verbose(message: string): void; + + /** + * Generates a log message with info level. + */ + info(message: string): void; + + /** + * Generates a log message with warn level. + */ + warn(message: string): void; + + /** + * Generates a log message with error level. + */ + error(message: string): void; + + /** + * Registers a listener to log events. + */ + $on(level: LogLevel, callback: (event: LogEvent) => void): void; } /** @@ -162,3 +192,18 @@ export function getServerErrorMessage(code: ServerErrorCode): string { return `generic error: ${code}`; } } + +export type LogEventHandler = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + LogEvent: any, + handler: (event: LogEvent) => void +) => void; + +export type LogEvent = { + timestamp: Date; + query?: string; + params?: string; + duration?: number; + target?: string; + message?: string; +}; diff --git a/packages/internal/tsconfig.json b/packages/internal/tsconfig.json index f58f60c5e..5f19b5c6d 100644 --- a/packages/internal/tsconfig.json +++ b/packages/internal/tsconfig.json @@ -14,6 +14,7 @@ "forceConsistentCasingInFileNames": true, "declaration": true, "resolveJsonModule": true, + "strictPropertyInitialization": false, "paths": {} }, "include": ["src/**/*.ts"], diff --git a/packages/runtime/package.json b/packages/runtime/package.json index c7da9dc11..0da45ec7b 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "0.2.8", + "version": "0.2.9", "description": "This package contains runtime library for consuming client and server side code generated by ZenStack.", "repository": { "type": "git", diff --git a/packages/schema/package.json b/packages/schema/package.json index 984c6fe8f..3dbe8e7bd 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "ZenStack is a toolkit that simplifies full-stack development", - "version": "0.2.8", + "version": "0.2.9", "author": { "name": "ZenStack Team" }, @@ -65,9 +65,9 @@ "main": "./bundle/extension.js", "scripts": { "vscode:publish": "vsce publish --no-dependencies", - "vscode:prepublish": "cp ../../README.md ./ && pnpm lint && pnpm build && pnpm bundle", + "vscode:prepublish": "cp ../../README.md ./ && pnpm lint && pnpm build", "vscode:package": "vsce package --no-dependencies", - "build": "cp -r src/res/* bundle/res/ && pnpm langium:generate && tsc --noEmit", + "build": "pnpm langium:generate && tsc --noEmit && pnpm bundle && cp -r src/res/* bundle/res/", "bundle": "node build/bundle.js --minify", "bundle-watch": "node build/bundle.js --watch", "ts:watch": "tsc --watch --noEmit", diff --git a/packages/schema/src/generator/next-auth/index.ts b/packages/schema/src/generator/next-auth/index.ts index f6a7b5771..0af334c9e 100644 --- a/packages/schema/src/generator/next-auth/index.ts +++ b/packages/schema/src/generator/next-auth/index.ts @@ -3,6 +3,7 @@ import { Project } from 'ts-morph'; import * as path from 'path'; import colors from 'colors'; import { DataModel, isDataModel, Model } from '@lang/generated/ast'; +import { execSync } from 'child_process'; /** * Generates NextAuth adaptor code @@ -20,7 +21,7 @@ export default class NextAuthGenerator implements Generator { async generate(context: Context): Promise { try { - require('next-auth'); + execSync('npm ls next-auth'); } catch (err) { console.warn( colors.yellow( diff --git a/packages/schema/src/generator/service/index.ts b/packages/schema/src/generator/service/index.ts index c80ec193b..167b42905 100644 --- a/packages/schema/src/generator/service/index.ts +++ b/packages/schema/src/generator/service/index.ts @@ -1,5 +1,5 @@ import { Context, Generator } from '../types'; -import { Project, StructureKind, VariableDeclarationKind } from 'ts-morph'; +import { Project } from 'ts-morph'; import * as path from 'path'; import colors from 'colors'; import { INTERNAL_PACKAGE } from '../constants'; @@ -16,90 +16,31 @@ export default class ServiceGenerator implements Generator { { overwrite: true } ); - sf.addImportDeclaration({ - namedImports: ['PrismaClient'], - moduleSpecifier: '../.prisma', - }); - - sf.addImportDeclaration({ - namedImports: ['Service', 'PolicyOperationKind', 'QueryContext'], - moduleSpecifier: INTERNAL_PACKAGE, - isTypeOnly: true, - }); - - sf.addVariableStatement({ - declarationKind: VariableDeclarationKind.Let, - declarations: [ - { - name: 'guardModule', - type: 'any', - }, - ], - }); + sf.addStatements([ + `import { PrismaClient } from "../.prisma";`, + `import { DefaultService } from "${INTERNAL_PACKAGE}";`, + ]); const cls = sf.addClass({ name: 'ZenStackService', isExported: true, - implements: ['Service'], - }); - cls.addMember({ - kind: StructureKind.Property, - name: 'private readonly _prisma', - initializer: 'new PrismaClient()', + extends: 'DefaultService', }); - cls.addGetAccessor({ - name: 'db', - }) - .addBody() - .setBodyText('return this._prisma;'); - - cls - .addMethod({ - name: 'resolveField', - isAsync: true, - parameters: [ - { - name: 'model', - type: 'string', - }, - { - name: 'field', - type: 'string', - }, - ], - }) - .addBody().setBodyText(` - if (!guardModule) { - guardModule = await import('./query/guard'); - } - return guardModule._fieldMapping?.[model]?.[field]; - `); - - cls - .addMethod({ - name: 'buildQueryGuard', - isAsync: true, - parameters: [ - { - name: 'model', - type: 'string', - }, - { - name: 'operation', - type: 'PolicyOperationKind', - }, - { - name: 'context', - type: 'QueryContext', - }, - ], - }) - .addBody().setBodyText(` - const module: any = await import('./query/guard'); - const provider: (context: QueryContext) => any = module[model+ '_' + operation]; - return provider(context); - `); + cls.addMethod({ + name: 'initializePrisma', + }).setBodyText(` + const logConfig = (this.config.log || []) + .filter(item => typeof item === 'string' ? ['info', 'warn', 'error', 'query'].includes(item): ['info', 'warn', 'error', 'query'].includes(item.level)); + return new PrismaClient({log: logConfig as any }); + `); + + cls.addMethod({ + name: 'loadGuardModule', + isAsync: true, + }).setBodyText(` + return import('./query/guard'); + `); // Recommended by Prisma for Next.js // https://www.prisma.io/docs/guides/database/troubleshooting-orm/help-articles/nextjs-prisma-client-dev-practices#problem diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65a8fd4cf..f6f0d47da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,7 @@ importers: '@types/node': ^14.18.29 '@types/uuid': ^8.3.4 bcryptjs: ^2.4.3 + colors: ^1.4.0 cuid: ^2.1.8 deepcopy: ^2.1.0 jest: ^29.0.3 @@ -27,9 +28,10 @@ importers: typescript: ^4.6.2 dependencies: bcryptjs: 2.4.3 + colors: 1.4.0 cuid: 2.1.8 deepcopy: 2.1.0 - next: 12.3.1_6tziyx3dehkoeijunclpkpolha + next: 12.3.1_qtpcxnaaarbm4ws7ughq6oxfve react: 18.2.0 react-dom: 18.2.0_react@18.2.0 swr: 1.3.0_react@18.2.0 @@ -145,6 +147,7 @@ importers: bcryptjs: ^2.4.3 jest: ^29.0.3 next: ^12.3.1 + sleep-promise: ^9.1.0 supertest: ^6.3.0 tmp: ^0.2.1 ts-jest: ^29.0.1 @@ -153,16 +156,17 @@ importers: dependencies: '@types/node': 14.18.29 bcryptjs: 2.4.3 + sleep-promise: 9.1.0 devDependencies: '@types/bcryptjs': 2.4.2 '@types/jest': 29.0.3 '@types/supertest': 2.0.12 '@types/tmp': 0.2.3 jest: 29.0.3_johvxhudwcpndp4mle25vwrlq4 - next: 12.3.1_6tziyx3dehkoeijunclpkpolha + next: 12.3.1_qtpcxnaaarbm4ws7ughq6oxfve supertest: 6.3.0 tmp: 0.2.1 - ts-jest: 29.0.1_poggjixajg6vd6yquly7s7dsj4 + ts-jest: 29.0.1_t3cec5bure72u77t3utxqeumoa ts-node: 10.9.1_ck2axrxkiif44rdbzjywaqjysa typescript: 4.8.3 @@ -212,6 +216,7 @@ packages: semver: 6.3.0 transitivePeerDependencies: - supports-color + dev: true /@babel/core/7.19.6: resolution: {integrity: sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==} @@ -234,7 +239,6 @@ packages: semver: 6.3.0 transitivePeerDependencies: - supports-color - dev: true /@babel/generator/7.19.5: resolution: {integrity: sha512-DxbNz9Lz4aMZ99qPpO1raTbcrI1ZeYh+9NR9qhfkQIbFtVEqotHojEBxHzmxhVONkGt6VyrqVQcgpefMy9pqcg==} @@ -243,6 +247,7 @@ packages: '@babel/types': 7.19.4 '@jridgewell/gen-mapping': 0.3.2 jsesc: 2.5.2 + dev: true /@babel/generator/7.19.6: resolution: {integrity: sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==} @@ -263,6 +268,7 @@ packages: '@babel/helper-validator-option': 7.18.6 browserslist: 4.21.4 semver: 6.3.0 + dev: true /@babel/helper-compilation-targets/7.19.3_@babel+core@7.19.6: resolution: {integrity: sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==} @@ -275,7 +281,6 @@ packages: '@babel/helper-validator-option': 7.18.6 browserslist: 4.21.4 semver: 6.3.0 - dev: true /@babel/helper-environment-visitor/7.18.9: resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} @@ -314,6 +319,7 @@ packages: '@babel/types': 7.19.4 transitivePeerDependencies: - supports-color + dev: true /@babel/helper-module-transforms/7.19.6: resolution: {integrity: sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==} @@ -329,7 +335,6 @@ packages: '@babel/types': 7.19.4 transitivePeerDependencies: - supports-color - dev: true /@babel/helper-plugin-utils/7.19.0: resolution: {integrity: sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==} @@ -341,13 +346,13 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.19.4 + dev: true /@babel/helper-simple-access/7.19.4: resolution: {integrity: sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.19.4 - dev: true /@babel/helper-split-export-declaration/7.18.6: resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} @@ -391,6 +396,7 @@ packages: hasBin: true dependencies: '@babel/types': 7.19.4 + dev: true /@babel/parser/7.19.6: resolution: {integrity: sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==} @@ -681,6 +687,7 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color + dev: true /@babel/traverse/7.19.6: resolution: {integrity: sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==} @@ -5045,7 +5052,7 @@ packages: engines: {node: '>=10'} dev: true - /next/12.3.1_6tziyx3dehkoeijunclpkpolha: + /next/12.3.1_qtpcxnaaarbm4ws7ughq6oxfve: resolution: {integrity: sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==} engines: {node: '>=12.22.0'} hasBin: true @@ -5069,7 +5076,7 @@ packages: postcss: 8.4.14 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - styled-jsx: 5.0.7_b6k74wvxdvqypha4emuv7fd2ke + styled-jsx: 5.0.7_otspjrsspon4ofp37rshhlhp2y use-sync-external-store: 1.2.0_react@18.2.0 optionalDependencies: '@next/swc-android-arm-eabi': 12.3.1 @@ -5773,6 +5780,10 @@ packages: engines: {node: '>=8'} dev: true + /sleep-promise/9.1.0: + resolution: {integrity: sha512-UHYzVpz9Xn8b+jikYSD6bqvf754xL2uBUzDFwiU6NcdZeifPr6UfgU43xpkPu67VMS88+TI2PSI7Eohgqf2fKA==} + dev: false + /slice-ansi/3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} engines: {node: '>=8'} @@ -5914,7 +5925,7 @@ packages: engines: {node: '>=8'} dev: true - /styled-jsx/5.0.7_b6k74wvxdvqypha4emuv7fd2ke: + /styled-jsx/5.0.7_otspjrsspon4ofp37rshhlhp2y: resolution: {integrity: sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==} engines: {node: '>= 12.0.0'} peerDependencies: @@ -5927,7 +5938,7 @@ packages: babel-plugin-macros: optional: true dependencies: - '@babel/core': 7.19.3 + '@babel/core': 7.19.6 react: 18.2.0 /superagent/8.0.2: @@ -6148,6 +6159,40 @@ packages: yargs-parser: 21.1.1 dev: true + /ts-jest/29.0.1_t3cec5bure72u77t3utxqeumoa: + resolution: {integrity: sha512-htQOHshgvhn93QLxrmxpiQPk69+M1g7govO1g6kf6GsjCv4uvRV0znVmDrrvjUrVCnTYeY4FBxTYYYD4airyJA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.19.6 + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.0.3_johvxhudwcpndp4mle25vwrlq4 + jest-util: 29.0.3 + json5: 2.2.1 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.3.7 + typescript: 4.8.3 + yargs-parser: 21.1.1 + dev: true + /ts-jest/29.0.3_nvckv3qbfhmmsla6emqlkyje4a: resolution: {integrity: sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} diff --git a/samples/todo/package-lock.json b/samples/todo/package-lock.json index fe3e8b7bd..244c5595a 100644 --- a/samples/todo/package-lock.json +++ b/samples/todo/package-lock.json @@ -1,22 +1,23 @@ { "name": "todo", - "version": "0.2.4", + "version": "0.2.9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "todo", - "version": "0.2.4", + "version": "0.2.9", "dependencies": { "@heroicons/react": "^2.0.12", "@prisma/client": "^4.4.0", - "@zenstackhq/internal": "^0.2.4", - "@zenstackhq/runtime": "^0.2.4", + "@zenstackhq/internal": "^0.2.9", + "@zenstackhq/runtime": "^0.2.9", + "bcryptjs": "^2.4.3", "daisyui": "^2.31.0", "moment": "^2.29.4", "nanoid": "^4.0.0", "next": "12.3.1", - "next-auth": "^4.10.3", + "next-auth": "^4.15.1", "react": "18.2.0", "react-dom": "18.2.0", "react-toastify": "^9.0.8", @@ -24,6 +25,7 @@ }, "devDependencies": { "@tailwindcss/line-clamp": "^0.4.2", + "@types/bcryptjs": "^2.4.2", "@types/node": "^14.17.3", "@types/react": "18.0.21", "@types/react-dom": "18.0.6", @@ -33,7 +35,7 @@ "postcss": "^8.4.16", "tailwindcss": "^3.1.8", "typescript": "^4.6.2", - "zenstack": "^0.2.4" + "zenstack": "^0.2.9" } }, "node_modules/@babel/code-frame": { @@ -563,8 +565,7 @@ "node_modules/@types/bcryptjs": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz", - "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==", - "peer": true + "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==" }, "node_modules/@types/json5": { "version": "0.0.29", @@ -721,11 +722,12 @@ } }, "node_modules/@zenstackhq/internal": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.2.4.tgz", - "integrity": "sha512-+Rg3Jq3RPaRav8zKxqyyrGLqP5JHxo1JEiXxTQHYqhAxdgtzzTOGkVAXMuB+OP/PTpe/6EBHwyd4T2CVyyQfcw==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.2.9.tgz", + "integrity": "sha512-Hq+JfXZ9s0v9m085V53kSwlLcZXlNMktuU4DHjiIlPFkTBssYZXphg0FxKHEhfA+FQct1vwDagIAB0UsZyYScg==", "dependencies": { "bcryptjs": "^2.4.3", + "colors": "^1.4.0", "cuid": "^2.1.8", "deepcopy": "^2.1.0", "swr": "^1.3.0" @@ -738,9 +740,9 @@ } }, "node_modules/@zenstackhq/runtime": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.2.4.tgz", - "integrity": "sha512-6rgbQeYjdI+Eiqp2ubp8ZocIRz2EsLY+zm3OOypoVgjxTFs+gqr2Z3hdWgwSjnEoZBoh2laFJ0tPI4rbHF8lww==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.2.9.tgz", + "integrity": "sha512-l2DqjAqKQe4bCyu0mta1WKtDKNE4/VH51n4UfOTy6xCTSqmTTqTRdEqWypC2u52yMzrRb8re5c749HN8B0OL7A==", "dependencies": { "@zenstackhq/internal": "latest" }, @@ -1262,7 +1264,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, "engines": { "node": ">=0.1.90" } @@ -2695,9 +2696,9 @@ "dev": true }, "node_modules/jose": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.10.0.tgz", - "integrity": "sha512-KEhB/eLGLomWGPTb+/RNbYsTjIyx03JmbqAyIyiXBuNSa7CmNrJd5ysFhblayzs/e/vbOPMUaLnjHUMhGp4yLw==", + "version": "4.10.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.10.4.tgz", + "integrity": "sha512-eBH77Xs9Yc/oTDvukhAEDVMijhekPuNktXJL4tUlB22jqKP1k48v5nmsUmc8feoJPsxB3HsfEt2LbVSoz+1mng==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -2987,9 +2988,9 @@ } }, "node_modules/next-auth": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.14.0.tgz", - "integrity": "sha512-pD5sin6kq/uIx3Cod2/0JFnViEnngBTTNy4CdfRaYc2QzV2zwpWAbQny2Ezlg0GjEozDhKC53JJxRRE4AmNKEw==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.15.1.tgz", + "integrity": "sha512-yB/cSEqslaXAsLZ3lvkJbB7bRR5JeZvfUXSw0CmWeP+TaMZWB85fG9PWdfBeFrDCLBsoyATw9FF6fzApE0SxSw==", "dependencies": { "@babel/runtime": "^7.16.3", "@panva/hkdf": "^1.0.1", @@ -3002,10 +3003,10 @@ "uuid": "^8.3.2" }, "engines": { - "node": "^12.19.0 || ^14.15.0 || ^16.13.0" + "node": "^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0" }, "peerDependencies": { - "next": "^12.2.5", + "next": "^12.2.5 || ^13", "nodemailer": "^6.6.5", "react": "^17.0.2 || ^18", "react-dom": "^17.0.2 || ^18" @@ -3016,14 +3017,6 @@ } } }, - "node_modules/next-auth/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/next/node_modules/nanoid": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", @@ -3226,9 +3219,9 @@ } }, "node_modules/openid-client": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.2.0.tgz", - "integrity": "sha512-fswY95ZqQr8xBgnlcC9TBJs/QeZAANRaiDliIwHktvpxpWhv5+cfd41OFNflI/ycf09HnqEPWbGrmvSOPgD3HQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.2.1.tgz", + "integrity": "sha512-KPxqWnxobG/70Cxqyvd43RWfCfHedFnCdHSBpw5f7WnTnuBAeBnvot/BIo+brrcTr0wyAYUlL/qejQSGwWtdIg==", "dependencies": { "jose": "^4.10.0", "lru-cache": "^6.0.0", @@ -3510,9 +3503,9 @@ } }, "node_modules/preact-render-to-string": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.5.tgz", - "integrity": "sha512-rEBn42C3Wh+AjPxXUbDkb6xw0cTJQgxdYlp6ytUR1uBZF647Wn6ykkopMeQlRl7ggX+qnYYjZ4Hs1abZENl7ww==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", "dependencies": { "pretty-format": "^3.8.0" }, @@ -4377,10 +4370,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "dev": true, + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "bin": { "uuid": "dist/bin/uuid" } @@ -4528,12 +4520,12 @@ } }, "node_modules/zenstack": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.2.4.tgz", - "integrity": "sha512-+FjPCiBmy9T/bERNqPZNN6B8Clj7dHKZg2vdyBKSLjC+tLZZsB6eGW1FlPaj3qdQp7zlxvacF8nvwnO1BwEWyg==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.2.9.tgz", + "integrity": "sha512-qLEM04i5T2rByrgCf8AWgc1LvBa4f3+IsxxBK+DtwiRaNPfYaZB7Z7Nck1vLHlu8S2eas/Sxm+DmeCiX5NlsyA==", "dev": true, "dependencies": { - "@zenstackhq/internal": "0.2.4", + "@zenstackhq/internal": "0.2.9", "change-case": "^4.1.2", "chevrotain": "^9.1.0", "colors": "^1.4.0", @@ -4556,6 +4548,15 @@ "engines": { "vscode": "^1.72.0" } + }, + "node_modules/zenstack/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } } }, "dependencies": { @@ -4908,8 +4909,7 @@ "@types/bcryptjs": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz", - "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==", - "peer": true + "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==" }, "@types/json5": { "version": "0.0.29", @@ -5017,20 +5017,21 @@ } }, "@zenstackhq/internal": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.2.4.tgz", - "integrity": "sha512-+Rg3Jq3RPaRav8zKxqyyrGLqP5JHxo1JEiXxTQHYqhAxdgtzzTOGkVAXMuB+OP/PTpe/6EBHwyd4T2CVyyQfcw==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.2.9.tgz", + "integrity": "sha512-Hq+JfXZ9s0v9m085V53kSwlLcZXlNMktuU4DHjiIlPFkTBssYZXphg0FxKHEhfA+FQct1vwDagIAB0UsZyYScg==", "requires": { "bcryptjs": "^2.4.3", + "colors": "^1.4.0", "cuid": "^2.1.8", "deepcopy": "^2.1.0", "swr": "^1.3.0" } }, "@zenstackhq/runtime": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.2.4.tgz", - "integrity": "sha512-6rgbQeYjdI+Eiqp2ubp8ZocIRz2EsLY+zm3OOypoVgjxTFs+gqr2Z3hdWgwSjnEoZBoh2laFJ0tPI4rbHF8lww==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.2.9.tgz", + "integrity": "sha512-l2DqjAqKQe4bCyu0mta1WKtDKNE4/VH51n4UfOTy6xCTSqmTTqTRdEqWypC2u52yMzrRb8re5c749HN8B0OL7A==", "requires": { "@zenstackhq/internal": "latest" } @@ -5401,8 +5402,7 @@ "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" }, "commander": { "version": "8.3.0", @@ -6470,9 +6470,9 @@ "dev": true }, "jose": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.10.0.tgz", - "integrity": "sha512-KEhB/eLGLomWGPTb+/RNbYsTjIyx03JmbqAyIyiXBuNSa7CmNrJd5ysFhblayzs/e/vbOPMUaLnjHUMhGp4yLw==" + "version": "4.10.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.10.4.tgz", + "integrity": "sha512-eBH77Xs9Yc/oTDvukhAEDVMijhekPuNktXJL4tUlB22jqKP1k48v5nmsUmc8feoJPsxB3HsfEt2LbVSoz+1mng==" }, "js-tokens": { "version": "4.0.0", @@ -6699,9 +6699,9 @@ } }, "next-auth": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.14.0.tgz", - "integrity": "sha512-pD5sin6kq/uIx3Cod2/0JFnViEnngBTTNy4CdfRaYc2QzV2zwpWAbQny2Ezlg0GjEozDhKC53JJxRRE4AmNKEw==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.15.1.tgz", + "integrity": "sha512-yB/cSEqslaXAsLZ3lvkJbB7bRR5JeZvfUXSw0CmWeP+TaMZWB85fG9PWdfBeFrDCLBsoyATw9FF6fzApE0SxSw==", "requires": { "@babel/runtime": "^7.16.3", "@panva/hkdf": "^1.0.1", @@ -6712,13 +6712,6 @@ "preact": "^10.6.3", "preact-render-to-string": "^5.1.19", "uuid": "^8.3.2" - }, - "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - } } }, "no-case": { @@ -6844,9 +6837,9 @@ } }, "openid-client": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.2.0.tgz", - "integrity": "sha512-fswY95ZqQr8xBgnlcC9TBJs/QeZAANRaiDliIwHktvpxpWhv5+cfd41OFNflI/ycf09HnqEPWbGrmvSOPgD3HQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.2.1.tgz", + "integrity": "sha512-KPxqWnxobG/70Cxqyvd43RWfCfHedFnCdHSBpw5f7WnTnuBAeBnvot/BIo+brrcTr0wyAYUlL/qejQSGwWtdIg==", "requires": { "jose": "^4.10.0", "lru-cache": "^6.0.0", @@ -7029,9 +7022,9 @@ "integrity": "sha512-skAwGDFmgxhq1DCBHke/9e12ewkhc7WYwjuhHB8HHS8zkdtITXLRmUMTeol2ldxvLwYtwbFeifZ9uDDWuyL4Iw==" }, "preact-render-to-string": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.5.tgz", - "integrity": "sha512-rEBn42C3Wh+AjPxXUbDkb6xw0cTJQgxdYlp6ytUR1uBZF647Wn6ykkopMeQlRl7ggX+qnYYjZ4Hs1abZENl7ww==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", "requires": { "pretty-format": "^3.8.0" } @@ -7653,10 +7646,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "dev": true + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "v8-compile-cache": { "version": "2.3.0", @@ -7774,12 +7766,12 @@ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "zenstack": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.2.4.tgz", - "integrity": "sha512-+FjPCiBmy9T/bERNqPZNN6B8Clj7dHKZg2vdyBKSLjC+tLZZsB6eGW1FlPaj3qdQp7zlxvacF8nvwnO1BwEWyg==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.2.9.tgz", + "integrity": "sha512-qLEM04i5T2rByrgCf8AWgc1LvBa4f3+IsxxBK+DtwiRaNPfYaZB7Z7Nck1vLHlu8S2eas/Sxm+DmeCiX5NlsyA==", "dev": true, "requires": { - "@zenstackhq/internal": "0.2.4", + "@zenstackhq/internal": "0.2.9", "change-case": "^4.1.2", "chevrotain": "^9.1.0", "colors": "^1.4.0", @@ -7795,6 +7787,14 @@ "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", "vscode-uri": "^3.0.6" + }, + "dependencies": { + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true + } } } } diff --git a/samples/todo/package.json b/samples/todo/package.json index 8a89ca097..16d1f3739 100644 --- a/samples/todo/package.json +++ b/samples/todo/package.json @@ -1,6 +1,6 @@ { "name": "todo", - "version": "0.2.8", + "version": "0.2.9", "private": true, "scripts": { "dev": "next dev", @@ -17,13 +17,14 @@ "dependencies": { "@heroicons/react": "^2.0.12", "@prisma/client": "^4.4.0", - "@zenstackhq/internal": "^0.2.4", - "@zenstackhq/runtime": "^0.2.4", + "@zenstackhq/internal": "^0.2.9", + "@zenstackhq/runtime": "^0.2.9", + "bcryptjs": "^2.4.3", "daisyui": "^2.31.0", "moment": "^2.29.4", "nanoid": "^4.0.0", "next": "12.3.1", - "next-auth": "^4.10.3", + "next-auth": "^4.15.1", "react": "18.2.0", "react-dom": "18.2.0", "react-toastify": "^9.0.8", @@ -31,6 +32,7 @@ }, "devDependencies": { "@tailwindcss/line-clamp": "^0.4.2", + "@types/bcryptjs": "^2.4.2", "@types/node": "^14.17.3", "@types/react": "18.0.21", "@types/react-dom": "18.0.6", @@ -40,6 +42,6 @@ "postcss": "^8.4.16", "tailwindcss": "^3.1.8", "typescript": "^4.6.2", - "zenstack": "^0.2.4" + "zenstack": "^0.2.9" } } diff --git a/samples/todo/tsconfig.json b/samples/todo/tsconfig.json index d65ace5ca..cdfdd1e3e 100644 --- a/samples/todo/tsconfig.json +++ b/samples/todo/tsconfig.json @@ -20,9 +20,9 @@ "@lib/*": ["lib/*"], "@components/*": ["lib/components/*"], "@components": ["lib/components/index"], - "@zenstack/*": [".zenstack/*"] + ".zenstack/*": ["node_modules/.zenstack/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "zenstack.config.json"], "exclude": ["node_modules"] } diff --git a/tests/integration/jest.config.ts b/tests/integration/jest.config.ts index baae4eb9e..bba1c0b6d 100644 --- a/tests/integration/jest.config.ts +++ b/tests/integration/jest.config.ts @@ -6,10 +6,13 @@ export default { // Automatically clear mock calls, instances, contexts and results before every test clearMocks: true, + // Automatically reset mock state before every test + resetMocks: true, + // A map from regular expressions to paths to transformers transform: { '^.+\\.tsx?$': 'ts-jest' }, - testTimeout: 120000, + testTimeout: 300000, // explicitly specify moduel paths so that resolution from local dependencies work modulePaths: ['/tests/test-run/node_modules'], diff --git a/tests/integration/package.json b/tests/integration/package.json index 8a9a5f4c8..7d79e122b 100644 --- a/tests/integration/package.json +++ b/tests/integration/package.json @@ -24,6 +24,7 @@ }, "dependencies": { "@types/node": "^14.18.29", - "bcryptjs": "^2.4.3" + "bcryptjs": "^2.4.3", + "sleep-promise": "^9.1.0" } } diff --git a/tests/integration/tests/logging.test.ts b/tests/integration/tests/logging.test.ts new file mode 100644 index 000000000..6d0f9eb2b --- /dev/null +++ b/tests/integration/tests/logging.test.ts @@ -0,0 +1,256 @@ +import path from 'path'; +import { makeClient, run, setup } from './utils'; +import * as fs from 'fs'; +import type { DefaultService } from '../../../packages/runtime/server'; + +describe('Logging tests', () => { + let origDir: string; + + beforeAll(async () => { + origDir = path.resolve('.'); + await setup('./tests/todo.zmodel'); + }); + + beforeEach(() => { + run('npx prisma migrate reset --schema ./zenstack/schema.prisma -f'); + }); + + afterAll(() => { + process.chdir(origDir); + }); + + it('logging with default settings', async () => { + const service: DefaultService = require('@zenstackhq/runtime'); + service.reinitialize(); + + let gotInfoEmit = false; + let gotQueryEmit = false; + let gotVerboseEmit = false; + let gotErrorEmit = false; + + let gotInfoStd = false; + let gotQueryStd = false; + let gotVerboseStd = false; + let gotErrorStd = false; + + console.log = jest.fn((...args) => { + const msg = args?.[0] as string; + if (msg.includes('prisma:query')) { + gotQueryStd = true; + } + if (msg.includes(':verbose')) { + gotVerboseStd = true; + } + if (msg.includes(':info')) { + gotInfoStd = true; + } + }); + + console.error = jest.fn((...args) => { + const msg = args?.[0] as string; + if (msg.includes(':error')) { + gotErrorStd = true; + } + }); + + service.$on('info', (event) => { + console.log('Got info', event); + gotInfoEmit = true; + }); + + service.$on('query', (event) => { + console.log('Got query', event); + gotQueryEmit = true; + }); + + service.$on('verbose', (event) => { + console.log('Got verbose', event); + gotVerboseEmit = true; + }); + + service.$on('error', (event) => { + console.log('Got error', event); + gotErrorEmit = true; + }); + + await makeClient('/api/data/User').post('/').send({ + data: {}, + }); + + expect(gotQueryStd).toBeFalsy(); + expect(gotVerboseStd).toBeFalsy(); + expect(gotInfoStd).toBeFalsy(); + expect(gotErrorStd).toBeTruthy(); + + expect(gotInfoEmit).toBeFalsy(); + expect(gotQueryEmit).toBeFalsy(); + expect(gotVerboseEmit).toBeFalsy(); + expect(gotErrorEmit).toBeFalsy(); + }); + + it('logging with stdout', async () => { + fs.writeFileSync( + './zenstack.config.json', + ` + { + "log": ["query", "verbose", "info", "error"] + } + ` + ); + + const service: DefaultService = require('@zenstackhq/runtime'); + service.reinitialize(); + + let gotInfoEmit = false; + let gotQueryEmit = false; + let gotVerboseEmit = false; + let gotErrorEmit = false; + + let gotInfoStd = false; + let gotQueryStd = false; + let gotVerboseStd = false; + let gotErrorStd = false; + + console.log = jest.fn((...args) => { + const msg = args?.[0] as string; + if (msg.includes(':query')) { + gotQueryStd = true; + } + if (msg.includes(':verbose')) { + gotVerboseStd = true; + } + if (msg.includes(':info')) { + gotInfoStd = true; + } + }); + + console.error = jest.fn((...args) => { + const msg = args?.[0] as string; + if (msg.includes(':error')) { + gotErrorStd = true; + } + }); + + service.$on('info', (event) => { + console.log('Got info', event); + gotInfoEmit = true; + }); + + service.$on('query', (event) => { + console.log('Got query', event); + gotQueryEmit = true; + }); + + service.$on('verbose', (event) => { + console.log('Got verbose', event); + gotVerboseEmit = true; + }); + + service.$on('error', (event) => { + console.log('Got error', event); + gotErrorEmit = true; + }); + + await makeClient('/api/data/User').post('/').send({ + data: {}, + }); + + expect(gotQueryStd).toBeTruthy(); + expect(gotVerboseStd).toBeTruthy(); + expect(gotInfoStd).toBeTruthy(); + expect(gotErrorStd).toBeTruthy(); + + expect(gotInfoEmit).toBeFalsy(); + expect(gotQueryEmit).toBeFalsy(); + expect(gotVerboseEmit).toBeFalsy(); + expect(gotErrorEmit).toBeFalsy(); + }); + + it('logging with event', async () => { + fs.writeFileSync( + './zenstack.config.json', + ` + { + "log": [ + { "level": "query", "emit": "event" }, + { "level": "verbose", "emit": "event" }, + { "level": "info", "emit": "event" }, + { "level": "error", "emit": "event" } + ] + } + ` + ); + + const service: DefaultService = require('@zenstackhq/runtime'); + service.reinitialize(); + + let gotInfoEmit = false; + let gotQueryEmit = false; + let gotVerboseEmit = false; + let gotErrorEmit = false; + + let gotInfoStd = false; + let gotQueryStd = false; + let gotVerboseStd = false; + let gotErrorStd = false; + + console.log = jest.fn((...args) => { + const msg = args?.[0] as string; + if (msg.includes(':query')) { + gotQueryStd = true; + } + if (msg.includes(':verbose')) { + gotVerboseStd = true; + } + if (msg.includes(':info')) { + gotInfoStd = true; + } + }); + + console.error = jest.fn((...args) => { + const msg = args?.[0] as string; + if (msg.includes('zenstack:error')) { + gotErrorStd = true; + } + }); + + service.$on('info', (event) => { + expect(event.timestamp).toBeTruthy(); + expect(event.message).toBeTruthy(); + gotInfoEmit = true; + }); + + service.$on('query', (event) => { + expect(event.timestamp).not.toBeUndefined(); + expect(event.query).not.toBeUndefined(); + expect(event.duration).not.toBeUndefined(); + gotQueryEmit = true; + }); + + service.$on('verbose', (event) => { + expect(event.timestamp).not.toBeUndefined(); + expect(event.message).not.toBeUndefined(); + gotVerboseEmit = true; + }); + + service.$on('error', (event) => { + expect(event.timestamp).not.toBeUndefined(); + expect(event.message).not.toBeUndefined(); + gotErrorEmit = true; + }); + + await makeClient('/api/data/User').post('/').send({ + data: {}, + }); + + expect(gotInfoEmit).toBeTruthy(); + expect(gotQueryEmit).toBeTruthy(); + expect(gotVerboseEmit).toBeTruthy(); + expect(gotErrorEmit).toBeTruthy(); + + expect(gotInfoStd).toBeFalsy(); + expect(gotQueryStd).toBeFalsy(); + expect(gotVerboseStd).toBeFalsy(); + expect(gotErrorStd).toBeFalsy(); + }); +}); From 756cfbfaa173338e8a774d903642cdb70a3213e2 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 7 Nov 2022 17:26:34 +0800 Subject: [PATCH 04/13] fix: wrong query guard generated when context user doesn't have an id (#52) --- README.md | 2 +- docs/ref/setup-logging.md | 31 +++++++++--- package.json | 2 +- packages/internal/package.json | 2 +- packages/runtime/package.json | 2 +- packages/schema/package.json | 2 +- .../generator/prisma/query-guard-generator.ts | 3 +- samples/todo/package-lock.json | 50 +++++++++---------- samples/todo/package.json | 8 +-- tests/integration/tests/todo-e2e.test.ts | 20 ++++++++ tests/integration/tests/utils.ts | 8 ++- 11 files changed, 86 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 2316cd15b..a976aef69 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ export const getServerSideProps: GetServerSideProps = async () => { ### [Database hosting considerations](/docs/ref/database-hosting-considerations.md) -### [Setup logging](/docs/ref/setup-logging.md) +### [Setting up logging](/docs/ref/setup-logging.md) ## Reach out to us for issues, feedback and ideas! diff --git a/docs/ref/setup-logging.md b/docs/ref/setup-logging.md index 7e5194e27..ee8c0cfdd 100644 --- a/docs/ref/setup-logging.md +++ b/docs/ref/setup-logging.md @@ -1,24 +1,24 @@ -# Setup Logging +# Setting Up Logging ZenStack uses the following levels to control server-side logging: -1. error +- error Error level logging -1. warn +- warn Warning level logging -1. info +- info Info level logging -1. verbose +- verbose Verbose level logging -1. query +- query Detailed database query logging @@ -28,7 +28,24 @@ You can turn log levels on and off in `zenstack.config.json`: ```json { - "log": ["verbose", "info", "warn"] + "log": ["verbose", "info"] +} +``` + +The settings shown above is an shorthand for: + +```json +{ + "log": [ + { + "level": "verbose", + "emit": "stdout" + }, + { + "level": "info", + "emit": "stdout" + } + ] } ``` diff --git a/package.json b/package.json index 5f9c2d7fc..cd0358ae9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "0.2.9", + "version": "0.2.10", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/internal/package.json b/packages/internal/package.json index 895906c38..64b09387d 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/internal", - "version": "0.2.9", + "version": "0.2.10", "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": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 0da45ec7b..ae62c212e 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "0.2.9", + "version": "0.2.10", "description": "This package contains runtime library for consuming client and server side code generated by ZenStack.", "repository": { "type": "git", diff --git a/packages/schema/package.json b/packages/schema/package.json index 3dbe8e7bd..cd81f01f9 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "ZenStack is a toolkit that simplifies full-stack development", - "version": "0.2.9", + "version": "0.2.10", "author": { "name": "ZenStack Team" }, diff --git a/packages/schema/src/generator/prisma/query-guard-generator.ts b/packages/schema/src/generator/prisma/query-guard-generator.ts index f0cbf7fbc..84204a77d 100644 --- a/packages/schema/src/generator/prisma/query-guard-generator.ts +++ b/packages/schema/src/generator/prisma/query-guard-generator.ts @@ -160,7 +160,8 @@ export default class QueryGuardGenerator { .addBody(); func.addStatements( - `const user = context.user ?? { id: '${UNKNOWN_USER_ID}' };` + // make suer user id is always available + `const user = context.user?.id ? context.user : { ...context.user, id: '${UNKNOWN_USER_ID}' };` ); // r = ; diff --git a/samples/todo/package-lock.json b/samples/todo/package-lock.json index 244c5595a..12f7a96c5 100644 --- a/samples/todo/package-lock.json +++ b/samples/todo/package-lock.json @@ -1,17 +1,17 @@ { "name": "todo", - "version": "0.2.9", + "version": "0.2.10", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "todo", - "version": "0.2.9", + "version": "0.2.10", "dependencies": { "@heroicons/react": "^2.0.12", "@prisma/client": "^4.4.0", - "@zenstackhq/internal": "^0.2.9", - "@zenstackhq/runtime": "^0.2.9", + "@zenstackhq/internal": "^0.2.10", + "@zenstackhq/runtime": "^0.2.10", "bcryptjs": "^2.4.3", "daisyui": "^2.31.0", "moment": "^2.29.4", @@ -35,7 +35,7 @@ "postcss": "^8.4.16", "tailwindcss": "^3.1.8", "typescript": "^4.6.2", - "zenstack": "^0.2.9" + "zenstack": "^0.2.10" } }, "node_modules/@babel/code-frame": { @@ -722,9 +722,9 @@ } }, "node_modules/@zenstackhq/internal": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.2.9.tgz", - "integrity": "sha512-Hq+JfXZ9s0v9m085V53kSwlLcZXlNMktuU4DHjiIlPFkTBssYZXphg0FxKHEhfA+FQct1vwDagIAB0UsZyYScg==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.2.10.tgz", + "integrity": "sha512-w7yqWrbB+y2QVkKbEJmBtklIQ4gtK0LjzzGxph7ZHIQHP6E1nWFuqwweu4ALdkbufA9pGoxmoTJ0LZ5H1zpF/Q==", "dependencies": { "bcryptjs": "^2.4.3", "colors": "^1.4.0", @@ -740,9 +740,9 @@ } }, "node_modules/@zenstackhq/runtime": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.2.9.tgz", - "integrity": "sha512-l2DqjAqKQe4bCyu0mta1WKtDKNE4/VH51n4UfOTy6xCTSqmTTqTRdEqWypC2u52yMzrRb8re5c749HN8B0OL7A==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.2.10.tgz", + "integrity": "sha512-Xk2BALRdXXUZC7XmlqaD3oH+fp8ix3sSbYjc+mxfNIkZK67am6QkGF+4ggnKZwWi1BDHXP75+mtAHbWcJkEEOw==", "dependencies": { "@zenstackhq/internal": "latest" }, @@ -4520,12 +4520,12 @@ } }, "node_modules/zenstack": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.2.9.tgz", - "integrity": "sha512-qLEM04i5T2rByrgCf8AWgc1LvBa4f3+IsxxBK+DtwiRaNPfYaZB7Z7Nck1vLHlu8S2eas/Sxm+DmeCiX5NlsyA==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.2.10.tgz", + "integrity": "sha512-AsIP25k0qV/u7AegDrG38TbzOFIeccU1RIT9XDr4ae7c05RwD9+dniIgjNxO1AzvqZWl+hYQH7/ZGrMEcXQ0Mw==", "dev": true, "dependencies": { - "@zenstackhq/internal": "0.2.9", + "@zenstackhq/internal": "0.2.10", "change-case": "^4.1.2", "chevrotain": "^9.1.0", "colors": "^1.4.0", @@ -5017,9 +5017,9 @@ } }, "@zenstackhq/internal": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.2.9.tgz", - "integrity": "sha512-Hq+JfXZ9s0v9m085V53kSwlLcZXlNMktuU4DHjiIlPFkTBssYZXphg0FxKHEhfA+FQct1vwDagIAB0UsZyYScg==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.2.10.tgz", + "integrity": "sha512-w7yqWrbB+y2QVkKbEJmBtklIQ4gtK0LjzzGxph7ZHIQHP6E1nWFuqwweu4ALdkbufA9pGoxmoTJ0LZ5H1zpF/Q==", "requires": { "bcryptjs": "^2.4.3", "colors": "^1.4.0", @@ -5029,9 +5029,9 @@ } }, "@zenstackhq/runtime": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.2.9.tgz", - "integrity": "sha512-l2DqjAqKQe4bCyu0mta1WKtDKNE4/VH51n4UfOTy6xCTSqmTTqTRdEqWypC2u52yMzrRb8re5c749HN8B0OL7A==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.2.10.tgz", + "integrity": "sha512-Xk2BALRdXXUZC7XmlqaD3oH+fp8ix3sSbYjc+mxfNIkZK67am6QkGF+4ggnKZwWi1BDHXP75+mtAHbWcJkEEOw==", "requires": { "@zenstackhq/internal": "latest" } @@ -7766,12 +7766,12 @@ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "zenstack": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.2.9.tgz", - "integrity": "sha512-qLEM04i5T2rByrgCf8AWgc1LvBa4f3+IsxxBK+DtwiRaNPfYaZB7Z7Nck1vLHlu8S2eas/Sxm+DmeCiX5NlsyA==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.2.10.tgz", + "integrity": "sha512-AsIP25k0qV/u7AegDrG38TbzOFIeccU1RIT9XDr4ae7c05RwD9+dniIgjNxO1AzvqZWl+hYQH7/ZGrMEcXQ0Mw==", "dev": true, "requires": { - "@zenstackhq/internal": "0.2.9", + "@zenstackhq/internal": "0.2.10", "change-case": "^4.1.2", "chevrotain": "^9.1.0", "colors": "^1.4.0", diff --git a/samples/todo/package.json b/samples/todo/package.json index 16d1f3739..62e2a17e4 100644 --- a/samples/todo/package.json +++ b/samples/todo/package.json @@ -1,6 +1,6 @@ { "name": "todo", - "version": "0.2.9", + "version": "0.2.10", "private": true, "scripts": { "dev": "next dev", @@ -17,8 +17,8 @@ "dependencies": { "@heroicons/react": "^2.0.12", "@prisma/client": "^4.4.0", - "@zenstackhq/internal": "^0.2.9", - "@zenstackhq/runtime": "^0.2.9", + "@zenstackhq/internal": "^0.2.10", + "@zenstackhq/runtime": "^0.2.10", "bcryptjs": "^2.4.3", "daisyui": "^2.31.0", "moment": "^2.29.4", @@ -42,6 +42,6 @@ "postcss": "^8.4.16", "tailwindcss": "^3.1.8", "typescript": "^4.6.2", - "zenstack": "^0.2.9" + "zenstack": "^0.2.10" } } diff --git a/tests/integration/tests/todo-e2e.test.ts b/tests/integration/tests/todo-e2e.test.ts index d7ffbb001..c19d821da 100644 --- a/tests/integration/tests/todo-e2e.test.ts +++ b/tests/integration/tests/todo-e2e.test.ts @@ -298,6 +298,26 @@ describe('Todo E2E Tests', () => { await list1CredClientUser1.get('/').expect(404); }); + it('todo list with empty user id', async () => { + await createSpaceAndUsers(); + + await makeClient('/api/data/List', user1.id) + .post('/') + .send({ + data: { + id: 'list1', + title: 'List 1', + owner: { connect: { id: user1.id } }, + space: { connect: { id: space1.id } }, + }, + }) + .expect(201); + + await makeClient('/api/data/List', '') + .get('/') + .expect((resp) => expect(resp.body).toHaveLength(0)); + }); + it('todo', async () => { await createSpaceAndUsers(); diff --git a/tests/integration/tests/utils.ts b/tests/integration/tests/utils.ts index b9b20d760..825e8a3b8 100644 --- a/tests/integration/tests/utils.ts +++ b/tests/integration/tests/utils.ts @@ -52,7 +52,10 @@ export async function setup(schemaFile: string) { const options: RequestHandlerOptions = { async getServerUser(req: NextApiRequest, res: NextApiResponse) { - if (req.cookies.userId) { + if (req.cookies.userId === '') { + // simulate "undefined" user id + return {} as { id: string}; + } else if (req.cookies.userId) { return { id: req.cookies.userId }; } else { return undefined; @@ -108,7 +111,7 @@ export function makeClient(apiPath: string, userId?: string, queryArgs?: any) { prop: string | symbol, receiver: any ) { - if (!userId) { + if (userId === undefined) { return Reflect.get(target, prop, receiver); } @@ -119,6 +122,7 @@ export function makeClient(apiPath: string, userId?: string, queryArgs?: any) { case 'del': case 'delete': return (url: string) => { + // use userId cookie to simulate a logged in user return target[prop](url).set('Cookie', [ `userId=${userId}`, ]); From 646e3add13b259756b4e25ee5e39890b105accba Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 7 Nov 2022 18:04:24 +0800 Subject: [PATCH 05/13] fix: lower vscode engine version requirement (#55) --- packages/schema/package.json | 10 +- pnpm-lock.yaml | 345 ++++++++++++++++------------------- 2 files changed, 167 insertions(+), 188 deletions(-) diff --git a/packages/schema/package.json b/packages/schema/package.json index cd81f01f9..485692d6e 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -20,7 +20,7 @@ "url": "https://github.com/zenstackhq/zenstack" }, "engines": { - "vscode": "^1.72.0" + "vscode": "^1.56.0" }, "categories": [ "Programming Languages" @@ -104,12 +104,12 @@ "@types/pluralize": "^0.0.29", "@types/tmp": "^0.2.3", "@types/uuid": "^8.3.4", - "@types/vscode": "^1.72.0", - "@typescript-eslint/eslint-plugin": "^4.33.0", - "@typescript-eslint/parser": "^4.33.0", + "@types/vscode": "^1.56.0", + "@typescript-eslint/eslint-plugin": "^5.42.0", + "@typescript-eslint/parser": "^5.42.0", "concurrently": "^7.4.0", "esbuild": "^0.15.12", - "eslint": "^7.32.0", + "eslint": "^8.27.0", "jest": "^29.2.1", "langium-cli": "^0.5.0", "tmp": "^0.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6f0d47da..f4b934fbc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,9 +66,9 @@ importers: '@types/pluralize': ^0.0.29 '@types/tmp': ^0.2.3 '@types/uuid': ^8.3.4 - '@types/vscode': ^1.72.0 - '@typescript-eslint/eslint-plugin': ^4.33.0 - '@typescript-eslint/parser': ^4.33.0 + '@types/vscode': ^1.56.0 + '@typescript-eslint/eslint-plugin': ^5.42.0 + '@typescript-eslint/parser': ^5.42.0 '@zenstackhq/internal': workspace:* change-case: ^4.1.2 chevrotain: ^9.1.0 @@ -76,7 +76,7 @@ importers: commander: ^8.3.0 concurrently: ^7.4.0 esbuild: ^0.15.12 - eslint: ^7.32.0 + eslint: ^8.27.0 jest: ^29.2.1 langium: ^0.5.0 langium-cli: ^0.5.0 @@ -122,11 +122,11 @@ importers: '@types/tmp': 0.2.3 '@types/uuid': 8.3.4 '@types/vscode': 1.72.0 - '@typescript-eslint/eslint-plugin': 4.33.0_k4l66av2tbo6kxzw52jzgbfzii - '@typescript-eslint/parser': 4.33.0_3rubbgt5ekhqrcgx4uwls3neim + '@typescript-eslint/eslint-plugin': 5.42.0_ofgjrzjuekeo7s3hdyz2yuzw34 + '@typescript-eslint/parser': 5.42.0_rmayb2veg2btbq6mbmnyivgasy concurrently: 7.4.0 esbuild: 0.15.12 - eslint: 7.32.0 + eslint: 8.27.0 jest: 29.2.1_4f2ldd7um3b3u4eyvetyqsphze langium-cli: 0.5.0 tmp: 0.2.1 @@ -179,12 +179,6 @@ packages: '@jridgewell/gen-mapping': 0.1.1 '@jridgewell/trace-mapping': 0.3.17 - /@babel/code-frame/7.12.11: - resolution: {integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==} - dependencies: - '@babel/highlight': 7.18.6 - dev: true - /@babel/code-frame/7.18.6: resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} engines: {node: '>=6.9.0'} @@ -749,25 +743,25 @@ packages: dev: true optional: true - /@eslint/eslintrc/0.4.3: - resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==} - engines: {node: ^10.12.0 || >=12.0.0} + /@eslint/eslintrc/1.3.3: + resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.4 - espree: 7.3.1 + espree: 9.4.1 globals: 13.17.0 - ignore: 4.0.6 + ignore: 5.2.0 import-fresh: 3.3.0 - js-yaml: 3.14.1 + js-yaml: 4.1.0 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color dev: true - /@humanwhocodes/config-array/0.5.0: - resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==} + /@humanwhocodes/config-array/0.11.7: + resolution: {integrity: sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==} engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 1.2.1 @@ -777,6 +771,11 @@ packages: - supports-color dev: true + /@humanwhocodes/module-importer/1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + /@humanwhocodes/object-schema/1.2.1: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true @@ -1763,6 +1762,10 @@ packages: resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} dev: true + /@types/semver/7.3.13: + resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} + dev: true + /@types/stack-utils/2.0.1: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: true @@ -1808,24 +1811,25 @@ packages: '@types/yargs-parser': 21.0.0 dev: true - /@typescript-eslint/eslint-plugin/4.33.0_k4l66av2tbo6kxzw52jzgbfzii: - resolution: {integrity: sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==} - engines: {node: ^10.12.0 || >=12.0.0} + /@typescript-eslint/eslint-plugin/5.42.0_ofgjrzjuekeo7s3hdyz2yuzw34: + resolution: {integrity: sha512-5TJh2AgL6+wpL8H/GTSjNb4WrjKoR2rqvFxR/DDTqYNk6uXn8BJMEcncLSpMbf/XV1aS0jAjYwn98uvVCiAywQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: - '@typescript-eslint/parser': ^4.0.0 - eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/experimental-utils': 4.33.0_3rubbgt5ekhqrcgx4uwls3neim - '@typescript-eslint/parser': 4.33.0_3rubbgt5ekhqrcgx4uwls3neim - '@typescript-eslint/scope-manager': 4.33.0 + '@typescript-eslint/parser': 5.42.0_rmayb2veg2btbq6mbmnyivgasy + '@typescript-eslint/scope-manager': 5.42.0 + '@typescript-eslint/type-utils': 5.42.0_rmayb2veg2btbq6mbmnyivgasy + '@typescript-eslint/utils': 5.42.0_rmayb2veg2btbq6mbmnyivgasy debug: 4.3.4 - eslint: 7.32.0 - functional-red-black-tree: 1.0.1 + eslint: 8.27.0 ignore: 5.2.0 + natural-compare-lite: 1.4.0 regexpp: 3.2.0 semver: 7.3.8 tsutils: 3.21.0_typescript@4.8.4 @@ -1834,68 +1838,70 @@ packages: - supports-color dev: true - /@typescript-eslint/experimental-utils/4.33.0_3rubbgt5ekhqrcgx4uwls3neim: - resolution: {integrity: sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==} - engines: {node: ^10.12.0 || >=12.0.0} + /@typescript-eslint/parser/5.42.0_rmayb2veg2btbq6mbmnyivgasy: + resolution: {integrity: sha512-Ixh9qrOTDRctFg3yIwrLkgf33AHyEIn6lhyf5cCfwwiGtkWhNpVKlEZApi3inGQR/barWnY7qY8FbGKBO7p3JA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: - eslint: '*' + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: - '@types/json-schema': 7.0.11 - '@typescript-eslint/scope-manager': 4.33.0 - '@typescript-eslint/types': 4.33.0 - '@typescript-eslint/typescript-estree': 4.33.0_typescript@4.8.4 - eslint: 7.32.0 - eslint-scope: 5.1.1 - eslint-utils: 3.0.0_eslint@7.32.0 + '@typescript-eslint/scope-manager': 5.42.0 + '@typescript-eslint/types': 5.42.0 + '@typescript-eslint/typescript-estree': 5.42.0_typescript@4.8.4 + debug: 4.3.4 + eslint: 8.27.0 + typescript: 4.8.4 transitivePeerDependencies: - supports-color - - typescript dev: true - /@typescript-eslint/parser/4.33.0_3rubbgt5ekhqrcgx4uwls3neim: - resolution: {integrity: sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==} - engines: {node: ^10.12.0 || >=12.0.0} + /@typescript-eslint/scope-manager/5.42.0: + resolution: {integrity: sha512-l5/3IBHLH0Bv04y+H+zlcLiEMEMjWGaCX6WyHE5Uk2YkSGAMlgdUPsT/ywTSKgu9D1dmmKMYgYZijObfA39Wow==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.42.0 + '@typescript-eslint/visitor-keys': 5.42.0 + dev: true + + /@typescript-eslint/type-utils/5.42.0_rmayb2veg2btbq6mbmnyivgasy: + resolution: {integrity: sha512-HW14TXC45dFVZxnVW8rnUGnvYyRC0E/vxXShFCthcC9VhVTmjqOmtqj6H5rm9Zxv+ORxKA/1aLGD7vmlLsdlOg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: - eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 + eslint: '*' typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 4.33.0 - '@typescript-eslint/types': 4.33.0 - '@typescript-eslint/typescript-estree': 4.33.0_typescript@4.8.4 + '@typescript-eslint/typescript-estree': 5.42.0_typescript@4.8.4 + '@typescript-eslint/utils': 5.42.0_rmayb2veg2btbq6mbmnyivgasy debug: 4.3.4 - eslint: 7.32.0 + eslint: 8.27.0 + tsutils: 3.21.0_typescript@4.8.4 typescript: 4.8.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager/4.33.0: - resolution: {integrity: sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==} - engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} - dependencies: - '@typescript-eslint/types': 4.33.0 - '@typescript-eslint/visitor-keys': 4.33.0 - dev: true - - /@typescript-eslint/types/4.33.0: - resolution: {integrity: sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==} - engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} + /@typescript-eslint/types/5.42.0: + resolution: {integrity: sha512-t4lzO9ZOAUcHY6bXQYRuu+3SSYdD9TS8ooApZft4WARt4/f2Cj/YpvbTe8A4GuhT4bNW72goDMOy7SW71mZwGw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree/4.33.0_typescript@4.8.4: - resolution: {integrity: sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==} - engines: {node: ^10.12.0 || >=12.0.0} + /@typescript-eslint/typescript-estree/5.42.0_typescript@4.8.4: + resolution: {integrity: sha512-2O3vSq794x3kZGtV7i4SCWZWCwjEtkWfVqX4m5fbUBomOsEOyd6OAD1qU2lbvV5S8tgy/luJnOYluNyYVeOTTg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/types': 4.33.0 - '@typescript-eslint/visitor-keys': 4.33.0 + '@typescript-eslint/types': 5.42.0 + '@typescript-eslint/visitor-keys': 5.42.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -1906,20 +1912,40 @@ packages: - supports-color dev: true - /@typescript-eslint/visitor-keys/4.33.0: - resolution: {integrity: sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==} - engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} + /@typescript-eslint/utils/5.42.0_rmayb2veg2btbq6mbmnyivgasy: + resolution: {integrity: sha512-JZ++3+h1vbeG1NUECXQZE3hg0kias9kOtcQr3+JVQ3whnjvKuMyktJAAIj6743OeNPnGBmjj7KEmiDL7qsdnCQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@typescript-eslint/types': 4.33.0 - eslint-visitor-keys: 2.1.0 + '@types/json-schema': 7.0.11 + '@types/semver': 7.3.13 + '@typescript-eslint/scope-manager': 5.42.0 + '@typescript-eslint/types': 5.42.0 + '@typescript-eslint/typescript-estree': 5.42.0_typescript@4.8.4 + eslint: 8.27.0 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0_eslint@8.27.0 + semver: 7.3.8 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/visitor-keys/5.42.0: + resolution: {integrity: sha512-QHbu5Hf/2lOEOwy+IUw0GoSCuAzByTAWWrOTKzTzsotiUnWFpuKnXcAhC9YztAf2EElQ0VvIK+pHJUPkM0q7jg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.42.0 + eslint-visitor-keys: 3.3.0 dev: true - /acorn-jsx/5.3.2_acorn@7.4.1: + /acorn-jsx/5.3.2_acorn@8.8.0: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 7.4.1 + acorn: 8.8.0 dev: true /acorn-walk/8.2.0: @@ -1927,12 +1953,6 @@ packages: engines: {node: '>=0.4.0'} dev: true - /acorn/7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - /acorn/8.8.0: resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==} engines: {node: '>=0.4.0'} @@ -1965,20 +1985,6 @@ packages: uri-js: 4.4.1 dev: true - /ajv/8.11.0: - resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==} - dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 - dev: true - - /ansi-colors/4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - dev: true - /ansi-escapes/4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -2866,13 +2872,6 @@ packages: once: 1.4.0 dev: true - /enquirer/2.3.6: - resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} - engines: {node: '>=8.6'} - dependencies: - ansi-colors: 4.1.3 - dev: true - /entities/2.1.0: resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==} dev: true @@ -3129,89 +3128,89 @@ packages: estraverse: 4.3.0 dev: true - /eslint-utils/2.1.0: - resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} - engines: {node: '>=6'} + /eslint-scope/7.1.1: + resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - eslint-visitor-keys: 1.3.0 + esrecurse: 4.3.0 + estraverse: 5.3.0 dev: true - /eslint-utils/3.0.0_eslint@7.32.0: + /eslint-utils/3.0.0_eslint@8.27.0: resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} peerDependencies: eslint: '>=5' dependencies: - eslint: 7.32.0 + eslint: 8.27.0 eslint-visitor-keys: 2.1.0 dev: true - /eslint-visitor-keys/1.3.0: - resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} - engines: {node: '>=4'} - dev: true - /eslint-visitor-keys/2.1.0: resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} engines: {node: '>=10'} dev: true - /eslint/7.32.0: - resolution: {integrity: sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==} - engines: {node: ^10.12.0 || >=12.0.0} + /eslint-visitor-keys/3.3.0: + resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint/8.27.0: + resolution: {integrity: sha512-0y1bfG2ho7mty+SiILVf9PfuRA49ek4Nc60Wmmu62QlobNR+CeXa4xXIJgcuwSQgZiWaPH+5BDsctpIW0PR/wQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@babel/code-frame': 7.12.11 - '@eslint/eslintrc': 0.4.3 - '@humanwhocodes/config-array': 0.5.0 + '@eslint/eslintrc': 1.3.3 + '@humanwhocodes/config-array': 0.11.7 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 debug: 4.3.4 doctrine: 3.0.0 - enquirer: 2.3.6 escape-string-regexp: 4.0.0 - eslint-scope: 5.1.1 - eslint-utils: 2.1.0 - eslint-visitor-keys: 2.1.0 - espree: 7.3.1 + eslint-scope: 7.1.1 + eslint-utils: 3.0.0_eslint@8.27.0 + eslint-visitor-keys: 3.3.0 + espree: 9.4.1 esquery: 1.4.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 6.0.1 - functional-red-black-tree: 1.0.1 - glob-parent: 5.1.2 + find-up: 5.0.0 + glob-parent: 6.0.2 globals: 13.17.0 - ignore: 4.0.6 + grapheme-splitter: 1.0.4 + ignore: 5.2.0 import-fresh: 3.3.0 imurmurhash: 0.1.4 is-glob: 4.0.3 - js-yaml: 3.14.1 + is-path-inside: 3.0.3 + js-sdsl: 4.1.5 + js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.1 - progress: 2.0.3 regexpp: 3.2.0 - semver: 7.3.8 strip-ansi: 6.0.1 strip-json-comments: 3.1.1 - table: 6.8.0 text-table: 0.2.0 - v8-compile-cache: 2.3.0 transitivePeerDependencies: - supports-color dev: true - /espree/7.3.1: - resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==} - engines: {node: ^10.12.0 || >=12.0.0} + /espree/9.4.1: + resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 7.4.1 - acorn-jsx: 5.3.2_acorn@7.4.1 - eslint-visitor-keys: 1.3.0 + acorn: 8.8.0 + acorn-jsx: 5.3.2_acorn@8.8.0 + eslint-visitor-keys: 3.3.0 dev: true /esprima/4.0.1: @@ -3460,10 +3459,6 @@ packages: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: true - /functional-red-black-tree/1.0.1: - resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} - dev: true - /gensync/1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -3501,6 +3496,13 @@ packages: dependencies: is-glob: 4.0.3 + /glob-parent/6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + /glob/7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} dependencies: @@ -3546,6 +3548,10 @@ packages: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} dev: true + /grapheme-splitter/1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + dev: true + /has-flag/3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -3646,11 +3652,6 @@ packages: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: true - /ignore/4.0.6: - resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} - engines: {node: '>= 4'} - dev: true - /ignore/5.2.0: resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} engines: {node: '>= 4'} @@ -4712,6 +4713,10 @@ packages: - ts-node dev: true + /js-sdsl/4.1.5: + resolution: {integrity: sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==} + dev: true + /js-tokens/4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -4723,6 +4728,13 @@ packages: esprima: 4.0.1 dev: true + /js-yaml/4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + /jsesc/2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} @@ -4736,10 +4748,6 @@ packages: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true - /json-schema-traverse/1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - dev: true - /json-stable-stringify-without-jsonify/1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true @@ -4864,10 +4872,6 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true - /lodash.truncate/4.4.2: - resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - dev: true - /lodash.union/4.6.0: resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} dev: true @@ -5043,6 +5047,10 @@ packages: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} dev: true + /natural-compare-lite/1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + dev: true + /natural-compare/1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -5611,11 +5619,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /require-from-string/2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - dev: true - /resolve-cwd/3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} @@ -5793,15 +5796,6 @@ packages: is-fullwidth-code-point: 3.0.0 dev: true - /slice-ansi/4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - dev: true - /snake-case/3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} dependencies: @@ -6011,17 +6005,6 @@ packages: react: 18.2.0 dev: false - /table/6.8.0: - resolution: {integrity: sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==} - engines: {node: '>=10.0.0'} - dependencies: - ajv: 8.11.0 - lodash.truncate: 4.4.2 - slice-ansi: 4.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: true - /tar-fs/2.1.1: resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} dependencies: @@ -6489,10 +6472,6 @@ packages: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true - /v8-compile-cache/2.3.0: - resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} - dev: true - /v8-to-istanbul/9.0.1: resolution: {integrity: sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==} engines: {node: '>=10.12.0'} From 4f358de218de03439e4ab808d17dcfa331a0ffa8 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 7 Nov 2022 18:55:16 +0800 Subject: [PATCH 06/13] fix: regenerate Prisma schema on related cli commands (#56) --- package.json | 2 +- packages/internal/package.json | 3 +- packages/runtime/package.json | 2 +- packages/schema/package.json | 2 +- packages/schema/src/cli/cli-util.ts | 34 +++++++++ packages/schema/src/cli/index.ts | 30 ++------ packages/schema/src/generator/index.ts | 73 ++++++------------- .../schema/src/generator/next-auth/index.ts | 4 + packages/schema/src/generator/prisma/index.ts | 4 + .../schema/src/generator/react-hooks/index.ts | 4 + .../schema/src/generator/service/index.ts | 4 + packages/schema/src/generator/tsc/index.ts | 52 +++++++++++++ packages/schema/src/generator/types.ts | 1 + pnpm-lock.yaml | 2 + samples/todo/package-lock.json | 52 ++++++------- samples/todo/package.json | 13 ++-- 16 files changed, 174 insertions(+), 108 deletions(-) create mode 100644 packages/schema/src/generator/tsc/index.ts diff --git a/package.json b/package.json index cd0358ae9..c4d344673 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "0.2.10", + "version": "0.2.11", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/internal/package.json b/packages/internal/package.json index 64b09387d..9ba9462d9 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/internal", - "version": "0.2.10", + "version": "0.2.11", "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": { @@ -42,6 +42,7 @@ "@types/jest": "^29.0.3", "@types/node": "^14.18.29", "@types/uuid": "^8.3.4", + "eslint": "^8.27.0", "jest": "^29.0.3", "ts-jest": "^29.0.1", "ts-node": "^10.9.1", diff --git a/packages/runtime/package.json b/packages/runtime/package.json index ae62c212e..f27c34861 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "0.2.10", + "version": "0.2.11", "description": "This package contains runtime library for consuming client and server side code generated by ZenStack.", "repository": { "type": "git", diff --git a/packages/schema/package.json b/packages/schema/package.json index 485692d6e..c104fa434 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "ZenStack is a toolkit that simplifies full-stack development", - "version": "0.2.10", + "version": "0.2.11", "author": { "name": "ZenStack Team" }, diff --git a/packages/schema/src/cli/cli-util.ts b/packages/schema/src/cli/cli-util.ts index 28beee3d0..8c08aac62 100644 --- a/packages/schema/src/cli/cli-util.ts +++ b/packages/schema/src/cli/cli-util.ts @@ -1,10 +1,15 @@ import { STD_LIB_MODULE_NAME } from '@lang/constants'; import { Model } from '@lang/generated/ast'; +import { createZModelServices } from '@lang/zmodel-module'; import colors from 'colors'; import fs from 'fs'; import { LangiumServices } from 'langium'; +import { NodeFileSystem } from 'langium/node'; import path from 'path'; +import { ZenStackGenerator } from '../generator'; import { URI } from 'vscode-uri'; +import { GENERATED_CODE_PATH } from '../generator/constants'; +import { Context, GeneratorError } from '../generator/types'; /** * Loads a zmodel document from a file. @@ -69,3 +74,32 @@ export async function loadDocument( return document.parseResult.value as Model; } + +export async function runGenerator( + options: { schema: string }, + includedGenerators?: string[], + clearOutput = true +) { + const services = createZModelServices(NodeFileSystem).ZModel; + const model = await loadDocument(options.schema, services); + + const context: Context = { + schema: model, + outDir: path.dirname(options.schema), + // TODO: make this configurable + generatedCodeDir: GENERATED_CODE_PATH, + }; + + try { + await new ZenStackGenerator().generate( + context, + includedGenerators, + clearOutput + ); + } catch (err) { + if (err instanceof GeneratorError) { + console.error(colors.red(err.message)); + process.exit(1); + } + } +} diff --git a/packages/schema/src/cli/index.ts b/packages/schema/src/cli/index.ts index 095bd6a46..84fd1d9f2 100644 --- a/packages/schema/src/cli/index.ts +++ b/packages/schema/src/cli/index.ts @@ -1,38 +1,16 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Command, Option } from 'commander'; -import { NodeFileSystem } from 'langium/node'; import { ZModelLanguageMetaData } from '../language-server/generated/module'; -import { createZModelServices } from '../language-server/zmodel-module'; -import { Context, GeneratorError } from '../generator/types'; -import { ZenStackGenerator } from '../generator'; -import { GENERATED_CODE_PATH } from '../generator/constants'; import colors from 'colors'; import { execSync } from '../utils/exec-utils'; import { paramCase } from 'change-case'; import path from 'path'; -import { loadDocument } from './cli-util'; +import { runGenerator } from './cli-util'; export const generateAction = async (options: { schema: string; }): Promise => { - const services = createZModelServices(NodeFileSystem).ZModel; - const model = await loadDocument(options.schema, services); - - const context: Context = { - schema: model, - outDir: path.dirname(options.schema), - // TODO: make this configurable - generatedCodeDir: GENERATED_CODE_PATH, - }; - - try { - await new ZenStackGenerator().generate(context); - } catch (err) { - if (err instanceof GeneratorError) { - console.error(colors.red(err.message)); - process.exit(1); - } - } + await runGenerator(options); }; function prismaAction(prismaCmd: string): (...args: any[]) => Promise { @@ -50,6 +28,10 @@ function prismaAction(prismaCmd: string): (...args: any[]) => Promise { ); }) .join(' '); + + // regenerate prisma schema first + await runGenerator(options, ['prisma'], false); + const prismaExec = `npx prisma ${prismaCmd} ${command.name()} ${optStr}`; console.log(prismaExec); try { diff --git a/packages/schema/src/generator/index.ts b/packages/schema/src/generator/index.ts index 1b232095b..5781f5bdc 100644 --- a/packages/schema/src/generator/index.ts +++ b/packages/schema/src/generator/index.ts @@ -1,13 +1,12 @@ /* eslint-disable @typescript-eslint/no-var-requires */ -import { Context, GeneratorError } from './types'; +import { Context } from './types'; import * as fs from 'fs'; import colors from 'colors'; import PrismaGenerator from './prisma'; import ServiceGenerator from './service'; import ReactHooksGenerator from './react-hooks'; import NextAuthGenerator from './next-auth'; -import path from 'path'; -import { execSync } from '../utils/exec-utils'; +import { TypescriptCompilation } from './tsc'; /** * ZenStack code generator @@ -16,20 +15,26 @@ export class ZenStackGenerator { /** * Runs a series of nested generators */ - async generate(context: Context): Promise { - // folder that stores generated prisma schema and migrations + async generate( + context: Context, + includeGenerators?: string[], + clearOutput = true + ): Promise { + // ensure folder that stores generated prisma schema and migrations if (!fs.existsSync(context.outDir)) { fs.mkdirSync(context.outDir); } - // folder that stores generated zenstack code - if (fs.existsSync(context.generatedCodeDir)) { - fs.rmSync(context.generatedCodeDir, { - force: true, - recursive: true, - }); + if (clearOutput) { + // recreate folder that stores generated zenstack code + if (fs.existsSync(context.generatedCodeDir)) { + fs.rmSync(context.generatedCodeDir, { + force: true, + recursive: true, + }); + } + fs.mkdirSync(context.generatedCodeDir); } - fs.mkdirSync(context.generatedCodeDir); const version = require('../../package.json').version; console.log(colors.bold(`⌛️ Running ZenStack generator v${version}`)); @@ -40,49 +45,19 @@ export class ZenStackGenerator { new ServiceGenerator(), new ReactHooksGenerator(), new NextAuthGenerator(), + new TypescriptCompilation(), ]; for (const generator of generators) { + if ( + includeGenerators && + !includeGenerators.includes(generator.name) + ) { + continue; + } await generator.generate(context); } - // generate package.json - const packageJson = require(path.join( - __dirname, - '../res', - 'package.template.json' - )); - fs.writeFileSync( - path.join(context.generatedCodeDir, 'package.json'), - JSON.stringify(packageJson, undefined, 4) - ); - - // compile ts sources - const tsConfig = require(path.join( - __dirname, - '../res', - 'tsconfig.template.json' - )); - fs.writeFileSync( - path.join(context.generatedCodeDir, 'tsconfig.json'), - JSON.stringify(tsConfig, undefined, 4) - ); - - try { - execSync( - `npx tsc -p "${path.join( - context.generatedCodeDir, - 'tsconfig.json' - )}"` - ); - } catch { - throw new GeneratorError( - 'Something went wrong, generated runtime code failed to compile...\nPlease check errors above.' - ); - } - - console.log(colors.blue(' ✔️ Typescript source files transpiled')); - console.log( colors.green( colors.bold('👻 All generators completed successfully!') diff --git a/packages/schema/src/generator/next-auth/index.ts b/packages/schema/src/generator/next-auth/index.ts index 0af334c9e..a043104f6 100644 --- a/packages/schema/src/generator/next-auth/index.ts +++ b/packages/schema/src/generator/next-auth/index.ts @@ -9,6 +9,10 @@ import { execSync } from 'child_process'; * Generates NextAuth adaptor code */ export default class NextAuthGenerator implements Generator { + get name() { + return 'next-auth'; + } + private findModel(schema: Model, name: string) { return schema.declarations.find( (d) => isDataModel(d) && d.name === name diff --git a/packages/schema/src/generator/prisma/index.ts b/packages/schema/src/generator/prisma/index.ts index 6fdc06e40..183c468ef 100644 --- a/packages/schema/src/generator/prisma/index.ts +++ b/packages/schema/src/generator/prisma/index.ts @@ -8,6 +8,10 @@ import QueryGuardGenerator from './query-guard-generator'; * Generates Prisma schema and db client */ export default class PrismaGenerator implements Generator { + get name() { + return 'prisma'; + } + async generate(context: Context): Promise { // generate prisma schema const schemaFile = await new PrismaSchemaGenerator(context).generate(); diff --git a/packages/schema/src/generator/react-hooks/index.ts b/packages/schema/src/generator/react-hooks/index.ts index 6e0968638..313619f01 100644 --- a/packages/schema/src/generator/react-hooks/index.ts +++ b/packages/schema/src/generator/react-hooks/index.ts @@ -11,6 +11,10 @@ import { API_ROUTE_NAME, INTERNAL_PACKAGE } from '../constants'; * Generate react data query hooks code */ export default class ReactHooksGenerator implements Generator { + get name() { + return 'react-hooks'; + } + async generate(context: Context): Promise { const project = new Project(); diff --git a/packages/schema/src/generator/service/index.ts b/packages/schema/src/generator/service/index.ts index 167b42905..fb883b03d 100644 --- a/packages/schema/src/generator/service/index.ts +++ b/packages/schema/src/generator/service/index.ts @@ -8,6 +8,10 @@ import { INTERNAL_PACKAGE } from '../constants'; * Generates ZenStack service code */ export default class ServiceGenerator implements Generator { + get name() { + return 'service'; + } + async generate(context: Context): Promise { const project = new Project(); const sf = project.createSourceFile( diff --git a/packages/schema/src/generator/tsc/index.ts b/packages/schema/src/generator/tsc/index.ts new file mode 100644 index 000000000..50321f73d --- /dev/null +++ b/packages/schema/src/generator/tsc/index.ts @@ -0,0 +1,52 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +import colors from 'colors'; +import * as fs from 'fs'; +import path from 'path'; +import { execSync } from '../../utils/exec-utils'; +import { Context, Generator, GeneratorError } from '../types'; + +export class TypescriptCompilation implements Generator { + get name(): string { + return 'tsc'; + } + + async generate(context: Context) { + // generate package.json + const packageJson = require(path.join( + __dirname, + '../res', + 'package.template.json' + )); + + fs.writeFileSync( + path.join(context.generatedCodeDir, 'package.json'), + JSON.stringify(packageJson, undefined, 4) + ); + + // compile ts sources + const tsConfig = require(path.join( + __dirname, + '../res', + 'tsconfig.template.json' + )); + fs.writeFileSync( + path.join(context.generatedCodeDir, 'tsconfig.json'), + JSON.stringify(tsConfig, undefined, 4) + ); + + try { + execSync( + `npx tsc -p "${path.join( + context.generatedCodeDir, + 'tsconfig.json' + )}"` + ); + } catch { + throw new GeneratorError( + 'Something went wrong, generated runtime code failed to compile...\nPlease check errors above.' + ); + } + + console.log(colors.blue(' ✔️ Typescript source files transpiled')); + } +} diff --git a/packages/schema/src/generator/types.ts b/packages/schema/src/generator/types.ts index 87a5c1546..49733e443 100644 --- a/packages/schema/src/generator/types.ts +++ b/packages/schema/src/generator/types.ts @@ -7,6 +7,7 @@ export interface Context { } export interface Generator { + get name(): string; generate(context: Context): Promise; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4b934fbc..d41794e3e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,6 +16,7 @@ importers: colors: ^1.4.0 cuid: ^2.1.8 deepcopy: ^2.1.0 + eslint: ^8.27.0 jest: ^29.0.3 next: 12.3.1 react: ^17.0.2 || ^18 @@ -41,6 +42,7 @@ importers: '@types/jest': 29.0.3 '@types/node': 14.18.29 '@types/uuid': 8.3.4 + eslint: 8.27.0 jest: 29.0.3_johvxhudwcpndp4mle25vwrlq4 ts-jest: 29.0.1_poggjixajg6vd6yquly7s7dsj4 ts-node: 10.9.1_ck2axrxkiif44rdbzjywaqjysa diff --git a/samples/todo/package-lock.json b/samples/todo/package-lock.json index 12f7a96c5..c786f3797 100644 --- a/samples/todo/package-lock.json +++ b/samples/todo/package-lock.json @@ -1,17 +1,17 @@ { "name": "todo", - "version": "0.2.10", + "version": "0.2.11", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "todo", - "version": "0.2.10", + "version": "0.2.11", "dependencies": { "@heroicons/react": "^2.0.12", "@prisma/client": "^4.4.0", - "@zenstackhq/internal": "^0.2.10", - "@zenstackhq/runtime": "^0.2.10", + "@zenstackhq/internal": "^0.2.11", + "@zenstackhq/runtime": "^0.2.11", "bcryptjs": "^2.4.3", "daisyui": "^2.31.0", "moment": "^2.29.4", @@ -35,7 +35,7 @@ "postcss": "^8.4.16", "tailwindcss": "^3.1.8", "typescript": "^4.6.2", - "zenstack": "^0.2.10" + "zenstack": "^0.2.11" } }, "node_modules/@babel/code-frame": { @@ -722,9 +722,9 @@ } }, "node_modules/@zenstackhq/internal": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.2.10.tgz", - "integrity": "sha512-w7yqWrbB+y2QVkKbEJmBtklIQ4gtK0LjzzGxph7ZHIQHP6E1nWFuqwweu4ALdkbufA9pGoxmoTJ0LZ5H1zpF/Q==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.2.11.tgz", + "integrity": "sha512-BVh7MRBJwDpoRR90fDgiqAbUDj9fYsC3lVRUcOWSz6MaXCOztNX6y+YOKaoM+2lxjqjXfR82HNB1pSWlDlCkmQ==", "dependencies": { "bcryptjs": "^2.4.3", "colors": "^1.4.0", @@ -740,9 +740,9 @@ } }, "node_modules/@zenstackhq/runtime": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.2.10.tgz", - "integrity": "sha512-Xk2BALRdXXUZC7XmlqaD3oH+fp8ix3sSbYjc+mxfNIkZK67am6QkGF+4ggnKZwWi1BDHXP75+mtAHbWcJkEEOw==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.2.11.tgz", + "integrity": "sha512-gWzM2WRqg0sbTkXGe2yG1E7y1LFw03g4oUy52OGTzHTCc51cszAJsYacEY0/eWFjmUZD0QF8jokD0NcgrMDKng==", "dependencies": { "@zenstackhq/internal": "latest" }, @@ -4520,12 +4520,12 @@ } }, "node_modules/zenstack": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.2.10.tgz", - "integrity": "sha512-AsIP25k0qV/u7AegDrG38TbzOFIeccU1RIT9XDr4ae7c05RwD9+dniIgjNxO1AzvqZWl+hYQH7/ZGrMEcXQ0Mw==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.2.11.tgz", + "integrity": "sha512-ddrTHkAGC1KbxVNTCx0X7zt8E75w83YgZpz/bxpmSVWaRWusrcA53mV0feR/vSJZTiezWKiJl7DQj2cMfdoTog==", "dev": true, "dependencies": { - "@zenstackhq/internal": "0.2.10", + "@zenstackhq/internal": "0.2.11", "change-case": "^4.1.2", "chevrotain": "^9.1.0", "colors": "^1.4.0", @@ -4546,7 +4546,7 @@ "zenstack": "bin/cli" }, "engines": { - "vscode": "^1.72.0" + "vscode": "^1.56.0" } }, "node_modules/zenstack/node_modules/uuid": { @@ -5017,9 +5017,9 @@ } }, "@zenstackhq/internal": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.2.10.tgz", - "integrity": "sha512-w7yqWrbB+y2QVkKbEJmBtklIQ4gtK0LjzzGxph7ZHIQHP6E1nWFuqwweu4ALdkbufA9pGoxmoTJ0LZ5H1zpF/Q==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.2.11.tgz", + "integrity": "sha512-BVh7MRBJwDpoRR90fDgiqAbUDj9fYsC3lVRUcOWSz6MaXCOztNX6y+YOKaoM+2lxjqjXfR82HNB1pSWlDlCkmQ==", "requires": { "bcryptjs": "^2.4.3", "colors": "^1.4.0", @@ -5029,9 +5029,9 @@ } }, "@zenstackhq/runtime": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.2.10.tgz", - "integrity": "sha512-Xk2BALRdXXUZC7XmlqaD3oH+fp8ix3sSbYjc+mxfNIkZK67am6QkGF+4ggnKZwWi1BDHXP75+mtAHbWcJkEEOw==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.2.11.tgz", + "integrity": "sha512-gWzM2WRqg0sbTkXGe2yG1E7y1LFw03g4oUy52OGTzHTCc51cszAJsYacEY0/eWFjmUZD0QF8jokD0NcgrMDKng==", "requires": { "@zenstackhq/internal": "latest" } @@ -7766,12 +7766,12 @@ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "zenstack": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.2.10.tgz", - "integrity": "sha512-AsIP25k0qV/u7AegDrG38TbzOFIeccU1RIT9XDr4ae7c05RwD9+dniIgjNxO1AzvqZWl+hYQH7/ZGrMEcXQ0Mw==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.2.11.tgz", + "integrity": "sha512-ddrTHkAGC1KbxVNTCx0X7zt8E75w83YgZpz/bxpmSVWaRWusrcA53mV0feR/vSJZTiezWKiJl7DQj2cMfdoTog==", "dev": true, "requires": { - "@zenstackhq/internal": "0.2.10", + "@zenstackhq/internal": "0.2.11", "change-case": "^4.1.2", "chevrotain": "^9.1.0", "colors": "^1.4.0", diff --git a/samples/todo/package.json b/samples/todo/package.json index 62e2a17e4..f6d3e5443 100644 --- a/samples/todo/package.json +++ b/samples/todo/package.json @@ -1,6 +1,6 @@ { "name": "todo", - "version": "0.2.10", + "version": "0.2.11", "private": true, "scripts": { "dev": "next dev", @@ -11,14 +11,17 @@ "db:migrate": "zenstack migrate dev", "db:deploy": "zenstack migrate deploy", "db:reset": "zenstack migrate reset", + "db:browse": "zenstack studio", "generate": "zenstack generate", - "vercel-build": "npm run build && npm run db:deploy" + "vercel-build": "npm run build && npm run db:deploy", + "deps-local": "npm i -D ../../packages/schema && npm i ../../packages/internal ../../packages/runtime", + "deps-npm": "npm i -D zenstack@latest && npm i @zenstackhq/internal@latest @zenstackhq/runtime@latest" }, "dependencies": { "@heroicons/react": "^2.0.12", "@prisma/client": "^4.4.0", - "@zenstackhq/internal": "^0.2.10", - "@zenstackhq/runtime": "^0.2.10", + "@zenstackhq/internal": "^0.2.11", + "@zenstackhq/runtime": "^0.2.11", "bcryptjs": "^2.4.3", "daisyui": "^2.31.0", "moment": "^2.29.4", @@ -42,6 +45,6 @@ "postcss": "^8.4.16", "tailwindcss": "^3.1.8", "typescript": "^4.6.2", - "zenstack": "^0.2.10" + "zenstack": "^0.2.11" } } From 0315bb0be6d539f4618859e541b845f8fca7dbd9 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 7 Nov 2022 23:41:47 +0800 Subject: [PATCH 07/13] fix: handling of bytes/data/bigint/decimal data types (#59) use extra typing information for back and forth serialization --- package.json | 2 +- packages/internal/package.json | 5 +- .../internal/src/handler/data/policy-utils.ts | 51 ++++++++++- packages/internal/src/request.ts | 71 +++++++++++++++- packages/runtime/package.json | 2 +- packages/schema/package.json | 2 +- packages/schema/src/res/stdlib.zmodel | 38 ++++----- pnpm-lock.yaml | 85 +++++++++++++++++-- samples/todo/package-lock.json | 4 +- samples/todo/package.json | 2 +- tests/integration/tests/omit.zmodel | 2 +- tests/integration/tests/type-coverage.test.ts | 70 +++++++++++++++ tests/integration/tests/type-coverage.zmodel | 19 +++++ 13 files changed, 311 insertions(+), 42 deletions(-) create mode 100644 tests/integration/tests/type-coverage.test.ts create mode 100644 tests/integration/tests/type-coverage.zmodel diff --git a/package.json b/package.json index c4d344673..a7df610c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "0.2.11", + "version": "0.2.12", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/internal/package.json b/packages/internal/package.json index 9ba9462d9..0014dbddf 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/internal", - "version": "0.2.11", + "version": "0.2.12", "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": { @@ -27,12 +27,13 @@ "bcryptjs": "^2.4.3", "colors": "^1.4.0", "cuid": "^2.1.8", + "decimal.js": "^10.4.2", "deepcopy": "^2.1.0", "swr": "^1.3.0" }, "peerDependencies": { "@prisma/client": "^4.4.0", - "next": "12.3.1", + "next": "^12.3.1", "react": "^17.0.2 || ^18", "react-dom": "^17.0.2 || ^18" }, diff --git a/packages/internal/src/handler/data/policy-utils.ts b/packages/internal/src/handler/data/policy-utils.ts index 051c02f86..bcb015185 100644 --- a/packages/internal/src/handler/data/policy-utils.ts +++ b/packages/internal/src/handler/data/policy-utils.ts @@ -187,6 +187,40 @@ async function postProcessForRead( if (await shouldOmit(service, model, field)) { delete entityData[field]; } + + const fieldValue = entityData[field]; + + if (typeof fieldValue === 'bigint') { + // serialize BigInt with typing info + entityData[field] = { + type: 'BigInt', + data: fieldValue.toString(), + }; + } + + if (fieldValue instanceof Date) { + // serialize Date with typing info + entityData[field] = { + type: 'Date', + data: fieldValue.toISOString(), + }; + } + + if (typeof fieldValue === 'object') { + const fieldInfo = await service.resolveField(model, field); + if (fieldInfo?.type === 'Decimal') { + // serialize Decimal with typing info + entityData[field] = { + type: 'Decimal', + data: fieldValue.toString(), + }; + } else if (fieldInfo?.type === 'Bytes') { + entityData[field] = { + type: 'Bytes', + data: Array.from(fieldValue as Buffer), + }; + } + } } const injectTarget = args.select ?? args.include; @@ -596,13 +630,11 @@ export async function preprocessWritePayload( fieldData: any, parentData: any ) => { - if (fieldInfo.type !== 'String') { - return true; - } + // process @password field const pwdAttr = fieldInfo.attributes?.find( (attr) => attr.name === '@password' ); - if (pwdAttr) { + if (pwdAttr && fieldInfo.type !== 'String') { // hash password value let salt: string | number | undefined = pwdAttr.args.find( (arg) => arg.name === 'salt' @@ -616,6 +648,17 @@ export async function preprocessWritePayload( } parentData[fieldInfo.name] = hashSync(fieldData, salt); } + + // deserialize Buffer field + if (fieldInfo.type === 'Bytes' && Array.isArray(fieldData.data)) { + parentData[fieldInfo.name] = Buffer.from(fieldData.data); + } + + // deserialize BigInt field + if (fieldInfo.type === 'BigInt' && typeof fieldData === 'string') { + parentData[fieldInfo.name] = BigInt(fieldData); + } + return true; }; diff --git a/packages/internal/src/request.ts b/packages/internal/src/request.ts index 41ea2224a..59de775d0 100644 --- a/packages/internal/src/request.ts +++ b/packages/internal/src/request.ts @@ -1,3 +1,4 @@ +import Decimal from 'decimal.js'; import useSWR, { useSWRConfig } from 'swr'; import type { MutatorCallback, @@ -5,6 +6,66 @@ import type { SWRResponse, } from 'swr/dist/types'; +type BufferShape = { type: 'Buffer'; data: number[] }; +function isBuffer(value: unknown): value is BufferShape { + return ( + !!value && + (value as BufferShape).type === 'Buffer' && + Array.isArray((value as BufferShape).data) + ); +} + +type BigIntShape = { type: 'BigInt'; data: string }; +function isBigInt(value: unknown): value is BigIntShape { + return ( + !!value && + (value as BigIntShape).type === 'BigInt' && + typeof (value as BigIntShape).data === 'string' + ); +} + +type DateShape = { type: 'Date'; data: string }; +function isDate(value: unknown): value is BigIntShape { + return ( + !!value && + (value as DateShape).type === 'Date' && + typeof (value as DateShape).data === 'string' + ); +} + +type DecmalShape = { type: 'Decimal'; data: string }; +function isDecimal(value: unknown): value is DecmalShape { + return ( + !!value && + (value as DecmalShape).type === 'Decimal' && + typeof (value as DateShape).data === 'string' + ); +} + +const dataReviver = (key: string, value: unknown) => { + // Buffer + if (isBuffer(value)) { + return Buffer.from(value.data); + } + + // BigInt + if (isBigInt(value)) { + return BigInt(value.data); + } + + // Date + if (isDate(value)) { + return new Date(value.data); + } + + // Decimal + if (isDecimal(value)) { + return new Decimal(value.data); + } + + return value; +}; + const fetcher = async (url: string, options?: RequestInit) => { const res = await fetch(url, options); if (!res.ok) { @@ -15,7 +76,15 @@ const fetcher = async (url: string, options?: RequestInit) => { error.status = res.status; throw error; } - return res.json(); + + const textResult = await res.text(); + console.log; + try { + return JSON.parse(textResult, dataReviver); + } catch (err) { + console.error(`Unable to deserialize data:`, textResult); + throw err; + } }; function makeUrl(url: string, args: unknown) { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index f27c34861..bec0d68b1 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "0.2.11", + "version": "0.2.12", "description": "This package contains runtime library for consuming client and server side code generated by ZenStack.", "repository": { "type": "git", diff --git a/packages/schema/package.json b/packages/schema/package.json index c104fa434..1af0273c5 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "ZenStack is a toolkit that simplifies full-stack development", - "version": "0.2.11", + "version": "0.2.12", "author": { "name": "ZenStack Team" }, diff --git a/packages/schema/src/res/stdlib.zmodel b/packages/schema/src/res/stdlib.zmodel index ff248b199..c08141ba2 100644 --- a/packages/schema/src/res/stdlib.zmodel +++ b/packages/schema/src/res/stdlib.zmodel @@ -10,33 +10,33 @@ enum ReferentialAction { } /* - * Reads value from an environment variable + * Reads value from an environment variable. */ function env(name: String): String {} /* - * Gets thec current login user + * Gets thec current login user. */ function auth(): Any {} /* - * Gets current date-time (as DateTime type) + * Gets current date-time (as DateTime type). */ function now(): DateTime {} /* - * Generate a globally unique identifier based on the UUID spec + * Generates a globally unique identifier based on the UUID specs. */ function uuid(): String {} /* - * Generate a globally unique identifier based on the CUID spec + * Generates a globally unique identifier based on the CUID spec. */ function cuid(): String {} /* - * Create a sequence of integers in the underlying database and assign the incremented - * values to the ID values of the created records based on the sequence + * Creates a sequence of integers in the underlying database and assign the incremented + * values to the ID values of the created records based on the sequence. */ function autoincrement(): Int {} @@ -46,57 +46,57 @@ function autoincrement(): Int {} function dbgenerated(expr: String): Any {} /* - * Defines an ID on the model + * Defines an ID on the model. */ attribute @id(map: String?) /* - * Defines a default value for a field + * Defines a default value for a field. */ attribute @default(_ value: ContextType) /* - * Defines a unique constraint for this field + * Defines a unique constraint for this field. */ attribute @unique(map: String?) /* - * Defines a compound unique constraint for the specified fields + * Defines a compound unique constraint for the specified fields. */ attribute @@unique(_ fields: FieldReference[], name: String?, map: String?) /* - * Defines an index in the database + * Defines an index in the database. */ attribute @@index(_ fields: FieldReference[], map: String?) /* - * Defines meta information about the relation + * Defines meta information about the relation. */ attribute @relation(_ name: String?, fields: FieldReference[]?, references: FieldReference[]?, onDelete: ReferentialAction?, onUpdate: ReferentialAction?, map: String?) /* - * Maps a field name or enum value from the schema to a column with a different name in the database + * Maps a field name or enum value from the schema to a column with a different name in the database. */ attribute @map(_ name: String) /* - * Maps the schema model name to a table with a different name, or an enum name to a different underlying enum in the database + * Maps the schema model name to a table with a different name, or an enum name to a different underlying enum in the database. */ attribute @@map(_ name: String) /* - * Automatically stores the time when a record was last updated + * Automatically stores the time when a record was last updated. */ attribute @updatedAt() /* - * Defines an access policy that allows a set of operations when the given condition is true + * Defines an access policy that allows a set of operations when the given condition is true. */ attribute @@allow(_ operation: String, _ condition: Boolean) /* - * Defines an access policy that denies a set of operations when the given condition is true + * Defines an access policy that denies a set of operations when the given condition is true. */ attribute @@deny(_ operation: String, _ condition: Boolean) @@ -113,6 +113,6 @@ attribute @@deny(_ operation: String, _ condition: Boolean) attribute @password(saltLength: Int?, salt: String?) /* - * Indicates that the field should be omitted when read from the generated services + * Indicates that the field should be omitted when read from the generated services. */ attribute @omit() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d41794e3e..65c3a4d7d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,10 +15,11 @@ importers: bcryptjs: ^2.4.3 colors: ^1.4.0 cuid: ^2.1.8 + decimal.js: ^10.4.2 deepcopy: ^2.1.0 eslint: ^8.27.0 jest: ^29.0.3 - next: 12.3.1 + next: ^12.3.1 react: ^17.0.2 || ^18 react-dom: ^17.0.2 || ^18 swr: ^1.3.0 @@ -31,8 +32,9 @@ importers: bcryptjs: 2.4.3 colors: 1.4.0 cuid: 2.1.8 + decimal.js: 10.4.2 deepcopy: 2.1.0 - next: 12.3.1_qtpcxnaaarbm4ws7ughq6oxfve + next: 12.3.1_6tziyx3dehkoeijunclpkpolha react: 18.2.0 react-dom: 18.2.0_react@18.2.0 swr: 1.3.0_react@18.2.0 @@ -212,7 +214,6 @@ packages: semver: 6.3.0 transitivePeerDependencies: - supports-color - dev: true /@babel/core/7.19.6: resolution: {integrity: sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==} @@ -235,6 +236,7 @@ packages: semver: 6.3.0 transitivePeerDependencies: - supports-color + dev: true /@babel/generator/7.19.5: resolution: {integrity: sha512-DxbNz9Lz4aMZ99qPpO1raTbcrI1ZeYh+9NR9qhfkQIbFtVEqotHojEBxHzmxhVONkGt6VyrqVQcgpefMy9pqcg==} @@ -243,7 +245,6 @@ packages: '@babel/types': 7.19.4 '@jridgewell/gen-mapping': 0.3.2 jsesc: 2.5.2 - dev: true /@babel/generator/7.19.6: resolution: {integrity: sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==} @@ -264,7 +265,6 @@ packages: '@babel/helper-validator-option': 7.18.6 browserslist: 4.21.4 semver: 6.3.0 - dev: true /@babel/helper-compilation-targets/7.19.3_@babel+core@7.19.6: resolution: {integrity: sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==} @@ -277,6 +277,7 @@ packages: '@babel/helper-validator-option': 7.18.6 browserslist: 4.21.4 semver: 6.3.0 + dev: true /@babel/helper-environment-visitor/7.18.9: resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} @@ -315,7 +316,6 @@ packages: '@babel/types': 7.19.4 transitivePeerDependencies: - supports-color - dev: true /@babel/helper-module-transforms/7.19.6: resolution: {integrity: sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==} @@ -331,6 +331,7 @@ packages: '@babel/types': 7.19.4 transitivePeerDependencies: - supports-color + dev: true /@babel/helper-plugin-utils/7.19.0: resolution: {integrity: sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==} @@ -342,13 +343,13 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.19.4 - dev: true /@babel/helper-simple-access/7.19.4: resolution: {integrity: sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.19.4 + dev: true /@babel/helper-split-export-declaration/7.18.6: resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} @@ -392,7 +393,6 @@ packages: hasBin: true dependencies: '@babel/types': 7.19.4 - dev: true /@babel/parser/7.19.6: resolution: {integrity: sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==} @@ -683,7 +683,6 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color - dev: true /@babel/traverse/7.19.6: resolution: {integrity: sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==} @@ -2715,6 +2714,10 @@ packages: dependencies: ms: 2.1.2 + /decimal.js/10.4.2: + resolution: {integrity: sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==} + dev: false + /decompress-response/6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -5062,6 +5065,51 @@ packages: engines: {node: '>=10'} dev: true + /next/12.3.1_6tziyx3dehkoeijunclpkpolha: + resolution: {integrity: sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==} + engines: {node: '>=12.22.0'} + hasBin: true + peerDependencies: + fibers: '>= 3.1.0' + node-sass: ^6.0.0 || ^7.0.0 + react: ^17.0.2 || ^18.0.0-0 + react-dom: ^17.0.2 || ^18.0.0-0 + sass: ^1.3.0 + peerDependenciesMeta: + fibers: + optional: true + node-sass: + optional: true + sass: + optional: true + dependencies: + '@next/env': 12.3.1 + '@swc/helpers': 0.4.11 + caniuse-lite: 1.0.30001409 + postcss: 8.4.14 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + styled-jsx: 5.0.7_b6k74wvxdvqypha4emuv7fd2ke + use-sync-external-store: 1.2.0_react@18.2.0 + optionalDependencies: + '@next/swc-android-arm-eabi': 12.3.1 + '@next/swc-android-arm64': 12.3.1 + '@next/swc-darwin-arm64': 12.3.1 + '@next/swc-darwin-x64': 12.3.1 + '@next/swc-freebsd-x64': 12.3.1 + '@next/swc-linux-arm-gnueabihf': 12.3.1 + '@next/swc-linux-arm64-gnu': 12.3.1 + '@next/swc-linux-arm64-musl': 12.3.1 + '@next/swc-linux-x64-gnu': 12.3.1 + '@next/swc-linux-x64-musl': 12.3.1 + '@next/swc-win32-arm64-msvc': 12.3.1 + '@next/swc-win32-ia32-msvc': 12.3.1 + '@next/swc-win32-x64-msvc': 12.3.1 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: false + /next/12.3.1_qtpcxnaaarbm4ws7ughq6oxfve: resolution: {integrity: sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==} engines: {node: '>=12.22.0'} @@ -5105,6 +5153,7 @@ packages: transitivePeerDependencies: - '@babel/core' - babel-plugin-macros + dev: true /no-case/3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -5921,6 +5970,23 @@ packages: engines: {node: '>=8'} dev: true + /styled-jsx/5.0.7_b6k74wvxdvqypha4emuv7fd2ke: + resolution: {integrity: sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + dependencies: + '@babel/core': 7.19.3 + react: 18.2.0 + dev: false + /styled-jsx/5.0.7_otspjrsspon4ofp37rshhlhp2y: resolution: {integrity: sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==} engines: {node: '>= 12.0.0'} @@ -5936,6 +6002,7 @@ packages: dependencies: '@babel/core': 7.19.6 react: 18.2.0 + dev: true /superagent/8.0.2: resolution: {integrity: sha512-QtYZ9uaNAMexI7XWl2vAXAh0j4q9H7T0WVEI/y5qaUB3QLwxo+voUgCQ217AokJzUTIVOp0RTo7fhZrwhD7A2Q==} diff --git a/samples/todo/package-lock.json b/samples/todo/package-lock.json index c786f3797..73dd94718 100644 --- a/samples/todo/package-lock.json +++ b/samples/todo/package-lock.json @@ -1,12 +1,12 @@ { "name": "todo", - "version": "0.2.11", + "version": "0.2.12", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "todo", - "version": "0.2.11", + "version": "0.2.12", "dependencies": { "@heroicons/react": "^2.0.12", "@prisma/client": "^4.4.0", diff --git a/samples/todo/package.json b/samples/todo/package.json index f6d3e5443..9efdf340d 100644 --- a/samples/todo/package.json +++ b/samples/todo/package.json @@ -1,6 +1,6 @@ { "name": "todo", - "version": "0.2.11", + "version": "0.2.12", "private": true, "scripts": { "dev": "next dev", diff --git a/tests/integration/tests/omit.zmodel b/tests/integration/tests/omit.zmodel index 0ced7342d..01677619e 100644 --- a/tests/integration/tests/omit.zmodel +++ b/tests/integration/tests/omit.zmodel @@ -1,6 +1,6 @@ datasource db { provider = 'sqlite' - url = 'file:./password.db' + url = 'file:./omit.db' } model User { diff --git a/tests/integration/tests/type-coverage.test.ts b/tests/integration/tests/type-coverage.test.ts new file mode 100644 index 000000000..c9dc2c944 --- /dev/null +++ b/tests/integration/tests/type-coverage.test.ts @@ -0,0 +1,70 @@ +import path from 'path'; +import { makeClient, run, setup } from './utils'; +import { ServerErrorCode } from '../../../packages/internal/src/types'; + +describe('Type Coverage Tests', () => { + let origDir: string; + + beforeAll(async () => { + origDir = path.resolve('.'); + await setup('./tests/type-coverage.zmodel'); + }); + + beforeEach(() => { + run('npx prisma migrate reset --schema ./zenstack/schema.prisma -f'); + }); + + afterAll(() => { + process.chdir(origDir); + }); + + it('all types', async () => { + const data = { + string: 'string', + int: 100, + bigInt: 9007199254740991, + date: new Date(), + float: 1.23, + decimal: 1.2345, + boolean: true, + bytes: Buffer.from('hello'), + }; + await makeClient('/api/data/Foo') + .post('/') + .send({ + data, + }) + .expect(201) + .expect((resp) => { + console.log(resp.body); + + expect(resp.body.bigInt).toEqual( + expect.objectContaining({ + type: 'BigInt', + data: data.bigInt.toString(), + }) + ); + + expect(resp.body.date).toEqual( + expect.objectContaining({ + type: 'Date', + data: data.date.toISOString(), + }) + ); + + expect(resp.body.decimal).toEqual( + expect.objectContaining({ + type: 'Decimal', + data: data.decimal.toString(), + }) + ); + + expect(resp.body.bytes).toEqual( + expect.objectContaining({ + type: 'Bytes', + data: Array.from(data.bytes), + }) + ); + }); + }); +}); diff --git a/tests/integration/tests/type-coverage.zmodel b/tests/integration/tests/type-coverage.zmodel new file mode 100644 index 000000000..2d44a8842 --- /dev/null +++ b/tests/integration/tests/type-coverage.zmodel @@ -0,0 +1,19 @@ +datasource db { + provider = 'sqlite' + url = 'file:./type-coverage.db' +} + +model Foo { + id String @id @default(cuid()) + + string String + int Int + bigInt BigInt + date DateTime + float Float + decimal Decimal + boolean Boolean + bytes Bytes + + @@allow('all', true) +} From 0763ea87fd32b873780bdf19558eedd66b59fc8d Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 8 Nov 2022 10:51:20 +0800 Subject: [PATCH 08/13] fix: regression of password hasing & fine tune default logging (#60) --- package.json | 2 +- packages/internal/package.json | 6 +- packages/internal/src/handler/data/handler.ts | 8 ++- .../internal/src/handler/data/policy-utils.ts | 2 +- packages/runtime/package.json | 2 +- packages/schema/package.json | 8 ++- packages/schema/src/res/stdlib.zmodel | 10 +-- pnpm-lock.yaml | 4 ++ samples/todo/package.json | 2 +- samples/todo/zenstack.config.json | 3 + tests/integration/tests/logging.test.ts | 62 +++++++++---------- tests/integration/tests/password.test.ts | 8 ++- 12 files changed, 68 insertions(+), 49 deletions(-) create mode 100644 samples/todo/zenstack.config.json diff --git a/package.json b/package.json index a7df610c9..b640da001 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "0.2.12", + "version": "0.2.15", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/internal/package.json b/packages/internal/package.json index 0014dbddf..4ff9a625d 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/internal", - "version": "0.2.12", + "version": "0.2.15", "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": { @@ -10,7 +10,8 @@ "main": "lib/index.js", "types": "lib/index.d.ts", "scripts": { - "build": "tsc", + "clean": "rimraf lib", + "build": "npm run clean && tsc", "watch": "tsc --watch", "lint": "eslint src --ext ts", "prepublishOnly": "pnpm build" @@ -45,6 +46,7 @@ "@types/uuid": "^8.3.4", "eslint": "^8.27.0", "jest": "^29.0.3", + "rimraf": "^3.0.2", "ts-jest": "^29.0.1", "ts-node": "^10.9.1", "tsc-alias": "^1.7.0", diff --git a/packages/internal/src/handler/data/handler.ts b/packages/internal/src/handler/data/handler.ts index ee664e292..09dbc85c2 100644 --- a/packages/internal/src/handler/data/handler.ts +++ b/packages/internal/src/handler/data/handler.ts @@ -78,9 +78,9 @@ export default class DataHandler break; } } catch (err: unknown) { - this.service.error(`${method} ${model}: ${err}`); - if (err instanceof RequestHandlerError) { + this.service.warn(`${method} ${model}: ${err}`); + // in case of errors thrown directly by ZenStack switch (err.code) { case ServerErrorCode.DENIED_BY_POLICY: @@ -105,6 +105,8 @@ export default class DataHandler }); } } else if (this.isPrismaClientKnownRequestError(err)) { + this.service.warn(`${method} ${model}: ${err}`); + // errors thrown by Prisma, try mapping to a known error if (PRISMA_ERROR_MAPPING[err.code]) { res.status(400).send({ @@ -120,6 +122,8 @@ export default class DataHandler }); } } else if (this.isPrismaClientValidationError(err)) { + this.service.warn(`${method} ${model}: ${err}`); + // prisma validation error res.status(400).send({ code: ServerErrorCode.INVALID_REQUEST_PARAMS, diff --git a/packages/internal/src/handler/data/policy-utils.ts b/packages/internal/src/handler/data/policy-utils.ts index bcb015185..317270195 100644 --- a/packages/internal/src/handler/data/policy-utils.ts +++ b/packages/internal/src/handler/data/policy-utils.ts @@ -634,7 +634,7 @@ export async function preprocessWritePayload( const pwdAttr = fieldInfo.attributes?.find( (attr) => attr.name === '@password' ); - if (pwdAttr && fieldInfo.type !== 'String') { + if (pwdAttr && fieldInfo.type === 'String') { // hash password value let salt: string | number | undefined = pwdAttr.args.find( (arg) => arg.name === 'salt' diff --git a/packages/runtime/package.json b/packages/runtime/package.json index bec0d68b1..b0779d542 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "0.2.12", + "version": "0.2.15", "description": "This package contains runtime library for consuming client and server side code generated by ZenStack.", "repository": { "type": "git", diff --git a/packages/schema/package.json b/packages/schema/package.json index 1af0273c5..f618436de 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "ZenStack is a toolkit that simplifies full-stack development", - "version": "0.2.12", + "version": "0.2.15", "author": { "name": "ZenStack Team" }, @@ -67,8 +67,9 @@ "vscode:publish": "vsce publish --no-dependencies", "vscode:prepublish": "cp ../../README.md ./ && pnpm lint && pnpm build", "vscode:package": "vsce package --no-dependencies", + "clean": "rimraf bundle", "build": "pnpm langium:generate && tsc --noEmit && pnpm bundle && cp -r src/res/* bundle/res/", - "bundle": "node build/bundle.js --minify", + "bundle": "npm run clean && node build/bundle.js --minify", "bundle-watch": "node build/bundle.js --watch", "ts:watch": "tsc --watch --noEmit", "tsc-alias:watch": "tsc-alias --watch", @@ -77,7 +78,7 @@ "langium:watch": "langium generate --watch", "watch": "concurrently --kill-others \"npm:langium:watch\" \"npm:bundle-watch\"", "test": "jest", - "prepublishOnly": "cp ../../README.md ./ && pnpm build && pnpm bundle" + "prepublishOnly": "cp ../../README.md ./ && pnpm build" }, "dependencies": { "@zenstackhq/internal": "workspace:*", @@ -112,6 +113,7 @@ "eslint": "^8.27.0", "jest": "^29.2.1", "langium-cli": "^0.5.0", + "rimraf": "^3.0.2", "tmp": "^0.2.1", "ts-jest": "^29.0.3", "ts-node": "^10.9.1", diff --git a/packages/schema/src/res/stdlib.zmodel b/packages/schema/src/res/stdlib.zmodel index c08141ba2..f4679ba3b 100644 --- a/packages/schema/src/res/stdlib.zmodel +++ b/packages/schema/src/res/stdlib.zmodel @@ -104,11 +104,13 @@ attribute @@deny(_ operation: String, _ condition: Boolean) * Indicates that the field is a password field and needs to be hashed before persistence. * * ZenStack uses `bcryptjs` library to hash password. You can use the `saltLength` parameter - * to configure length of salt, or use parameter to provide an explicit salt. By default, 12-byte - * long salt is used. + * to configure the cost of hashing, or use `salt` parameter to provide an explicit salt. + * By default, salt length of 12 is used. * - * @saltLength: length of salt to use - * @salt: salt to use + * @see https://www.npmjs.com/package/bcryptjs for details + * + * @saltLength: length of salt to use (cost factor for the hash function) + * @salt: salt to use (a pregenerated valid salt) */ attribute @password(saltLength: Int?, salt: String?) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65c3a4d7d..5bfa7c4e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,7 @@ importers: next: ^12.3.1 react: ^17.0.2 || ^18 react-dom: ^17.0.2 || ^18 + rimraf: ^3.0.2 swr: ^1.3.0 ts-jest: ^29.0.1 ts-node: ^10.9.1 @@ -46,6 +47,7 @@ importers: '@types/uuid': 8.3.4 eslint: 8.27.0 jest: 29.0.3_johvxhudwcpndp4mle25vwrlq4 + rimraf: 3.0.2 ts-jest: 29.0.1_poggjixajg6vd6yquly7s7dsj4 ts-node: 10.9.1_ck2axrxkiif44rdbzjywaqjysa tsc-alias: 1.7.0 @@ -87,6 +89,7 @@ importers: pluralize: ^8.0.0 prisma: ^4.5.0 promisify: ^0.0.3 + rimraf: ^3.0.2 tmp: ^0.2.1 ts-jest: ^29.0.3 ts-morph: ^16.0.0 @@ -133,6 +136,7 @@ importers: eslint: 8.27.0 jest: 29.2.1_4f2ldd7um3b3u4eyvetyqsphze langium-cli: 0.5.0 + rimraf: 3.0.2 tmp: 0.2.1 ts-jest: 29.0.3_nvckv3qbfhmmsla6emqlkyje4a ts-node: 10.9.1_jcmx33t3olsvcxopqdljsohpme diff --git a/samples/todo/package.json b/samples/todo/package.json index 9efdf340d..99c23dc06 100644 --- a/samples/todo/package.json +++ b/samples/todo/package.json @@ -1,6 +1,6 @@ { "name": "todo", - "version": "0.2.12", + "version": "0.2.15", "private": true, "scripts": { "dev": "next dev", diff --git a/samples/todo/zenstack.config.json b/samples/todo/zenstack.config.json new file mode 100644 index 000000000..b1b6cafaf --- /dev/null +++ b/samples/todo/zenstack.config.json @@ -0,0 +1,3 @@ +{ + "log": ["info", "warn", "error"] +} diff --git a/tests/integration/tests/logging.test.ts b/tests/integration/tests/logging.test.ts index 6d0f9eb2b..c76d85c42 100644 --- a/tests/integration/tests/logging.test.ts +++ b/tests/integration/tests/logging.test.ts @@ -26,12 +26,12 @@ describe('Logging tests', () => { let gotInfoEmit = false; let gotQueryEmit = false; let gotVerboseEmit = false; - let gotErrorEmit = false; + let gotWarnEmit = false; let gotInfoStd = false; let gotQueryStd = false; let gotVerboseStd = false; - let gotErrorStd = false; + let gotWarnStd = false; console.log = jest.fn((...args) => { const msg = args?.[0] as string; @@ -46,10 +46,10 @@ describe('Logging tests', () => { } }); - console.error = jest.fn((...args) => { + console.warn = jest.fn((...args) => { const msg = args?.[0] as string; - if (msg.includes(':error')) { - gotErrorStd = true; + if (msg.includes(':warn')) { + gotWarnStd = true; } }); @@ -68,9 +68,9 @@ describe('Logging tests', () => { gotVerboseEmit = true; }); - service.$on('error', (event) => { - console.log('Got error', event); - gotErrorEmit = true; + service.$on('warn', (event) => { + console.log('Got warn', event); + gotWarnEmit = true; }); await makeClient('/api/data/User').post('/').send({ @@ -80,12 +80,12 @@ describe('Logging tests', () => { expect(gotQueryStd).toBeFalsy(); expect(gotVerboseStd).toBeFalsy(); expect(gotInfoStd).toBeFalsy(); - expect(gotErrorStd).toBeTruthy(); + expect(gotWarnStd).toBeTruthy(); expect(gotInfoEmit).toBeFalsy(); expect(gotQueryEmit).toBeFalsy(); expect(gotVerboseEmit).toBeFalsy(); - expect(gotErrorEmit).toBeFalsy(); + expect(gotWarnEmit).toBeFalsy(); }); it('logging with stdout', async () => { @@ -93,7 +93,7 @@ describe('Logging tests', () => { './zenstack.config.json', ` { - "log": ["query", "verbose", "info", "error"] + "log": ["query", "verbose", "info", "warn"] } ` ); @@ -104,12 +104,12 @@ describe('Logging tests', () => { let gotInfoEmit = false; let gotQueryEmit = false; let gotVerboseEmit = false; - let gotErrorEmit = false; + let gotWarnEmit = false; let gotInfoStd = false; let gotQueryStd = false; let gotVerboseStd = false; - let gotErrorStd = false; + let gotWarnStd = false; console.log = jest.fn((...args) => { const msg = args?.[0] as string; @@ -124,10 +124,10 @@ describe('Logging tests', () => { } }); - console.error = jest.fn((...args) => { + console.warn = jest.fn((...args) => { const msg = args?.[0] as string; - if (msg.includes(':error')) { - gotErrorStd = true; + if (msg.includes(':warn')) { + gotWarnStd = true; } }); @@ -146,9 +146,9 @@ describe('Logging tests', () => { gotVerboseEmit = true; }); - service.$on('error', (event) => { - console.log('Got error', event); - gotErrorEmit = true; + service.$on('warn', (event) => { + console.log('Got warn', event); + gotWarnEmit = true; }); await makeClient('/api/data/User').post('/').send({ @@ -158,12 +158,12 @@ describe('Logging tests', () => { expect(gotQueryStd).toBeTruthy(); expect(gotVerboseStd).toBeTruthy(); expect(gotInfoStd).toBeTruthy(); - expect(gotErrorStd).toBeTruthy(); + expect(gotWarnStd).toBeTruthy(); expect(gotInfoEmit).toBeFalsy(); expect(gotQueryEmit).toBeFalsy(); expect(gotVerboseEmit).toBeFalsy(); - expect(gotErrorEmit).toBeFalsy(); + expect(gotWarnEmit).toBeFalsy(); }); it('logging with event', async () => { @@ -175,7 +175,7 @@ describe('Logging tests', () => { { "level": "query", "emit": "event" }, { "level": "verbose", "emit": "event" }, { "level": "info", "emit": "event" }, - { "level": "error", "emit": "event" } + { "level": "warn", "emit": "event" } ] } ` @@ -187,12 +187,12 @@ describe('Logging tests', () => { let gotInfoEmit = false; let gotQueryEmit = false; let gotVerboseEmit = false; - let gotErrorEmit = false; + let gotWarnEmit = false; let gotInfoStd = false; let gotQueryStd = false; let gotVerboseStd = false; - let gotErrorStd = false; + let gotWarnStd = false; console.log = jest.fn((...args) => { const msg = args?.[0] as string; @@ -207,10 +207,10 @@ describe('Logging tests', () => { } }); - console.error = jest.fn((...args) => { + console.warn = jest.fn((...args) => { const msg = args?.[0] as string; - if (msg.includes('zenstack:error')) { - gotErrorStd = true; + if (msg.includes('zenstack:warn')) { + gotWarnStd = true; } }); @@ -233,10 +233,10 @@ describe('Logging tests', () => { gotVerboseEmit = true; }); - service.$on('error', (event) => { + service.$on('warn', (event) => { expect(event.timestamp).not.toBeUndefined(); expect(event.message).not.toBeUndefined(); - gotErrorEmit = true; + gotWarnEmit = true; }); await makeClient('/api/data/User').post('/').send({ @@ -246,11 +246,11 @@ describe('Logging tests', () => { expect(gotInfoEmit).toBeTruthy(); expect(gotQueryEmit).toBeTruthy(); expect(gotVerboseEmit).toBeTruthy(); - expect(gotErrorEmit).toBeTruthy(); + expect(gotWarnEmit).toBeTruthy(); expect(gotInfoStd).toBeFalsy(); expect(gotQueryStd).toBeFalsy(); expect(gotVerboseStd).toBeFalsy(); - expect(gotErrorStd).toBeFalsy(); + expect(gotWarnStd).toBeFalsy(); }); }); diff --git a/tests/integration/tests/password.test.ts b/tests/integration/tests/password.test.ts index 241b7d746..2bba24d78 100644 --- a/tests/integration/tests/password.test.ts +++ b/tests/integration/tests/password.test.ts @@ -28,7 +28,7 @@ describe('Password attribute tests', () => { }, }) .expect(async (resp) => - expect(compareSync('abc123', resp.body.password)) + expect(compareSync('abc123', resp.body.password)).toBeTruthy() ); await makeClient('/api/data/User/1') @@ -39,7 +39,7 @@ describe('Password attribute tests', () => { }, }) .expect(async (resp) => - expect(compareSync('abc456', resp.body.password)) + expect(compareSync('abc456', resp.body.password)).toBeTruthy() ); }); @@ -71,7 +71,9 @@ describe('Password attribute tests', () => { }, }) .expect(async (resp) => - expect(compareSync('abc456', resp.body.user.password)) + expect( + compareSync('abc456', resp.body.user.password) + ).toBeTruthy() ); }); }); From d32daf90093065a6f35d334511358bffc60d646a Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 8 Nov 2022 11:14:47 +0800 Subject: [PATCH 09/13] chore: prep for v0.3.0 (#61) --- package.json | 2 +- packages/internal/package.json | 2 +- packages/runtime/package.json | 2 +- packages/schema/package.json | 2 +- samples/todo/package-lock.json | 64 ++++++++++++++++++++-------------- samples/todo/package.json | 8 ++--- 6 files changed, 46 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index b640da001..9008d282a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "0.2.15", + "version": "0.3.0", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/internal/package.json b/packages/internal/package.json index 4ff9a625d..736d5cf22 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/internal", - "version": "0.2.15", + "version": "0.3.0", "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": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index b0779d542..adc8ae070 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "0.2.15", + "version": "0.3.0", "description": "This package contains runtime library for consuming client and server side code generated by ZenStack.", "repository": { "type": "git", diff --git a/packages/schema/package.json b/packages/schema/package.json index f618436de..4a2bc5ac9 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "ZenStack is a toolkit that simplifies full-stack development", - "version": "0.2.15", + "version": "0.3.0", "author": { "name": "ZenStack Team" }, diff --git a/samples/todo/package-lock.json b/samples/todo/package-lock.json index 73dd94718..a3d815e62 100644 --- a/samples/todo/package-lock.json +++ b/samples/todo/package-lock.json @@ -1,17 +1,17 @@ { "name": "todo", - "version": "0.2.12", + "version": "0.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "todo", - "version": "0.2.12", + "version": "0.3.0", "dependencies": { "@heroicons/react": "^2.0.12", "@prisma/client": "^4.4.0", - "@zenstackhq/internal": "^0.2.11", - "@zenstackhq/runtime": "^0.2.11", + "@zenstackhq/internal": "~0.3.0", + "@zenstackhq/runtime": "~0.3.0", "bcryptjs": "^2.4.3", "daisyui": "^2.31.0", "moment": "^2.29.4", @@ -35,7 +35,7 @@ "postcss": "^8.4.16", "tailwindcss": "^3.1.8", "typescript": "^4.6.2", - "zenstack": "^0.2.11" + "zenstack": "~0.3.0" } }, "node_modules/@babel/code-frame": { @@ -722,27 +722,28 @@ } }, "node_modules/@zenstackhq/internal": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.2.11.tgz", - "integrity": "sha512-BVh7MRBJwDpoRR90fDgiqAbUDj9fYsC3lVRUcOWSz6MaXCOztNX6y+YOKaoM+2lxjqjXfR82HNB1pSWlDlCkmQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.3.0.tgz", + "integrity": "sha512-zEp6rSiQwg/3aZeuWHhReoRK7P1rubbm79encVelL1JGBDQruw1wQ+5xm/LMMLH6ZHYDjfsJAlqX5yHfsYvtWg==", "dependencies": { "bcryptjs": "^2.4.3", "colors": "^1.4.0", "cuid": "^2.1.8", + "decimal.js": "^10.4.2", "deepcopy": "^2.1.0", "swr": "^1.3.0" }, "peerDependencies": { "@prisma/client": "^4.4.0", - "next": "12.3.1", + "next": "^12.3.1", "react": "^17.0.2 || ^18", "react-dom": "^17.0.2 || ^18" } }, "node_modules/@zenstackhq/runtime": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.2.11.tgz", - "integrity": "sha512-gWzM2WRqg0sbTkXGe2yG1E7y1LFw03g4oUy52OGTzHTCc51cszAJsYacEY0/eWFjmUZD0QF8jokD0NcgrMDKng==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.3.0.tgz", + "integrity": "sha512-+PpnXTijAxImn5jxDDAR/NJhNOkVD9kZ4NZvFgsAmyL/96dM68mRT0e//SL0b7LDgeH4QgSkykqtvOTmXsmHWg==", "dependencies": { "@zenstackhq/internal": "latest" }, @@ -1396,6 +1397,11 @@ } } }, + "node_modules/decimal.js": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz", + "integrity": "sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4520,12 +4526,12 @@ } }, "node_modules/zenstack": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.2.11.tgz", - "integrity": "sha512-ddrTHkAGC1KbxVNTCx0X7zt8E75w83YgZpz/bxpmSVWaRWusrcA53mV0feR/vSJZTiezWKiJl7DQj2cMfdoTog==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.3.0.tgz", + "integrity": "sha512-B0LYvU+dNQUOBguwRxBeQUnotdxt1AvxXmzRTQgwHZMgvxr59zbveAXnkt2sHMC44YiHe5YAxb3LEejFp5IxHQ==", "dev": true, "dependencies": { - "@zenstackhq/internal": "0.2.11", + "@zenstackhq/internal": "0.3.0", "change-case": "^4.1.2", "chevrotain": "^9.1.0", "colors": "^1.4.0", @@ -5017,21 +5023,22 @@ } }, "@zenstackhq/internal": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.2.11.tgz", - "integrity": "sha512-BVh7MRBJwDpoRR90fDgiqAbUDj9fYsC3lVRUcOWSz6MaXCOztNX6y+YOKaoM+2lxjqjXfR82HNB1pSWlDlCkmQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@zenstackhq/internal/-/internal-0.3.0.tgz", + "integrity": "sha512-zEp6rSiQwg/3aZeuWHhReoRK7P1rubbm79encVelL1JGBDQruw1wQ+5xm/LMMLH6ZHYDjfsJAlqX5yHfsYvtWg==", "requires": { "bcryptjs": "^2.4.3", "colors": "^1.4.0", "cuid": "^2.1.8", + "decimal.js": "^10.4.2", "deepcopy": "^2.1.0", "swr": "^1.3.0" } }, "@zenstackhq/runtime": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.2.11.tgz", - "integrity": "sha512-gWzM2WRqg0sbTkXGe2yG1E7y1LFw03g4oUy52OGTzHTCc51cszAJsYacEY0/eWFjmUZD0QF8jokD0NcgrMDKng==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.3.0.tgz", + "integrity": "sha512-+PpnXTijAxImn5jxDDAR/NJhNOkVD9kZ4NZvFgsAmyL/96dM68mRT0e//SL0b7LDgeH4QgSkykqtvOTmXsmHWg==", "requires": { "@zenstackhq/internal": "latest" } @@ -5500,6 +5507,11 @@ "ms": "2.1.2" } }, + "decimal.js": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz", + "integrity": "sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==" + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -7766,12 +7778,12 @@ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "zenstack": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.2.11.tgz", - "integrity": "sha512-ddrTHkAGC1KbxVNTCx0X7zt8E75w83YgZpz/bxpmSVWaRWusrcA53mV0feR/vSJZTiezWKiJl7DQj2cMfdoTog==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.3.0.tgz", + "integrity": "sha512-B0LYvU+dNQUOBguwRxBeQUnotdxt1AvxXmzRTQgwHZMgvxr59zbveAXnkt2sHMC44YiHe5YAxb3LEejFp5IxHQ==", "dev": true, "requires": { - "@zenstackhq/internal": "0.2.11", + "@zenstackhq/internal": "0.3.0", "change-case": "^4.1.2", "chevrotain": "^9.1.0", "colors": "^1.4.0", diff --git a/samples/todo/package.json b/samples/todo/package.json index 99c23dc06..a25a9bd3e 100644 --- a/samples/todo/package.json +++ b/samples/todo/package.json @@ -1,6 +1,6 @@ { "name": "todo", - "version": "0.2.15", + "version": "0.3.0", "private": true, "scripts": { "dev": "next dev", @@ -20,8 +20,8 @@ "dependencies": { "@heroicons/react": "^2.0.12", "@prisma/client": "^4.4.0", - "@zenstackhq/internal": "^0.2.11", - "@zenstackhq/runtime": "^0.2.11", + "@zenstackhq/internal": "~0.3.0", + "@zenstackhq/runtime": "~0.3.0", "bcryptjs": "^2.4.3", "daisyui": "^2.31.0", "moment": "^2.29.4", @@ -45,6 +45,6 @@ "postcss": "^8.4.16", "tailwindcss": "^3.1.8", "typescript": "^4.6.2", - "zenstack": "^0.2.11" + "zenstack": "~0.3.0" } } From af45b31bdfe472ec431e5dbc5188e9d69dfebd90 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Wed, 9 Nov 2022 10:10:35 +0800 Subject: [PATCH 10/13] chore: fix wrong error message for policy rejection (#72) --- packages/internal/src/handler/data/policy-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/internal/src/handler/data/policy-utils.ts b/packages/internal/src/handler/data/policy-utils.ts index 317270195..5c15df1ea 100644 --- a/packages/internal/src/handler/data/policy-utils.ts +++ b/packages/internal/src/handler/data/policy-utils.ts @@ -437,7 +437,7 @@ export async function checkPolicyForIds( const gap = ids.filter((id) => !filteredIds.includes(id)); throw new RequestHandlerError( ServerErrorCode.DENIED_BY_POLICY, - `denied by policy before update: entities failed '${operation}' check, ${model}#[${gap.join( + `denied by policy: entities failed '${operation}' check, ${model}#[${gap.join( ', ' )}]` ); From 658c924652f81a60485bd500214595922e52f85c Mon Sep 17 00:00:00 2001 From: Yiming <104139426+ymc9@users.noreply.github.com> Date: Wed, 9 Nov 2022 11:19:32 +0800 Subject: [PATCH 11/13] chore: pin version of "colors" dependency due to security vulnerability (#73) --- packages/internal/package.json | 2 +- packages/schema/package.json | 2 +- pnpm-lock.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/internal/package.json b/packages/internal/package.json index 736d5cf22..1f65048d8 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -26,7 +26,7 @@ ], "dependencies": { "bcryptjs": "^2.4.3", - "colors": "^1.4.0", + "colors": "1.4.0", "cuid": "^2.1.8", "decimal.js": "^10.4.2", "deepcopy": "^2.1.0", diff --git a/packages/schema/package.json b/packages/schema/package.json index 4a2bc5ac9..581acc6ff 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -84,7 +84,7 @@ "@zenstackhq/internal": "workspace:*", "change-case": "^4.1.2", "chevrotain": "^9.1.0", - "colors": "^1.4.0", + "colors": "1.4.0", "commander": "^8.3.0", "langium": "^0.5.0", "pluralize": "^8.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5bfa7c4e0..c50ae3e29 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: '@types/node': ^14.18.29 '@types/uuid': ^8.3.4 bcryptjs: ^2.4.3 - colors: ^1.4.0 + colors: 1.4.0 cuid: ^2.1.8 decimal.js: ^10.4.2 deepcopy: ^2.1.0 @@ -78,7 +78,7 @@ importers: '@zenstackhq/internal': workspace:* change-case: ^4.1.2 chevrotain: ^9.1.0 - colors: ^1.4.0 + colors: 1.4.0 commander: ^8.3.0 concurrently: ^7.4.0 esbuild: ^0.15.12 From 5f2458876fe4431bfab912eb8be5f50683cfddaa Mon Sep 17 00:00:00 2001 From: Yiming <104139426+ymc9@users.noreply.github.com> Date: Wed, 9 Nov 2022 20:48:03 +0800 Subject: [PATCH 12/13] chore: update README (#74) --- README.md | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index a976aef69..32d9435b4 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,15 @@ +## 📣 Our Discord Server is Live + +[Join us](https://discord.gg/6HhebQynfz) to chat about questions, bugs, plans, or anything off the top of your head. + ## What is ZenStack? -ZenStack is a toolkit for simplifying full-stack development with Node.js web frameworks like [Next.js](https://nextjs.org/), [Nuxt.js](https://nuxtjs.org/) , and [SvelteKit](https://kit.svelte.dev/) , using Typescript language. +ZenStack is a toolkit for simplifying full-stack development with [Next.js](https://nextjs.org/), using Typescript. -Thanks to the increasing power of frameworks, building a complex web app within one unified framework is becoming more practical than ever. However, you'll still need to spend a significant amount of energy designing and building up your app's server-side part. +Thanks to the increasing power of Next.js, building a complex web app within one unified framework is becoming more practical than ever. However, you'll still need to spend a significant amount of energy designing and building up your app's server-side part. Things that make you stressed include: @@ -60,7 +64,7 @@ model Post { } ``` -- Auto-generated CRUD services and strongly typed front-end library +- Auto-generated CRUD services and strongly typed React hooks ```jsx // React example @@ -77,26 +81,22 @@ return ( ); ``` -Since CRUD APIs are automatically generated with access policies injected, you can safely implement most of your business logic in your front-end code. Read operations never return data that's not supposed to be visible to the current user, and writes will be rejected if unauthorized. The generated front-end library also supports nested writes, allowing you to make a batch of creates/updates atomically, eliminating the need for explicitly using a transaction. +Since CRUD APIs are automatically generated with access policies injected, you can safely implement most of your business logic in your front-end code. Read operations never return data that's not supposed to be visible to the current user, and writes will be rejected if unauthorized. -ZenStack is heavily inspired and built over [Prisma](https://www.prisma.io) ORM, which is, in our opinion, the best ORM toolkit in the market. Familiarity with Prisma should make it easy to pick up ZenStack, but it's not a prerequisite since the modeling language is intuitive and the development workflow is straightforward. +ZenStack is heavily inspired and built above [Prisma](https://www.prisma.io) ORM, which is, in our opinion, the best ORM toolkit in the market. Familiarity with Prisma should make it easy to pick up ZenStack, but it's not a prerequisite since the modeling language is intuitive and the development workflow is straightforward. ## Getting started -### [For Next.js](docs/get-started/next-js.md) - -### For Nuxt.js - -### For SvelteKit +[A step by step guide for getting started](docs/get-started/next-js.md) ## How does it work? ZenStack has four essential responsibilities: -1. Modeling data and mapping the model to DB schema and program types +1. Modeling data and mapping the model to DB schema and programmable client library 1. Integrating with authentication 1. Generating CRUD APIs and enforcing data access policies -1. Providing type-safe client CRUD library +1. Providing type-safe React hooks Let's briefly go through each of them in this section. @@ -158,9 +158,11 @@ When client code sends a query to list all `Post`s, ZenStack's generated code in Similar procedures are applied to write operations and more complex queries involving nested reads and writes. To ensure good performance, ZenStack generates conditions statically, so it doesn't need to introspect ZModel at runtime. The engine also makes the best effort to push down policy constraints to the database to avoid fetching data unnecessarily and discarding afterward. -Please **beware** that policy checking is only applied when data access is done using the generated client-side hooks or, equivalently, the RESTful API. If you use `service.db` to access the database directly from server-side code, policies are bypassed, and you have to do all necessary checking by yourself. We've planned to add helper functions for "injecting" the policy checking on the server side in the future. +Please **BEWARE** that policy checking is only applied when data access is done using the generated client-side hooks or, equivalently, the RESTful API. If you use `service.db` to access the database directly from server-side code, policies are bypassed, and you have to do all necessary checking by yourself. We've planned to add helper functions for "injecting" the policy checking on the server side in the future. -### Type-safe client library +### Type-safe React hooks + +Strongly-typed React hooks are generated for CRUD operations, saving the need to write boilerplate code. Thanks to Prisma's power, ZenStack generates accurate Typescript types for your data models: @@ -174,11 +176,7 @@ The cool thing is that the generated types are shared between client-side and se ### Client-side -#### For Next.js - -The generated CRUD services should be mounted at `/api/zenstack` route. React hooks are generated for calling these services without explicitly writing Http requests. - -The following hooks methods are generated: +The generated CRUD services should be mounted at `/api/zenstack` route. The following React hooks are generated for each data model: - find: listing entities with filtering, ordering, pagination, and nested relations From babca1979af2c6583f2ada84ca976c6605a73717 Mon Sep 17 00:00:00 2001 From: Yiming <104139426+ymc9@users.noreply.github.com> Date: Wed, 9 Nov 2022 20:58:48 +0800 Subject: [PATCH 13/13] release: v0.3.0 (#62) (#76)