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
Expand Up @@ -2,7 +2,7 @@
"name": "zenstack-v3",
"displayName": "ZenStack",
"description": "ZenStack",
"version": "3.5.4",
"version": "3.5.5",
"type": "module",
"author": {
"name": "ZenStack Team",
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
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/better-auth",
"displayName": "ZenStack Better Auth Adapter",
"description": "ZenStack Better Auth Adapter. This adapter is modified from better-auth's Prisma adapter.",
"version": "3.5.4",
"version": "3.5.5",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/cli",
"displayName": "ZenStack CLI",
"description": "FullStack database toolkit with built-in access control and automatic API generation.",
"version": "3.5.4",
"version": "3.5.5",
"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
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/client-helpers",
"displayName": "ZenStack Client Helpers",
"description": "Helpers for implementing clients that consume ZenStack's CRUD service",
"version": "3.5.4",
"version": "3.5.5",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
4 changes: 2 additions & 2 deletions packages/clients/tanstack-query/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/tanstack-query",
"displayName": "ZenStack TanStack Query Integration",
"description": "TanStack Query Client for consuming ZenStack v3's CRUD service",
"version": "3.5.4",
"version": "3.5.5",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down Expand Up @@ -74,7 +74,7 @@
"@zenstackhq/sdk": "workspace:*",
"@zenstackhq/typescript-config": "workspace:*",
"@zenstackhq/vitest-config": "workspace:*",
"happy-dom": "^20.0.10",
"happy-dom": "^20.8.9",
"nock": "^14.0.10",
"react": "catalog:",
"svelte": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion packages/common-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/common-helpers",
"displayName": "ZenStack Common Helpers",
"description": "ZenStack Common Helpers",
"version": "3.5.4",
"version": "3.5.5",
"type": "module",
"author": {
"name": "ZenStack Team",
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.5.4",
"version": "3.5.5",
"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.5.4",
"version": "3.5.5",
"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.5.4",
"version": "3.5.5",
"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
Expand Up @@ -2,7 +2,7 @@
"name": "create-zenstack",
"displayName": "Create ZenStack",
"description": "Create a new ZenStack project",
"version": "3.5.4",
"version": "3.5.5",
"type": "module",
"author": {
"name": "ZenStack Team",
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.5.4",
"version": "3.5.5",
"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
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/language",
"displayName": "ZenStack Language Tooling",
"description": "ZenStack ZModel language specification",
"version": "3.5.4",
"version": "3.5.5",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
2 changes: 1 addition & 1 deletion packages/orm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/orm",
"displayName": "ZenStack ORM",
"description": "ZenStack ORM",
"version": "3.5.4",
"version": "3.5.5",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
14 changes: 12 additions & 2 deletions packages/orm/src/client/crud-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@ type TypedJsonTypedFilter<
| (Array extends true
? ArrayTypedJsonFilter<Schema, TypeDefName, AllowedKinds>
: NonArrayTypedJsonFilter<Schema, TypeDefName, AllowedKinds>)
| (Optional extends true ? null : never)
| (Optional extends true ? null | JsonNullValues : never)
: {};

type ArrayTypedJsonFilter<
Expand Down Expand Up @@ -1375,14 +1375,24 @@ type ScalarFieldMutationPayload<
? ModelFieldIsOptional<Schema, Model, Field> extends true
? JsonValue | JsonNull | DbNull
: JsonValue | JsonNull
: MapModelFieldType<Schema, Model, Field>;
: IsTypedJsonField<Schema, Model, Field> extends true
? ModelFieldIsOptional<Schema, Model, Field> extends true
? MapModelFieldType<Schema, Model, Field> | JsonNull | DbNull
: MapModelFieldType<Schema, Model, Field>
: MapModelFieldType<Schema, Model, Field>;

type IsJsonField<
Schema extends SchemaDef,
Model extends GetModels<Schema>,
Field extends GetModelFields<Schema, Model>,
> = GetModelFieldType<Schema, Model, Field> extends 'Json' ? true : false;

type IsTypedJsonField<
Schema extends SchemaDef,
Model extends GetModels<Schema>,
Field extends GetModelFields<Schema, Model>,
> = GetModelFieldType<Schema, Model, Field> extends GetTypeDefs<Schema> ? true : false;

type CreateFKPayload<Schema extends SchemaDef, Model extends GetModels<Schema>> = OptionalWrap<
Schema,
Model,
Expand Down
16 changes: 12 additions & 4 deletions packages/orm/src/client/crud/dialects/base-dialect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,10 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
}

if (isTypeDef(this.schema, fieldDef.type)) {
if (payload instanceof DbNullClass || payload instanceof JsonNullClass || payload instanceof AnyNullClass) {
// null sentinel passed directly (e.g. where: { field: DbNull }) — treat like { equals: sentinel }
return this.buildJsonValueFilterClause(fieldRef, payload);
}
return this.buildJsonFilter(fieldRef, payload, fieldDef);
}

Expand Down Expand Up @@ -1288,25 +1292,29 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
const fieldModel = fieldDef.type as GetModels<Schema>;
let fieldCountQuery: SelectQueryBuilder<any, any, any>;

// Use a unique alias for the subquery to avoid ambiguous references when
// fieldModel === model (self-referential relation on a delegate model)
const subQueryAlias = tmpAlias(`${parentAlias}$_${field}$count`);

// join conditions
const m2m = getManyToManyRelation(this.schema, model, field);
if (m2m) {
// many-to-many relation, count the join table
fieldCountQuery = this.buildModelSelect(fieldModel, fieldModel, value as any, false)
fieldCountQuery = this.buildModelSelect(fieldModel, subQueryAlias, value as any, false)
.innerJoin(m2m.joinTable, (join) =>
join
.onRef(`${m2m.joinTable}.${m2m.otherFkName}`, '=', `${fieldModel}.${m2m.otherPKName}`)
.onRef(`${m2m.joinTable}.${m2m.otherFkName}`, '=', `${subQueryAlias}.${m2m.otherPKName}`)
.onRef(`${m2m.joinTable}.${m2m.parentFkName}`, '=', `${parentAlias}.${m2m.parentPKName}`),
)
.select(eb.fn.countAll().as(`_count$${field}`));
} else {
// build a nested query to count the number of records in the relation
fieldCountQuery = this.buildModelSelect(fieldModel, fieldModel, value as any, false).select(
fieldCountQuery = this.buildModelSelect(fieldModel, subQueryAlias, value as any, false).select(
eb.fn.countAll().as(`_count$${field}`),
);

// join conditions
const joinPairs = buildJoinPairs(this.schema, model, parentAlias, field, fieldModel);
const joinPairs = buildJoinPairs(this.schema, model, parentAlias, field, subQueryAlias);
for (const [left, right] of joinPairs) {
fieldCountQuery = fieldCountQuery.whereRef(left, '=', right);
}
Expand Down
23 changes: 19 additions & 4 deletions packages/orm/src/client/crud/dialects/postgresql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,10 +335,25 @@ export class PostgresCrudDialect<Schema extends SchemaDef> extends LateralJoinDi
}

override buildJsonObject(value: Record<string, Expression<unknown>>) {
return this.eb.fn(
'jsonb_build_object',
Object.entries(value).flatMap(([key, value]) => [sql.lit(key), value]),
);
const entries = Object.entries(value);

// PostgreSQL's FUNC_MAX_ARGS limit is 100. jsonb_build_object takes key-value pairs,
// so at most 50 pairs (100 args) fit in one call. Split larger objects and merge with ||.
const MAX_PAIRS = 50;

const buildChunk = (chunk: [string, Expression<unknown>][]) =>
this.eb.fn('jsonb_build_object', chunk.flatMap(([k, v]) => [sql.lit(k), v]));

if (entries.length <= MAX_PAIRS) {
return buildChunk(entries);
}

const chunks: Expression<unknown>[] = [];
for (let i = 0; i < entries.length; i += MAX_PAIRS) {
chunks.push(buildChunk(entries.slice(i, i + MAX_PAIRS)));
}

return chunks.reduce((acc, chunk) => sql`${acc} || ${chunk}`) as AliasableExpression<unknown>;
}

override castInt<T extends Expression<any>>(expression: T): T {
Expand Down
34 changes: 33 additions & 1 deletion packages/orm/src/client/zod/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -599,14 +599,42 @@ export class ZodSchemaFactory<
candidates.push(this.makeJsonFilterSchema(contextModel, field, optional));

if (optional) {
// allow null as well
// allow null and null sentinel values
candidates.push(z.null());
candidates.push(z.instanceof(DbNullClass));
candidates.push(z.instanceof(JsonNullClass));
candidates.push(z.instanceof(AnyNullClass));
}

// either plain json filter or field filters
return z.union(candidates);
}

// For optional typed JSON fields, allow DbNull, JsonNull, and null.
// z.union doesn't work here because `z.any()` (returned by `makeScalarSchema`)
// always wins, so we create a wrapper superRefine instead.
// The caller must pass the already-built fieldSchema so that array/list
// mutation shapes (set, push, etc.) are preserved.
private makeNullableTypedJsonMutationSchema(fieldSchema: z.ZodTypeAny) {
return z
.any()
.superRefine((value, ctx) => {
if (
value instanceof DbNullClass ||
value instanceof JsonNullClass ||
value === null ||
value === undefined
) {
return;
}
const parseResult = fieldSchema.safeParse(value);
if (!parseResult.success) {
parseResult.error.issues.forEach((issue) => ctx.addIssue(issue as any));
}
})
.optional();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

private isTypeDefType(type: string) {
return this.schema.typeDefs && type in this.schema.typeDefs;
}
Expand Down Expand Up @@ -1309,6 +1337,8 @@ export class ZodSchemaFactory<
if (fieldDef.type === 'Json') {
// DbNull for Json fields
fieldSchema = z.union([fieldSchema, z.instanceof(DbNullClass)]);
} else if (this.isTypeDefType(fieldDef.type)) {
fieldSchema = this.makeNullableTypedJsonMutationSchema(fieldSchema);
} else {
fieldSchema = fieldSchema.nullable();
}
Expand Down Expand Up @@ -1667,6 +1697,8 @@ export class ZodSchemaFactory<
if (fieldDef.type === 'Json') {
// DbNull for Json fields
fieldSchema = z.union([fieldSchema, z.instanceof(DbNullClass)]);
} else if (this.isTypeDefType(fieldDef.type)) {
fieldSchema = this.makeNullableTypedJsonMutationSchema(fieldSchema);
} else {
fieldSchema = fieldSchema.nullable();
}
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/policy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/plugin-policy",
"displayName": "ZenStack Access Policy Plugin",
"description": "ZenStack plugin that enforces access control policies defined in the schema",
"version": "3.5.4",
"version": "3.5.5",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/schema",
"displayName": "ZenStack Schema Object Model",
"description": "TypeScript representation of ZModel schema",
"version": "3.5.4",
"version": "3.5.5",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/sdk",
"displayName": "ZenStack SDK",
"description": "Utilities for building ZenStack plugins",
"version": "3.5.4",
"version": "3.5.5",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
2 changes: 1 addition & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/server",
"displayName": "ZenStack Automatic CRUD Server",
"description": "ZenStack automatic CRUD API handlers and server adapters for popular frameworks",
"version": "3.5.4",
"version": "3.5.5",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
2 changes: 1 addition & 1 deletion packages/testtools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/testtools",
"displayName": "ZenStack Test Tools",
"description": "ZenStack Test Tools",
"version": "3.5.4",
"version": "3.5.5",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
2 changes: 1 addition & 1 deletion packages/zod/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@zenstackhq/zod",
"displayName": "ZenStack Zod Integration",
"description": "Automatically deriving Zod schemas from ZModel schemas",
"version": "3.5.4",
"version": "3.5.5",
"type": "module",
"author": {
"name": "ZenStack Team",
Expand Down
Loading
Loading