@adonisjs/lucid
Version:
SQL ORM built on top of Active Record pattern
387 lines (386 loc) • 15.3 kB
TypeScript
import type { ApplicationService } from '@adonisjs/core/types';
import { Database } from '../src/database/main.ts';
import { type LucidRow } from '../src/types/model.ts';
import { DatabaseTestUtils } from '../src/test_utils/database.js';
import { type ExtractModelRelations } from '../src/types/relations.ts';
import { type VineDbSearchCallback, type VineDbSearchOptions } from '../src/types/vine.ts';
import type { ConnectionContract, DbQueryEventNode } from '../src/types/database.ts';
/**
* Extending AdonisJS types
*/
declare module '@adonisjs/core/types' {
/**
* Registers Lucid's IoC container bindings so that `app.container.make()`
* and the `@inject()` decorator can resolve them by name.
*/
interface ContainerBindings {
/**
* The singleton {@link Database} instance that manages all configured
* database connections for the current application.
*
* @example
* const db = await app.container.make('lucid.db')
* const users = await db.query().from('users')
*/
'lucid.db': Database;
}
/**
* Registers the database-related events that Lucid emits through the
* application's event emitter so that listeners are fully typed.
*/
interface EventsList {
/**
* Emitted after every database query is executed. The payload contains
* the raw SQL, duration, connection name, and optional model context.
*
* @example
* emitter.on('db:query', (event) => {
* console.log(event.sql, event.duration)
* })
*/
'db:query': DbQueryEventNode;
/**
* Emitted when a database connection is successfully established.
* The payload is the {@link ConnectionContract} instance that connected.
*
* @example
* emitter.on('db:connection:connect', (connection) => {
* console.log(`Connected: ${connection.name}`)
* })
*/
'db:connection:connect': ConnectionContract;
/**
* Emitted when a database connection is gracefully closed.
* The payload is the {@link ConnectionContract} instance that disconnected.
*
* @example
* emitter.on('db:connection:disconnect', (connection) => {
* console.log(`Disconnected: ${connection.name}`)
* })
*/
'db:connection:disconnect': ConnectionContract;
/**
* Emitted when a database connection encounters an error. The first
* element of the tuple is the `Error` that was thrown; the second is
* the {@link ConnectionContract} on which it occurred.
*
* @example
* emitter.on('db:connection:error', ([error, connection]) => {
* console.error(`Error on ${connection.name}:`, error.message)
* })
*/
'db:connection:error': [Error, ConnectionContract];
}
}
declare module '@adonisjs/core/transformers' {
/**
* Augments {@link BaseTransformer} with helper methods for reading
* relationship counts and custom aggregates that were eagerly loaded
* via the Lucid query builder's `.withCount()` / `.withAggregate()`
* methods.
*
* When `T` is a Lucid model the `relationship` parameter is
* narrowed to only the declared relation names on that model;
* otherwise it falls back to a plain `string`.
*/
interface BaseTransformer<T> {
/**
* Retrieve the count for a previously loaded relationship.
* Throws if the count was not loaded on the model — use
* {@link whenCounted} for a non-throwing alternative.
*
* @param {string} relationship - The relationship name that was
* passed to `query().withCount(relationship)`.
*
* @return {number} The relationship count as a number.
*
* @example
* class UserTransformer extends BaseTransformer<User> {
* toObject() {
* return {
* postsCount: this.withCount('posts'),
* }
* }
* }
*/
withCount(relationship: T extends LucidRow ? ExtractModelRelations<T> : string): number;
/**
* Safely retrieve the count for a previously loaded relationship.
* Returns `undefined` when the count was not loaded instead of
* throwing — use {@link withCount} for the strict variant.
*
* @param {string} relationship - The relationship name that was
* passed to `query().withCount(relationship)`.
*
* @return {number | undefined} The relationship count, or
* `undefined` if the count was not loaded.
*
* @example
* class UserTransformer extends BaseTransformer<User> {
* toObject() {
* return {
* postsCount: this.whenCounted('posts'),
* }
* }
* }
*/
whenCounted(relationship: T extends LucidRow ? ExtractModelRelations<T> : string): number | undefined;
/**
* Retrieve a custom aggregate value by the alias it was stored
* under in `$extras`. Throws if the aggregate was not loaded —
* use {@link whenAggregated} for a non-throwing alternative.
*
* @param {string} alias - The alias provided via `.as(alias)` in
* the aggregate callback (e.g. `'totalPosts'`).
*
* @return {number} The aggregate value as a number.
*
* @example
* class UserTransformer extends BaseTransformer<User> {
* toObject() {
* return {
* totalPosts: this.withAggregate('totalPosts'),
* }
* }
* }
*/
withAggregate(alias: string): number;
/**
* Safely retrieve a custom aggregate value by the alias it was
* stored under in `$extras`. Returns `undefined` when the
* aggregate was not loaded instead of throwing — use
* {@link withAggregate} for the strict variant.
*
* @param {string} alias - The alias provided via `.as(alias)` in
* the aggregate callback (e.g. `'totalPosts'`).
*
* @return {number | undefined} The aggregate value, or
* `undefined` if the aggregate was not loaded.
*
* @example
* class UserTransformer extends BaseTransformer<User> {
* toObject() {
* return {
* totalPosts: this.whenAggregated('totalPosts'),
* }
* }
* }
*/
whenAggregated(alias: string): number | undefined;
}
}
declare module '@adonisjs/core/test_utils' {
/**
* Augments the core {@link TestUtils} class with a `db` macro that
* exposes database-specific test helpers (migrate, rollback, refresh,
* seed, truncate, etc.).
*/
interface TestUtils {
/**
* Returns a {@link DatabaseTestUtils} instance scoped to the given
* connection. When no connection name is provided the default
* connection from your `database.ts` config file is used.
*
* @param {string} [connectionName] - An optional connection name
* defined in your `database.ts` configuration.
*
* @return {DatabaseTestUtils} A helper bound to the chosen connection.
*
* @example
* const { db } = await TestUtils.create(app)
*
* // Use the default connection
* await db.migrate()
*
* // Use a specific connection
* await db('sqlite').migrate()
*/
db(connectionName?: string): DatabaseTestUtils;
}
}
/**
* Extending VineJS schema types
*/
declare module '@vinejs/vine' {
/**
* Shared database-backed validation rules mixed into both
* {@link VineNumber} and {@link VineString} schema types.
*
* `ValueType` is the raw JavaScript type of the field being
* validated (`number` or `string`).
*/
interface VineLucidBindings<ValueType> {
/**
* Ensure the value is unique inside the database by table and column name.
* Optionally, you can define a filter to narrow down the query.
*
* @param {VineDbSearchOptions<ValueType>} options - Table, column, and
* optional filter configuration.
*
* @return {this} The current schema instance for chaining.
*
* @example
* vine.string().unique({ table: 'users', column: 'email' })
*/
unique(options: VineDbSearchOptions<ValueType>): this;
/**
* Ensure the value is unique inside the database by self
* executing a query.
*
* - The callback must return `true` if the value is unique (does not exist).
* - The callback must return `false` if the value is not unique (already exists).
*
* @param {VineDbSearchCallback<ValueType>} callback - An async callback
* that receives `(db, value, field)` and must resolve to a boolean.
*
* @return {this} The current schema instance for chaining.
*
* @example
* vine.string().unique(async (db, value) => {
* const row = await db.query().from('users').where('email', value).first()
* return !row
* })
*/
unique(callback: VineDbSearchCallback<ValueType>): this;
/**
* Ensure the value exists inside the database by table and column name.
* Optionally, you can define a filter to narrow down the query.
*
* @param {VineDbSearchOptions<ValueType>} options - Table, column, and
* optional filter configuration.
*
* @return {this} The current schema instance for chaining.
*
* @example
* vine.number().exists({ table: 'roles', column: 'id' })
*/
exists(options: VineDbSearchOptions<ValueType>): this;
/**
* Ensure the value exists inside the database by self
* executing a query.
*
* - The callback must return `true` if the value exists.
* - The callback must return `false` if the value does not exist.
*
* @param {VineDbSearchCallback<ValueType>} callback - An async callback
* that receives `(db, value, field)` and must resolve to a boolean.
*
* @return {this} The current schema instance for chaining.
*
* @example
* vine.number().exists(async (db, value) => {
* const row = await db.query().from('roles').where('id', value).first()
* return !!row
* })
*/
exists(callback: VineDbSearchCallback<ValueType>): this;
}
/**
* Extends the VineJS `number` schema type with the database-backed
* `unique` and `exists` validation rules from {@link VineLucidBindings}.
*/
interface VineNumber extends VineLucidBindings<number> {
}
/**
* Extends the VineJS `string` schema type with the database-backed
* `unique` and `exists` validation rules from {@link VineLucidBindings}.
*/
interface VineString extends VineLucidBindings<string> {
}
}
/**
* AdonisJS service provider that wires up the entire Lucid ORM layer.
*
* Responsibilities:
* - Registers the {@link Database} singleton and the default
* {@link QueryClient} in the IoC container.
* - Attaches the ORM {@link Adapter} to {@link BaseModel}.
* - Registers the `db` macro on {@link TestUtils} for test helpers.
* - Binds REPL helpers when running in the `repl` environment.
* - Registers the `unique` / `exists` VineJS validation rules when
* VineJS is present.
* - Optionally enables pretty-printed debug query logging.
*
* @example
* // providers/database_provider.ts is auto-discovered by AdonisJS.
* // No manual instantiation is required.
*/
export default class DatabaseServiceProvider {
protected app: ApplicationService;
/**
* @param {ApplicationService} app - The AdonisJS application instance
* used to access the IoC container, configuration, and environment
* information.
*/
constructor(app: ApplicationService);
/**
* Registers REPL bindings so that interactive database helpers
* (e.g. `db`, `User`) are available when the application is
* started with `node ace repl`.
*
* This is a no-op in any environment other than `repl`.
*/
protected registerReplBindings(): Promise<void>;
/**
* Registers the database-backed `unique` and `exists` validation
* rules on VineJS when the application is using VineJS as its
* validation engine.
*
* This is a no-op when VineJS is not installed or configured.
*
* @param {Database} db - The resolved {@link Database} singleton
* that will be passed into the validation rule implementations.
*/
protected registerVineJSRules(db: Database): Promise<void>;
/**
* Registers a `db` macro on the core {@link TestUtils} class.
* The macro is lazily set up via `container.resolving('testUtils')`
* so it is only wired when tests actually request the `testUtils`
* binding.
*
* @example
* // Inside a test file
* const { db } = await TestUtils.create(app)
* await db.migrate()
*/
protected registerTestUtils(): Promise<void>;
/**
* Attaches a listener to the `db:query` event that pretty-prints
* every executed query to the console. Only active when the
* `prettyPrintDebugQueries` flag is enabled in the `database.ts`
* configuration file.
*
* @param {Database} db - The resolved {@link Database} singleton
* whose config is checked and whose `prettyPrint` handler is used.
*/
protected prettyPrintDebugQueries(db: Database): Promise<void>;
/**
* Lifecycle hook invoked by AdonisJS during the **register** phase.
*
* Registers the following singletons in the IoC container:
* - `Database` (also aliased as `lucid.db`) — the central connection
* manager, built from the `database` config key.
* - `QueryClient` — a query client bound to the **default**
* connection, convenient for simple queries without explicitly
* choosing a connection.
*/
register(): void;
/**
* Lifecycle hook invoked by AdonisJS during the **boot** phase,
* after all providers have registered their bindings.
*
* - Sets the ORM {@link Adapter} on {@link BaseModel} so that all
* model classes share a single, application-scoped adapter.
* - Calls the remaining setup helpers: debug query logging, test
* utilities, REPL bindings, and VineJS rules.
*/
boot(): Promise<void>;
/**
* Lifecycle hook invoked by AdonisJS during application shutdown.
*
* Gracefully closes **all** open database connections managed by the
* {@link Database} connection manager so that no sockets or pools
* are left dangling.
*/
shutdown(): Promise<void>;
}