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

fix: batch bug fixes #273

Merged
merged 4 commits into from
Mar 19, 2023
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 package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zenstack-monorepo",
"version": "1.0.0-alpha.74",
"version": "1.0.0-alpha.78",
"description": "",
"scripts": {
"build": "pnpm -r build",
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": "1.0.0-alpha.74",
"version": "1.0.0-alpha.78",
"displayName": "ZenStack modeling language compiler",
"description": "ZenStack modeling language compiler",
"homepage": "https://zenstack.dev",
Expand Down
2 changes: 1 addition & 1 deletion packages/next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/next",
"version": "1.0.0-alpha.74",
"version": "1.0.0-alpha.78",
"displayName": "ZenStack Next.js integration",
"description": "ZenStack Next.js integration",
"homepage": "https://zenstack.dev",
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": "1.0.0-alpha.74",
"version": "1.0.0-alpha.78",
"description": "ZenStack plugin and runtime supporting OpenAPI",
"main": "index.js",
"repository": {
Expand Down
41 changes: 32 additions & 9 deletions packages/plugins/openapi/src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class OpenAPIGenerator {
private outputObjectTypes: DMMF.OutputType[] = [];
private usedComponents: Set<string> = new Set<string>();
private aggregateOperationSupport: AggregateOperationSupport;
private includedModels: DataModel[];

constructor(private model: Model, private options: PluginOptions, private dmmf: DMMF.Document) {}

Expand All @@ -39,6 +40,9 @@ export class OpenAPIGenerator {
// input types
this.inputObjectTypes.push(...this.dmmf.schema.inputObjectTypes.prisma);
this.outputObjectTypes.push(...this.dmmf.schema.outputObjectTypes.prisma);
this.includedModels = this.model.declarations.filter(
(d): d is DataModel => isDataModel(d) && !hasAttribute(d, '@@openapi.ignore')
);

// add input object types that are missing from Prisma dmmf
addMissingInputObjectTypesForModelArgs(this.inputObjectTypes, this.dmmf.datamodel.models);
Expand All @@ -59,7 +63,13 @@ export class OpenAPIGenerator {
info: {
title: this.getOption('title', 'ZenStack Generated API'),
version: this.getOption('version', '1.0.0'),
description: this.getOption('description', undefined),
summary: this.getOption('summary', undefined),
},
tags: this.includedModels.map((model) => ({
name: camelCase(model.name),
description: `${model.name} operations`,
})),
components,
paths,
};
Expand Down Expand Up @@ -125,12 +135,10 @@ export class OpenAPIGenerator {
private generatePaths(components: OAPI.ComponentsObject): OAPI.PathsObject {
let result: OAPI.PathsObject = {};

const includeModels = this.model.declarations
.filter((d) => isDataModel(d) && !hasAttribute(d, '@@openapi.ignore'))
.map((d) => d.name);
const includeModelNames = this.includedModels.map((d) => d.name);

for (const model of this.dmmf.datamodel.models) {
if (includeModels.includes(model.name)) {
if (includeModelNames.includes(model.name)) {
const zmodel = this.model.declarations.find(
(d) => isDataModel(d) && d.name === model.name
) as DataModel;
Expand Down Expand Up @@ -465,11 +473,16 @@ export class OpenAPIGenerator {
resolvedPath = resolvedPath.substring(1);
}

let prefix = this.getOption('prefix', '');
if (prefix.endsWith('/')) {
prefix = prefix.substring(0, prefix.length - 1);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const def: any = {
operationId: `${operation}${model.name}`,
description: meta?.description ?? description,
tags: meta?.tags,
tags: meta?.tags || [camelCase(model.name)],
summary: meta?.summary,
responses: {
[successCode !== undefined ? successCode : '200']: {
Expand Down Expand Up @@ -504,13 +517,17 @@ export class OpenAPIGenerator {
name: 'q',
in: 'query',
required: true,
schema: inputType,
content: {
'application/json': {
schema: inputType,
},
},
},
] satisfies OAPI.ParameterObject[];
}
}

result[`/${camelCase(model.name)}/${resolvedPath}`] = {
result[`${prefix}/${camelCase(model.name)}/${resolvedPath}`] = {
[resolvedMethod]: def,
};
}
Expand Down Expand Up @@ -546,8 +563,14 @@ export class OpenAPIGenerator {
return this.ref(name);
}

private getOption(name: string, defaultValue: string) {
return this.options[name] ? (this.options[name] as string) : defaultValue;
private getOption<T extends string | undefined>(
name: string,
defaultValue: T
): T extends string ? string : string | undefined {
const value = this.options[name];
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
return typeof value === 'string' ? value : defaultValue;
}

private generateComponents() {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/react/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/react",
"displayName": "ZenStack plugin and runtime for ReactJS",
"version": "1.0.0-alpha.74",
"version": "1.0.0-alpha.78",
"description": "ZenStack plugin and runtime for ReactJS",
"main": "index.js",
"repository": {
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": "1.0.0-alpha.74",
"version": "1.0.0-alpha.78",
"description": "ZenStack plugin for tRPC",
"main": "index.js",
"repository": {
Expand Down
4 changes: 3 additions & 1 deletion 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": "1.0.0-alpha.74",
"version": "1.0.0-alpha.78",
"description": "Runtime of ZenStack for both client-side and server-side environments.",
"repository": {
"type": "git",
Expand All @@ -28,6 +28,7 @@
"cuid": "^2.1.8",
"decimal.js": "^10.4.2",
"deepcopy": "^2.1.0",
"pluralize": "^8.0.0",
"superjson": "^1.11.0",
"tslib": "^2.4.1",
"zod": "^3.19.1",
Expand All @@ -45,6 +46,7 @@
"@types/bcryptjs": "^2.4.2",
"@types/jest": "^29.0.3",
"@types/node": "^14.18.29",
"@types/pluralize": "^0.0.29",
"copyfiles": "^2.4.1",
"rimraf": "^3.0.2",
"typescript": "^4.9.3"
Expand Down
43 changes: 33 additions & 10 deletions packages/runtime/src/enhancements/nested-write-vistor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { resolveField } from './model-meta';
import { ModelMeta } from './types';
import { Enumerable, ensureArray, getModelFields } from './utils';

type NestingPathItem = { field?: FieldInfo; where: any; unique: boolean };

/**
* Context for visiting
*/
Expand All @@ -23,7 +25,7 @@ export type VisitorContext = {
/**
* A top-down path of all nested update conditions and corresponding field till now
*/
nestingPath: { field?: FieldInfo; where: any }[];
nestingPath: NestingPathItem[];
};

/**
Expand All @@ -38,6 +40,10 @@ export type NestedWriterVisitorCallback = {
context: VisitorContext
) => Promise<void>;

connect?: (model: string, args: Enumerable<object>, context: VisitorContext) => Promise<void>;

disconnect?: (model: string, args: Enumerable<object>, context: VisitorContext) => Promise<void>;

update?: (model: string, args: Enumerable<{ where: object; data: any }>, context: VisitorContext) => Promise<void>;

updateMany?: (
Expand Down Expand Up @@ -103,7 +109,7 @@ export class NestedWriteVisitor {
data: any,
parent: any,
field: FieldInfo | undefined,
nestingPath: { field?: FieldInfo; where: any }[]
nestingPath: NestingPathItem[]
): Promise<void> {
if (!data) {
return;
Expand All @@ -116,7 +122,7 @@ export class NestedWriteVisitor {
// visit payload
switch (action) {
case 'create':
context.nestingPath.push({ field, where: {} });
context.nestingPath.push({ field, where: {}, unique: false });
if (this.callback.create) {
await this.callback.create(model, data, context);
}
Expand All @@ -126,7 +132,7 @@ export class NestedWriteVisitor {
case 'createMany':
// skip the 'data' layer so as to keep consistency with 'create'
if (data.data) {
context.nestingPath.push({ field, where: {} });
context.nestingPath.push({ field, where: {}, unique: false });
if (this.callback.create) {
await this.callback.create(model, data.data, context);
}
Expand All @@ -135,31 +141,48 @@ export class NestedWriteVisitor {
break;

case 'connectOrCreate':
context.nestingPath.push({ field, where: data.where });
context.nestingPath.push({ field, where: data.where, unique: true });
if (this.callback.connectOrCreate) {
await this.callback.connectOrCreate(model, data, context);
}
fieldContainers.push(...ensureArray(data).map((d) => d.create));
break;

case 'connect':
context.nestingPath.push({ field, where: data, unique: true });
if (this.callback.connect) {
await this.callback.connect(model, data, context);
}
break;

case 'disconnect':
// disconnect has two forms:
// if relation is to-many, the payload is a unique filter object
// if relation is to-one, the payload can only be boolean `true`
context.nestingPath.push({ field, where: data, unique: typeof data === 'object' });
if (this.callback.disconnect) {
await this.callback.disconnect(model, data, context);
}
break;

case 'update':
context.nestingPath.push({ field, where: data.where });
context.nestingPath.push({ field, where: data.where, unique: false });
if (this.callback.update) {
await this.callback.update(model, data, context);
}
fieldContainers.push(...ensureArray(data).map((d) => (isToOneUpdate ? d : d.data)));
break;

case 'updateMany':
context.nestingPath.push({ field, where: data.where });
context.nestingPath.push({ field, where: data.where, unique: false });
if (this.callback.updateMany) {
await this.callback.updateMany(model, data, context);
}
fieldContainers.push(...ensureArray(data));
break;

case 'upsert':
context.nestingPath.push({ field, where: data.where });
context.nestingPath.push({ field, where: data.where, unique: true });
if (this.callback.upsert) {
await this.callback.upsert(model, data, context);
}
Expand All @@ -168,14 +191,14 @@ export class NestedWriteVisitor {
break;

case 'delete':
context.nestingPath.push({ field, where: data.where });
context.nestingPath.push({ field, where: data.where, unique: false });
if (this.callback.delete) {
await this.callback.delete(model, data, context);
}
break;

case 'deleteMany':
context.nestingPath.push({ field, where: data.where });
context.nestingPath.push({ field, where: data.where, unique: false });
if (this.callback.deleteMany) {
await this.callback.deleteMany(model, data, context);
}
Expand Down
15 changes: 13 additions & 2 deletions packages/runtime/src/enhancements/policy/handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import { PrismaClientValidationError } from '@prisma/client/runtime';
import { CrudFailureReason } from '@zenstackhq/sdk';
import { AuthUser, DbClientContract, PolicyOperationKind } from '../../types';
import { BatchResult, PrismaProxyHandler } from '../proxy';
import { ModelMeta, PolicyDef } from '../types';
Expand Down Expand Up @@ -227,7 +228,12 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
await this.modelClient.delete(args);

if (!readResult) {
throw this.utils.deniedByPolicy(this.model, 'delete', 'result not readable');
throw this.utils.deniedByPolicy(
this.model,
'delete',
'result is not allowed to be read back',
CrudFailureReason.RESULT_NOT_READABLE
);
} else {
return readResult;
}
Expand Down Expand Up @@ -296,7 +302,12 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
const result = await this.utils.readWithCheck(this.model, readArgs);
if (result.length === 0) {
this.logger.warn(`${action} result cannot be read back`);
throw this.utils.deniedByPolicy(this.model, operation, 'result is not allowed to be read back');
throw this.utils.deniedByPolicy(
this.model,
operation,
'result is not allowed to be read back',
CrudFailureReason.RESULT_NOT_READABLE
);
} else if (result.length > 1) {
throw this.utils.unknownError('write unexpected resulted in multiple readback entities');
}
Expand Down
Loading