@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.
- package/content/best-practices/data-modeling.md +5 -5
- package/content/best-practices/performance-optimization.md +5 -4
- package/content/extensions/helpers/kafka/compile-binary.md +92 -0
- package/content/extensions/helpers/kafka/index.md +3 -0
- package/content/guides/core-concepts/persistent/datasources.md +13 -20
- package/content/guides/core-concepts/persistent/index.md +2 -4
- package/content/guides/core-concepts/persistent/postgres-drivers.md +59 -25
- package/content/guides/core-concepts/persistent/search-meilisearch.md +3 -1
- package/content/guides/core-concepts/persistent/search-typesense.md +4 -2
- package/content/guides/tutorials/building-a-crud-api.md +9 -13
- package/content/guides/tutorials/ecommerce-api.md +7 -7
- package/content/guides/tutorials/realtime-chat.md +7 -7
- package/content/references/base/connectors.md +1 -1
- package/content/references/base/datasources.md +23 -25
- package/content/references/configuration/environment-variables.md +3 -1
- package/package.json +1 -1
|
@@ -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 {
|
|
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:
|
|
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
|
-
|
|
322
|
+
this.logger.debug('[configure] Auto-discovered schema | Keys: %o', Object.keys(this.getSchema()));
|
|
323
323
|
|
|
324
|
-
// Keep the pool on this.client -
|
|
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 {
|
|
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` -
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
46
|
-
|
|
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:
|
|
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.
|
|
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:
|
|
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:
|
|
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() #
|
|
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 {
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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 -
|
|
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`,
|
|
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
|
-
##
|
|
27
|
+
## Naming the Driver Class
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
`@datasource({ driver })` takes the driver **class**, not a driver-name string:
|
|
30
30
|
|
|
31
31
|
```typescript
|
|
32
|
-
import {
|
|
32
|
+
import { datasource, ValueOrPromise } from '@venizia/ignis';
|
|
33
33
|
import { BasePostgresDataSource } from '@venizia/ignis/postgres';
|
|
34
|
-
import {
|
|
34
|
+
import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
|
|
35
35
|
import { Pool } from 'pg';
|
|
36
36
|
|
|
37
|
-
@datasource({ driver:
|
|
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`
|
|
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
|
-
|
|
54
|
+
Same shape, different class:
|
|
51
55
|
|
|
52
56
|
```typescript
|
|
53
|
-
import {
|
|
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:
|
|
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
|
|
97
|
+
driver: new MyCustomDriver({ client: myClient }),
|
|
69
98
|
schema: this.getSchema(),
|
|
70
99
|
});
|
|
71
100
|
}
|
|
72
101
|
}
|
|
73
102
|
```
|
|
74
103
|
|
|
75
|
-
|
|
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 {
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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,
|
|
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(
|
|
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 {
|
|
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(
|
|
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 {
|
|
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.
|
|
199
|
-
* 3.
|
|
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:
|
|
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
|
|
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
|
-
-
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
392
|
-
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
303
|
-
|
|
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
|
|
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:
|
|
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
|
-
-
|
|
206
|
-
-
|
|
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.
|
|
237
|
-
* 3.
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
266
|
-
|
|
261
|
+
schema.length,
|
|
262
|
+
schema,
|
|
267
263
|
);
|
|
268
264
|
|
|
269
|
-
//
|
|
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:
|
|
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
|
-
//
|
|
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
|
|
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:
|
|
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` | `
|
|
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
|
|
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
|
-
|
|
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.
|
|
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",
|