From d7dbba9477302307a071bc7d96be250b8e27700b Mon Sep 17 00:00:00 2001 From: Yiming Date: Sat, 16 Mar 2024 18:01:12 -0700 Subject: [PATCH 1/6] chore: update jetbrains changelog (#1147) --- packages/ide/jetbrains/CHANGELOG.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/ide/jetbrains/CHANGELOG.md b/packages/ide/jetbrains/CHANGELOG.md index 1fa15f2eb..226419eb2 100644 --- a/packages/ide/jetbrains/CHANGELOG.md +++ b/packages/ide/jetbrains/CHANGELOG.md @@ -1,11 +1,23 @@ # Changelog ## [Unreleased] + +### Fixed + +- General improvements to language service. + +## 1.9.0 + ### Added -- Added support to complex usage of `@@index` attribute like `@@index([content(ops: raw("gin_trgm_ops"))], type: Gin)`. + +- Added support to complex usage of `@@index` attribute like `@@index([content(ops: raw("gin_trgm_ops"))], type: Gin)`. + ### Fixed -- Fixed several ZModel validation issues related to model inheritance. + +- Fixed several ZModel validation issues related to model inheritance. ## 1.7.0 + ### Added -- Auto-completion is now supported inside attributes. + +- Auto-completion is now supported inside attributes. From 24b189eb61d874ed269e9527ee5ed9c60a5955c9 Mon Sep 17 00:00:00 2001 From: Jiasheng Date: Tue, 19 Mar 2024 12:27:35 +0800 Subject: [PATCH 2/6] test: fix test failing in windows (#1153) --- packages/language/package.json | 2 +- packages/misc/redwood/package.json | 2 +- packages/plugins/openapi/package.json | 2 +- .../openapi/tests/openapi-restful.test.ts | 16 ++++++------- .../plugins/openapi/tests/openapi-rpc.test.ts | 24 +++++++++---------- packages/plugins/swr/package.json | 2 +- packages/plugins/swr/tests/swr.test.ts | 6 ++--- packages/plugins/tanstack-query/package.json | 2 +- .../tanstack-query/tests/plugin.test.ts | 14 +++++------ packages/plugins/trpc/package.json | 2 +- packages/plugins/trpc/tests/trpc.test.ts | 22 ++++++++--------- packages/testtools/src/schema.ts | 9 ++++--- 12 files changed, 53 insertions(+), 50 deletions(-) diff --git a/packages/language/package.json b/packages/language/package.json index 50758ce23..b6e146450 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -9,7 +9,7 @@ "generate": "langium generate && npx ts-node script/generate-plist.ts", "watch": "concurrently \"langium generate --watch\" \"tsc --watch\"", "lint": "eslint src --ext ts", - "build": "pnpm lint --max-warnings=0 && pnpm clean && pnpm generate && tsc && copyfiles -F ./README.md ./LICENSE ./package.json 'syntaxes/**/*' dist && pnpm pack dist --pack-destination '../../../.build'", + "build": "pnpm lint --max-warnings=0 && pnpm clean && pnpm generate && tsc && copyfiles -F ./README.md ./LICENSE ./package.json 'syntaxes/**/*' dist && pnpm pack dist --pack-destination ../../../.build", "prepublishOnly": "pnpm build" }, "publishConfig": { diff --git a/packages/misc/redwood/package.json b/packages/misc/redwood/package.json index d95ef1925..207173636 100644 --- a/packages/misc/redwood/package.json +++ b/packages/misc/redwood/package.json @@ -9,7 +9,7 @@ }, "scripts": { "clean": "rimraf dist", - "build": "pnpm lint --max-warnings=0 && pnpm clean && tsc && pnpm pack dist --pack-destination '../../../.build'", + "build": "pnpm lint --max-warnings=0 && pnpm clean && tsc && pnpm pack dist --pack-destination ../../../.build", "watch": "tsc --watch", "lint": "eslint src --ext ts", "prepublishOnly": "pnpm build" diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index 3db67dead..a8b6f1d49 100644 --- a/packages/plugins/openapi/package.json +++ b/packages/plugins/openapi/package.json @@ -14,7 +14,7 @@ }, "scripts": { "clean": "rimraf dist", - "build": "pnpm lint --max-warnings=0 && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE dist && copyfiles -u 1 ./src/plugin.zmodel dist && pnpm pack dist --pack-destination '../../../../.build'", + "build": "pnpm lint --max-warnings=0 && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE dist && copyfiles -u 1 ./src/plugin.zmodel dist && pnpm pack dist --pack-destination ../../../../.build", "watch": "tsc --watch", "lint": "eslint src --ext ts", "test": "jest", diff --git a/packages/plugins/openapi/tests/openapi-restful.test.ts b/packages/plugins/openapi/tests/openapi-restful.test.ts index fb01e390e..9e84ad047 100644 --- a/packages/plugins/openapi/tests/openapi-restful.test.ts +++ b/packages/plugins/openapi/tests/openapi-restful.test.ts @@ -4,7 +4,7 @@ import OpenAPIParser from '@readme/openapi-parser'; import { getLiteral, getObjectLiteral } from '@zenstackhq/sdk'; import { Model, Plugin, isPlugin } from '@zenstackhq/sdk/ast'; -import { loadZModelAndDmmf } from '@zenstackhq/testtools'; +import { loadZModelAndDmmf, normalizePath } from '@zenstackhq/testtools'; import fs from 'fs'; import path from 'path'; import * as tmp from 'tmp'; @@ -16,7 +16,7 @@ describe('Open API Plugin RESTful Tests', () => { for (const specVersion of ['3.0.0', '3.1.0']) { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' specVersion = '${specVersion}' } @@ -114,7 +114,7 @@ model Bar { it('options', async () => { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' specVersion = '3.0.0' title = 'My Awesome API' version = '1.0.0' @@ -151,7 +151,7 @@ model User { it('security schemes valid', async () => { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' securitySchemes = { myBasic: { type: 'http', scheme: 'basic' }, myBearer: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, @@ -198,7 +198,7 @@ model Post { it('security model level override', async () => { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' securitySchemes = { myBasic: { type: 'http', scheme: 'basic' } } @@ -230,7 +230,7 @@ model User { it('security schemes invalid', async () => { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' securitySchemes = { myBasic: { type: 'invalid', scheme: 'basic' } } @@ -251,7 +251,7 @@ model User { it('ignored model used as relation', async () => { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' } model User { @@ -284,7 +284,7 @@ model Post { for (const specVersion of ['3.0.0', '3.1.0']) { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' specVersion = '${specVersion}' } diff --git a/packages/plugins/openapi/tests/openapi-rpc.test.ts b/packages/plugins/openapi/tests/openapi-rpc.test.ts index c0cb74ab6..8e8e3a6ac 100644 --- a/packages/plugins/openapi/tests/openapi-rpc.test.ts +++ b/packages/plugins/openapi/tests/openapi-rpc.test.ts @@ -4,7 +4,7 @@ import OpenAPIParser from '@readme/openapi-parser'; import { getLiteral, getObjectLiteral } from '@zenstackhq/sdk'; import { Model, Plugin, isPlugin } from '@zenstackhq/sdk/ast'; -import { loadZModelAndDmmf } from '@zenstackhq/testtools'; +import { loadZModelAndDmmf, normalizePath } from '@zenstackhq/testtools'; import fs from 'fs'; import path from 'path'; import * as tmp from 'tmp'; @@ -16,7 +16,7 @@ describe('Open API Plugin RPC Tests', () => { for (const specVersion of ['3.0.0', '3.1.0']) { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' specVersion = '${specVersion}' } @@ -127,7 +127,7 @@ model Bar { it('options', async () => { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' specVersion = '3.0.0' title = 'My Awesome API' version = '1.0.0' @@ -164,7 +164,7 @@ model User { it('security schemes valid', async () => { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' securitySchemes = { myBasic: { type: 'http', scheme: 'basic' }, myBearer: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, @@ -198,7 +198,7 @@ model User { it('security schemes invalid', async () => { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' securitySchemes = { myBasic: { type: 'invalid', scheme: 'basic' } } @@ -219,7 +219,7 @@ model User { it('security model level override', async () => { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' securitySchemes = { myBasic: { type: 'http', scheme: 'basic' } } @@ -247,7 +247,7 @@ model User { it('security operation level override', async () => { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' securitySchemes = { myBasic: { type: 'http', scheme: 'basic' } } @@ -280,7 +280,7 @@ model User { it('security inferred', async () => { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' securitySchemes = { myBasic: { type: 'http', scheme: 'basic' } } @@ -306,7 +306,7 @@ model User { it('v3.1.0 fields', async () => { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' summary = 'awesome api' } @@ -330,7 +330,7 @@ model User { it('ignored model used as relation', async () => { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' } model User { @@ -362,7 +362,7 @@ model Post { for (const specVersion of ['3.0.0', '3.1.0']) { const { model, dmmf, modelFile } = await loadZModelAndDmmf(` plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' specVersion = '${specVersion}' } @@ -408,7 +408,7 @@ generator js { } plugin openapi { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' } enum role { diff --git a/packages/plugins/swr/package.json b/packages/plugins/swr/package.json index 0d1fb7147..376ad2e3b 100644 --- a/packages/plugins/swr/package.json +++ b/packages/plugins/swr/package.json @@ -10,7 +10,7 @@ }, "scripts": { "clean": "rimraf dist", - "build": "pnpm lint --max-warnings=0 && pnpm clean && tsc && tsup-node --config ./tsup.config.ts && copyfiles ./package.json ./README.md ./LICENSE dist && pnpm pack dist --pack-destination '../../../../.build'", + "build": "pnpm lint --max-warnings=0 && pnpm clean && tsc && tsup-node --config ./tsup.config.ts && copyfiles ./package.json ./README.md ./LICENSE dist && pnpm pack dist --pack-destination ../../../../.build", "watch": "concurrently \"tsc --watch\" \"tsup-node --config ./tsup.config.ts --watch\"", "lint": "eslint src --ext ts", "test": "jest", diff --git a/packages/plugins/swr/tests/swr.test.ts b/packages/plugins/swr/tests/swr.test.ts index 9d198269b..d12c3b37b 100644 --- a/packages/plugins/swr/tests/swr.test.ts +++ b/packages/plugins/swr/tests/swr.test.ts @@ -1,6 +1,6 @@ /// -import { loadSchema } from '@zenstackhq/testtools'; +import { loadSchema, normalizePath } from '@zenstackhq/testtools'; import path from 'path'; describe('SWR Plugin Tests', () => { @@ -50,7 +50,7 @@ model Foo { await loadSchema( ` plugin swr { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = '$projectRoot/hooks' } @@ -60,7 +60,7 @@ ${sharedModel} provider: 'postgresql', pushDb: false, extraDependencies: [ - `${path.join(__dirname, '../dist')}`, + `${normalizePath(path.join(__dirname, '../dist'))}`, 'react@18.2.0', '@types/react@18.2.0', 'swr@^2', diff --git a/packages/plugins/tanstack-query/package.json b/packages/plugins/tanstack-query/package.json index 8d034e8f4..69c3f5fda 100644 --- a/packages/plugins/tanstack-query/package.json +++ b/packages/plugins/tanstack-query/package.json @@ -66,7 +66,7 @@ }, "scripts": { "clean": "rimraf dist", - "build": "pnpm lint --max-warnings=0 && pnpm clean && tsc && tsup-node --config ./tsup.config.ts && tsup-node --config ./tsup-v5.config.ts && node scripts/postbuild && copyfiles ./package.json ./README.md ./LICENSE dist && pnpm pack dist --pack-destination '../../../../.build'", + "build": "pnpm lint --max-warnings=0 && pnpm clean && tsc && tsup-node --config ./tsup.config.ts && tsup-node --config ./tsup-v5.config.ts && node scripts/postbuild && copyfiles ./package.json ./README.md ./LICENSE dist && pnpm pack dist --pack-destination ../../../../.build", "watch": "concurrently \"tsc --watch\" \"tsup-node --config ./tsup.config.ts --watch\" \"tsup-node --config ./tsup-v5.config.ts --watch\"", "lint": "eslint src --ext ts", "test": "jest", diff --git a/packages/plugins/tanstack-query/tests/plugin.test.ts b/packages/plugins/tanstack-query/tests/plugin.test.ts index 38370d38a..824174ba5 100644 --- a/packages/plugins/tanstack-query/tests/plugin.test.ts +++ b/packages/plugins/tanstack-query/tests/plugin.test.ts @@ -1,6 +1,6 @@ /// -import { loadSchema } from '@zenstackhq/testtools'; +import { loadSchema, normalizePath } from '@zenstackhq/testtools'; import path from 'path'; describe('Tanstack Query Plugin Tests', () => { @@ -50,7 +50,7 @@ model Foo { await loadSchema( ` plugin tanstack { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = '$projectRoot/hooks' target = 'react' } @@ -71,7 +71,7 @@ ${sharedModel} await loadSchema( ` plugin tanstack { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = '$projectRoot/hooks' target = 'react' version = 'v5' @@ -93,7 +93,7 @@ ${sharedModel} await loadSchema( ` plugin tanstack { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = '$projectRoot/hooks' target = 'vue' } @@ -114,7 +114,7 @@ ${sharedModel} await loadSchema( ` plugin tanstack { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = '$projectRoot/hooks' target = 'vue' version = 'v5' @@ -136,7 +136,7 @@ ${sharedModel} await loadSchema( ` plugin tanstack { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = '$projectRoot/hooks' target = 'svelte' } @@ -157,7 +157,7 @@ ${sharedModel} await loadSchema( ` plugin tanstack { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = '$projectRoot/hooks' target = 'svelte' version = 'v5' diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index ad76965c2..b0a5a28c9 100644 --- a/packages/plugins/trpc/package.json +++ b/packages/plugins/trpc/package.json @@ -10,7 +10,7 @@ }, "scripts": { "clean": "rimraf dist", - "build": "pnpm lint --max-warnings=0 && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE 'res/**/*' dist && pnpm pack dist --pack-destination '../../../../.build'", + "build": "pnpm lint --max-warnings=0 && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE 'res/**/*' dist && pnpm pack dist --pack-destination ../../../../.build", "watch": "tsc --watch", "lint": "eslint src --ext ts", "test": "jest", diff --git a/packages/plugins/trpc/tests/trpc.test.ts b/packages/plugins/trpc/tests/trpc.test.ts index ca4a9c14d..4c79c740a 100644 --- a/packages/plugins/trpc/tests/trpc.test.ts +++ b/packages/plugins/trpc/tests/trpc.test.ts @@ -1,6 +1,6 @@ /// -import { loadSchema } from '@zenstackhq/testtools'; +import { loadSchema, normalizePath } from '@zenstackhq/testtools'; import fs from 'fs'; import path from 'path'; @@ -19,7 +19,7 @@ describe('tRPC Plugin Tests', () => { await loadSchema( ` plugin trpc { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = '$projectRoot/trpc' } @@ -67,7 +67,7 @@ model Foo { const { projectDir } = await loadSchema( ` plugin trpc { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = './trpc' } @@ -110,7 +110,7 @@ model Foo { const { projectDir } = await loadSchema( ` plugin trpc { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = './trpc' } @@ -141,7 +141,7 @@ model Post { const { projectDir } = await loadSchema( ` plugin trpc { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = './trpc' generateModelActions = 'findMany,findUnique,update' } @@ -171,7 +171,7 @@ model Post { const { projectDir } = await loadSchema( ` plugin trpc { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = './trpc' generateModelActions = ['findMany', 'findUnique', 'update'] } @@ -220,7 +220,7 @@ model Post { await loadSchema( ` plugin trpc { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = '$projectRoot/trpc' generateClientHelpers = 'react' } @@ -245,7 +245,7 @@ model Post { await loadSchema( ` plugin trpc { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = '$projectRoot/trpc' generateClientHelpers = 'next' } @@ -265,7 +265,7 @@ model Post { await loadSchema( ` plugin trpc { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = '$projectRoot/trpc' } @@ -304,7 +304,7 @@ generator js { } plugin trpc { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = '$projectRoot/trpc' generateModels = ['Post'] generateModelActions = ['findMany', 'update'] @@ -375,7 +375,7 @@ plugin zod { } plugin trpc { - provider = '${path.resolve(__dirname, '../dist')}' + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' output = '$projectRoot/trpc' generateModels = ['Post'] generateModelActions = ['findMany', 'update'] diff --git a/packages/testtools/src/schema.ts b/packages/testtools/src/schema.ts index 88ffa9ba8..5fef95299 100644 --- a/packages/testtools/src/schema.ts +++ b/packages/testtools/src/schema.ts @@ -54,7 +54,7 @@ export function installPackage(pkg: string, dev = false) { run(`npm install ${dev ? '-D' : ''} --no-audit --no-fund ${pkg}`); } -function normalizePath(p: string) { +export function normalizePath(p: string) { return p ? p.split(path.sep).join(path.posix.sep) : p; } @@ -174,6 +174,9 @@ export async function loadSchema(schema: string, options?: SchemaLoadOptions) { const files = schema.split(FILE_SPLITTER); + // Use this one to replace $projectRoot placeholder in the schema file + const normalizedProjectRoot = normalizePath(projectRoot); + if (files.length > 1) { // multiple files files.forEach((file, index) => { @@ -190,12 +193,12 @@ export async function loadSchema(schema: string, options?: SchemaLoadOptions) { } } - fileContent = fileContent.replaceAll('$projectRoot', projectRoot); + fileContent = fileContent.replaceAll('$projectRoot', normalizedProjectRoot); const filePath = path.join(projectRoot, fileName); fs.writeFileSync(filePath, fileContent); }); } else { - schema = schema.replaceAll('$projectRoot', projectRoot); + schema = schema.replaceAll('$projectRoot', normalizedProjectRoot); const content = opt.addPrelude ? `${makePrelude(opt)}\n${schema}` : schema; if (opt.customSchemaFilePath) { zmodelPath = path.join(projectRoot, opt.customSchemaFilePath); From 269809a360e389de1df40f751056b345a09cab32 Mon Sep 17 00:00:00 2001 From: Jiasheng Date: Tue, 19 Mar 2024 12:39:23 +0800 Subject: [PATCH 3/6] doc: add new sponsor (#1159) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d874cfefb..968f5b16a 100644 --- a/README.md +++ b/README.md @@ -235,6 +235,8 @@ Thank you for your support! Johann Rohn
Johann Rohn
Benjamin Zecirovic
Benjamin Zecirovic
+ Fabian Jocks
Fabian Jocks
+ From 018d59f58295cee4530b9650c49dc868251029dd Mon Sep 17 00:00:00 2001 From: Yiming Date: Tue, 19 Mar 2024 19:03:49 -0700 Subject: [PATCH 4/6] feat: allow to pass in a custom `Prisma` module when calling `enhance` (#1160) --- .../src/enhancements/policy/handler.ts | 47 ++++++++++--------- .../src/enhancements/policy/policy-utils.ts | 9 ++-- packages/runtime/src/enhancements/types.ts | 7 +++ packages/runtime/src/enhancements/utils.ts | 25 +++++++--- .../enhancements/with-policy/options.test.ts | 37 +++++++++++++-- 5 files changed, 90 insertions(+), 35 deletions(-) diff --git a/packages/runtime/src/enhancements/policy/handler.ts b/packages/runtime/src/enhancements/policy/handler.ts index 808763ae8..598d87881 100644 --- a/packages/runtime/src/enhancements/policy/handler.ts +++ b/packages/runtime/src/enhancements/policy/handler.ts @@ -4,6 +4,7 @@ import { lowerCaseFirst } from 'lower-case-first'; import invariant from 'tiny-invariant'; import { upperCaseFirst } from 'upper-case-first'; import { fromZodError } from 'zod-validation-error'; +import type { WithPolicyOptions } from '.'; import { CrudFailureReason } from '../../constants'; import { ModelDataVisitor, @@ -23,7 +24,6 @@ import { formatObject, prismaClientValidationError } from '../utils'; import { Logger } from './logger'; import { PolicyUtil } from './policy-utils'; import { createDeferredPromise } from './promise'; -import { WithPolicyOptions } from '.'; // a record for post-write policy check type PostWriteCheckRecord = { @@ -58,6 +58,7 @@ export class PolicyProxyHandler implements Pr this.logger = new Logger(prisma); this.utils = new PolicyUtil( this.prisma, + this.options, this.modelMeta, this.policy, this.zodSchemas, @@ -77,20 +78,20 @@ export class PolicyProxyHandler implements Pr findUnique(args: any) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.options, 'query argument is required'); } if (!args.where) { - throw prismaClientValidationError(this.prisma, 'where field is required in query argument'); + throw prismaClientValidationError(this.prisma, this.options, 'where field is required in query argument'); } return this.findWithFluentCallStubs(args, 'findUnique', false, () => null); } findUniqueOrThrow(args: any) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.options, 'query argument is required'); } if (!args.where) { - throw prismaClientValidationError(this.prisma, 'where field is required in query argument'); + throw prismaClientValidationError(this.prisma, this.options, 'where field is required in query argument'); } return this.findWithFluentCallStubs(args, 'findUniqueOrThrow', true, () => { throw this.utils.notFound(this.model); @@ -220,10 +221,10 @@ export class PolicyProxyHandler implements Pr async create(args: any) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.options, 'query argument is required'); } if (!args.data) { - throw prismaClientValidationError(this.prisma, 'data field is required in query argument'); + throw prismaClientValidationError(this.prisma, this.options, 'data field is required in query argument'); } this.utils.tryReject(this.prisma, this.model, 'create'); @@ -476,10 +477,10 @@ export class PolicyProxyHandler implements Pr async createMany(args: { data: any; skipDuplicates?: boolean }) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.options, 'query argument is required'); } if (!args.data) { - throw prismaClientValidationError(this.prisma, 'data field is required in query argument'); + throw prismaClientValidationError(this.prisma, this.options, 'data field is required in query argument'); } this.utils.tryReject(this.prisma, this.model, 'create'); @@ -596,13 +597,13 @@ export class PolicyProxyHandler implements Pr async update(args: any) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.options, 'query argument is required'); } if (!args.where) { - throw prismaClientValidationError(this.prisma, 'where field is required in query argument'); + throw prismaClientValidationError(this.prisma, this.options, 'where field is required in query argument'); } if (!args.data) { - throw prismaClientValidationError(this.prisma, 'data field is required in query argument'); + throw prismaClientValidationError(this.prisma, this.options, 'data field is required in query argument'); } args = this.utils.clone(args); @@ -1071,10 +1072,10 @@ export class PolicyProxyHandler implements Pr async updateMany(args: any) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.options, 'query argument is required'); } if (!args.data) { - throw prismaClientValidationError(this.prisma, 'data field is required in query argument'); + throw prismaClientValidationError(this.prisma, this.options, 'data field is required in query argument'); } this.utils.tryReject(this.prisma, this.model, 'update'); @@ -1130,16 +1131,16 @@ export class PolicyProxyHandler implements Pr async upsert(args: any) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.options, 'query argument is required'); } if (!args.where) { - throw prismaClientValidationError(this.prisma, 'where field is required in query argument'); + throw prismaClientValidationError(this.prisma, this.options, 'where field is required in query argument'); } if (!args.create) { - throw prismaClientValidationError(this.prisma, 'create field is required in query argument'); + throw prismaClientValidationError(this.prisma, this.options, 'create field is required in query argument'); } if (!args.update) { - throw prismaClientValidationError(this.prisma, 'update field is required in query argument'); + throw prismaClientValidationError(this.prisma, this.options, 'update field is required in query argument'); } this.utils.tryReject(this.prisma, this.model, 'create'); @@ -1183,10 +1184,10 @@ export class PolicyProxyHandler implements Pr async delete(args: any) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.options, 'query argument is required'); } if (!args.where) { - throw prismaClientValidationError(this.prisma, 'where field is required in query argument'); + throw prismaClientValidationError(this.prisma, this.options, 'where field is required in query argument'); } this.utils.tryReject(this.prisma, this.model, 'delete'); @@ -1239,7 +1240,7 @@ export class PolicyProxyHandler implements Pr async aggregate(args: any) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.options, 'query argument is required'); } args = this.utils.clone(args); @@ -1255,7 +1256,7 @@ export class PolicyProxyHandler implements Pr async groupBy(args: any) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.options, 'query argument is required'); } args = this.utils.clone(args); @@ -1299,7 +1300,7 @@ export class PolicyProxyHandler implements Pr args = { create: {}, update: {}, delete: {} }; } else { if (typeof args !== 'object') { - throw prismaClientValidationError(this.prisma, 'argument must be an object'); + throw prismaClientValidationError(this.prisma, this.options, 'argument must be an object'); } if (Object.keys(args).length === 0) { // include all diff --git a/packages/runtime/src/enhancements/policy/policy-utils.ts b/packages/runtime/src/enhancements/policy/policy-utils.ts index ea5816f6c..5a04c0430 100644 --- a/packages/runtime/src/enhancements/policy/policy-utils.ts +++ b/packages/runtime/src/enhancements/policy/policy-utils.ts @@ -5,6 +5,7 @@ import { lowerCaseFirst } from 'lower-case-first'; import { upperCaseFirst } from 'upper-case-first'; import { ZodError } from 'zod'; import { fromZodError } from 'zod-validation-error'; +import type { EnhancementOptions } from '..'; import { CrudFailureReason, FIELD_LEVEL_OVERRIDE_READ_GUARD_PREFIX, @@ -48,6 +49,7 @@ export class PolicyUtil { constructor( private readonly db: DbClientContract, + private readonly options: EnhancementOptions | undefined, private readonly modelMeta: ModelMeta, private readonly policy: PolicyDef, private readonly zodSchemas: ZodSchemas | undefined, @@ -1098,24 +1100,25 @@ export class PolicyUtil { return prismaClientKnownRequestError( this.db, + this.options, `denied by policy: ${model} entities failed '${operation}' check${extra ? ', ' + extra : ''}`, args ); } notFound(model: string) { - return prismaClientKnownRequestError(this.db, `entity not found for model ${model}`, { + return prismaClientKnownRequestError(this.db, this.options, `entity not found for model ${model}`, { clientVersion: getVersion(), code: 'P2025', }); } validationError(message: string) { - return prismaClientValidationError(this.db, message); + return prismaClientValidationError(this.db, this.options, message); } unknownError(message: string) { - return prismaClientUnknownRequestError(this.db, message, { + return prismaClientUnknownRequestError(this.db, this.options, message, { clientVersion: getVersion(), }); } diff --git a/packages/runtime/src/enhancements/types.ts b/packages/runtime/src/enhancements/types.ts index 9c8080096..dec8f097e 100644 --- a/packages/runtime/src/enhancements/types.ts +++ b/packages/runtime/src/enhancements/types.ts @@ -19,6 +19,13 @@ export interface CommonEnhancementOptions { * Path for loading CLI-generated code */ loadPath?: string; + + /** + * The `Prisma` module generated together with `PrismaClient`. You only need to + * pass it when you specified a custom `PrismaClient` output path. The module can + * be loaded like: `import { Prisma } from '';`. + */ + prismaModule?: any; } /** diff --git a/packages/runtime/src/enhancements/utils.ts b/packages/runtime/src/enhancements/utils.ts index 73b4d42a0..72b1e393d 100644 --- a/packages/runtime/src/enhancements/utils.ts +++ b/packages/runtime/src/enhancements/utils.ts @@ -3,6 +3,7 @@ import path from 'path'; import * as util from 'util'; import type { DbClientContract } from '../types'; +import type { EnhancementOptions } from './enhance'; /** * Formats an object for pretty printing. @@ -53,25 +54,37 @@ function loadPrismaModule(prisma: any) { } } -export function prismaClientValidationError(prisma: DbClientContract, message: string) { +export function prismaClientValidationError( + prisma: DbClientContract, + options: EnhancementOptions | undefined, + message: string +) { if (!_PrismaClientValidationError) { - const _prisma = loadPrismaModule(prisma); + const _prisma = options?.prismaModule ?? loadPrismaModule(prisma); _PrismaClientValidationError = _prisma.PrismaClientValidationError; } throw new _PrismaClientValidationError(message, { clientVersion: prisma._clientVersion }); } -export function prismaClientKnownRequestError(prisma: DbClientContract, ...args: unknown[]) { +export function prismaClientKnownRequestError( + prisma: DbClientContract, + options: EnhancementOptions | undefined, + ...args: unknown[] +) { if (!_PrismaClientKnownRequestError) { - const _prisma = loadPrismaModule(prisma); + const _prisma = options?.prismaModule ?? loadPrismaModule(prisma); _PrismaClientKnownRequestError = _prisma.PrismaClientKnownRequestError; } return new _PrismaClientKnownRequestError(...args); } -export function prismaClientUnknownRequestError(prisma: DbClientContract, ...args: unknown[]) { +export function prismaClientUnknownRequestError( + prisma: DbClientContract, + options: EnhancementOptions | undefined, + ...args: unknown[] +) { if (!_PrismaClientUnknownRequestError) { - const _prisma = loadPrismaModule(prisma); + const _prisma = options?.prismaModule ?? loadPrismaModule(prisma); _PrismaClientUnknownRequestError = _prisma.PrismaClientUnknownRequestError; } throw new _PrismaClientUnknownRequestError(...args); diff --git a/tests/integration/tests/enhancements/with-policy/options.test.ts b/tests/integration/tests/enhancements/with-policy/options.test.ts index 2c661ceb4..9c82e4305 100644 --- a/tests/integration/tests/enhancements/with-policy/options.test.ts +++ b/tests/integration/tests/enhancements/with-policy/options.test.ts @@ -1,4 +1,4 @@ -import { withPolicy } from '@zenstackhq/runtime'; +import { enhance } from '@zenstackhq/runtime'; import { loadSchema } from '@zenstackhq/testtools'; import path from 'path'; @@ -20,17 +20,48 @@ describe('Password test', () => { id String @id @default(cuid()) x Int + @@allow('read', true) @@allow('create', x > 0) }`, { getPrismaOnly: true, output: './zen' } ); - const db = withPolicy(prisma, undefined, { loadPath: './zen' }); + const db = enhance(prisma, undefined, { loadPath: './zen' }); await expect( db.foo.create({ data: { x: 0 }, }) ).toBeRejectedByPolicy(); + await expect( + db.foo.create({ + data: { x: 1 }, + }) + ).toResolveTruthy(); + }); + + it('prisma module', async () => { + const { prisma, Prisma, modelMeta, policy } = await loadSchema( + ` + model Foo { + id String @id @default(cuid()) + x Int + + @@allow('read', true) + @@allow('create', x > 0) + }` + ); + + const db = enhance(prisma, undefined, { modelMeta, policy, prismaModule: Prisma }); + await expect( + db.foo.create({ + data: { x: 0 }, + }) + ).toBeRejectedByPolicy(); + await expect( + db.foo.create({ + data: { x: 1 }, + }) + ).toResolveTruthy(); }); it('overrides', async () => { @@ -45,7 +76,7 @@ describe('Password test', () => { { getPrismaOnly: true, output: './zen' } ); - const db = withPolicy(prisma, undefined, { + const db = enhance(prisma, undefined, { modelMeta: require(path.resolve('./zen/model-meta')).default, policy: require(path.resolve('./zen/policy')).default, }); From 1cff7f0513d8d1af8435e5f41a2b87ba3907a59e Mon Sep 17 00:00:00 2001 From: Yiming Date: Tue, 19 Mar 2024 19:08:49 -0700 Subject: [PATCH 5/6] chore: bump version (#1161) --- package.json | 2 +- packages/ide/jetbrains/build.gradle.kts | 2 +- packages/ide/jetbrains/package.json | 2 +- packages/language/package.json | 2 +- packages/misc/redwood/package.json | 2 +- packages/plugins/openapi/package.json | 2 +- packages/plugins/swr/package.json | 2 +- packages/plugins/tanstack-query/package.json | 2 +- packages/plugins/trpc/package.json | 2 +- packages/runtime/package.json | 2 +- packages/schema/package.json | 2 +- packages/sdk/package.json | 2 +- packages/server/package.json | 2 +- packages/testtools/package.json | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index d61f79174..0bb55596e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "1.11.0", + "version": "1.11.1", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/ide/jetbrains/build.gradle.kts b/packages/ide/jetbrains/build.gradle.kts index 23dc11e99..ea192ff0b 100644 --- a/packages/ide/jetbrains/build.gradle.kts +++ b/packages/ide/jetbrains/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "dev.zenstack" -version = "1.11.0" +version = "1.11.1" repositories { mavenCentral() diff --git a/packages/ide/jetbrains/package.json b/packages/ide/jetbrains/package.json index 09476b216..564af0351 100644 --- a/packages/ide/jetbrains/package.json +++ b/packages/ide/jetbrains/package.json @@ -1,6 +1,6 @@ { "name": "jetbrains", - "version": "1.11.0", + "version": "1.11.1", "displayName": "ZenStack JetBrains IDE Plugin", "description": "ZenStack JetBrains IDE plugin", "homepage": "https://zenstack.dev", diff --git a/packages/language/package.json b/packages/language/package.json index b6e146450..7e8fa9fdc 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "1.11.0", + "version": "1.11.1", "displayName": "ZenStack modeling language compiler", "description": "ZenStack modeling language compiler", "homepage": "https://zenstack.dev", diff --git a/packages/misc/redwood/package.json b/packages/misc/redwood/package.json index 207173636..33d41a01c 100644 --- a/packages/misc/redwood/package.json +++ b/packages/misc/redwood/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/redwood", "displayName": "ZenStack RedwoodJS Integration", - "version": "1.11.0", + "version": "1.11.1", "description": "CLI and runtime for integrating ZenStack with RedwoodJS projects.", "repository": { "type": "git", diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index a8b6f1d49..38c85462b 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.11.0", + "version": "1.11.1", "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 376ad2e3b..9266a6a36 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.11.0", + "version": "1.11.1", "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 69c3f5fda..51871526a 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.11.0", + "version": "1.11.1", "description": "ZenStack plugin for generating tanstack-query hooks", "main": "index.js", "exports": { diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index b0a5a28c9..e9fddc330 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.11.0", + "version": "1.11.1", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 5665262c1..8d582796e 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "1.11.0", + "version": "1.11.1", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", diff --git a/packages/schema/package.json b/packages/schema/package.json index 249389e68..25ebd253b 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "Build scalable web apps with minimum code by defining authorization and validation rules inside the data schema that closer to the database", - "version": "1.11.0", + "version": "1.11.1", "author": { "name": "ZenStack Team" }, diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 110fec6c0..5e3707444 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "1.11.0", + "version": "1.11.1", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index ea60177a5..699933197 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "1.11.0", + "version": "1.11.1", "displayName": "ZenStack Server-side Adapters", "description": "ZenStack server-side adapters", "homepage": "https://zenstack.dev", diff --git a/packages/testtools/package.json b/packages/testtools/package.json index 97e842f00..8e68dabc0 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "1.11.0", + "version": "1.11.1", "description": "ZenStack Test Tools", "main": "index.js", "private": true, From fef6e83a36f451f671ac2b7db1bc06e2e29faf43 Mon Sep 17 00:00:00 2001 From: Yiming Date: Tue, 19 Mar 2024 20:29:37 -0700 Subject: [PATCH 6/6] fix: nested `createMany` with `skipDuplicates` option is not handled correctly (#1163) --- .../src/enhancements/policy/handler.ts | 5 +- .../tests/regression/issue-1162.test.ts | 56 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 tests/integration/tests/regression/issue-1162.test.ts diff --git a/packages/runtime/src/enhancements/policy/handler.ts b/packages/runtime/src/enhancements/policy/handler.ts index 598d87881..322b0261e 100644 --- a/packages/runtime/src/enhancements/policy/handler.ts +++ b/packages/runtime/src/enhancements/policy/handler.ts @@ -735,7 +735,10 @@ export class PolicyProxyHandler implements Pr ) => { for (const item of enumerate(args.data)) { if (args.skipDuplicates) { - if (await this.hasDuplicatedUniqueConstraint(model, item, db)) { + // get a reversed query to include fields inherited from upstream mutation, + // it'll be merged with the create payload for unique constraint checking + const reversedQuery = this.utils.buildReversedQuery(context); + if (await this.hasDuplicatedUniqueConstraint(model, { ...reversedQuery, ...item }, db)) { if (this.shouldLogQuery) { this.logger.info(`[policy] \`createMany\` skipping duplicate ${formatObject(item)}`); } diff --git a/tests/integration/tests/regression/issue-1162.test.ts b/tests/integration/tests/regression/issue-1162.test.ts new file mode 100644 index 000000000..fd7f0dded --- /dev/null +++ b/tests/integration/tests/regression/issue-1162.test.ts @@ -0,0 +1,56 @@ +import { loadSchema } from '@zenstackhq/testtools'; + +describe('issue 1162', () => { + it('regression', async () => { + const { enhance } = await loadSchema( + ` + model User { + id String @id @default(cuid()) + companies CompanyUser[] + @@allow('all', true) + } + + model Company { + id String @id @default(cuid()) + users CompanyUser[] + @@allow('all', true) + } + + model CompanyUser { + company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) + companyId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + userId String + @@id([companyId, userId]) + @@allow('all', true) + } + `, + { logPrismaQuery: true } + ); + + const db = enhance(); + + await db.user.create({ data: { id: 'abc' } }); + await db.user.create({ data: { id: 'def' } }); + await db.company.create({ data: { id: '1', users: { create: { userId: 'abc' } } } }); + await expect( + db.company.update({ + where: { id: '1' }, + data: { + users: { + createMany: { + data: [{ userId: 'abc' }, { userId: 'def' }], + skipDuplicates: true, + }, + }, + }, + include: { users: true }, + }) + ).resolves.toMatchObject({ + users: expect.arrayContaining([ + { companyId: '1', userId: 'abc' }, + { companyId: '1', userId: 'def' }, + ]), + }); + }); +});