@venizia/ignis-docs 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -296,7 +296,7 @@ DataSources automatically discover their schema from the repositories that bind
296
296
  // src/datasources/postgres.datasource.ts
297
297
  import { datasource, ValueOrPromise } from '@venizia/ignis';
298
298
  import { BasePostgresDataSource } from '@venizia/ignis/postgres';
299
- import { drizzle } from 'drizzle-orm/node-postgres';
299
+ import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
300
300
  import { Pool } from 'pg';
301
301
 
302
302
  interface IDataSourceConfigs {
@@ -307,7 +307,7 @@ interface IDataSourceConfigs {
307
307
  password: string;
308
308
  }
309
309
 
310
- @datasource({ driver: 'node-postgres' })
310
+ @datasource({ driver: NodePostgresDriver })
311
311
  export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfigs> {
312
312
  constructor() {
313
313
  super({
@@ -319,11 +319,11 @@ export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfig
319
319
 
320
320
  override configure(): ValueOrPromise<void> {
321
321
  // getSchema() automatically collects all schemas from bound repositories
322
- const schema = this.getSchema();
322
+ this.logger.debug('[configure] Auto-discovered schema | Keys: %o', Object.keys(this.getSchema()));
323
323
 
324
- // Keep the pool on this.client - beginTransaction() resolves its driver from it
324
+ // Keep the pool on this.client - naming NodePostgresDriver above is what wires the driver
325
+ // and Drizzle connector; beginTransaction() resolves its driver from this.client lazily.
325
326
  this.client = new Pool(this.settings);
326
- this.connector = drizzle({ client: this.client, schema });
327
327
  }
328
328
 
329
329
  override getConnectionString(): ValueOrPromise<string> {
@@ -207,13 +207,16 @@ Connection pooling significantly improves performance by reusing database connec
207
207
 
208
208
  ```typescript
209
209
  import { Pool } from 'pg';
210
- import { drizzle } from 'drizzle-orm/node-postgres';
210
+ import { datasource } from '@venizia/ignis';
211
211
  import { BasePostgresDataSource } from '@venizia/ignis/postgres';
212
+ import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
212
213
 
213
214
  // IDataSourceConfigs: your settings interface (host/port/user/password/database)
215
+ @datasource({ driver: NodePostgresDriver })
214
216
  export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfigs> {
215
217
  override configure(): void {
216
- // Keep the pool on `this.client` - beginTransaction() resolves its driver from it
218
+ // Keep the pool on `this.client` - NodePostgresDriver above wires the driver and Drizzle
219
+ // connector from it lazily, on first getConnector()/beginTransaction()
217
220
  this.client = new Pool({
218
221
  host: this.settings.host,
219
222
  port: this.settings.port,
@@ -228,8 +231,6 @@ export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfig
228
231
  connectionTimeoutMillis: 5000, // Fail if can't connect in 5s
229
232
  maxUses: 7500, // Close connection after 7500 queries
230
233
  });
231
-
232
- this.connector = drizzle({ client: this.client, schema: this.getSchema() });
233
234
  }
234
235
  }
235
236
  ```
@@ -0,0 +1,92 @@
1
+ # Compiling to a Single Binary
2
+
3
+ `bun build --compile` produces a standalone executable that crashes on startup when the application
4
+ imports any Kafka helper, unless the build registers `platformaticWasmPlugin`.
5
+
6
+ ```
7
+ ENOENT: no such file or directory, open '/$bunfs/dist/native.wasm'
8
+ ```
9
+
10
+ The failure happens while the module graph is still loading -- before the IGNIS application boots, so
11
+ no log line, no lifecycle hook, and no error handler of yours ever runs.
12
+
13
+ ## Why it happens
14
+
15
+ `@platformatic/kafka` computes Kafka's CRC32C checksums and lz4/snappy compression in WebAssembly,
16
+ through `@platformatic/wasm-utils`. The default entrypoint of that package reads the wasm payload
17
+ from disk at module load time:
18
+
19
+ ```javascript
20
+ // @platformatic/wasm-utils/dist/index.js
21
+ const wasm = readFileSync(new URL('../dist/native.wasm', import.meta.url));
22
+ ```
23
+
24
+ `bun build --compile` embeds JavaScript modules only -- assets such as `native.wasm` are not carried
25
+ into the executable. Inside the binary, `import.meta.url` resolves against the virtual `/$bunfs`
26
+ filesystem, the file is not there, and the read throws.
27
+
28
+ Running from source (`bun run`, `bun .`) is unaffected: `node_modules` is on disk, so the read
29
+ succeeds. The bug only exists in compiled binaries.
30
+
31
+ ## The fix
32
+
33
+ `@platformatic/wasm-utils` ships a second entrypoint, `@platformatic/wasm-utils/bundled`, exposing
34
+ the same API with the wasm payload inlined as base64 -- no filesystem read. `platformaticWasmPlugin`
35
+ swaps one entrypoint for the other at bundle time.
36
+
37
+ Compile through a Bun build script instead of the `bun build --compile` CLI, which cannot register
38
+ plugins:
39
+
40
+ ```typescript
41
+ // scripts/compile.ts
42
+ import { platformaticWasmPlugin } from '@venizia/ignis-helpers/kafka';
43
+
44
+ const built = await Bun.build({
45
+ entrypoints: ['./dist/index.js'],
46
+ target: 'bun',
47
+ minify: { whitespace: true, syntax: true },
48
+ sourcemap: 'linked',
49
+ compile: {
50
+ target: process.env.BUN_TARGET ?? 'bun-linux-x64',
51
+ outfile: './dist/bin',
52
+ },
53
+ plugins: [platformaticWasmPlugin()],
54
+ });
55
+
56
+ if (!built.success) {
57
+ console.error(built.logs);
58
+ process.exit(1);
59
+ }
60
+ ```
61
+
62
+ ```json
63
+ {
64
+ "scripts": {
65
+ "compile": "bun run ./scripts/compile.ts"
66
+ }
67
+ }
68
+ ```
69
+
70
+ The plugin resolves `@platformatic/wasm-utils/bundled` from the importing module's own directory, so
71
+ it works with hoisted and isolated `node_modules` layouts alike, and pins no package version.
72
+
73
+ ## Verifying
74
+
75
+ A correctly built binary contains no reference to the wasm file on disk:
76
+
77
+ ```bash
78
+ grep -c 'native.wasm' ./dist/bin # 0 -- the payload is inlined
79
+ ./dist/bin # boots instead of throwing ENOENT
80
+ ```
81
+
82
+ The binary grows by roughly 76 KB, the base64 form of the 57 KB wasm module.
83
+
84
+ ## Notes
85
+
86
+ - Upgrading `@platformatic/kafka` does not remove the need for the plugin: every release to date,
87
+ including 2.6.1, imports the default `@platformatic/wasm-utils` entrypoint.
88
+ - Applications that never import a Kafka helper need no plugin -- nothing pulls in
89
+ `@platformatic/wasm-utils`, and the plugin's resolver never fires.
90
+ - Patching `node_modules` during the build achieves the same result, but mutates a dependency in
91
+ place, pins the store path to one version, and leaves the tree dirty when a build fails. The
92
+ plugin needs neither.
@@ -88,6 +88,9 @@ import type {
88
88
  > [!NOTE]
89
89
  > Kafka helpers are **not** re-exported from the main `@venizia/ignis-helpers` entry point. You must use the `@venizia/ignis-helpers/kafka` subpath import. This keeps the optional `@platformatic/kafka` peer dependency isolated for tree-shaking.
90
90
 
91
+ > [!WARNING]
92
+ > Compiling an application that uses these helpers with `bun build --compile` produces a binary that dies on startup with `ENOENT: /$bunfs/dist/native.wasm`. The build must register `platformaticWasmPlugin` from `@venizia/ignis-helpers/kafka` -- see [Compiling to a Single Binary](./compile-binary.md).
93
+
91
94
  ### Installation
92
95
 
93
96
  ```bash
@@ -14,7 +14,7 @@ import {
14
14
  datasource,
15
15
  ValueOrPromise,
16
16
  } from '@venizia/ignis';
17
- import { drizzle } from 'drizzle-orm/node-postgres';
17
+ import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
18
18
  import { Pool } from 'pg';
19
19
 
20
20
  interface IDSConfigs {
@@ -25,7 +25,7 @@ interface IDSConfigs {
25
25
  password: string;
26
26
  }
27
27
 
28
- @datasource({ driver: 'node-postgres' })
28
+ @datasource({ driver: NodePostgresDriver })
29
29
  export class PostgresDataSource extends BasePostgresDataSource<IDSConfigs> {
30
30
  constructor() {
31
31
  super({
@@ -42,16 +42,11 @@ export class PostgresDataSource extends BasePostgresDataSource<IDSConfigs> {
42
42
  }
43
43
 
44
44
  override configure(): ValueOrPromise<void> {
45
- // getSchema() auto-discovers models from @repository bindings
46
- const schema = this.getSchema();
47
-
48
- this.logger.debug(
49
- '[configure] Auto-discovered schema | Keys: %o',
50
- Object.keys(schema),
51
- );
45
+ const schema = Object.keys(this.getSchema());
46
+ this.logger.debug('[configure] Auto-discovered schema | Keys: %o', schema);
52
47
 
48
+ // That is all - naming NodePostgresDriver above is what wires the driver and connector.
53
49
  this.client = new Pool(this.settings);
54
- this.connector = drizzle({ client: this.client, schema });
55
50
  }
56
51
 
57
52
  override getConnectionString(): ValueOrPromise<string> {
@@ -62,22 +57,22 @@ export class PostgresDataSource extends BasePostgresDataSource<IDSConfigs> {
62
57
  ```
63
58
 
64
59
  > [!NOTE] Driver seam: the raw client goes on `this.client`
65
- > `this.client = new Pool(...)` is the short path: IGNIS resolves a `node-postgres` driver from that client on first use. There is no `pool` field - the raw-client slot is `client`, whatever the client happens to be. The alternative is to wire a driver yourself: `configure()` calls `this.useDriver({ driver, schema? })`, which assigns `this.driver` **and** builds `this.connector` in one step (so the half-wired state cannot exist). That is also how you select the `postgres-js` driver or run on Supabase. See [Postgres Drivers & Supabase](./postgres-drivers).
60
+ > `this.client = new Pool(...)` is the short path: `configure()` builds only the client, and `getConnector()`/`beginTransaction()` lazily instantiate the class named in `@datasource({ driver })` over it - `NodePostgresDriver` here. There is no `pool` field - the raw-client slot is `client`, whatever the client happens to be. Naming the driver class (rather than a driver-name string) is what carries `pg` into the app's bundle - a bundler only packages a real value reference, never text. The alternative is to wire a driver yourself for a custom or third-party driver: `configure()` calls `this.useDriver({ driver, schema? })`, which assigns `this.driver` **and** builds `this.connector` in one step (so the half-wired state cannot exist), bypassing `@datasource({ driver })` entirely. See [Postgres Drivers & Supabase](./postgres-drivers) for `postgres-js` and Supabase.
66
61
 
67
62
  **How auto-discovery works:**
68
63
 
69
64
  1. `@repository` decorators register model-datasource bindings in the `MetadataRegistry`
70
- 2. When `configure()` is called, `getSchema()` invokes `discoverSchema()` which calls `MetadataRegistry.buildSchema({ dataSource })` to collect all bound models and their relations
71
- 3. Drizzle is initialized with the complete schema (tables + Drizzle relations)
65
+ 2. `getSchema()` invokes `discoverSchema()` which calls `MetadataRegistry.buildSchema({ dataSource })` to collect all bound models and their relations
66
+ 3. The lazily-built Drizzle connector is initialized with the complete schema (tables + Drizzle relations)
72
67
 
73
- You can disable auto-discovery per datasource via `@datasource({ driver: 'node-postgres', autoDiscovery: false })`.
68
+ You can disable auto-discovery per datasource via `@datasource({ driver: NodePostgresDriver, autoDiscovery: false })`.
74
69
 
75
70
  ## Manual Schema (Optional)
76
71
 
77
72
  If you need explicit control, you can still provide schema manually:
78
73
 
79
74
  ```typescript
80
- @datasource({ driver: 'node-postgres' })
75
+ @datasource({ driver: NodePostgresDriver })
81
76
  export class PostgresDataSource extends BasePostgresDataSource<IDSConfigs> {
82
77
  constructor() {
83
78
  super({
@@ -99,7 +94,7 @@ export class PostgresDataSource extends BasePostgresDataSource<IDSConfigs> {
99
94
  AbstractDataSource extends BaseHelper # engine-neutral, src/base - no pool, no Drizzle
100
95
  └── AbstractPostgresDataSource # connectors/postgres - adds pool, connector
101
96
  └── BasePostgresDataSource (alias: BaseDataSource)
102
- ├── configure() # Setup pool + Drizzle connector (abstract)
97
+ ├── configure() # Assign this.client (abstract) - base wires driver + connector
103
98
  ├── getConnectionString() # Build connection URL (abstract)
104
99
  ├── getSchema() # Auto-discover from @repository bindings
105
100
  ├── discoverSchema() # Internal: reads MetadataRegistry
@@ -135,7 +130,7 @@ DataSources are bound as **singletons** to ensure connection pool sharing across
135
130
 
136
131
  ```typescript
137
132
  import { BasePostgresDataSource, datasource, ValueOrPromise } from '@venizia/ignis';
138
- import { drizzle } from 'drizzle-orm/node-postgres';
133
+ import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
139
134
  import { Pool } from 'pg';
140
135
 
141
136
  interface IDSConfigs {
@@ -146,7 +141,7 @@ interface IDSConfigs {
146
141
  password: string;
147
142
  }
148
143
 
149
- @datasource({ driver: 'node-postgres' })
144
+ @datasource({ driver: NodePostgresDriver })
150
145
  export class PostgresDataSource extends BasePostgresDataSource<IDSConfigs> {
151
146
  constructor() {
152
147
  super({
@@ -162,9 +157,7 @@ export class PostgresDataSource extends BasePostgresDataSource<IDSConfigs> {
162
157
  }
163
158
 
164
159
  override configure(): ValueOrPromise<void> {
165
- const schema = this.getSchema();
166
160
  this.client = new Pool(this.settings);
167
- this.connector = drizzle({ client: this.client, schema });
168
161
  }
169
162
 
170
163
  override getConnectionString(): ValueOrPromise<string> {
@@ -49,7 +49,7 @@ export class User extends BasePostgresEntity<typeof User.schema> {
49
49
  }
50
50
 
51
51
  // 2. Create a DataSource
52
- @datasource({ driver: 'node-postgres' })
52
+ @datasource({ driver: NodePostgresDriver })
53
53
  export class PostgresDataSource extends BasePostgresDataSource<IDSConfigs> {
54
54
  constructor() {
55
55
  super({
@@ -65,9 +65,7 @@ export class PostgresDataSource extends BasePostgresDataSource<IDSConfigs> {
65
65
  }
66
66
 
67
67
  override configure(): ValueOrPromise<void> {
68
- const schema = this.getSchema();
69
- this.client = new Pool(this.settings);
70
- this.connector = drizzle({ client: this.client, schema });
68
+ this.client = new Pool(this.settings); // NodePostgresDriver above wires the driver + connector
71
69
  }
72
70
 
73
71
  override getConnectionString(): ValueOrPromise<string> {
@@ -8,7 +8,7 @@ IGNIS talks to PostgreSQL through a **driver seam**: `IRelationalDriver` owns co
8
8
  Supabase is unmodified PostgreSQL, so it is not a separate connector: it varies the **driver**, not the SQL dialect. The `@venizia/ignis/postgres/supabase` submodule adds the two things Supabase deployments actually need - pooler presets and an RLS auth-context helper.
9
9
 
10
10
  > [!IMPORTANT] Every database client is optional
11
- > `pg` and `postgres` are both **optional peer dependencies**. The `@venizia/ignis/postgres` module pulls in neither - each driver is imported lazily, only when the client you built selects it. Install the one your app uses:
11
+ > `pg` and `postgres` are both **optional peer dependencies**. The `@venizia/ignis/postgres` module pulls in neither - only the driver class you import and name in `@datasource({ driver })` reaches your bundle. Install the one your app uses:
12
12
  >
13
13
  > ```bash
14
14
  > bun add pg # node-postgres
@@ -19,60 +19,89 @@ Supabase is unmodified PostgreSQL, so it is not a separate connector: it varies
19
19
 
20
20
  | Import | Contents | Loads |
21
21
  | :--- | :--- | :--- |
22
- | `@venizia/ignis/postgres` | `BasePostgresDataSource`, `IRelationalDriver`, `resolveDatabaseDriver`, repository hierarchy | no client library |
22
+ | `@venizia/ignis/postgres` | `BasePostgresDataSource`, `IRelationalDriver`, repository hierarchy | no client library |
23
23
  | `@venizia/ignis/postgres/node-postgres` | `NodePostgresDriver` | `pg` |
24
24
  | `@venizia/ignis/postgres/postgres-js` | `PostgresJsDriver` | `postgres` |
25
25
  | `@venizia/ignis/postgres/supabase` | `PoolerModes`, `buildPostgresJsOptions`, `withAuthContext`, Supabase role re-exports | `drizzle-orm/supabase` |
26
26
 
27
- ## The Default: a Bare Pool
27
+ ## Naming the Driver Class
28
28
 
29
- Hand IGNIS a `pg.Pool` on `this.client` and it adopts it into a `NodePostgresDriver` on first use - no driver import needed:
29
+ `@datasource({ driver })` takes the driver **class**, not a driver-name string:
30
30
 
31
31
  ```typescript
32
- import { DataSourceDrivers, datasource } from '@venizia/ignis';
32
+ import { datasource, ValueOrPromise } from '@venizia/ignis';
33
33
  import { BasePostgresDataSource } from '@venizia/ignis/postgres';
34
- import { drizzle } from 'drizzle-orm/node-postgres';
34
+ import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
35
35
  import { Pool } from 'pg';
36
36
 
37
- @datasource({ driver: DataSourceDrivers.NODE_POSTGRES })
37
+ @datasource({ driver: NodePostgresDriver })
38
38
  export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfigs> {
39
- configure() {
40
- this.client = new Pool({ connectionString: this.getConnectionString() });
41
- this.connector = drizzle({ client: this.client, schema: this.getSchema() });
39
+ override configure(): ValueOrPromise<void> {
40
+ this.client = new Pool({ connectionString: this.getConnectionString() }); // that is all
42
41
  }
43
42
  }
44
43
  ```
45
44
 
46
- `this.client` is the raw-client slot: the `pg.Pool` (or postgres-js `Sql`) your `configure()` built. `getClient()` hands it back as the escape hatch, and `beginTransaction()` resolves a driver from it lazily. A datasource that sets neither `this.client` nor a driver throws `No driver and no client` on its first transaction.
45
+ `configure()` only builds `this.client` - the raw `pg.Pool` (or postgres-js `Sql`) your app's connection settings produce. The base class wires the driver **and** the connector lazily, on first call to `getConnector()` or `beginTransaction()`: it reads the class named in `@datasource({ driver })`, instantiates it over `this.client`, and builds the pooled Drizzle connector from that. `getClient()` hands `this.client` back as the raw-client escape hatch. A datasource that sets neither `this.client` nor a driver (via `useDriver()`, below) throws `No driver and no client` on first use.
46
+
47
+ > [!IMPORTANT] Why a class, not a name
48
+ > A driver-name string cannot carry `pg` or `postgres` into your bundle - it is just text. A dynamic `import('./node-postgres.js')` keyed off that string would defer *execution*, not *packaging*: every bundler statically resolves a literal specifier and packages whatever it points to, so a build that only used node-postgres would still fail with `Could not resolve: "postgres"` the moment postgres-js's import appeared anywhere in the module graph reachable at build time. Naming the class instead makes the driver module a real value reference - the one thing a bundler is forced to keep - which is what lets `pg` and `postgres` stay genuinely optional peers. A bare side-effect import (`import '@venizia/ignis/postgres/node-postgres'`) would not work either: `@venizia/ignis` declares `sideEffects: false`, so a bundler is free to drop an import whose exports go unused.
49
+ >
50
+ > Two tests pin this from different angles: `packages/core/src/__tests__/connectors/postgres/no-eager-driver-import.test.ts` proves no barrel **loads** a driver package in a fresh process (the runtime module graph), and `packages/core/src/__tests__/connectors/postgres/bundle/optional-peers.test.ts` proves no barrel gets a driver package **packaged** by a real bundler.
47
51
 
48
52
  ## Using postgres-js
49
53
 
50
- Wire a driver explicitly with `useDriver()` - it assigns the driver **and** builds the pooled connector in one step, so the half-wired state (driver set, connector forgotten) cannot exist:
54
+ Same shape, different class:
51
55
 
52
56
  ```typescript
53
- import { DataSourceDrivers, datasource } from '@venizia/ignis';
57
+ import { datasource, ValueOrPromise } from '@venizia/ignis';
54
58
  import { BasePostgresDataSource } from '@venizia/ignis/postgres';
55
59
  import { PostgresJsDriver } from '@venizia/ignis/postgres/postgres-js';
56
60
  import postgres from 'postgres';
57
61
  import type { Sql } from 'postgres';
58
62
 
59
- @datasource({ driver: DataSourceDrivers.POSTGRES_JS })
63
+ @datasource({ driver: PostgresJsDriver })
60
64
  export class PostgresDataSource extends BasePostgresDataSource<
61
65
  IDataSourceConfigs,
62
66
  typeof schema,
63
67
  {},
64
68
  Sql // getClient() is now honestly typed as postgres-js's Sql, not pg.Pool
65
69
  > {
66
- configure() {
70
+ override configure(): ValueOrPromise<void> {
71
+ this.client = postgres(this.getConnectionString());
72
+ }
73
+ }
74
+ ```
75
+
76
+ The fourth type parameter (`Client`) defaults to `pg.Pool`; declare it when the raw client escape hatch (`getClient()`) should carry the real type.
77
+
78
+ ## Driver Constructors Validate Their Client
79
+
80
+ Both shipped drivers throw immediately if constructed with the wrong shape of client, instead of failing later inside a query:
81
+
82
+ ```typescript
83
+ new NodePostgresDriver({ client: pool }); // client must expose connect() AND totalCount (pool accounting)
84
+ new PostgresJsDriver({ client: sql }); // client must expose reserve() AND unsafe()
85
+ ```
86
+
87
+ `NodePostgresDriver` rejects a bare `pg.Client` - it exposes `connect()` too, but has no pool accounting and cannot hand out a dedicated connection per transaction. `PostgresJsDriver` rejects a `pg.Pool` the same way. You will not normally construct these yourself: `wireDriverFromMetadata()` does it for you from `this.client`, so this validation fires the first time a datasource wired the wrong client behind the wrong `@datasource({ driver })` class.
88
+
89
+ ## Custom or Third-Party Drivers: `useDriver()`
90
+
91
+ For a driver IGNIS does not ship, wire it explicitly with `useDriver()` - it assigns the driver **and** builds the pooled connector in one step, so the half-wired state (driver set, connector forgotten) cannot exist:
92
+
93
+ ```typescript
94
+ export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfigs> {
95
+ override configure(): ValueOrPromise<void> {
67
96
  this.useDriver({
68
- driver: new PostgresJsDriver({ client: postgres(this.getConnectionString()) }),
97
+ driver: new MyCustomDriver({ client: myClient }),
69
98
  schema: this.getSchema(),
70
99
  });
71
100
  }
72
101
  }
73
102
  ```
74
103
 
75
- The fourth type parameter (`Client`) defaults to `pg.Pool`; declare it when the raw client escape hatch (`getClient()`) should carry the real type.
104
+ `useDriver()` bypasses `@datasource({ driver })` entirely - you never need to name a class in the decorator when you wire the driver yourself in `configure()`.
76
105
 
77
106
  > [!WARNING] postgres-js cannot destroy a poisoned connection
78
107
  > After a failed `COMMIT` or `ROLLBACK`, node-postgres **destroys** the connection instead of pooling it - the session may still hold an open transaction that the next borrower would inherit. postgres-js has no destroy semantics (`ReservedSql.release()` takes no argument), so the connection is returned to the pool anyway. This asymmetry is real and IGNIS does not paper over it; it is pinned by the driver's own tests.
@@ -117,18 +146,23 @@ Supabase exposes three ways in, and one of them silently breaks prepared stateme
117
146
  The transaction pooler (Supavisor) rebinds the backend per transaction, so a server-side prepared statement created on one backend simply is not there next time. `buildPostgresJsOptions` encodes this so you cannot forget it:
118
147
 
119
148
  ```typescript
120
- import { buildPostgresJsOptions, PoolerModes } from '@venizia/ignis/postgres/supabase';
149
+ import { datasource } from '@venizia/ignis';
150
+ import { BasePostgresDataSource } from '@venizia/ignis/postgres';
121
151
  import { PostgresJsDriver } from '@venizia/ignis/postgres/postgres-js';
152
+ import { buildPostgresJsOptions, PoolerModes } from '@venizia/ignis/postgres/supabase';
122
153
  import postgres from 'postgres';
123
154
 
124
- const client = postgres(connectionString, {
125
- ...buildPostgresJsOptions({ mode: PoolerModes.TRANSACTION, max: 10 }),
126
- });
127
-
128
- this.useDriver({ driver: new PostgresJsDriver({ client }), schema: this.getSchema() });
155
+ @datasource({ driver: PostgresJsDriver })
156
+ export class SupabaseDataSource extends BasePostgresDataSource<IDataSourceConfigs> {
157
+ override configure() {
158
+ this.client = postgres(connectionString, {
159
+ ...buildPostgresJsOptions({ mode: PoolerModes.TRANSACTION, max: 10 }),
160
+ });
161
+ }
162
+ }
129
163
  ```
130
164
 
131
- `prepare: false` is emitted only for `TRANSACTION` mode; `max` is forwarded only when you pass it, so postgres-js's own default survives.
165
+ `prepare: false` is emitted only for `TRANSACTION` mode; `max` is forwarded only when you pass it, so postgres-js's own default survives. Naming `PostgresJsDriver` in `@datasource` is what wires it - `configure()` only needs to build the client, same as node-postgres.
132
166
 
133
167
  ### Row Level Security
134
168
 
@@ -164,4 +198,4 @@ The submodule also re-exports Drizzle's Supabase helpers (`anonRole`, `authentic
164
198
 
165
199
  ## Adding a Driver
166
200
 
167
- One file under `src/connectors/postgres/drivers/`, implementing the four verbs above, plus a fake client and a test that runs the shared conformance suite (`run({ driver, resolveDatabaseDriver })` in `src/__tests__/connectors/postgres/drivers/conformance/`). Register a sub-path export and an optional peer dependency; never re-export the driver from the drivers barrel - that is what would make its package load eagerly for everyone.
201
+ One file under `src/connectors/postgres/drivers/`, implementing the four verbs above, plus a fake client and a test that runs the shared conformance suite (`run({ driver, buildDriverProbe })` in `src/__tests__/connectors/postgres/drivers/conformance/`). Register a sub-path export and an optional peer dependency; never re-export the driver from the drivers barrel - that is what would make its package load eagerly for everyone.
@@ -61,10 +61,12 @@ The `searchable` and `filterable` flags are **load-bearing here** and are silent
61
61
  import { datasource } from '@venizia/ignis';
62
62
  import { MeilisearchDataSource } from '@venizia/ignis/meilisearch';
63
63
 
64
- @datasource({ driver: 'meilisearch' })
64
+ @datasource()
65
65
  export class ArticleSearchDataSource extends MeilisearchDataSource {}
66
66
  ```
67
67
 
68
+ No `driver` in the decorator, and that is not an omission. A relational datasource has to name one, because a single `BasePostgresDataSource` runs on either `pg` or `postgres` and something must pick. A search datasource has already picked: `extends MeilisearchDataSource` **is** the engine reference, and it is what carries the `meilisearch` package into your bundle. Naming the engine twice would just be a second chance to disagree with yourself.
69
+
68
70
  Configured through `IMeilisearchDataSourceSettings`:
69
71
 
70
72
  ```typescript
@@ -176,11 +176,11 @@ await connector.linkSynonymSets({ collection: 'articles', synonymSets: ['article
176
176
 
177
177
  ```typescript
178
178
  // src/datasources/search.datasource.ts
179
- import { DataSourceDrivers, datasource } from '@venizia/ignis';
179
+ import { datasource } from '@venizia/ignis';
180
180
  import { TypesenseDataSource } from '@venizia/ignis/typesense';
181
181
  import { applicationEnvironment, int } from '@venizia/ignis-helpers';
182
182
 
183
- @datasource({ driver: DataSourceDrivers.TYPESENSE })
183
+ @datasource()
184
184
  export class SearchDataSource extends TypesenseDataSource {
185
185
  constructor() {
186
186
  super({
@@ -201,6 +201,8 @@ export class SearchDataSource extends TypesenseDataSource {
201
201
  }
202
202
  ```
203
203
 
204
+ No `driver` in the decorator, and that is not an omission. A relational datasource has to name one, because a single `BasePostgresDataSource` runs on either `pg` or `postgres` and something must pick. A search datasource has already picked: `extends TypesenseDataSource` **is** the engine reference, and it is what carries the `typesense` package into your bundle. Naming the engine twice would just be a second chance to disagree with yourself.
205
+
204
206
  `TypesenseDataSource` extends `BaseSearchDataSource` (adds auto-discovery/provisioning) which extends `AbstractSearchDataSource` (engine contract: `getDriver()`, `getQueryDialect()`, `compileCollection()`, `ensureCollection()`) which extends the engine-neutral `AbstractDataSource`. Since `TypesenseDataSource` never overrides `beginTransaction()`, it inherits the neutral `NotSupported` default - see [Connectors](/references/base/connectors).
205
207
 
206
208
  On `configure()`, the datasource auto-provisions every discovered collection (`ensureCollection()` per definition, plus any declared `synonyms`) unless constructed with `autoProvision: false`.
@@ -179,7 +179,7 @@ import {
179
179
  datasource,
180
180
  ValueOrPromise,
181
181
  } from '@venizia/ignis';
182
- import { drizzle } from 'drizzle-orm/node-postgres';
182
+ import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
183
183
  import { Pool } from 'pg';
184
184
 
185
185
  interface IDSConfigs {
@@ -195,15 +195,14 @@ interface IDSConfigs {
195
195
  *
196
196
  * How it works:
197
197
  * 1. @repository decorator binds model to datasource
198
- * 2. When configure() is called, getSchema() auto-discovers all bound models
199
- * 3. Drizzle is initialized with the auto-discovered schema
198
+ * 2. getSchema() auto-discovers all bound models when the driver/connector are wired
199
+ * 3. Naming NodePostgresDriver in @datasource is what wires the driver and Drizzle connector
200
200
  */
201
- @datasource({ driver: 'node-postgres' })
201
+ @datasource({ driver: NodePostgresDriver })
202
202
  export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
203
203
  constructor() {
204
204
  super({
205
205
  name: PostgresDataSource.name,
206
- // Driver is read from @datasource decorator - no need to pass here!
207
206
  config: {
208
207
  host: process.env.APP_ENV_POSTGRES_HOST ?? 'localhost',
209
208
  port: +(process.env.APP_ENV_POSTGRES_PORT ?? 5432),
@@ -216,11 +215,8 @@ export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
216
215
  }
217
216
 
218
217
  override configure(): ValueOrPromise<void> {
219
- // getSchema() auto-discovers models from @repository bindings
220
- const schema = this.getSchema();
221
-
222
- // Log discovered schema for debugging
223
- const schemaKeys = Object.keys(schema);
218
+ // getSchema() auto-discovers models from @repository bindings; log it for debugging
219
+ const schemaKeys = Object.keys(this.getSchema());
224
220
  this.logger.debug(
225
221
  '[configure] Auto-discovered schema | Schema + Relations (%s): %o',
226
222
  schemaKeys.length,
@@ -228,9 +224,9 @@ export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
228
224
  );
229
225
 
230
226
  // The client must land on this.client - a local would leave beginTransaction() with nothing
231
- // to resolve a driver from, and it would throw `No driver and no client`.
227
+ // to resolve a driver from, and it would throw `No driver and no client`. NodePostgresDriver
228
+ // named in @datasource above is what wires the driver and Drizzle connector from it.
232
229
  this.client = new Pool(this.settings);
233
- this.connector = drizzle({ client: this.client, schema });
234
230
  }
235
231
  }
236
232
  ```
@@ -239,7 +235,7 @@ export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
239
235
  - Schema is auto-discovered from `@repository` decorators - no manual registration needed
240
236
  - Uses `getSchema()` for lazy schema resolution (resolves when all models are loaded)
241
237
  - Uses environment variables for connection config
242
- - Implements `configure()` for connection setup and `getConnectionString()` for URL generation
238
+ - `configure()` only assigns `this.client` - the base class wires the driver and connector from `@datasource({ driver })`; implements `getConnectionString()` for URL generation
243
239
 
244
240
  > **Deep Dive:** See [DataSources Reference](/references/base/datasources) for advanced configuration and multiple database support.
245
241
 
@@ -357,7 +357,7 @@ import {
357
357
  datasource,
358
358
  ValueOrPromise,
359
359
  } from '@venizia/ignis';
360
- import { drizzle } from 'drizzle-orm/node-postgres';
360
+ import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
361
361
  import { Pool } from 'pg';
362
362
 
363
363
  interface IDSConfigs {
@@ -368,7 +368,7 @@ interface IDSConfigs {
368
368
  password: string;
369
369
  }
370
370
 
371
- @datasource({ driver: 'node-postgres' })
371
+ @datasource({ driver: NodePostgresDriver })
372
372
  export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
373
373
  constructor() {
374
374
  super({
@@ -384,18 +384,18 @@ export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
384
384
  }
385
385
 
386
386
  override configure(): ValueOrPromise<void> {
387
- const schema = this.getSchema();
387
+ const schema = Object.keys(this.getSchema());
388
388
 
389
389
  this.logger.debug(
390
390
  '[configure] Auto-discovered schema | Schema + Relations (%s): %o',
391
- Object.keys(schema).length,
392
- Object.keys(schema),
391
+ schema.length,
392
+ schema,
393
393
  );
394
394
 
395
395
  // The client must land on this.client - a local would leave beginTransaction() with nothing
396
- // to resolve a driver from, and it would throw `No driver and no client`.
396
+ // to resolve a driver from, and it would throw `No driver and no client`. NodePostgresDriver
397
+ // named in @datasource above is what wires the driver and Drizzle connector from it.
397
398
  this.client = new Pool(this.settings);
398
- this.connector = drizzle({ client: this.client, schema });
399
399
  }
400
400
  }
401
401
  ```
@@ -268,7 +268,7 @@ import {
268
268
  datasource,
269
269
  ValueOrPromise,
270
270
  } from '@venizia/ignis';
271
- import { drizzle } from 'drizzle-orm/node-postgres';
271
+ import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
272
272
  import { Pool } from 'pg';
273
273
 
274
274
  interface IDSConfigs {
@@ -279,7 +279,7 @@ interface IDSConfigs {
279
279
  password: string;
280
280
  }
281
281
 
282
- @datasource({ driver: 'node-postgres' })
282
+ @datasource({ driver: NodePostgresDriver })
283
283
  export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
284
284
  constructor() {
285
285
  super({
@@ -295,18 +295,18 @@ export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
295
295
  }
296
296
 
297
297
  override configure(): ValueOrPromise<void> {
298
- const schema = this.getSchema();
298
+ const schema = Object.keys(this.getSchema());
299
299
 
300
300
  this.logger.debug(
301
301
  '[configure] Auto-discovered schema | Schema + Relations (%s): %o',
302
- Object.keys(schema).length,
303
- Object.keys(schema),
302
+ schema.length,
303
+ schema,
304
304
  );
305
305
 
306
306
  // The client must land on this.client - a local would leave beginTransaction() with nothing
307
- // to resolve a driver from, and it would throw `No driver and no client`.
307
+ // to resolve a driver from, and it would throw `No driver and no client`. NodePostgresDriver
308
+ // named in @datasource above is what wires the driver and Drizzle connector from it.
308
309
  this.client = new Pool(this.settings);
309
- this.connector = drizzle({ client: this.client, schema });
310
310
  }
311
311
  }
312
312
  ```
@@ -105,7 +105,7 @@ IGNIS ships both a **root barrel** (backward-compatible default) and **per-engin
105
105
  ```
106
106
 
107
107
  > [!IMPORTANT] Concrete Postgres drivers are sub-path only
108
- > `pg` and `postgres` are **both optional peer dependencies** (`peerDependenciesMeta.pg.optional` / `.postgres.optional`). The root barrel, `@venizia/ignis/postgres`, and the drivers barrel (`connectors/postgres/drivers/index.ts`) load **zero** `pg`/`postgres` modules - the drivers barrel exports only the neutral contract plus `resolveDatabaseDriver`. A concrete driver value-imports its own client, so reach it by its own sub-path only: `@venizia/ignis/postgres/node-postgres` or `@venizia/ignis/postgres/postgres-js`. `resolveDatabaseDriver({ client })` structurally detects which client the app built and lazily `import()`s only the matching driver. This is what keeps IGNIS from forcing a database client on a project that does not use one. See [Postgres Drivers & Supabase](/guides/core-concepts/persistent/postgres-drivers).
108
+ > `pg` and `postgres` are **both optional peer dependencies** (`peerDependenciesMeta.pg.optional` / `.postgres.optional`). The root barrel, `@venizia/ignis/postgres`, and the drivers barrel (`connectors/postgres/drivers/index.ts`) load **zero** `pg`/`postgres` modules - the drivers barrel exports only the neutral `IRelationalDriver` contract. A concrete driver value-imports its own client, so reach it by its own sub-path only: `@venizia/ignis/postgres/node-postgres` or `@venizia/ignis/postgres/postgres-js`. There is no structural client sniffing: `@datasource({ driver })` names the driver **class** directly (`NodePostgresDriver`/`PostgresJsDriver`), and the base datasource's `wireDriverFromMetadata()` instantiates that named class over `this.client` lazily, on first `getConnector()`/`beginTransaction()`. Naming a class rather than a driver-name string is what carries `pg`/`postgres` into the app's bundle - a bundler only packages a real value reference, never text, and a bare side-effect import would not survive `sideEffects: false`. This is what keeps IGNIS from forcing a database client on a project that does not use one. See [Postgres Drivers & Supabase](/guides/core-concepts/persistent/postgres-drivers).
109
109
 
110
110
  > [!NOTE] `internal/` modules are not public API
111
111
  > The search connectors (`search`/`typesense`/`meilisearch`) no longer re-export their `internal/` barrels (`SearchConnectorInternal`, `TypesenseInternal`, `MeilisearchInternal`) from the connector barrel. There is no sub-path export for them either, and package `exports` blocks deep imports - so no specifier reaches them from outside the package. They are implementation detail; if you need what they do, ask for a supported API.
@@ -133,11 +133,11 @@ abstract class AbstractPostgresDataSource<
133
133
  | Property | Type | Visibility | Description |
134
134
  |----------|------|------------|-------------|
135
135
  | `connector` | `TRelationalConnector<Schema>` | public | Drizzle ORM instance (any Drizzle pg driver satisfies this - see the driver seam below) |
136
- | `driver` | `IRelationalDriver` | protected | The connection driver (`node-postgres` or `postgres-js`); built by `useDriver()` |
137
- | `client` | `Client` (`Pool` by default) | protected | The raw driver client `configure()` built - a `pg.Pool`, or a postgres-js `Sql`. Assigning it alone is enough: a driver is resolved from it on first use. Absent once `useDriver()` wired a driver instead |
136
+ | `driver` | `IRelationalDriver` | protected | The connection driver (`node-postgres` or `postgres-js`); built lazily by `wireDriverFromMetadata()` from the class named in `@datasource({ driver })`, or explicitly by `useDriver()` |
137
+ | `client` | `Client` (`Pool` by default) | protected | The raw driver client `configure()` built - a `pg.Pool`, or a postgres-js `Sql`. Assigning it alone is enough: `wireDriverFromMetadata()` instantiates the `@datasource({ driver })` class over it on first use. Absent once `useDriver()` wired a driver instead |
138
138
 
139
139
  > [!NOTE] Driver seam
140
- > `AbstractRelationalDataSource`/`BaseRelationalDataSource` (exported as `AbstractPostgresDataSource`/`BasePostgresDataSource`) now take a fourth generic - `<Settings, Schema, ConfigurableOptions, Client = Pool>` - so a `postgres-js` datasource can declare `Client = Sql` and keep `getClient()` honest. The protected `useDriver({ driver, schema? })` assigns `this.driver` **and** builds `this.connector` in one step. `pg` and `postgres` are both optional peer dependencies; concrete drivers live at `@venizia/ignis/postgres/node-postgres` and `@venizia/ignis/postgres/postgres-js`, and Supabase support at `@venizia/ignis/postgres/supabase`. See [Postgres Drivers & Supabase](/guides/core-concepts/persistent/postgres-drivers).
140
+ > `AbstractRelationalDataSource`/`BaseRelationalDataSource` (exported as `AbstractPostgresDataSource`/`BasePostgresDataSource`) now take a fourth generic - `<Settings, Schema, ConfigurableOptions, Client = Pool>` - so a `postgres-js` datasource can declare `Client = Sql` and keep `getClient()` honest. `@datasource({ driver })` names the driver **class** (`NodePostgresDriver` or `PostgresJsDriver`), never a string - a driver-name string cannot carry `pg`/`postgres` into the app's bundle, only a real class reference can. `configure()` only needs to assign `this.client`; the protected `wireDriverFromMetadata()` (called internally by `getConnector()`/`resolveDriver()`) instantiates the named class over it and builds `this.connector`, lazily and idempotently. The protected `useDriver({ driver, schema? })` stays available for a custom or third-party driver - it assigns `this.driver` **and** builds `this.connector` in one step, bypassing `@datasource({ driver })` entirely. `pg` and `postgres` are both optional peer dependencies; concrete drivers live at `@venizia/ignis/postgres/node-postgres` and `@venizia/ignis/postgres/postgres-js`, and Supabase support at `@venizia/ignis/postgres/supabase`. See [Postgres Drivers & Supabase](/guides/core-concepts/persistent/postgres-drivers).
141
141
 
142
142
  **Additional abstract method:**
143
143
 
@@ -202,9 +202,8 @@ When you use `@repository({ model: YourModel, dataSource: YourDataSource })`, th
202
202
  - It calls the `configure()` method on your instance
203
203
 
204
204
  3. **Your `configure()` method runs**:
205
- - Call `this.getSchema()` to get the auto-discovered schema
206
- - Create a `Pool` instance and assign it to `this.client` (required for transaction support)
207
- - Create the Drizzle connector from that client and the schema
205
+ - Create a `Pool` instance and assign it to `this.client` - that is the whole method
206
+ - `getConnector()`/`beginTransaction()` lazily instantiate the class named in `@datasource({ driver })` over `this.client` and build the Drizzle connector from it - your `configure()` never touches `this.connector` directly
208
207
 
209
208
  ### Example Implementations
210
209
 
@@ -215,8 +214,8 @@ Simplest approach - schema is auto-discovered from repositories:
215
214
  ```typescript
216
215
  // src/datasources/postgres.datasource.ts
217
216
  import { BasePostgresDataSource, datasource } from '@venizia/ignis';
217
+ import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
218
218
  import { applicationEnvironment, int, ValueOrPromise } from '@venizia/ignis-helpers';
219
- import { drizzle } from 'drizzle-orm/node-postgres';
220
219
  import { Pool } from 'pg';
221
220
 
222
221
  interface IDataSourceConfigs {
@@ -233,10 +232,10 @@ interface IDataSourceConfigs {
233
232
  *
234
233
  * How it works:
235
234
  * 1. @repository decorator binds model to datasource
236
- * 2. When configure() is called, getSchema() auto-discovers all bound models
237
- * 3. Drizzle is initialized with the auto-discovered schema
235
+ * 2. getSchema() auto-discovers all bound models when the driver/connector are wired
236
+ * 3. Naming NodePostgresDriver in @datasource is what wires the driver and Drizzle connector
238
237
  */
239
- @datasource({ driver: 'node-postgres' })
238
+ @datasource({ driver: NodePostgresDriver })
240
239
  export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfigs> {
241
240
  private readonly protocol = 'postgresql';
242
241
 
@@ -256,19 +255,15 @@ export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfig
256
255
  }
257
256
 
258
257
  override configure(): ValueOrPromise<void> {
259
- // getSchema() auto-discovers models from @repository bindings
260
- const schema = this.getSchema();
261
-
262
- const dataSourceSchema = Object.keys(schema);
258
+ const schema = Object.keys(this.getSchema());
263
259
  this.logger.debug(
264
260
  '[configure] Auto-discovered schema | Schema + Relations (%s): %o',
265
- dataSourceSchema.length,
266
- dataSourceSchema,
261
+ schema.length,
262
+ schema,
267
263
  );
268
264
 
269
- // The client slot is what beginTransaction() resolves its driver from
265
+ // That is all - the base class wires the driver + connector from @datasource({ driver }).
270
266
  this.client = new Pool(this.settings);
271
- this.connector = drizzle({ client: this.client, schema });
272
267
  }
273
268
 
274
269
  override getConnectionString(): ValueOrPromise<string> {
@@ -295,12 +290,13 @@ The `PostgresDataSource.schema` will automatically include User and Configuratio
295
290
  When you need explicit control over schema (e.g., subset of models, custom ordering):
296
291
 
297
292
  ```typescript
293
+ import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
298
294
  import {
299
295
  User, userTable, userRelations,
300
296
  Configuration, configurationTable, configurationRelations,
301
297
  } from '@/models/entities';
302
298
 
303
- @datasource({ driver: 'node-postgres' })
299
+ @datasource({ driver: NodePostgresDriver })
304
300
  export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfigs> {
305
301
  constructor() {
306
302
  super({
@@ -323,9 +319,8 @@ export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfig
323
319
  }
324
320
 
325
321
  override configure(): ValueOrPromise<void> {
326
- // When schema is manually provided, getSchema() returns it directly
322
+ // Manually-provided schema is used as-is by the connector the base class builds from this.client
327
323
  this.client = new Pool(this.settings);
328
- this.connector = drizzle({ client: this.client, schema: this.getSchema() });
329
324
  }
330
325
 
331
326
  override getConnectionString(): ValueOrPromise<string> {
@@ -335,7 +330,7 @@ export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfig
335
330
  ```
336
331
 
337
332
  > [!IMPORTANT]
338
- > Your `configure()` must leave the datasource with a way to reach the database: either assign the raw client to `this.client`, or wire a driver with `this.useDriver({ driver })`. `beginTransaction()` resolves a driver lazily from whichever you provided. With neither, it throws `No driver and no client`.
333
+ > Your `configure()` must leave the datasource with a way to reach the database: either assign the raw client to `this.client` (paired with naming the driver class in `@datasource({ driver })`), or wire a driver directly with `this.useDriver({ driver })` for a custom or third-party driver. `getConnector()`/`beginTransaction()` resolve the driver lazily from whichever you provided. With neither, it throws `No driver and no client`.
339
334
 
340
335
  ### `@datasource` Decorator
341
336
 
@@ -343,14 +338,14 @@ The `@datasource` decorator registers datasource metadata:
343
338
 
344
339
  ```typescript
345
340
  @datasource({
346
- driver: 'node-postgres', // Required - database driver
341
+ driver: NodePostgresDriver, // Required - driver CLASS (or a search engine's driver-name string)
347
342
  autoDiscovery?: true // Optional - defaults to true
348
343
  })
349
344
  ```
350
345
 
351
346
  | Option | Type | Default | Description |
352
347
  |--------|------|---------|-------------|
353
- | `driver` | `TDataSourceDriver` | - | Driver name - `DataSourceDrivers` defines four constants: `'node-postgres'` and `'postgres-js'` (relational), `'typesense'` and `'meilisearch'` (search); any other engine-driver string is also accepted |
348
+ | `driver` | `TDataSourceDriverClass` | - | The driver **class** - `NodePostgresDriver` or `PostgresJsDriver` (imported from `@venizia/ignis/postgres/node-postgres` / `.../postgres-js`), never a driver-name string. A class reference is the only thing that carries `pg`/`postgres` into the app's bundle. **Omit it for a search datasource**: `extends TypesenseDataSource` already names the engine, and is what carries `typesense` into the bundle |
354
349
  | `autoDiscovery` | `boolean` | `true` | Enable/disable schema auto-discovery |
355
350
 
356
351
  ### Abstract Methods
@@ -359,7 +354,7 @@ When extending `BasePostgresDataSource`, these methods must be implemented:
359
354
 
360
355
  | Method | Return Type | Description |
361
356
  |--------|-------------|-------------|
362
- | `configure(opts?)` | `ValueOrPromise<void>` | Initialize the client and Drizzle connector. Must set `this.client` (or call `this.useDriver()`) and `this.connector`. |
357
+ | `configure(opts?)` | `ValueOrPromise<void>` | Initialize the client. Must set `this.client` (the base class wires the driver and Drizzle connector from `@datasource({ driver })`), or call `this.useDriver()` directly for a custom driver. |
363
358
  | `getConnectionString()` | `ValueOrPromise<string>` | Return the database connection string. |
364
359
 
365
360
  ### Helper Methods
@@ -401,6 +396,9 @@ DataSourceDrivers.MEILISEARCH // 'meilisearch'
401
396
  DataSourceDrivers.isValid('node-postgres') // true
402
397
  ```
403
398
 
399
+ > [!NOTE]
400
+ > `NODE_POSTGRES`/`POSTGRES_JS` remain valid `TDataSourceDriver` string values, but `@datasource({ driver })` on a **relational** datasource no longer accepts them - it takes the `NodePostgresDriver`/`PostgresJsDriver` class instead (see [Postgres Drivers & Supabase](/guides/core-concepts/persistent/postgres-drivers)). Search connectors (`TYPESENSE`, `MEILISEARCH`) still take the driver-name string form.
401
+
404
402
  ## Transaction Support
405
403
 
406
404
  Only engines that declare `getCapabilities().transactions === true` implement real transactions - currently just the PostgreSQL connector. Calling `beginTransaction()` on the typesense connector throws `NotSupported` (HTTP 501).
@@ -157,7 +157,9 @@ APP_ENV_POSTGRES_DATABASE=my_app_prod
157
157
  ### DataSource Configuration
158
158
 
159
159
  ```typescript
160
- @datasource({ driver: 'node-postgres' })
160
+ import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
161
+
162
+ @datasource({ driver: NodePostgresDriver })
161
163
  export class PostgresDataSource extends BaseDataSource {
162
164
  constructor() {
163
165
  super({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@venizia/ignis-docs",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Interactive documentation site and MCP (Model Context Protocol) server for the Ignis Framework. Includes a VitePress-powered documentation site with guides, API references, and best practices. Ships an MCP server (CLI: ignis-docs-mcp) with 11 tools for AI assistants to search docs, browse source code, verify dependencies, and access real-time framework knowledge. Built with Mastra MCP SDK and Fuse.js fuzzy search.",
5
5
  "keywords": [
6
6
  "ai",