Skip to content

Commit

Permalink
merge dev to main (v2.12.0) (#2013)
Browse files Browse the repository at this point in the history
  • Loading branch information
ymc9 authored Feb 25, 2025
2 parents 584d8af + 82b8d25 commit 0107e1c
Show file tree
Hide file tree
Showing 48 changed files with 1,144 additions and 264 deletions.
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

0 comments on commit 0107e1c

Please sign in to comment.