From dde193103ad5448209fdde67b3e25cf4a41ee309 Mon Sep 17 00:00:00 2001 From: Simon Edelmann Date: Sat, 3 Feb 2024 11:01:13 +0100 Subject: [PATCH 1/2] Add Zod's built-in string transformations as field attributes (#981) Co-authored-by: Yiming --- .../schema/src/plugins/zod/utils/schema-gen.ts | 12 ++++++++++++ packages/schema/src/res/stdlib.zmodel | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/packages/schema/src/plugins/zod/utils/schema-gen.ts b/packages/schema/src/plugins/zod/utils/schema-gen.ts index 802127c58..39e7d2bb2 100644 --- a/packages/schema/src/plugins/zod/utils/schema-gen.ts +++ b/packages/schema/src/plugins/zod/utils/schema-gen.ts @@ -89,6 +89,18 @@ export function makeFieldSchema(field: DataModelField, respectDefault = false) { schema += `.url(${messageArgFirst})`; break; } + case '@trim': { + schema += `.trim()`; + break; + } + case '@lower': { + schema += `.toLowerCase()`; + break; + } + case '@upper': { + schema += `.toUpperCase()`; + break; + } case '@datetime': { schema += `.datetime({ offset: true${message ? ', message: ' + JSON.stringify(message) : ''} })`; break; diff --git a/packages/schema/src/res/stdlib.zmodel b/packages/schema/src/res/stdlib.zmodel index 1a9446d7b..be241fe2c 100644 --- a/packages/schema/src/res/stdlib.zmodel +++ b/packages/schema/src/res/stdlib.zmodel @@ -514,6 +514,21 @@ attribute @datetime(_ message: String?) @@@targetField([StringField]) @@@validat */ attribute @url(_ message: String?) @@@targetField([StringField]) @@@validation +/** + * Trims whitespaces from the start and end of the string. + */ +attribute @trim() @@@targetField([StringField]) @@@validation + +/** + * Transform entire string toLowerCase. + */ +attribute @lower() @@@targetField([StringField]) @@@validation + +/** + * Transform entire string toUpperCase. + */ +attribute @upper() @@@targetField([StringField]) @@@validation + /** * Validates a number field is greater than the given value. */ From d0745b149a5ce6abfef546de0b9243ddc4f6e765 Mon Sep 17 00:00:00 2001 From: Yiming Date: Sat, 10 Feb 2024 13:15:19 +0800 Subject: [PATCH 2/2] feat: make parameters of transactions configurable (#988) --- 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 +- .../src/enhancements/policy/handler.ts | 24 +++++++++++++---- .../runtime/src/enhancements/policy/index.ts | 27 ++++++++++++++++++- packages/schema/package.json | 2 +- .../src/plugins/prisma/schema-generator.ts | 8 ++++-- packages/sdk/package.json | 2 +- packages/server/package.json | 2 +- packages/testtools/package.json | 2 +- tests/integration/tests/cli/generate.test.ts | 14 ++++++++++ 18 files changed, 79 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index a94b0bda1..16b18ea0a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "1.8.1", + "version": "1.8.2", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/ide/jetbrains/build.gradle.kts b/packages/ide/jetbrains/build.gradle.kts index 82af5c7cf..2643f4e2a 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.8.1" +version = "1.8.2" repositories { mavenCentral() diff --git a/packages/ide/jetbrains/package.json b/packages/ide/jetbrains/package.json index 1a5d33d1b..ca7e7c57e 100644 --- a/packages/ide/jetbrains/package.json +++ b/packages/ide/jetbrains/package.json @@ -1,6 +1,6 @@ { "name": "jetbrains", - "version": "1.8.1", + "version": "1.8.2", "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 0abdfe86b..ff2cfd5ec 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "1.8.1", + "version": "1.8.2", "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 ce5e88e41..47cddae1b 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.8.1", + "version": "1.8.2", "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 7e0091592..a910ab0e8 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.8.1", + "version": "1.8.2", "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 4e5a644b6..223433bec 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.8.1", + "version": "1.8.2", "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 050bf85d4..3d5a6d94b 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.8.1", + "version": "1.8.2", "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 dafc77411..6620e365e 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.8.1", + "version": "1.8.2", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index ad513e1cd..4c0125474 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "1.8.1", + "version": "1.8.2", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", diff --git a/packages/runtime/src/enhancements/policy/handler.ts b/packages/runtime/src/enhancements/policy/handler.ts index d35d36af2..e9f4daae0 100644 --- a/packages/runtime/src/enhancements/policy/handler.ts +++ b/packages/runtime/src/enhancements/policy/handler.ts @@ -23,6 +23,7 @@ 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 = { @@ -42,14 +43,17 @@ export class PolicyProxyHandler implements Pr private readonly utils: PolicyUtil; private readonly model: string; + private readonly DEFAULT_TX_MAXWAIT = 100000; + private readonly DEFAULT_TX_TIMEOUT = 100000; + constructor( private readonly prisma: DbClient, private readonly policy: PolicyDef, private readonly modelMeta: ModelMeta, private readonly zodSchemas: ZodSchemas | undefined, model: string, - private readonly user?: AuthUser, - private readonly logPrismaQuery?: boolean + private readonly user: AuthUser | undefined, + private readonly options: WithPolicyOptions | undefined ) { this.logger = new Logger(prisma); this.utils = new PolicyUtil( @@ -1276,12 +1280,22 @@ export class PolicyProxyHandler implements Pr //#region Utils private get shouldLogQuery() { - return !!this.logPrismaQuery && this.logger.enabled('info'); + return !!this.options?.logPrismaQuery && this.logger.enabled('info'); } private transaction(action: (tx: Record) => Promise) { if (this.prisma['$transaction']) { - return this.prisma.$transaction((tx) => action(tx), { maxWait: 100000, timeout: 100000 }); + const txOptions: any = { maxWait: this.DEFAULT_TX_MAXWAIT, timeout: this.DEFAULT_TX_TIMEOUT }; + if (this.options?.transactionMaxWait !== undefined) { + txOptions.maxWait = this.options.transactionMaxWait; + } + if (this.options?.transactionTimeout !== undefined) { + txOptions.timeout = this.options.transactionTimeout; + } + if (this.options?.transactionIsolationLevel !== undefined) { + txOptions.isolationLevel = this.options.transactionIsolationLevel; + } + return this.prisma.$transaction((tx) => action(tx), txOptions); } else { // already in transaction, don't nest return action(this.prisma); @@ -1304,7 +1318,7 @@ export class PolicyProxyHandler implements Pr this.zodSchemas, model, this.user, - this.logPrismaQuery + this.options ); } diff --git a/packages/runtime/src/enhancements/policy/index.ts b/packages/runtime/src/enhancements/policy/index.ts index f1246659a..d4380d72b 100644 --- a/packages/runtime/src/enhancements/policy/index.ts +++ b/packages/runtime/src/enhancements/policy/index.ts @@ -18,6 +18,16 @@ export type WithPolicyContext = { user?: AuthUser; }; +/** + * Transaction isolation levels: https://www.prisma.io/docs/orm/prisma-client/queries/transactions#transaction-isolation-level + */ +export type TransactionIsolationLevel = + | 'ReadUncommitted' + | 'ReadCommitted' + | 'RepeatableRead' + | 'Snapshot' + | 'Serializable'; + /** * Options for @see withPolicy */ @@ -46,6 +56,21 @@ export interface WithPolicyOptions extends CommonEnhancementOptions { * Hook for transforming errors before they are thrown to the caller. */ errorTransformer?: ErrorTransformer; + + /** + * The `maxWait` option passed to `prisma.$transaction()` call for transactions initiated by ZenStack. + */ + transactionMaxWait?: number; + + /** + * The `timeout` option passed to `prisma.$transaction()` call for transactions initiated by ZenStack. + */ + transactionTimeout?: number; + + /** + * The `isolationLevel` option passed to `prisma.$transaction()` call for transactions initiated by ZenStack. + */ + transactionIsolationLevel?: TransactionIsolationLevel; } /** @@ -115,7 +140,7 @@ export function withPolicy( _zodSchemas, model, context?.user, - options?.logPrismaQuery + options ), 'policy', options?.errorTransformer diff --git a/packages/schema/package.json b/packages/schema/package.json index ac8f812dc..d7726b3be 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.8.1", + "version": "1.8.2", "author": { "name": "ZenStack Team" }, diff --git a/packages/schema/src/plugins/prisma/schema-generator.ts b/packages/schema/src/plugins/prisma/schema-generator.ts index 1d59f7429..98dfa717e 100644 --- a/packages/schema/src/plugins/prisma/schema-generator.ts +++ b/packages/schema/src/plugins/prisma/schema-generator.ts @@ -136,14 +136,18 @@ export default class PrismaSchemaGenerator { const generateClient = options.generateClient !== false; if (generateClient) { + let generateCmd = `npx prisma generate --schema "${outFile}"`; + if (typeof options.generateArgs === 'string') { + generateCmd += ` ${options.generateArgs}`; + } try { // run 'prisma generate' - await execSync(`npx prisma generate --schema "${outFile}"`, 'ignore'); + await execSync(generateCmd, 'ignore'); } catch { await this.trackPrismaSchemaError(outFile); try { // run 'prisma generate' again with output to the console - await execSync(`npx prisma generate --schema "${outFile}"`); + await execSync(generateCmd); } catch { // noop } diff --git a/packages/sdk/package.json b/packages/sdk/package.json index a424bc2ba..8fa8cf619 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "1.8.1", + "version": "1.8.2", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 2cacfccd4..f4b4b68ab 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "1.8.1", + "version": "1.8.2", "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 5621e8c83..107b4d659 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "1.8.1", + "version": "1.8.2", "description": "ZenStack Test Tools", "main": "index.js", "private": true, diff --git a/tests/integration/tests/cli/generate.test.ts b/tests/integration/tests/cli/generate.test.ts index 40a7981c8..0367033bd 100644 --- a/tests/integration/tests/cli/generate.test.ts +++ b/tests/integration/tests/cli/generate.test.ts @@ -180,4 +180,18 @@ model Post { expect(fs.existsSync('./node_modules/.zenstack/zod/index.js')).toBeFalsy(); expect(fs.existsSync('./node_modules/.zenstack/zod/index.ts')).toBeTruthy(); }); + + it('generate with prisma generateArgs', async () => { + fs.appendFileSync( + 'schema.zmodel', + ` + plugin prisma { + provider = '@core/prisma' + generateArgs = '--no-engine' + } + ` + ); + const program = createProgram(); + await program.parseAsync(['generate', '--no-dependency-check', '--no-default-plugins'], { from: 'user' }); + }); });