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-v3",
"version": "3.2.0",
"version": "3.2.1",
"description": "ZenStack",
"packageManager": "[email protected]",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion packages/auth-adapters/better-auth/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/better-auth",
"version": "3.2.0",
"version": "3.2.1",
"description": "ZenStack Better Auth Adapter. This adapter is modified from better-auth's Prisma adapter.",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"publisher": "zenstack",
"displayName": "ZenStack CLI",
"description": "FullStack database toolkit with built-in access control and automatic API generation.",
"version": "3.2.0",
"version": "3.2.1",
"type": "module",
"author": {
"name": "ZenStack Team"
Expand Down
2 changes: 1 addition & 1 deletion packages/clients/client-helpers/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/client-helpers",
"version": "3.2.0",
"version": "3.2.1",
"description": "Helpers for implementing clients that consume ZenStack's CRUD service",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/clients/tanstack-query/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/tanstack-query",
"version": "3.2.0",
"version": "3.2.1",
"description": "TanStack Query Client for consuming ZenStack v3's CRUD service",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/common-helpers/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/common-helpers",
"version": "3.2.0",
"version": "3.2.1",
"description": "ZenStack Common Helpers",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/config/eslint-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/eslint-config",
"version": "3.2.0",
"version": "3.2.1",
"type": "module",
"private": true,
"license": "MIT"
Expand Down
2 changes: 1 addition & 1 deletion packages/config/typescript-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/typescript-config",
"version": "3.2.0",
"version": "3.2.1",
"private": true,
"license": "MIT"
}
2 changes: 1 addition & 1 deletion packages/config/vitest-config/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/vitest-config",
"type": "module",
"version": "3.2.0",
"version": "3.2.1",
"private": true,
"license": "MIT",
"exports": {
Expand Down
2 changes: 1 addition & 1 deletion packages/create-zenstack/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-zenstack",
"version": "3.2.0",
"version": "3.2.1",
"description": "Create a new ZenStack project",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/ide/vscode/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "zenstack-v3",
"publisher": "zenstack",
"version": "3.2.0",
"version": "3.2.1",
"displayName": "ZenStack V3 Language Tools",
"description": "VSCode extension for ZenStack (v3) ZModel language",
"private": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/language/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/language",
"description": "ZenStack ZModel language specification",
"version": "3.2.0",
"version": "3.2.1",
"license": "MIT",
"author": "ZenStack Team",
"files": [
Expand Down
119 changes: 97 additions & 22 deletions packages/language/src/validators/attribute-application-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
isLiteralExpr,
isModel,
isReferenceExpr,
isStringLiteral,
isTypeDef,
} from '../generated/ast';
import {
Expand Down Expand Up @@ -103,10 +104,9 @@ export default class AttributeApplicationValidator implements AstValidator<Attri
}
}

if (!assignableToAttributeParam(arg, paramDecl, attr)) {
accept('error', `Value is not assignable to parameter`, {
node: arg,
});
const argAssignable = assignableToAttributeParam(arg, paramDecl, attr);
if (!argAssignable.result) {
accept('error', argAssignable.error, { node: arg });
return;
}

Expand Down Expand Up @@ -393,10 +393,21 @@ export default class AttributeApplicationValidator implements AstValidator<Attri
}
}

function assignableToAttributeParam(arg: AttributeArg, param: AttributeParam, attr: AttributeApplication): boolean {
function assignableToAttributeParam(
arg: AttributeArg,
param: AttributeParam,
attr: AttributeApplication,
):
| {
result: true;
}
| { result: false; error: string } {
const genericError = { result: false, error: 'invalid argument type' } as const;
const success = { result: true } as const;

const argResolvedType = arg.$resolvedType;
if (!argResolvedType) {
return false;
return { result: false, error: 'unable to resolve argument type' };
}

let dstType = param.type.type;
Expand All @@ -405,10 +416,30 @@ function assignableToAttributeParam(arg: AttributeArg, param: AttributeParam, at
if (dstType === 'ContextType') {
// ContextType is inferred from the attribute's container's type
if (isDataField(attr.$container)) {
// If the field is Typed JSON, and the attribute is @default, the argument must be a string
const dstIsTypedJson = hasAttribute(attr.$container, '@json');
if (dstIsTypedJson && attr.decl.ref?.name === '@default') {
return argResolvedType.decl === 'String';
// If the field is JSON, and the attribute is @default, the argument must be a JSON string
// (design inherited from Prisma)
const dstIsJson = attr.$container.type.type === 'Json' || hasAttribute(attr.$container, '@json');
if (dstIsJson && attr.decl.ref?.name === '@default') {
if (attr.$container.type.array && attr.$container.type.type === 'Json') {
// Json[] default value, must be array of JSON strings
if (isArrayExpr(arg.value) && arg.value.items.every((item) => isLiteralJsonString(item))) {
return success;
} else {
return {
result: false,
error: 'expected an array of JSON string literals',
};
}
} else {
if (isLiteralJsonString(arg.value)) {
return success;
} else {
return {
result: false,
error: 'expected a JSON string literal',
};
}
}
}
dstIsArray = attr.$container.type.array;
}
Expand All @@ -417,30 +448,45 @@ function assignableToAttributeParam(arg: AttributeArg, param: AttributeParam, at
const dstRef = param.type.reference;

if (dstType === 'Any' && !dstIsArray) {
return true;
return success;
}

if (argResolvedType.decl === 'Any') {
// arg is any type
if (!argResolvedType.array) {
// if it's not an array, it's assignable to any type
return true;
return success;
} else {
// otherwise it's assignable to any array type
return argResolvedType.array === dstIsArray;
if (argResolvedType.array === dstIsArray) {
return success;
} else {
return {
result: false,
error: `expected ${dstIsArray ? 'array' : 'non-array'}`,
};
}
}
}

// destination is field reference or transitive field reference, check if
// argument is reference or array or reference
if (dstType === 'FieldReference' || dstType === 'TransitiveFieldReference') {
if (dstIsArray) {
return (
if (
isArrayExpr(arg.value) &&
!arg.value.items.find((item) => !isReferenceExpr(item) || !isDataField(item.target.ref))
);
!arg.value.items.some((item) => !isReferenceExpr(item) || !isDataField(item.target.ref))
) {
return success;
} else {
return { result: false, error: 'expected an array of field references' };
}
} else {
return isReferenceExpr(arg.value) && isDataField(arg.value.target.ref);
if (isReferenceExpr(arg.value) && isDataField(arg.value.target.ref)) {
return success;
} else {
return { result: false, error: 'expected a field reference' };
}
}
}

Expand All @@ -454,21 +500,30 @@ function assignableToAttributeParam(arg: AttributeArg, param: AttributeParam, at
attrArgDeclType = resolved(attr.$container.type.reference);
dstIsArray = attr.$container.type.array;
}
return attrArgDeclType === argResolvedType.decl && dstIsArray === argResolvedType.array;

if (attrArgDeclType !== argResolvedType.decl) {
return genericError;
}

if (dstIsArray !== argResolvedType.array) {
return { result: false, error: `expected ${dstIsArray ? 'array' : 'non-array'}` };
}

return success;
} else if (dstType) {
// scalar type

if (typeof argResolvedType?.decl !== 'string') {
// destination type is not a reference, so argument type must be a plain expression
return false;
return genericError;
}

if (dstType === 'ContextType') {
// attribute parameter type is ContextType, need to infer type from
// the attribute's container
if (isDataField(attr.$container)) {
if (!attr.$container?.type?.type) {
return false;
return genericError;
}
dstType = mapBuiltinTypeToExpressionType(attr.$container.type.type);
dstIsArray = attr.$container.type.array;
Expand All @@ -477,10 +532,18 @@ function assignableToAttributeParam(arg: AttributeArg, param: AttributeParam, at
}
}

return typeAssignable(dstType, argResolvedType.decl, arg.value) && dstIsArray === argResolvedType.array;
if (typeAssignable(dstType, argResolvedType.decl, arg.value) && dstIsArray === argResolvedType.array) {
return success;
} else {
return genericError;
}
} else {
// reference type
return (dstRef?.ref === argResolvedType.decl || dstType === 'Any') && dstIsArray === argResolvedType.array;
if ((dstRef?.ref === argResolvedType.decl || dstType === 'Any') && dstIsArray === argResolvedType.array) {
return success;
} else {
return genericError;
}
}
}

Expand Down Expand Up @@ -552,3 +615,15 @@ export function validateAttributeApplication(
) {
new AttributeApplicationValidator().validate(attr, accept, contextDataModel);
}

function isLiteralJsonString(value: Expression) {
if (!isStringLiteral(value)) {
return false;
}
try {
JSON.parse(value.value);
return true;
} catch {
return false;
}
}
2 changes: 1 addition & 1 deletion packages/orm/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/orm",
"version": "3.2.0",
"version": "3.2.1",
"description": "ZenStack ORM",
"type": "module",
"scripts": {
Expand Down
6 changes: 5 additions & 1 deletion packages/orm/src/client/crud-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,11 @@ export type ModelResult<
ModelFieldIsOptional<Schema, Model, Key>,
FieldIsArray<Schema, Model, Key>
>;
}
} & ('_count' extends keyof I
? I['_count'] extends false | undefined
? {}
: { _count: SelectCountResult<Schema, Model, I['_count']> }
: {})
: Args extends { omit: infer O } & Record<string, unknown>
? DefaultModelResult<Schema, Model, O, Options, false, false>
: DefaultModelResult<Schema, Model, undefined, Options, false, false>,
Expand Down
3 changes: 2 additions & 1 deletion packages/orm/src/client/crud/operations/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export class UpdateOperationHandler<Schema extends SchemaDef> extends BaseOperat
if (needReadBack) {
// updated can be undefined if there's nothing to update, in that case we'll use the original
// filter to read back the entity
const readFilter = updateResult ?? args.where;
// note that we trim filter to id fields only, just in case underlying executor returns more fields
const readFilter = updateResult ? getIdValues(this.schema, this.model, updateResult) : args.where;
let readBackResult: any = undefined;
readBackResult = await this.readUnique(tx, this.model, {
select: args.select,
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/policy/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/plugin-policy",
"version": "3.2.0",
"version": "3.2.1",
"description": "ZenStack Policy Plugin",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/schema",
"version": "3.2.0",
"version": "3.2.1",
"description": "ZenStack Runtime Schema",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/sdk",
"version": "3.2.0",
"version": "3.2.1",
"description": "ZenStack SDK",
"type": "module",
"scripts": {
Expand Down
Loading
Loading