Skip to content

Commit 519b557

Browse files
committed
fix error handling
1 parent 9cb3514 commit 519b557

File tree

4 files changed

+76
-83
lines changed

4 files changed

+76
-83
lines changed

packages/runtime/src/client/errors.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
/**
2+
* Base for all ZenStack runtime errors.
3+
*/
4+
export class ZenStackError extends Error {}
5+
16
/**
27
* Error thrown when input validation fails.
38
*/
4-
export class InputValidationError extends Error {
9+
export class InputValidationError extends ZenStackError {
510
constructor(message: string, cause?: unknown) {
611
super(message, { cause });
712
}
@@ -10,7 +15,7 @@ export class InputValidationError extends Error {
1015
/**
1116
* Error thrown when a query fails.
1217
*/
13-
export class QueryError extends Error {
18+
export class QueryError extends ZenStackError {
1419
constructor(message: string, cause?: unknown) {
1520
super(message, { cause });
1621
}
@@ -19,12 +24,12 @@ export class QueryError extends Error {
1924
/**
2025
* Error thrown when an internal error occurs.
2126
*/
22-
export class InternalError extends Error {}
27+
export class InternalError extends ZenStackError {}
2328

2429
/**
2530
* Error thrown when an entity is not found.
2631
*/
27-
export class NotFoundError extends Error {
32+
export class NotFoundError extends ZenStackError {
2833
constructor(model: string, details?: string) {
2934
super(`Entity not found for model "${model}"${details ? `: ${details}` : ''}`);
3035
}

packages/runtime/src/client/executor/zenstack-query-executor.ts

Lines changed: 62 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { match } from 'ts-pattern';
2525
import type { GetModels, SchemaDef } from '../../schema';
2626
import { type ClientImpl } from '../client-impl';
2727
import { TransactionIsolationLevel, type ClientContract } from '../contract';
28-
import { InternalError, QueryError } from '../errors';
28+
import { InternalError, QueryError, ZenStackError } from '../errors';
2929
import { stripAlias } from '../kysely-utils';
3030
import type { AfterEntityMutationCallback, OnKyselyQueryCallback } from '../plugin';
3131
import { QueryNameMapper } from './name-mapper';
@@ -94,7 +94,13 @@ export class ZenStackQueryExecutor<Schema extends SchemaDef> extends DefaultQuer
9494
if (startedTx) {
9595
await this.driver.rollbackTransaction(connection);
9696
}
97-
throw err;
97+
if (err instanceof ZenStackError) {
98+
throw err;
99+
} else {
100+
// wrap error
101+
const message = `Failed to execute query: ${err}, sql: ${compiledQuery?.sql}`;
102+
throw new QueryError(message, err);
103+
}
98104
}
99105
});
100106
}
@@ -162,84 +168,75 @@ export class ZenStackQueryExecutor<Schema extends SchemaDef> extends DefaultQuer
162168
) {
163169
let compiled: CompiledQuery | undefined;
164170

165-
try {
166-
if (this.suppressMutationHooks || !this.isMutationNode(query) || !this.hasEntityMutationPlugins) {
167-
// no need to handle mutation hooks, just proceed
168-
const finalQuery = this.nameMapper.transformNode(query);
169-
compiled = this.compileQuery(finalQuery);
170-
if (parameters) {
171-
compiled = { ...compiled, parameters };
172-
}
173-
return connection.executeQuery<any>(compiled);
174-
}
175-
176-
if (
177-
(InsertQueryNode.is(query) || UpdateQueryNode.is(query)) &&
178-
this.hasEntityMutationPluginsWithAfterMutationHooks
179-
) {
180-
// need to make sure the query node has "returnAll" for insert and update queries
181-
// so that after-mutation hooks can get the mutated entities with all fields
182-
query = {
183-
...query,
184-
returning: ReturningNode.create([SelectionNode.createSelectAll()]),
185-
};
186-
}
171+
if (this.suppressMutationHooks || !this.isMutationNode(query) || !this.hasEntityMutationPlugins) {
172+
// no need to handle mutation hooks, just proceed
187173
const finalQuery = this.nameMapper.transformNode(query);
188174
compiled = this.compileQuery(finalQuery);
189175
if (parameters) {
190176
compiled = { ...compiled, parameters };
191177
}
178+
return connection.executeQuery<any>(compiled);
179+
}
192180

193-
// the client passed to hooks needs to be in sync with current in-transaction
194-
// status so that it doesn't try to create a nested one
195-
const currentlyInTx = this.driver.isTransactionConnection(connection);
181+
if (
182+
(InsertQueryNode.is(query) || UpdateQueryNode.is(query)) &&
183+
this.hasEntityMutationPluginsWithAfterMutationHooks
184+
) {
185+
// need to make sure the query node has "returnAll" for insert and update queries
186+
// so that after-mutation hooks can get the mutated entities with all fields
187+
query = {
188+
...query,
189+
returning: ReturningNode.create([SelectionNode.createSelectAll()]),
190+
};
191+
}
192+
const finalQuery = this.nameMapper.transformNode(query);
193+
compiled = this.compileQuery(finalQuery);
194+
if (parameters) {
195+
compiled = { ...compiled, parameters };
196+
}
196197

197-
const connectionClient = this.createClientForConnection(connection, currentlyInTx);
198+
// the client passed to hooks needs to be in sync with current in-transaction
199+
// status so that it doesn't try to create a nested one
200+
const currentlyInTx = this.driver.isTransactionConnection(connection);
198201

199-
const mutationInfo = this.getMutationInfo(finalQuery);
202+
const connectionClient = this.createClientForConnection(connection, currentlyInTx);
200203

201-
// cache already loaded before-mutation entities
202-
let beforeMutationEntities: Record<string, unknown>[] | undefined;
203-
const loadBeforeMutationEntities = async () => {
204-
if (beforeMutationEntities === undefined && (UpdateQueryNode.is(query) || DeleteQueryNode.is(query))) {
205-
beforeMutationEntities = await this.loadEntities(
206-
mutationInfo.model,
207-
mutationInfo.where,
208-
connection,
209-
);
210-
}
211-
return beforeMutationEntities;
212-
};
204+
const mutationInfo = this.getMutationInfo(finalQuery);
213205

214-
// call before mutation hooks
215-
await this.callBeforeMutationHooks(
216-
finalQuery,
217-
mutationInfo,
218-
loadBeforeMutationEntities,
219-
connectionClient,
220-
queryId,
221-
);
206+
// cache already loaded before-mutation entities
207+
let beforeMutationEntities: Record<string, unknown>[] | undefined;
208+
const loadBeforeMutationEntities = async () => {
209+
if (beforeMutationEntities === undefined && (UpdateQueryNode.is(query) || DeleteQueryNode.is(query))) {
210+
beforeMutationEntities = await this.loadEntities(mutationInfo.model, mutationInfo.where, connection);
211+
}
212+
return beforeMutationEntities;
213+
};
222214

223-
const result = await connection.executeQuery<any>(compiled);
215+
// call before mutation hooks
216+
await this.callBeforeMutationHooks(
217+
finalQuery,
218+
mutationInfo,
219+
loadBeforeMutationEntities,
220+
connectionClient,
221+
queryId,
222+
);
224223

225-
if (!this.driver.isTransactionConnection(connection)) {
226-
// not in a transaction, just call all after-mutation hooks
227-
await this.callAfterMutationHooks(result, finalQuery, mutationInfo, connectionClient, 'all', queryId);
228-
} else {
229-
// run after-mutation hooks that are requested to be run inside tx
230-
await this.callAfterMutationHooks(result, finalQuery, mutationInfo, connectionClient, 'inTx', queryId);
224+
const result = await connection.executeQuery<any>(compiled);
231225

232-
// register other after-mutation hooks to be run after the tx is committed
233-
this.driver.registerTransactionCommitCallback(connection, () =>
234-
this.callAfterMutationHooks(result, finalQuery, mutationInfo, connectionClient, 'outTx', queryId),
235-
);
236-
}
226+
if (!this.driver.isTransactionConnection(connection)) {
227+
// not in a transaction, just call all after-mutation hooks
228+
await this.callAfterMutationHooks(result, finalQuery, mutationInfo, connectionClient, 'all', queryId);
229+
} else {
230+
// run after-mutation hooks that are requested to be run inside tx
231+
await this.callAfterMutationHooks(result, finalQuery, mutationInfo, connectionClient, 'inTx', queryId);
237232

238-
return result;
239-
} catch (err) {
240-
const message = `Failed to execute query: ${err}, sql: ${compiled?.sql}`;
241-
throw new QueryError(message, err);
233+
// register other after-mutation hooks to be run after the tx is committed
234+
this.driver.registerTransactionCommitCallback(connection, () =>
235+
this.callAfterMutationHooks(result, finalQuery, mutationInfo, connectionClient, 'outTx', queryId),
236+
);
242237
}
238+
239+
return result;
243240
}
244241

245242
private createClientForConnection(connection: DatabaseConnection, inTx: boolean) {

packages/runtime/src/plugins/policy/errors.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { ZenStackError } from '../../client';
2+
13
/**
24
* Reason code for policy rejection.
35
*/
@@ -21,7 +23,7 @@ export enum RejectedByPolicyReason {
2123
/**
2224
* Error thrown when an operation is rejected by access policy.
2325
*/
24-
export class RejectedByPolicyError extends Error {
26+
export class RejectedByPolicyError extends ZenStackError {
2527
constructor(
2628
public readonly model: string | undefined,
2729
public readonly reason: RejectedByPolicyReason = RejectedByPolicyReason.NO_ACCESS,

packages/runtime/test/policy/migrated/multi-field-unique.test.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
1-
import path from 'path';
2-
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
3-
import { createPolicyTestClient } from '../utils';
1+
import { describe, expect, it } from 'vitest';
42
import { QueryError } from '../../../src';
3+
import { createPolicyTestClient } from '../utils';
54

65
describe('Policy tests multi-field unique', () => {
7-
let origDir: string;
8-
9-
beforeAll(async () => {
10-
origDir = path.resolve('.');
11-
});
12-
13-
afterEach(() => {
14-
process.chdir(origDir);
15-
});
16-
176
it('toplevel crud test unnamed constraint', async () => {
187
const db = await createPolicyTestClient(
198
`

0 commit comments

Comments
 (0)