Skip to content

Connectors

A connector is how IGNIS adds a storage engine - PostgreSQL, 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 postgres connector's BasePostgresDataSource adds all of that, and @datasource({ driver }) names the concrete client class:

typescript
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/core-server/src/base declares three roots every connector implements:
RootPurposeNeutral default
AbstractDataSourceConnection ownershipNo pool, no Drizzle connector; beginTransaction() throws NotSupported
AbstractEntityModel/schema contractname, getSchema(), getIdType()
AbstractRepositoryData access contractGenerics named for role (data/persist/options), not any one engine's vocabulary
  • Connectors narrow the roots into a real engine. packages/core-server/src/connectors/<engine> adds engine-specific members:
ConnectorAddsgetCapabilities()
postgres (connectors/postgres)pool, Drizzle connector, SQL-shaped TWhere/TFilter, real transactions with isolation levels{ transactions: true }
search (connectors/search)Engine-neutral search base - no SQL, no transactions. Both typesense and meilisearch extend it (not AbstractDataSource directly), sharing one query-dialect and capability shapeinherited neutral default
  • @datasource({ driver }) picks the concrete client, within an engine. Postgres takes a driver class (NodePostgresDriver or PostgresJsDriver), 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/postgres alone loads zero client libraries.
  • Search connectors are subpath-only. @venizia/ignis/typesense and @venizia/ignis/meilisearch are excluded from the root @venizia/ignis barrel. An app that never touches search never pulls a search client into its bundle.

Canonical names and aliases

LayerCanonical (Relational paradigm)Compatibility alias (Postgres-prefixed)
DataSourceBaseRelationalDataSourceBasePostgresDataSource
EntityBaseRelationalEntityBasePostgresEntity
RepositoryRelationalBaseRepositoryPostgresBaseRepository

Both names resolve to the same class - existing imports keep working.

Common tasks

Pick an engine

Import the connector for the engine you need - @venizia/ignis/postgres 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; search engines are always subpath-only:

typescript
// Postgres: available at the root or the subpath - same class either way
import { BaseDataSource } from '@venizia/ignis';
import { BasePostgresDataSource } from '@venizia/ignis/postgres';

// Search engines: subpath only, never at the root
import { TypesenseDataSource } from '@venizia/ignis/typesense';
import { MeilisearchDataSource } from '@venizia/ignis/meilisearch';

Know what lives in base vs a connector

  • Engine-specific -> connector. A connection pool, query dialect, isolation level, or transaction lives in a connector, not src/base.
  • Engine-universal -> base. src/base only ever grows members every engine can implement, such as getSchema() or getIdType().
  • Litmus test. Could typesense (no pool, no SQL, no transactions) implement it? If not, it belongs in the postgres connector, not the neutral root.

Add a new engine connector

  • Mirror the shape. Under src/connectors/<engine>/, add a datasources/ extending AbstractDataSource and a repositories/core/ extending AbstractRepository with a Readable/Persistable/DefaultCRUD-style tier ladder. Add a models/ extending AbstractEntity too, if the engine needs entity definitions.
  • Override transactions only if supported. Override getCapabilities() and beginTransaction() only if the engine truly supports transactions - otherwise inherit the neutral NotSupported default.
  • Export as a subpath. Add the connector to package.json exports as ./<engine>. If its driver is an optional peer dependency, keep it out of connectors/index.ts and register it as a subpath-only export instead, mirroring typesense and meilisearch.

See also

Files: