diff --git a/package.json b/package.json index 9b93f63eb..9e3bb72aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "2.13.3", + "version": "2.14.0", "description": "", "scripts": { "build": "pnpm -r --filter=\"!./packages/ide/*\" build", diff --git a/packages/ide/jetbrains/build.gradle.kts b/packages/ide/jetbrains/build.gradle.kts index fd0b97d73..85716617e 100644 --- a/packages/ide/jetbrains/build.gradle.kts +++ b/packages/ide/jetbrains/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "dev.zenstack" -version = "2.13.3" +version = "2.14.0" repositories { mavenCentral() diff --git a/packages/ide/jetbrains/package.json b/packages/ide/jetbrains/package.json index 4c817178b..6579e7d56 100644 --- a/packages/ide/jetbrains/package.json +++ b/packages/ide/jetbrains/package.json @@ -1,6 +1,6 @@ { "name": "jetbrains", - "version": "2.13.3", + "version": "2.14.0", "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 b65363f8d..8ccc656f9 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "2.13.3", + "version": "2.14.0", "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 e2b9283e8..f527c4e48 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": "2.13.3", + "version": "2.14.0", "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 f11dd9f2c..2f9765fd3 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": "2.13.3", + "version": "2.14.0", "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 9c01d567c..217e63497 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": "2.13.3", + "version": "2.14.0", "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 066851027..b20b8ec81 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": "2.13.3", + "version": "2.14.0", "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 5fcfb5b20..7dfa15283 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": "2.13.3", + "version": "2.14.0", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { diff --git a/packages/plugins/trpc/tests/projects/nuxt-trpc-v10/package.json b/packages/plugins/trpc/tests/projects/nuxt-trpc-v10/package.json index a26fb9acf..c3c2d8d33 100644 --- a/packages/plugins/trpc/tests/projects/nuxt-trpc-v10/package.json +++ b/packages/plugins/trpc/tests/projects/nuxt-trpc-v10/package.json @@ -10,7 +10,7 @@ "postinstall": "nuxt prepare" }, "dependencies": { - "@prisma/client": "6.5.x", + "@prisma/client": "6.6.x", "@trpc/client": "^10.45.2", "@trpc/server": "^10.45.2", "nuxt": "^3.14.1592", @@ -21,7 +21,7 @@ }, "devDependencies": { "esbuild": "^0.24.0", - "prisma": "6.5.x", + "prisma": "6.6.x", "typescript": "^5.6.2", "vue-tsc": "^2.1.10" } diff --git a/packages/plugins/trpc/tests/projects/nuxt-trpc-v11/package.json b/packages/plugins/trpc/tests/projects/nuxt-trpc-v11/package.json index 26bb4cd05..7073f896c 100644 --- a/packages/plugins/trpc/tests/projects/nuxt-trpc-v11/package.json +++ b/packages/plugins/trpc/tests/projects/nuxt-trpc-v11/package.json @@ -10,7 +10,7 @@ "postinstall": "nuxt prepare" }, "dependencies": { - "@prisma/client": "6.5.x", + "@prisma/client": "6.6.x", "@trpc/client": "^11.0.0-rc.563", "@trpc/server": "^11.0.0-rc.563", "nuxt": "^3.14.1592", @@ -21,7 +21,7 @@ }, "devDependencies": { "esbuild": "^0.24.0", - "prisma": "6.5.x", + "prisma": "6.6s.x", "typescript": "^5.6.2", "vue-tsc": "^2.1.10" } diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v11/package.json b/packages/plugins/trpc/tests/projects/t3-trpc-v11/package.json index 20099f251..3c230ca8b 100644 --- a/packages/plugins/trpc/tests/projects/t3-trpc-v11/package.json +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v11/package.json @@ -15,7 +15,7 @@ "start": "next start" }, "dependencies": { - "@prisma/client": "6.5.x", + "@prisma/client": "6.6.x", "@t3-oss/env-nextjs": "^0.10.1", "@tanstack/react-query": "^5.50.0", "@trpc/client": "^11.0.0-rc.446", @@ -39,7 +39,7 @@ "@typescript-eslint/parser": "^8.1.0", "eslint": "^8.57.0", "eslint-config-next": "^14.2.4", - "prisma": "6.5.x", + "prisma": "6.6.x", "typescript": "^5.5.3" }, "ct3aMetadata": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index b53370c3d..7457154fe 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "2.13.3", + "version": "2.14.0", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", @@ -116,7 +116,7 @@ "zod-validation-error": "^1.5.0" }, "peerDependencies": { - "@prisma/client": "5.0.0 - 6.5.x" + "@prisma/client": "5.0.0 - 6.6.x" }, "author": { "name": "ZenStack Team" diff --git a/packages/schema/package.json b/packages/schema/package.json index 760bfc0c7..23bcecff5 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "FullStack enhancement for Prisma ORM: seamless integration from database to UI", - "version": "2.13.3", + "version": "2.14.0", "author": { "name": "ZenStack Team" }, @@ -123,10 +123,10 @@ "zod-validation-error": "^1.5.0" }, "peerDependencies": { - "prisma": "5.0.0 - 6.5.x" + "prisma": "5.0.0 - 6.6.x" }, "devDependencies": { - "@prisma/client": "6.5.x", + "@prisma/client": "6.6.x", "@types/async-exit-hook": "^2.0.0", "@types/pluralize": "^0.0.29", "@types/semver": "^7.3.13", diff --git a/packages/schema/src/plugins/enhancer/enhance/index.ts b/packages/schema/src/plugins/enhancer/enhance/index.ts index c5a497240..efbda3f67 100644 --- a/packages/schema/src/plugins/enhancer/enhance/index.ts +++ b/packages/schema/src/plugins/enhancer/enhance/index.ts @@ -1,4 +1,3 @@ -import { ReadonlyDeep } from '@prisma/generator-helper'; import { DELEGATE_AUX_RELATION_PREFIX } from '@zenstackhq/runtime'; import { PluginError, @@ -114,21 +113,18 @@ export class EnhancerGenerator { if (this.needsLogicalClient) { prismaTypesFixed = true; - resultPrismaTypeImport = `${LOGICAL_CLIENT_GENERATION_PATH}/index-fixed`; + resultPrismaTypeImport = LOGICAL_CLIENT_GENERATION_PATH; const result = await this.generateLogicalPrisma(); dmmf = result.dmmf; } // reexport PrismaClient types (original or fixed) - const modelsDts = this.project.createSourceFile( - path.join(this.outDir, 'models.d.ts'), + const modelsTs = this.project.createSourceFile( + path.join(this.outDir, 'models.ts'), `export * from '${resultPrismaTypeImport}';`, { overwrite: true } ); - await modelsDts.save(); - - // reexport values from the original PrismaClient (enums, etc.) - fs.writeFileSync(path.join(this.outDir, 'models.js'), `module.exports = require('${prismaImport}');`); + this.saveSourceFile(modelsTs); const authDecl = getAuthDecl(getDataModelAndTypeDefs(this.model)); const authTypes = authDecl ? generateAuthType(this.model, authDecl) : ''; @@ -177,7 +173,7 @@ ${ return { dmmf, newPrismaClientDtsPath: prismaTypesFixed - ? path.resolve(this.outDir, LOGICAL_CLIENT_GENERATION_PATH, 'index-fixed.d.ts') + ? path.resolve(this.outDir, LOGICAL_CLIENT_GENERATION_PATH, 'index.d.ts') : undefined, }; } @@ -422,7 +418,7 @@ export type Enhanced = return dmmf; } - private shouldBeOptional(field: ReadonlyDeep, dataModel: DataModel) { + private shouldBeOptional(field: DMMF.SchemaArg, dataModel: DataModel) { const dmField = dataModel.fields.find((f) => f.name === field.name); if (!dmField) { return false; @@ -457,7 +453,7 @@ export type Enhanced = } private async processClientTypes(prismaClientDir: string) { - // make necessary updates to the generated `index.d.ts` file and save it as `index-fixed.d.ts` + // make necessary updates to the generated `index.d.ts` file and overwrite it const project = new Project(); const sf = project.addSourceFileAtPath(path.join(prismaClientDir, 'index.d.ts')); @@ -472,8 +468,7 @@ export type Enhanced = } }); - // transform index.d.ts and save it into a new file (better perf than in-line editing) - + // transform index.d.ts and write it into a new file (better perf than in-line editing) const sfNew = project.createSourceFile(path.join(prismaClientDir, 'index-fixed.d.ts'), undefined, { overwrite: true, }); @@ -483,6 +478,9 @@ export type Enhanced = this.generateExtraTypes(sfNew); sfNew.formatText(); + + // Save the transformed file over the original + await sfNew.move(sf.getFilePath(), { overwrite: true }); await sfNew.save(); } diff --git a/packages/schema/src/plugins/enhancer/policy/expression-writer.ts b/packages/schema/src/plugins/enhancer/policy/expression-writer.ts index 0d792bdc1..2c3334fb3 100644 --- a/packages/schema/src/plugins/enhancer/policy/expression-writer.ts +++ b/packages/schema/src/plugins/enhancer/policy/expression-writer.ts @@ -21,6 +21,7 @@ import { } from '@zenstackhq/language/ast'; import { DELEGATE_AUX_RELATION_PREFIX, PolicyOperationKind } from '@zenstackhq/runtime'; import { + CodeWriter, ExpressionContext, getFunctionExpressionContext, getIdFields, @@ -37,7 +38,6 @@ import { } from '@zenstackhq/sdk'; import { lowerCaseFirst } from 'lower-case-first'; import invariant from 'tiny-invariant'; -import { CodeBlockWriter } from 'ts-morph'; import { name } from '..'; import { isCheckInvocation } from '../../../utils/ast-utils'; @@ -77,7 +77,7 @@ export class ExpressionWriter { /** * Constructs a new ExpressionWriter */ - constructor(private readonly writer: CodeBlockWriter, private readonly options: ExpressionWriterOptions) { + constructor(private readonly writer: CodeWriter, private readonly options: ExpressionWriterOptions) { this.plainExprBuilder = new TypeScriptExpressionTransformer({ context: ExpressionContext.AccessPolicy, isPostGuard: this.options.isPostGuard, diff --git a/packages/schema/src/plugins/enhancer/policy/policy-guard-generator.ts b/packages/schema/src/plugins/enhancer/policy/policy-guard-generator.ts index 9ffe41dcb..e7651754d 100644 --- a/packages/schema/src/plugins/enhancer/policy/policy-guard-generator.ts +++ b/packages/schema/src/plugins/enhancer/policy/policy-guard-generator.ts @@ -15,7 +15,9 @@ import { } from '@zenstackhq/language/ast'; import { PolicyCrudKind, type PolicyOperationKind } from '@zenstackhq/runtime'; import { + type CodeWriter, ExpressionContext, + FastWriter, PluginOptions, PolicyAnalysisResult, RUNTIME_PACKAGE, @@ -32,14 +34,7 @@ import { getPrismaClientImportSpec } from '@zenstackhq/sdk/prisma'; import { streamAst } from 'langium'; import { lowerCaseFirst } from 'lower-case-first'; import path from 'path'; -import { - CodeBlockWriter, - FunctionDeclaration, - Project, - SourceFile, - VariableDeclarationKind, - WriterFunction, -} from 'ts-morph'; +import { FunctionDeclarationStructure, OptionalKind, Project, SourceFile, VariableDeclarationKind } from 'ts-morph'; import { isCheckInvocation } from '../../../utils/ast-utils'; import { ConstraintTransformer } from './constraint-transformer'; import { @@ -56,6 +51,8 @@ import { * Generates source file that contains Prisma query guard objects used for injecting database queries */ export class PolicyGenerator { + private extraFunctions: OptionalKind[] = []; + constructor(private options: PluginOptions) {} generate(project: Project, model: Model, output: string) { @@ -65,23 +62,28 @@ export class PolicyGenerator { const models = getDataModels(model); + const writer = new FastWriter(); + writer.block(() => { + this.writePolicy(writer, models); + this.writeValidationMeta(writer, models); + this.writeAuthSelector(models, writer); + }); + sf.addVariableStatement({ declarationKind: VariableDeclarationKind.Const, declarations: [ { name: 'policy', type: 'PolicyDef', - initializer: (writer) => { - writer.block(() => { - this.writePolicy(writer, models, sf); - this.writeValidationMeta(writer, models); - this.writeAuthSelector(models, writer); - }); - }, + initializer: writer.result, }, ], }); + if (this.extraFunctions.length > 0) { + sf.addFunctions(this.extraFunctions); + } + sf.addStatements('export default policy'); // save ts files if requested explicitly or the user provided @@ -121,7 +123,7 @@ export class PolicyGenerator { } } - private writePolicy(writer: CodeBlockWriter, models: DataModel[], sourceFile: SourceFile) { + private writePolicy(writer: CodeWriter, models: DataModel[]) { writer.write('policy:'); writer.inlineBlock(() => { for (const model of models) { @@ -129,10 +131,10 @@ export class PolicyGenerator { writer.block(() => { // model-level guards - this.writeModelLevelDefs(model, writer, sourceFile); + this.writeModelLevelDefs(model, writer); // field-level guards - this.writeFieldLevelDefs(model, writer, sourceFile); + this.writeFieldLevelDefs(model, writer); }); writer.writeLine(','); @@ -145,55 +147,45 @@ export class PolicyGenerator { // writes model-level policy def for each operation kind for a model // `[modelName]: { [operationKind]: [funcName] },` - private writeModelLevelDefs(model: DataModel, writer: CodeBlockWriter, sourceFile: SourceFile) { + private writeModelLevelDefs(model: DataModel, writer: CodeWriter) { const policies = analyzePolicies(model); writer.write('modelLevel:'); writer.inlineBlock(() => { - this.writeModelReadDef(model, policies, writer, sourceFile); - this.writeModelCreateDef(model, policies, writer, sourceFile); - this.writeModelUpdateDef(model, policies, writer, sourceFile); - this.writeModelPostUpdateDef(model, policies, writer, sourceFile); - this.writeModelDeleteDef(model, policies, writer, sourceFile); + this.writeModelReadDef(model, policies, writer); + this.writeModelCreateDef(model, policies, writer); + this.writeModelUpdateDef(model, policies, writer); + this.writeModelPostUpdateDef(model, policies, writer); + this.writeModelDeleteDef(model, policies, writer); }); writer.writeLine(','); } // writes `read: ...` for a given model - private writeModelReadDef( - model: DataModel, - policies: PolicyAnalysisResult, - writer: CodeBlockWriter, - sourceFile: SourceFile - ) { + private writeModelReadDef(model: DataModel, policies: PolicyAnalysisResult, writer: CodeWriter) { writer.write(`read:`); writer.inlineBlock(() => { - this.writeCommonModelDef(model, 'read', policies, writer, sourceFile); + this.writeCommonModelDef(model, 'read', policies, writer); }); writer.writeLine(','); } // writes `create: ...` for a given model - private writeModelCreateDef( - model: DataModel, - policies: PolicyAnalysisResult, - writer: CodeBlockWriter, - sourceFile: SourceFile - ) { + private writeModelCreateDef(model: DataModel, policies: PolicyAnalysisResult, writer: CodeWriter) { writer.write(`create:`); writer.inlineBlock(() => { - this.writeCommonModelDef(model, 'create', policies, writer, sourceFile); + this.writeCommonModelDef(model, 'create', policies, writer); // create policy has an additional input checker for validating the payload - this.writeCreateInputChecker(model, writer, sourceFile); + this.writeCreateInputChecker(model, writer); }); writer.writeLine(','); } // writes `inputChecker: [funcName]` for a given model - private writeCreateInputChecker(model: DataModel, writer: CodeBlockWriter, sourceFile: SourceFile) { + private writeCreateInputChecker(model: DataModel, writer: CodeWriter) { if (this.canCheckCreateBasedOnInput(model)) { - const inputCheckFunc = this.generateCreateInputCheckerFunction(model, sourceFile); - writer.write(`inputChecker: ${inputCheckFunc.getName()!},`); + const inputCheckFuncName = this.generateCreateInputCheckerFunction(model); + writer.write(`inputChecker: ${inputCheckFuncName},`); } } @@ -237,19 +229,18 @@ export class PolicyGenerator { } // generates a function for checking "create" input - private generateCreateInputCheckerFunction(model: DataModel, sourceFile: SourceFile) { - const statements: (string | WriterFunction)[] = []; + private generateCreateInputCheckerFunction(model: DataModel) { + const statements: string[] = []; const allows = getPolicyExpressions(model, 'allow', 'create'); const denies = getPolicyExpressions(model, 'deny', 'create'); generateNormalizedAuthRef(model, allows, denies, statements); - statements.push((writer) => { - if (allows.length === 0) { - writer.write('return false;'); - return; - } - + // write allow and deny rules + const writer = new FastWriter(); + if (allows.length === 0) { + writer.write('return false;'); + } else { const transformer = new TypeScriptExpressionTransformer({ context: ExpressionContext.AccessPolicy, fieldReferenceContext: 'input', @@ -275,10 +266,12 @@ export class PolicyGenerator { expr = expr ? `${expr} && (${allowStmt})` : allowStmt; writer.write('return ' + expr); - }); + } + statements.push(writer.result); - const func = sourceFile.addFunction({ - name: model.name + '_create_input', + const funcName = model.name + '_create_input'; + this.extraFunctions.push({ + name: funcName, returnType: 'boolean', parameters: [ { @@ -293,33 +286,23 @@ export class PolicyGenerator { statements, }); - return func; + return funcName; } // writes `update: ...` for a given model - private writeModelUpdateDef( - model: DataModel, - policies: PolicyAnalysisResult, - writer: CodeBlockWriter, - sourceFile: SourceFile - ) { + private writeModelUpdateDef(model: DataModel, policies: PolicyAnalysisResult, writer: CodeWriter) { writer.write(`update:`); writer.inlineBlock(() => { - this.writeCommonModelDef(model, 'update', policies, writer, sourceFile); + this.writeCommonModelDef(model, 'update', policies, writer); }); writer.writeLine(','); } // writes `postUpdate: ...` for a given model - private writeModelPostUpdateDef( - model: DataModel, - policies: PolicyAnalysisResult, - writer: CodeBlockWriter, - sourceFile: SourceFile - ) { + private writeModelPostUpdateDef(model: DataModel, policies: PolicyAnalysisResult, writer: CodeWriter) { writer.write(`postUpdate:`); writer.inlineBlock(() => { - this.writeCommonModelDef(model, 'postUpdate', policies, writer, sourceFile); + this.writeCommonModelDef(model, 'postUpdate', policies, writer); // post-update policy has an additional selector for reading the pre-update entity data this.writePostUpdatePreValueSelector(model, writer); @@ -327,7 +310,7 @@ export class PolicyGenerator { writer.writeLine(','); } - private writePostUpdatePreValueSelector(model: DataModel, writer: CodeBlockWriter) { + private writePostUpdatePreValueSelector(model: DataModel, writer: CodeWriter) { const allows = getPolicyExpressions(model, 'allow', 'postUpdate'); const denies = getPolicyExpressions(model, 'deny', 'postUpdate'); const preValueSelect = generateSelectForRules([...allows, ...denies], 'postUpdate'); @@ -337,15 +320,10 @@ export class PolicyGenerator { } // writes `delete: ...` for a given model - private writeModelDeleteDef( - model: DataModel, - policies: PolicyAnalysisResult, - writer: CodeBlockWriter, - sourceFile: SourceFile - ) { + private writeModelDeleteDef(model: DataModel, policies: PolicyAnalysisResult, writer: CodeWriter) { writer.write(`delete:`); writer.inlineBlock(() => { - this.writeCommonModelDef(model, 'delete', policies, writer, sourceFile); + this.writeCommonModelDef(model, 'delete', policies, writer); }); } @@ -354,23 +332,22 @@ export class PolicyGenerator { model: DataModel, kind: PolicyOperationKind, policies: PolicyAnalysisResult, - writer: CodeBlockWriter, - sourceFile: SourceFile + writer: CodeWriter ) { const allows = getPolicyExpressions(model, 'allow', kind); const denies = getPolicyExpressions(model, 'deny', kind); // policy guard - this.writePolicyGuard(model, kind, policies, allows, denies, writer, sourceFile); + this.writePolicyGuard(model, kind, policies, allows, denies, writer); // permission checker if (kind !== 'postUpdate') { - this.writePermissionChecker(model, kind, policies, allows, denies, writer, sourceFile); + this.writePermissionChecker(model, kind, policies, allows, denies, writer); } // write cross-model comparison rules as entity checker functions // because they cannot be checked inside Prisma - const { functionName, selector } = this.writeEntityChecker(model, kind, sourceFile, false); + const { functionName, selector } = this.writeEntityChecker(model, kind, false); if (this.shouldUseEntityChecker(model, kind, true, false)) { writer.write(`entityChecker: { func: ${functionName}, selector: ${JSON.stringify(selector)} },`); @@ -420,18 +397,12 @@ export class PolicyGenerator { }); } - private writeEntityChecker( - target: DataModel | DataModelField, - kind: PolicyOperationKind, - sourceFile: SourceFile, - forOverride: boolean - ) { + private writeEntityChecker(target: DataModel | DataModelField, kind: PolicyOperationKind, forOverride: boolean) { const allows = getPolicyExpressions(target, 'allow', kind, forOverride, 'all'); const denies = getPolicyExpressions(target, 'deny', kind, forOverride, 'all'); const model = isDataModel(target) ? target : (target.$container as DataModel); const func = generateEntityCheckerFunction( - sourceFile, model, kind, allows, @@ -439,9 +410,10 @@ export class PolicyGenerator { isDataModelField(target) ? target : undefined, forOverride ); + this.extraFunctions.push(func); const selector = generateSelectForRules([...allows, ...denies], kind, false, kind !== 'postUpdate') ?? {}; - return { functionName: func.getName()!, selector }; + return { functionName: func.name, selector }; } // writes `guard: ...` for a given policy operation kind @@ -451,46 +423,48 @@ export class PolicyGenerator { policies: ReturnType, allows: Expression[], denies: Expression[], - writer: CodeBlockWriter, - sourceFile: SourceFile + writer: CodeWriter ) { // first handle several cases where a constant function can be used if (kind === 'update' && allows.length === 0) { // no allow rule for 'update', policy is constant based on if there's // post-update counterpart - let func: FunctionDeclaration; + let func: OptionalKind; if (getPolicyExpressions(model, 'allow', 'postUpdate').length === 0) { - func = generateConstantQueryGuardFunction(sourceFile, model, kind, false); + func = generateConstantQueryGuardFunction(model, kind, false); } else { - func = generateConstantQueryGuardFunction(sourceFile, model, kind, true); + func = generateConstantQueryGuardFunction(model, kind, true); } - writer.write(`guard: ${func.getName()!},`); + this.extraFunctions.push(func); + writer.write(`guard: ${func.name},`); return; } if (kind === 'postUpdate' && allows.length === 0 && denies.length === 0) { // no 'postUpdate' rule, always allow - const func = generateConstantQueryGuardFunction(sourceFile, model, kind, true); - writer.write(`guard: ${func.getName()},`); + const func = generateConstantQueryGuardFunction(model, kind, true); + this.extraFunctions.push(func); + writer.write(`guard: ${func.name},`); return; } if (kind in policies && typeof policies[kind as keyof typeof policies] === 'boolean') { // constant policy const func = generateConstantQueryGuardFunction( - sourceFile, model, kind, policies[kind as keyof typeof policies] as boolean ); - writer.write(`guard: ${func.getName()!},`); + this.extraFunctions.push(func); + writer.write(`guard: ${func.name},`); return; } // generate a policy function that evaluates a partial prisma query - const guardFunc = generateQueryGuardFunction(sourceFile, model, kind, allows, denies); - writer.write(`guard: ${guardFunc.getName()!},`); + const guardFunc = generateQueryGuardFunction(model, kind, allows, denies); + this.extraFunctions.push(guardFunc); + writer.write(`guard: ${guardFunc.name},`); } // writes `permissionChecker: ...` for a given policy operation kind @@ -500,8 +474,7 @@ export class PolicyGenerator { policies: PolicyAnalysisResult, allows: Expression[], denies: Expression[], - writer: CodeBlockWriter, - sourceFile: SourceFile + writer: CodeWriter ) { if (this.options.generatePermissionChecker !== true) { return; @@ -524,16 +497,15 @@ export class PolicyGenerator { return; } - const guardFunc = this.generatePermissionCheckerFunction(model, kind, allows, denies, sourceFile); - writer.write(`permissionChecker: ${guardFunc.getName()!},`); + const guardFuncName = this.generatePermissionCheckerFunction(model, kind, allows, denies); + writer.write(`permissionChecker: ${guardFuncName},`); } private generatePermissionCheckerFunction( model: DataModel, kind: string, allows: Expression[], - denies: Expression[], - sourceFile: SourceFile + denies: Expression[] ) { const statements: string[] = []; @@ -545,8 +517,9 @@ export class PolicyGenerator { statements.push(`return ${transformed};`); - const func = sourceFile.addFunction({ - name: `${model.name}$checker$${kind}`, + const funcName = `${model.name}$checker$${kind}`; + this.extraFunctions.push({ + name: funcName, returnType: 'PermissionCheckerConstraint', parameters: [ { @@ -557,23 +530,23 @@ export class PolicyGenerator { statements, }); - return func; + return funcName; } // #endregion // #region Field-level definitions - private writeFieldLevelDefs(model: DataModel, writer: CodeBlockWriter, sf: SourceFile) { + private writeFieldLevelDefs(model: DataModel, writer: CodeWriter) { writer.write('fieldLevel:'); writer.inlineBlock(() => { - this.writeFieldReadDef(model, writer, sf); - this.writeFieldUpdateDef(model, writer, sf); + this.writeFieldReadDef(model, writer); + this.writeFieldUpdateDef(model, writer); }); writer.writeLine(','); } - private writeFieldReadDef(model: DataModel, writer: CodeBlockWriter, sourceFile: SourceFile) { + private writeFieldReadDef(model: DataModel, writer: CodeWriter) { writer.writeLine('read:'); writer.block(() => { for (const field of model.fields) { @@ -589,12 +562,13 @@ export class PolicyGenerator { writer.block(() => { // guard - const guardFunc = generateQueryGuardFunction(sourceFile, model, 'read', allows, denies, field); - writer.write(`guard: ${guardFunc.getName()},`); + const guardFunc = generateQueryGuardFunction(model, 'read', allows, denies, field); + this.extraFunctions.push(guardFunc); + writer.write(`guard: ${guardFunc.name},`); // checker function // write all field-level rules as entity checker function - const { functionName, selector } = this.writeEntityChecker(field, 'read', sourceFile, false); + const { functionName, selector } = this.writeEntityChecker(field, 'read', false); if (this.shouldUseEntityChecker(field, 'read', false, false)) { writer.write( @@ -606,7 +580,6 @@ export class PolicyGenerator { // override guard function const denies = getPolicyExpressions(field, 'deny', 'read'); const overrideGuardFunc = generateQueryGuardFunction( - sourceFile, model, 'read', overrideAllows, @@ -614,10 +587,11 @@ export class PolicyGenerator { field, true ); - writer.write(`overrideGuard: ${overrideGuardFunc.getName()},`); + this.extraFunctions.push(overrideGuardFunc); + writer.write(`overrideGuard: ${overrideGuardFunc.name},`); // additional entity checker for override - const { functionName, selector } = this.writeEntityChecker(field, 'read', sourceFile, true); + const { functionName, selector } = this.writeEntityChecker(field, 'read', true); if (this.shouldUseEntityChecker(field, 'read', false, true)) { writer.write( `overrideEntityChecker: { func: ${functionName}, selector: ${JSON.stringify( @@ -633,7 +607,7 @@ export class PolicyGenerator { writer.writeLine(','); } - private writeFieldUpdateDef(model: DataModel, writer: CodeBlockWriter, sourceFile: SourceFile) { + private writeFieldUpdateDef(model: DataModel, writer: CodeWriter) { writer.writeLine('update:'); writer.block(() => { for (const field of model.fields) { @@ -649,12 +623,13 @@ export class PolicyGenerator { writer.block(() => { // guard - const guardFunc = generateQueryGuardFunction(sourceFile, model, 'update', allows, denies, field); - writer.write(`guard: ${guardFunc.getName()},`); + const guardFunc = generateQueryGuardFunction(model, 'update', allows, denies, field); + this.extraFunctions.push(guardFunc); + writer.write(`guard: ${guardFunc.name},`); // write cross-model comparison rules as entity checker functions // because they cannot be checked inside Prisma - const { functionName, selector } = this.writeEntityChecker(field, 'update', sourceFile, false); + const { functionName, selector } = this.writeEntityChecker(field, 'update', false); if (this.shouldUseEntityChecker(field, 'update', true, false)) { writer.write( `entityChecker: { func: ${functionName}, selector: ${JSON.stringify(selector)} },` @@ -664,7 +639,6 @@ export class PolicyGenerator { if (overrideAllows.length > 0) { // override guard const overrideGuardFunc = generateQueryGuardFunction( - sourceFile, model, 'update', overrideAllows, @@ -672,11 +646,12 @@ export class PolicyGenerator { field, true ); - writer.write(`overrideGuard: ${overrideGuardFunc.getName()},`); + this.extraFunctions.push(overrideGuardFunc); + writer.write(`overrideGuard: ${overrideGuardFunc.name},`); // write cross-model comparison override rules as entity checker functions // because they cannot be checked inside Prisma - const { functionName, selector } = this.writeEntityChecker(field, 'update', sourceFile, true); + const { functionName, selector } = this.writeEntityChecker(field, 'update', true); if (this.shouldUseEntityChecker(field, 'update', true, true)) { writer.write( `overrideEntityChecker: { func: ${functionName}, selector: ${JSON.stringify( @@ -696,7 +671,7 @@ export class PolicyGenerator { //#region Auth selector - private writeAuthSelector(models: DataModel[], writer: CodeBlockWriter) { + private writeAuthSelector(models: DataModel[], writer: CodeWriter) { const authSelector = this.generateAuthSelector(models); if (authSelector) { writer.write(`authSelector: ${JSON.stringify(authSelector)},`); @@ -744,7 +719,7 @@ export class PolicyGenerator { // #region Validation meta - private writeValidationMeta(writer: CodeBlockWriter, models: DataModel[]) { + private writeValidationMeta(writer: CodeWriter, models: DataModel[]) { writer.write('validation:'); writer.inlineBlock(() => { for (const model of models) { diff --git a/packages/schema/src/plugins/enhancer/policy/utils.ts b/packages/schema/src/plugins/enhancer/policy/utils.ts index fee0cc15a..ae9a7846f 100644 --- a/packages/schema/src/plugins/enhancer/policy/utils.ts +++ b/packages/schema/src/plugins/enhancer/policy/utils.ts @@ -2,6 +2,7 @@ import type { PolicyKind, PolicyOperationKind } from '@zenstackhq/runtime'; import { ExpressionContext, + FastWriter, PluginError, TypeScriptExpressionTransformer, TypeScriptExpressionTransformerError, @@ -39,7 +40,7 @@ import { } from '@zenstackhq/sdk/ast'; import deepmerge from 'deepmerge'; import { getContainerOfType, streamAllContents, streamAst, streamContents } from 'langium'; -import { SourceFile, WriterFunction } from 'ts-morph'; +import { FunctionDeclarationStructure, OptionalKind } from 'ts-morph'; import { name } from '..'; import { isCheckInvocation, isCollectionPredicate, isFutureInvocation } from '../../../utils/ast-utils'; import { ExpressionWriter, FALSE, TRUE } from './expression-writer'; @@ -265,13 +266,8 @@ export function generateSelectForRules( /** * Generates a constant query guard function */ -export function generateConstantQueryGuardFunction( - sourceFile: SourceFile, - model: DataModel, - kind: PolicyOperationKind, - value: boolean -) { - const func = sourceFile.addFunction({ +export function generateConstantQueryGuardFunction(model: DataModel, kind: PolicyOperationKind, value: boolean) { + return { name: getQueryGuardFunctionName(model, undefined, false, kind), returnType: 'any', parameters: [ @@ -286,16 +282,13 @@ export function generateConstantQueryGuardFunction( }, ], statements: [`return ${value ? TRUE : FALSE};`], - }); - - return func; + } as OptionalKind; } /** * Generates a query guard function that returns a partial Prisma query for the given model or field */ export function generateQueryGuardFunction( - sourceFile: SourceFile, model: DataModel, kind: PolicyOperationKind, allows: Expression[], @@ -303,7 +296,7 @@ export function generateQueryGuardFunction( forField?: DataModelField, fieldOverride = false ) { - const statements: (string | WriterFunction)[] = []; + const statements: string[] = []; const allowRules = allows.filter((rule) => !hasCrossModelComparison(rule)); const denyRules = denies.filter((rule) => !hasCrossModelComparison(rule)); @@ -325,100 +318,101 @@ export function generateQueryGuardFunction( if (!hasFieldAccess) { // none of the rules reference model fields, we can compile down to a plain boolean // function in this case (so we can skip doing SQL queries when validating) - statements.push((writer) => { - const transformer = new TypeScriptExpressionTransformer({ - context: ExpressionContext.AccessPolicy, - isPostGuard: kind === 'postUpdate', - operationContext: kind, + const writer = new FastWriter(); + const transformer = new TypeScriptExpressionTransformer({ + context: ExpressionContext.AccessPolicy, + isPostGuard: kind === 'postUpdate', + operationContext: kind, + }); + try { + denyRules.forEach((rule) => { + writer.write(`if (${transformer.transform(rule, false)}) { return ${FALSE}; }`); }); - try { - denyRules.forEach((rule) => { - writer.write(`if (${transformer.transform(rule, false)}) { return ${FALSE}; }`); - }); - allowRules.forEach((rule) => { - writer.write(`if (${transformer.transform(rule, false)}) { return ${TRUE}; }`); - }); - } catch (err) { - if (err instanceof TypeScriptExpressionTransformerError) { - throw new PluginError(name, err.message); - } else { - throw err; - } + allowRules.forEach((rule) => { + writer.write(`if (${transformer.transform(rule, false)}) { return ${TRUE}; }`); + }); + } catch (err) { + if (err instanceof TypeScriptExpressionTransformerError) { + throw new PluginError(name, err.message); + } else { + throw err; } + } - if (forField) { - if (allows.length === 0) { - // if there's no allow rule, for field-level rules, by default we allow - writer.write(`return ${TRUE};`); - } else { - if (allowRules.length < allows.length) { - writer.write(`return ${TRUE};`); - } else { - // if there's any allow rule, we deny unless any allow rule evaluates to true - writer.write(`return ${FALSE};`); - } - } + if (forField) { + if (allows.length === 0) { + // if there's no allow rule, for field-level rules, by default we allow + writer.write(`return ${TRUE};`); } else { if (allowRules.length < allows.length) { - // some rules are filtered out here and will be generated as additional - // checker functions, so we allow here to avoid a premature denial writer.write(`return ${TRUE};`); } else { - // for model-level rules, the default is always deny unless for 'postUpdate' - writer.write(`return ${kind === 'postUpdate' ? TRUE : FALSE};`); + // if there's any allow rule, we deny unless any allow rule evaluates to true + writer.write(`return ${FALSE};`); } } - }); - } else { - statements.push((writer) => { - writer.write('return '); - const exprWriter = new ExpressionWriter(writer, { - isPostGuard: kind === 'postUpdate', - operationContext: kind, - }); - const writeDenies = () => { - writer.conditionalWrite(denyRules.length > 1, '{ AND: ['); - denyRules.forEach((expr, i) => { - writer.inlineBlock(() => { - writer.write('NOT: '); - exprWriter.write(expr); - }); - writer.conditionalWrite(i !== denyRules.length - 1, ','); - }); - writer.conditionalWrite(denyRules.length > 1, ']}'); - }; - - const writeAllows = () => { - writer.conditionalWrite(allowRules.length > 1, '{ OR: ['); - allowRules.forEach((expr, i) => { - exprWriter.write(expr); - writer.conditionalWrite(i !== allowRules.length - 1, ','); - }); - writer.conditionalWrite(allowRules.length > 1, ']}'); - }; - - if (allowRules.length > 0 && denyRules.length > 0) { - // include both allow and deny rules - writer.write('{ AND: ['); - writeDenies(); - writer.write(','); - writeAllows(); - writer.write(']}'); - } else if (denyRules.length > 0) { - // only deny rules - writeDenies(); - } else if (allowRules.length > 0) { - // only allow rules - writeAllows(); + } else { + if (allowRules.length < allows.length) { + // some rules are filtered out here and will be generated as additional + // checker functions, so we allow here to avoid a premature denial + writer.write(`return ${TRUE};`); } else { - // disallow any operation unless for 'postUpdate' + // for model-level rules, the default is always deny unless for 'postUpdate' writer.write(`return ${kind === 'postUpdate' ? TRUE : FALSE};`); } - writer.write(';'); + } + + statements.push(writer.result); + } else { + const writer = new FastWriter(); + writer.write('return '); + const exprWriter = new ExpressionWriter(writer, { + isPostGuard: kind === 'postUpdate', + operationContext: kind, }); + const writeDenies = () => { + writer.conditionalWrite(denyRules.length > 1, '{ AND: ['); + denyRules.forEach((expr, i) => { + writer.inlineBlock(() => { + writer.write('NOT: '); + exprWriter.write(expr); + }); + writer.conditionalWrite(i !== denyRules.length - 1, ','); + }); + writer.conditionalWrite(denyRules.length > 1, ']}'); + }; + + const writeAllows = () => { + writer.conditionalWrite(allowRules.length > 1, '{ OR: ['); + allowRules.forEach((expr, i) => { + exprWriter.write(expr); + writer.conditionalWrite(i !== allowRules.length - 1, ','); + }); + writer.conditionalWrite(allowRules.length > 1, ']}'); + }; + + if (allowRules.length > 0 && denyRules.length > 0) { + // include both allow and deny rules + writer.write('{ AND: ['); + writeDenies(); + writer.write(','); + writeAllows(); + writer.write(']}'); + } else if (denyRules.length > 0) { + // only deny rules + writeDenies(); + } else if (allowRules.length > 0) { + // only allow rules + writeAllows(); + } else { + // disallow any operation unless for 'postUpdate' + writer.write(`return ${kind === 'postUpdate' ? TRUE : FALSE};`); + } + writer.write(';'); + statements.push(writer.result); } - const func = sourceFile.addFunction({ + return { name: getQueryGuardFunctionName(model, forField, fieldOverride, kind), returnType: 'any', parameters: [ @@ -433,13 +427,10 @@ export function generateQueryGuardFunction( }, ], statements, - }); - - return func; + } as OptionalKind; } export function generateEntityCheckerFunction( - sourceFile: SourceFile, model: DataModel, kind: PolicyOperationKind, allows: Expression[], @@ -447,7 +438,7 @@ export function generateEntityCheckerFunction( forField?: DataModelField, fieldOverride = false ) { - const statements: (string | WriterFunction)[] = []; + const statements: string[] = []; generateNormalizedAuthRef(model, allows, denies, statements); @@ -488,7 +479,7 @@ export function generateEntityCheckerFunction( } } - const func = sourceFile.addFunction({ + return { name: getEntityCheckerFunctionName(model, forField, fieldOverride, kind), returnType: 'any', parameters: [ @@ -502,9 +493,7 @@ export function generateEntityCheckerFunction( }, ], statements, - }); - - return func; + } as OptionalKind; } /** @@ -514,7 +503,7 @@ export function generateNormalizedAuthRef( model: DataModel, allows: Expression[], denies: Expression[], - statements: (string | WriterFunction)[] + statements: string[] ) { // check if any allow or deny rule contains 'auth()' invocation const hasAuthRef = [...allows, ...denies].some((rule) => streamAst(rule).some((child) => isAuthInvocation(child))); diff --git a/packages/schema/src/plugins/plugin-utils.ts b/packages/schema/src/plugins/plugin-utils.ts index e1bbda408..3510b0979 100644 --- a/packages/schema/src/plugins/plugin-utils.ts +++ b/packages/schema/src/plugins/plugin-utils.ts @@ -67,6 +67,7 @@ export function ensureDefaultOutputFolder(options: PluginRunnerOptions) { }, './models': { types: './models.d.ts', + default: './models.js', }, }, }; diff --git a/packages/schema/src/plugins/prisma/schema-generator.ts b/packages/schema/src/plugins/prisma/schema-generator.ts index 1194099d2..177edbd0e 100644 --- a/packages/schema/src/plugins/prisma/schema-generator.ts +++ b/packages/schema/src/plugins/prisma/schema-generator.ts @@ -82,7 +82,7 @@ import { const MODEL_PASSTHROUGH_ATTR = '@@prisma.passthrough'; const FIELD_PASSTHROUGH_ATTR = '@prisma.passthrough'; const PROVIDERS_SUPPORTING_NAMED_CONSTRAINTS = ['postgresql', 'mysql', 'cockroachdb']; -const PROVIDERS_SUPPORTING_TYPEDEF_FIELDS = ['postgresql']; +const PROVIDERS_SUPPORTING_TYPEDEF_FIELDS = ['postgresql', 'sqlite']; // Some database providers like postgres and mysql have default limit to the length of identifiers // Here we use a conservative value that should work for most cases, and truncate names if needed diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 44a4e2bf6..f22e1f9ba 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "2.13.3", + "version": "2.14.0", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { @@ -18,8 +18,8 @@ "author": "", "license": "MIT", "dependencies": { - "@prisma/generator-helper": "6.5.x", - "@prisma/internals": "6.5.x", + "@prisma/generator-helper": "6.6.x", + "@prisma/internals": "6.6.x", "@zenstackhq/language": "workspace:*", "@zenstackhq/runtime": "workspace:*", "langium": "1.3.1", diff --git a/packages/sdk/src/code-gen.ts b/packages/sdk/src/code-gen.ts index 67833b788..1b68ccf62 100644 --- a/packages/sdk/src/code-gen.ts +++ b/packages/sdk/src/code-gen.ts @@ -70,3 +70,60 @@ export async function emitProject(project: Project) { throw new PluginError('', `Error emitting generated code`); } } + +/* + * Abstraction for source code writer. + */ +export interface CodeWriter { + block(callback: () => void): void; + inlineBlock(callback: () => void): void; + write(text: string): void; + writeLine(text: string): void; + conditionalWrite(condition: boolean, text: string): void; +} + +/** + * A fast code writer. + */ +export class FastWriter implements CodeWriter { + private content = ''; + private indentLevel = 0; + + constructor(private readonly indentSize = 4) {} + + get result() { + return this.content; + } + + block(callback: () => void) { + this.content += '{\n'; + this.indentLevel++; + callback(); + this.indentLevel--; + this.content += '\n}'; + } + + inlineBlock(callback: () => void) { + this.content += '{'; + callback(); + this.content += '}'; + } + + write(text: string) { + this.content += this.indent(text); + } + + writeLine(text: string) { + this.content += `${this.indent(text)}\n`; + } + + conditionalWrite(condition: boolean, text: string) { + if (condition) { + this.write(text); + } + } + + private indent(text: string) { + return ' '.repeat(this.indentLevel * this.indentSize) + text; + } +} diff --git a/packages/sdk/src/model-meta-generator.ts b/packages/sdk/src/model-meta-generator.ts index 88e064512..c5b866417 100644 --- a/packages/sdk/src/model-meta-generator.ts +++ b/packages/sdk/src/model-meta-generator.ts @@ -19,9 +19,11 @@ import { import type { RuntimeAttribute } from '@zenstackhq/runtime'; import { streamAst } from 'langium'; import { lowerCaseFirst } from 'lower-case-first'; -import { CodeBlockWriter, Project, SourceFile, VariableDeclarationKind } from 'ts-morph'; +import { FunctionDeclarationStructure, OptionalKind, Project, VariableDeclarationKind } from 'ts-morph'; import { + CodeWriter, ExpressionContext, + FastWriter, getAttribute, getAttributeArg, getAttributeArgs, @@ -73,12 +75,20 @@ export function generate( options: ModelMetaGeneratorOptions ) { const sf = project.createSourceFile(options.output, undefined, { overwrite: true }); + + const writer = new FastWriter(); + const extraFunctions: OptionalKind[] = []; + generateModelMetadata(models, typeDefs, writer, options, extraFunctions); + sf.addVariableStatement({ declarationKind: VariableDeclarationKind.Const, - declarations: [ - { name: 'metadata', initializer: (writer) => generateModelMetadata(models, typeDefs, sf, writer, options) }, - ], + declarations: [{ name: 'metadata', initializer: writer.result }], }); + + if (extraFunctions.length > 0) { + sf.addFunctions(extraFunctions); + } + sf.addStatements('export default metadata;'); if (options.preserveTsFiles) { @@ -91,13 +101,13 @@ export function generate( function generateModelMetadata( dataModels: DataModel[], typeDefs: TypeDef[], - sourceFile: SourceFile, - writer: CodeBlockWriter, - options: ModelMetaGeneratorOptions + writer: CodeWriter, + options: ModelMetaGeneratorOptions, + extraFunctions: OptionalKind[] ) { writer.block(() => { - writeModels(sourceFile, writer, dataModels, options); - writeTypeDefs(sourceFile, writer, typeDefs, options); + writeModels(writer, dataModels, options, extraFunctions); + writeTypeDefs(writer, typeDefs, options, extraFunctions); writeDeleteCascade(writer, dataModels); writeShortNameMap(options, writer); writeAuthModel(writer, dataModels, typeDefs); @@ -105,10 +115,10 @@ function generateModelMetadata( } function writeModels( - sourceFile: SourceFile, - writer: CodeBlockWriter, + writer: CodeWriter, dataModels: DataModel[], - options: ModelMetaGeneratorOptions + options: ModelMetaGeneratorOptions, + extraFunctions: OptionalKind[] ) { writer.write('models:'); writer.block(() => { @@ -117,7 +127,7 @@ function writeModels( writer.block(() => { writer.write(`name: '${model.name}',`); writeBaseTypes(writer, model); - writeFields(sourceFile, writer, model, options); + writeFields(writer, model, options, extraFunctions); writeUniqueConstraints(writer, model); if (options.generateAttributes) { writeModelAttributes(writer, model); @@ -131,10 +141,10 @@ function writeModels( } function writeTypeDefs( - sourceFile: SourceFile, - writer: CodeBlockWriter, + writer: CodeWriter, typedDefs: TypeDef[], - options: ModelMetaGeneratorOptions + options: ModelMetaGeneratorOptions, + extraFunctions: OptionalKind[] ) { if (typedDefs.length === 0) { return; @@ -145,7 +155,7 @@ function writeTypeDefs( writer.write(`${lowerCaseFirst(typeDef.name)}:`); writer.block(() => { writer.write(`name: '${typeDef.name}',`); - writeFields(sourceFile, writer, typeDef, options); + writeFields(writer, typeDef, options, extraFunctions); }); writer.writeLine(','); } @@ -153,7 +163,7 @@ function writeTypeDefs( writer.writeLine(','); } -function writeBaseTypes(writer: CodeBlockWriter, model: DataModel) { +function writeBaseTypes(writer: CodeWriter, model: DataModel) { if (model.superTypes.length > 0) { writer.write('baseTypes: ['); writer.write(model.superTypes.map((t) => `'${t.ref?.name}'`).join(', ')); @@ -161,14 +171,14 @@ function writeBaseTypes(writer: CodeBlockWriter, model: DataModel) { } } -function writeAuthModel(writer: CodeBlockWriter, dataModels: DataModel[], typeDefs: TypeDef[]) { +function writeAuthModel(writer: CodeWriter, dataModels: DataModel[], typeDefs: TypeDef[]) { const authModel = getAuthDecl([...dataModels, ...typeDefs]); if (authModel) { writer.writeLine(`authModel: '${authModel.name}'`); } } -function writeDeleteCascade(writer: CodeBlockWriter, dataModels: DataModel[]) { +function writeDeleteCascade(writer: CodeWriter, dataModels: DataModel[]) { writer.write('deleteCascade:'); writer.block(() => { for (const model of dataModels) { @@ -181,7 +191,7 @@ function writeDeleteCascade(writer: CodeBlockWriter, dataModels: DataModel[]) { writer.writeLine(','); } -function writeUniqueConstraints(writer: CodeBlockWriter, model: DataModel) { +function writeUniqueConstraints(writer: CodeWriter, model: DataModel) { const constraints = getUniqueConstraints(model); if (constraints.length > 0) { writer.write('uniqueConstraints:'); @@ -197,7 +207,7 @@ function writeUniqueConstraints(writer: CodeBlockWriter, model: DataModel) { } } -function writeModelAttributes(writer: CodeBlockWriter, model: DataModel) { +function writeModelAttributes(writer: CodeWriter, model: DataModel) { const attrs = getAttributes(model); if (attrs.length > 0) { writer.write(` @@ -205,7 +215,7 @@ attributes: ${JSON.stringify(attrs)},`); } } -function writeDiscriminator(writer: CodeBlockWriter, model: DataModel) { +function writeDiscriminator(writer: CodeWriter, model: DataModel) { const delegateAttr = getAttribute(model, '@@delegate'); if (!delegateAttr) { return; @@ -220,10 +230,10 @@ function writeDiscriminator(writer: CodeBlockWriter, model: DataModel) { } function writeFields( - sourceFile: SourceFile, - writer: CodeBlockWriter, + writer: CodeWriter, container: DataModel | TypeDef, - options: ModelMetaGeneratorOptions + options: ModelMetaGeneratorOptions, + extraFunctions: OptionalKind[] ) { writer.write('fields:'); writer.block(() => { @@ -279,7 +289,7 @@ function writeFields( } } - const defaultValueProvider = generateDefaultValueProvider(f, sourceFile); + const defaultValueProvider = generateDefaultValueProvider(f, extraFunctions); if (defaultValueProvider) { writer.write(` defaultValueProvider: ${defaultValueProvider},`); @@ -496,7 +506,10 @@ function getDeleteCascades(model: DataModel): string[] { .map((m) => m.name); } -function generateDefaultValueProvider(field: DataModelField | TypeDefField, sourceFile: SourceFile) { +function generateDefaultValueProvider( + field: DataModelField | TypeDefField, + extraFunctions: OptionalKind[] +) { const defaultAttr = getAttribute(field, '@default'); if (!defaultAttr) { return undefined; @@ -515,8 +528,9 @@ function generateDefaultValueProvider(field: DataModelField | TypeDefField, sour // generates a provider function like: // function $default$Model$field(user: any) { ... } - const func = sourceFile.addFunction({ - name: `$default$${field.$container.name}$${field.name}`, + const funcName = `$default$${field.$container.name}$${field.name}`; + extraFunctions.push({ + name: funcName, parameters: [{ name: 'user', type: 'any' }], returnType: 'unknown', statements: (writer) => { @@ -526,7 +540,7 @@ function generateDefaultValueProvider(field: DataModelField | TypeDefField, sour }, }); - return func.getName(); + return funcName; } function isAutoIncrement(field: DataModelField) { @@ -543,7 +557,7 @@ function isAutoIncrement(field: DataModelField) { return isInvocationExpr(arg) && arg.function.$refText === 'autoincrement'; } -function writeShortNameMap(options: ModelMetaGeneratorOptions, writer: CodeBlockWriter) { +function writeShortNameMap(options: ModelMetaGeneratorOptions, writer: CodeWriter) { if (options.shortNameMap && options.shortNameMap.size > 0) { writer.write('shortNameMap:'); writer.block(() => { diff --git a/packages/server/package.json b/packages/server/package.json index 2d6caad0a..0a438cdcc 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "2.13.3", + "version": "2.14.0", "displayName": "ZenStack Server-side Adapters", "description": "ZenStack server-side adapters", "homepage": "https://zenstack.dev", diff --git a/packages/server/src/api/rest/index.ts b/packages/server/src/api/rest/index.ts index ffb617245..f2af87f0b 100644 --- a/packages/server/src/api/rest/index.ts +++ b/packages/server/src/api/rest/index.ts @@ -1750,7 +1750,12 @@ class RequestHandler extends APIHandlerBase { : this.makePrismaIdFilter(info.idFields, value, false); return { some: filterValue }; } else { - return { is: this.makePrismaIdFilter(info.idFields, value, false) }; + const values = value.split(',').filter((i) => i); + if (values.length > 1) { + return { OR: values.map((v) => this.makePrismaIdFilter(info.idFields, v, false)) }; + } else { + return { is: this.makePrismaIdFilter(info.idFields, value, false) }; + } } } else { const coerced = this.coerce(fieldInfo.type, value); diff --git a/packages/server/tests/api/rest.test.ts b/packages/server/tests/api/rest.test.ts index fd2ddc262..e246859cb 100644 --- a/packages/server/tests/api/rest.test.ts +++ b/packages/server/tests/api/rest.test.ts @@ -664,6 +664,15 @@ describe('REST server tests', () => { expect(r.body.data).toHaveLength(1); expect(r.body.data[0]).toMatchObject({ id: 1 }); + // relation filter with multiple values + r = await handler({ + method: 'get', + path: '/post', + query: { ['filter[author]']: 'user1,user2' }, + prisma, + }); + expect(r.body.data).toHaveLength(2); + // invalid filter field r = await handler({ method: 'get', diff --git a/packages/testtools/package.json b/packages/testtools/package.json index 941c28e2b..216768013 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "2.13.3", + "version": "2.14.0", "description": "ZenStack Test Tools", "main": "index.js", "private": true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a73bb0f1b..d3f18b5d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -392,8 +392,8 @@ importers: packages/runtime: dependencies: '@prisma/client': - specifier: 5.0.0 - 6.5.x - version: 6.5.0(prisma@6.0.0)(typescript@5.5.2) + specifier: 5.0.0 - 6.6.x + version: 6.6.0(prisma@6.0.0)(typescript@5.5.2) bcryptjs: specifier: ^2.4.3 version: 2.4.3 @@ -523,7 +523,7 @@ importers: specifier: ^4.0.0 version: 4.0.1 prisma: - specifier: 5.0.0 - 6.5.x + specifier: 5.0.0 - 6.6.x version: 6.0.0 semver: specifier: ^7.5.2 @@ -575,8 +575,8 @@ importers: version: 1.5.0(zod@3.23.8) devDependencies: '@prisma/client': - specifier: 6.5.x - version: 6.5.0(prisma@6.0.0)(typescript@5.5.2) + specifier: 6.6.x + version: 6.6.0(prisma@6.0.0)(typescript@5.5.2) '@types/async-exit-hook': specifier: ^2.0.0 version: 2.0.2 @@ -627,11 +627,11 @@ importers: packages/sdk: dependencies: '@prisma/generator-helper': - specifier: 6.5.x - version: 6.5.0 + specifier: 6.6.x + version: 6.6.0 '@prisma/internals': - specifier: 6.5.x - version: 6.5.0(typescript@5.5.2) + specifier: 6.6.x + version: 6.6.0(typescript@5.5.2) '@zenstackhq/language': specifier: workspace:* version: link:../language/dist @@ -2574,8 +2574,8 @@ packages: prisma: optional: true - '@prisma/client@6.5.0': - resolution: {integrity: sha512-M6w1Ql/BeiGoZmhMdAZUXHu5sz5HubyVcKukbLs3l0ELcQb8hTUJxtGEChhv4SVJ0QJlwtLnwOLgIRQhpsm9dw==} + '@prisma/client@6.6.0': + resolution: {integrity: sha512-vfp73YT/BHsWWOAuthKQ/1lBgESSqYqAWZEYyTdGXyFAHpmewwWL2Iz6ErIzkj4aHbuc6/cGSsE6ZY+pBO04Cg==} engines: {node: '>=18.18'} peerDependencies: prisma: '*' @@ -2586,8 +2586,8 @@ packages: typescript: optional: true - '@prisma/config@6.5.0': - resolution: {integrity: sha512-sOH/2Go9Zer67DNFLZk6pYOHj+rumSb0VILgltkoxOjYnlLqUpHPAN826vnx8HigqnOCxj9LRhT6U7uLiIIWgw==} + '@prisma/config@6.6.0': + resolution: {integrity: sha512-d8FlXRHsx72RbN8nA2QCRORNv5AcUnPXgtPvwhXmYkQSMF/j9cKaJg+9VcUzBRXGy9QBckNzEQDEJZdEOZ+ubA==} '@prisma/debug@5.14.0': resolution: {integrity: sha512-iq56qBZuFfX3fCxoxT8gBX33lQzomBU0qIUaEj1RebsKVz1ob/BVH1XSBwwwvRVtZEV1b7Fxx2eVu34Ge/mg3w==} @@ -2595,8 +2595,14 @@ packages: '@prisma/debug@6.0.0': resolution: {integrity: sha512-eUjoNThlDXdyJ1iQ2d7U6aTVwm59EwvODb5zFVNJEokNoSiQmiYWNzZIwZyDmZ+j51j42/0iTaHIJ4/aZPKFRg==} - '@prisma/debug@6.5.0': - resolution: {integrity: sha512-fc/nusYBlJMzDmDepdUtH9aBsJrda2JNErP9AzuHbgUEQY0/9zQYZdNlXmKoIWENtio+qarPNe/+DQtrX5kMcQ==} + '@prisma/debug@6.6.0': + resolution: {integrity: sha512-DL6n4IKlW5k2LEXzpN60SQ1kP/F6fqaCgU/McgaYsxSf43GZ8lwtmXLke9efS+L1uGmrhtBUP4npV/QKF8s2ZQ==} + + '@prisma/dmmf@6.6.0': + resolution: {integrity: sha512-2LoSX0ZVwIiKck5wMCVxeGg9K9z6CULB8xyTbLF3YTvuhOp+fQozelUphaGZA9jN4MF58z6TfUXIMMPVwtxjbQ==} + + '@prisma/driver-adapter-utils@6.6.0': + resolution: {integrity: sha512-hbYdZQsJS1ix924mg3eVpGKQ01ZzDPhI/VnA3Opgz1sNB7QMLNw1kl6sEmwSDbtJlVvZOvRxRZmG2BfkW9eaqA==} '@prisma/engines-version@5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48': resolution: {integrity: sha512-ip6pNkRo1UxWv+6toxNcYvItNYaqQjXdFNGJ+Nuk2eYtRoEdoF13wxo7/jsClJFFenMPVNVqXQDV0oveXnR1cA==} @@ -2604,8 +2610,8 @@ packages: '@prisma/engines-version@5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e': resolution: {integrity: sha512-JmIds0Q2/vsOmnuTJYxY4LE+sajqjYKhLtdOT6y4imojqv5d/aeVEfbBGC74t8Be1uSp0OP8lxIj2OqoKbLsfQ==} - '@prisma/engines-version@6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60': - resolution: {integrity: sha512-iK3EmiVGFDCmXjSpdsKGNqy9hOdLnvYBrJB61far/oP03hlIxrb04OWmDjNTwtmZ3UZdA5MCvI+f+3k2jPTflQ==} + '@prisma/engines-version@6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a': + resolution: {integrity: sha512-JzRaQ5Em1fuEcbR3nUsMNYaIYrOT1iMheenjCvzZblJcjv/3JIuxXN7RCNT5i6lRkLodW5ojCGhR7n5yvnNKrw==} '@prisma/engines@5.14.0': resolution: {integrity: sha512-lgxkKZ6IEygVcw6IZZUlPIfLQ9hjSYAtHjZ5r64sCLDgVzsPFCi2XBBJgzPMkOQ5RHzUD4E/dVdpn9+ez8tk1A==} @@ -2613,8 +2619,8 @@ packages: '@prisma/engines@6.0.0': resolution: {integrity: sha512-ZZCVP3q22ifN6Ex6C8RIcTDBlRtMJS2H1ljV0knCiWNGArvvkEbE88W3uDdq/l4+UvyvHpGzdf9ZsCWSQR7ZQQ==} - '@prisma/engines@6.5.0': - resolution: {integrity: sha512-FVPQYHgOllJklN9DUyujXvh3hFJCY0NX86sDmBErLvoZjy2OXGiZ5FNf3J/C4/RZZmCypZBYpBKEhx7b7rEsdw==} + '@prisma/engines@6.6.0': + resolution: {integrity: sha512-nC0IV4NHh7500cozD1fBoTwTD1ydJERndreIjpZr/S3mno3P6tm8qnXmIND5SwUkibNeSJMpgl4gAnlqJ/gVlg==} '@prisma/fetch-engine@5.14.0': resolution: {integrity: sha512-VrheA9y9DMURK5vu8OJoOgQpxOhas3qF0IBHJ8G/0X44k82kc8E0w98HCn2nhnbOOMwbWsJWXfLC2/F8n5u0gQ==} @@ -2622,14 +2628,17 @@ packages: '@prisma/fetch-engine@6.0.0': resolution: {integrity: sha512-j2m+iO5RDPRI7SUc7sHo8wX7SA4iTkJ+18Sxch8KinQM46YiCQD1iXKN6qU79C1Fliw5Bw/qDyTHaTsa3JMerA==} - '@prisma/fetch-engine@6.5.0': - resolution: {integrity: sha512-3LhYA+FXP6pqY8FLHCjewyE8pGXXJ7BxZw2rhPq+CZAhvflVzq4K8Qly3OrmOkn6wGlz79nyLQdknyCG2HBTuA==} + '@prisma/fetch-engine@6.6.0': + resolution: {integrity: sha512-Ohfo8gKp05LFLZaBlPUApM0M7k43a0jmo86YY35u1/4t+vuQH9mRGU7jGwVzGFY3v+9edeb/cowb1oG4buM1yw==} '@prisma/generator-helper@5.14.0': resolution: {integrity: sha512-xVc71cmTnPZ0lnSs4FAY6Ta72vFJ3webrQwKMQ2ujr6hDG1VPIEf820T1TOS3ZZQd/OKigNKXnq3co8biz9/qw==} - '@prisma/generator-helper@6.5.0': - resolution: {integrity: sha512-71ELYxnSE4soeV0BlWJEMgO4KkCowuzHsPY3o7quFOtlcmds5ZX190VZK/k9HMJWdPQ893HooBv3BkKvieR7vA==} + '@prisma/generator-helper@6.6.0': + resolution: {integrity: sha512-pKxkyb+tCb5vPaMkK6SdHkK5qkQIPDSw1T3BAfl6XWDwDwnP4f5CsJ6/VaBhtvqZ5bqfUQLvT+slaGrJQXiw9g==} + + '@prisma/generator@6.6.0': + resolution: {integrity: sha512-UO1PFYd8Qh0LZoN/9wwAukgoDs8chLqGPho7ZBZejVKv1UpaEJvxgwS+LwlmkQ3Ay0GdsL6mjN8QxEi91xIomQ==} '@prisma/get-platform@5.14.0': resolution: {integrity: sha512-/yAyBvcEjRv41ynZrhdrPtHgk47xLRRq/o5eWGcUpBJ1YrUZTYB8EoPiopnP7iQrMATK8stXQdPOoVlrzuTQZw==} @@ -2637,14 +2646,14 @@ packages: '@prisma/get-platform@6.0.0': resolution: {integrity: sha512-PS6nYyIm9g8C03E4y7LknOfdCw/t2KyEJxntMPQHQZCOUgOpF82Ma60mdlOD08w90I3fjLiZZ0+MadenR3naDQ==} - '@prisma/get-platform@6.5.0': - resolution: {integrity: sha512-xYcvyJwNMg2eDptBYFqFLUCfgi+wZLcj6HDMsj0Qw0irvauG4IKmkbywnqwok0B+k+W+p+jThM2DKTSmoPCkzw==} + '@prisma/get-platform@6.6.0': + resolution: {integrity: sha512-3qCwmnT4Jh5WCGUrkWcc6VZaw0JY7eWN175/pcb5Z6FiLZZ3ygY93UX0WuV41bG51a6JN/oBH0uywJ90Y+V5eA==} '@prisma/internals@5.14.0': resolution: {integrity: sha512-s0JRNDmR2bvcyy0toz89jy7SbbjANAs4e9KCReNvSm5czctIaZzDf68tcOXdtH0G7m9mKhVhNPdS9lMky0DhWA==} - '@prisma/internals@6.5.0': - resolution: {integrity: sha512-4hI114ho/BVvxSIZ7lQB3TPxGrSnt5+aYhKMzQU5+l659Qcjjiz56hZ/rXph3xIlgJUWVQyzwAS9/ZAMgXUySw==} + '@prisma/internals@6.6.0': + resolution: {integrity: sha512-wKGuKZ5IDHoT9DIjxT1JCoSYsEJ160MMKgsmKnHVM4rq0rmQEPVo2qiVuhV0kJSipx+uIi1b7LOL25/0zO7Xog==} peerDependencies: typescript: '>=5.1.0' peerDependenciesMeta: @@ -2657,14 +2666,17 @@ packages: '@prisma/prisma-schema-wasm@5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48': resolution: {integrity: sha512-WeTmJ0mK8ALoKJUQFO+465k9lm1JWS4ODUg7akJq1wjgyDU1RTAzDFli8ESmNJlMVgJgoAd6jXmzcnoA0HT9Lg==} - '@prisma/prisma-schema-wasm@6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60': - resolution: {integrity: sha512-8zmXO5Luw5sCOgTw9nyN4/x7MgmIUUt/9zQZPPStiMUhZsXH72oqFwYJlibdS77gDuH2+DXgSRv6aPleDdTMsQ==} + '@prisma/prisma-schema-wasm@6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a': + resolution: {integrity: sha512-lyfyH5xm9hygB1mx083ofzfDwByghcMN2b3a3dG22HsYbWvHUatTtgxtzqgLH7m1HTTxf8UkMu0P6Q9VHfBW6A==} + + '@prisma/schema-engine-wasm@6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a': + resolution: {integrity: sha512-uzkaVo0K/j7XBtL1uHh78w0dQo5uks4M+/qdN/Y69HQo09WUy6iFQvNjI8haQgLcj/G3M4Ru5i4bHYtaS9vKWg==} '@prisma/schema-files-loader@5.14.0': resolution: {integrity: sha512-n1QHR2C63dARKPZe0WPn7biybcBHzXe+BEmiHC5Drq9KPWnpmQtIfGpqm1ZKdvCZfcA5FF3wgpSMPK4LnB0obQ==} - '@prisma/schema-files-loader@6.5.0': - resolution: {integrity: sha512-BE+YPIcqXx+9MkPsA/mACz3rGhKUfh5rsoGe1Male/0c9my9CaHKzJFxNAV4f70d6GTMMITQgFpwfrudGFOwpw==} + '@prisma/schema-files-loader@6.6.0': + resolution: {integrity: sha512-g8PWSF8cLKTfmgVHkg79T/9EKsGR+6HozqNpcAK2Avpe0MGwIXwTC/HW2Lc8kJfoPmMiCoh6Y5ClafMvp6nlPA==} '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -10707,12 +10719,12 @@ snapshots: optionalDependencies: prisma: 6.0.0 - '@prisma/client@6.5.0(prisma@6.0.0)(typescript@5.5.2)': + '@prisma/client@6.6.0(prisma@6.0.0)(typescript@5.5.2)': optionalDependencies: prisma: 6.0.0 typescript: 5.5.2 - '@prisma/config@6.5.0': + '@prisma/config@6.6.0': dependencies: esbuild: 0.24.0 esbuild-register: 3.6.0(esbuild@0.24.0) @@ -10723,13 +10735,19 @@ snapshots: '@prisma/debug@6.0.0': {} - '@prisma/debug@6.5.0': {} + '@prisma/debug@6.6.0': {} + + '@prisma/dmmf@6.6.0': {} + + '@prisma/driver-adapter-utils@6.6.0': + dependencies: + '@prisma/debug': 6.6.0 '@prisma/engines-version@5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48': {} '@prisma/engines-version@5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e': {} - '@prisma/engines-version@6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60': {} + '@prisma/engines-version@6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a': {} '@prisma/engines@5.14.0': dependencies: @@ -10745,12 +10763,12 @@ snapshots: '@prisma/fetch-engine': 6.0.0 '@prisma/get-platform': 6.0.0 - '@prisma/engines@6.5.0': + '@prisma/engines@6.6.0': dependencies: - '@prisma/debug': 6.5.0 - '@prisma/engines-version': 6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60 - '@prisma/fetch-engine': 6.5.0 - '@prisma/get-platform': 6.5.0 + '@prisma/debug': 6.6.0 + '@prisma/engines-version': 6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a + '@prisma/fetch-engine': 6.6.0 + '@prisma/get-platform': 6.6.0 '@prisma/fetch-engine@5.14.0': dependencies: @@ -10764,19 +10782,23 @@ snapshots: '@prisma/engines-version': 5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e '@prisma/get-platform': 6.0.0 - '@prisma/fetch-engine@6.5.0': + '@prisma/fetch-engine@6.6.0': dependencies: - '@prisma/debug': 6.5.0 - '@prisma/engines-version': 6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60 - '@prisma/get-platform': 6.5.0 + '@prisma/debug': 6.6.0 + '@prisma/engines-version': 6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a + '@prisma/get-platform': 6.6.0 '@prisma/generator-helper@5.14.0': dependencies: '@prisma/debug': 5.14.0 - '@prisma/generator-helper@6.5.0': + '@prisma/generator-helper@6.6.0': dependencies: - '@prisma/debug': 6.5.0 + '@prisma/debug': 6.6.0 + '@prisma/dmmf': 6.6.0 + '@prisma/generator': 6.6.0 + + '@prisma/generator@6.6.0': {} '@prisma/get-platform@5.14.0': dependencies: @@ -10786,9 +10808,9 @@ snapshots: dependencies: '@prisma/debug': 6.0.0 - '@prisma/get-platform@6.5.0': + '@prisma/get-platform@6.6.0': dependencies: - '@prisma/debug': 6.5.0 + '@prisma/debug': 6.6.0 '@prisma/internals@5.14.0': dependencies: @@ -10802,16 +10824,20 @@ snapshots: arg: 5.0.2 prompts: 2.4.2 - '@prisma/internals@6.5.0(typescript@5.5.2)': - dependencies: - '@prisma/config': 6.5.0 - '@prisma/debug': 6.5.0 - '@prisma/engines': 6.5.0 - '@prisma/fetch-engine': 6.5.0 - '@prisma/generator-helper': 6.5.0 - '@prisma/get-platform': 6.5.0 - '@prisma/prisma-schema-wasm': 6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60 - '@prisma/schema-files-loader': 6.5.0 + '@prisma/internals@6.6.0(typescript@5.5.2)': + dependencies: + '@prisma/config': 6.6.0 + '@prisma/debug': 6.6.0 + '@prisma/dmmf': 6.6.0 + '@prisma/driver-adapter-utils': 6.6.0 + '@prisma/engines': 6.6.0 + '@prisma/fetch-engine': 6.6.0 + '@prisma/generator': 6.6.0 + '@prisma/generator-helper': 6.6.0 + '@prisma/get-platform': 6.6.0 + '@prisma/prisma-schema-wasm': 6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a + '@prisma/schema-engine-wasm': 6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a + '@prisma/schema-files-loader': 6.6.0 arg: 5.0.2 prompts: 2.4.2 optionalDependencies: @@ -10823,16 +10849,18 @@ snapshots: '@prisma/prisma-schema-wasm@5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48': {} - '@prisma/prisma-schema-wasm@6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60': {} + '@prisma/prisma-schema-wasm@6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a': {} + + '@prisma/schema-engine-wasm@6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a': {} '@prisma/schema-files-loader@5.14.0': dependencies: '@prisma/prisma-schema-wasm': 5.14.0-17.56ca112d5a19c9925b53af75c3c6b7ada97f9f85 fs-extra: 11.1.1 - '@prisma/schema-files-loader@6.5.0': + '@prisma/schema-files-loader@6.6.0': dependencies: - '@prisma/prisma-schema-wasm': 6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60 + '@prisma/prisma-schema-wasm': 6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a fs-extra: 11.3.0 '@protobufjs/aspromise@1.1.2': {} diff --git a/script/test-scaffold.ts b/script/test-scaffold.ts index 97ef4c6e6..cd8a69123 100644 --- a/script/test-scaffold.ts +++ b/script/test-scaffold.ts @@ -19,6 +19,6 @@ function run(cmd: string) { } run('npm init -y'); -run('npm i --no-audit --no-fund typescript prisma@6.5.x @prisma/client@6.5.x zod@^3.22.4 decimal.js @types/node'); +run('npm i --no-audit --no-fund typescript prisma@6.6.x @prisma/client@6.6.x zod@^3.22.4 decimal.js @types/node'); console.log('Test scaffold setup complete.'); diff --git a/tests/integration/test-run/package.json b/tests/integration/test-run/package.json index b00a3cb6e..d2c56d7ad 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": "6.5.x", + "@prisma/client": "6.6.x", "@zenstackhq/runtime": "file:../../../packages/runtime/dist", - "prisma": "6.5.x", + "prisma": "6.6.x", "react": "^18.2.0", "swr": "^1.3.0", "typescript": "^4.9.3", diff --git a/tests/integration/tests/cli/plugins.test.ts b/tests/integration/tests/cli/plugins.test.ts index 22a0dffc4..d538dc9f0 100644 --- a/tests/integration/tests/cli/plugins.test.ts +++ b/tests/integration/tests/cli/plugins.test.ts @@ -75,7 +75,7 @@ describe('CLI Plugins Tests', () => { 'swr', '@tanstack/react-query@5.56.x', '@trpc/server', - '@prisma/client@6.5.x', + '@prisma/client@6.6.x', `${path.join(__dirname, '../../../../.build/zenstackhq-language-' + ver + '.tgz')}`, `${path.join(__dirname, '../../../../.build/zenstackhq-sdk-' + ver + '.tgz')}`, `${path.join(__dirname, '../../../../.build/zenstackhq-runtime-' + ver + '.tgz')}`, @@ -85,7 +85,7 @@ describe('CLI Plugins Tests', () => { const devDepPkgs = [ 'typescript', '@types/react', - 'prisma@6.5.x', + 'prisma@6.6.x', `${path.join(__dirname, '../../../../.build/zenstack-' + ver + '.tgz')}`, `${path.join(__dirname, '../../../../.build/zenstackhq-tanstack-query-' + ver + '.tgz')}`, `${path.join(__dirname, '../../../../.build/zenstackhq-swr-' + ver + '.tgz')}`, diff --git a/tests/integration/tests/enhancements/json/crud.test.ts b/tests/integration/tests/enhancements/json/crud.test.ts index f13b5727b..1a41b1b43 100644 --- a/tests/integration/tests/enhancements/json/crud.test.ts +++ b/tests/integration/tests/enhancements/json/crud.test.ts @@ -1,18 +1,22 @@ import { createPostgresDb, dropPostgresDb, loadSchema } from '@zenstackhq/testtools'; -describe('Json field CRUD', () => { +describe.each(['sqlite' as const, 'postgresql' as const])('Json field CRUD - %p', (provider) => { let dbUrl: string; let prisma: any; beforeEach(async () => { - dbUrl = await createPostgresDb('json-field-typing'); + if (provider === 'postgresql') { + dbUrl = await createPostgresDb('json-field-typing'); + } }); afterEach(async () => { - if (prisma) { - await prisma.$disconnect(); + if (provider === 'postgresql') { + if (prisma) { + await prisma.$disconnect(); + } + await dropPostgresDb(dbUrl); } - await dropPostgresDb(dbUrl); }); it('works with simple cases', async () => { @@ -41,7 +45,7 @@ describe('Json field CRUD', () => { } `, { - provider: 'postgresql', + provider, dbUrl, enhancements: ['validation'], } @@ -90,7 +94,7 @@ describe('Json field CRUD', () => { } `, { - provider: 'postgresql', + provider, dbUrl, } ); @@ -137,7 +141,7 @@ describe('Json field CRUD', () => { } `, { - provider: 'postgresql', + provider, dbUrl, } ); @@ -211,7 +215,7 @@ describe('Json field CRUD', () => { } `, { - provider: 'postgresql', + provider, dbUrl, } ); @@ -255,7 +259,7 @@ describe('Json field CRUD', () => { } `, { - provider: 'postgresql', + provider, dbUrl, } ); @@ -291,7 +295,7 @@ describe('Json field CRUD', () => { } `, { - provider: 'postgresql', + provider, dbUrl, } ); @@ -328,7 +332,7 @@ describe('Json field CRUD', () => { } `, { - provider: 'postgresql', + provider, dbUrl, } ); @@ -368,7 +372,7 @@ describe('Json field CRUD', () => { } `, { - provider: 'postgresql', + provider, dbUrl, } ); @@ -401,7 +405,7 @@ describe('Json field CRUD', () => { } `, { - provider: 'postgresql', + provider, dbUrl, } ); @@ -446,7 +450,7 @@ describe('Json field CRUD', () => { } `, { - provider: 'postgresql', + provider, dbUrl, compile: true, extraSourceFiles: [ diff --git a/tests/integration/tests/enhancements/json/typing.test.ts b/tests/integration/tests/enhancements/json/typing.test.ts index a2053770c..99869accd 100644 --- a/tests/integration/tests/enhancements/json/typing.test.ts +++ b/tests/integration/tests/enhancements/json/typing.test.ts @@ -1,6 +1,6 @@ import { loadSchema } from '@zenstackhq/testtools'; -describe('JSON field typing', () => { +describe.each(['sqlite' as const, 'postgresql' as const])('JSON field typing - %p', (provider) => { it('works with simple field', async () => { await loadSchema( ` @@ -23,7 +23,7 @@ describe('JSON field typing', () => { } `, { - provider: 'postgresql', + provider, pushDb: false, compile: true, extraSourceFiles: [ @@ -64,7 +64,7 @@ async function main() { } `, { - provider: 'postgresql', + provider, pushDb: false, compile: true, extraSourceFiles: [ @@ -105,7 +105,7 @@ async function main() { } `, { - provider: 'postgresql', + provider, pushDb: false, compile: true, extraSourceFiles: [ @@ -151,7 +151,7 @@ async function main() { } `, { - provider: 'postgresql', + provider, pushDb: false, compile: true, extraSourceFiles: [ @@ -203,7 +203,7 @@ async function main() { } `, { - provider: 'postgresql', + provider, pushDb: false, compile: true, extraSourceFiles: [ @@ -252,7 +252,7 @@ async function main() { } `, { - provider: 'postgresql', + provider, pushDb: false, compile: true, extraSourceFiles: [ @@ -303,7 +303,7 @@ async function main() { } `, { - provider: 'postgresql', + provider, pushDb: false, compile: true, extraSourceFiles: [ @@ -348,7 +348,7 @@ async function main() { } `, { - provider: 'postgresql', + provider, pushDb: false, compile: true, extraSourceFiles: [ diff --git a/tests/integration/tests/enhancements/json/validation.test.ts b/tests/integration/tests/enhancements/json/validation.test.ts index df5dfc281..a398234f9 100644 --- a/tests/integration/tests/enhancements/json/validation.test.ts +++ b/tests/integration/tests/enhancements/json/validation.test.ts @@ -1,24 +1,6 @@ import { loadModel, loadModelWithError, loadSchema } from '@zenstackhq/testtools'; describe('JSON field typing', () => { - it('is only supported by postgres', async () => { - await expect( - loadSchema( - ` - type Profile { - age Int @gt(0) - } - - model User { - id Int @id @default(autoincrement()) - profile Profile @json - @@allow('all', true) - } - ` - ) - ).rejects.toThrow('Datasource provider "sqlite" does not support "@json" fields'); - }); - it('requires field to have @json attribute', async () => { await expect( loadModelWithError( diff --git a/tests/integration/tests/frameworks/nextjs/test-project/package.json b/tests/integration/tests/frameworks/nextjs/test-project/package.json index 6f5b71d05..55e1c035c 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": "6.5.x", + "@prisma/client": "6.6.x", "@types/node": "18.11.18", "@types/react": "18.0.27", "@types/react-dom": "18.0.10", @@ -26,6 +26,6 @@ "@zenstackhq/swr": "../../../../../../../packages/plugins/swr/dist" }, "devDependencies": { - "prisma": "6.5.x" + "prisma": "6.6.x" } } diff --git a/tests/integration/tests/frameworks/trpc/test-project/package.json b/tests/integration/tests/frameworks/trpc/test-project/package.json index 2ba147baa..fb76791bd 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": "6.5.x", + "@prisma/client": "6.6.x", "@tanstack/react-query": "^4.22.4", "@trpc/client": "^10.34.0", "@trpc/next": "^10.34.0", @@ -31,6 +31,6 @@ "@zenstackhq/trpc": "../../../../../../../packages/plugins/trpc/dist" }, "devDependencies": { - "prisma": "6.5.x" + "prisma": "6.6.x" } }