From 53d80c5b5bb1ed83120b902397f84e4a67f430b8 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 8 Jan 2024 21:18:04 +0800 Subject: [PATCH 1/5] refactor: generate `enhance` API and avoid dynamic `require` in runtime --- .github/workflows/build-test.yml | 7 -- packages/plugins/tanstack-query/tsconfig.json | 2 +- packages/runtime/package.json | 2 - packages/runtime/res/enhance.d.ts | 1 + packages/runtime/res/enhance.js | 10 ++ packages/runtime/src/constants.ts | 2 +- packages/runtime/src/enhance.d.ts | 2 + packages/runtime/src/enhancements/enhance.ts | 12 +-- packages/runtime/src/enhancements/index.ts | 1 - packages/runtime/src/enhancements/omit.ts | 10 +- packages/runtime/src/enhancements/password.ts | 10 +- .../src/enhancements/policy/handler.ts | 91 ++++++++++++++----- .../runtime/src/enhancements/policy/index.ts | 21 +++-- .../src/enhancements/policy/policy-utils.ts | 8 +- packages/runtime/src/enhancements/preset.ts | 20 ---- packages/runtime/src/enhancements/types.ts | 2 +- packages/runtime/src/enhancements/utils.ts | 72 ++------------- packages/runtime/src/index.ts | 2 +- packages/runtime/src/loader.ts | 88 ------------------ packages/runtime/src/package.json | 1 + packages/runtime/src/version.ts | 43 +-------- packages/schema/package.json | 4 +- packages/schema/src/cli/plugin-runner.ts | 25 ++++- packages/schema/src/plugins/enhancer/index.ts | 64 +++++++++++++ packages/schema/src/plugins/plugin-utils.ts | 2 +- packages/schema/src/plugins/prisma/index.ts | 1 + .../src/plugins/prisma/schema-generator.ts | 2 +- packages/schema/src/plugins/zod/index.ts | 1 + packages/schema/src/utils/exec-utils.ts | 9 +- .../tests/generator/prisma-generator.test.ts | 22 ++++- packages/sdk/src/prisma.ts | 28 +++++- packages/server/src/api/base.ts | 5 +- packages/server/src/shared.ts | 89 +++++++++++++++++- packages/server/tests/api/rest.test.ts | 6 +- packages/testtools/src/package.template.json | 4 +- packages/testtools/src/schema.ts | 53 +++++------ pnpm-lock.yaml | 74 ++++++++++----- script/test-prisma-v5.sh | 3 - tests/integration/test-run/package.json | 4 +- tests/integration/tests/cli/generate.test.ts | 20 ---- tests/integration/tests/cli/init.test.ts | 36 +++++--- .../enhancements/with-omit/with-omit.test.ts | 29 ------ .../with-password/with-password.test.ts | 23 ----- .../with-policy/client-extensions.test.ts | 36 ++++---- .../enhancements/with-policy/options.test.ts | 24 ++--- .../nextjs/test-project/package.json | 4 +- .../tests/frameworks/trpc/generation.test.ts | 4 +- .../frameworks/trpc/test-project/package.json | 4 +- .../frameworks/trpc/test-project/todo.zmodel | 10 -- 49 files changed, 500 insertions(+), 493 deletions(-) create mode 100644 packages/runtime/res/enhance.d.ts create mode 100644 packages/runtime/res/enhance.js create mode 100644 packages/runtime/src/enhance.d.ts delete mode 100644 packages/runtime/src/enhancements/preset.ts delete mode 100644 packages/runtime/src/loader.ts create mode 120000 packages/runtime/src/package.json create mode 100644 packages/schema/src/plugins/enhancer/index.ts delete mode 100755 script/test-prisma-v5.sh diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index b6388804b..f2749da3b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -32,18 +32,11 @@ jobs: strategy: matrix: node-version: [18.x] - prisma-version: [v4, v5] steps: - name: Checkout uses: actions/checkout@v3 - - name: Set Prisma Version - if: ${{ matrix.prisma-version == 'v5' }} - shell: bash - run: | - bash ./script/test-prisma-v5.sh - - name: Install pnpm uses: pnpm/action-setup@v2 with: diff --git a/packages/plugins/tanstack-query/tsconfig.json b/packages/plugins/tanstack-query/tsconfig.json index 9e4f772c5..c51ec9bae 100644 --- a/packages/plugins/tanstack-query/tsconfig.json +++ b/packages/plugins/tanstack-query/tsconfig.json @@ -6,5 +6,5 @@ "jsx": "react" }, "include": ["src/**/*.ts"], - "exclude": ["src/runtime"] + "exclude": ["src/runtime", "src/runtime-v5"] } diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 336ad6d34..5f8d393e2 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -53,11 +53,9 @@ "linkDirectory": true }, "dependencies": { - "@types/bcryptjs": "^2.4.2", "bcryptjs": "^2.4.3", "buffer": "^6.0.3", "change-case": "^4.1.2", - "colors": "1.4.0", "decimal.js": "^10.4.2", "deepcopy": "^2.1.0", "lower-case-first": "^2.0.2", diff --git a/packages/runtime/res/enhance.d.ts b/packages/runtime/res/enhance.d.ts new file mode 100644 index 000000000..4ae717bc4 --- /dev/null +++ b/packages/runtime/res/enhance.d.ts @@ -0,0 +1 @@ +export { enhance } from '.zenstack/enhance'; diff --git a/packages/runtime/res/enhance.js b/packages/runtime/res/enhance.js new file mode 100644 index 000000000..aa19af865 --- /dev/null +++ b/packages/runtime/res/enhance.js @@ -0,0 +1,10 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); + +try { + exports.enhance = require('.zenstack/enhance').enhance; +} catch { + exports.enhance = function () { + throw new Error('Generated "enhance" function not found. Please run `zenstack generate` first.'); + }; +} diff --git a/packages/runtime/src/constants.ts b/packages/runtime/src/constants.ts index 3c12b5a88..bd191924a 100644 --- a/packages/runtime/src/constants.ts +++ b/packages/runtime/src/constants.ts @@ -66,7 +66,7 @@ export const PRISMA_PROXY_ENHANCER = '$__zenstack_enhancer'; /** * Minimum Prisma version supported */ -export const PRISMA_MINIMUM_VERSION = '4.8.0'; +export const PRISMA_MINIMUM_VERSION = '5.0.0'; /** * Selector function name for fetching pre-update entity values. diff --git a/packages/runtime/src/enhance.d.ts b/packages/runtime/src/enhance.d.ts new file mode 100644 index 000000000..48e877878 --- /dev/null +++ b/packages/runtime/src/enhance.d.ts @@ -0,0 +1,2 @@ +// @ts-expect-error stub for re-exporting generated code +export { enhance } from '.zenstack/enhance'; diff --git a/packages/runtime/src/enhancements/enhance.ts b/packages/runtime/src/enhancements/enhance.ts index 42a504bdf..977a8e950 100644 --- a/packages/runtime/src/enhancements/enhance.ts +++ b/packages/runtime/src/enhancements/enhance.ts @@ -1,4 +1,3 @@ -import { getDefaultModelMeta } from '../loader'; import { withOmit, WithOmitOptions } from './omit'; import { withPassword, WithPasswordOptions } from './password'; import { withPolicy, WithPolicyContext, WithPolicyOptions } from './policy'; @@ -21,16 +20,15 @@ let hasOmit: boolean | undefined = undefined; * @param context The context to for evaluating access policies. * @param options Options. */ -export function enhance( +export function createEnhancement( prisma: DbClient, - context?: WithPolicyContext, - options?: EnhancementOptions + options: EnhancementOptions, + context?: WithPolicyContext ) { let result = prisma; if (hasPassword === undefined || hasOmit === undefined) { - const modelMeta = options?.modelMeta ?? getDefaultModelMeta(options?.loadPath); - const allFields = Object.values(modelMeta.fields).flatMap((modelInfo) => Object.values(modelInfo)); + const allFields = Object.values(options.modelMeta.fields).flatMap((modelInfo) => Object.values(modelInfo)); hasPassword = allFields.some((field) => field.attributes?.some((attr) => attr.name === '@password')); hasOmit = allFields.some((field) => field.attributes?.some((attr) => attr.name === '@omit')); } @@ -46,7 +44,7 @@ export function enhance( } // policy proxy - result = withPolicy(result, context, options); + result = withPolicy(result, options, context); return result; } diff --git a/packages/runtime/src/enhancements/index.ts b/packages/runtime/src/enhancements/index.ts index 25b150a71..51f304657 100644 --- a/packages/runtime/src/enhancements/index.ts +++ b/packages/runtime/src/enhancements/index.ts @@ -3,7 +3,6 @@ export * from './enhance'; export * from './omit'; export * from './password'; export * from './policy'; -export * from './preset'; export * from './types'; export * from './utils'; export * from './where-visitor'; diff --git a/packages/runtime/src/enhancements/omit.ts b/packages/runtime/src/enhancements/omit.ts index e8b3f8c98..22b002309 100644 --- a/packages/runtime/src/enhancements/omit.ts +++ b/packages/runtime/src/enhancements/omit.ts @@ -2,7 +2,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { enumerate, getModelFields, resolveField, type ModelMeta } from '../cross'; -import { getDefaultModelMeta } from '../loader'; import { DbClientContract } from '../types'; import { DefaultPrismaProxyHandler, makeProxy } from './proxy'; import { CommonEnhancementOptions } from './types'; @@ -14,18 +13,17 @@ export interface WithOmitOptions extends CommonEnhancementOptions { /** * Model metadata */ - modelMeta?: ModelMeta; + modelMeta: ModelMeta; } /** * Gets an enhanced Prisma client that supports @omit attribute. */ -export function withOmit(prisma: DbClient, options?: WithOmitOptions): DbClient { - const _modelMeta = options?.modelMeta ?? getDefaultModelMeta(options?.loadPath); +export function withOmit(prisma: DbClient, options: WithOmitOptions): DbClient { return makeProxy( prisma, - _modelMeta, - (_prisma, model) => new OmitHandler(_prisma as DbClientContract, model, _modelMeta), + options.modelMeta, + (_prisma, model) => new OmitHandler(_prisma as DbClientContract, model, options.modelMeta), 'omit' ); } diff --git a/packages/runtime/src/enhancements/password.ts b/packages/runtime/src/enhancements/password.ts index 34844dfb4..5846bc8dc 100644 --- a/packages/runtime/src/enhancements/password.ts +++ b/packages/runtime/src/enhancements/password.ts @@ -4,7 +4,6 @@ import { hash } from 'bcryptjs'; import { DEFAULT_PASSWORD_SALT_LENGTH } from '../constants'; import { NestedWriteVisitor, type ModelMeta, type PrismaWriteActionType } from '../cross'; -import { getDefaultModelMeta } from '../loader'; import { DbClientContract } from '../types'; import { DefaultPrismaProxyHandler, PrismaProxyActions, makeProxy } from './proxy'; import { CommonEnhancementOptions } from './types'; @@ -16,18 +15,17 @@ export interface WithPasswordOptions extends CommonEnhancementOptions { /** * Model metadata */ - modelMeta?: ModelMeta; + modelMeta: ModelMeta; } /** * Gets an enhanced Prisma client that supports @password attribute. */ -export function withPassword(prisma: DbClient, options?: WithPasswordOptions): DbClient { - const _modelMeta = options?.modelMeta ?? getDefaultModelMeta(options?.loadPath); +export function withPassword(prisma: DbClient, options: WithPasswordOptions): DbClient { return makeProxy( prisma, - _modelMeta, - (_prisma, model) => new PasswordHandler(_prisma as DbClientContract, model, _modelMeta), + options.modelMeta, + (_prisma, model) => new PasswordHandler(_prisma as DbClientContract, model, options.modelMeta), 'password' ); } diff --git a/packages/runtime/src/enhancements/policy/handler.ts b/packages/runtime/src/enhancements/policy/handler.ts index f002002d2..748877805 100644 --- a/packages/runtime/src/enhancements/policy/handler.ts +++ b/packages/runtime/src/enhancements/policy/handler.ts @@ -47,6 +47,7 @@ export class PolicyProxyHandler implements Pr private readonly policy: PolicyDef, private readonly modelMeta: ModelMeta, private readonly zodSchemas: ZodSchemas | undefined, + private readonly prismaModule: any, model: string, private readonly user?: AuthUser, private readonly logPrismaQuery?: boolean @@ -57,6 +58,7 @@ export class PolicyProxyHandler implements Pr this.modelMeta, this.policy, this.zodSchemas, + this.prismaModule, this.user, this.shouldLogQuery ); @@ -73,20 +75,28 @@ export class PolicyProxyHandler implements Pr findUnique(args: any) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.prismaModule, 'query argument is required'); } if (!args.where) { - throw prismaClientValidationError(this.prisma, 'where field is required in query argument'); + throw prismaClientValidationError( + this.prisma, + this.prismaModule, + '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.prismaModule, 'query argument is required'); } if (!args.where) { - throw prismaClientValidationError(this.prisma, 'where field is required in query argument'); + throw prismaClientValidationError( + this.prisma, + this.prismaModule, + 'where field is required in query argument' + ); } return this.findWithFluentCallStubs(args, 'findUniqueOrThrow', true, () => { throw this.utils.notFound(this.model); @@ -216,10 +226,14 @@ export class PolicyProxyHandler implements Pr async create(args: any) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.prismaModule, 'query argument is required'); } if (!args.data) { - throw prismaClientValidationError(this.prisma, 'data field is required in query argument'); + throw prismaClientValidationError( + this.prisma, + this.prismaModule, + 'data field is required in query argument' + ); } this.utils.tryReject(this.prisma, this.model, 'create'); @@ -469,10 +483,14 @@ 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.prismaModule, 'query argument is required'); } if (!args.data) { - throw prismaClientValidationError(this.prisma, 'data field is required in query argument'); + throw prismaClientValidationError( + this.prisma, + this.prismaModule, + 'data field is required in query argument' + ); } this.utils.tryReject(this.prisma, this.model, 'create'); @@ -576,13 +594,21 @@ export class PolicyProxyHandler implements Pr async update(args: any) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.prismaModule, 'query argument is required'); } if (!args.where) { - throw prismaClientValidationError(this.prisma, 'where field is required in query argument'); + throw prismaClientValidationError( + this.prisma, + this.prismaModule, + '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.prismaModule, + 'data field is required in query argument' + ); } args = this.utils.clone(args); @@ -967,10 +993,14 @@ export class PolicyProxyHandler implements Pr async updateMany(args: any) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.prismaModule, 'query argument is required'); } if (!args.data) { - throw prismaClientValidationError(this.prisma, 'data field is required in query argument'); + throw prismaClientValidationError( + this.prisma, + this.prismaModule, + 'data field is required in query argument' + ); } this.utils.tryReject(this.prisma, this.model, 'update'); @@ -1024,16 +1054,28 @@ export class PolicyProxyHandler implements Pr async upsert(args: any) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.prismaModule, 'query argument is required'); } if (!args.where) { - throw prismaClientValidationError(this.prisma, 'where field is required in query argument'); + throw prismaClientValidationError( + this.prisma, + this.prismaModule, + '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.prismaModule, + '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.prismaModule, + 'update field is required in query argument' + ); } this.utils.tryReject(this.prisma, this.model, 'create'); @@ -1077,10 +1119,14 @@ export class PolicyProxyHandler implements Pr async delete(args: any) { if (!args) { - throw prismaClientValidationError(this.prisma, 'query argument is required'); + throw prismaClientValidationError(this.prisma, this.prismaModule, 'query argument is required'); } if (!args.where) { - throw prismaClientValidationError(this.prisma, 'where field is required in query argument'); + throw prismaClientValidationError( + this.prisma, + this.prismaModule, + 'where field is required in query argument' + ); } this.utils.tryReject(this.prisma, this.model, 'delete'); @@ -1133,7 +1179,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.prismaModule, 'query argument is required'); } args = this.utils.clone(args); @@ -1149,7 +1195,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.prismaModule, 'query argument is required'); } args = this.utils.clone(args); @@ -1193,7 +1239,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.prismaModule, 'argument must be an object'); } if (Object.keys(args).length === 0) { // include all @@ -1254,6 +1300,7 @@ export class PolicyProxyHandler implements Pr this.policy, this.modelMeta, this.zodSchemas, + this.prismaModule, model, this.user, this.logPrismaQuery diff --git a/packages/runtime/src/enhancements/policy/index.ts b/packages/runtime/src/enhancements/policy/index.ts index 678f777ef..497379cc3 100644 --- a/packages/runtime/src/enhancements/policy/index.ts +++ b/packages/runtime/src/enhancements/policy/index.ts @@ -4,7 +4,6 @@ import semver from 'semver'; import { PRISMA_MINIMUM_VERSION } from '../../constants'; import { getIdFields, type ModelMeta } from '../../cross'; -import { getDefaultModelMeta, getDefaultPolicy, getDefaultZodSchemas } from '../../loader'; import { AuthUser, DbClientContract } from '../../types'; import { hasAllFields } from '../../validation'; import { ErrorTransformer, makeProxy } from '../proxy'; @@ -25,12 +24,12 @@ export interface WithPolicyOptions extends CommonEnhancementOptions { /** * Policy definition */ - policy?: PolicyDef; + policy: PolicyDef; /** * Model metadata */ - modelMeta?: ModelMeta; + modelMeta: ModelMeta; /** * Zod schemas for validation @@ -46,6 +45,11 @@ export interface WithPolicyOptions extends CommonEnhancementOptions { * Hook for transforming errors before they are thrown to the caller. */ errorTransformer?: ErrorTransformer; + + /** + * The Node module that contains PrismaClient + */ + prismaModule: any; } /** @@ -58,8 +62,8 @@ export interface WithPolicyOptions extends CommonEnhancementOptions { */ export function withPolicy( prisma: DbClient, - context?: WithPolicyContext, - options?: WithPolicyOptions + options: WithPolicyOptions, + context?: WithPolicyContext ): DbClient { if (!prisma) { throw new Error('Invalid prisma instance'); @@ -72,9 +76,9 @@ export function withPolicy( ); } - const _policy = options?.policy ?? getDefaultPolicy(options?.loadPath); - const _modelMeta = options?.modelMeta ?? getDefaultModelMeta(options?.loadPath); - const _zodSchemas = options?.zodSchemas ?? getDefaultZodSchemas(options?.loadPath); + const _policy = options.policy; + const _modelMeta = options.modelMeta; + const _zodSchemas = options?.zodSchemas; // validate user context const userContext = context?.user; @@ -111,6 +115,7 @@ export function withPolicy( _policy, _modelMeta, _zodSchemas, + options.prismaModule, model, context?.user, options?.logPrismaQuery diff --git a/packages/runtime/src/enhancements/policy/policy-utils.ts b/packages/runtime/src/enhancements/policy/policy-utils.ts index d6f9595b2..7b1597ccd 100644 --- a/packages/runtime/src/enhancements/policy/policy-utils.ts +++ b/packages/runtime/src/enhancements/policy/policy-utils.ts @@ -51,6 +51,7 @@ export class PolicyUtil { private readonly modelMeta: ModelMeta, private readonly policy: PolicyDef, private readonly zodSchemas: ZodSchemas | undefined, + private readonly prismaModule: any, private readonly user?: AuthUser, private readonly shouldLogQuery = false ) { @@ -1083,24 +1084,25 @@ export class PolicyUtil { return prismaClientKnownRequestError( this.db, + this.prismaModule, `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.prismaModule, `entity not found for model ${model}`, { clientVersion: getVersion(), code: 'P2025', }); } validationError(message: string) { - return prismaClientValidationError(this.db, message); + return prismaClientValidationError(this.db, this.prismaModule, message); } unknownError(message: string) { - return prismaClientUnknownRequestError(this.db, message, { + return prismaClientUnknownRequestError(this.db, this.prismaModule, message, { clientVersion: getVersion(), }); } diff --git a/packages/runtime/src/enhancements/preset.ts b/packages/runtime/src/enhancements/preset.ts deleted file mode 100644 index b4c29dd7b..000000000 --- a/packages/runtime/src/enhancements/preset.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { EnhancementOptions, enhance } from './enhance'; -import { WithPolicyContext } from './policy'; - -/** - * Gets a Prisma client enhanced with all essential behaviors, including access - * policy, field validation, field omission and password hashing. - * - * It's a shortcut for calling withOmit(withPassword(withPolicy(prisma, options))). - * - * @param prisma The Prisma client to enhance. - * @param context The context to for evaluating access policies. - * @param options Options. - */ -export function withPresets( - prisma: DbClient, - context?: WithPolicyContext, - options?: EnhancementOptions -) { - return enhance(prisma, context, options); -} diff --git a/packages/runtime/src/enhancements/types.ts b/packages/runtime/src/enhancements/types.ts index 9c8080096..4dcfa1c1a 100644 --- a/packages/runtime/src/enhancements/types.ts +++ b/packages/runtime/src/enhancements/types.ts @@ -76,5 +76,5 @@ export type PolicyDef = { */ export type ZodSchemas = { models: Record; - input: Record>; + input?: Record>; }; diff --git a/packages/runtime/src/enhancements/utils.ts b/packages/runtime/src/enhancements/utils.ts index 73b4d42a0..ba2f9a2d8 100644 --- a/packages/runtime/src/enhancements/utils.ts +++ b/packages/runtime/src/enhancements/utils.ts @@ -1,6 +1,3 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ - -import path from 'path'; import * as util from 'util'; import type { DbClientContract } from '../types'; @@ -11,68 +8,17 @@ export function formatObject(value: unknown) { return util.formatWithOptions({ depth: 20 }, value); } -let _PrismaClientValidationError: new (...args: unknown[]) => Error; -let _PrismaClientKnownRequestError: new (...args: unknown[]) => Error; -let _PrismaClientUnknownRequestError: new (...args: unknown[]) => Error; - -/* eslint-disable @typescript-eslint/no-explicit-any */ -function loadPrismaModule(prisma: any) { - // https://github.com/prisma/prisma/discussions/17832 - if (prisma._engineConfig?.datamodelPath) { - // try engine path first - const loadPath = path.dirname(prisma._engineConfig.datamodelPath); - try { - const _prisma = require(loadPath).Prisma; - if (typeof _prisma !== 'undefined') { - return _prisma; - } - } catch { - // noop - } - } - - try { - // Prisma v4 - return require('@prisma/client/runtime'); - } catch { - try { - // Prisma v5 - return require('@prisma/client'); - } catch (err) { - if (process.env.ZENSTACK_TEST === '1') { - // running in test, try cwd - try { - return require(path.join(process.cwd(), 'node_modules/@prisma/client/runtime')); - } catch { - return require(path.join(process.cwd(), 'node_modules/@prisma/client')); - } - } else { - throw err; - } - } - } -} - -export function prismaClientValidationError(prisma: DbClientContract, message: string) { - if (!_PrismaClientValidationError) { - const _prisma = loadPrismaModule(prisma); - _PrismaClientValidationError = _prisma.PrismaClientValidationError; - } - throw new _PrismaClientValidationError(message, { clientVersion: prisma._clientVersion }); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function prismaClientValidationError(prisma: DbClientContract, prismaModule: any, message: string): Error { + throw new prismaModule.PrismaClientValidationError(message, { clientVersion: prisma._clientVersion }); } -export function prismaClientKnownRequestError(prisma: DbClientContract, ...args: unknown[]) { - if (!_PrismaClientKnownRequestError) { - const _prisma = loadPrismaModule(prisma); - _PrismaClientKnownRequestError = _prisma.PrismaClientKnownRequestError; - } - return new _PrismaClientKnownRequestError(...args); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function prismaClientKnownRequestError(prisma: DbClientContract, prismaModule: any, ...args: unknown[]): Error { + return new prismaModule.PrismaClientKnownRequestError(...args); } -export function prismaClientUnknownRequestError(prisma: DbClientContract, ...args: unknown[]) { - if (!_PrismaClientUnknownRequestError) { - const _prisma = loadPrismaModule(prisma); - _PrismaClientUnknownRequestError = _prisma.PrismaClientUnknownRequestError; - } - throw new _PrismaClientUnknownRequestError(...args); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function prismaClientUnknownRequestError(prismaModule: any, ...args: unknown[]): Error { + throw new prismaModule.PrismaClientUnknownRequestError(...args); } diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 57df37ee4..6a2609156 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -1,7 +1,7 @@ export * from './constants'; export * from './enhancements'; export * from './error'; -export * from './loader'; export * from './types'; export * from './validation'; export * from './version'; +export * from './enhance'; diff --git a/packages/runtime/src/loader.ts b/packages/runtime/src/loader.ts deleted file mode 100644 index 1c2eef7bd..000000000 --- a/packages/runtime/src/loader.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -import path from 'path'; -import { ModelMeta, PolicyDef, ZodSchemas } from './enhancements'; - -/** - * Load model metadata. - * - * @param loadPath The path to load model metadata from. If not provided, - * will use default load path. - */ -export function getDefaultModelMeta(loadPath: string | undefined): ModelMeta { - try { - if (loadPath) { - const toLoad = path.resolve(loadPath, 'model-meta'); - return require(toLoad).default; - } else { - return require('.zenstack/model-meta').default; - } - } catch { - if (process.env.ZENSTACK_TEST === '1' && !loadPath) { - 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.'); - } -} - -/** - * Load access policies. - * - * @param loadPath The path to load access policies from. If not provided, - * will use default load path. - */ -export function getDefaultPolicy(loadPath: string | undefined): PolicyDef { - try { - if (loadPath) { - const toLoad = path.resolve(loadPath, 'policy'); - return require(toLoad).default; - } else { - return require('.zenstack/policy').default; - } - } catch { - if (process.env.ZENSTACK_TEST === '1' && !loadPath) { - 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.' - ); - } -} - -/** - * Load zod schemas. - * - * @param loadPath The path to load zod schemas from. If not provided, - * will use default load path. - */ -export function getDefaultZodSchemas(loadPath: string | undefined): ZodSchemas | undefined { - try { - if (loadPath) { - const toLoad = path.resolve(loadPath, 'zod'); - return require(toLoad); - } else { - return require('.zenstack/zod'); - } - } catch { - if (process.env.ZENSTACK_TEST === '1' && !loadPath) { - 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/runtime/src/package.json b/packages/runtime/src/package.json new file mode 120000 index 000000000..4e26811d4 --- /dev/null +++ b/packages/runtime/src/package.json @@ -0,0 +1 @@ +../package.json \ No newline at end of file diff --git a/packages/runtime/src/version.ts b/packages/runtime/src/version.ts index 567ef7a71..b8e941547 100644 --- a/packages/runtime/src/version.ts +++ b/packages/runtime/src/version.ts @@ -1,42 +1,9 @@ -import path from 'path'; - -/* eslint-disable @typescript-eslint/no-var-requires */ -export function getVersion() { - try { - return require('./package.json').version; - } catch { - try { - // dev environment - return require('../package.json').version; - } catch { - return 'unknown'; - } - } -} +import * as pkgJson from './package.json'; /** - * Gets installed Prisma version by first checking "@prisma/client" and if not available, - * "prisma". + * Gets this package's version. + * @returns */ -export function getPrismaVersion(): string | undefined { - if (process.env.ZENSTACK_TEST === '1') { - // test environment - try { - return require(path.resolve('./node_modules/@prisma/client/package.json')).version; - } catch { - return undefined; - } - } - - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - return require('@prisma/client/package.json').version; - } catch { - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - return require('prisma/package.json').version; - } catch { - return undefined; - } - } +export function getVersion() { + return pkgJson.version; } diff --git a/packages/schema/package.json b/packages/schema/package.json index 83d8792e7..47d4305e0 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -112,7 +112,7 @@ "zod-validation-error": "^1.5.0" }, "devDependencies": { - "@prisma/client": "^4.8.0", + "@prisma/client": "^5.7.1", "@types/async-exit-hook": "^2.0.0", "@types/pluralize": "^0.0.29", "@types/semver": "^7.3.13", @@ -124,7 +124,7 @@ "@zenstackhq/runtime": "workspace:*", "dotenv": "^16.0.3", "esbuild": "^0.15.12", - "prisma": "^4.8.0", + "prisma": "^5.7.1", "renamer": "^4.0.0", "tmp": "^0.2.1", "tsc-alias": "^1.7.0", diff --git a/packages/schema/src/cli/plugin-runner.ts b/packages/schema/src/cli/plugin-runner.ts index 0609fd4fb..2914ae43b 100644 --- a/packages/schema/src/cli/plugin-runner.ts +++ b/packages/schema/src/cli/plugin-runner.ts @@ -24,6 +24,7 @@ import { getVersion } from '../utils/version-utils'; type PluginInfo = { name: string; + description?: string; provider: string; options: PluginOptions; run: PluginFunction; @@ -93,6 +94,7 @@ export class PluginRunner { plugins.push({ name: pluginName, + description: this.getPluginDescription(pluginModule), provider: pluginProvider, dependencies, options: pluginOptions, @@ -123,6 +125,7 @@ export class PluginRunner { const pluginName = this.getPluginName(pluginModule, corePlugin.provider); plugins.unshift({ name: pluginName, + description: this.getPluginDescription(pluginModule), provider: corePlugin.provider, dependencies: [], options: { schemaPath: options.schemaPath, name: pluginName, ...corePlugin.options }, @@ -153,9 +156,9 @@ export class PluginRunner { const warnings: string[] = []; let dmmf: DMMF.Document | undefined = undefined; - for (const { name, provider, run, options: pluginOptions } of plugins) { + for (const { name, description, provider, run, options: pluginOptions } of plugins) { // const start = Date.now(); - await this.runPlugin(name, run, options, pluginOptions, dmmf, warnings); + await this.runPlugin(name, description, run, options, pluginOptions, dmmf, warnings); // console.log(`✅ Plugin ${colors.bold(name)} (${provider}) completed in ${Date.now() - start}ms`); if (provider === '@core/prisma') { // load prisma DMMF @@ -201,6 +204,13 @@ export class PluginRunner { } } + if (options.defaultPlugins) { + corePlugins.push({ + provider: '@core/enhancer', + options: { withZodSchemas: corePlugins.some((p) => p.provider === '@core/zod') }, + }); + } + // core plugins introduced by dependencies plugins.forEach((plugin) => { // TODO: generalize this @@ -251,10 +261,15 @@ export class PluginRunner { } // eslint-disable-next-line @typescript-eslint/no-explicit-any - private getPluginName(pluginModule: any, pluginProvider: string): string { + private getPluginName(pluginModule: any, pluginProvider: string) { return typeof pluginModule.name === 'string' ? (pluginModule.name as string) : pluginProvider; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private getPluginDescription(pluginModule: any) { + return typeof pluginModule.description === 'string' ? (pluginModule.description as string) : undefined; + } + private getPluginDependencies(pluginModule: any) { return Array.isArray(pluginModule.dependencies) ? (pluginModule.dependencies as string[]) : []; } @@ -266,13 +281,15 @@ export class PluginRunner { private async runPlugin( name: string, + description: string | undefined, run: PluginFunction, runnerOptions: PluginRunnerOptions, options: PluginOptions, dmmf: DMMF.Document | undefined, warnings: string[] ) { - const spinner = ora(`Running plugin ${colors.cyan(name)}`).start(); + const title = description ?? `Running plugin ${colors.cyan(name)}`; + const spinner = ora(title).start(); try { await telemetry.trackSpan( 'cli:plugin:start', diff --git a/packages/schema/src/plugins/enhancer/index.ts b/packages/schema/src/plugins/enhancer/index.ts new file mode 100644 index 000000000..9af0ebcca --- /dev/null +++ b/packages/schema/src/plugins/enhancer/index.ts @@ -0,0 +1,64 @@ +import { + PluginError, + createProject, + emitProject, + getPrismaClientImportSpec, + resolvePath, + saveProject, + type PluginFunction, +} from '@zenstackhq/sdk'; +import path from 'path'; +import { getDefaultOutputFolder } from '../plugin-utils'; + +export const name = 'Prisma Enhancer'; + +const run: PluginFunction = async (_model, options, _dmmf, globalOptions) => { + let output = options.output ? (options.output as string) : getDefaultOutputFolder(globalOptions); + if (!output) { + throw new PluginError(options.name, `Unable to determine output path, not running plugin`); + } + + output = resolvePath(output, options); + const outFile = path.join(output, 'enhance.ts'); + const project = createProject(); + + let shouldCompile = true; + if (typeof options.compile === 'boolean') { + // explicit override + shouldCompile = options.compile; + } else if (globalOptions) { + // from CLI or config file + shouldCompile = globalOptions.compile; + } + + project.createSourceFile( + outFile, + `import { createEnhancement, type WithPolicyContext, type EnhancementOptions, type ZodSchemas } from '@zenstackhq/runtime'; +import modelMeta from './model-meta'; +import policy from './policy'; +${options.withZodSchemas ? "import * as zodSchemas from './zod';" : 'const zodSchemas = undefined;'} +import { Prisma } from '${getPrismaClientImportSpec(_model, output)}'; + +export function enhance(prisma: DbClient, context?: WithPolicyContext, options?: EnhancementOptions): DbClient { + return createEnhancement(prisma, { + modelMeta, + policy, + zodSchemas: zodSchemas as (ZodSchemas | undefined), + prismaModule: Prisma, + ...options + }, context); +} +`, + { overwrite: true } + ); + + if (!shouldCompile || options.preserveTsFiles === true) { + await saveProject(project); + } + + if (shouldCompile) { + await emitProject(project); + } +}; + +export default run; diff --git a/packages/schema/src/plugins/plugin-utils.ts b/packages/schema/src/plugins/plugin-utils.ts index e095de898..4b1be34ff 100644 --- a/packages/schema/src/plugins/plugin-utils.ts +++ b/packages/schema/src/plugins/plugin-utils.ts @@ -81,7 +81,7 @@ export function getDefaultOutputFolder(globalOptions?: PluginGlobalOptions) { let runtimeModulePath = require.resolve('@zenstackhq/runtime'); if (process.env.ZENSTACK_TEST === '1') { - // handling the case when running as tests, resolve relative to CWD + // handle the case when running as tests, resolve relative to CWD runtimeModulePath = path.resolve(path.join(process.cwd(), 'node_modules', '@zenstackhq', 'runtime')); } diff --git a/packages/schema/src/plugins/prisma/index.ts b/packages/schema/src/plugins/prisma/index.ts index 3a96cf40f..c4b209aa6 100644 --- a/packages/schema/src/plugins/prisma/index.ts +++ b/packages/schema/src/plugins/prisma/index.ts @@ -2,6 +2,7 @@ import { PluginFunction } from '@zenstackhq/sdk'; import PrismaSchemaGenerator from './schema-generator'; export const name = 'Prisma'; +export const description = 'Generating Prisma schema'; const run: PluginFunction = async (model, options, _dmmf, _globalOptions) => { return new PrismaSchemaGenerator().generate(model, options); diff --git a/packages/schema/src/plugins/prisma/schema-generator.ts b/packages/schema/src/plugins/prisma/schema-generator.ts index 45a025d27..62fe2e47f 100644 --- a/packages/schema/src/plugins/prisma/schema-generator.ts +++ b/packages/schema/src/plugins/prisma/schema-generator.ts @@ -136,7 +136,7 @@ export default class PrismaSchemaGenerator { if (generateClient) { try { // run 'prisma generate' - await execSync(`npx prisma generate --schema "${outFile}"`, 'ignore'); + await execSync(`npx prisma generate --schema "${outFile}"`, { stdio: 'ignore' }); } catch { await this.trackPrismaSchemaError(outFile); try { diff --git a/packages/schema/src/plugins/zod/index.ts b/packages/schema/src/plugins/zod/index.ts index b2b43cb40..53a30b4e3 100644 --- a/packages/schema/src/plugins/zod/index.ts +++ b/packages/schema/src/plugins/zod/index.ts @@ -3,6 +3,7 @@ import invariant from 'tiny-invariant'; import { generate } from './generator'; export const name = 'Zod'; +export const description = 'Generating Zod schemas'; const run: PluginFunction = async (model, options, dmmf, globalOptions) => { invariant(dmmf); diff --git a/packages/schema/src/utils/exec-utils.ts b/packages/schema/src/utils/exec-utils.ts index f355ae2b4..d88e42b3d 100644 --- a/packages/schema/src/utils/exec-utils.ts +++ b/packages/schema/src/utils/exec-utils.ts @@ -1,9 +1,10 @@ -import { execSync as _exec, StdioOptions } from 'child_process'; +import { execSync as _exec, ExecSyncOptions } from 'child_process'; /** * Utility for executing command synchronously and prints outputs on current console */ -export function execSync(cmd: string, stdio: StdioOptions = 'inherit', env?: Record): void { - const mergedEnv = { ...process.env, ...env }; - _exec(cmd, { encoding: 'utf-8', stdio, env: mergedEnv }); +export function execSync(cmd: string, options?: Omit & { env?: Record }): void { + const { env, ...restOptions } = options ?? {}; + const mergedEnv = env ? { ...process.env, ...env } : undefined; + _exec(cmd, { encoding: 'utf-8', stdio: options?.stdio ?? 'inherit', env: mergedEnv, ...restOptions }); } diff --git a/packages/schema/tests/generator/prisma-generator.test.ts b/packages/schema/tests/generator/prisma-generator.test.ts index 049104b1e..5307b2837 100644 --- a/packages/schema/tests/generator/prisma-generator.test.ts +++ b/packages/schema/tests/generator/prisma-generator.test.ts @@ -6,9 +6,26 @@ import path from 'path'; import tmp from 'tmp'; import { loadDocument } from '../../src/cli/cli-util'; import PrismaSchemaGenerator from '../../src/plugins/prisma/schema-generator'; +import { execSync } from '../../src/utils/exec-utils'; import { loadModel } from '../utils'; describe('Prisma generator test', () => { + let origDir: string; + + beforeEach(() => { + origDir = process.cwd(); + const r = tmp.dirSync({ unsafeCleanup: true }); + console.log(`Project dir: ${r.name}`); + process.chdir(r.name); + + execSync('npm init -y', { stdio: 'ignore' }); + execSync('npm install prisma'); + }); + + afterEach(() => { + process.chdir(origDir); + }); + it('datasource coverage', async () => { const model = await loadModel(` datasource db { @@ -32,15 +49,14 @@ describe('Prisma generator test', () => { } `); - const { name } = tmp.fileSync({ postfix: '.prisma' }); await new PrismaSchemaGenerator().generate(model, { name: 'Prisma', provider: '@core/prisma', schemaPath: 'schema.zmodel', - output: name, + output: 'schema.prisma', }); - const content = fs.readFileSync(name, 'utf-8'); + const content = fs.readFileSync('schema.prisma', 'utf-8'); expect(content).toContain('provider = "postgresql"'); expect(content).toContain('url = env("DATABASE_URL")'); expect(content).toContain('directUrl = env("DATABASE_URL")'); diff --git a/packages/sdk/src/prisma.ts b/packages/sdk/src/prisma.ts index 4b4e461a1..73502d58b 100644 --- a/packages/sdk/src/prisma.ts +++ b/packages/sdk/src/prisma.ts @@ -1,15 +1,11 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import type { DMMF } from '@prisma/generator-helper'; -import { getPrismaVersion } from '@zenstackhq/runtime'; import path from 'path'; import * as semver from 'semver'; import { GeneratorDecl, Model, Plugin, isGeneratorDecl, isPlugin } from './ast'; import { getLiteral } from './utils'; -// reexport -export { getPrismaVersion } from '@zenstackhq/runtime'; - /** * Given a ZModel and an import context directory, compute the import spec for the Prisma Client. */ @@ -91,3 +87,27 @@ export function getDMMF(options: GetDMMFOptions): Promise { return _getDMMF(options); } } + +/** + * Gets the installed Prisma's version + */ +export function getPrismaVersion(): string | undefined { + if (process.env.ZENSTACK_TEST === '1') { + // test environment + try { + return require(path.resolve('./node_modules/@prisma/client/package.json')).version; + } catch { + return undefined; + } + } + + try { + return require('@prisma/client/package.json').version; + } catch { + try { + return require('prisma/package.json').version; + } catch { + return undefined; + } + } +} diff --git a/packages/server/src/api/base.ts b/packages/server/src/api/base.ts index ba385f31c..96c547204 100644 --- a/packages/server/src/api/base.ts +++ b/packages/server/src/api/base.ts @@ -1,5 +1,6 @@ -import { DbClientContract, ModelMeta, ZodSchemas, getDefaultModelMeta } from '@zenstackhq/runtime'; -import { LoggerConfig } from '../types'; +import type { DbClientContract, ModelMeta, ZodSchemas } from '@zenstackhq/runtime'; +import { getDefaultModelMeta } from '../shared'; +import type { LoggerConfig } from '../types'; /** * API request context diff --git a/packages/server/src/shared.ts b/packages/server/src/shared.ts index 6001fbbaa..1a9c62119 100644 --- a/packages/server/src/shared.ts +++ b/packages/server/src/shared.ts @@ -1,4 +1,6 @@ -import { ZodSchemas, getDefaultModelMeta, getDefaultZodSchemas } from '@zenstackhq/runtime'; +/* eslint-disable @typescript-eslint/no-var-requires */ +import type { ModelMeta, PolicyDef, ZodSchemas } from '@zenstackhq/runtime'; +import path from 'path'; import { AdapterBaseOptions } from './types'; export function loadAssets(options: AdapterBaseOptions) { @@ -18,3 +20,88 @@ export function loadAssets(options: AdapterBaseOptions) { return { modelMeta, zodSchemas }; } + +/** + * Load model metadata. + * + * @param loadPath The path to load model metadata from. If not provided, + * will use default load path. + */ +export function getDefaultModelMeta(loadPath: string | undefined): ModelMeta { + try { + if (loadPath) { + const toLoad = path.resolve(loadPath, 'model-meta'); + return require(toLoad).default; + } else { + return require('.zenstack/model-meta').default; + } + } catch { + if (process.env.ZENSTACK_TEST === '1' && !loadPath) { + 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.'); + } +} + +/** + * Load access policies. + * + * @param loadPath The path to load access policies from. If not provided, + * will use default load path. + */ +export function getDefaultPolicy(loadPath: string | undefined): PolicyDef { + try { + if (loadPath) { + const toLoad = path.resolve(loadPath, 'policy'); + return require(toLoad).default; + } else { + return require('.zenstack/policy').default; + } + } catch { + if (process.env.ZENSTACK_TEST === '1' && !loadPath) { + 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.' + ); + } +} + +/** + * Load zod schemas. + * + * @param loadPath The path to load zod schemas from. If not provided, + * will use default load path. + */ +export function getDefaultZodSchemas(loadPath: string | undefined): ZodSchemas | undefined { + try { + if (loadPath) { + const toLoad = path.resolve(loadPath, 'zod'); + return require(toLoad); + } else { + return require('.zenstack/zod'); + } + } catch { + if (process.env.ZENSTACK_TEST === '1' && !loadPath) { + 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/server/tests/api/rest.test.ts b/packages/server/tests/api/rest.test.ts index 7b084ef8a..c1b3bfd4e 100644 --- a/packages/server/tests/api/rest.test.ts +++ b/packages/server/tests/api/rest.test.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /// -import { CrudFailureReason, ModelMeta, withPolicy } from '@zenstackhq/runtime'; +import { CrudFailureReason, withPolicy, type ModelMeta } from '@zenstackhq/runtime'; import { loadSchema, run } from '@zenstackhq/testtools'; import { Decimal } from 'decimal.js'; import SuperJSON from 'superjson'; @@ -1882,7 +1882,7 @@ describe('REST server tests', () => { beforeAll(async () => { const params = await loadSchema(schema); - prisma = withPolicy(params.prisma, undefined, params); + prisma = withPolicy(params.prisma, params); zodSchemas = params.zodSchemas; modelMeta = params.modelMeta; @@ -1995,7 +1995,7 @@ describe('REST server tests', () => { beforeAll(async () => { const params = await loadSchema(schema); - prisma = withPolicy(params.prisma, undefined, params); + prisma = withPolicy(params.prisma, params); zodSchemas = params.zodSchemas; modelMeta = params.modelMeta; diff --git a/packages/testtools/src/package.template.json b/packages/testtools/src/package.template.json index 8ea542361..cd443d32d 100644 --- a/packages/testtools/src/package.template.json +++ b/packages/testtools/src/package.template.json @@ -7,12 +7,12 @@ "author": "", "license": "ISC", "dependencies": { - "@prisma/client": "^4.8.0", + "@prisma/client": "^5.7.1", "@zenstackhq/runtime": "file:/packages/runtime/dist", "@zenstackhq/swr": "file:/packages/plugins/swr/dist", "@zenstackhq/trpc": "file:/packages/plugins/trpc/dist", "@zenstackhq/openapi": "file:/packages/plugins/openapi/dist", - "prisma": "^4.8.0", + "prisma": "^5.7.1", "typescript": "^4.9.3", "zenstack": "file:/packages/schema/dist", "zod": "^3.22.4", diff --git a/packages/testtools/src/schema.ts b/packages/testtools/src/schema.ts index f69a845cc..25ffa4621 100644 --- a/packages/testtools/src/schema.ts +++ b/packages/testtools/src/schema.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { DMMF } from '@prisma/generator-helper'; import type { Model } from '@zenstackhq/language/ast'; -import { enhance, withOmit, withPassword, withPolicy, type AuthUser, type DbOperations } from '@zenstackhq/runtime'; +import { withOmit, withPassword, withPolicy, type AuthUser, type DbOperations } from '@zenstackhq/runtime'; import { getDMMF } from '@zenstackhq/sdk'; import { execSync } from 'child_process'; import * as fs from 'fs'; @@ -35,14 +35,14 @@ export type FullDbClientContract = Record & { }; export function run(cmd: string, env?: Record, cwd?: string) { - const start = Date.now(); + // const start = Date.now(); execSync(cmd, { stdio: 'pipe', encoding: 'utf-8', env: { ...process.env, DO_NOT_TRACK: '1', ...env }, cwd, }); - console.log('Execution took', Date.now() - start, 'ms', '-', cmd); + // console.log('Execution took', Date.now() - start, 'ms', '-', cmd); } function normalizePath(p: string) { @@ -224,7 +224,7 @@ export async function loadSchema(schema: string, options?: SchemaLoadOptions) { // https://github.com/prisma/prisma/issues/18292 prisma[Symbol.for('nodejs.util.inspect.custom')] = 'PrismaClient'; - const Prisma = require(path.join(projectRoot, 'node_modules/@prisma/client')).Prisma; + const prismaModule = require(path.join(projectRoot, 'node_modules/@prisma/client')).Prisma; if (opt.pulseApiKey) { const withPulse = require(path.join(projectRoot, 'node_modules/@prisma/extension-pulse/dist/cjs')).withPulse; @@ -248,58 +248,53 @@ export async function loadSchema(schema: string, options?: SchemaLoadOptions) { if (options?.getPrismaOnly) { return { prisma, - Prisma, + prismaModule, projectDir: projectRoot, withPolicy: undefined as any, withOmit: undefined as any, withPassword: undefined as any, enhance: undefined as any, + policy: undefined as any, + modelMeta: undefined as any, + zodSchemas: undefined as any, }; } - let policy: any; - let modelMeta: any; - let zodSchemas: any; + const outputPath = opt.output + ? path.isAbsolute(opt.output) + ? opt.output + : path.join(projectRoot, opt.output) + : path.join(projectRoot, 'node_modules', '.zenstack'); - const outputPath = path.join(projectRoot, 'node_modules'); + const policy = require(path.join(outputPath, 'policy')).default; + const modelMeta = require(path.join(outputPath, 'model-meta')).default; + let zodSchemas: any; try { - policy = require(path.join(outputPath, '.zenstack/policy')).default; - } catch { - /* noop */ - } - try { - modelMeta = require(path.join(outputPath, '.zenstack/model-meta')).default; - } catch { - /* noop */ - } - try { - zodSchemas = require(path.join(outputPath, '.zenstack/zod')); + zodSchemas = require(path.join(outputPath, 'zod')); } catch { /* noop */ } + const enhance = require(path.join(outputPath, 'enhance')).enhance; + return { projectDir: projectRoot, prisma, - Prisma, withPolicy: (user?: AuthUser) => withPolicy( prisma, - { user }, - { policy, modelMeta, zodSchemas, logPrismaQuery: opt.logPrismaQuery } + { policy, modelMeta, zodSchemas, prismaModule, logPrismaQuery: opt.logPrismaQuery }, + { user } ), withOmit: () => withOmit(prisma, { modelMeta }), withPassword: () => withPassword(prisma, { modelMeta }), - enhance: (user?: AuthUser) => - enhance( - prisma, - { user }, - { policy, modelMeta, zodSchemas, logPrismaQuery: opt.logPrismaQuery } - ), + enhance: (user?: AuthUser): FullDbClientContract => + enhance(prisma, { user }, { policy, modelMeta, zodSchemas, logPrismaQuery: opt.logPrismaQuery }), policy, modelMeta, zodSchemas, + prismaModule, }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 253f1f6d4..4806970f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -391,9 +391,6 @@ importers: packages/runtime: dependencies: - '@types/bcryptjs': - specifier: ^2.4.2 - version: 2.4.2 bcryptjs: specifier: ^2.4.3 version: 2.4.3 @@ -403,9 +400,6 @@ importers: change-case: specifier: ^4.1.2 version: 4.1.2 - colors: - specifier: 1.4.0 - version: 1.4.0 decimal.js: specifier: ^10.4.2 version: 10.4.2 @@ -443,6 +437,9 @@ importers: specifier: ^1.5.0 version: 1.5.0(zod@3.22.4) devDependencies: + '@types/bcryptjs': + specifier: ^2.4.2 + version: 2.4.2 '@types/pluralize': specifier: ^0.0.29 version: 0.0.29 @@ -554,8 +551,8 @@ importers: version: 1.5.0(zod@3.22.4) devDependencies: '@prisma/client': - specifier: ^4.8.0 - version: 4.16.2(prisma@4.16.2) + specifier: ^5.7.1 + version: 5.7.1(prisma@5.7.1) '@types/async-exit-hook': specifier: ^2.0.0 version: 2.0.0 @@ -590,8 +587,8 @@ importers: specifier: ^0.15.12 version: 0.15.12 prisma: - specifier: ^4.8.0 - version: 4.16.2 + specifier: ^5.7.1 + version: 5.7.1 renamer: specifier: ^4.0.0 version: 4.0.0 @@ -3489,22 +3486,19 @@ packages: resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} dev: true - /@prisma/client@4.16.2(prisma@4.16.2): - resolution: {integrity: sha512-qCoEyxv1ZrQ4bKy39GnylE8Zq31IRmm8bNhNbZx7bF2cU5aiCCnSa93J2imF88MBjn7J9eUQneNxUQVJdl/rPQ==} - engines: {node: '>=14.17'} + /@prisma/client@5.7.0: + resolution: {integrity: sha512-cZmglCrfNbYpzUtz7HscVHl38e9CrUs31nrVoGUK1nIPXGgt8hT4jj2s657UXcNdQ/jBUxDgGmHyu2Nyrq1txg==} + engines: {node: '>=16.13'} requiresBuild: true peerDependencies: prisma: '*' peerDependenciesMeta: prisma: optional: true - dependencies: - '@prisma/engines-version': 4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81 - prisma: 4.16.2 dev: true - /@prisma/client@5.7.0: - resolution: {integrity: sha512-cZmglCrfNbYpzUtz7HscVHl38e9CrUs31nrVoGUK1nIPXGgt8hT4jj2s657UXcNdQ/jBUxDgGmHyu2Nyrq1txg==} + /@prisma/client@5.7.1(prisma@5.7.1): + resolution: {integrity: sha512-TUSa4nUcC4nf/e7X3jyO1pEd6XcI/TLRCA0KjkA46RDIpxUaRsBYEOqITwXRW2c0bMFyKcCRXrH4f7h4q9oOlg==} engines: {node: '>=16.13'} requiresBuild: true peerDependencies: @@ -3512,6 +3506,8 @@ packages: peerDependenciesMeta: prisma: optional: true + dependencies: + prisma: 5.7.1 dev: true /@prisma/debug@4.16.2: @@ -3538,17 +3534,22 @@ packages: resolution: {integrity: sha512-tZ+MOjWlVvz1kOEhNYMa4QUGURY+kgOUBqLHYIV8jmCsMuvA1tWcn7qtIMLzYWCbDcQT4ZS8xDgK0R2gl6/0wA==} dev: false - /@prisma/engines-version@4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81: - resolution: {integrity: sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg==} + /@prisma/debug@5.7.1: + resolution: {integrity: sha512-yrVSO/YZOxdeIxcBtZ5BaNqUfPrZkNsAKQIQg36cJKMxj/VYK3Vk5jMKkI+gQLl0KReo1YvX8GWKfV788SELjw==} dev: true /@prisma/engines-version@5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9: resolution: {integrity: sha512-V6tgRVi62jRwTm0Hglky3Scwjr/AKFBFtS+MdbsBr7UOuiu1TKLPc6xfPiyEN1+bYqjEtjxwGsHgahcJsd1rNg==} dev: false + /@prisma/engines-version@5.7.1-1.0ca5ccbcfa6bdc81c003cf549abe4269f59c41e5: + resolution: {integrity: sha512-dIR5IQK/ZxEoWRBDOHF87r1Jy+m2ih3Joi4vzJRP+FOj5yxCwS2pS5SBR3TWoVnEK1zxtLI/3N7BjHyGF84fgw==} + dev: true + /@prisma/engines@4.16.2: resolution: {integrity: sha512-vx1nxVvN4QeT/cepQce68deh/Turxy5Mr+4L4zClFuK1GlxN3+ivxfuv+ej/gvidWn1cE1uAhW7ALLNlYbRUAw==} requiresBuild: true + dev: false /@prisma/engines@5.0.0: resolution: {integrity: sha512-kyT/8fd0OpWmhAU5YnY7eP31brW1q1YrTGoblWrhQJDiN/1K+Z8S1kylcmtjqx5wsUGcP1HBWutayA/jtyt+sg==} @@ -3565,6 +3566,16 @@ packages: '@prisma/get-platform': 5.7.0 dev: false + /@prisma/engines@5.7.1: + resolution: {integrity: sha512-R+Pqbra8tpLP2cvyiUpx+SIKglav3nTCpA+rn6826CThviQ8yvbNG0s8jNpo51vS9FuZO3pOkARqG062vKX7uA==} + requiresBuild: true + dependencies: + '@prisma/debug': 5.7.1 + '@prisma/engines-version': 5.7.1-1.0ca5ccbcfa6bdc81c003cf549abe4269f59c41e5 + '@prisma/fetch-engine': 5.7.1 + '@prisma/get-platform': 5.7.1 + dev: true + /@prisma/fetch-engine@4.16.2: resolution: {integrity: sha512-lnCnHcOaNn0kw8qTJbVcNhyfIf5Lus2GFXbj3qpkdKEIB9xLgqkkuTP+35q1xFaqwQ0vy4HFpdRUpFP7njE15g==} dependencies: @@ -3623,6 +3634,14 @@ packages: '@prisma/get-platform': 5.7.0 dev: false + /@prisma/fetch-engine@5.7.1: + resolution: {integrity: sha512-9ELauIEBkIaEUpMIYPRlh5QELfoC6pyHolHVQgbNxglaINikZ9w9X7r1TIePAcm05pCNp2XPY1ObQIJW5nYfBQ==} + dependencies: + '@prisma/debug': 5.7.1 + '@prisma/engines-version': 5.7.1-1.0ca5ccbcfa6bdc81c003cf549abe4269f59c41e5 + '@prisma/get-platform': 5.7.1 + dev: true + /@prisma/generator-helper@4.16.2: resolution: {integrity: sha512-bMOH7y73Ui7gpQrioFeavMQA+Tf8ksaVf8Nhs9rQNzuSg8SSV6E9baczob0L5KGZTSgYoqnrRxuo03kVJYrnIg==} dependencies: @@ -3691,6 +3710,12 @@ packages: '@prisma/debug': 5.7.0 dev: false + /@prisma/get-platform@5.7.1: + resolution: {integrity: sha512-eDlswr3a1m5z9D/55Iyt/nZqS5UpD+DZ9MooBB3hvrcPhDQrcf9m4Tl7buy4mvAtrubQ626ECtb8c6L/f7rGSQ==} + dependencies: + '@prisma/debug': 5.7.1 + dev: true + /@prisma/internals@4.16.2: resolution: {integrity: sha512-/3OiSADA3RRgsaeEE+MDsBgL6oAMwddSheXn6wtYGUnjERAV/BmF5bMMLnTykesQqwZ1s8HrISrJ0Vf6cjOxMg==} dependencies: @@ -4594,6 +4619,7 @@ packages: /@types/bcryptjs@2.4.2: resolution: {integrity: sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==} + dev: true /@types/body-parser@1.19.2: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} @@ -12474,13 +12500,13 @@ packages: hasBin: true dev: true - /prisma@4.16.2: - resolution: {integrity: sha512-SYCsBvDf0/7XSJyf2cHTLjLeTLVXYfqp7pG5eEVafFLeT0u/hLFz/9W196nDRGUOo1JfPatAEb+uEnTQImQC1g==} - engines: {node: '>=14.17'} + /prisma@5.7.1: + resolution: {integrity: sha512-ekho7ziH0WEJvC4AxuJz+ewRTMskrebPcrKuBwcNzVDniYxx+dXOGcorNeIb9VEMO5vrKzwNYvhD271Ui2jnNw==} + engines: {node: '>=16.13'} hasBin: true requiresBuild: true dependencies: - '@prisma/engines': 4.16.2 + '@prisma/engines': 5.7.1 dev: true /process-nextick-args@2.0.1: diff --git a/script/test-prisma-v5.sh b/script/test-prisma-v5.sh deleted file mode 100755 index 51fc8e3cb..000000000 --- a/script/test-prisma-v5.sh +++ /dev/null @@ -1,3 +0,0 @@ -echo Setting Prisma Versions to V5 -npx replace-in-file '/"prisma":\s*"\^4.\d.\d"/g' '"prisma": "^5.0.0"' 'packages/testtools/**/package*.json' 'tests/integration/**/package*.json' --isRegex -npx replace-in-file '/"@prisma/client":\s*"\^4.\d.\d"/g' '"@prisma/client": "^5.0.0"' 'packages/testtools/**/package*.json' 'tests/integration/**/package*.json' --isRegex \ No newline at end of file diff --git a/tests/integration/test-run/package.json b/tests/integration/test-run/package.json index d4e05bd29..31497a99a 100644 --- a/tests/integration/test-run/package.json +++ b/tests/integration/test-run/package.json @@ -10,9 +10,9 @@ "author": "", "license": "ISC", "dependencies": { - "@prisma/client": "^4.8.0", + "@prisma/client": "^5.0.0", "@zenstackhq/runtime": "file:../../../packages/runtime/dist", - "prisma": "^4.8.0", + "prisma": "^5.0.0", "react": "^18.2.0", "swr": "^1.3.0", "typescript": "^4.9.3", diff --git a/tests/integration/tests/cli/generate.test.ts b/tests/integration/tests/cli/generate.test.ts index 40a7981c8..c8e1b0e6b 100644 --- a/tests/integration/tests/cli/generate.test.ts +++ b/tests/integration/tests/cli/generate.test.ts @@ -86,26 +86,6 @@ model Post { expect(fs.existsSync('./out/zod')).toBeTruthy(); }); - it('generate custom output override', async () => { - fs.appendFileSync( - 'schema.zmodel', - ` - plugin policy { - provider = '@core/access-policy' - output = 'policy-out' - } - ` - ); - - const program = createProgram(); - await program.parseAsync(['generate', '--no-dependency-check', '-o', 'out'], { from: 'user' }); - expect(fs.existsSync('./node_modules/.zenstack')).toBeFalsy(); - expect(fs.existsSync('./out/model-meta.js')).toBeTruthy(); - expect(fs.existsSync('./out/zod')).toBeTruthy(); - expect(fs.existsSync('./out/policy.js')).toBeFalsy(); - expect(fs.existsSync('./policy-out/policy.js')).toBeTruthy(); - }); - it('generate no default plugins run nothing', async () => { const program = createProgram(); await program.parseAsync(['generate', '--no-dependency-check', '--no-default-plugins'], { from: 'user' }); diff --git a/tests/integration/tests/cli/init.test.ts b/tests/integration/tests/cli/init.test.ts index 96492b286..987752bd2 100644 --- a/tests/integration/tests/cli/init.test.ts +++ b/tests/integration/tests/cli/init.test.ts @@ -24,9 +24,12 @@ describe('CLI init command tests', () => { }); it('init project t3 npm std', async () => { - execSync('npx --yes create-t3-app@latest --prisma --CI --noGit .', 'inherit', { - npm_config_user_agent: 'npm', - npm_config_cache: getWorkspaceNpmCacheFolder(__dirname), + execSync('npx --yes create-t3-app@latest --prisma --CI --noGit .', { + stdio: 'inherit', + env: { + npm_config_user_agent: 'npm', + npm_config_cache: getWorkspaceNpmCacheFolder(__dirname), + }, }); createNpmrc(); @@ -42,9 +45,12 @@ describe('CLI init command tests', () => { // Disabled because it blows up memory on MAC, not sure why ... // eslint-disable-next-line jest/no-disabled-tests it.skip('init project t3 yarn std', async () => { - execSync('npx --yes create-t3-app@latest --prisma --CI --noGit .', 'inherit', { - npm_config_user_agent: 'yarn', - npm_config_cache: getWorkspaceNpmCacheFolder(__dirname), + execSync('npx --yes create-t3-app@latest --prisma --CI --noGit .', { + stdio: 'inherit', + env: { + npm_config_user_agent: 'yarn', + npm_config_cache: getWorkspaceNpmCacheFolder(__dirname), + }, }); createNpmrc(); @@ -58,9 +64,12 @@ describe('CLI init command tests', () => { }); it('init project t3 pnpm std', async () => { - execSync('npx --yes create-t3-app@latest --prisma --CI --noGit .', 'inherit', { - npm_config_user_agent: 'pnpm', - npm_config_cache: getWorkspaceNpmCacheFolder(__dirname), + execSync('npx --yes create-t3-app@latest --prisma --CI --noGit .', { + stdio: 'inherit', + env: { + npm_config_user_agent: 'pnpm', + npm_config_cache: getWorkspaceNpmCacheFolder(__dirname), + }, }); createNpmrc(); @@ -74,9 +83,12 @@ describe('CLI init command tests', () => { }); it('init project t3 non-std prisma schema', async () => { - execSync('npx --yes create-t3-app@latest --prisma --CI --noGit .', 'inherit', { - npm_config_user_agent: 'npm', - npm_config_cache: getWorkspaceNpmCacheFolder(__dirname), + execSync('npx --yes create-t3-app@latest --prisma --CI --noGit .', { + stdio: 'inherit', + env: { + npm_config_user_agent: 'npm', + npm_config_cache: getWorkspaceNpmCacheFolder(__dirname), + }, }); createNpmrc(); fs.renameSync('prisma/schema.prisma', 'prisma/my.prisma'); diff --git a/tests/integration/tests/enhancements/with-omit/with-omit.test.ts b/tests/integration/tests/enhancements/with-omit/with-omit.test.ts index 61d44b440..82e3ec2c8 100644 --- a/tests/integration/tests/enhancements/with-omit/with-omit.test.ts +++ b/tests/integration/tests/enhancements/with-omit/with-omit.test.ts @@ -1,4 +1,3 @@ -import { withOmit } from '@zenstackhq/runtime'; import { loadSchema } from '@zenstackhq/testtools'; import path from 'path'; @@ -77,32 +76,4 @@ describe('Omit test', () => { expect(e.profile.image).toBeUndefined(); }); }); - - it('customization', async () => { - const { prisma } = await loadSchema(model, { getPrismaOnly: true, output: './zen' }); - - const db = withOmit(prisma, { loadPath: './zen' }); - const r = await db.user.create({ - include: { profile: true }, - data: { - id: '1', - password: 'abc123', - profile: { create: { image: 'an image' } }, - }, - }); - expect(r.password).toBeUndefined(); - expect(r.profile.image).toBeUndefined(); - - const db1 = withOmit(prisma, { modelMeta: require(path.resolve('./zen/model-meta')).default }); - const r1 = await db1.user.create({ - include: { profile: true }, - data: { - id: '2', - password: 'abc123', - profile: { create: { image: 'an image' } }, - }, - }); - expect(r1.password).toBeUndefined(); - expect(r1.profile.image).toBeUndefined(); - }); }); diff --git a/tests/integration/tests/enhancements/with-password/with-password.test.ts b/tests/integration/tests/enhancements/with-password/with-password.test.ts index 62e30636b..737338666 100644 --- a/tests/integration/tests/enhancements/with-password/with-password.test.ts +++ b/tests/integration/tests/enhancements/with-password/with-password.test.ts @@ -1,4 +1,3 @@ -import { withPassword } from '@zenstackhq/runtime'; import { loadSchema } from '@zenstackhq/testtools'; import { compareSync } from 'bcryptjs'; import path from 'path'; @@ -42,26 +41,4 @@ describe('Password test', () => { }); expect(compareSync('abc456', r1.password)).toBeTruthy(); }); - - it('customization', async () => { - const { prisma } = await loadSchema(model, { getPrismaOnly: true, output: './zen' }); - - const db = withPassword(prisma, { loadPath: './zen' }); - const r = await db.user.create({ - data: { - id: '1', - password: 'abc123', - }, - }); - expect(compareSync('abc123', r.password)).toBeTruthy(); - - const db1 = withPassword(prisma, { modelMeta: require(path.resolve('./zen/model-meta')).default }); - const r1 = await db1.user.create({ - data: { - id: '2', - password: 'abc123', - }, - }); - expect(compareSync('abc123', r1.password)).toBeTruthy(); - }); }); diff --git a/tests/integration/tests/enhancements/with-policy/client-extensions.test.ts b/tests/integration/tests/enhancements/with-policy/client-extensions.test.ts index cadb42767..3db969e09 100644 --- a/tests/integration/tests/enhancements/with-policy/client-extensions.test.ts +++ b/tests/integration/tests/enhancements/with-policy/client-extensions.test.ts @@ -14,7 +14,7 @@ describe('With Policy: client extensions', () => { }); it('all model new method', async () => { - const { prisma, Prisma } = await loadSchema( + const { prisma, prismaModule } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -29,13 +29,13 @@ describe('With Policy: client extensions', () => { await prisma.model.create({ data: { value: 1 } }); await prisma.model.create({ data: { value: 2 } }); - const ext = Prisma.defineExtension((_prisma: any) => { + const ext = prismaModule.defineExtension((_prisma: any) => { return _prisma.$extends({ name: 'prisma-extension-getAll', model: { $allModels: { async getAll(this: T, args?: any) { - const context = Prisma.getExtensionContext(this); + const context = prismaModule.getExtensionContext(this); const r = await (context as any).findMany(args); console.log('getAll result:', r); return r; @@ -55,7 +55,7 @@ describe('With Policy: client extensions', () => { }); it('one model new method', async () => { - const { prisma, Prisma } = await loadSchema( + const { prisma, prismaModule } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -70,13 +70,13 @@ describe('With Policy: client extensions', () => { await prisma.model.create({ data: { value: 1 } }); await prisma.model.create({ data: { value: 2 } }); - const ext = Prisma.defineExtension((_prisma: any) => { + const ext = prismaModule.defineExtension((_prisma: any) => { return _prisma.$extends({ name: 'prisma-extension-getAll', model: { model: { async getAll(this: T, args?: any) { - const context = Prisma.getExtensionContext(this); + const context = prismaModule.getExtensionContext(this); const r = await (context as any).findMany(args); return r; }, @@ -91,7 +91,7 @@ describe('With Policy: client extensions', () => { }); it('add client method', async () => { - const { prisma, Prisma } = await loadSchema( + const { prisma, prismaModule } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -104,7 +104,7 @@ describe('With Policy: client extensions', () => { let logged = false; - const ext = Prisma.defineExtension((_prisma: any) => { + const ext = prismaModule.defineExtension((_prisma: any) => { return _prisma.$extends({ name: 'prisma-extension-log', client: { @@ -122,7 +122,7 @@ describe('With Policy: client extensions', () => { }); it('query override one model', async () => { - const { prisma, Prisma } = await loadSchema( + const { prisma, prismaModule } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -138,7 +138,7 @@ describe('With Policy: client extensions', () => { await prisma.model.create({ data: { x: 1, y: 200 } }); await prisma.model.create({ data: { x: 2, y: 300 } }); - const ext = Prisma.defineExtension((_prisma: any) => { + const ext = prismaModule.defineExtension((_prisma: any) => { return _prisma.$extends({ name: 'prisma-extension-queryOverride', query: { @@ -159,7 +159,7 @@ describe('With Policy: client extensions', () => { }); it('query override all models', async () => { - const { prisma, Prisma } = await loadSchema( + const { prisma, prismaModule } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -175,7 +175,7 @@ describe('With Policy: client extensions', () => { await prisma.model.create({ data: { x: 1, y: 200 } }); await prisma.model.create({ data: { x: 2, y: 300 } }); - const ext = Prisma.defineExtension((_prisma: any) => { + const ext = prismaModule.defineExtension((_prisma: any) => { return _prisma.$extends({ name: 'prisma-extension-queryOverride', query: { @@ -197,7 +197,7 @@ describe('With Policy: client extensions', () => { }); it('query override all operations', async () => { - const { prisma, Prisma } = await loadSchema( + const { prisma, prismaModule } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -213,7 +213,7 @@ describe('With Policy: client extensions', () => { await prisma.model.create({ data: { x: 1, y: 200 } }); await prisma.model.create({ data: { x: 2, y: 300 } }); - const ext = Prisma.defineExtension((_prisma: any) => { + const ext = prismaModule.defineExtension((_prisma: any) => { return _prisma.$extends({ name: 'prisma-extension-queryOverride', query: { @@ -235,7 +235,7 @@ describe('With Policy: client extensions', () => { }); it('query override everything', async () => { - const { prisma, Prisma } = await loadSchema( + const { prisma, prismaModule } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -251,7 +251,7 @@ describe('With Policy: client extensions', () => { await prisma.model.create({ data: { x: 1, y: 200 } }); await prisma.model.create({ data: { x: 2, y: 300 } }); - const ext = Prisma.defineExtension((_prisma: any) => { + const ext = prismaModule.defineExtension((_prisma: any) => { return _prisma.$extends({ name: 'prisma-extension-queryOverride', query: { @@ -271,7 +271,7 @@ describe('With Policy: client extensions', () => { }); it('result mutation', async () => { - const { prisma, Prisma } = await loadSchema( + const { prisma, prismaModule } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -310,7 +310,7 @@ describe('With Policy: client extensions', () => { }); it('result custom fields', async () => { - const { prisma, Prisma } = await loadSchema( + const { prisma, prismaModule } = await loadSchema( ` model Model { id String @id @default(uuid()) diff --git a/tests/integration/tests/enhancements/with-policy/options.test.ts b/tests/integration/tests/enhancements/with-policy/options.test.ts index 2c661ceb4..79adb8c49 100644 --- a/tests/integration/tests/enhancements/with-policy/options.test.ts +++ b/tests/integration/tests/enhancements/with-policy/options.test.ts @@ -3,18 +3,8 @@ import { loadSchema } from '@zenstackhq/testtools'; import path from 'path'; describe('Password test', () => { - let origDir: string; - - beforeAll(async () => { - origDir = path.resolve('.'); - }); - - afterEach(async () => { - process.chdir(origDir); - }); - it('load path', async () => { - const { prisma } = await loadSchema( + const { prisma, projectDir } = await loadSchema( ` model Foo { id String @id @default(cuid()) @@ -25,7 +15,8 @@ describe('Password test', () => { { getPrismaOnly: true, output: './zen' } ); - const db = withPolicy(prisma, undefined, { loadPath: './zen' }); + const enhance = require(path.join(projectDir, 'zen/enhance')).enhance; + const db = enhance(prisma, { loadPath: './zen' }); await expect( db.foo.create({ data: { x: 0 }, @@ -34,7 +25,7 @@ describe('Password test', () => { }); it('overrides', async () => { - const { prisma } = await loadSchema( + const { prisma, projectDir } = await loadSchema( ` model Foo { id String @id @default(cuid()) @@ -45,9 +36,10 @@ describe('Password test', () => { { getPrismaOnly: true, output: './zen' } ); - const db = withPolicy(prisma, undefined, { - modelMeta: require(path.resolve('./zen/model-meta')).default, - policy: require(path.resolve('./zen/policy')).default, + const enhance = require(path.join(projectDir, 'zen/enhance')).enhance; + const db = enhance(prisma, { + modelMeta: require(path.join(projectDir, 'zen/model-meta')).default, + policy: require(path.resolve(projectDir, 'zen/policy')).default, }); await expect( db.foo.create({ diff --git a/tests/integration/tests/frameworks/nextjs/test-project/package.json b/tests/integration/tests/frameworks/nextjs/test-project/package.json index 1461849f1..7b93ec340 100644 --- a/tests/integration/tests/frameworks/nextjs/test-project/package.json +++ b/tests/integration/tests/frameworks/nextjs/test-project/package.json @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "@prisma/client": "^4.8.0", + "@prisma/client": "^5.0.0", "@types/node": "18.11.18", "@types/react": "18.0.27", "@types/react-dom": "18.0.10", @@ -22,6 +22,6 @@ "zod": "^3.22.4" }, "devDependencies": { - "prisma": "^4.8.0" + "prisma": "^5.0.0" } } diff --git a/tests/integration/tests/frameworks/trpc/generation.test.ts b/tests/integration/tests/frameworks/trpc/generation.test.ts index 5e15d9943..3c867bc0f 100644 --- a/tests/integration/tests/frameworks/trpc/generation.test.ts +++ b/tests/integration/tests/frameworks/trpc/generation.test.ts @@ -35,7 +35,9 @@ describe('tRPC Routers Generation Tests', () => { process.chdir(testDir); run('npm install'); run('npm install ' + deps); - run('npx zenstack generate --no-dependency-check --schema ./todo.zmodel', { NODE_PATH: 'node_modules' }); + run('npx zenstack generate --no-dependency-check --schema ./todo.zmodel', { + NODE_PATH: 'node_modules', + }); run('npm run build', { NODE_PATH: 'node_modules' }); }); }); diff --git a/tests/integration/tests/frameworks/trpc/test-project/package.json b/tests/integration/tests/frameworks/trpc/test-project/package.json index f27687e63..8445cc451 100644 --- a/tests/integration/tests/frameworks/trpc/test-project/package.json +++ b/tests/integration/tests/frameworks/trpc/test-project/package.json @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "@prisma/client": "^4.8.0", + "@prisma/client": "^5.0.0", "@tanstack/react-query": "^4.22.4", "@trpc/client": "^10.34.0", "@trpc/next": "^10.34.0", @@ -26,6 +26,6 @@ "zod": "^3.22.4" }, "devDependencies": { - "prisma": "^4.8.0" + "prisma": "^5.0.0" } } diff --git a/tests/integration/tests/frameworks/trpc/test-project/todo.zmodel b/tests/integration/tests/frameworks/trpc/test-project/todo.zmodel index 6840f8978..92363c825 100644 --- a/tests/integration/tests/frameworks/trpc/test-project/todo.zmodel +++ b/tests/integration/tests/frameworks/trpc/test-project/todo.zmodel @@ -7,16 +7,6 @@ generator js { provider = 'prisma-client-js' } -plugin meta { - provider = '@core/model-meta' - output = '.zenstack' -} - -plugin policy { - provider = '@core/access-policy' - output = '.zenstack' -} - plugin trpc { provider = '@zenstackhq/trpc' output = 'server/routers/generated' From c52e25521c7670881eecf58cc7b290880a9d9b40 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 8 Jan 2024 21:22:05 +0800 Subject: [PATCH 2/5] fix test --- tests/integration/tests/cli/plugins.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/integration/tests/cli/plugins.test.ts b/tests/integration/tests/cli/plugins.test.ts index 005a0f69b..4f93106e8 100644 --- a/tests/integration/tests/cli/plugins.test.ts +++ b/tests/integration/tests/cli/plugins.test.ts @@ -131,12 +131,10 @@ describe('CLI Plugins Tests', () => { }`, `plugin meta { provider = '@core/model-meta' - output = 'model-meta' } `, `plugin policy { provider = '@core/access-policy' - output = 'policy' }`, `plugin tanstack { provider = '@zenstackhq/tanstack-query' From 12cb89c2ce8e7f9f5d018be880daf3db6b8d3f53 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 8 Jan 2024 21:23:10 +0800 Subject: [PATCH 3/5] update CI --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 9c46129ff..484779e32 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -9,7 +9,7 @@ env: on: pull_request: - branches: ['dev', 'main'] + branches: ['dev', 'main', 'v2'] jobs: build-test: From ba20f0954fee408e8ddb1a51049f3a905e30edcd Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 8 Jan 2024 22:44:38 +0800 Subject: [PATCH 4/5] fix test --- packages/schema/src/plugins/enhancer/index.ts | 2 +- .../tests/enhancements/with-policy/client-extensions.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/schema/src/plugins/enhancer/index.ts b/packages/schema/src/plugins/enhancer/index.ts index 9af0ebcca..f18418f9c 100644 --- a/packages/schema/src/plugins/enhancer/index.ts +++ b/packages/schema/src/plugins/enhancer/index.ts @@ -43,7 +43,7 @@ export function enhance(prisma: DbClient, context?: Wit return createEnhancement(prisma, { modelMeta, policy, - zodSchemas: zodSchemas as (ZodSchemas | undefined), + zodSchemas: zodSchemas as unknown as (ZodSchemas | undefined), prismaModule: Prisma, ...options }, context); diff --git a/tests/integration/tests/enhancements/with-policy/client-extensions.test.ts b/tests/integration/tests/enhancements/with-policy/client-extensions.test.ts index 3db969e09..646e11399 100644 --- a/tests/integration/tests/enhancements/with-policy/client-extensions.test.ts +++ b/tests/integration/tests/enhancements/with-policy/client-extensions.test.ts @@ -285,7 +285,7 @@ describe('With Policy: client extensions', () => { await prisma.model.create({ data: { value: 0 } }); await prisma.model.create({ data: { value: 1 } }); - const ext = Prisma.defineExtension((_prisma: any) => { + const ext = prismaModule.defineExtension((_prisma: any) => { return _prisma.$extends({ name: 'prisma-extension-resultMutation', query: { @@ -324,7 +324,7 @@ describe('With Policy: client extensions', () => { await prisma.model.create({ data: { value: 0 } }); await prisma.model.create({ data: { value: 1 } }); - const ext = Prisma.defineExtension((_prisma: any) => { + const ext = prismaModule.defineExtension((_prisma: any) => { return _prisma.$extends({ name: 'prisma-extension-resultNewFields', result: { From c42a7f648dc0c3ba29b75315c159bfbda9387b0f Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 8 Jan 2024 23:25:29 +0800 Subject: [PATCH 5/5] fix test --- packages/testtools/src/schema.ts | 2 ++ .../with-policy/client-extensions.test.ts | 35 +++++++++---------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/testtools/src/schema.ts b/packages/testtools/src/schema.ts index 25ffa4621..52d0e2376 100644 --- a/packages/testtools/src/schema.ts +++ b/packages/testtools/src/schema.ts @@ -254,6 +254,7 @@ export async function loadSchema(schema: string, options?: SchemaLoadOptions) { withOmit: undefined as any, withPassword: undefined as any, enhance: undefined as any, + enhanceRaw: undefined as any, policy: undefined as any, modelMeta: undefined as any, zodSchemas: undefined as any, @@ -291,6 +292,7 @@ export async function loadSchema(schema: string, options?: SchemaLoadOptions) { withPassword: () => withPassword(prisma, { modelMeta }), enhance: (user?: AuthUser): FullDbClientContract => enhance(prisma, { user }, { policy, modelMeta, zodSchemas, logPrismaQuery: opt.logPrismaQuery }), + enhanceRaw: enhance, policy, modelMeta, zodSchemas, diff --git a/tests/integration/tests/enhancements/with-policy/client-extensions.test.ts b/tests/integration/tests/enhancements/with-policy/client-extensions.test.ts index 646e11399..13f05aa51 100644 --- a/tests/integration/tests/enhancements/with-policy/client-extensions.test.ts +++ b/tests/integration/tests/enhancements/with-policy/client-extensions.test.ts @@ -1,4 +1,3 @@ -import { enhance } from '@zenstackhq/runtime'; import { loadSchema } from '@zenstackhq/testtools'; import path from 'path'; @@ -14,7 +13,7 @@ describe('With Policy: client extensions', () => { }); it('all model new method', async () => { - const { prisma, prismaModule } = await loadSchema( + const { prisma, enhanceRaw, prismaModule } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -46,7 +45,7 @@ describe('With Policy: client extensions', () => { }); const xprisma = prisma.$extends(ext); - const db = enhance(xprisma); + const db = enhanceRaw(xprisma); await expect(db.model.getAll()).resolves.toHaveLength(2); // FIXME: extending an enhanced client doesn't work for this case @@ -55,7 +54,7 @@ describe('With Policy: client extensions', () => { }); it('one model new method', async () => { - const { prisma, prismaModule } = await loadSchema( + const { prisma, enhanceRaw, prismaModule } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -86,12 +85,12 @@ describe('With Policy: client extensions', () => { }); const xprisma = prisma.$extends(ext); - const db = enhance(xprisma); + const db = enhanceRaw(xprisma); await expect(db.model.getAll()).resolves.toHaveLength(2); }); it('add client method', async () => { - const { prisma, prismaModule } = await loadSchema( + const { prisma, prismaModule, enhanceRaw } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -122,7 +121,7 @@ describe('With Policy: client extensions', () => { }); it('query override one model', async () => { - const { prisma, prismaModule } = await loadSchema( + const { prisma, prismaModule, enhanceRaw } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -154,12 +153,12 @@ describe('With Policy: client extensions', () => { }); const xprisma = prisma.$extends(ext); - const db = enhance(xprisma); + const db = enhanceRaw(xprisma); await expect(db.model.findMany()).resolves.toHaveLength(1); }); it('query override all models', async () => { - const { prisma, prismaModule } = await loadSchema( + const { prisma, prismaModule, enhanceRaw } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -192,12 +191,12 @@ describe('With Policy: client extensions', () => { }); const xprisma = prisma.$extends(ext); - const db = enhance(xprisma); + const db = enhanceRaw(xprisma); await expect(db.model.findMany()).resolves.toHaveLength(1); }); it('query override all operations', async () => { - const { prisma, prismaModule } = await loadSchema( + const { prisma, prismaModule, enhanceRaw } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -230,12 +229,12 @@ describe('With Policy: client extensions', () => { }); const xprisma = prisma.$extends(ext); - const db = enhance(xprisma); + const db = enhanceRaw(xprisma); await expect(db.model.findMany()).resolves.toHaveLength(1); }); it('query override everything', async () => { - const { prisma, prismaModule } = await loadSchema( + const { prisma, prismaModule, enhanceRaw } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -266,12 +265,12 @@ describe('With Policy: client extensions', () => { }); const xprisma = prisma.$extends(ext); - const db = enhance(xprisma); + const db = enhanceRaw(xprisma); await expect(db.model.findMany()).resolves.toHaveLength(1); }); it('result mutation', async () => { - const { prisma, prismaModule } = await loadSchema( + const { prisma, prismaModule, enhanceRaw } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -303,14 +302,14 @@ describe('With Policy: client extensions', () => { }); const xprisma = prisma.$extends(ext); - const db = enhance(xprisma); + const db = enhanceRaw(xprisma); const r = await db.model.findMany(); expect(r).toHaveLength(1); expect(r).toEqual(expect.arrayContaining([expect.objectContaining({ value: 2 })])); }); it('result custom fields', async () => { - const { prisma, prismaModule } = await loadSchema( + const { prisma, prismaModule, enhanceRaw } = await loadSchema( ` model Model { id String @id @default(uuid()) @@ -341,7 +340,7 @@ describe('With Policy: client extensions', () => { }); const xprisma = prisma.$extends(ext); - const db = enhance(xprisma); + const db = enhanceRaw(xprisma); const r = await db.model.findMany(); expect(r).toHaveLength(1); expect(r).toEqual(expect.arrayContaining([expect.objectContaining({ doubleValue: 2 })]));