diff --git a/.vscode/launch.json b/.vscode/launch.json index 193903fc4..18b3b5721 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,20 +5,6 @@ { "version": "0.2.0", "configurations": [ - { - "name": "Generate for Todo Sample", - "program": "${workspaceFolder}/packages/schema/dist/bin/cli", - "cwd": "${workspaceFolder}/samples/todo/", - "args": [ - "generate" - ], - "request": "launch", - "skipFiles": ["/**"], - "type": "node", - "env": { - "NODE_PATH": "${workspaceFolder}/samples/todo/node_modules" - } - }, { "name": "Attach", "port": 9229, @@ -42,13 +28,6 @@ "skipFiles": ["/**"], "sourceMaps": true, "outFiles": ["${workspaceFolder}/packages/schema/bundle/**/*.js"] - }, - { - "name": "Todo sample: debug server-side", - "type": "node-terminal", - "request": "launch", - "command": "pnpm dev", - "cwd": "${workspaceFolder}/samples/todo/" } ] } diff --git a/README.md b/README.md index bf04b29b5..41cb26e15 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ Our goal is to let you save time writing boilerplate code and focus on building ## How it works +> Read full documentation at 👉🏻 [zenstack.dev](https://zenstack.dev). + ZenStack incrementally extends Prisma's power with the following four layers: ### 1. ZModel - an extended Prisma schema language @@ -181,7 +183,11 @@ Check out the [Collaborative Todo App](https://zenstack-todo.vercel.app/) for a ## Community -Join our [discord server](https://go.zenstack.dev/chat) for chat and updates! +Join our [discord server](https://discord.gg/Ykhr738dUe) for chat and updates! + +## Contributing + +If you like ZenStack, join us to make it a better tool! Please use the [Contributing Guide](CONTRIBUTING.md) for details on how to get started, and don't hesitate to join [Discord](https://discord.gg/Ykhr738dUe) to share your thoughts. ## License diff --git a/package.json b/package.json index 5d00c9db1..ba5e02efa 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "zenstack-monorepo", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "description": "", "scripts": { "build": "pnpm -r build", - "test": "pnpm -r run test --silent", - "test-ci": "pnpm -r run test --silent", + "test": "ZENSTACK_TEST=1 pnpm -r run test --silent", + "test-ci": "ZENSTACK_TEST=1 pnpm -r run test --silent", "lint": "pnpm -r lint", "publish-all": "pnpm --filter \"./packages/**\" -r publish --access public", "publish-preview": "pnpm --filter \"./packages/**\" -r publish --registry http://localhost:4873" diff --git a/packages/language/package.json b/packages/language/package.json index 0ea67b480..1f1d217b0 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "displayName": "ZenStack modeling language compiler", "description": "ZenStack modeling language compiler", "homepage": "https://zenstack.dev", diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index ea92e19fc..4791eb354 100644 --- a/packages/plugins/openapi/package.json +++ b/packages/plugins/openapi/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/openapi", "displayName": "ZenStack Plugin and Runtime for OpenAPI", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "description": "ZenStack plugin and runtime supporting OpenAPI", "main": "index.js", "repository": { diff --git a/packages/plugins/swr/package.json b/packages/plugins/swr/package.json index ad39c1022..8d95d6831 100644 --- a/packages/plugins/swr/package.json +++ b/packages/plugins/swr/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/swr", "displayName": "ZenStack plugin for generating SWR hooks", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "description": "ZenStack plugin for generating SWR hooks", "main": "index.js", "repository": { diff --git a/packages/plugins/tanstack-query/package.json b/packages/plugins/tanstack-query/package.json index c8b487d67..9cf4075ab 100644 --- a/packages/plugins/tanstack-query/package.json +++ b/packages/plugins/tanstack-query/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/tanstack-query", "displayName": "ZenStack plugin for generating tanstack-query hooks", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "description": "ZenStack plugin for generating tanstack-query hooks", "main": "index.js", "repository": { diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index 632b03ecc..eedaca491 100644 --- a/packages/plugins/trpc/package.json +++ b/packages/plugins/trpc/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/trpc", "displayName": "ZenStack plugin for tRPC", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 6a85ee5e8..4b07fe118 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", diff --git a/packages/runtime/src/enhancements/model-meta.ts b/packages/runtime/src/enhancements/model-meta.ts index 0211304d7..8109bbacc 100644 --- a/packages/runtime/src/enhancements/model-meta.ts +++ b/packages/runtime/src/enhancements/model-meta.ts @@ -1,20 +1,24 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import { lowerCaseFirst } from 'lower-case-first'; -import { ModelMeta } from './types'; import path from 'path'; +import { ModelMeta } from './types'; /** * Load model meta from standard location. */ export function getDefaultModelMeta(): ModelMeta { try { - if (process.env.NODE_ENV === 'test') { - // handling the case when running as tests, resolve relative to CWD - return require(path.join(process.cwd(), 'node_modules', '.zenstack', 'model-meta')); - } else { - return require('.zenstack/model-meta').default; - } + // normal load + return require('.zenstack/model-meta').default; } catch { + if (process.env.ZENSTACK_TEST === '1') { + try { + // special handling for running as tests, try resolving relative to CWD + return require(path.join(process.cwd(), 'node_modules', '.zenstack', 'model-meta')).default; + } catch { + throw new Error('Model meta cannot be loaded. Please make sure "zenstack generate" has been run.'); + } + } throw new Error('Model meta cannot be loaded. Please make sure "zenstack generate" has been run.'); } } diff --git a/packages/runtime/src/enhancements/policy/index.ts b/packages/runtime/src/enhancements/policy/index.ts index 59c4fb453..1fbe249f1 100644 --- a/packages/runtime/src/enhancements/policy/index.ts +++ b/packages/runtime/src/enhancements/policy/index.ts @@ -1,5 +1,7 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-explicit-any */ +import path from 'path'; import { AuthUser, DbClientContract } from '../../types'; import { getDefaultModelMeta } from '../model-meta'; import { makeProxy } from '../proxy'; @@ -74,9 +76,18 @@ export function withPolicy( function getDefaultPolicy(): PolicyDef { try { - // eslint-disable-next-line @typescript-eslint/no-var-requires return require('.zenstack/policy').default; } catch { + if (process.env.ZENSTACK_TEST === '1') { + try { + // special handling for running as tests, try resolving relative to CWD + return require(path.join(process.cwd(), 'node_modules', '.zenstack', 'policy')).default; + } catch { + throw new Error( + 'Policy definition cannot be loaded from default location. Please make sure "zenstack generate" has been run.' + ); + } + } throw new Error( 'Policy definition cannot be loaded from default location. Please make sure "zenstack generate" has been run.' ); @@ -88,6 +99,14 @@ function getDefaultZodSchemas(): ZodSchemas | undefined { // eslint-disable-next-line @typescript-eslint/no-var-requires return require('.zenstack/zod'); } catch { + if (process.env.ZENSTACK_TEST === '1') { + try { + // special handling for running as tests, try resolving relative to CWD + return require(path.join(process.cwd(), 'node_modules', '.zenstack', 'zod')); + } catch { + return undefined; + } + } return undefined; } } diff --git a/packages/schema/package.json b/packages/schema/package.json index dfb1666b2..4b56e0ef8 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "A toolkit for building secure CRUD apps with Next.js + Typescript", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "author": { "name": "ZenStack Team" }, diff --git a/packages/sdk/package.json b/packages/sdk/package.json index e6512706e..5074646c7 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { diff --git a/packages/sdk/src/utils.ts b/packages/sdk/src/utils.ts index 3b26c01f1..edd0fa84e 100644 --- a/packages/sdk/src/utils.ts +++ b/packages/sdk/src/utils.ts @@ -227,7 +227,7 @@ export function getFunctionExpressionContext(funcDecl: FunctionDecl) { const funcAllowedContext: ExpressionContext[] = []; const funcAttr = funcDecl.attributes.find((attr) => attr.decl.$refText === '@@@expressionContext'); if (funcAttr) { - const contextArg = getAttributeArg(funcAttr, 'context'); + const contextArg = funcAttr.args[0].value; if (isArrayExpr(contextArg)) { contextArg.items.forEach((item) => { if (isEnumFieldReference(item)) { diff --git a/packages/server/package.json b/packages/server/package.json index 7a12d27da..fd71504f8 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "displayName": "ZenStack Server-side Adapters", "description": "ZenStack server-side adapters", "homepage": "https://zenstack.dev", diff --git a/packages/server/src/express/middleware.ts b/packages/server/src/express/middleware.ts index 71a1c6ea0..492004a66 100644 --- a/packages/server/src/express/middleware.ts +++ b/packages/server/src/express/middleware.ts @@ -14,6 +14,17 @@ export interface MiddlewareOptions extends AdapterBaseOptions { * Callback for getting a PrismaClient for the given request */ getPrisma: (req: Request, res: Response) => unknown | Promise; + + /** + * Controls if the middleware directly sends a response. If set to false, + * the response is stored in the `res.locals` object and then the middleware + * calls the `next()` function to pass the control to the next middleware. + * Subsequent middleware or request handlers need to make sure to send + * a response. + * + * Defaults to true; + */ + sendResponse?: boolean; } /** @@ -33,13 +44,18 @@ const factory = (options: MiddlewareOptions): Handler => { const requestHandler = options.handler || RPCAPIHandler(); const useSuperJson = options.useSuperJson === true; - return async (request, response) => { + return async (request, response, next) => { const prisma = (await options.getPrisma(request, response)) as DbClientContract; + const { sendResponse } = options; + + if (sendResponse === false && !prisma) { + throw new Error('unable to get prisma from request context'); + } + if (!prisma) { - response + return response .status(500) .json(marshalToObject({ message: 'unable to get prisma from request context' }, useSuperJson)); - return; } let query: Record = {}; @@ -56,8 +72,10 @@ const factory = (options: MiddlewareOptions): Handler => { } query = buildUrlQuery(rawQuery, useSuperJson); } catch { - response.status(400).json(marshalToObject({ message: 'invalid query parameters' }, useSuperJson)); - return; + if (sendResponse === false) { + throw new Error('invalid query parameters'); + } + return response.status(400).json(marshalToObject({ message: 'invalid query parameters' }, useSuperJson)); } try { @@ -71,9 +89,20 @@ const factory = (options: MiddlewareOptions): Handler => { zodSchemas, logger: options.logger, }); - response.status(r.status).json(marshalToObject(r.body, useSuperJson)); + if (sendResponse === false) { + // attach response and pass control to the next middleware + response.locals = { + status: r.status, + body: r.body, + }; + return next(); + } + return response.status(r.status).json(marshalToObject(r.body, useSuperJson)); } catch (err) { - response + if (sendResponse === false) { + throw err; + } + return response .status(500) .json(marshalToObject({ message: `An unhandled error occurred: ${err}` }, useSuperJson)); } diff --git a/packages/server/tests/adapter/express.test.ts b/packages/server/tests/adapter/express.test.ts index df96ec7a2..7121edcf9 100644 --- a/packages/server/tests/adapter/express.test.ts +++ b/packages/server/tests/adapter/express.test.ts @@ -251,3 +251,30 @@ describe('Express adapter tests - rest handler', () => { expect(await prisma.user.findMany()).toHaveLength(0); }); }); + +describe('Express adapter tests - rest handler with customMiddleware', () => { + it('run middleware', async () => { + const { prisma, zodSchemas, modelMeta } = await loadSchema(schema); + + const app = express(); + app.use(bodyParser.json()); + app.use( + '/api', + ZenStackMiddleware({ + getPrisma: () => prisma, + modelMeta, + zodSchemas, + handler: RESTAPIHandler({ endpoint: 'http://localhost/api' }), + sendResponse: false, + }) + ); + + app.use((req, res) => { + res.status(res.locals.status).json({ message: res.locals.body }); + }); + + const r = await request(app).get(makeUrl('/api/post/1')); + expect(r.status).toBe(404); + expect(r.body.message).toHaveProperty('errors'); + }); +}); diff --git a/packages/testtools/package.json b/packages/testtools/package.json index e882cc664..90dc5302c 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "description": "ZenStack Test Tools", "main": "index.js", "publishConfig": { diff --git a/tests/integration/test-run/package-lock.json b/tests/integration/test-run/package-lock.json index df60b97df..453ba7693 100644 --- a/tests/integration/test-run/package-lock.json +++ b/tests/integration/test-run/package-lock.json @@ -126,7 +126,7 @@ }, "../../../packages/runtime/dist": { "name": "@zenstackhq/runtime", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "license": "MIT", "dependencies": { "@paralleldrive/cuid2": "^2.2.0", @@ -160,7 +160,7 @@ }, "../../../packages/schema/dist": { "name": "zenstack", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "hasInstallScript": true, "license": "MIT", "dependencies": {