diff --git a/packages/orm/src/errors.ts b/packages/orm/src/errors.ts new file mode 100644 index 0000000..e25d3d1 --- /dev/null +++ b/packages/orm/src/errors.ts @@ -0,0 +1,59 @@ +export type SingleRowOperation = "findOne" | "updateOne" | "deleteOne"; + +export class OrmError extends Error { + public override readonly name: string = "OrmError"; + + constructor(message: string) { + super(message); + Object.setPrototypeOf(this, OrmError.prototype); + } +} + +export class NotFoundError extends OrmError { + public override readonly name = "NotFoundError"; + public readonly modelName: string; + public readonly operation: SingleRowOperation; + + constructor( + message: string, + details: { modelName: string; operation: SingleRowOperation }, + ) { + super(message); + this.modelName = details.modelName; + this.operation = details.operation; + Object.setPrototypeOf(this, NotFoundError.prototype); + } +} + +export class TooManyRowsError extends OrmError { + public override readonly name = "TooManyRowsError"; + public readonly modelName: string; + public readonly operation: SingleRowOperation; + public readonly rowCount: number; + + constructor( + message: string, + details: { + modelName: string; + operation: SingleRowOperation; + rowCount: number; + }, + ) { + super(message); + this.modelName = details.modelName; + this.operation = details.operation; + this.rowCount = details.rowCount; + Object.setPrototypeOf(this, TooManyRowsError.prototype); + } +} + +export class CreateFailedError extends OrmError { + public override readonly name = "CreateFailedError"; + public readonly modelName: string; + + constructor(message: string, details: { modelName: string }) { + super(message); + this.modelName = details.modelName; + Object.setPrototypeOf(this, CreateFailedError.prototype); + } +} diff --git a/packages/orm/src/index.ts b/packages/orm/src/index.ts index 11760ab..c5e64a7 100644 --- a/packages/orm/src/index.ts +++ b/packages/orm/src/index.ts @@ -3,6 +3,14 @@ export * from "./operators.js"; export { orm, type Orm } from "./orm.js"; +export { + CreateFailedError, + NotFoundError, + OrmError, + type SingleRowOperation, + TooManyRowsError, +} from "./errors.js"; + export type { Config, FieldDefinition, diff --git a/packages/orm/src/orm.createOne.ts b/packages/orm/src/orm.createOne.ts index b02d10b..fd11308 100644 --- a/packages/orm/src/orm.createOne.ts +++ b/packages/orm/src/orm.createOne.ts @@ -3,6 +3,7 @@ import { ModelDefinitions } from "@casekit/orm-schema"; import { buildCreate } from "./builders/buildCreate.js"; import { Connection } from "./connection.js"; +import { CreateFailedError } from "./errors.js"; import { createToSql } from "./sql/createToSql.js"; import { CreateOneParams } from "./types/CreateOneParams.js"; import { Middleware } from "./types/Middleware.js"; @@ -31,7 +32,9 @@ export const createOne = async ( const result = await tx.query(statement); if (!result.rowCount && query.onConflict?.do !== "nothing") { - throw new Error("createOne failed to create a row"); + throw new CreateFailedError("createOne failed to create a row", { + modelName, + }); } await tx.commit(); diff --git a/packages/orm/src/orm.deleteOne.ts b/packages/orm/src/orm.deleteOne.ts index 36d247c..f5c805d 100644 --- a/packages/orm/src/orm.deleteOne.ts +++ b/packages/orm/src/orm.deleteOne.ts @@ -3,6 +3,7 @@ import { ModelDefinitions, OperatorDefinitions } from "@casekit/orm-schema"; import { buildDelete } from "./builders/buildDelete.js"; import { Connection } from "./connection.js"; +import { NotFoundError, TooManyRowsError } from "./errors.js"; import { deleteToSql } from "./sql/deleteToSql.js"; import { DeleteParams } from "./types/DeleteParams.js"; import { Middleware } from "./types/Middleware.js"; @@ -28,9 +29,19 @@ export const deleteOne = async ( const result = await tx.query(statement); if (!result.rowCount || result.rowCount === 0) { - throw new Error("Delete one failed to delete a row"); + throw new NotFoundError("Delete one failed to delete a row", { + modelName, + operation: "deleteOne", + }); } else if (result.rowCount > 1) { - throw new Error("Delete one would have deleted more than one row"); + throw new TooManyRowsError( + "Delete one would have deleted more than one row", + { + modelName, + operation: "deleteOne", + rowCount: result.rowCount, + }, + ); } await tx.commit(); diff --git a/packages/orm/src/orm.findOne.ts b/packages/orm/src/orm.findOne.ts index db05d8c..7ec255d 100644 --- a/packages/orm/src/orm.findOne.ts +++ b/packages/orm/src/orm.findOne.ts @@ -2,6 +2,7 @@ import { NormalizedConfig } from "@casekit/orm-config"; import { ModelDefinitions, OperatorDefinitions } from "@casekit/orm-schema"; import { Connection } from "./connection.js"; +import { NotFoundError, TooManyRowsError } from "./errors.js"; import { findMany } from "./orm.findMany.js"; import { FindParams } from "./types/FindParams.js"; import { Middleware } from "./types/Middleware.js"; @@ -18,10 +19,17 @@ export const findOne = async ( const results = await findMany(config, conn, middleware, modelName, query); if (results.length === 0) { - throw new Error("Expected one row, but found none"); + throw new NotFoundError("Expected one row, but found none", { + modelName, + operation: "findOne", + }); } if (results.length > 1) { - throw new Error("Expected one row, but found more"); + throw new TooManyRowsError("Expected one row, but found more", { + modelName, + operation: "findOne", + rowCount: results.length, + }); } return results[0]!; }; diff --git a/packages/orm/src/orm.updateOne.ts b/packages/orm/src/orm.updateOne.ts index e5bf4eb..680e0b6 100644 --- a/packages/orm/src/orm.updateOne.ts +++ b/packages/orm/src/orm.updateOne.ts @@ -3,6 +3,7 @@ import { ModelDefinitions, OperatorDefinitions } from "@casekit/orm-schema"; import { buildUpdate } from "./builders/buildUpdate.js"; import { Connection } from "./connection.js"; +import { NotFoundError, TooManyRowsError } from "./errors.js"; import { updateToSql } from "./sql/updateToSql.js"; import { Middleware } from "./types/Middleware.js"; import { UpdateParams } from "./types/UpdateParams.js"; @@ -32,9 +33,19 @@ export const updateOne = async ( const result = await tx.query(statement); if (!result.rowCount || result.rowCount === 0) { - throw new Error("Update one failed to update a row"); + throw new NotFoundError("Update one failed to update a row", { + modelName, + operation: "updateOne", + }); } else if (result.rowCount > 1) { - throw new Error("Update one would have updated more than one row"); + throw new TooManyRowsError( + "Update one would have updated more than one row", + { + modelName, + operation: "updateOne", + rowCount: result.rowCount, + }, + ); } await tx.commit();