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 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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" } }