Skip to content

Commit ff0808d

Browse files
authored
refactor: revised error system (#377)
* refactor: revised error system * addressing PR comments
1 parent 2946115 commit ff0808d

35 files changed

+434
-769
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ ZenStack is a TypeScript database toolkit for developing full-stack or backend N
3535
- Automatic CRUD web APIs with adapters for popular frameworks (coming soon)
3636
- Automatic [TanStack Query](https://github.com/TanStack/query) hooks for easy CRUD from the frontend (coming soon)
3737

38-
# What's new with V3
38+
# What's New in V3
3939

4040
ZenStack V3 is a major rewrite of [V2](https://github.com/zenstackhq/zenstack). The biggest change is V3 doesn't have a runtime dependency to Prisma anymore. Instead of working as a big wrapper of Prisma as in V2, V3 made a bold move and implemented the entire ORM engine using [Kysely](https://github.com/kysely-org/kysely), while keeping the query API fully compatible with Prisma.
4141

@@ -49,7 +49,7 @@ Even without using advanced features, ZenStack offers the following benefits as
4949

5050
> Although ZenStack v3's ORM runtime doesn't depend on Prisma anymore (specifically, `@prisma/client`), it still relies on Prisma to handle database migration. See [database migration](https://zenstack.dev/docs/3.x/orm/migration) for more details.
5151
52-
# Quick start
52+
# Quick Start
5353

5454
- [ORM](./samples/orm): A simple example demonstrating ZenStack ORM usage.
5555
- [Next.js + TanStack Query](./samples/next.js): A full-stack sample demonstrating using TanStack Query to consume ZenStack's automatic CRUD services in a Next.js app.
@@ -72,7 +72,7 @@ Or, if you have an existing project, use the CLI to initialize it:
7272
npx @zenstackhq/cli@next init
7373
```
7474

75-
### 3. Manual setup
75+
### 3. Setting up manually
7676

7777
Alternatively, you can set it up manually:
7878

packages/language/res/stdlib.zmodel

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,12 +382,14 @@ attribute @map(_ name: String) @@@prisma
382382
attribute @@map(_ name: String) @@@prisma
383383

384384
/**
385-
* Exclude a field from the Prisma Client (for example, a field that you do not want Prisma users to update).
385+
* Exclude a field from the ORM Client (for example, a field that you do not want Prisma users to update).
386+
* The field is still recognized by database schema migrations.
386387
*/
387388
attribute @ignore() @@@prisma
388389

389390
/**
390-
* Exclude a model from the Prisma Client (for example, a model that you do not want Prisma users to update).
391+
* Exclude a model from the ORM Client (for example, a model that you do not want Prisma users to update).
392+
* The model is still recognized by database schema migrations.
391393
*/
392394
attribute @@ignore() @@@prisma
393395

packages/orm/src/client/client-impl.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { FindOperationHandler } from './crud/operations/find';
2929
import { GroupByOperationHandler } from './crud/operations/group-by';
3030
import { UpdateOperationHandler } from './crud/operations/update';
3131
import { InputValidator } from './crud/validator';
32-
import { NotFoundError, QueryError } from './errors';
32+
import { createConfigError, createNotFoundError } from './errors';
3333
import { ZenStackDriver } from './executor/zenstack-driver';
3434
import { ZenStackQueryExecutor } from './executor/zenstack-query-executor';
3535
import * as BuiltinFunctions from './functions';
@@ -223,7 +223,7 @@ export class ClientImpl<Schema extends SchemaDef> {
223223

224224
private async handleProc(name: string, args: unknown[]) {
225225
if (!('procedures' in this.$options) || !this.$options || typeof this.$options.procedures !== 'object') {
226-
throw new QueryError('Procedures are not configured for the client.');
226+
throw createConfigError('Procedures are not configured for the client.');
227227
}
228228

229229
const procOptions = this.$options.procedures as ProceduresOptions<
@@ -389,7 +389,7 @@ function createModelCrudHandler<Schema extends SchemaDef, Model extends GetModel
389389
const _handler = txClient ? handler.withClient(txClient) : handler;
390390
const r = await _handler.handle(operation, _args);
391391
if (!r && throwIfNoResult) {
392-
throw new NotFoundError(model);
392+
throw createNotFoundError(model);
393393
}
394394
let result: unknown;
395395
if (r && postProcess) {

packages/orm/src/client/crud/dialects/base-dialect.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type {
1414
SortOrder,
1515
StringFilter,
1616
} from '../../crud-types';
17-
import { InternalError, QueryError } from '../../errors';
17+
import { createConfigError, createInvalidInputError, createNotSupportedError } from '../../errors';
1818
import type { ClientOptions } from '../../options';
1919
import {
2020
aggregate,
@@ -95,7 +95,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
9595
if (this.supportsDistinctOn) {
9696
result = result.distinctOn(distinct.map((f) => this.eb.ref(`${modelAlias}.${f}`)));
9797
} else {
98-
throw new QueryError(`"distinct" is not supported by "${this.schema.provider.type}" provider`);
98+
throw createNotSupportedError(`"distinct" is not supported by "${this.schema.provider.type}" provider`);
9999
}
100100
}
101101

@@ -482,7 +482,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
482482
}
483483

484484
default: {
485-
throw new InternalError(`Invalid array filter key: ${key}`);
485+
throw createInvalidInputError(`Invalid array filter key: ${key}`);
486486
}
487487
}
488488
}
@@ -510,10 +510,10 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
510510
.with('Bytes', () => this.buildBytesFilter(fieldRef, payload))
511511
// TODO: JSON filters
512512
.with('Json', () => {
513-
throw new InternalError('JSON filters are not supported yet');
513+
throw createNotSupportedError('JSON filters are not supported yet');
514514
})
515515
.with('Unsupported', () => {
516-
throw new QueryError(`Unsupported field cannot be used in filters`);
516+
throw createInvalidInputError(`Unsupported field cannot be used in filters`);
517517
})
518518
.exhaustive()
519519
);
@@ -589,7 +589,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
589589
})
590590
.otherwise(() => {
591591
if (throwIfInvalid) {
592-
throw new QueryError(`Invalid filter key: ${op}`);
592+
throw createInvalidInputError(`Invalid filter key: ${op}`);
593593
} else {
594594
return undefined;
595595
}
@@ -642,7 +642,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
642642
: this.eb(fieldRef, 'like', sql.val(`%${value}`)),
643643
)
644644
.otherwise(() => {
645-
throw new QueryError(`Invalid string filter key: ${key}`);
645+
throw createInvalidInputError(`Invalid string filter key: ${key}`);
646646
});
647647

648648
if (condition) {
@@ -815,7 +815,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
815815
if (fieldDef.array) {
816816
// order by to-many relation
817817
if (typeof value !== 'object') {
818-
throw new QueryError(`invalid orderBy value for field "${field}"`);
818+
throw createInvalidInputError(`invalid orderBy value for field "${field}"`);
819819
}
820820
if ('_count' in value) {
821821
invariant(
@@ -1084,7 +1084,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
10841084
computer = computedFields?.[fieldDef.originModel ?? model]?.[field];
10851085
}
10861086
if (!computer) {
1087-
throw new QueryError(`Computed field "${field}" implementation not provided for model "${model}"`);
1087+
throw createConfigError(`Computed field "${field}" implementation not provided for model "${model}"`);
10881088
}
10891089
return computer(this.eb, { modelAlias });
10901090
}

packages/orm/src/client/crud/dialects/postgresql.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { match } from 'ts-pattern';
1212
import type { BuiltinType, FieldDef, GetModels, SchemaDef } from '../../../schema';
1313
import { DELEGATE_JOINED_FIELD_PREFIX } from '../../constants';
1414
import type { FindArgs } from '../../crud-types';
15-
import { QueryError } from '../../errors';
1615
import type { ClientOptions } from '../../options';
1716
import {
1817
buildJoinPairs,
@@ -24,6 +23,7 @@ import {
2423
requireModel,
2524
} from '../../query-utils';
2625
import { BaseCrudDialect } from './base-dialect';
26+
import { createInternalError } from '../../errors';
2727

2828
export class PostgresCrudDialect<Schema extends SchemaDef> extends BaseCrudDialect<Schema> {
2929
constructor(schema: Schema, options: ClientOptions<Schema>) {
@@ -438,7 +438,7 @@ export class PostgresCrudDialect<Schema extends SchemaDef> extends BaseCrudDiale
438438
override getFieldSqlType(fieldDef: FieldDef) {
439439
// TODO: respect `@db.x` attributes
440440
if (fieldDef.relation) {
441-
throw new QueryError('Cannot get SQL type of a relation field');
441+
throw createInternalError('Cannot get SQL type of a relation field');
442442
}
443443

444444
let result: string;

packages/orm/src/client/crud/dialects/sqlite.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { match } from 'ts-pattern';
1212
import type { BuiltinType, FieldDef, GetModels, SchemaDef } from '../../../schema';
1313
import { DELEGATE_JOINED_FIELD_PREFIX } from '../../constants';
1414
import type { FindArgs } from '../../crud-types';
15-
import { QueryError } from '../../errors';
15+
import { createInternalError } from '../../errors';
1616
import {
1717
getDelegateDescendantModels,
1818
getManyToManyRelation,
@@ -121,7 +121,7 @@ export class SqliteCrudDialect<Schema extends SchemaDef> extends BaseCrudDialect
121121
try {
122122
return JSON.parse(value);
123123
} catch (e) {
124-
throw new QueryError('Invalid JSON returned', e);
124+
throw createInternalError('Invalid JSON returned', undefined, { cause: e });
125125
}
126126
}
127127
return value;
@@ -376,10 +376,10 @@ export class SqliteCrudDialect<Schema extends SchemaDef> extends BaseCrudDialect
376376
override getFieldSqlType(fieldDef: FieldDef) {
377377
// TODO: respect `@db.x` attributes
378378
if (fieldDef.relation) {
379-
throw new QueryError('Cannot get SQL type of a relation field');
379+
throw createInternalError('Cannot get SQL type of a relation field');
380380
}
381381
if (fieldDef.array) {
382-
throw new QueryError('SQLite does not support scalar list type');
382+
throw createInternalError('SQLite does not support scalar list type');
383383
}
384384

385385
if (this.schema.enums?.[fieldDef.type]) {

0 commit comments

Comments
 (0)