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.4.1",
"version": "3.4.2",
"description": "ZenStack",
"packageManager": "pnpm@10.23.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check the actual packageManager field in package.json
echo "Current packageManager value in package.json:"
grep -n '"packageManager"' package.json || echo "Not found"

echo ""
echo "Full package.json for context:"
head -20 package.json

Repository: zenstackhq/zenstack

Length of output: 1141


Pin packageManager to the required pnpm version.

The repository requires pnpm@10.12.1 per coding guidelines, but package.json specifies pnpm@10.23.0. This mismatch can reduce reproducibility between environments.

Proposed fix
-    "packageManager": "pnpm@10.23.0",
+    "packageManager": "pnpm@10.12.1",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"packageManager": "pnpm@10.23.0",
"packageManager": "pnpm@10.12.1",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 5, The packageManager entry in package.json is pinned
to pnpm@10.23.0 but the repo requires pnpm@10.12.1; update the "packageManager"
field value to "pnpm@10.12.1" in package.json (edit the packageManager property)
so the declared pnpm version matches the required one and ensures reproducible
installs.

"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.4.1",
"version": "3.4.2",
"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.4.1",
"version": "3.4.2",
"type": "module",
"author": {
"name": "ZenStack Team"
Expand Down
13 changes: 11 additions & 2 deletions packages/cli/src/actions/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ export async function run(options: Options) {

const dialect = await createDialect(provider, databaseUrl!, outputPath);

const jiti = createJiti(import.meta.url);
const fileUrl = typeof __filename !== 'undefined' ? __filename : import.meta.url;

const jiti = createJiti(fileUrl);

const schemaModule = (await jiti.import(path.join(outputPath, 'schema'))) as any;

Expand All @@ -92,6 +94,7 @@ export async function run(options: Options) {
dialect: dialect,
log: log && log.length > 0 ? log : undefined,
omit: Object.keys(omit).length > 0 ? omit : undefined,
skipValidationForComputedFields: true,
});

// check whether the database is reachable
Expand Down Expand Up @@ -195,7 +198,7 @@ async function createDialect(provider: string, databaseUrl: string, outputPath:
}
}

function startServer(client: ClientContract<any, any>, schema: any, options: Options) {
export function createProxyApp(client: ClientContract<any, any>, schema: any): express.Application {
const app = express();
app.use(cors());
app.use(express.json({ limit: '5mb' }));
Expand All @@ -213,6 +216,12 @@ function startServer(client: ClientContract<any, any>, schema: any, options: Opt
res.json({ ...schema, zenstackVersion: getVersion() });
});

return app;
}

function startServer(client: ClientContract<any, any>, schema: any, options: Options) {
const app = createProxyApp(client, schema);

const server = app.listen(options.port, () => {
console.log(`ZenStack proxy server is running on port: ${options.port}`);
console.log(`You can visit ZenStack Studio at: ${colors.blue('https://studio.zenstack.dev')}`);
Expand Down
170 changes: 170 additions & 0 deletions packages/cli/test/proxy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { createTestClient } from '@zenstackhq/testtools';
import http from 'node:http';
import { afterEach, describe, expect, it } from 'vitest';
import { createProxyApp } from '../src/actions/proxy';

describe('CLI proxy tests', () => {
let server: http.Server | undefined;

afterEach(async () => {
await new Promise<void>((resolve) => {
if (server) {
server.close(() => resolve());
server = undefined;
} else {
resolve();
}
});
});
Comment on lines +7 to +18
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Close test clients in teardown to avoid leaked DB handles.

afterEach closes only the HTTP server. The createTestClient(...) instances should also be disconnected to avoid lingering connections across tests.

Suggested fix
 describe('CLI proxy tests', () => {
     let server: http.Server | undefined;
+    const clients: Array<{ $disconnect: () => Promise<void> }> = [];

     afterEach(async () => {
         await new Promise<void>((resolve) => {
             if (server) {
                 server.close(() => resolve());
                 server = undefined;
             } else {
                 resolve();
             }
         });
+        await Promise.allSettled(clients.map((c) => c.$disconnect()));
+        clients.length = 0;
     });
-        const client = await createTestClient(zmodel);
+        const client = await createTestClient(zmodel);
+        clients.push(client);

(Apply the clients.push(client) addition in each test right after createTestClient(...).)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/test/proxy.test.ts` around lines 7 - 18, The teardown only
closes the HTTP server (variable server) but doesn't disconnect test client
instances created with createTestClient(...), which leaks DB handles; declare
and use a clients array alongside server (e.g., let clients: typeof someClient[]
= []) and after each createTestClient(...) call add the returned client to
clients (clients.push(client)), then update afterEach() to first iterate clients
and call a proper disconnection method on each client (e.g., client.disconnect()
or client.close()) and clear the clients array before/after closing server to
ensure all test clients are torn down.


async function startAt(app: ReturnType<typeof createProxyApp>): Promise<string> {
return new Promise((resolve) => {
server = app.listen(0, () => {
const addr = server!.address() as { port: number };
resolve(`http://localhost:${addr.port}`);
});
});
}

it('should serve schema at /api/schema endpoint', async () => {
const zmodel = `
model User {
id String @id @default(cuid())
email String @unique
}
`;

const client = await createTestClient(zmodel);
const app = createProxyApp(client, client.$schema);
const baseUrl = await startAt(app);

const r = await fetch(`${baseUrl}/api/schema`);
expect(r.status).toBe(200);

const body = await r.json();
// schema fields are present
expect(body).toHaveProperty('models');
expect(body.models).toHaveProperty('User');
expect(body).toHaveProperty('provider');
// zenstackVersion is injected by the proxy; when running tests directly
// from source (no built dist/) getVersion() returns undefined and the
// key is omitted from JSON — tolerate that, but if present it must be a string.
if ('zenstackVersion' in body) {
expect(typeof body.zenstackVersion).toBe('string');
}
});

it('should omit computed fields from default query responses', async () => {
// postCount is a @computed field — the proxy must not try to SELECT it
// by default (it has no backing column in the DB).
const zmodel = `
model User {
id String @id @default(cuid())
name String
postCount Int @computed
}
`;

// Mirror what proxy.ts does: build omit config from the schema, then
// create the client with skipValidationForComputedFields.
const client = await createTestClient(zmodel, {
skipValidationForComputedFields: true,
omit: { User: { postCount: true } },
});

const app = createProxyApp(client, client.$schema);
const baseUrl = await startAt(app);

// Create a user via the proxy API.
const createRes = await fetch(`${baseUrl}/api/model/user/create`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: { name: 'Alice' } }),
});
expect(createRes.status).toBe(201);
const created = await createRes.json();

// The regular fields should be present …
expect(created.data).toHaveProperty('id');
expect(created.data).toHaveProperty('name', 'Alice');
// … but the computed field must be absent in the default response.
expect(created.data).not.toHaveProperty('postCount');

// A findMany should behave the same way.
const listRes = await fetch(`${baseUrl}/api/model/user/findMany`);
expect(listRes.status).toBe(200);
const list = await listRes.json();
expect(list.data).toHaveLength(1);
expect(list.data[0]).not.toHaveProperty('postCount');
});

it('should handle sequential transaction calls', async () => {
const zmodel = `
model User {
id String @id @default(cuid())
email String @unique
posts Post[]

@@allow('all', true)
}

model Post {
id String @id @default(cuid())
title String
author User? @relation(fields: [authorId], references: [id])
authorId String?

@@allow('all', true)
}
`;

const client = await createTestClient(zmodel);
const app = createProxyApp(client, client.$schema);
const baseUrl = await startAt(app);

const txRes = await fetch(`${baseUrl}/api/model/$transaction/sequential`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify([
{
model: 'User',
op: 'create',
args: { data: { id: 'u1', email: 'alice@example.com' } },
},
{
model: 'Post',
op: 'create',
args: { data: { id: 'p1', title: 'Hello World', authorId: 'u1' } },
},
{
model: 'Post',
op: 'findMany',
args: { where: { authorId: 'u1' } },
},
]),
});
expect(txRes.status).toBe(200);
const tx = await txRes.json();

// Should return results for each operation in the transaction.
expect(Array.isArray(tx.data)).toBe(true);
expect(tx.data).toHaveLength(3);

// First result: created user
expect(tx.data[0]).toMatchObject({ id: 'u1', email: 'alice@example.com' });
// Second result: created post
expect(tx.data[1]).toMatchObject({ id: 'p1', title: 'Hello World', authorId: 'u1' });
// Third result: findMany — should find the newly created post
expect(Array.isArray(tx.data[2])).toBe(true);
expect(tx.data[2]).toHaveLength(1);
expect(tx.data[2][0]).toMatchObject({ id: 'p1', title: 'Hello World' });

// Confirm persisted outside transaction too.
const userRes = await fetch(
`${baseUrl}/api/model/user/findUnique?q=${encodeURIComponent(JSON.stringify({ where: { id: 'u1' } }))}`,
);
expect(userRes.status).toBe(200);
const user = await userRes.json();
expect(user.data).toMatchObject({ id: 'u1', email: 'alice@example.com' });
});
});
5 changes: 5 additions & 0 deletions packages/cli/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@ export default defineConfig({
clean: true,
dts: true,
format: ['esm', 'cjs'],
esbuildOptions: (options) => {
options.logOverride = {
'empty-import-meta': 'silent',
};
},
});
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.4.1",
"version": "3.4.2",
"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.4.1",
"version": "3.4.2",
"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.4.1",
"version": "3.4.2",
"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.4.1",
"version": "3.4.2",
"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.4.1",
"version": "3.4.2",
"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.4.1",
"version": "3.4.2",
"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.4.1",
"version": "3.4.2",
"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.4.1",
"version": "3.4.2",
"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.4.1",
"version": "3.4.2",
"license": "MIT",
"author": "ZenStack Team",
"files": [
Expand Down
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.4.1",
"version": "3.4.2",
"description": "ZenStack ORM",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/orm/src/client/client-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class ClientImpl {
...this.$options.functions,
};

if (!baseClient) {
if (!baseClient && !options.skipValidationForComputedFields) {
// validate computed fields configuration once for the root client
this.validateComputedFieldsConfig();
}
Expand Down
5 changes: 5 additions & 0 deletions packages/orm/src/client/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@ export type ClientOptions<Schema extends SchemaDef> = QueryOptions<Schema> & {
* Defaults to `true`.
*/
useCompactAliasNames?: boolean;

/**
* Whether to skip validation for computed fields.
*/
skipValidationForComputedFields?: boolean;
} & (HasComputedFields<Schema> extends true
? {
/**
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.4.1",
"version": "3.4.2",
"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.4.1",
"version": "3.4.2",
"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.4.1",
"version": "3.4.2",
"description": "ZenStack SDK",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/server",
"version": "3.4.1",
"version": "3.4.2",
"description": "ZenStack automatic CRUD API handlers and server adapters",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/testtools/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/testtools",
"version": "3.4.1",
"version": "3.4.2",
"description": "ZenStack Test Tools",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/zod/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/zod",
"version": "3.4.1",
"version": "3.4.2",
"description": "ZenStack Zod integration",
"type": "module",
"scripts": {
Expand Down
Loading
Loading