Skip to content
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.121",
"version": "1.0.0-alpha.122",
"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.121",
"version": "1.0.0-alpha.122",
"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.121",
"version": "1.0.0-alpha.122",
"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.121",
"version": "1.0.0-alpha.122",
"description": "ZenStack plugin and runtime supporting OpenAPI",
"main": "index.js",
"repository": {
Expand Down
40 changes: 25 additions & 15 deletions packages/plugins/openapi/src/rest-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
_jsonapi: {
type: 'object',
description: 'An object describing the server’s implementation',
required: ['version'],
properties: {
version: { type: 'string' },
meta: this.ref('_meta'),
Expand All @@ -548,44 +549,50 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
description: 'Identifier for a resource',
required: ['type', 'id'],
properties: {
type: { type: 'string' },
id: { type: 'string' },
type: { type: 'string', description: 'Resource type' },
id: { type: 'string', description: 'Resource id' },
},
},
_resource: this.allOf(this.ref('_resourceIdentifier'), {
type: 'object',
description: 'A resource with attributes and relationships',
properties: {
attributes: { type: 'object' },
relationships: { type: 'object' },
attributes: { type: 'object', description: 'Resource attributes' },
relationships: { type: 'object', description: 'Resource relationships' },
},
}),
_links: {
type: 'object',
required: ['self'],
description: 'Links related to the resource',
properties: { self: { type: 'string' } },
properties: { self: { type: 'string', description: 'Link for refetching the curent results' } },
},
_pagination: {
type: 'object',
description: 'Pagination information',
required: ['first', 'last', 'prev', 'next'],
properties: {
first: this.nullable({ type: 'string' }),
last: this.nullable({ type: 'string' }),
prev: this.nullable({ type: 'string' }),
next: this.nullable({ type: 'string' }),
first: this.nullable({ type: 'string', description: 'Link to the first page' }),
last: this.nullable({ type: 'string', description: 'Link to the last page' }),
prev: this.nullable({ type: 'string', description: 'Link to the previous page' }),
next: this.nullable({ type: 'string', description: 'Link to the next page' }),
},
},
_errors: {
type: 'array',
description: 'An array of error objects',
items: {
type: 'object',
required: ['status', 'code'],
properties: {
status: { type: 'string' },
code: { type: 'string' },
title: { type: 'string' },
detail: { type: 'string' },
status: { type: 'string', description: 'HTTP status' },
code: { type: 'string', description: 'Error code' },
prismaCode: {
type: 'string',
description: 'Prisma error code if the error is thrown by Prisma',
},
title: { type: 'string', description: 'Error title' },
detail: { type: 'string', description: 'Error detail' },
},
},
},
Expand All @@ -603,8 +610,11 @@ export class RESTfulOpenAPIGenerator extends OpenAPIGeneratorBase {
required: ['self', 'related'],
description: 'Links related to a relationship',
properties: {
self: { type: 'string' },
related: { type: 'string' },
self: { type: 'string', description: 'Link for fetching this relationship' },
related: {
type: 'string',
description: 'Link for fetching the resource represented by this relationship',
},
},
},
_toOneRelationship: {
Expand Down
28 changes: 28 additions & 0 deletions packages/plugins/openapi/tests/baseline/rest.baseline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,8 @@ components:
_jsonapi:
type: object
description: An object describing the server’s implementation
required:
- version
properties:
version:
type: string
Expand All @@ -1314,8 +1316,10 @@ components:
properties:
type:
type: string
description: Resource type
id:
type: string
description: Resource id
_resource:
allOf:
- $ref: '#/components/schemas/_resourceIdentifier'
Expand All @@ -1324,8 +1328,10 @@ components:
properties:
attributes:
type: object
description: Resource attributes
relationships:
type: object
description: Resource relationships
_links:
type: object
required:
Expand All @@ -1334,40 +1340,60 @@ components:
properties:
self:
type: string
description: Link for refetching the curent results
_pagination:
type: object
description: Pagination information
required:
- first
- last
- prev
- next
properties:
first:
oneOf:
- type: string
description: Link to the first page
- type: 'null'
last:
oneOf:
- type: string
description: Link to the last page
- type: 'null'
prev:
oneOf:
- type: string
description: Link to the previous page
- type: 'null'
next:
oneOf:
- type: string
description: Link to the next page
- type: 'null'
_errors:
type: array
description: An array of error objects
items:
type: object
required:
- status
- code
properties:
status:
type: string
description: HTTP status
code:
type: string
description: Error code
prismaCode:
type: string
description: Prisma error code if the error is thrown by Prisma
title:
type: string
description: Error title
detail:
type: string
description: Error detail
_errorResponse:
type: object
required:
Expand All @@ -1387,8 +1413,10 @@ components:
properties:
self:
type: string
description: Link for fetching this relationship
related:
type: string
description: Link for fetching the resource represented by this relationship
_toOneRelationship:
type: object
description: A to-one relationship
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.121",
"version": "1.0.0-alpha.122",
"description": "ZenStack plugin and runtime for ReactJS",
"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": "1.0.0-alpha.121",
"version": "1.0.0-alpha.122",
"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": "1.0.0-alpha.121",
"version": "1.0.0-alpha.122",
"description": "ZenStack plugin for generating tanstack-query hooks",
"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.121",
"version": "1.0.0-alpha.122",
"description": "ZenStack plugin for tRPC",
"main": "index.js",
"repository": {
Expand Down
2 changes: 1 addition & 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.121",
"version": "1.0.0-alpha.122",
"description": "Runtime of ZenStack for both client-side and server-side environments.",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/enhancements/policy/policy-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -864,7 +864,7 @@ export class PolicyUtil {
* Gets "id" field for a given model.
*/
getIdFields(model: string) {
return getIdFields(this.modelMeta, model);
return getIdFields(this.modelMeta, model, true);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime/src/enhancements/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ export function getModelFields(data: object) {
/**
* Gets id fields for the given model.
*/
export function getIdFields(modelMeta: ModelMeta, model: string) {
export function getIdFields(modelMeta: ModelMeta, model: string, throwIfNotFound = false) {
const fields = modelMeta.fields[lowerCaseFirst(model)];
if (!fields) {
throw new Error(`Unable to load fields for ${model}`);
}
const result = Object.values(fields).filter((f) => f.isId);
if (result.length === 0) {
if (result.length === 0 && throwIfNotFound) {
throw new Error(`model ${model} does not have an id field`);
}
return result;
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"publisher": "zenstack",
"displayName": "ZenStack Language Tools",
"description": "A toolkit for building secure CRUD apps with Next.js + Typescript",
"version": "1.0.0-alpha.121",
"version": "1.0.0-alpha.122",
"author": {
"name": "ZenStack Team"
},
Expand Down
39 changes: 36 additions & 3 deletions packages/schema/src/plugins/prisma/schema-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from '@zenstackhq/language/ast';
import {
analyzePolicies,
getDataModels,
getLiteral,
getLiteralArray,
GUARD_FIELD_NAME,
Expand Down Expand Up @@ -219,9 +220,7 @@ export default class PrismaSchemaGenerator {
this.generateModelField(model, field);
}

const { allowAll, denyAll, hasFieldValidation } = analyzePolicies(decl);

if ((!allowAll && !denyAll) || hasFieldValidation) {
if (this.shouldGenerateAuxFields(decl)) {
// generate auxiliary fields for policy check

// add an "zenstack_guard" field for dealing with boolean conditions
Expand Down Expand Up @@ -274,6 +273,40 @@ export default class PrismaSchemaGenerator {
decl.comments.forEach((c) => model.addComment(c));
}

private shouldGenerateAuxFields(decl: DataModel) {
const { allowAll, denyAll, hasFieldValidation } = analyzePolicies(decl);

if (!allowAll && !denyAll) {
// has policy conditions
return true;
}

if (hasFieldValidation) {
return true;
}

// check if the model is related by other models, if so
// aux fields are needed for nested queries
const root = decl.$container;
for (const model of getDataModels(root)) {
if (model === decl) {
continue;
}
for (const field of model.fields) {
if (field.type.reference?.ref === decl) {
// found a relation with policies
const otherPolicies = analyzePolicies(model);
if ((!otherPolicies.allowAll && !otherPolicies.denyAll) || otherPolicies.hasFieldValidation) {
// the relating side has policies
return true;
}
}
}
}

return false;
}

private isPrismaAttribute(attr: DataModelAttribute | DataModelFieldAttribute) {
if (!attr.decl.ref) {
return false;
Expand Down
Loading