From aec69f7342148b073e32fcefe927241f7424fd5a Mon Sep 17 00:00:00 2001 From: Romain Lanz Date: Thu, 5 Oct 2023 22:43:48 +0200 Subject: [PATCH 1/2] feat(errors): use new way to define exceptions --- index.ts | 1 + src/connection/index.ts | 12 ++--- src/connection/manager.ts | 7 +-- src/database/query_builder/database.ts | 9 ++-- src/errors.ts | 74 ++++++++++++++++++++++++++ src/migration/runner.ts | 27 +++++----- src/orm/adapter/index.ts | 2 +- src/orm/base_model/index.ts | 44 ++++++++------- src/orm/decorators/date.ts | 35 +++++------- src/orm/decorators/date_time.ts | 35 +++++------- src/orm/preloader/index.ts | 17 ++---- src/orm/query_builder/index.ts | 33 +++++------- src/orm/relations/keys_extractor.ts | 10 +--- src/utils/index.ts | 10 ++-- 14 files changed, 171 insertions(+), 145 deletions(-) create mode 100644 src/errors.ts diff --git a/index.ts b/index.ts index c0d9fb6e..4d39b223 100644 --- a/index.ts +++ b/index.ts @@ -7,6 +7,7 @@ * file that was distributed with this source code. */ +export * as errors from './src/errors.js' export { configure } from './configure.js' export { stubsRoot } from './stubs/main.js' export { defineConfig } from './src/define_config.js' diff --git a/src/connection/index.ts b/src/connection/index.ts index 6ccce980..de5bec94 100644 --- a/src/connection/index.ts +++ b/src/connection/index.ts @@ -10,7 +10,6 @@ import { Pool } from 'tarn' import knex, { Knex } from 'knex' import { EventEmitter } from 'node:events' -import { Exception } from '@poppinss/utils' import { patchKnex } from 'knex-dynamic-connection' import type { Logger } from '@adonisjs/core/logger' // @ts-expect-error @@ -18,6 +17,7 @@ import { resolveClientNameWithAliases } from 'knex/lib/util/helpers.js' import { ConnectionConfig, ConnectionContract, ReportNode } from '../types/database.js' import { Logger as ConnectionLogger } from './logger.js' +import * as errors from '../errors.js' /** * Connection class manages a given database connection. Internally it uses @@ -81,17 +81,11 @@ export class Connection extends EventEmitter implements ConnectionContract { private validateConfig(): void { if (this.config.replicas) { if (!this.config.replicas.read || !this.config.replicas.write) { - throw new Exception('Make sure to define read/write replicas or use connection property', { - code: 'E_INCOMPLETE_REPLICAS_CONFIG', - status: 500, - }) + throw new errors.E_INCOMPLETE_REPLICAS_CONFIG() } if (!this.config.replicas.read.connection || !this.config.replicas.read.connection) { - throw new Exception('Make sure to define connection property inside read/write replicas', { - status: 500, - code: 'E_INVALID_REPLICAS_CONFIG', - }) + throw new errors.E_INVALID_REPLICAS_CONFIG() } } } diff --git a/src/connection/manager.ts b/src/connection/manager.ts index 42a68238..1d501b2b 100644 --- a/src/connection/manager.ts +++ b/src/connection/manager.ts @@ -7,7 +7,6 @@ * file that was distributed with this source code. */ -import { Exception } from '@poppinss/utils' import type { Emitter } from '@adonisjs/core/events' import type { Logger } from '@adonisjs/core/logger' @@ -20,6 +19,7 @@ import { } from '../types/database.js' import { Connection } from './index.js' +import * as errors from '../errors.js' /** * Connection manager job is to manage multiple named connections. You can add any number @@ -126,10 +126,7 @@ export class ConnectionManager implements ConnectionManagerContract { connect(connectionName: string): void { const connection = this.connections.get(connectionName) if (!connection) { - throw new Exception(`Cannot connect to unregistered connection ${connectionName}`, { - code: 'E_UNMANAGED_DB_CONNECTION', - status: 500, - }) + throw new errors.E_UNMANAGED_DB_CONNECTION([connectionName]) } /** diff --git a/src/database/query_builder/database.ts b/src/database/query_builder/database.ts index 1dc477d5..8daaae16 100644 --- a/src/database/query_builder/database.ts +++ b/src/database/query_builder/database.ts @@ -19,6 +19,7 @@ import { DBQueryCallback, DatabaseQueryBuilderContract } from '../../types/query import { Chainable } from './chainable.js' import { QueryRunner } from '../../query_runner/index.js' import { SimplePaginator } from '../paginator/simple_paginator.js' +import * as errors from '../../errors.js' /** * Wrapping the user function for a query callback and give them @@ -29,7 +30,7 @@ const queryCallback: DBQueryCallback = (userFn, keysResolver) => { return (builder: Knex.QueryBuilder) => { /** * Sub queries don't need the client, since client is used to execute the query - * and subqueries are not executed seperately. That's why we just pass + * and sub-queries are not executed separately. That's why we just pass * an empty object. * * Other option is to have this method for each instance of the class, but this @@ -198,7 +199,7 @@ export class DatabaseQueryBuilder extends Chainable implements DatabaseQueryBuil async firstOrFail(): Promise { const row = await this.first() if (!row) { - throw new Exception('Row not found', { status: 404, code: 'E_ROW_NOT_FOUND' }) + throw new errors.E_ROW_NOT_FOUND() } return row @@ -330,8 +331,8 @@ export class DatabaseQueryBuilder extends Chainable implements DatabaseQueryBuil /** * Implementation of `finally` for the promise API */ - finally(fullfilled: any) { - return this.exec().finally(fullfilled) + finally(fulfilled: any) { + return this.exec().finally(fulfilled) } /** diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 00000000..ba217231 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,74 @@ +/* + * @adonisjs/lucid + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { createError } from '@poppinss/utils' + +export const E_INVALID_DATE_COLUMN_VALUE = createError<[string, string | null]>( + 'Invalid value for "%s". %s', + 'E_INVALID_DATE_COLUMN_VALUE', + 500 +) + +export const E_UNMANAGED_DB_CONNECTION = createError<[string]>( + 'Cannot connect to unregistered connection %s', + 'E_UNMANAGED_DB_CONNECTION', + 500 +) + +export const E_MISSING_MODEL_ATTRIBUTE = createError<[string, string, string]>( + '"%s" expects "%s" to exist on "%s" model, but is missing', + 'E_MISSING_MODEL_ATTRIBUTE', + 500 +) + +export const E_INCOMPLETE_REPLICAS_CONFIG = createError( + 'Make sure to define read/write replicas or use connection property', + 'E_INCOMPLETE_REPLICAS_CONFIG', + 500 +) + +export const E_INVALID_REPLICAS_CONFIG = createError( + 'Make sure to define connection property inside read/write replicas', + 'E_INVALID_REPLICAS_CONFIG', + 500 +) + +export const E_MODEL_DELETED = createError( + 'Cannot mutate delete model instance', + 'E_MODEL_DELETED', + 500 +) + +export const E_ROW_NOT_FOUND = createError('Row not found', 'E_ROW_NOT_FOUND', 404) + +export const E_UNABLE_ACQUIRE_LOCK = createError( + 'Unable to acquire lock. Concurrent migrations are not allowed', + 'E_UNABLE_ACQUIRE_LOCK', + 500 +) + +export const E_UNABLE_RELEASE_LOCK = createError( + 'Migration completed, but unable to release database lock', + 'E_UNABLE_RELEASE_LOCK', + 500 +) + +export const E_MISSING_SCHEMA_FILES = createError( + 'Cannot perform rollback. Schema file "%s" is missing', + 'E_MISSING_SCHEMA_FILES', + 500 +) + +export const E_UNDEFINED_RELATIONSHIP = createError( + '"%s" is not defined as a relationship on "%s" model', + 'E_UNDEFINED_RELATIONSHIP', + 500 +) + +export const E_RUNTIME_EXCEPTION = createError('%s', 'E_RUNTIME_EXCEPTION', 500) diff --git a/src/migration/runner.ts b/src/migration/runner.ts index 4c65b7aa..fe902cf6 100644 --- a/src/migration/runner.ts +++ b/src/migration/runner.ts @@ -9,7 +9,6 @@ import slash from 'slash' import { EventEmitter } from 'node:events' -import { Exception } from '@poppinss/utils' import { MigratorOptions, MigratedFileNode, MigrationListNode } from '../types/migrator.js' import { @@ -24,6 +23,7 @@ import { MigrationSource } from './source.js' import { Database } from '../database/main.js' import { Application } from '@adonisjs/core/app' import { BaseSchema } from '../schema/main.js' +import * as errors from '../errors.js' /** * Migrator exposes the API to execute migrations using the schema files @@ -45,7 +45,7 @@ export class MigrationRunner extends EventEmitter { private schemaVersionsTableName: string /** - * Whether or not the migrator has been booted + * Whether the migrator has been booted */ private booted: boolean = false @@ -254,7 +254,7 @@ export class MigrationRunner extends EventEmitter { /** * Acquires a lock to disallow concurrent transactions. Only works with - * `Mysql`, `PostgreSQL` and `MariaDb` for now. + * `Mysql`, `PostgresSQL` and `MariaDb` for now. * * Make sure we are acquiring lock outside the transactions, since we want * to block other processes from acquiring the same lock. @@ -269,14 +269,14 @@ export class MigrationRunner extends EventEmitter { const acquired = await this.client.dialect.getAdvisoryLock(1) if (!acquired) { - throw new Exception('Unable to acquire lock. Concurrent migrations are not allowed') + throw new errors.E_UNABLE_ACQUIRE_LOCK() } this.emit('acquire:lock') } /** * Release a lock once complete the migration process. Only works with - * `Mysql`, `PostgreSQL` and `MariaDb` for now. + * `Mysql`, `PostgresSQL` and `MariaDb` for now. */ private async releaseLock() { if (!this.client.dialect.supportsAdvisoryLocks || this.disableLocks) { @@ -285,7 +285,7 @@ export class MigrationRunner extends EventEmitter { const released = await this.client.dialect.releaseAdvisoryLock(1) if (!released) { - throw new Exception('Migration completed, but unable to release database lock') + throw new errors.E_UNABLE_RELEASE_LOCK() } this.emit('release:lock') } @@ -293,7 +293,7 @@ export class MigrationRunner extends EventEmitter { /** * Makes the migrations table (if missing). Also created in dry run, since * we always reads from the schema table to find which migrations files to - * execute and that cannot done without missing table. + * execute and that cannot be done without missing table. */ private async makeMigrationsTable() { const hasTable = await this.client.schema.hasTable(this.schemaTableName) @@ -332,7 +332,7 @@ export class MigrationRunner extends EventEmitter { } /** - * Returns the latest migrations version. If no rows exists + * Returns the latest migrations version. If no rows exist * it inserts a new row for version 1 */ private async getLatestVersion() { @@ -349,7 +349,7 @@ export class MigrationRunner extends EventEmitter { /** * Upgrade migrations name from version 1 to version 2 */ - private async upgradeFromOnetoTwo() { + private async upgradeFromOneToTwo() { const migrations = await this.getMigratedFilesTillBatch(0) const client = await this.getClient(false) @@ -368,7 +368,7 @@ export class MigrationRunner extends EventEmitter { await client.from(this.schemaVersionsTableName).where('version', 1).update({ version: 2 }) await this.commit(client) } catch (error) { - this.rollback(client) + await this.rollback(client) throw error } } @@ -379,7 +379,7 @@ export class MigrationRunner extends EventEmitter { private async upgradeVersion(latestVersion: number): Promise { if (latestVersion === 1) { this.emit('upgrade:version', { from: 1, to: 2 }) - await this.upgradeFromOnetoTwo() + await this.upgradeFromOneToTwo() } } @@ -488,10 +488,7 @@ export class MigrationRunner extends EventEmitter { existing.forEach((file) => { const migration = collected.find(({ name }) => name === file.name) if (!migration) { - throw new Exception(`Cannot perform rollback. Schema file {${file.name}} is missing`, { - status: 500, - code: 'E_MISSING_SCHEMA_FILES', - }) + throw new errors.E_MISSING_SCHEMA_FILES([file.name]) } this.migratedFiles[migration.name] = { diff --git a/src/orm/adapter/index.ts b/src/orm/adapter/index.ts index 4751cce4..675d56fb 100644 --- a/src/orm/adapter/index.ts +++ b/src/orm/adapter/index.ts @@ -44,7 +44,7 @@ export class Adapter implements AdapterContract { } /** - * Returns query client for a model instance by inspecting it's options + * Returns query client for a model instance by inspecting its options */ modelClient(instance: LucidRow): any { const modelConstructor = instance.constructor as unknown as LucidModel diff --git a/src/orm/base_model/index.ts b/src/orm/base_model/index.ts index ec525bc0..8b3d6e8e 100644 --- a/src/orm/base_model/index.ts +++ b/src/orm/base_model/index.ts @@ -63,6 +63,7 @@ import { } from '../../utils/index.js' import { SnakeCaseNamingStrategy } from '../naming_strategies/snake_case.js' import { LazyLoadAggregates } from '../relations/aggregates_loader/lazy_load.js' +import * as errors from '../../errors.js' const MANY_RELATIONS = ['hasMany', 'manyToMany', 'hasManyThrough'] const DATE_TIME_TYPES = { @@ -106,7 +107,7 @@ class BaseModelImpl implements LucidRow { static primaryKey: string /** - * Whether or not the model has been booted. Booting the model initializes it's + * Whether the model has been booted. Booting the model initializes its * static properties. Base models must not be initialized. */ static booted: boolean @@ -307,7 +308,7 @@ class BaseModelImpl implements LucidRow { } /** - * Set column as the primary column, when `primary` is to true + * Set column as the primary column, when `primary` is true */ if (column.isPrimary) { this.primaryKey = name @@ -401,7 +402,7 @@ class BaseModelImpl implements LucidRow { } /** - * Register many to many relationship + * Register many-to-many relationship */ protected static $addManyToMany( name: string, @@ -412,7 +413,7 @@ class BaseModelImpl implements LucidRow { } /** - * Register many to many relationship + * Register has many through relationship */ protected static $addHasManyThrough( name: string, @@ -533,7 +534,7 @@ class BaseModelImpl implements LucidRow { this.$defineProperty('selfAssignPrimaryKey', false, 'inherit') /** - * Define the keys property. This allows looking up variations + * Define the keys' property. This allows looking up variations * for model keys */ this.$defineProperty( @@ -927,7 +928,7 @@ class BaseModelImpl implements LucidRow { ) /** - * Perist inside db inside a transaction + * Persist inside db inside a transaction */ await managedTransaction(query.client, async (trx) => { for (let row of rows) { @@ -1033,7 +1034,7 @@ class BaseModelImpl implements LucidRow { /** * The transaction listener listens for the `commit` and `rollback` events and - * cleansup the `$trx` reference + * cleanup the `$trx` reference */ private transactionListener = function listener(this: BaseModelImpl) { this.modelTrx = undefined @@ -1061,10 +1062,7 @@ class BaseModelImpl implements LucidRow { */ private ensureIsntDeleted() { if (this.$isDeleted) { - throw new Exception('Cannot mutate delete model instance', { - status: 500, - code: 'E_MODEL_DELETED', - }) + throw new errors.E_MODEL_DELETED() } } @@ -1203,7 +1201,7 @@ class BaseModelImpl implements LucidRow { /** * Extras are dynamic properties set on the model instance, which - * are not serialized and neither casted for adapter calls. + * are not serialized and neither cast for adapter calls. * * This is helpful when adapter wants to load some extra data conditionally * and that data must not be persisted back the adapter. @@ -1211,18 +1209,18 @@ class BaseModelImpl implements LucidRow { $extras: ModelObject = {} /** - * Sideloaded are dynamic properties set on the model instance, which - * are not serialized and neither casted for adapter calls. + * Side-loaded are dynamic properties set on the model instance, which + * are not serialized and neither cast for adapter calls. * - * This is helpful when you want to add dynamic meta data to the model + * This is helpful when you want to add dynamic metadata to the model * and it's children as well. * * The difference between [[extras]] and [[sideloaded]] is: * * - Extras can be different for each model instance * - Extras are not shared down the hierarchy (example relationships) - * - Sideloaded are shared across multiple model instances created via `$createMultipleFromAdapterResult`. - * - Sideloaded are passed to the relationships as well. + * - Side-loaded are shared across multiple model instances created via `$createMultipleFromAdapterResult`. + * - Side-loaded are passed to the relationships as well. */ $sideloaded: ModelObject = {} @@ -1535,7 +1533,7 @@ class BaseModelImpl implements LucidRow { } /** - * Dis-allow setting multiple model instances for a one to one relationship + * Dis-allow setting multiple model instances for a one-to-one relationship */ if (Array.isArray(models)) { throw new Error( @@ -1666,7 +1664,7 @@ class BaseModelImpl implements LucidRow { /** * Resolve the attribute name from the column names. Since people - * usaully define the column names directly as well by + * usually define the column names directly as well by * accepting them directly from the API. */ const attributeName = Model.$keys.columnsToAttributes.get(key) @@ -1791,7 +1789,7 @@ class BaseModelImpl implements LucidRow { const Model = this.constructor as typeof BaseModel /** - * Persit the model when it's not persisted already + * Persist the model when it's not persisted already */ if (!this.$isPersisted) { await Model.$hooks.runner('before:create').run(this) @@ -1809,7 +1807,7 @@ class BaseModelImpl implements LucidRow { } /** - * Call hooks before hand, so that they have the chance + * Call hooks beforehand, so that they have the chance * to make mutations that produces one or more `$dirty` * fields. */ @@ -1896,7 +1894,7 @@ class BaseModelImpl implements LucidRow { /** * Serializes relationships to a plain object. When `raw=true`, it will - * recurisvely serialize the relationships as well. + * recursively serialize the relationships as well. */ serializeRelations( cherryPick?: CherryPick['relations'], @@ -1925,7 +1923,7 @@ class BaseModelImpl implements LucidRow { } /** - * Always make sure we passing a valid object or undefined + * Always make sure we are passing a valid object or undefined * to the relationships */ const relationOptions = cherryPick ? cherryPick[relation.serializeAs] : undefined diff --git a/src/orm/decorators/date.ts b/src/orm/decorators/date.ts index b498730f..43c172e9 100644 --- a/src/orm/decorators/date.ts +++ b/src/orm/decorators/date.ts @@ -8,7 +8,7 @@ */ import { DateTime } from 'luxon' -import { Exception } from '@poppinss/utils' +import * as errors from '../../errors.js' import { LucidRow, LucidModel, DateColumnDecorator } from '../../types/model.js' /** @@ -31,13 +31,10 @@ function prepareDateColumn(value: any, attributeName: string, modelInstance: Luc */ if (DateTime.isDateTime(value)) { if (!value.isValid) { - throw new Exception( - `Invalid value for "${modelName}.${attributeName}". ${value.invalidReason}`, - { - status: 500, - code: 'E_INVALID_DATE_COLUMN_VALUE', - } - ) + throw new errors.E_INVALID_DATE_COLUMN_VALUE([ + `${modelName}.${attributeName}`, + value.invalidReason, + ]) } return value.toISODate() @@ -46,13 +43,10 @@ function prepareDateColumn(value: any, attributeName: string, modelInstance: Luc /** * Anything else if not an acceptable value for date column */ - throw new Exception( - `The value for "${modelName}.${attributeName}" must be an instance of "luxon.DateTime"`, - { - status: 500, - code: 'E_INVALID_DATE_COLUMN_VALUE', - } - ) + throw new errors.E_INVALID_DATE_COLUMN_VALUE([ + `${modelName}.${attributeName}`, + 'The value must be an instance of "luxon.DateTime"', + ]) } /** @@ -84,13 +78,10 @@ function consumeDateColumn(value: any, attributeName: string, modelInstance: Luc * Any another value cannot be formatted */ const modelName = modelInstance.constructor.name - throw new Exception( - `Cannot format "${modelName}.${attributeName}" ${typeof value} value to an instance of "luxon.DateTime"`, - { - status: 500, - code: 'E_INVALID_DATE_COLUMN_VALUE', - } - ) + throw new errors.E_INVALID_DATE_COLUMN_VALUE([ + `${modelName}.${attributeName}`, + `${typeof value} cannot be formatted`, + ]) } /** diff --git a/src/orm/decorators/date_time.ts b/src/orm/decorators/date_time.ts index 8181cdc8..ca617056 100644 --- a/src/orm/decorators/date_time.ts +++ b/src/orm/decorators/date_time.ts @@ -8,8 +8,8 @@ */ import { DateTime } from 'luxon' -import { Exception } from '@poppinss/utils' import { LucidRow, LucidModel, DateTimeColumnDecorator } from '../../types/model.js' +import * as errors from '../../errors.js' /** * The method to prepare the datetime column before persisting it's @@ -32,13 +32,10 @@ function prepareDateTimeColumn(value: any, attributeName: string, modelInstance: */ if (DateTime.isDateTime(value)) { if (!value.isValid) { - throw new Exception( - `Invalid value for "${modelName}.${attributeName}". ${value.invalidReason}`, - { - status: 500, - code: 'E_INVALID_DATETIME_COLUMN_VALUE', - } - ) + throw new errors.E_INVALID_DATE_COLUMN_VALUE([ + `${modelName}.${attributeName}`, + value.invalidReason, + ]) } const dateTimeFormat = model.query(modelInstance.$options).client.dialect.dateTimeFormat @@ -48,13 +45,10 @@ function prepareDateTimeColumn(value: any, attributeName: string, modelInstance: /** * Anything else if not an acceptable value for date column */ - throw new Exception( - `The value for "${modelName}.${attributeName}" must be an instance of "luxon.DateTime"`, - { - status: 500, - code: 'E_INVALID_DATETIME_COLUMN_VALUE', - } - ) + throw new errors.E_INVALID_DATE_COLUMN_VALUE([ + `${modelName}.${attributeName}`, + 'It must be an instance of "luxon.DateTime', + ]) } /** @@ -86,13 +80,10 @@ function consumeDateTimeColumn(value: any, attributeName: string, modelInstance: * Any another value cannot be formatted */ const modelName = modelInstance.constructor.name - throw new Exception( - `Cannot format "${modelName}.${attributeName}" ${typeof value} value to an instance of "luxon.DateTime"`, - { - status: 500, - code: 'E_INVALID_DATETIME_COLUMN_VALUE', - } - ) + throw new errors.E_INVALID_DATE_COLUMN_VALUE([ + `${modelName}.${attributeName}`, + `${typeof value} cannot be formatted`, + ]) } /** diff --git a/src/orm/preloader/index.ts b/src/orm/preloader/index.ts index 2b8b9391..650d3aa7 100644 --- a/src/orm/preloader/index.ts +++ b/src/orm/preloader/index.ts @@ -7,8 +7,6 @@ * file that was distributed with this source code. */ -import { Exception } from '@poppinss/utils' - import { LucidRow, LucidModel, ModelObject } from '../../types/model.js' import { @@ -18,6 +16,7 @@ import { } from '../../types/relations.js' import { QueryClientContract } from '../../types/database.js' +import * as errors from '../../errors.js' /** * Exposes the API to define and preload relationships in reference to @@ -32,7 +31,7 @@ export class Preloader implements PreloaderContract { } = {} /** - * When invoked via query builder. The preloader will get the sideloaded + * When invoked via query builder. The preloader will get the side-loaded * object, that should be transferred to relationship model instances. */ private sideloaded: ModelObject = {} @@ -76,7 +75,7 @@ export class Preloader implements PreloaderContract { /** * Process a given relationship for many parent instances. This happens - * during eagerloading + * during eager-loading */ private async processRelationForMany( name: string, @@ -110,13 +109,7 @@ export class Preloader implements PreloaderContract { load(name: any, callback?: any): this { const relation = this.model.$getRelation(name) as RelationshipsContract if (!relation) { - throw new Exception( - `"${name}" is not defined as a relationship on "${this.model.name}" model`, - { - status: 500, - code: 'E_UNDEFINED_RELATIONSHIP', - } - ) + throw new errors.E_UNDEFINED_RELATIONSHIP([name, this.model.name]) } relation.boot() @@ -145,7 +138,7 @@ export class Preloader implements PreloaderContract { /** * Define attributes to be passed to all the model instance as - * sideloaded attributes + * side-loaded attributes */ sideload(values: ModelObject): this { this.sideloaded = values diff --git a/src/orm/query_builder/index.ts b/src/orm/query_builder/index.ts index a8dd50b0..a4a3fe50 100644 --- a/src/orm/query_builder/index.ts +++ b/src/orm/query_builder/index.ts @@ -38,6 +38,7 @@ import { ModelPaginator } from '../paginator/index.js' import { QueryRunner } from '../../query_runner/index.js' import { Chainable } from '../../database/query_builder/chainable.js' import { SimplePaginator } from '../../database/paginator/simple_paginator.js' +import * as errors from '../../errors.js' /** * A wrapper to invoke scope methods on the query builder @@ -73,7 +74,7 @@ export class ModelQueryBuilder implements ModelQueryBuilderContract { /** - * Sideloaded attributes that will be passed to the model instances + * Side-loaded attributes that will be passed to the model instances */ protected sideloaded: ModelObject = {} @@ -100,7 +101,7 @@ export class ModelQueryBuilder private scopesWrapper: ModelScopes | undefined = undefined /** - * Control whether or not to wrap adapter result to model + * Control whether to wrap adapter result to model * instances or not */ protected wrapResultsToModelInstances: boolean = true @@ -129,7 +130,7 @@ export class ModelQueryBuilder clientOptions: ModelAdapterOptions /** - * Whether or not query is a subquery for `.where` callback + * Whether query is a sub-query for `.where` callback */ isChildQuery = false @@ -231,7 +232,7 @@ export class ModelQueryBuilder } /** - * Defines sub query for checking the existance of a relationship + * Defines sub query for checking the existence of a relationship */ private addWhereHas( relationName: any, @@ -325,13 +326,7 @@ export class ModelQueryBuilder * Ensure relationship exists */ if (!relation) { - throw new Exception( - `"${name}" is not defined as a relationship on "${this.model.name}" model`, - { - status: 500, - code: 'E_UNDEFINED_RELATIONSHIP', - } - ) + throw new errors.E_UNDEFINED_RELATIONSHIP([name, this.model.name]) } relation.boot() @@ -450,7 +445,7 @@ export class ModelQueryBuilder } /** - * Set sideloaded properties to be passed to the model instance + * Set side-loaded properties to be passed to the model instance */ sideload(value: ModelObject) { this.sideloaded = value @@ -485,14 +480,14 @@ export class ModelQueryBuilder async firstOrFail(): Promise { const row = await this.first() if (!row) { - throw new Exception('Row not found', { status: 404, code: 'E_ROW_NOT_FOUND' }) + throw new errors.E_ROW_NOT_FOUND() } return row } /** - * Load aggregate value as a subquery for a relationship + * Load aggregate value as a sub-query for a relationship */ withAggregate(relationName: any, userCallback: any): this { const subQuery = this.getRelationship(relationName).subQuery(this.client) @@ -525,7 +520,7 @@ export class ModelQueryBuilder } /** - * Count subquery selection + * Count sub-query selection */ this.select(subQuery.prepare()) @@ -538,7 +533,7 @@ export class ModelQueryBuilder } /** - * Get count of a relationship along side the main query results + * Get count of a relationship alongside the main query results */ withCount(relationName: any, userCallback?: any): this { this.withAggregate(relationName, (subQuery: RelationQueryBuilderContract) => { @@ -554,7 +549,7 @@ export class ModelQueryBuilder } /** - * Define alias for the subquery + * Define alias for the sub-query */ if (!subQuery.subQueryAlias) { subQuery.as(`${relationName}_count`) @@ -860,8 +855,8 @@ export class ModelQueryBuilder /** * Implementation of `finally` for the promise API */ - finally(fullfilled: any) { - return this.exec().finally(fullfilled) + finally(fulfilled: any) { + return this.exec().finally(fulfilled) } /** diff --git a/src/orm/relations/keys_extractor.ts b/src/orm/relations/keys_extractor.ts index dd7c8203..ebbae1ab 100644 --- a/src/orm/relations/keys_extractor.ts +++ b/src/orm/relations/keys_extractor.ts @@ -7,8 +7,8 @@ * file that was distributed with this source code. */ -import { Exception } from '@poppinss/utils' import { LucidModel } from '../../types/model.js' +import * as errors from '../../errors.js' /** * Utility to consistently extract relationship keys from the model @@ -33,13 +33,7 @@ export class KeysExtractor Date: Fri, 6 Oct 2023 13:45:57 +0200 Subject: [PATCH 2/2] test(errors): update testing suite to match new error --- src/orm/decorators/date_time.ts | 2 +- test/migrations/migrator.spec.ts | 6 ++++-- test/orm/base_model.spec.ts | 21 +++++++++++++++------ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/orm/decorators/date_time.ts b/src/orm/decorators/date_time.ts index ca617056..e32bc98a 100644 --- a/src/orm/decorators/date_time.ts +++ b/src/orm/decorators/date_time.ts @@ -47,7 +47,7 @@ function prepareDateTimeColumn(value: any, attributeName: string, modelInstance: */ throw new errors.E_INVALID_DATE_COLUMN_VALUE([ `${modelName}.${attributeName}`, - 'It must be an instance of "luxon.DateTime', + 'It must be an instance of "luxon.DateTime"', ]) } diff --git a/test/migrations/migrator.spec.ts b/test/migrations/migrator.spec.ts index 0b7c76ea..261f9641 100644 --- a/test/migrations/migrator.spec.ts +++ b/test/migrations/migrator.spec.ts @@ -18,6 +18,7 @@ import { getMigrator, cleanup as cleanupTables, } from '../../test-helpers/index.js' +import * as errors from '../../src/errors.js' test.group('Migrator', (group) => { group.each.setup(async () => { @@ -834,7 +835,7 @@ test.group('Migrator', (group) => { const db = getDb() cleanup(() => db.manager.closeAll()) - assert.plan(4) + assert.plan(5) await fs.create( 'database/migrations/users_v11.ts', @@ -892,9 +893,10 @@ test.group('Migrator', (group) => { assert.lengthOf(migrated, 2) assert.isTrue(hasUsersTable) assert.isTrue(hasAccountsTable) + assert.instanceOf(migrator1.error, errors.E_MISSING_SCHEMA_FILES) assert.equal( migrator1.error!.message, - 'Cannot perform rollback. Schema file {database/migrations/accounts_v11} is missing' + 'Cannot perform rollback. Schema file "database/migrations/accounts_v11" is missing' ) }) diff --git a/test/orm/base_model.spec.ts b/test/orm/base_model.spec.ts index 2debdafa..634917d7 100644 --- a/test/orm/base_model.spec.ts +++ b/test/orm/base_model.spec.ts @@ -53,6 +53,7 @@ import { LucidRow } from '../../src/types/model.js' import { ModelPaginator } from '../../src/orm/paginator/index.js' import { SimplePaginator } from '../../src/database/paginator/simple_paginator.js' import { SnakeCaseNamingStrategy } from '../../src/orm/naming_strategies/snake_case.js' +import * as errors from '../../src/errors.js' test.group('Base model | boot', (group) => { group.setup(async () => { @@ -6018,7 +6019,7 @@ test.group('Base Model | date', (group) => { }) test('raise error when date column value is unprocessable', async ({ fs, assert }) => { - assert.plan(1) + assert.plan(2) const app = new AppFactory().create(fs.baseUrl, () => {}) await app.init() @@ -6044,8 +6045,12 @@ test.group('Base Model | date', (group) => { user.dob = 10 as any try { await user.save() - } catch ({ message }) { - assert.equal(message, 'The value for "User.dob" must be an instance of "luxon.DateTime"') + } catch (error) { + assert.instanceOf(error, errors.E_INVALID_DATE_COLUMN_VALUE) + assert.equal( + error.message, + 'Invalid value for "User.dob". The value must be an instance of "luxon.DateTime"' + ) } }) @@ -6414,7 +6419,7 @@ test.group('Base Model | datetime', (group) => { }) test('raise error when datetime column value is unprocessable', async ({ fs, assert }) => { - assert.plan(1) + assert.plan(2) const app = new AppFactory().create(fs.baseUrl, () => {}) await app.init() @@ -6440,8 +6445,12 @@ test.group('Base Model | datetime', (group) => { user.dob = 10 as any try { await user.save() - } catch ({ message }) { - assert.equal(message, 'The value for "User.dob" must be an instance of "luxon.DateTime"') + } catch (error) { + assert.instanceOf(error, errors.E_INVALID_DATE_COLUMN_VALUE) + assert.equal( + error.message, + 'Invalid value for "User.dob". It must be an instance of "luxon.DateTime"' + ) } })