@venizia/ignis-docs 0.2.1-0 → 0.2.1-1
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/architectural-patterns.md +3 -3
- package/content/best-practices/code-style-standards/naming-conventions.md +1 -1
- package/content/best-practices/contribution-workflow.md +2 -2
- package/content/best-practices/error-handling.md +94 -89
- package/content/best-practices/security-guidelines.md +5 -5
- package/content/extensions/components/api-reference.md +22 -21
- package/content/extensions/components/authentication/api.md +64 -28
- package/content/extensions/components/authentication/errors.md +19 -5
- package/content/extensions/components/authentication/index.md +19 -18
- package/content/extensions/components/authentication/usage.md +55 -30
- package/content/extensions/components/authorization/api.md +351 -84
- package/content/extensions/components/authorization/errors.md +51 -17
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +24 -16
- package/content/extensions/components/authorization/usage.md +45 -21
- package/content/extensions/components/health-check.md +15 -9
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +105 -54
- package/content/extensions/components/mail/errors.md +12 -10
- package/content/extensions/components/mail/index.md +32 -13
- package/content/extensions/components/mail/usage.md +26 -18
- package/content/extensions/components/request-tracker.md +18 -14
- package/content/extensions/components/socket-io/api.md +377 -882
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +72 -88
- package/content/extensions/components/socket-io/usage.md +107 -117
- package/content/extensions/components/static-asset/api.md +83 -31
- package/content/extensions/components/static-asset/errors.md +18 -7
- package/content/extensions/components/static-asset/index.md +18 -11
- package/content/extensions/components/static-asset/usage.md +11 -6
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +58 -27
- package/content/extensions/components/websocket/errors.md +3 -3
- package/content/extensions/components/websocket/index.md +8 -7
- package/content/extensions/components/websocket/usage.md +21 -8
- package/content/extensions/helpers/cron/index.md +8 -7
- package/content/extensions/helpers/crypto/index.md +16 -8
- package/content/extensions/helpers/crypto/reference.md +96 -24
- package/content/extensions/helpers/env/index.md +14 -10
- package/content/extensions/helpers/error/index.md +99 -23
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +23 -6
- package/content/extensions/helpers/inversion/reference.md +30 -22
- package/content/extensions/helpers/kafka/admin.md +3 -2
- package/content/extensions/helpers/kafka/compile-binary.md +69 -44
- package/content/extensions/helpers/kafka/consumer.md +24 -21
- package/content/extensions/helpers/kafka/examples.md +22 -234
- package/content/extensions/helpers/kafka/index.md +30 -62
- package/content/extensions/helpers/kafka/producer.md +31 -26
- package/content/extensions/helpers/kafka/schema-registry.md +19 -14
- package/content/extensions/helpers/logger/hf-logger.md +49 -22
- package/content/extensions/helpers/logger/index.md +37 -12
- package/content/extensions/helpers/logger/pino.md +31 -11
- package/content/extensions/helpers/logger/reference.md +243 -52
- package/content/extensions/helpers/network/api.md +65 -30
- package/content/extensions/helpers/network/index.md +32 -11
- package/content/extensions/helpers/queue/index.md +17 -6
- package/content/extensions/helpers/queue/reference.md +52 -25
- package/content/extensions/helpers/redis/index.md +29 -11
- package/content/extensions/helpers/redis/reference.md +85 -28
- package/content/extensions/helpers/secrets/index.md +82 -12
- package/content/extensions/helpers/socket-io/api.md +40 -21
- package/content/extensions/helpers/socket-io/index.md +26 -10
- package/content/extensions/helpers/storage/api.md +42 -24
- package/content/extensions/helpers/storage/index.md +10 -7
- package/content/extensions/helpers/types/index.md +20 -7
- package/content/extensions/helpers/types/reference.md +53 -14
- package/content/extensions/helpers/uid/index.md +166 -10
- package/content/extensions/helpers/websocket/api.md +52 -13
- package/content/extensions/helpers/websocket/index.md +7 -4
- package/content/extensions/helpers/worker-thread/index.md +9 -5
- package/content/extensions/helpers/worker-thread/reference.md +20 -20
- package/content/extensions/index.md +38 -39
- package/content/extensions/src-details/mcp-server.md +96 -548
- package/content/guides/core-concepts/persistent/datasources.md +2 -2
- package/content/guides/core-concepts/persistent/index.md +5 -1
- package/content/guides/core-concepts/persistent/pglite.md +218 -0
- package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
- package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
- package/content/guides/core-concepts/persistent/search-typesense.md +26 -14
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/get-started/5-minute-quickstart.md +59 -206
- package/content/guides/get-started/philosophy.md +135 -670
- package/content/guides/get-started/setup.md +53 -74
- package/content/guides/migrations/redis-helpers-migration.md +4 -3
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +7 -6
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +63 -21
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +14 -14
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +32 -31
- package/content/references/base/datasources.md +6 -6
- package/content/references/base/dependency-injection.md +12 -11
- package/content/references/base/filter-system/application-usage.md +54 -30
- package/content/references/base/filter-system/array-operators.md +24 -46
- package/content/references/base/filter-system/comparison-operators.md +47 -67
- package/content/references/base/filter-system/default-filter.md +59 -53
- package/content/references/base/filter-system/fields-order-pagination.md +92 -146
- package/content/references/base/filter-system/index.md +25 -13
- package/content/references/base/filter-system/json-filtering.md +45 -184
- package/content/references/base/filter-system/list-operators.md +23 -53
- package/content/references/base/filter-system/logical-operators.md +63 -121
- package/content/references/base/filter-system/null-operators.md +34 -104
- package/content/references/base/filter-system/pattern-matching.md +40 -55
- package/content/references/base/filter-system/quick-reference.md +86 -198
- package/content/references/base/filter-system/range-operators.md +18 -46
- package/content/references/base/filter-system/tips.md +6 -6
- package/content/references/base/filter-system/use-cases.md +33 -15
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +11 -10
- package/content/references/base/models-reference.md +17 -17
- package/content/references/base/models.md +4 -3
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +224 -326
- package/content/references/base/repositories/index.md +19 -6
- package/content/references/base/repositories/mixins.md +5 -5
- package/content/references/base/repositories/relations.md +160 -293
- package/content/references/base/repositories/soft-deletable.md +16 -6
- package/content/references/base/secrets.md +17 -13
- package/content/references/base/services.md +6 -4
- package/content/references/configuration/environment-variables.md +31 -23
- package/content/references/configuration/index.md +6 -4
- package/content/references/index.md +1 -1
- package/content/references/utilities/duration.md +85 -0
- package/content/references/utilities/index.md +5 -1
- package/content/references/utilities/jsx-reference.md +11 -11
- package/content/references/utilities/jsx.md +2 -2
- package/content/references/utilities/module.md +78 -25
- package/content/references/utilities/request.md +2 -1
- package/content/references/utilities/retry.md +139 -0
- package/content/references/utilities/schema.md +2 -2
- package/content/references/utilities/statuses-reference.md +4 -4
- package/content/references/utilities/statuses.md +5 -5
- package/dist/mcp-server/index.js +0 -0
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
- package/package.json +17 -16
|
@@ -6,11 +6,11 @@ difficulty: intermediate
|
|
|
6
6
|
|
|
7
7
|
# Use Case Gallery
|
|
8
8
|
|
|
9
|
-
Runnable `filter` objects paired with the SQL `FilterBuilder` produces for them
|
|
9
|
+
Runnable `filter` objects, paired with the SQL `FilterBuilder` produces for them. Copy the shape closest to what you need. For the operators themselves, start at the [Filter System Overview](./).
|
|
10
10
|
|
|
11
11
|
## Soft delete
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Goal: return only non-deleted rows, or only deleted ones.
|
|
14
14
|
|
|
15
15
|
```typescript
|
|
16
16
|
// Active (non-deleted) records
|
|
@@ -26,11 +26,13 @@ const deletedRecords = await repository.find({
|
|
|
26
26
|
// SQL: SELECT * FROM "Record" WHERE "deleted_at" IS NOT NULL
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
Notice: `is`/`isn` against a nullable timestamp is the whole pattern - no separate `deleted: boolean` column needed.
|
|
30
|
+
|
|
31
|
+
If every query on a model should exclude deleted rows, encode this once as `settings.defaultFilter` instead of repeating it at every call site. See [Default Filter](./default-filter).
|
|
30
32
|
|
|
31
33
|
## E-commerce product search
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
Goal: a price range, a minimum quantity, and a status, sorted and paged for a listing page.
|
|
34
36
|
|
|
35
37
|
```typescript
|
|
36
38
|
const products = await productRepository.find({
|
|
@@ -58,9 +60,11 @@ const products = await productRepository.find({
|
|
|
58
60
|
// LIMIT 24
|
|
59
61
|
```
|
|
60
62
|
|
|
63
|
+
Notice: four `where` keys AND-compose automatically - no explicit `and` needed for a flat condition list.
|
|
64
|
+
|
|
61
65
|
## Admin dashboard: recent users
|
|
62
66
|
|
|
63
|
-
|
|
67
|
+
Goal: users created in the last 30 days, excluding banned or suspended accounts, with a verified email.
|
|
64
68
|
|
|
65
69
|
```typescript
|
|
66
70
|
const thirtyDaysAgo = new Date();
|
|
@@ -89,9 +93,11 @@ const recentUsers = await userRepository.find({
|
|
|
89
93
|
// LIMIT 50
|
|
90
94
|
```
|
|
91
95
|
|
|
96
|
+
Notice: `nin` on `status` silently drops any row where `status` is `NULL`. See [Tips & Edge Cases](./tips) before relying on this for a nullable column.
|
|
97
|
+
|
|
92
98
|
## Multi-tenant isolation at the call site
|
|
93
99
|
|
|
94
|
-
`settings.defaultFilter` (see [Default Filter](./default-filter)) is the model-level way to enforce a tenant scope.
|
|
100
|
+
`settings.defaultFilter` (see [Default Filter](./default-filter)) is the model-level way to enforce a tenant scope. The alternative below is a helper that injects `tenantId` at every call site instead. Use it when tenant isolation is a caller concern, not a per-model constant.
|
|
95
101
|
|
|
96
102
|
```typescript
|
|
97
103
|
const getTenantProducts = (tenantId: string, filter: TFilter<TProductSchema>) =>
|
|
@@ -115,11 +121,11 @@ await getTenantProducts('tenant-abc', {
|
|
|
115
121
|
// LIMIT 20
|
|
116
122
|
```
|
|
117
123
|
|
|
118
|
-
|
|
124
|
+
Notice: this is a plain object spread, not `mergeFilter`'s narrowing merge - `tenantId`/`deletedAt` overwrite same-named keys from `filter.where` because they're spread last.
|
|
119
125
|
|
|
120
126
|
## Task management: priority tags
|
|
121
127
|
|
|
122
|
-
|
|
128
|
+
Goal: open tasks assigned to the current user that carry an urgent or high-priority tag, with the parent project loaded.
|
|
123
129
|
|
|
124
130
|
```typescript
|
|
125
131
|
const priorityTasks = await taskRepository.find({
|
|
@@ -146,9 +152,11 @@ const priorityTasks = await taskRepository.find({
|
|
|
146
152
|
// SELECT * FROM "Project" WHERE "id" IN (...)
|
|
147
153
|
```
|
|
148
154
|
|
|
155
|
+
Notice: `include` runs as a separate query, not a SQL `JOIN` - see [Relations & Includes](../repositories/relations).
|
|
156
|
+
|
|
149
157
|
## Date range queries
|
|
150
158
|
|
|
151
|
-
|
|
159
|
+
Goal: a closed window (a specific week) versus a rolling window (the last 7 days).
|
|
152
160
|
|
|
153
161
|
```typescript
|
|
154
162
|
const startOfWeek = new Date('2024-12-29');
|
|
@@ -184,9 +192,11 @@ const recentOrders = await orderRepository.find({
|
|
|
184
192
|
// ORDER BY "total" DESC LIMIT 100
|
|
185
193
|
```
|
|
186
194
|
|
|
195
|
+
Notice: `between` needs exactly two elements - `FilterBuilder` throws on any other array length.
|
|
196
|
+
|
|
187
197
|
## Inventory low-stock alert
|
|
188
198
|
|
|
189
|
-
|
|
199
|
+
Goal: active products at or under a reorder threshold, flagged critical below 5 units or fast-moving stock at 10 or fewer.
|
|
190
200
|
|
|
191
201
|
```typescript
|
|
192
202
|
const lowStockProducts = await productRepository.find({
|
|
@@ -218,9 +228,11 @@ const lowStockProducts = await productRepository.find({
|
|
|
218
228
|
// ORDER BY "quantity" ASC
|
|
219
229
|
```
|
|
220
230
|
|
|
231
|
+
Notice: `or` nests an `and` group one level deep - `FilterBuilder` recurses through logical groups, so nesting depth is not limited to one.
|
|
232
|
+
|
|
221
233
|
## Complex authorization filter
|
|
222
234
|
|
|
223
|
-
|
|
235
|
+
Goal: an admin sees everything; everyone else sees only what they own, what is public, or what is shared with them.
|
|
224
236
|
|
|
225
237
|
```typescript
|
|
226
238
|
const getAuthorizedFilter = (user: User): TWhere<TDocumentSchema> => {
|
|
@@ -255,9 +267,11 @@ const documents = await documentRepository.find({
|
|
|
255
267
|
// ORDER BY "updated_at" DESC LIMIT 100
|
|
256
268
|
```
|
|
257
269
|
|
|
270
|
+
Notice: a plain TypeScript function builds the `where`, branching on role. A filter is a normal object, not a DSL with its own control flow.
|
|
271
|
+
|
|
258
272
|
## Full-text search with metadata
|
|
259
273
|
|
|
260
|
-
|
|
274
|
+
Goal: assemble a `where` clause from optional caller input, adding a key only when the caller supplied it.
|
|
261
275
|
|
|
262
276
|
```typescript
|
|
263
277
|
const searchProducts = async (
|
|
@@ -295,9 +309,11 @@ const searchProducts = async (
|
|
|
295
309
|
// ORDER BY "rating" DESC, "created_at" DESC LIMIT 50
|
|
296
310
|
```
|
|
297
311
|
|
|
312
|
+
Notice: `ilike` reaches into a JSON path (`'metadata.keywords'`) the same way it reaches a top-level column.
|
|
313
|
+
|
|
298
314
|
## Everything at once
|
|
299
315
|
|
|
300
|
-
Every operator family, a JSON path, a three-way `or`, and a scoped relation include in one filter
|
|
316
|
+
Every operator family, a JSON path, a three-way `or`, and a scoped relation include - all in one filter. This is the ceiling of what a single `TFilter` can express.
|
|
301
317
|
|
|
302
318
|
```typescript
|
|
303
319
|
const massiveFilter: TFilter<TProductSchema> = {
|
|
@@ -363,6 +379,8 @@ const products = await productRepository.find({ filter: massiveFilter });
|
|
|
363
379
|
// SELECT * FROM "Review" WHERE "product_id" IN (...) AND "rating" >= 4 ORDER BY "created_at" DESC LIMIT 5
|
|
364
380
|
```
|
|
365
381
|
|
|
382
|
+
Notice: `'metadata.priority': { gte: 3 }` gets the numeric `CASE` cast because the operand is a number. `'metadata.isNewArrival': true` does not, because it compares as text. See [Tips & Edge Cases](./tips) for the full casting rule.
|
|
383
|
+
|
|
366
384
|
## See also
|
|
367
385
|
|
|
368
386
|
- [Filter System Overview](./) - the `filter` shape and every `where` operator family
|
|
@@ -372,5 +390,5 @@ const products = await productRepository.find({ filter: massiveFilter });
|
|
|
372
390
|
|
|
373
391
|
**Files:**
|
|
374
392
|
|
|
375
|
-
- [`packages/core/src/connectors/
|
|
376
|
-
- [`packages/core/src/connectors/postgres/repositories/dialect/query.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/dialect/query.ts) - `PostgresQueryOperators.FNS`, per-operator SQL builders
|
|
393
|
+
- [`packages/core-server/src/connectors/relational/repositories/dialect/filter.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/relational/repositories/dialect/filter.ts) - `FilterBuilder`, translates `TFilter` to Drizzle/SQL
|
|
394
|
+
- [`packages/core-server/src/connectors/postgres/repositories/dialect/query.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/repositories/dialect/query.ts) - `PostgresQueryOperators.FNS`, per-operator SQL builders
|
|
@@ -11,13 +11,13 @@ Technical reference for gRPC controller classes -- the foundation for building g
|
|
|
11
11
|
IGNIS gRPC controllers follow the same patterns as REST controllers (decorator-based routing, `binding()` method, DI integration) while bridging to ConnectRPC's universal handler system. REST and gRPC controllers coexist in the same application, sharing the same DI container, middleware pipeline, and lifecycle.
|
|
12
12
|
|
|
13
13
|
**Files:**
|
|
14
|
-
- `packages/core/src/base/controllers/grpc/abstract.ts`
|
|
15
|
-
- `packages/core/src/base/controllers/grpc/base.ts`
|
|
16
|
-
- `packages/core/src/base/controllers/grpc/adapter.ts`
|
|
17
|
-
- `packages/core/src/base/controllers/grpc/common/types.ts`
|
|
18
|
-
- `packages/core/src/base/metadata/routes/rpc.ts`
|
|
19
|
-
- `packages/core/src/components/controller/grpc/grpc.component.ts`
|
|
20
|
-
- `packages/core/src/components/controller/grpc/common/types.ts`
|
|
14
|
+
- `packages/core-server/src/base/controllers/grpc/abstract.ts`
|
|
15
|
+
- `packages/core-server/src/base/controllers/grpc/base.ts`
|
|
16
|
+
- `packages/core-server/src/base/controllers/grpc/adapter.ts`
|
|
17
|
+
- `packages/core-server/src/base/controllers/grpc/common/types.ts`
|
|
18
|
+
- `packages/core-server/src/base/metadata/routes/rpc.ts`
|
|
19
|
+
- `packages/core-server/src/components/controller/grpc/grpc.component.ts`
|
|
20
|
+
- `packages/core-server/src/components/controller/grpc/common/types.ts`
|
|
21
21
|
|
|
22
22
|
## Quick Reference
|
|
23
23
|
|
|
@@ -35,7 +35,7 @@ IGNIS gRPC controllers follow the same patterns as REST controllers (decorator-b
|
|
|
35
35
|
| **@rpc** | Generic method decorator (requires explicit `method` in configs) |
|
|
36
36
|
|
|
37
37
|
> [!WARNING]
|
|
38
|
-
> **Current version supports unary RPCs only.** The `@serverStream`, `@clientStream`, and `@bidiStream` decorators still exist and set metadata correctly
|
|
38
|
+
> **Current version supports unary RPCs only.** The `@serverStream`, `@clientStream`, and `@bidiStream` decorators still exist and set metadata correctly. But `BaseGrpcController.registerRoute()` throws a clear error at boot time if a non-unary RPC is registered - the Connect protocol over HTTP/1.1 cannot support streaming. The decorators are preserved for forward compatibility.
|
|
39
39
|
|
|
40
40
|
## Prerequisites
|
|
41
41
|
|
|
@@ -57,7 +57,7 @@ bun add @connectrpc/connect-web
|
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
> [!NOTE]
|
|
60
|
-
> `@connectrpc/connect` is an **optional** peer dependency of `@venizia/ignis
|
|
60
|
+
> `@connectrpc/connect` is an **optional** peer dependency of `@venizia/ignis`. It is only loaded at runtime when a gRPC controller is configured, via `createRequire` from the application's `node_modules`. If it is missing, `GrpcRequestAdapter.build()` throws a clear error at startup via `ModuleUtility.assertInstalled()`. A compiled binary has no `node_modules` to resolve against and must pass the peer through `IGrpcComponentConfig.module` - see [Peer Dependency Loading](#peer-dependency-loading). `@bufbuild/protobuf` is required by your generated protobuf code (e.g. `create()`), not by the framework itself.
|
|
61
61
|
|
|
62
62
|
### Protobuf Code Generation
|
|
63
63
|
|
|
@@ -229,7 +229,7 @@ The `configure()` method on `AbstractGrpcController` is idempotent (guarded by `
|
|
|
229
229
|
|
|
230
230
|
## RPC Decorators
|
|
231
231
|
|
|
232
|
-
All RPC decorators live in `packages/core/src/base/metadata/routes/rpc.ts`. They register metadata in the `MetadataRegistry`, which is read during `configure()`.
|
|
232
|
+
All RPC decorators live in `packages/core-server/src/base/metadata/routes/rpc.ts`. They register metadata in the `MetadataRegistry`, which is read during `configure()`.
|
|
233
233
|
|
|
234
234
|
### `@rpc` -- Generic
|
|
235
235
|
|
|
@@ -415,7 +415,7 @@ Internal bridge between IGNIS gRPC controllers and ConnectRPC's universal handle
|
|
|
415
415
|
|
|
416
416
|
### Architecture
|
|
417
417
|
|
|
418
|
-
The adapter solves a key challenge: ConnectRPC handlers have their own `(request, context) => response` signature
|
|
418
|
+
The adapter solves a key challenge: ConnectRPC handlers have their own `(request, context) => response` signature. IGNIS controllers, though, need access to the Hono `Context` for middleware, auth, and request-scoped state. The adapter uses `AsyncLocalStorage` to provide request-scoped context isolation, ensuring concurrent requests never share state.
|
|
419
419
|
|
|
420
420
|
```
|
|
421
421
|
Hono Request
|
|
@@ -429,7 +429,7 @@ Hono Request
|
|
|
429
429
|
|
|
430
430
|
### Static `build()` Method
|
|
431
431
|
|
|
432
|
-
The only public API. Validates peer deps via `
|
|
432
|
+
The only public API. Validates peer deps via `ModuleUtility.assertInstalled()`, creates the adapter, and returns the middleware + registered paths:
|
|
433
433
|
|
|
434
434
|
```typescript
|
|
435
435
|
static async build(opts: {
|
|
@@ -462,12 +462,27 @@ The optional `interceptors` array is passed to ConnectRPC's `createConnectRouter
|
|
|
462
462
|
|
|
463
463
|
### Peer Dependency Loading
|
|
464
464
|
|
|
465
|
-
The adapter
|
|
465
|
+
The adapter needs two entry points:
|
|
466
466
|
|
|
467
467
|
- `@connectrpc/connect` -- for `createConnectRouter`
|
|
468
468
|
- `@connectrpc/connect/protocol` -- for `universalServerRequestFromFetch` and `universalServerResponseToFetch`
|
|
469
469
|
|
|
470
|
-
|
|
470
|
+
By default it loads both at runtime using `createRequire` from the application's `node_modules`. That keeps the specifier invisible to `Bun.build`, so a consumer who never uses gRPC is not forced to install the peer.
|
|
471
|
+
|
|
472
|
+
**A compiled application must pass `module`.** A `bun build --compile` binary ships without `node_modules`, so `createRequire` has nothing to resolve against. Hand the peer over through the component options instead - the static import is what embeds it in the binary:
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
import * as connect from '@connectrpc/connect';
|
|
476
|
+
import * as protocol from '@connectrpc/connect/protocol';
|
|
477
|
+
|
|
478
|
+
this.bind({ key: GrpcBindingKeys.GRPC_COMPONENT_OPTIONS }).toValue({
|
|
479
|
+
module: { connect, protocol },
|
|
480
|
+
});
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
`GrpcComponent` assigns the module to each controller before `configure()`, and the adapter skips both `assertInstalled` and `createRequire` when it is present.
|
|
484
|
+
|
|
485
|
+
`ModuleUtility.register` does not work here. The adapter resolves the specifier itself, so the registry never reaches it.
|
|
471
486
|
|
|
472
487
|
### Error Handling
|
|
473
488
|
|
|
@@ -477,7 +492,7 @@ On handler errors, the adapter returns a JSON response with:
|
|
|
477
492
|
- `grpc-message` header: URL-encoded error message
|
|
478
493
|
- Body: JSON `{ message, code }`
|
|
479
494
|
|
|
480
|
-
The adapter uses a duck-type check on `error.code` to preserve gRPC status codes from ConnectRPC errors without importing `ConnectError` directly
|
|
495
|
+
The adapter uses a duck-type check on `error.code` to preserve gRPC status codes from ConnectRPC errors, without importing `ConnectError` directly. That avoids tight coupling to the peer dependency.
|
|
481
496
|
|
|
482
497
|
## `GrpcComponent`
|
|
483
498
|
|
|
@@ -488,9 +503,30 @@ Auto-discovers and configures gRPC controllers during the application lifecycle.
|
|
|
488
503
|
```typescript
|
|
489
504
|
interface IGrpcComponentConfig {
|
|
490
505
|
interceptors?: unknown[];
|
|
506
|
+
module?: IConnectRpcModule;
|
|
491
507
|
}
|
|
492
508
|
```
|
|
493
509
|
|
|
510
|
+
| Option | Type | Default | Meaning |
|
|
511
|
+
|---|---|---|---|
|
|
512
|
+
| `interceptors` | `unknown[]` | none | ConnectRPC interceptors, passed to `createConnectRouter` for every gRPC controller. An empty list passes nothing |
|
|
513
|
+
| `module` | `IConnectRpcModule` | none | The ConnectRPC peer, as `{ connect, protocol }`. Required for a compiled binary - see [Peer Dependency Loading](#peer-dependency-loading) |
|
|
514
|
+
|
|
515
|
+
Both options are component-wide. `GrpcComponent` assigns them to every gRPC controller it discovers, before calling `configure()`:
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
import type { Interceptor } from '@connectrpc/connect';
|
|
519
|
+
|
|
520
|
+
const logging: Interceptor = next => async request => {
|
|
521
|
+
const response = await next(request);
|
|
522
|
+
return response;
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
this.bind({ key: GrpcBindingKeys.GRPC_COMPONENT_OPTIONS }).toValue({
|
|
526
|
+
interceptors: [logging],
|
|
527
|
+
});
|
|
528
|
+
```
|
|
529
|
+
|
|
494
530
|
The component registers a default (empty) config binding under the key `'@app/grpc/options'` (`GrpcBindingKeys.GRPC_COMPONENT_OPTIONS`).
|
|
495
531
|
|
|
496
532
|
### Behavior
|
|
@@ -623,7 +659,7 @@ export class Application extends BaseApplication {
|
|
|
623
659
|
```
|
|
624
660
|
|
|
625
661
|
> [!WARNING]
|
|
626
|
-
> If `transports` does not include `ControllerTransports.GRPC`, gRPC controllers are still registered in the DI container
|
|
662
|
+
> If `transports` does not include `ControllerTransports.GRPC`, gRPC controllers are still registered in the DI container. But the `GrpcComponent` is never mounted -- their `configure()` is never called and no routes are served.
|
|
627
663
|
|
|
628
664
|
### Dual Transport
|
|
629
665
|
|
|
@@ -639,7 +675,7 @@ preConfigure() {
|
|
|
639
675
|
}
|
|
640
676
|
```
|
|
641
677
|
|
|
642
|
-
REST controllers are handled by the `RestComponent
|
|
678
|
+
REST controllers are handled by the `RestComponent`, active when `transports` includes `ControllerTransports.REST` (the default). gRPC controllers are handled by the `GrpcComponent`, active when that transport is enabled. They share the same DI container and lifecycle.
|
|
643
679
|
|
|
644
680
|
## Complete Example
|
|
645
681
|
|
|
@@ -9,7 +9,7 @@ Core classes that power every IGNIS application - from the Application entry poi
|
|
|
9
9
|
|
|
10
10
|
| Class | Purpose | Extends |
|
|
11
11
|
|-------|---------|---------|
|
|
12
|
-
| `BaseApplication` | Application entry point, DI container | `AbstractApplication` |
|
|
12
|
+
| `BaseApplication` | Application entry point, DI container | `ServerApplication` -> `RestApplication` -> `AbstractApplication` |
|
|
13
13
|
| `BaseRestController` | REST/HTTP route handlers | `AbstractRestController` |
|
|
14
14
|
| `BaseGrpcController` | gRPC route handlers (ConnectRPC) | `AbstractGrpcController` |
|
|
15
15
|
| `BaseService` | Business logic layer | - |
|
|
@@ -79,8 +79,10 @@ Core classes that power every IGNIS application - from the Application entry poi
|
|
|
79
79
|
## Class Hierarchy
|
|
80
80
|
|
|
81
81
|
```
|
|
82
|
-
AbstractApplication
|
|
83
|
-
└──
|
|
82
|
+
AbstractApplication (browser-pure, @venizia/ignis-kernel)
|
|
83
|
+
└── RestApplication (browser-pure, owns the router)
|
|
84
|
+
└── ServerApplication (opens the socket)
|
|
85
|
+
└── BaseApplication ──────► Your Application
|
|
84
86
|
|
|
85
87
|
AbstractRepository (engine-neutral, src/base)
|
|
86
88
|
├── PostgresBaseRepository (connectors/postgres)
|
|
@@ -10,10 +10,10 @@ lastUpdated: 2026-06-14
|
|
|
10
10
|
IGNIS provides built-in middleware functions and a provider-based middleware class for handling common HTTP concerns: error handling, request logging, 404 responses, and favicon serving. These are registered automatically by `BaseApplication` during startup - you do not import or wire them manually.
|
|
11
11
|
|
|
12
12
|
**Files:**
|
|
13
|
-
- `packages/core/src/base/middlewares/app-error/app-error.middleware.ts`
|
|
14
|
-
- `packages/core/src/base/middlewares/not-found/not-found.middleware.ts`
|
|
15
|
-
- `packages/core/src/base/middlewares/request-spy/request-spy.middleware.ts`
|
|
16
|
-
- `packages/core/src/base/middlewares/emoji-favicon/emoji-favicon.middleware.ts`
|
|
13
|
+
- `packages/core-server/src/base/middlewares/app-error/app-error.middleware.ts`
|
|
14
|
+
- `packages/core-server/src/base/middlewares/not-found/not-found.middleware.ts`
|
|
15
|
+
- `packages/core-server/src/base/middlewares/request-spy/request-spy.middleware.ts`
|
|
16
|
+
- `packages/core-server/src/base/middlewares/emoji-favicon/emoji-favicon.middleware.ts`
|
|
17
17
|
|
|
18
18
|
## Prerequisites
|
|
19
19
|
|
|
@@ -86,7 +86,7 @@ class AppErrorMiddleware extends BaseHelper implements IProvider<ErrorHandler> {
|
|
|
86
86
|
|
|
87
87
|
When `error.name === 'ZodError'`, returns HTTP `422 Unprocessable Entity`.
|
|
88
88
|
|
|
89
|
-
`message` and `normalized.code` come from the first failing issue
|
|
89
|
+
`message` and `normalized.code` come from the first failing issue. `message` is that issue's message; `normalized.code` is its `params.code` if the schema set one, otherwise its raw Zod code. The full per-field list stays under `details.cause`. `normalized.args` is always `{}` - a Zod issue carries no interpolation values.
|
|
90
90
|
|
|
91
91
|
```json
|
|
92
92
|
{
|
|
@@ -126,11 +126,11 @@ z.string().refine(isEmail, {
|
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
> [!NOTE]
|
|
129
|
-
> When `error.message` cannot be parsed as the expected Zod issue array (a malformed or unrecognized `ZodError`), no issue-derived code exists
|
|
129
|
+
> When `error.message` cannot be parsed as the expected Zod issue array (a malformed or unrecognized `ZodError`), no issue-derived code exists. `normalized.code` still resolves to `MessageCode.DEFAULT` (`"core.system_error"`) via `MessageCode.resolve(undefined)`. No error response from this middleware is ever missing `normalized.code`.
|
|
130
130
|
|
|
131
131
|
#### 2. PostgreSQL Constraint Violations
|
|
132
132
|
|
|
133
|
-
Database errors in SQLSTATE class `22` (data exception), `23` (integrity constraint), and `44` (WITH CHECK OPTION violation) are detected by class
|
|
133
|
+
Database errors in SQLSTATE class `22` (data exception), `23` (integrity constraint), and `44` (WITH CHECK OPTION violation) are detected by class. They return HTTP `400 Bad Request`. A known code uses its specific message; any other in-class code uses `"Invalid database request"` as a fallback.
|
|
134
134
|
|
|
135
135
|
| Class | Codes with a specific message |
|
|
136
136
|
|-------|-------------------------------|
|
|
@@ -139,7 +139,7 @@ Database errors in SQLSTATE class `22` (data exception), `23` (integrity constra
|
|
|
139
139
|
| `44` View check | `44000` WITH CHECK OPTION violation |
|
|
140
140
|
|
|
141
141
|
:::tip Transient conflicts return 409, not 400/500
|
|
142
|
-
Class `40` (`40001` serialization failure, `40P01` deadlock) is transient/retryable
|
|
142
|
+
Class `40` (`40001` serialization failure, `40P01` deadlock) is transient/retryable. It returns **409 Conflict** with `normalized.code: "database.conflict"` and a safe "please retry" message - the client can safely retry the same request. Programming/infra classes (`42` syntax, `53` resources, `0A`, `25`, `28`) remain 500.
|
|
143
143
|
:::
|
|
144
144
|
|
|
145
145
|
:::warning Production sanitizes database internals
|
|
@@ -249,7 +249,8 @@ function notFoundHandler(opts: {
|
|
|
249
249
|
}
|
|
250
250
|
```
|
|
251
251
|
|
|
252
|
-
The handler logs the 404 at
|
|
252
|
+
The handler logs the 404 at warn level with the request ID, path, and full URL. An unrouted path is a
|
|
253
|
+
client mistake, not a server fault - alerting tuned to error level should not fire on it.
|
|
253
254
|
|
|
254
255
|
|
|
255
256
|
## RequestSpyMiddleware
|
|
@@ -496,7 +497,7 @@ export class MyMiddleware extends BaseHelper implements IProvider<MiddlewareHand
|
|
|
496
497
|
}
|
|
497
498
|
```
|
|
498
499
|
|
|
499
|
-
Register it with `.toProvider()` (the same pattern `RequestTrackerComponent` uses for `RequestSpyMiddleware`)
|
|
500
|
+
Register it with `.toProvider()` (the same pattern `RequestTrackerComponent` uses for `RequestSpyMiddleware`). Then use the resolved handler inside `setupMiddlewares()`: `get()` returns the produced `MiddlewareHandler`, because the container calls `value()` for provider bindings.
|
|
500
501
|
|
|
501
502
|
```typescript
|
|
502
503
|
export class MyApplication extends BaseApplication {
|
|
@@ -10,10 +10,10 @@ Exhaustive reference for the `@model` decorator, the entity class hierarchy, and
|
|
|
10
10
|
|
|
11
11
|
**Files:**
|
|
12
12
|
|
|
13
|
-
- [`packages/core/src/base/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/models/base.ts) - neutral `AbstractEntity`
|
|
14
|
-
- [`packages/core/src/connectors/postgres/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/base.ts) - PostgreSQL entity (`BaseRelationalEntity`, aliases `BaseEntity`/`BasePostgresEntity`)
|
|
15
|
-
- [`packages/core/src/base/metadata/persistents.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/metadata/persistents.ts) - `@model` decorator
|
|
16
|
-
- [`packages/core/src/connectors/postgres/models/enrichers`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/enrichers) - schema enrichers
|
|
13
|
+
- [`packages/core-server/src/base/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/models/base.ts) - neutral `AbstractEntity`
|
|
14
|
+
- [`packages/core-server/src/connectors/postgres/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/models/base.ts) - PostgreSQL entity (`BaseRelationalEntity`, aliases `BaseEntity`/`BasePostgresEntity`)
|
|
15
|
+
- [`packages/core-server/src/base/metadata/persistents.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/metadata/persistents.ts) - `@model` decorator
|
|
16
|
+
- [`packages/core-server/src/connectors/postgres/models/enrichers`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/models/enrichers) - schema enrichers
|
|
17
17
|
|
|
18
18
|
## `AbstractEntity` (neutral) vs. `BaseEntity` (connector)
|
|
19
19
|
|
|
@@ -23,7 +23,7 @@ IGNIS separates the engine-neutral entity root from the connector-specific imple
|
|
|
23
23
|
- **Minimal surface.** It carries only a `name`, an abstract `getSchema()`, a `getIdType(): TIdSchemaType` method (default `'string'`), and `toObject()`/`toJSON()`.
|
|
24
24
|
- **Everything else is connector-owned.** The Drizzle-backed entity, `drizzle-zod` schema generation, and all schema enrichers belong to the PostgreSQL connector, not the neutral base. See [Connectors](/references/base/connectors) for the full base-vs-connector architecture.
|
|
25
25
|
|
|
26
|
-
`Source ->` [`packages/core/src/base/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/models/base.ts)
|
|
26
|
+
`Source ->` [`packages/core-server/src/base/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/models/base.ts)
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
29
|
export abstract class AbstractEntity<Schema = unknown> extends BaseHelper {
|
|
@@ -57,7 +57,7 @@ export abstract class AbstractEntity<Schema = unknown> extends BaseHelper {
|
|
|
57
57
|
|
|
58
58
|
Marks a class as a database entity and configures its behavior.
|
|
59
59
|
|
|
60
|
-
`Source ->` [`packages/core/src/base/metadata/persistents.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/metadata/persistents.ts)
|
|
60
|
+
`Source ->` [`packages/core-server/src/base/metadata/persistents.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/metadata/persistents.ts)
|
|
61
61
|
|
|
62
62
|
```typescript
|
|
63
63
|
@model({
|
|
@@ -102,7 +102,7 @@ When the `@model` decorator is applied:
|
|
|
102
102
|
|
|
103
103
|
PostgreSQL connector entity class, wrapping a Drizzle ORM schema. Extends the neutral `AbstractEntity`.
|
|
104
104
|
|
|
105
|
-
`Source ->` [`packages/core/src/connectors/postgres/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/base.ts)
|
|
105
|
+
`Source ->` [`packages/core-server/src/connectors/postgres/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/models/base.ts)
|
|
106
106
|
|
|
107
107
|
### Purpose
|
|
108
108
|
|
|
@@ -418,7 +418,7 @@ export const myTable = pgTable('MyTable', {
|
|
|
418
418
|
|
|
419
419
|
Adds a primary key `id` column with full TypeScript type inference.
|
|
420
420
|
|
|
421
|
-
`Source ->` [`packages/core/src/connectors/postgres/models/enrichers/id.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/enrichers/id.enricher.ts)
|
|
421
|
+
`Source ->` [`packages/core-server/src/connectors/postgres/models/enrichers/id.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/models/enrichers/id.enricher.ts)
|
|
422
422
|
|
|
423
423
|
```typescript
|
|
424
424
|
generateIdColumnDefs<Opts extends TIdEnricherOptions | undefined>(opts?: Opts): TIdColumnDef<Opts>
|
|
@@ -466,7 +466,7 @@ enrichId(baseColumns: TColumnDefinitions, opts?: TIdEnricherOptions): TColumnDef
|
|
|
466
466
|
|
|
467
467
|
Adds timestamp columns for creation, modification, and soft deletion.
|
|
468
468
|
|
|
469
|
-
`Source ->` [`packages/core/src/connectors/postgres/models/enrichers/tz.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/enrichers/tz.enricher.ts)
|
|
469
|
+
`Source ->` [`packages/core-server/src/connectors/postgres/models/enrichers/tz.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/models/enrichers/tz.enricher.ts)
|
|
470
470
|
|
|
471
471
|
```typescript
|
|
472
472
|
generateTzColumnDefs<Opts extends TTzEnricherOptions | undefined>(opts?: Opts): TTzEnricherResult<Opts>
|
|
@@ -540,7 +540,7 @@ await db.update(myTable).set({ deletedAt: null }).where(eq(myTable.id, id));
|
|
|
540
540
|
|
|
541
541
|
Adds `createdBy` and `modifiedBy` columns tracking which user created or modified a record.
|
|
542
542
|
|
|
543
|
-
`Source ->` [`packages/core/src/connectors/postgres/models/enrichers/user-audit.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/enrichers/user-audit.enricher.ts)
|
|
543
|
+
`Source ->` [`packages/core-server/src/connectors/postgres/models/enrichers/user-audit.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/models/enrichers/user-audit.enricher.ts)
|
|
544
544
|
|
|
545
545
|
```typescript
|
|
546
546
|
generateUserAuditColumnDefs(opts?: TUserAuditEnricherOptions): {
|
|
@@ -571,7 +571,7 @@ Defaults:
|
|
|
571
571
|
|
|
572
572
|
**How it works**
|
|
573
573
|
|
|
574
|
-
- **Retrieval.** The enricher
|
|
574
|
+
- **Retrieval.** The enricher reads the request context from `RequestContextRegistry` at insert/update time, then takes the user ID off the `Authentication.AUDIT_USER_ID` key. An application installs the resolver over that registry when it registers its default middlewares, backed by Hono's `contextStorage`. A host that installs none - a browser Worker - has no request context at all, which is the `allowAnonymous` case below.
|
|
575
575
|
- **`createdBy`.** Set via `$default()` - creation only.
|
|
576
576
|
- **`modifiedBy`.** Set via both `$default()` and `$onUpdate()` - creation and every modification.
|
|
577
577
|
|
|
@@ -610,7 +610,7 @@ enrichUserAudit<ColumnDefinitions extends TColumnDefinitions>(
|
|
|
610
610
|
|
|
611
611
|
Adds polymorphic principal columns for associating a record with different entity types (a comment can belong to a Post, User, or Product).
|
|
612
612
|
|
|
613
|
-
`Source ->` [`packages/core/src/connectors/postgres/models/enrichers/principal.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/enrichers/principal.enricher.ts)
|
|
613
|
+
`Source ->` [`packages/core-server/src/connectors/postgres/models/enrichers/principal.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/models/enrichers/principal.enricher.ts)
|
|
614
614
|
|
|
615
615
|
```typescript
|
|
616
616
|
generatePrincipalColumnDefs<
|
|
@@ -666,7 +666,7 @@ enrichPrincipal<ColumnDefinitions extends TColumnDefinitions>(
|
|
|
666
666
|
|
|
667
667
|
Adds polymorphic data storage columns for entities that store values of different types in a single table (key-value stores, settings tables).
|
|
668
668
|
|
|
669
|
-
`Source ->` [`packages/core/src/connectors/postgres/models/enrichers/data-type.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/enrichers/data-type.enricher.ts)
|
|
669
|
+
`Source ->` [`packages/core-server/src/connectors/postgres/models/enrichers/data-type.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/models/enrichers/data-type.enricher.ts)
|
|
670
670
|
|
|
671
671
|
```typescript
|
|
672
672
|
generateDataTypeColumnDefs(opts?: TDataTypeEnricherOptions): {
|
|
@@ -731,7 +731,7 @@ enrichDataTypes(baseSchema: TColumnDefinitions, opts?: TDataTypeEnricherOptions)
|
|
|
731
731
|
|
|
732
732
|
## Key types
|
|
733
733
|
|
|
734
|
-
`Source ->` [`packages/core/src/connectors/postgres/models/common`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/common)
|
|
734
|
+
`Source ->` [`packages/core-server/src/connectors/postgres/models/common`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/models/common)
|
|
735
735
|
|
|
736
736
|
### `TTableSchemaWithId`
|
|
737
737
|
|
|
@@ -756,7 +756,7 @@ type TTableInsert<T extends TTableSchemaWithId> = T['$inferInsert'];
|
|
|
756
756
|
|
|
757
757
|
Configuration for entity relationships.
|
|
758
758
|
|
|
759
|
-
`Source ->` [`packages/core/src/connectors/postgres/repositories/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/common/types.ts)
|
|
759
|
+
`Source ->` [`packages/core-server/src/connectors/postgres/repositories/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/repositories/common/types.ts)
|
|
760
760
|
|
|
761
761
|
```typescript
|
|
762
762
|
type TRelationConfig = {
|
|
@@ -781,7 +781,7 @@ Used for `relations` on `BaseEntity` - store a function that returns the relatio
|
|
|
781
781
|
|
|
782
782
|
## Schema utilities
|
|
783
783
|
|
|
784
|
-
`Source ->` [`packages/core/src/base/models/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/models/common/types.ts)
|
|
784
|
+
`Source ->` [`packages/core-server/src/base/models/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/models/common/types.ts)
|
|
785
785
|
|
|
786
786
|
### `idParamsSchema`
|
|
787
787
|
|
|
@@ -862,7 +862,7 @@ There are two distinct `getIdType`s in the framework - do not confuse them.
|
|
|
862
862
|
|
|
863
863
|
| | Neutral instance method | PostgreSQL utility function |
|
|
864
864
|
|---|---|---|
|
|
865
|
-
| Location | `AbstractEntity.getIdType()` ([`base/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/models/base.ts)) | `getIdType()` ([`connectors/postgres/models/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/common/types.ts)) |
|
|
865
|
+
| Location | `AbstractEntity.getIdType()` ([`base/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/models/base.ts)) | `getIdType()` ([`connectors/postgres/models/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/models/common/types.ts)) |
|
|
866
866
|
| Signature | `getIdType(): TIdSchemaType` | `getIdType<T extends TTableSchemaWithId>(opts: { entity: T }): string` |
|
|
867
867
|
| Purpose | Neutral capability every engine's entity implements - returns `'string'` \| `'number'` at the entity level. Used by `idParamsSchema` to build the right Zod schema for path parameters. | PostgreSQL-specific - inspects a Drizzle table schema's `id` column and returns its `dataType`, or `'unknown'` if not determinable |
|
|
868
868
|
|
|
@@ -32,7 +32,8 @@ A `@repository` binds this model to a datasource, and the datasource auto-discov
|
|
|
32
32
|
- **Registration.** The `@model` decorator registers the class in the framework's metadata registry, keyed by table name (resolved as `tableName` > static `TABLE_NAME` > class name).
|
|
33
33
|
- **Validation at decoration time.** It validates `settings.defaultLimit` and, when you declare an authorization principal, copies it onto the static `AUTHORIZATION_SUBJECT` property.
|
|
34
34
|
- **Plain Drizzle schema.** The static `schema` is a plain Drizzle `pgTable`. Enrichers such as `generateIdColumnDefs` return column definitions you spread into that table, so common columns (id, timestamps, audit, principal) stay standardized across models.
|
|
35
|
-
- **Zod on demand.** `BaseEntity` generates Zod schemas from the Drizzle schema via `getSchema({ type })` - `'select'`, `'create'`, and `'update'` variants for validating query results, inserts, and updates.
|
|
35
|
+
- **Zod on demand.** `BaseEntity` generates Zod schemas from the Drizzle schema via `getSchema({ type })` - `'select'`, `'create'`, and `'update'` variants for validating query results, inserts, and updates.
|
|
36
|
+
- The generator is a shared lazy singleton, so there is no per-entity cost.
|
|
36
37
|
|
|
37
38
|
**Two layers**
|
|
38
39
|
|
|
@@ -149,5 +150,5 @@ See [Authorization](/extensions/components/authorization/usage#model-based-resou
|
|
|
149
150
|
|
|
150
151
|
**Files:**
|
|
151
152
|
|
|
152
|
-
- [`packages/core/src/base/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/models/base.ts) - neutral `AbstractEntity`
|
|
153
|
-
- [`packages/core/src/connectors/postgres/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/base.ts) - PostgreSQL `BaseEntity`
|
|
153
|
+
- [`packages/core-server/src/base/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/models/base.ts) - neutral `AbstractEntity`
|
|
154
|
+
- [`packages/core-server/src/connectors/postgres/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/connectors/postgres/models/base.ts) - PostgreSQL `BaseEntity`
|
|
@@ -10,7 +10,7 @@ lastUpdated: 2026-03-15
|
|
|
10
10
|
Providers implement the Factory pattern in IGNIS, allowing you to create and configure instances dynamically at runtime based on configuration or context. Unlike services that contain business logic, providers are factories that produce values, instances, or functions.
|
|
11
11
|
|
|
12
12
|
**Files:**
|
|
13
|
-
- `packages/core/src/base/providers/base.ts`
|
|
13
|
+
- `packages/core-server/src/base/providers/base.ts`
|
|
14
14
|
|
|
15
15
|
## Prerequisites
|
|
16
16
|
|
|
@@ -229,7 +229,7 @@ export class LoggerProvider extends BaseProvider<ILogger> {
|
|
|
229
229
|
}
|
|
230
230
|
```
|
|
231
231
|
|
|
232
|
-
Register the provider with `.toProvider()
|
|
232
|
+
Register the provider with `.toProvider()`. Consumers then `get()` the **produced value**, not the provider instance - the container instantiates the provider and calls `value(container)` for you:
|
|
233
233
|
|
|
234
234
|
```typescript
|
|
235
235
|
// In your application (e.g. preConfigure)
|
|
@@ -327,7 +327,7 @@ graph TD
|
|
|
327
327
|
|
|
328
328
|
1. **Registered via `.toProvider()`**: Providers are bound explicitly (`bind({ key }).toProvider(MyProvider)`), not auto-scanned
|
|
329
329
|
2. **`value()` Called by the Container**: `container.get({ key })` instantiates the provider and calls `value(container)` - consumers receive the produced value, never the provider instance
|
|
330
|
-
3. **Singleton Scope Caches the Produced Value**: With `.setScope(BindingScopes.SINGLETON)`, the container caches the result of `value()` and returns it on subsequent `get()` calls
|
|
330
|
+
3. **Singleton Scope Caches the Produced Value**: With `.setScope(BindingScopes.SINGLETON)`, the container caches the result of `value()` and returns it on subsequent `get()` calls. With the default transient scope, `value()` runs on every `get()`
|
|
331
331
|
4. **Factory vs Instance**: Providers can return:
|
|
332
332
|
- Direct instances (created each time `value()` is called)
|
|
333
333
|
- Factory functions (deferred creation)
|
|
@@ -350,7 +350,7 @@ app.bind({ key: 'providers.Database' }).toProvider(DatabaseProvider);
|
|
|
350
350
|
|
|
351
351
|
### Example 1: Mail Transport Provider
|
|
352
352
|
|
|
353
|
-
From `packages/core/src/components/mail/providers/mail-transporter.provider.ts`:
|
|
353
|
+
From `packages/core-server/src/components/mail/providers/mail-transporter.provider.ts`:
|
|
354
354
|
|
|
355
355
|
```typescript
|
|
356
356
|
export type TGetMailTransportFn = (options: TMailOptions) => IMailTransport;
|
|
@@ -419,7 +419,7 @@ this.application.bind({ key: MailKeys.MAIL_TRANSPORT_INSTANCE }).toValue(mailTra
|
|
|
419
419
|
|
|
420
420
|
### Example 2: Queue Executor Provider
|
|
421
421
|
|
|
422
|
-
From `packages/core/src/components/mail/providers/mail-queue-executor.provider.ts`:
|
|
422
|
+
From `packages/core-server/src/components/mail/providers/mail-queue-executor.provider.ts`:
|
|
423
423
|
|
|
424
424
|
```typescript
|
|
425
425
|
export type TGetMailQueueExecutorFn = (config: IMailQueueExecutorConfig) => IMailQueueExecutor;
|
|
@@ -472,7 +472,7 @@ export class MailQueueExecutorProvider extends BaseProvider<TGetMailQueueExecuto
|
|
|
472
472
|
Providers can also produce middleware. `RequestSpyMiddleware` is a real-world example that implements `IProvider<MiddlewareHandler>` directly (extending `BaseHelper`, not `BaseProvider`):
|
|
473
473
|
|
|
474
474
|
```typescript
|
|
475
|
-
// From packages/core/src/base/middlewares/request-spy/request-spy.middleware.ts
|
|
475
|
+
// From packages/core-server/src/base/middlewares/request-spy/request-spy.middleware.ts
|
|
476
476
|
export class RequestSpyMiddleware extends BaseHelper implements IProvider<MiddlewareHandler> {
|
|
477
477
|
static readonly REQUEST_ID_KEY = 'requestId';
|
|
478
478
|
|
|
@@ -509,7 +509,7 @@ export class RequestSpyMiddleware extends BaseHelper implements IProvider<Middle
|
|
|
509
509
|
|
|
510
510
|
See [Middlewares](./middlewares.md) for the full implementation (IP resolution, body-parsing rules, and production log redaction).
|
|
511
511
|
|
|
512
|
-
Note that `RequestSpyMiddleware.value()` does not accept a `container` parameter
|
|
512
|
+
Note that `RequestSpyMiddleware.value()` does not accept a `container` parameter. The `IProvider<T>` interface defines `value(container: Container): T`, but implementations may ignore the parameter when they don't need container access. In practice, `RequestSpyMiddleware` is registered via `RequestTrackerComponent`, which binds it as a provider in the DI container and resolves it automatically.
|
|
513
513
|
|
|
514
514
|
|
|
515
515
|
## Common Patterns
|
|
@@ -545,7 +545,7 @@ export class S3StorageProvider extends BaseProvider<S3Storage> {
|
|
|
545
545
|
|
|
546
546
|
### Pattern 2: Lazy Singleton
|
|
547
547
|
|
|
548
|
-
Create instance only once, lazily (or
|
|
548
|
+
Create instance only once, lazily (or bind with `.setScope(BindingScopes.SINGLETON)` and let the container cache the produced value):
|
|
549
549
|
|
|
550
550
|
```typescript
|
|
551
551
|
export class DatabaseConnectionProvider extends BaseProvider<DatabaseConnection> {
|