Connectors
A connector is how IGNIS adds a storage engine - PostgreSQL, SQLite, Typesense, Meilisearch - behind one engine-neutral contract. DataSource, Entity, and Repository mean the same thing no matter which engine backs them.
"A connector" (this page) means an engine-integration module. "The connector" means the Drizzle instance exposed as this.connector on a datasource or repository. The two senses are unrelated despite the shared word.
In one example
The neutral AbstractDataSource has no SQL, no pool, no Drizzle. The relational tier adds all of that, the postgres branch binds it to the Postgres dialect as BasePostgresDataSource, and @datasource({ driver }) names the concrete client class:
import { Pool } from 'pg';
import { datasource } from '@venizia/ignis';
import { BasePostgresDataSource } from '@venizia/ignis/postgres';
import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
interface IDataSourceConfigs {
host: string;
port: number;
database: string;
user: string;
password: string;
}
@datasource({ driver: NodePostgresDriver })
export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfigs> {
override configure(): void {
this.client = new Pool(this.settings);
}
override getConnectionString(): string {
const { host, port, user, password, database } = this.settings;
return `postgresql://${user}:${password}@${host}:${port}/${database}`;
}
}A Typesense datasource follows the same shape but extends the search connector's BaseSearchDataSource instead - no pool, no getConnectionString(), no transactions.
How it works
- Three engine-neutral roots.
packages/kernel/src/basedeclares three roots every connector implements:
| Root | Purpose | Neutral default |
|---|---|---|
AbstractDataSource | Connection ownership | No pool, no Drizzle connector; beginTransaction() throws NotSupported |
AbstractEntity | Model/schema contract | name, getSchema(), getIdType() |
AbstractRepository | Data access contract | Generics named for role (data/persist/options), not any one engine's vocabulary |
- Two paradigm tiers sit between the roots and the engines.
packages/connectors/src/relational/coreandpackages/connectors/src/search/corehold everything that is true of a whole family, so an engine branch stays thin:
| Tier | Adds | getCapabilities() |
|---|---|---|
relational (@venizia/ignis/relational) | Drizzle connector, driver seam, SQL-shaped TWhere/TFilter, the FilterBuilder, real transactions | { transactions: true } on BaseRelationalDataSource |
search (@venizia/ignis/search) | Collections, one shared search query-dialect, no SQL and no transactions | inherited neutral default |
- Engine branches bind a tier to a client. Each one subclasses the tier and supplies the dialect, the executor and the driver:
| Engine | Subpath | Tier | Supplies |
|---|---|---|---|
| postgres | @venizia/ignis/postgres | relational | PostgresQueryDialect, PostgresQueryExecutor, BEGIN ... ISOLATION LEVEL, three drivers |
| sqlite | @venizia/ignis/sqlite | relational | SqliteQueryDialect, SqliteQueryExecutor, the libsql driver, SqliteBeginModes locking modes |
| typesense | @venizia/ignis/typesense | search | The typesense client and its collection schema |
| meilisearch | @venizia/ignis/meilisearch | search | The meilisearch client and its index schema |
@datasource({ driver })picks the concrete client, within an engine. A relational datasource takes a driver class (NodePostgresDriver,PostgresJsDriver,PGliteDriver,LibSqlDriver), never a driver-name string. A bundler packages values, not text, so a string would leave the peer dependency uninstalled.- All engine clients stay optional peer dependencies. Importing
@venizia/ignis/postgresalone loads zero client libraries. - Search controllers ship on their own subpath.
@venizia/ignis/search,/typesenseand/meilisearchcarry no controller, so they load withouthonoordrizzle-orm. ImportAbstractSearchController,SearchControllerFactoryanddefineSearchRouteConfigsfrom@venizia/ignis/search/controllersor@venizia/ignis/typesense/controllers. - Only postgres is reachable from the root barrel.
@venizia/ignisre-exports@venizia/ignis/postgresfor backward compatibility. Every other engine, and both neutral tiers, are subpath-only. An app that never touches search never pulls a search client into its bundle.
Neutral names and engine names
| Layer | Neutral (@venizia/ignis/relational) | Postgres (@venizia/ignis/postgres) | Relationship |
|---|---|---|---|
| DataSource | BaseRelationalDataSource | BasePostgresDataSource | Subclass - adds the Postgres dialect, executor and BEGIN statement |
| Entity | BaseRelationalEntity | BasePostgresEntity, BaseEntity | Alias - one class, re-exported under two extra names |
| Repository | RelationalBaseRepository | PostgresBaseRepository | Subclass - rebinds ExtraOptions and TDataSource |
Only the Entity row is an alias. The other two are distinct classes, and the neutral names are deliberately not re-exported from the engine subpaths, so one name never denotes two classes. import { RelationalBaseRepository } from '@venizia/ignis' and import { BaseRelationalDataSource } from '@venizia/ignis/postgres' both fail to resolve; both live at @venizia/ignis/relational.
BaseDataSource is a genuine alias within the postgres subpath: it re-exports BasePostgresDataSource under its historical name.
Common tasks
Pick an engine
Import the connector for the engine you need - @venizia/ignis/postgres or @venizia/ignis/sqlite for a relational database with transactions, @venizia/ignis/typesense or @venizia/ignis/meilisearch for document search. The root @venizia/ignis barrel re-exports postgres for backward compatibility; every other engine is subpath-only:
// Postgres: available at the root or the subpath - same class either way
import { BaseDataSource } from '@venizia/ignis';
import { BasePostgresDataSource } from '@venizia/ignis/postgres';
// SQLite and the search engines: subpath only, never at the root
import { BaseSqliteDataSource } from '@venizia/ignis/sqlite';
import { TypesenseDataSource } from '@venizia/ignis/typesense';
import { MeilisearchDataSource } from '@venizia/ignis/meilisearch';
// The engine-neutral relational tier, when you write code for both SQL engines
import { BaseRelationalDataSource, DefaultRelationalRepository } from '@venizia/ignis/relational';Know what lives in the kernel vs a tier vs an engine
- Engine-specific -> engine branch. A query dialect, an isolation level, or a
BEGINstatement lives inrelational/postgresorrelational/sqlite, never in the tier above it. - Family-universal -> tier.
relational/coregrows members every SQL engine shares, such as theFilterBuilderor the driver seam. - Universal -> kernel.
packages/kernel/src/baseonly ever grows members every engine can implement, such asgetSchema()orgetIdType(). - Litmus test. Could typesense (no pool, no SQL, no transactions) implement it? If not, it does not belong in the neutral root.
Add a new engine connector
- Mirror the shape. Under
packages/connectors/src/<tier>/<engine>/, add adatasources/extending the tier's base datasource and arepositories/core/binding each rung of the tier's ladder. Add amodels/extending the tier's entity too, if the engine needs entity definitions. - Override transactions only if supported. Override
getCapabilities()andbeginTransaction()only if the engine truly supports transactions - otherwise inherit the neutralNotSupporteddefault. - Export as a subpath. Add the connector to
package.jsonexportsas./<engine>in both@venizia/ignis-connectorsand@venizia/ignis. If its driver is an optional peer dependency, keep it out of the root barrel and register it as a subpath-only export instead, mirroring sqlite, typesense and meilisearch. - Do not re-export the tier's names. Publishing
BaseRelationalDataSourcefrom your engine subpath would put two different classes behind one name.
See also
- DataSources - the engine-neutral contract and the PostgreSQL connector in depth
- Models & Enrichers - the engine-neutral entity contract and the PostgreSQL connector's entity
- Repositories - the CRUD layer built on top of a connector's datasource
- Filter System - querying through a connector's repository
- Persistent Layer - the guide these reference pages support
- Search & Typesense - the typesense connector in depth
- Search & Meilisearch - the meilisearch connector in depth
Files:
packages/kernel/src/base/datasources/abstract.ts- neutralAbstractDataSourcepackages/kernel/src/base/models/base.ts- neutralAbstractEntitypackages/kernel/src/base/repositories/core/abstract.ts- neutralAbstractRepositorypackages/connectors/src/relational/core/datasources/base.ts-BaseRelationalDataSource, wheregetCapabilities() -> { transactions: true }livespackages/connectors/src/relational/postgres/datasources/index.ts-BasePostgresDataSource, and theBaseDataSourcealias; the comment explaining why the neutral names are not re-exported herepackages/connectors/src/relational/sqlite/- the SQLite branch: datasource, libsql driver, repository bindings, enricherspackages/connectors/src/search/core/datasources/base.ts-BaseSearchDataSource, shared by typesense and meilisearch