@venizia/ignis-docs 0.0.8 → 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/README.md +7 -7
- package/content/best-practices/api-usage-examples.md +15 -12
- package/content/best-practices/architectural-patterns.md +70 -78
- package/content/best-practices/architecture-decisions.md +91 -60
- package/content/best-practices/code-style-standards/advanced-patterns.md +56 -44
- package/content/best-practices/code-style-standards/constants-configuration.md +11 -11
- package/content/best-practices/code-style-standards/control-flow.md +5 -2
- package/content/best-practices/code-style-standards/documentation.md +13 -13
- package/content/best-practices/code-style-standards/function-patterns.md +9 -10
- package/content/best-practices/code-style-standards/index.md +1 -1
- package/content/best-practices/code-style-standards/naming-conventions.md +10 -8
- package/content/best-practices/code-style-standards/route-definitions.md +30 -12
- package/content/best-practices/code-style-standards/tooling.md +8 -5
- package/content/best-practices/code-style-standards/type-safety.md +13 -12
- package/content/best-practices/common-pitfalls.md +56 -37
- package/content/best-practices/contribution-workflow.md +13 -14
- package/content/best-practices/data-modeling.md +46 -22
- package/content/best-practices/deployment-strategies.md +28 -27
- package/content/best-practices/error-handling.md +48 -24
- package/content/best-practices/index.md +5 -5
- package/content/best-practices/performance-optimization.md +40 -31
- package/content/best-practices/security-guidelines.md +52 -23
- package/content/best-practices/testing-strategies.md +65 -51
- package/content/best-practices/troubleshooting-tips.md +24 -24
- package/content/extensions/components/{swagger.md → api-reference.md} +40 -31
- package/content/extensions/components/authentication/api.md +19 -19
- package/content/extensions/components/authentication/errors.md +7 -7
- package/content/extensions/components/authentication/index.md +10 -8
- package/content/extensions/components/authentication/usage.md +101 -6
- package/content/extensions/components/authorization/api.md +45 -25
- package/content/extensions/components/authorization/errors.md +6 -6
- package/content/extensions/components/authorization/index.md +11 -10
- package/content/extensions/components/authorization/usage.md +21 -21
- package/content/extensions/components/health-check.md +1 -1
- package/content/extensions/components/index.md +5 -5
- package/content/extensions/components/mail/errors.md +15 -15
- package/content/extensions/components/mail/index.md +1 -2
- package/content/extensions/components/mail/usage.md +1 -1
- package/content/extensions/components/request-tracker.md +1 -1
- package/content/extensions/components/socket-io/api.md +9 -9
- package/content/extensions/components/socket-io/errors.md +5 -5
- package/content/extensions/components/socket-io/index.md +8 -8
- package/content/extensions/components/socket-io/usage.md +1 -1
- package/content/extensions/components/static-asset/api.md +17 -4
- package/content/extensions/components/static-asset/errors.md +4 -4
- package/content/extensions/components/static-asset/index.md +26 -28
- package/content/extensions/components/static-asset/usage.md +13 -12
- package/content/extensions/components/template/index.md +2 -2
- package/content/extensions/components/template/setup-page.md +1 -1
- package/content/extensions/components/websocket/api.md +3 -3
- package/content/extensions/components/websocket/errors.md +5 -5
- package/content/extensions/components/websocket/index.md +5 -5
- package/content/extensions/components/websocket/usage.md +3 -3
- package/content/extensions/helpers/cron/index.md +2 -2
- package/content/extensions/helpers/crypto/index.md +1 -1
- package/content/extensions/helpers/env/index.md +27 -12
- package/content/extensions/helpers/error/index.md +81 -25
- package/content/extensions/helpers/index.md +2 -3
- package/content/extensions/helpers/inversion/index.md +15 -7
- package/content/extensions/helpers/kafka/compile-binary.md +92 -0
- package/content/extensions/helpers/kafka/examples.md +1 -1
- package/content/extensions/helpers/kafka/index.md +3 -0
- package/content/extensions/helpers/logger/index.md +32 -2
- package/content/extensions/helpers/network/index.md +6 -0
- package/content/extensions/helpers/queue/index.md +14 -17
- package/content/extensions/helpers/redis/index.md +548 -323
- package/content/extensions/helpers/socket-io/index.md +14 -10
- package/content/extensions/helpers/storage/api.md +44 -8
- package/content/extensions/helpers/storage/index.md +43 -7
- package/content/extensions/helpers/template/index.md +6 -3
- package/content/extensions/helpers/types/index.md +11 -8
- package/content/extensions/helpers/websocket/api.md +9 -9
- package/content/extensions/helpers/websocket/index.md +7 -7
- package/content/extensions/helpers/worker-thread/index.md +2 -2
- package/content/extensions/index.md +3 -4
- package/content/extensions/src-details/mcp-server.md +18 -24
- package/content/guides/core-concepts/application/bootstrapping.md +11 -14
- package/content/guides/core-concepts/application/index.md +3 -3
- package/content/guides/core-concepts/components.md +19 -10
- package/content/guides/core-concepts/dependency-injection.md +6 -3
- package/content/guides/core-concepts/grpc-controllers.md +6 -5
- package/content/guides/core-concepts/persistent/datasources.md +42 -43
- package/content/guides/core-concepts/persistent/index.md +16 -7
- package/content/guides/core-concepts/persistent/models.md +24 -20
- package/content/guides/core-concepts/persistent/postgres-drivers.md +201 -0
- package/content/guides/core-concepts/persistent/repositories.md +40 -23
- package/content/guides/core-concepts/persistent/search-meilisearch.md +185 -0
- package/content/guides/core-concepts/persistent/search-typesense.md +431 -0
- package/content/guides/core-concepts/persistent/transactions.md +61 -25
- package/content/guides/core-concepts/rest-controllers.md +12 -9
- package/content/guides/core-concepts/services.md +330 -60
- package/content/guides/get-started/5-minute-quickstart.md +15 -15
- package/content/guides/get-started/philosophy.md +36 -36
- package/content/guides/get-started/setup.md +3 -3
- package/content/guides/index.md +3 -3
- package/content/guides/migrations/redis-helpers-migration.md +177 -0
- package/content/guides/migrations/scoped-rbac-migration.md +17 -17
- package/content/guides/migrations/unified-connectors-migration.md +113 -0
- package/content/guides/reference/glossary.md +19 -12
- package/content/guides/reference/mcp-docs-server.md +22 -18
- package/content/guides/tutorials/building-a-crud-api.md +37 -44
- package/content/guides/tutorials/complete-installation.md +17 -17
- package/content/guides/tutorials/ecommerce-api.md +163 -124
- package/content/guides/tutorials/realtime-chat.md +181 -135
- package/content/guides/tutorials/testing.md +65 -523
- package/content/index.md +2 -180
- package/content/public/apple-touch-icon.png +0 -0
- package/content/public/og-image.png +0 -0
- package/content/public/site.webmanifest +11 -0
- package/content/references/base/application.md +4 -5
- package/content/references/base/bootstrapping.md +18 -5
- package/content/references/base/components.md +149 -120
- package/content/references/base/connectors.md +178 -0
- package/content/references/base/controllers.md +41 -30
- package/content/references/base/datasources.md +163 -92
- package/content/references/base/dependency-injection.md +34 -22
- package/content/references/base/filter-system/application-usage.md +17 -14
- package/content/references/base/filter-system/array-operators.md +7 -2
- package/content/references/base/filter-system/comparison-operators.md +3 -0
- package/content/references/base/filter-system/default-filter.md +89 -71
- package/content/references/base/filter-system/fields-order-pagination.md +22 -22
- package/content/references/base/filter-system/index.md +6 -3
- package/content/references/base/filter-system/json-filtering.md +20 -1
- package/content/references/base/filter-system/list-operators.md +1 -1
- package/content/references/base/filter-system/logical-operators.md +33 -1
- package/content/references/base/filter-system/null-operators.md +30 -1
- package/content/references/base/filter-system/quick-reference.md +23 -4
- package/content/references/base/filter-system/tips.md +5 -5
- package/content/references/base/filter-system/use-cases.md +12 -12
- package/content/references/base/grpc-controllers.md +13 -13
- package/content/references/base/index.md +24 -12
- package/content/references/base/middlewares.md +265 -327
- package/content/references/base/models.md +63 -49
- package/content/references/base/providers.md +136 -130
- package/content/references/base/repositories/advanced.md +59 -58
- package/content/references/base/repositories/index.md +115 -91
- package/content/references/base/repositories/mixins.md +55 -291
- package/content/references/base/repositories/relations.md +54 -64
- package/content/references/base/repositories/soft-deletable.md +31 -30
- package/content/references/base/services.md +296 -93
- package/content/references/configuration/environment-variables.md +49 -31
- package/content/references/configuration/index.md +6 -6
- package/content/references/index.md +17 -12
- package/content/references/quick-reference.md +65 -106
- package/content/references/utilities/crypto.md +65 -23
- package/content/references/utilities/index.md +3 -3
- package/content/references/utilities/jsx.md +6 -4
- package/content/references/utilities/module.md +68 -20
- package/content/references/utilities/parse.md +4 -14
- package/content/references/utilities/promise.md +9 -7
- package/content/references/utilities/schema.md +5 -3
- package/dist/mcp-server/common/guards.d.ts +8 -0
- package/dist/mcp-server/common/guards.d.ts.map +1 -0
- package/dist/mcp-server/common/guards.js +14 -0
- package/dist/mcp-server/common/guards.js.map +1 -0
- package/dist/mcp-server/common/index.d.ts +1 -0
- package/dist/mcp-server/common/index.d.ts.map +1 -1
- package/dist/mcp-server/common/index.js +1 -0
- package/dist/mcp-server/common/index.js.map +1 -1
- package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
- package/dist/mcp-server/helpers/docs.helper.js +4 -2
- package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
- package/dist/mcp-server/helpers/github.helper.js +1 -1
- package/dist/mcp-server/index.js +7 -2
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/mcp-server/tools/base.tool.d.ts +6 -2
- package/dist/mcp-server/tools/base.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/base.tool.js.map +1 -1
- package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.js +4 -1
- package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +3 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -1
- package/package.json +9 -9
- package/content/extensions/helpers/testing/index.md +0 -510
- package/content/references/base/middleware.md +0 -347
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Connectors Reference
|
|
3
|
+
description: How IGNIS separates engine-neutral base classes from per-engine connectors, dual-door exports, and adding a new engine
|
|
4
|
+
difficulty: advanced
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Deep Dive: Connectors
|
|
8
|
+
|
|
9
|
+
IGNIS's persistence layer is split into two layers: an **engine-neutral root** and a set of **connectors**, one per storage engine. This page documents that architecture, the dual-door export strategy, naming symmetry between engines, and how to add a new connector.
|
|
10
|
+
|
|
11
|
+
**Files:** `packages/core/src/base/**` (neutral) and `packages/core/src/connectors/{postgres,typesense,meilisearch,search}/**` (per-engine)
|
|
12
|
+
|
|
13
|
+
## Why the split
|
|
14
|
+
|
|
15
|
+
Before the restructure, `AbstractDataSource`/`AbstractRepository`/`AbstractEntity` were PostgreSQL-and-Drizzle-aware from the start - `pool`, `connector` (Drizzle instance), and SQL-shaped `TWhere`/`TFilter` lived directly in `src/base`. Adding typesense (a document search engine, not a relational database) exposed the coupling: it has no connection pool, doesn't speak SQL, and doesn't support transactions or row-level locking.
|
|
16
|
+
|
|
17
|
+
The fix moves everything engine-specific out of `src/base` and into `src/connectors/<engine>/`, leaving `src/base` with only what every engine can implement:
|
|
18
|
+
|
|
19
|
+
| Concept | Neutral (`src/base`) | PostgreSQL (`connectors/postgres`) | Typesense (`connectors/typesense`) |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| DataSource | `AbstractDataSource` | `AbstractPostgresDataSource` → `BasePostgresDataSource` | `AbstractSearchDataSource` → `BaseSearchDataSource` → `TypesenseDataSource` |
|
|
22
|
+
| Entity | `AbstractEntity` | `BasePostgresEntity` (alias `BaseEntity`) | `BaseSearchEntity` |
|
|
23
|
+
| Repository | `AbstractRepository` | `PostgresBaseRepository` → `ReadableRepository`/`PersistableRepository`/`DefaultCRUDRepository`/`SoftDeletableRepository` | `TypesenseBaseRepository` → `ReadableSearchRepository`/`PersistableSearchRepository`/`DefaultSearchRepository` |
|
|
24
|
+
| Transactions | throws `NotSupported` by default | real transactions, 3 isolation levels | throws `NotSupported` |
|
|
25
|
+
| Filtering | neutral `TWhere`/`TFilter` | SQL WHERE via Drizzle | typesense query DSL via `TypesenseQueryDialect` |
|
|
26
|
+
|
|
27
|
+
## The neutral contract
|
|
28
|
+
|
|
29
|
+
### `AbstractDataSource`
|
|
30
|
+
|
|
31
|
+
`packages/core/src/base/datasources/abstract.ts` declares:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
abstract class AbstractDataSource<
|
|
35
|
+
Settings extends object = {},
|
|
36
|
+
Schema extends TAnyDataSourceSchema = TAnyDataSourceSchema,
|
|
37
|
+
ConfigurableOptions extends object = {},
|
|
38
|
+
> extends BaseHelper implements IDataSource<Settings, Schema, ConfigurableOptions> {
|
|
39
|
+
abstract configure(opts?: ConfigurableOptions): ValueOrPromise<void>;
|
|
40
|
+
|
|
41
|
+
getCapabilities(): IDataSourceCapabilities {
|
|
42
|
+
return { transactions: false };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async beginTransaction(_opts?: ITransactionOptions): Promise<ITransaction> {
|
|
46
|
+
return throwNotSupported({
|
|
47
|
+
scope: this.constructor.name,
|
|
48
|
+
feature: 'Transactions',
|
|
49
|
+
logger: this.logger,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
No `pool`, no `connector`, no SQL types. Every engine either overrides `getCapabilities()`/`beginTransaction()` (PostgreSQL) or accepts the neutral default (typesense).
|
|
56
|
+
|
|
57
|
+
### `AbstractEntity`
|
|
58
|
+
|
|
59
|
+
`packages/core/src/base/models/base.ts` declares the minimal entity contract, including `getIdType()`:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
abstract class AbstractEntity extends BaseHelper {
|
|
63
|
+
getIdType(): TIdSchemaType {
|
|
64
|
+
return 'string';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`BasePostgresEntity` overrides `getIdType()` based on the Drizzle column's actual data type; other engines can override it per their own ID representation.
|
|
70
|
+
|
|
71
|
+
### `AbstractRepository`
|
|
72
|
+
|
|
73
|
+
`packages/core/src/base/repositories/core/abstract.ts`:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
abstract class AbstractRepository<
|
|
77
|
+
TDataObject extends object,
|
|
78
|
+
TPersistObject extends object = TDataObject,
|
|
79
|
+
TOptions extends IExtraOptions = IExtraOptions,
|
|
80
|
+
> extends BaseHelper implements IPersistableRepository<TDataObject, TPersistObject, TOptions>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Notice the generic parameters are named for their role (`TDataObject`, `TPersistObject`, `TOptions`), not for Drizzle concepts (`EntitySchema`, `TTableObject`) - those Drizzle-flavored names now belong to `PostgresBaseRepository` and its subclasses, which narrow `AbstractRepository`'s generics into a `TTableSchemaWithId`-based signature. `AbstractRepository` does **not** compose `FieldsVisibilityMixin`/`DefaultFilterMixin` - see [Repository Mixins](./repositories/mixins) for what happened to those.
|
|
84
|
+
|
|
85
|
+
### Neutral `TFilter`/`TWhere` and `ITransaction`
|
|
86
|
+
|
|
87
|
+
`packages/core/src/base/repositories/common/types.ts` and `packages/core/src/base/datasources/common/types.ts` define engine-neutral filter and transaction shapes shared by both connectors. `ITransaction` has no `connector` field - each engine that supports transactions (currently only PostgreSQL) narrows it with `IDatabaseTransaction`, whose `connector` is a `TRelationalConnector<Schema>` bound to the transaction's dedicated connection (the older `TNodePostgresTransactionConnector` name is kept as a `@deprecated` alias). Its `commit()`/`rollback()` **throw** on failure (a failed `COMMIT` no longer resolves as success) and destroy the poisoned connection rather than pooling it under the `node-postgres` driver; a failed `BEGIN` likewise destroys the acquired connection instead of leaking it. See [Transactions](/guides/core-concepts/persistent/transactions).
|
|
88
|
+
|
|
89
|
+
## Dual-door exports
|
|
90
|
+
|
|
91
|
+
IGNIS ships both a **root barrel** (backward-compatible default) and **per-engine subpaths** (opt-in, tree-shakeable). Both resolve to the exact same compiled classes - there is no duplicated implementation.
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
// packages/core/package.json - exports map (persistence-relevant entries)
|
|
95
|
+
{
|
|
96
|
+
".": "dist/index.js", // framework + postgres (compatibility default)
|
|
97
|
+
"./postgres": "dist/connectors/postgres/index.js", // relational connector - loads ZERO client library
|
|
98
|
+
"./postgres/node-postgres": "dist/connectors/postgres/drivers/node-postgres.js", // concrete driver (value-imports `pg`)
|
|
99
|
+
"./postgres/postgres-js": "dist/connectors/postgres/drivers/postgres-js.js", // concrete driver (value-imports `postgres`)
|
|
100
|
+
"./postgres/supabase": "dist/connectors/postgres/supabase/index.js", // Supabase pooler + RLS helpers
|
|
101
|
+
"./search": "dist/connectors/search/index.js", // engine-neutral search paradigm (no engine client)
|
|
102
|
+
"./typesense": "dist/connectors/typesense/index.js", // subpath-only, optional peer
|
|
103
|
+
"./meilisearch": "dist/connectors/meilisearch/index.js" // subpath-only, optional peer
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
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 `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
|
+
|
|
110
|
+
> [!NOTE] `internal/` modules are not public API
|
|
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.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// packages/core/src/index.ts - root barrel
|
|
115
|
+
export * from './base';
|
|
116
|
+
export * from './common';
|
|
117
|
+
export * from './components';
|
|
118
|
+
export * from './connectors'; // -> connectors/index.ts: export * from './postgres';
|
|
119
|
+
export * from './helpers';
|
|
120
|
+
export * from './utilities';
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
- **Root (`@venizia/ignis`)** re-exports the framework plus the `postgres` connector - existing code that imports `BaseDataSource`/`DefaultCRUDRepository` from the root keeps working unchanged.
|
|
124
|
+
- **`@venizia/ignis/postgres`** re-exports the same connector in isolation, for code that wants to be explicit about which engine it depends on.
|
|
125
|
+
- **`@venizia/ignis/typesense`** is **subpath-only** - it is deliberately excluded from the root barrel because `typesense` is an optional peer dependency (`"peerDependenciesMeta": { "typesense": { "optional": true } }`). Importing from the root must never force-install the typesense client for apps that don't use search.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// Both of these resolve to the same BasePostgresDataSource class:
|
|
129
|
+
import { BaseDataSource } from '@venizia/ignis';
|
|
130
|
+
import { BasePostgresDataSource } from '@venizia/ignis/postgres';
|
|
131
|
+
|
|
132
|
+
// Typesense must be imported from its subpath - not available at the root:
|
|
133
|
+
import { TypesenseDataSource, defineSearchCollection } from '@venizia/ignis/typesense';
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Naming symmetry and compatibility aliases
|
|
137
|
+
|
|
138
|
+
Each connector's canonical class names carry the engine name (`BasePostgresDataSource`, `BasePostgresEntity`), matching the pattern typesense follows (`TypesenseDataSource`). PostgreSQL additionally re-exports the pre-restructure names as aliases, so existing imports do not break:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// packages/core/src/connectors/postgres/datasources/index.ts
|
|
142
|
+
export { BasePostgresDataSource as BaseDataSource } from './base-datasource';
|
|
143
|
+
|
|
144
|
+
// packages/core/src/connectors/postgres/models/index.ts
|
|
145
|
+
export { BasePostgresEntity as BaseEntity } from './base';
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| Canonical (new, engine-carrying) | Alias (compatibility) | Notes |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| `BasePostgresDataSource` | `BaseDataSource` | Same class, re-exported |
|
|
151
|
+
| `BasePostgresEntity` | `BaseEntity` | Same class, re-exported |
|
|
152
|
+
|
|
153
|
+
Write new code against the canonical names - they make it unambiguous which engine a class belongs to once you have more than one connector in play. Existing code using the aliases needs no changes.
|
|
154
|
+
|
|
155
|
+
## Adding a new engine connector
|
|
156
|
+
|
|
157
|
+
To add a third connector (e.g. a Redis-backed cache connector), follow the shape the two existing connectors share:
|
|
158
|
+
|
|
159
|
+
1. **`src/connectors/<engine>/datasources/`** - `Abstract<Engine>DataSource extends AbstractDataSource`, then `Base<Engine>DataSource` (or a single concrete `<Engine>DataSource` if there's no useful abstract/base split) implementing `configure()`/`getConnectionString()`. Override `getCapabilities()` and `beginTransaction()` only if the engine truly supports transactions - otherwise inherit the neutral `NotSupported` default.
|
|
160
|
+
2. **`src/connectors/<engine>/models/`** (if the engine needs entity definitions beyond plain objects) - extend `AbstractEntity`, override `getIdType()` if the engine's ID representation differs from the neutral default.
|
|
161
|
+
3. **`src/connectors/<engine>/repositories/core/`** - `<Engine>BaseRepository extends AbstractRepository`, narrowing the generics to the engine's data/persist/options shapes, followed by a `Readable`/`Persistable`/`DefaultCRUD`-style tier ladder mirroring the postgres and typesense connectors. Any operation the engine cannot support (transactions, locks) should call `throwNotSupported({ scope, feature, logger: this.logger })` for consistency.
|
|
162
|
+
4. **`src/connectors/<engine>/index.ts`** - barrel export for the connector.
|
|
163
|
+
5. **`src/connectors/index.ts`** - add `export * from './<engine>'` **only if** the engine has no optional runtime peer dependency (mirroring `postgres`). If the engine driver is an optional peer (like `typesense`), leave it out of `connectors/index.ts` and register it as a subpath-only export in `package.json`.
|
|
164
|
+
6. **`package.json` `exports`** - add `"./<engine>": "dist/connectors/<engine>/index.js"`. If the driver is an optional peer, add it to `peerDependencies` + `peerDependenciesMeta.<driver>.optional: true`.
|
|
165
|
+
|
|
166
|
+
## See Also
|
|
167
|
+
|
|
168
|
+
- **Related Concepts:**
|
|
169
|
+
- [DataSources](./datasources) - Full DataSource reference (neutral contract + PostgreSQL connector)
|
|
170
|
+
- [Models & Enrichers](./models) - Entity reference (neutral `AbstractEntity` + PostgreSQL `BasePostgresEntity`)
|
|
171
|
+
- [Repositories](./repositories/) - Repository hierarchy reference
|
|
172
|
+
- [Repository Mixins](./repositories/mixins) - Legacy mixins, now orphaned from the public API
|
|
173
|
+
|
|
174
|
+
- **Guides:**
|
|
175
|
+
- [Search & Typesense](/guides/core-concepts/persistent/search-typesense) - The typesense connector in depth
|
|
176
|
+
|
|
177
|
+
- **Best Practices:**
|
|
178
|
+
- [Architectural Patterns](/best-practices/architectural-patterns) - Layered architecture and separation of concerns
|
|
@@ -6,7 +6,7 @@ difficulty: beginner
|
|
|
6
6
|
|
|
7
7
|
# Deep Dive: REST Controllers
|
|
8
8
|
|
|
9
|
-
Technical reference for REST controller classes - the foundation for creating HTTP/JSON API endpoints in
|
|
9
|
+
Technical reference for REST controller classes - the foundation for creating HTTP/JSON API endpoints in IGNIS.
|
|
10
10
|
|
|
11
11
|
> [!NOTE]
|
|
12
12
|
> This page covers **REST controllers** (HTTP/JSON). For gRPC controllers using ConnectRPC, see the [gRPC Controllers Reference](./grpc-controllers.md).
|
|
@@ -32,7 +32,7 @@ Technical reference for REST controller classes - the foundation for creating HT
|
|
|
32
32
|
|
|
33
33
|
## Controller Transport System
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
IGNIS supports multiple controller transports. The `@controller` decorator accepts a `transport` field to distinguish between REST and gRPC controllers.
|
|
36
36
|
|
|
37
37
|
### `ControllerTransports`
|
|
38
38
|
|
|
@@ -57,7 +57,7 @@ interface IBaseControllerMetadata {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
interface IRestControllerMetadata extends IBaseControllerMetadata {
|
|
60
|
-
transport?: typeof ControllerTransports.REST; // Optional
|
|
60
|
+
transport?: typeof ControllerTransports.REST; // Optional - defaults to REST
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
interface IGrpcControllerMetadata<ServiceType = unknown> extends IBaseControllerMetadata {
|
|
@@ -68,7 +68,7 @@ interface IGrpcControllerMetadata<ServiceType = unknown> extends IBaseController
|
|
|
68
68
|
type TControllerMetadata = IRestControllerMetadata | IGrpcControllerMetadata;
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
REST controllers do not need to specify `transport` explicitly
|
|
71
|
+
REST controllers do not need to specify `transport` explicitly - it defaults to REST when omitted.
|
|
72
72
|
|
|
73
73
|
### Application Transport Configuration
|
|
74
74
|
|
|
@@ -130,7 +130,7 @@ export class RestComponent extends BaseComponent {
|
|
|
130
130
|
|
|
131
131
|
## `AbstractRestController`
|
|
132
132
|
|
|
133
|
-
Base class integrating Hono routing with
|
|
133
|
+
Base class integrating Hono routing with IGNIS DI, authentication/authorization middleware, and OpenAPI generation.
|
|
134
134
|
|
|
135
135
|
**Generic Parameters:**
|
|
136
136
|
|
|
@@ -153,7 +153,7 @@ constructor(opts: IControllerOptions)
|
|
|
153
153
|
| Option | Type | Default | Description |
|
|
154
154
|
| :--- | :--- | :--- | :--- |
|
|
155
155
|
| `scope` | `string` | Required | Logger scope name |
|
|
156
|
-
| `path` | `string` |
|
|
156
|
+
| `path` | `string` | - | Route base path. Falls back to `@controller` decorator path if not provided |
|
|
157
157
|
| `isStrict` | `boolean` | `true` | When `true`, `/users` and `/users/` are different routes |
|
|
158
158
|
|
|
159
159
|
Path resolution priority: `@controller` decorator metadata > constructor `path` option. Throws if neither provides a path.
|
|
@@ -171,7 +171,7 @@ Path resolution priority: `@controller` decorator metadata > constructor `path`
|
|
|
171
171
|
|
|
172
172
|
#### `configure(opts?): Promise<OpenAPIHono>`
|
|
173
173
|
|
|
174
|
-
Configures the controller. Idempotent
|
|
174
|
+
Configures the controller. Idempotent - returns the router immediately if already configured.
|
|
175
175
|
|
|
176
176
|
1. Calls `binding()` (your manual route definitions)
|
|
177
177
|
2. Calls `registerRoutesFromRegistry()` (decorator-based routes)
|
|
@@ -281,7 +281,11 @@ interface IControllerOptions {
|
|
|
281
281
|
Lightweight typed context that provides type-safe `req.valid()` calls:
|
|
282
282
|
|
|
283
283
|
```typescript
|
|
284
|
-
type TRouteContext<RouteEnv extends Env = Env> = TContext<
|
|
284
|
+
type TRouteContext<RouteEnv extends Env = Env, ResponseBody = unknown> = TContext<
|
|
285
|
+
RouteEnv,
|
|
286
|
+
keyof IValidRequestProps,
|
|
287
|
+
ResponseBody
|
|
288
|
+
>;
|
|
285
289
|
```
|
|
286
290
|
|
|
287
291
|
Where `IValidRequestProps` supports: `json`, `query`, `param`, `header`, `cookie`, `form`.
|
|
@@ -290,7 +294,7 @@ Where `IValidRequestProps` supports: `json`, `query`, `param`, `header`, `cookie
|
|
|
290
294
|
|
|
291
295
|
```typescript
|
|
292
296
|
type TRouteHandler<ResponseType = unknown, RouteEnv extends Env = Env> = (
|
|
293
|
-
context: TRouteContext<RouteEnv>,
|
|
297
|
+
context: TRouteContext<RouteEnv, ResponseType>,
|
|
294
298
|
) => ValueOrPromise<Response | TypedResponse<ResponseType>>;
|
|
295
299
|
```
|
|
296
300
|
|
|
@@ -361,6 +365,8 @@ Per-route customization for CRUD controller endpoints:
|
|
|
361
365
|
|
|
362
366
|
```typescript
|
|
363
367
|
type TCustomizableRouteConfig = TRouteAuthConfig & {
|
|
368
|
+
/** Whether this route is registered. Defaults to true. */
|
|
369
|
+
enabled?: boolean;
|
|
364
370
|
request?: {
|
|
365
371
|
params?: TAnyObjectSchema;
|
|
366
372
|
query?: TAnyObjectSchema;
|
|
@@ -416,7 +422,8 @@ export class UserController extends BaseRestController { ... }
|
|
|
416
422
|
Generic route decorator. Registers route config in the metadata registry.
|
|
417
423
|
|
|
418
424
|
```typescript
|
|
419
|
-
import {
|
|
425
|
+
import { z } from '@hono/zod-openapi';
|
|
426
|
+
import { api, BaseRestController, controller, jsonResponse, TRouteContext } from '@venizia/ignis';
|
|
420
427
|
import { HTTP } from '@venizia/ignis-helpers';
|
|
421
428
|
|
|
422
429
|
const MyRouteConfig = {
|
|
@@ -448,7 +455,8 @@ Each decorator calls `@api` internally with the appropriate `HTTP.Methods.*` val
|
|
|
448
455
|
**Example using `@get` and `@post`:**
|
|
449
456
|
|
|
450
457
|
```typescript
|
|
451
|
-
import {
|
|
458
|
+
import { z } from '@hono/zod-openapi';
|
|
459
|
+
import { get, post, jsonContent, jsonResponse, Authentication, TRouteContext } from '@venizia/ignis';
|
|
452
460
|
import { HTTP } from '@venizia/ignis-helpers';
|
|
453
461
|
|
|
454
462
|
const UserRoutes = {
|
|
@@ -508,12 +516,12 @@ const UserRoutes = {
|
|
|
508
516
|
|
|
509
517
|
- The `binding()` method is not required if you use only decorator-based routing
|
|
510
518
|
- Routes are discovered and registered during `configure()` via `registerRoutesFromRegistry()`
|
|
511
|
-
- TypeScript automatically infers and validates return types against the OpenAPI response schema
|
|
519
|
+
- TypeScript automatically infers and validates return types against the OpenAPI response schema - no need for explicit `TRouteResponse` annotations
|
|
512
520
|
- Use `as const` on route config objects for strict type inference
|
|
513
521
|
|
|
514
522
|
## Manual Route Definition
|
|
515
523
|
|
|
516
|
-
For advanced use cases
|
|
524
|
+
For advanced use cases - dynamic routes, feature flags, programmatic control - define routes inside `binding()`.
|
|
517
525
|
|
|
518
526
|
### `defineRoute` Example
|
|
519
527
|
|
|
@@ -524,7 +532,7 @@ this.defineRoute({
|
|
|
524
532
|
path: '/status',
|
|
525
533
|
responses: jsonResponse({ schema: z.object({ ok: z.boolean() }) }),
|
|
526
534
|
authenticate: { strategies: ['jwt'] },
|
|
527
|
-
authorize: {
|
|
535
|
+
authorize: { action: 'read', resource: 'status' },
|
|
528
536
|
},
|
|
529
537
|
handler: async (context) => {
|
|
530
538
|
return context.json({ ok: true }, 200);
|
|
@@ -626,21 +634,21 @@ class RestPaths {
|
|
|
626
634
|
|
|
627
635
|
| Constant | Headers Included |
|
|
628
636
|
| :--- | :--- |
|
|
629
|
-
| `trackableHeaders` | `x-request-id`, `x-request-channel`, `x-
|
|
630
|
-
| `countableHeaders` | `x-request-count`
|
|
637
|
+
| `trackableHeaders` | `x-request-id`, `x-request-channel`, `x-device-info` (all optional) |
|
|
638
|
+
| `countableHeaders` | `x-request-count` - controls `{count, data}` vs data-only response format |
|
|
631
639
|
| `defaultRequestHeaders` | `trackableHeaders` + `countableHeaders` combined |
|
|
632
640
|
| `commonResponseHeaders` | `x-request-id` (echo), `x-response-count`, `x-response-format` |
|
|
633
641
|
| `findResponseHeaders` | `commonResponseHeaders` + `content-range` for pagination |
|
|
634
642
|
|
|
635
643
|
## `ControllerFactory`
|
|
636
644
|
|
|
637
|
-
The `ControllerFactory` provides a static method `defineCrudController` to quickly generate a pre-configured CRUD controller for any given `BaseEntity` and its corresponding repository.
|
|
645
|
+
The `ControllerFactory` provides a static method `defineCrudController` to quickly generate a pre-configured CRUD controller for any given `AbstractEntity` subclass (e.g. `BaseEntity` for Postgres) and its corresponding repository.
|
|
638
646
|
|
|
639
647
|
**File:** `packages/core/src/base/controllers/factory/controller.ts`
|
|
640
648
|
|
|
641
|
-
### `static defineCrudController<
|
|
649
|
+
### `static defineCrudController<TDataObject, TPersistObject = TDataObject, Routes extends ICustomizableRoutes = ICustomizableRoutes>(opts: ICrudControllerOptions<Routes>)`
|
|
642
650
|
|
|
643
|
-
Returns a `BaseRestController` subclass with standard CRUD endpoints pre-configured. The returned class is dynamically named using `controller.name` from the options.
|
|
651
|
+
Returns a `BaseRestController` subclass with standard CRUD endpoints pre-configured. The returned class is dynamically named using `controller.name` from the options. `TDataObject`/`TPersistObject` can't be inferred from `entity` - pass them explicitly for typed fields.
|
|
644
652
|
|
|
645
653
|
| Route Name | Method | Path | Description |
|
|
646
654
|
| :--- | :--- | :--- | :--- |
|
|
@@ -654,15 +662,16 @@ Returns a `BaseRestController` subclass with standard CRUD endpoints pre-configu
|
|
|
654
662
|
| `deleteById` | `DELETE` | `/{id}` | Delete a record by its ID |
|
|
655
663
|
| `deleteBy` | `DELETE` | `/` | Bulk delete records matching a `where` filter |
|
|
656
664
|
|
|
657
|
-
### `ICrudControllerOptions<
|
|
665
|
+
### `ICrudControllerOptions<Routes>`
|
|
658
666
|
|
|
659
667
|
| Option | Type | Description |
|
|
660
668
|
| :--- | :--- | :--- |
|
|
661
|
-
| `entity` | `TClass<
|
|
669
|
+
| `entity` | `TClass<AbstractEntity> \| TResolver<TClass<AbstractEntity>>` | Entity class or resolver function returning it. Used to derive request/response schemas |
|
|
662
670
|
| `repository.name` | `string` | Repository binding key name in the IoC container (e.g., `'ConfigurationRepository'`) |
|
|
663
671
|
| `controller.name` | `string` | Unique name for the generated controller (e.g., `'ConfigurationController'`) |
|
|
664
672
|
| `controller.basePath` | `string` | Base path for all routes (e.g., `'/configurations'`). Required |
|
|
665
673
|
| `controller.readonly` | `boolean` | If `true`, only read operations (count, find, findOne, findById) are generated. Defaults to `false` |
|
|
674
|
+
| `controller.enabledRoutes` | `Array<keyof ICustomizableRoutes>` | Whitelist of routes to register; overrides per-route `enabled` flags in `routes` when set |
|
|
666
675
|
| `controller.isStrict` | `{ path?: boolean; requestSchema?: boolean }` | `path` (default `true`): strict path matching. `requestSchema` (default `true`): strict query parameter validation |
|
|
667
676
|
| `authenticate` | `{ strategies?: TAuthStrategy[]; mode?: TAuthMode }` | Authentication config applied to all routes (unless overridden per-route) |
|
|
668
677
|
| `authorize` | `IAuthorizationSpec \| IAuthorizationSpec[]` | Authorization config applied to all routes (unless overridden per-route) |
|
|
@@ -679,6 +688,8 @@ type TRouteAuthConfig = {
|
|
|
679
688
|
};
|
|
680
689
|
|
|
681
690
|
type TCustomizableRouteConfig = TRouteAuthConfig & {
|
|
691
|
+
/** Whether this route is registered. Defaults to true. */
|
|
692
|
+
enabled?: boolean;
|
|
682
693
|
request?: {
|
|
683
694
|
params?: TAnyObjectSchema;
|
|
684
695
|
query?: TAnyObjectSchema;
|
|
@@ -708,16 +719,16 @@ type TCustomizableRouteConfig = TRouteAuthConfig & {
|
|
|
708
719
|
|
|
709
720
|
When resolving authentication for a route:
|
|
710
721
|
|
|
711
|
-
1. **Endpoint `authenticate: { skip: true }`**
|
|
712
|
-
2. **Endpoint `authenticate: { strategies }`**
|
|
713
|
-
3. **Controller `authenticate`**
|
|
722
|
+
1. **Endpoint `authenticate: { skip: true }`** - No auth (ignores controller `authenticate`)
|
|
723
|
+
2. **Endpoint `authenticate: { strategies }`** - Override controller (empty array = no auth)
|
|
724
|
+
3. **Controller `authenticate`** - Default fallback
|
|
714
725
|
|
|
715
726
|
When resolving authorization for a route:
|
|
716
727
|
|
|
717
|
-
1. **Endpoint `authenticate: { skip: true }`**
|
|
718
|
-
2. **Endpoint `authorize: { skip: true }`**
|
|
719
|
-
3. **Endpoint `authorize: { ... }`**
|
|
720
|
-
4. **Controller `authorize`**
|
|
728
|
+
1. **Endpoint `authenticate: { skip: true }`** - No authorize (auth skipped entirely)
|
|
729
|
+
2. **Endpoint `authorize: { skip: true }`** - No authorize (explicitly skipped)
|
|
730
|
+
3. **Endpoint `authorize: { ... }`** - Override controller authorize
|
|
731
|
+
4. **Controller `authorize`** - Default fallback
|
|
721
732
|
|
|
722
733
|
### Authentication Examples
|
|
723
734
|
|
|
@@ -763,14 +774,14 @@ const OrderController = ControllerFactory.defineCrudController({
|
|
|
763
774
|
repository: { name: 'OrderRepository' },
|
|
764
775
|
controller: { name: 'OrderController', basePath: '/orders' },
|
|
765
776
|
authenticate: { strategies: [Authentication.STRATEGY_JWT] },
|
|
766
|
-
authorize: {
|
|
777
|
+
authorize: { action: 'read', resource: 'orders' },
|
|
767
778
|
routes: {
|
|
768
779
|
find: {
|
|
769
780
|
authenticate: { skip: true },
|
|
770
781
|
response: { schema: CustomOrderListSchema },
|
|
771
782
|
},
|
|
772
783
|
create: {
|
|
773
|
-
authorize: {
|
|
784
|
+
authorize: { action: 'write', resource: 'orders' },
|
|
774
785
|
request: { body: CustomOrderCreateSchema },
|
|
775
786
|
response: { schema: CustomOrderResponseSchema },
|
|
776
787
|
},
|