@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
|
@@ -105,9 +105,38 @@ Combine AND and OR for complex logic:
|
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
|
|
108
|
+
## Empty Groups
|
|
109
|
+
|
|
110
|
+
An empty `and`/`or` array is not a no-op -- each empty case resolves to what the operator means with zero conditions:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Empty AND is vacuously TRUE - dropped from the query entirely
|
|
114
|
+
{ where: { and: [] } }
|
|
115
|
+
// SQL: (no condition added)
|
|
116
|
+
|
|
117
|
+
// Empty OR is vacuously FALSE - compiles to a condition that matches nothing
|
|
118
|
+
{ where: { or: [] } }
|
|
119
|
+
// SQL: WHERE false
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This matters when the array is built from a caller-supplied list, e.g. `{ or: permittedOrgIds.map(id => ({ orgId: id })) }`: an empty permission list must return zero rows, not every row, so `or: []` matching nothing is the safe default.
|
|
123
|
+
|
|
124
|
+
|
|
108
125
|
## NOT Logic
|
|
109
126
|
|
|
110
|
-
|
|
127
|
+
IGNIS has a general-purpose `not` operator that negates whatever condition it wraps - a bare value negates `eq`, and a nested operator object negates that operator. It is supported on the PostgreSQL connector:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// NOT equal (bare value negates eq)
|
|
131
|
+
{ where: { status: { not: 'archived' } } }
|
|
132
|
+
// SQL: WHERE NOT ("status" = 'archived')
|
|
133
|
+
|
|
134
|
+
// Negate a nested operator condition
|
|
135
|
+
{ where: { views: { not: { gt: 100 } } } }
|
|
136
|
+
// SQL: WHERE NOT ("views" > 100)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The dedicated negation operators remain available and are often clearer for a single condition:
|
|
111
140
|
|
|
112
141
|
```typescript
|
|
113
142
|
// NOT equal
|
|
@@ -131,6 +160,9 @@ Ignis does not have a standalone `not` logical operator. Instead, use negation o
|
|
|
131
160
|
{ where: { score: { notBetween: [40, 60] } } }
|
|
132
161
|
```
|
|
133
162
|
|
|
163
|
+
> [!NOTE]
|
|
164
|
+
> `ne`/`neq`/`nin` follow SQL three-valued logic - a row whose field is `NULL` never matches them (`NULL <> value` is UNKNOWN, not TRUE). Use `exists`/`notExists` or an explicit `{ field: null }` branch when you need NULL rows in the result.
|
|
165
|
+
|
|
134
166
|
|
|
135
167
|
## Complex Example
|
|
136
168
|
|
|
@@ -71,6 +71,30 @@ The simplest way to check for NULL:
|
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
|
|
74
|
+
## exists / notExists - Presence Check
|
|
75
|
+
|
|
76
|
+
`exists` and `notExists` are presence operators - they take a boolean rather than a value, and read more naturally than `is`/`isn` against `null`. They work on the PostgreSQL connector, and `exists` also works over JSON paths there.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Field is present (IS NOT NULL)
|
|
80
|
+
{ where: { deletedAt: { exists: false } } } // no deletedAt -> IS NULL
|
|
81
|
+
{ where: { verifiedAt: { exists: true } } } // has verifiedAt -> IS NOT NULL
|
|
82
|
+
|
|
83
|
+
// notExists is the inverse
|
|
84
|
+
{ where: { verifiedAt: { notExists: true } } } // IS NULL
|
|
85
|
+
|
|
86
|
+
// exists over a JSON path (PostgreSQL)
|
|
87
|
+
{ where: { 'metadata.score': { exists: true } } }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
| Syntax | SQL | Description |
|
|
91
|
+
|--------|-----|-------------|
|
|
92
|
+
| `{ field: { exists: true } }` | `IS NOT NULL` | Field is present |
|
|
93
|
+
| `{ field: { exists: false } }` | `IS NULL` | Field is missing/null |
|
|
94
|
+
| `{ field: { notExists: true } }` | `IS NULL` | Inverse of exists |
|
|
95
|
+
| `{ field: { notExists: false } }` | `IS NOT NULL` | Inverse of exists |
|
|
96
|
+
|
|
97
|
+
|
|
74
98
|
## Null Check Summary
|
|
75
99
|
|
|
76
100
|
| Syntax | SQL | Description |
|
|
@@ -78,11 +102,16 @@ The simplest way to check for NULL:
|
|
|
78
102
|
| `{ field: null }` | `IS NULL` | Direct null check |
|
|
79
103
|
| `{ field: { eq: null } }` | `IS NULL` | Explicit null equality |
|
|
80
104
|
| `{ field: { is: null } }` | `IS NULL` | IS operator with null |
|
|
105
|
+
| `{ field: { exists: false } }` | `IS NULL` | Presence check (false = missing) |
|
|
81
106
|
| `{ field: { ne: null } }` | `IS NOT NULL` | Not-equal null check |
|
|
82
107
|
| `{ field: { neq: null } }` | `IS NOT NULL` | Alias for ne with null |
|
|
83
108
|
| `{ field: { isn: null } }` | `IS NOT NULL` | IS NOT operator with null |
|
|
109
|
+
| `{ field: { exists: true } }` | `IS NOT NULL` | Presence check (true = present) |
|
|
110
|
+
|
|
111
|
+
All the IS NULL / IS NOT NULL syntaxes above are equivalent -- use whichever reads best in context.
|
|
84
112
|
|
|
85
|
-
|
|
113
|
+
> [!NOTE]
|
|
114
|
+
> `ne`/`neq` follow SQL three-valued logic: a row whose field is `NULL` never matches `{ field: { neq: value } }`. This is intentional (`NULL <> value` is UNKNOWN, not TRUE). Reach for `exists`/`notExists` or an explicit `{ field: null }` branch when you want NULL rows included.
|
|
86
115
|
|
|
87
116
|
|
|
88
117
|
## Common Patterns
|
|
@@ -21,6 +21,9 @@ Complete single-page reference for all IGNIS filter operators. For detailed expl
|
|
|
21
21
|
| `lt` | `<` | `{ price: { lt: 100 } }` | Less than |
|
|
22
22
|
| `lte` | `<=` | `{ price: { lte: 100 } }` | Less than or equal |
|
|
23
23
|
|
|
24
|
+
> [!NOTE]
|
|
25
|
+
> `ne`/`neq` follow SQL three-valued logic: a row whose field is `NULL` **never** matches `{ field: { neq: value } }` (`NULL <> value` evaluates to UNKNOWN, not TRUE). To include NULL rows, add `{ or: [{ field: { neq: value } }, { field: null }] }`.
|
|
26
|
+
|
|
24
27
|
**See:** [Comparison Operators Guide](./comparison-operators.md)
|
|
25
28
|
|
|
26
29
|
|
|
@@ -88,12 +91,27 @@ Complete single-page reference for all IGNIS filter operators. For detailed expl
|
|
|
88
91
|
**See:** [Null Operators Guide](./null-operators.md)
|
|
89
92
|
|
|
90
93
|
|
|
94
|
+
## Presence & Negation Operators
|
|
95
|
+
|
|
96
|
+
| Operator | SQL | TypeScript Example | Description |
|
|
97
|
+
|----------|-----|-------------------|-------------|
|
|
98
|
+
| `exists` | `IS NOT NULL` / `IS NULL` | `{ deletedAt: { exists: false } }` | `exists: true` -> IS NOT NULL, `exists: false` -> IS NULL |
|
|
99
|
+
| `notExists` | `IS NULL` / `IS NOT NULL` | `{ verifiedAt: { notExists: true } }` | Inverse of `exists` (`notExists: true` -> IS NULL) |
|
|
100
|
+
| `not` | `NOT (...)` | `{ status: { not: 'archived' } }` / `{ views: { not: { gt: 100 } } }` | Negates the nested condition; a bare value negates `eq` |
|
|
101
|
+
|
|
102
|
+
`exists`/`notExists`/`not` are supported on the PostgreSQL connector. `not` recurses: `{ not: { gt: 100 } }` becomes `NOT (col > 100)`, and `{ not: 5 }` becomes `NOT (col = 5)`. `exists` also works over JSON paths on PostgreSQL (`{ 'metadata.score': { exists: true } }`).
|
|
103
|
+
|
|
104
|
+
**See:** [Null Operators Guide](./null-operators.md)
|
|
105
|
+
|
|
106
|
+
|
|
91
107
|
## Logical Operators
|
|
92
108
|
|
|
93
109
|
| Operator | SQL | TypeScript Example | Description |
|
|
94
110
|
|----------|-----|-------------------|-------------|
|
|
95
111
|
| `and` | `AND` | `{ and: [{ age: { gt: 18 } }, { status: 'active' }] }` | All conditions must be true |
|
|
96
112
|
| `or` | `OR` | `{ or: [{ role: 'admin' }, { role: 'moderator' }] }` | At least one condition must be true |
|
|
113
|
+
| `and: []` | (dropped) | `{ and: [] }` | Vacuously true - no condition added to the query |
|
|
114
|
+
| `or: []` | `false` | `{ or: [] }` | Vacuously false - matches no rows |
|
|
97
115
|
|
|
98
116
|
**Implicit AND:**
|
|
99
117
|
```typescript
|
|
@@ -106,7 +124,7 @@ Complete single-page reference for all IGNIS filter operators. For detailed expl
|
|
|
106
124
|
// WHERE status = 'active' AND age >= 18 AND role = 'user'
|
|
107
125
|
```
|
|
108
126
|
|
|
109
|
-
**NOT logic** is expressed via negation operators (`ne`, `neq`, `nin`, `nlike`, `nilike`, `notBetween`, `isn`).
|
|
127
|
+
**NOT logic** is expressed via the general-purpose `not` operator (`{ field: { not: <value | operatorObject> } }`) or the dedicated negation operators (`ne`, `neq`, `nin`, `nlike`, `nilike`, `notBetween`, `isn`).
|
|
110
128
|
|
|
111
129
|
**See:** [Logical Operators Guide](./logical-operators.md)
|
|
112
130
|
|
|
@@ -170,6 +188,7 @@ All comparison operators work with JSON path queries:
|
|
|
170
188
|
- `between`, `notBetween`
|
|
171
189
|
- `regexp`, `iregexp`
|
|
172
190
|
- `is`, `isn`
|
|
191
|
+
- `exists`, `notExists`, `not`
|
|
173
192
|
|
|
174
193
|
Numeric operators (`gt`, `gte`, `lt`, `lte`, `between`, `notBetween`) use safe numeric casting to handle mixed JSON value types.
|
|
175
194
|
|
|
@@ -181,7 +200,7 @@ Numeric operators (`gt`, `gte`, `lt`, `lte`, `between`, `notBetween`) use safe n
|
|
|
181
200
|
### Select Specific Fields
|
|
182
201
|
|
|
183
202
|
```typescript
|
|
184
|
-
const users = await
|
|
203
|
+
const users = await userRepository.find({
|
|
185
204
|
filter: {
|
|
186
205
|
where: { isActive: true },
|
|
187
206
|
fields: ['id', 'name', 'email'], // Only return these fields
|
|
@@ -242,11 +261,11 @@ export class User extends BaseEntity<typeof User.schema> {
|
|
|
242
261
|
}
|
|
243
262
|
|
|
244
263
|
// All queries automatically include the default filter
|
|
245
|
-
await
|
|
264
|
+
await userRepository.find({ filter: {} });
|
|
246
265
|
// WHERE isDeleted = false LIMIT 100
|
|
247
266
|
|
|
248
267
|
// Skip default filter for admin operations
|
|
249
|
-
await
|
|
268
|
+
await userRepository.find({
|
|
250
269
|
filter: {},
|
|
251
270
|
options: { shouldSkipDefaultFilter: true },
|
|
252
271
|
});
|
|
@@ -65,7 +65,7 @@ const chunkSize = 500;
|
|
|
65
65
|
const results = [];
|
|
66
66
|
for (let i = 0; i < allIds.length; i += chunkSize) {
|
|
67
67
|
const chunk = allIds.slice(i, i + chunkSize);
|
|
68
|
-
const chunkResults = await
|
|
68
|
+
const chunkResults = await repository.find({
|
|
69
69
|
filter: { where: { id: { in: chunk } } }
|
|
70
70
|
});
|
|
71
71
|
results.push(...chunkResults);
|
|
@@ -89,7 +89,7 @@ for (let i = 0; i < allIds.length; i += chunkSize) {
|
|
|
89
89
|
|
|
90
90
|
```typescript
|
|
91
91
|
// Enable logging to see generated SQL
|
|
92
|
-
const result = await
|
|
92
|
+
const result = await repository.find({
|
|
93
93
|
filter: complexFilter,
|
|
94
94
|
options: {
|
|
95
95
|
log: { use: true, level: 'debug' },
|
|
@@ -97,7 +97,7 @@ const result = await repo.find({
|
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
// Or use buildQuery to inspect without executing
|
|
100
|
-
const queryOptions =
|
|
100
|
+
const queryOptions = repository.buildQuery({ filter: complexFilter });
|
|
101
101
|
console.log('Generated query options:', queryOptions);
|
|
102
102
|
```
|
|
103
103
|
|
|
@@ -124,7 +124,7 @@ console.log('Generated query options:', queryOptions);
|
|
|
124
124
|
## Tip 8: Combining Multiple Array Conditions
|
|
125
125
|
|
|
126
126
|
```typescript
|
|
127
|
-
await
|
|
127
|
+
await productRepository.find({
|
|
128
128
|
filter: {
|
|
129
129
|
where: {
|
|
130
130
|
// Must have ALL these categories
|
|
@@ -181,7 +181,7 @@ const createPaginationFilter = (page: number, size: number = 20) => ({
|
|
|
181
181
|
});
|
|
182
182
|
|
|
183
183
|
// Usage
|
|
184
|
-
const products = await
|
|
184
|
+
const products = await productRepository.find({
|
|
185
185
|
filter: {
|
|
186
186
|
where: {
|
|
187
187
|
...createActiveFilter(),
|
|
@@ -12,7 +12,7 @@ Real-world examples of filter usage with corresponding SQL.
|
|
|
12
12
|
## E-commerce Product Search
|
|
13
13
|
|
|
14
14
|
```typescript
|
|
15
|
-
const products = await
|
|
15
|
+
const products = await productRepository.find({
|
|
16
16
|
filter: {
|
|
17
17
|
where: {
|
|
18
18
|
category: 'electronics',
|
|
@@ -44,7 +44,7 @@ const products = await productRepo.find({
|
|
|
44
44
|
const thirtyDaysAgo = new Date();
|
|
45
45
|
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
46
46
|
|
|
47
|
-
const recentUsers = await
|
|
47
|
+
const recentUsers = await userRepository.find({
|
|
48
48
|
filter: {
|
|
49
49
|
where: {
|
|
50
50
|
createdAt: { gte: thirtyDaysAgo },
|
|
@@ -71,7 +71,7 @@ const recentUsers = await userRepo.find({
|
|
|
71
71
|
## Task Management: Priority Tags
|
|
72
72
|
|
|
73
73
|
```typescript
|
|
74
|
-
const priorityTasks = await
|
|
74
|
+
const priorityTasks = await taskRepository.find({
|
|
75
75
|
filter: {
|
|
76
76
|
where: {
|
|
77
77
|
status: { nin: ['completed', 'cancelled'] },
|
|
@@ -100,7 +100,7 @@ const priorityTasks = await taskRepo.find({
|
|
|
100
100
|
|
|
101
101
|
```typescript
|
|
102
102
|
// Find active records (soft delete pattern)
|
|
103
|
-
const activeRecords = await
|
|
103
|
+
const activeRecords = await repository.find({
|
|
104
104
|
filter: {
|
|
105
105
|
where: { deletedAt: { is: null } },
|
|
106
106
|
}
|
|
@@ -112,7 +112,7 @@ const activeRecords = await repo.find({
|
|
|
112
112
|
|
|
113
113
|
```typescript
|
|
114
114
|
// Find ONLY soft-deleted records
|
|
115
|
-
const deletedRecords = await
|
|
115
|
+
const deletedRecords = await repository.find({
|
|
116
116
|
filter: {
|
|
117
117
|
where: { deletedAt: { isn: null } },
|
|
118
118
|
}
|
|
@@ -142,7 +142,7 @@ const getAuthorizedFilter = (user: User): TWhere<TDocumentSchema> => {
|
|
|
142
142
|
};
|
|
143
143
|
};
|
|
144
144
|
|
|
145
|
-
const documents = await
|
|
145
|
+
const documents = await documentRepository.find({
|
|
146
146
|
filter: {
|
|
147
147
|
where: getAuthorizedFilter(currentUser),
|
|
148
148
|
order: ['updatedAt DESC'],
|
|
@@ -206,7 +206,7 @@ const searchProducts = async (query: string, filters: {
|
|
|
206
206
|
where.categories = { contains: filters.categories };
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
return
|
|
209
|
+
return productRepository.find({
|
|
210
210
|
filter: {
|
|
211
211
|
where,
|
|
212
212
|
order: ['rating DESC', 'createdAt DESC'],
|
|
@@ -281,7 +281,7 @@ const massiveFilter: TFilter<TProductSchema> = {
|
|
|
281
281
|
],
|
|
282
282
|
};
|
|
283
283
|
|
|
284
|
-
const products = await
|
|
284
|
+
const products = await productRepository.find({ filter: massiveFilter });
|
|
285
285
|
|
|
286
286
|
// SQL:
|
|
287
287
|
// SELECT "id", "name", "price", "rating", "tags", "metadata"
|
|
@@ -334,7 +334,7 @@ const products = await productRepo.find({ filter: massiveFilter });
|
|
|
334
334
|
const startOfWeek = new Date('2024-12-29');
|
|
335
335
|
const endOfWeek = new Date('2025-01-04');
|
|
336
336
|
|
|
337
|
-
const weekEvents = await
|
|
337
|
+
const weekEvents = await eventRepository.find({
|
|
338
338
|
filter: {
|
|
339
339
|
where: {
|
|
340
340
|
eventDate: { between: [startOfWeek, endOfWeek] }
|
|
@@ -355,7 +355,7 @@ const weekEvents = await eventRepo.find({
|
|
|
355
355
|
const sevenDaysAgo = new Date();
|
|
356
356
|
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
|
357
357
|
|
|
358
|
-
const recentOrders = await
|
|
358
|
+
const recentOrders = await orderRepository.find({
|
|
359
359
|
filter: {
|
|
360
360
|
where: {
|
|
361
361
|
createdAt: { gte: sevenDaysAgo },
|
|
@@ -382,7 +382,7 @@ const recentOrders = await orderRepo.find({
|
|
|
382
382
|
|
|
383
383
|
```typescript
|
|
384
384
|
const getTenantProducts = async (tenantId: string, filter: TFilter<TProductSchema>) => {
|
|
385
|
-
return
|
|
385
|
+
return productRepository.find({
|
|
386
386
|
filter: {
|
|
387
387
|
...filter,
|
|
388
388
|
where: {
|
|
@@ -415,7 +415,7 @@ await getTenantProducts('tenant-abc', {
|
|
|
415
415
|
## Inventory Low Stock Alert
|
|
416
416
|
|
|
417
417
|
```typescript
|
|
418
|
-
const lowStockProducts = await
|
|
418
|
+
const lowStockProducts = await productRepository.find({
|
|
419
419
|
filter: {
|
|
420
420
|
where: {
|
|
421
421
|
status: 'active',
|
|
@@ -6,9 +6,9 @@ difficulty: intermediate
|
|
|
6
6
|
|
|
7
7
|
# Deep Dive: gRPC Controllers
|
|
8
8
|
|
|
9
|
-
Technical reference for gRPC controller classes -- the foundation for building gRPC services in
|
|
9
|
+
Technical reference for gRPC controller classes -- the foundation for building gRPC services in IGNIS, powered by [ConnectRPC](https://connectrpc.com/).
|
|
10
10
|
|
|
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
14
|
- `packages/core/src/base/controllers/grpc/abstract.ts`
|
|
@@ -25,7 +25,7 @@ Ignis gRPC controllers follow the same patterns as REST controllers (decorator-b
|
|
|
25
25
|
|------|-------------|
|
|
26
26
|
| **AbstractGrpcController** | Abstract base class with RPC registration, ConnectRPC adapter mounting, idempotent `configure()` |
|
|
27
27
|
| **BaseGrpcController** | Recommended concrete base class with `bindRoute()` and `defineRoute()` implementations |
|
|
28
|
-
| **GrpcRequestAdapter** | Internal bridge from
|
|
28
|
+
| **GrpcRequestAdapter** | Internal bridge from IGNIS handlers to ConnectRPC universal handlers via `AsyncLocalStorage` |
|
|
29
29
|
| **GrpcComponent** | Auto-discovers gRPC controllers and mounts them on the application router |
|
|
30
30
|
| **@controller** | Class decorator with `transport: ControllerTransports.GRPC` and `service` field |
|
|
31
31
|
| **@unary** | Method decorator for unary RPCs |
|
|
@@ -57,7 +57,7 @@ bun add @connectrpc/connect-web
|
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
> [!NOTE]
|
|
60
|
-
>
|
|
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 `validateModule()`. `@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
|
|
|
@@ -335,7 +335,7 @@ type TRpcHandler<
|
|
|
335
335
|
```
|
|
336
336
|
|
|
337
337
|
> [!NOTE]
|
|
338
|
-
>
|
|
338
|
+
> The adapter always calls handlers with `{ request, context }` - decorator-based handlers included. If your handler does not need the Hono context, declare only `(opts: { request: RequestType }) => Promise<ResponseType>` and ignore the `context` property.
|
|
339
339
|
|
|
340
340
|
### `IGrpcControllerOptions`
|
|
341
341
|
|
|
@@ -411,11 +411,11 @@ interface IConnectAdapterResult<
|
|
|
411
411
|
|
|
412
412
|
## `GrpcRequestAdapter`
|
|
413
413
|
|
|
414
|
-
Internal bridge between
|
|
414
|
+
Internal bridge between IGNIS gRPC controllers and ConnectRPC's universal handler system. You do not interact with this class directly -- it is created automatically during `configure()`.
|
|
415
415
|
|
|
416
416
|
### Architecture
|
|
417
417
|
|
|
418
|
-
The adapter solves a key challenge: ConnectRPC handlers have their own `(request, context) => response` signature, but
|
|
418
|
+
The adapter solves a key challenge: ConnectRPC handlers have their own `(request, context) => response` signature, but IGNIS controllers 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
|
|
@@ -423,7 +423,7 @@ Hono Request
|
|
|
423
423
|
-> AsyncLocalStorage.run(honoContext, ...)
|
|
424
424
|
-> Pre-built auth middlewares (authenticate -> authorize)
|
|
425
425
|
-> ConnectRPC universal handler
|
|
426
|
-
->
|
|
426
|
+
-> IGNIS TRpcHandler (reads context from AsyncLocalStorage)
|
|
427
427
|
-> Response
|
|
428
428
|
```
|
|
429
429
|
|
|
@@ -449,7 +449,7 @@ The optional `interceptors` array is passed to ConnectRPC's `createConnectRouter
|
|
|
449
449
|
|
|
450
450
|
### Internal Flow
|
|
451
451
|
|
|
452
|
-
1. **`buildConnectHandlers()`** -- Wraps each
|
|
452
|
+
1. **`buildConnectHandlers()`** -- Wraps each IGNIS `TRpcHandler` into ConnectRPC's `(request, context) => response` signature. The wrapper reads the Hono context from `AsyncLocalStorage`, runs pre-built auth middlewares (built by `AbstractGrpcController.buildRpcMiddlewares()`), then passes `{ request, context }` to the IGNIS handler.
|
|
453
453
|
|
|
454
454
|
2. **`registerService()`** -- Bridges the opaque `ServiceType` from `@controller` metadata to ConnectRPC's `router.service()` call, registering all handlers for the service.
|
|
455
455
|
|
|
@@ -839,7 +839,7 @@ import { GreeterService, SayHelloRequestSchema } from './controllers/greeter/def
|
|
|
839
839
|
const transport = createConnectTransport({ baseUrl: 'http://localhost:3000/grpc' });
|
|
840
840
|
const client = createClient(GreeterService, transport);
|
|
841
841
|
|
|
842
|
-
const response = await client.sayHello(create(SayHelloRequestSchema, { name: '
|
|
842
|
+
const response = await client.sayHello(create(SayHelloRequestSchema, { name: 'IGNIS' }));
|
|
843
843
|
console.log(response.message);
|
|
844
844
|
```
|
|
845
845
|
|
|
@@ -928,7 +928,7 @@ Per-RPC authentication and authorization are configured via the `authenticate` a
|
|
|
928
928
|
name: 'sayHello',
|
|
929
929
|
authenticate: {
|
|
930
930
|
strategies: ['jwt'],
|
|
931
|
-
mode: '
|
|
931
|
+
mode: 'any',
|
|
932
932
|
},
|
|
933
933
|
},
|
|
934
934
|
})
|
|
@@ -941,7 +941,7 @@ async sayHello(opts: { request: SayHelloRequest }): Promise<SayHelloResponse> {
|
|
|
941
941
|
| Field | Type | Default | Description |
|
|
942
942
|
|-------|------|---------|-------------|
|
|
943
943
|
| `strategies` | `TAuthStrategy[]` | `[]` | Authentication strategies to apply (e.g., `['jwt']`, `['basic']`) |
|
|
944
|
-
| `mode` | `TAuthMode` | `'any'` | `'
|
|
944
|
+
| `mode` | `TAuthMode` | `'any'` | `'any'` \| `'all'` (defaults to `AuthenticationModes.ANY`) |
|
|
945
945
|
|
|
946
946
|
### Per-RPC Authorization
|
|
947
947
|
|
|
@@ -949,7 +949,7 @@ async sayHello(opts: { request: SayHelloRequest }): Promise<SayHelloResponse> {
|
|
|
949
949
|
@unary({
|
|
950
950
|
configs: {
|
|
951
951
|
name: 'deleteUser',
|
|
952
|
-
authenticate: { strategies: ['jwt'], mode: '
|
|
952
|
+
authenticate: { strategies: ['jwt'], mode: 'any' },
|
|
953
953
|
authorize: { action: 'delete', resource: 'user' },
|
|
954
954
|
},
|
|
955
955
|
})
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# Base Abstractions
|
|
2
2
|
|
|
3
|
-
Core classes that power every
|
|
3
|
+
Core classes that power every IGNIS application - from the Application entry point to Repositories for data access.
|
|
4
|
+
|
|
5
|
+
> [!IMPORTANT] Base vs. Connectors
|
|
6
|
+
> The persistence layer (`BaseDataSource`/`BaseEntity`/CRUD repositories) is split into an engine-neutral root (`src/base`) and per-engine connectors (`src/connectors/{postgres,typesense}`). `BaseDataSource` and `BaseEntity` below refer to the **PostgreSQL connector**'s canonical `BasePostgresDataSource`/`BasePostgresEntity` (re-exported under these compatibility names) - see [Connectors](./connectors) for the full picture, and [Search & Typesense](/guides/core-concepts/persistent/search-typesense) for the other engine.
|
|
4
7
|
|
|
5
8
|
## Quick Reference
|
|
6
9
|
|
|
@@ -12,10 +15,10 @@ Core classes that power every Ignis application - from the Application entry poi
|
|
|
12
15
|
| `BaseService` | Business logic layer | - |
|
|
13
16
|
| `BaseProvider` | Factory pattern for runtime instantiation | `BaseHelper` |
|
|
14
17
|
| `BaseComponent` | Pluggable feature modules | - |
|
|
15
|
-
| `BaseDataSource` |
|
|
16
|
-
| `BaseEntity` |
|
|
17
|
-
| `DefaultCRUDRepository` | Full CRUD operations | `PersistableRepository` |
|
|
18
|
-
| `ReadableRepository` | Read-only operations | `AbstractRepository` |
|
|
18
|
+
| `BaseDataSource` (alias of `BasePostgresDataSource`) | PostgreSQL connections | `AbstractPostgresDataSource` -> `AbstractDataSource` |
|
|
19
|
+
| `BaseEntity` (alias of `BasePostgresEntity`) | Drizzle model definitions | `AbstractEntity` |
|
|
20
|
+
| `DefaultCRUDRepository` | Full CRUD operations (PostgreSQL connector) | `PersistableRepository` -> ... -> `AbstractRepository` |
|
|
21
|
+
| `ReadableRepository` | Read-only operations (PostgreSQL connector) | `PostgresBaseRepository` -> `AbstractRepository` |
|
|
19
22
|
|
|
20
23
|
## Architecture
|
|
21
24
|
|
|
@@ -67,6 +70,7 @@ Core classes that power every Ignis application - from the Application entry poi
|
|
|
67
70
|
- [Components](./components.md) - Pluggable modules, component lifecycle
|
|
68
71
|
|
|
69
72
|
### Data Layer
|
|
73
|
+
- [Connectors](./connectors.md) - Base-vs-connectors architecture, dual-door exports, adding an engine
|
|
70
74
|
- [Models & Enrichers](./models.md) - `BaseEntity`, schema definitions, enrichers
|
|
71
75
|
- [DataSources](./datasources.md) - Database connections, auto-discovery
|
|
72
76
|
- [Repositories](./repositories/) - CRUD operations, filtering, relations
|
|
@@ -78,11 +82,14 @@ Core classes that power every Ignis application - from the Application entry poi
|
|
|
78
82
|
AbstractApplication
|
|
79
83
|
└── BaseApplication ──────► Your Application
|
|
80
84
|
|
|
81
|
-
AbstractRepository
|
|
82
|
-
├──
|
|
83
|
-
│
|
|
84
|
-
│
|
|
85
|
-
│
|
|
85
|
+
AbstractRepository (engine-neutral, src/base)
|
|
86
|
+
├── PostgresBaseRepository (connectors/postgres)
|
|
87
|
+
│ ├── ReadableRepository
|
|
88
|
+
│ │ └── PersistableRepository
|
|
89
|
+
│ │ └── DefaultCRUDRepository ──────► Your Repository
|
|
90
|
+
└── TypesenseBaseRepository (connectors/typesense)
|
|
91
|
+
└── ReadableSearchRepository -> ... -> DefaultSearchRepository
|
|
92
|
+
|
|
86
93
|
AbstractRestController
|
|
87
94
|
└── BaseRestController ──────► Your REST Controller
|
|
88
95
|
|
|
@@ -91,8 +98,13 @@ AbstractGrpcController
|
|
|
91
98
|
BaseService ──────► Your Service
|
|
92
99
|
BaseProvider ──────► Your Provider
|
|
93
100
|
BaseComponent ──────► Your Component
|
|
94
|
-
|
|
95
|
-
|
|
101
|
+
|
|
102
|
+
AbstractDataSource (engine-neutral, src/base)
|
|
103
|
+
├── AbstractPostgresDataSource -> BasePostgresDataSource (alias: BaseDataSource) ──────► Your DataSource
|
|
104
|
+
└── AbstractSearchDataSource -> BaseSearchDataSource -> TypesenseDataSource
|
|
105
|
+
|
|
106
|
+
AbstractEntity (engine-neutral, src/base)
|
|
107
|
+
└── BasePostgresEntity (alias: BaseEntity) ──────► Your Model
|
|
96
108
|
```
|
|
97
109
|
|
|
98
110
|
> **Related:** [Core Concepts Guide](../../guides/core-concepts/application/) | [Persistent Layer Guide](../../guides/core-concepts/persistent/)
|