Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge dev to main (v2.12.0) #2013

Merged
merged 14 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/security-scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: 'Upload artifact'
uses: actions/upload-artifact@v3.1.0
uses: actions/upload-artifact@v4
with:
name: SARIF file
path: results.sarif
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zenstack-monorepo",
"version": "2.11.6",
"version": "2.12.0",
"description": "",
"scripts": {
"build": "pnpm -r --filter=\"!./packages/ide/*\" build",
Expand Down
6 changes: 6 additions & 0 deletions packages/ide/jetbrains/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

### Added

- Validating regex patterns in ZModel.

## 2.12.0

### Added

- Field encryption attribute `@encrypted`.

## 2.9.3
Expand Down
2 changes: 1 addition & 1 deletion packages/ide/jetbrains/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

group = "dev.zenstack"
version = "2.11.6"
version = "2.12.0"

repositories {
mavenCentral()
Expand Down
2 changes: 1 addition & 1 deletion packages/ide/jetbrains/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jetbrains",
"version": "2.11.6",
"version": "2.12.0",
"displayName": "ZenStack JetBrains IDE Plugin",
"description": "ZenStack JetBrains IDE plugin",
"homepage": "https://zenstack.dev",
Expand Down
2 changes: 1 addition & 1 deletion packages/language/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/language",
"version": "2.11.6",
"version": "2.12.0",
"displayName": "ZenStack modeling language compiler",
"description": "ZenStack modeling language compiler",
"homepage": "https://zenstack.dev",
Expand Down
2 changes: 1 addition & 1 deletion packages/misc/redwood/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/redwood",
"displayName": "ZenStack RedwoodJS Integration",
"version": "2.11.6",
"version": "2.12.0",
"description": "CLI and runtime for integrating ZenStack with RedwoodJS projects.",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/openapi/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/openapi",
"displayName": "ZenStack Plugin and Runtime for OpenAPI",
"version": "2.11.6",
"version": "2.12.0",
"description": "ZenStack plugin and runtime supporting OpenAPI",
"main": "index.js",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/swr/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/swr",
"displayName": "ZenStack plugin for generating SWR hooks",
"version": "2.11.6",
"version": "2.12.0",
"description": "ZenStack plugin for generating SWR hooks",
"main": "index.js",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/tanstack-query/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/tanstack-query",
"displayName": "ZenStack plugin for generating tanstack-query hooks",
"version": "2.11.6",
"version": "2.12.0",
"description": "ZenStack plugin for generating tanstack-query hooks",
"main": "index.js",
"exports": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/trpc/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/trpc",
"displayName": "ZenStack plugin for tRPC",
"version": "2.11.6",
"version": "2.12.0",
"description": "ZenStack plugin for tRPC",
"main": "index.js",
"repository": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
"@prisma/client": "6.3.x",
"@prisma/client": "6.4.x",
"@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2",
"nuxt": "^3.14.1592",
Expand All @@ -21,7 +21,7 @@
},
"devDependencies": {
"esbuild": "^0.24.0",
"prisma": "6.3.x",
"prisma": "6.4.x",
"typescript": "^5.6.2",
"vue-tsc": "^2.1.10"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
"@prisma/client": "6.3.x",
"@prisma/client": "6.4.x",
"@trpc/client": "^11.0.0-rc.563",
"@trpc/server": "^11.0.0-rc.563",
"nuxt": "^3.14.1592",
Expand All @@ -21,7 +21,7 @@
},
"devDependencies": {
"esbuild": "^0.24.0",
"prisma": "6.3.x",
"prisma": "6.4.x",
"typescript": "^5.6.2",
"vue-tsc": "^2.1.10"
}
Expand Down
4 changes: 2 additions & 2 deletions packages/plugins/trpc/tests/projects/t3-trpc-v11/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"start": "next start"
},
"dependencies": {
"@prisma/client": "6.3.x",
"@prisma/client": "6.4.x",
"@t3-oss/env-nextjs": "^0.10.1",
"@tanstack/react-query": "^5.50.0",
"@trpc/client": "^11.0.0-rc.446",
Expand All @@ -39,7 +39,7 @@
"@typescript-eslint/parser": "^8.1.0",
"eslint": "^8.57.0",
"eslint-config-next": "^14.2.4",
"prisma": "6.3.x",
"prisma": "6.4.x",
"typescript": "^5.5.3"
},
"ct3aMetadata": {
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/runtime",
"displayName": "ZenStack Runtime Library",
"version": "2.11.6",
"version": "2.12.0",
"description": "Runtime of ZenStack for both client-side and server-side environments.",
"repository": {
"type": "git",
Expand Down Expand Up @@ -115,7 +115,7 @@
"zod-validation-error": "^1.5.0"
},
"peerDependencies": {
"@prisma/client": "5.0.0 - 6.3.x"
"@prisma/client": "5.0.0 - 6.4.x"
},
"author": {
"name": "ZenStack Team"
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/res/enhance.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { auth, enhance, type PrismaClient } from '.zenstack/enhance';
export { auth, enhance, type PrismaClient, type Enhanced } from '.zenstack/enhance';
2 changes: 1 addition & 1 deletion packages/runtime/src/enhance.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// @ts-expect-error stub for re-exporting generated code
export { auth, enhance } from '.zenstack/enhance';
export { auth, enhance, type PrismaClient, type Enhanced } from '.zenstack/enhance';
114 changes: 78 additions & 36 deletions packages/runtime/src/enhancements/node/default-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { ACTIONS_WITH_WRITE_PAYLOAD } from '../../constants';
import {
FieldInfo,
NestedWriteVisitor,
NestedWriteVisitorContext,
PrismaWriteActionType,
clone,
enumerate,
getFields,
getModelInfo,
getTypeDefInfo,
requireField,
} from '../../cross';
Expand Down Expand Up @@ -61,7 +63,7 @@ class DefaultAuthHandler extends DefaultPrismaProxyHandler {
private async preprocessWritePayload(model: string, action: PrismaWriteActionType, args: any) {
const newArgs = clone(args);

const processCreatePayload = (model: string, data: any) => {
const processCreatePayload = (model: string, data: any, context: NestedWriteVisitorContext) => {
const fields = getFields(this.options.modelMeta, model);
for (const fieldInfo of Object.values(fields)) {
if (fieldInfo.isTypeDef) {
Expand All @@ -82,24 +84,24 @@ class DefaultAuthHandler extends DefaultPrismaProxyHandler {
const defaultValue = this.getDefaultValue(fieldInfo);
if (defaultValue !== undefined) {
// set field value extracted from `auth()`
this.setDefaultValueForModelData(fieldInfo, model, data, defaultValue);
this.setDefaultValueForModelData(fieldInfo, model, data, defaultValue, context);
}
}
};

// visit create payload and set default value to fields using `auth()` in `@default()`
const visitor = new NestedWriteVisitor(this.options.modelMeta, {
create: (model, data) => {
processCreatePayload(model, data);
create: (model, data, context) => {
processCreatePayload(model, data, context);
},

upsert: (model, data) => {
processCreatePayload(model, data.create);
upsert: (model, data, context) => {
processCreatePayload(model, data.create, context);
},

createMany: (model, args) => {
createMany: (model, args, context) => {
for (const item of enumerate(args.data)) {
processCreatePayload(model, item);
processCreatePayload(model, item, context);
}
},
});
Expand All @@ -108,42 +110,82 @@ class DefaultAuthHandler extends DefaultPrismaProxyHandler {
return newArgs;
}

private setDefaultValueForModelData(fieldInfo: FieldInfo, model: string, data: any, authDefaultValue: unknown) {
if (fieldInfo.isForeignKey && fieldInfo.relationField && fieldInfo.relationField in data) {
private setDefaultValueForModelData(
fieldInfo: FieldInfo,
model: string,
data: any,
authDefaultValue: unknown,
context: NestedWriteVisitorContext
) {
if (fieldInfo.isForeignKey) {
// if the field being inspected is a fk field, there are several cases we should not
// set the default value or should not set directly

// if the field is a fk, and the relation field is already set, we should not override it
return;
}
if (fieldInfo.relationField && fieldInfo.relationField in data) {
return;
}

if (fieldInfo.isForeignKey && !isUnsafeMutate(model, data, this.options.modelMeta)) {
// if the field is a fk, and the create payload is not unsafe, we need to translate
// the fk field setting to a `connect` of the corresponding relation field
const relFieldName = fieldInfo.relationField;
if (!relFieldName) {
throw new Error(
`Field \`${fieldInfo.name}\` is a foreign key field but no corresponding relation field is found`
if (context.field?.backLink && context.nestingPath.length > 1) {
// if the fk field is in a creation context where its implied by the parent,
// we should not set the default value, e.g.:
//
// ```
// parent.create({ data: { child: { create: {} } } })
// ```
//
// event if child's fk to parent has a default value, we should not set default
// value here

// fetch parent model from the parent context
const parentModel = getModelInfo(
this.options.modelMeta,
context.nestingPath[context.nestingPath.length - 2].model
);
}
const relationField = requireField(this.options.modelMeta, model, relFieldName);

// construct a `{ connect: { ... } }` payload
let connect = data[relationField.name]?.connect;
if (!connect) {
connect = {};
data[relationField.name] = { connect };
if (parentModel) {
// get the opposite side of the relation for the current create context
const oppositeRelationField = requireField(this.options.modelMeta, model, context.field.backLink);
if (parentModel.name === oppositeRelationField.type) {
// if the opposite side matches the parent model, it means we currently in a creation context
// that implicitly sets this fk field
return;
}
}
}

// sets the opposite fk field to value `authDefaultValue`
const oppositeFkFieldName = this.getOppositeFkFieldName(relationField, fieldInfo);
if (!oppositeFkFieldName) {
throw new Error(
`Cannot find opposite foreign key field for \`${fieldInfo.name}\` in relation field \`${relFieldName}\``
);
if (!isUnsafeMutate(model, data, this.options.modelMeta)) {
// if the field is a fk, and the create payload is not unsafe, we need to translate
// the fk field setting to a `connect` of the corresponding relation field
const relFieldName = fieldInfo.relationField;
if (!relFieldName) {
throw new Error(
`Field \`${fieldInfo.name}\` is a foreign key field but no corresponding relation field is found`
);
}
const relationField = requireField(this.options.modelMeta, model, relFieldName);

// construct a `{ connect: { ... } }` payload
let connect = data[relationField.name]?.connect;
if (!connect) {
connect = {};
data[relationField.name] = { connect };
}

// sets the opposite fk field to value `authDefaultValue`
const oppositeFkFieldName = this.getOppositeFkFieldName(relationField, fieldInfo);
if (!oppositeFkFieldName) {
throw new Error(
`Cannot find opposite foreign key field for \`${fieldInfo.name}\` in relation field \`${relFieldName}\``
);
}
connect[oppositeFkFieldName] = authDefaultValue;
return;
}
connect[oppositeFkFieldName] = authDefaultValue;
} else {
// set default value directly
data[fieldInfo.name] = authDefaultValue;
}

// set default value directly
data[fieldInfo.name] = authDefaultValue;
}

private getOppositeFkFieldName(relationField: FieldInfo, fieldInfo: FieldInfo) {
Expand Down
21 changes: 19 additions & 2 deletions packages/runtime/src/enhancements/node/delegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1106,15 +1106,32 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler {
const entities = await db[model].findMany(findArgs);

// recursively delete base entities (they all have the same id values)
await Promise.all(entities.map((entity) => this.doDelete(db, model, { where: entity })));

await Promise.all(
entities.map((entity) => {
let deleteFilter = entity;
if (Object.keys(deleteFilter).length > 1) {
// if the model has compound id fields, we need to compose a compound key filter,
// otherwise calling Prisma's `delete` won't work
deleteFilter = this.queryUtils.composeCompoundUniqueField(model, deleteFilter);
}
return this.doDelete(db, model, { where: deleteFilter });
})
);

return { count: entities.length };
}

private async deleteBaseRecursively(db: CrudContract, model: string, idValues: any) {
let base = this.getBaseModel(model);
while (base) {
await db[base.name].delete({ where: idValues });
let deleteFilter = idValues;
if (Object.keys(idValues).length > 1) {
// if the model has compound id fields, we need to compose a compound key filter,
// otherwise calling Prisma's `delete` won't work
deleteFilter = this.queryUtils.composeCompoundUniqueField(base.name, deleteFilter);
}
await db[base.name].delete({ where: deleteFilter });
base = this.getBaseModel(base.name);
}
}
Expand Down
Loading
Loading