@venizia/ignis-docs 0.0.1-7 → 0.0.1-9
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/package.json +12 -12
- package/wiki/changelogs/2025-12-17-refactor.md +22 -0
- package/wiki/changelogs/2025-12-18-performance-optimizations.md +192 -0
- package/wiki/changelogs/2025-12-18-repository-validation-security.md +445 -0
- package/wiki/changelogs/index.md +22 -0
- package/wiki/changelogs/v0.0.1-7-initial-architecture.md +137 -0
- package/wiki/changelogs/v0.0.1-8-model-repo-datasource-refactor.md +278 -0
- package/wiki/get-started/5-minute-quickstart.md +1 -1
- package/wiki/get-started/best-practices/api-usage-examples.md +12 -8
- package/wiki/get-started/best-practices/common-pitfalls.md +2 -2
- package/wiki/get-started/best-practices/data-modeling.md +14 -20
- package/wiki/get-started/building-a-crud-api.md +60 -75
- package/wiki/get-started/core-concepts/controllers.md +14 -14
- package/wiki/get-started/core-concepts/persistent.md +110 -130
- package/wiki/get-started/quickstart.md +1 -1
- package/wiki/references/base/controllers.md +40 -16
- package/wiki/references/base/datasources.md +195 -33
- package/wiki/references/base/dependency-injection.md +5 -5
- package/wiki/references/base/models.md +398 -28
- package/wiki/references/base/repositories.md +475 -22
- package/wiki/references/components/authentication.md +224 -7
- package/wiki/references/components/health-check.md +1 -1
- package/wiki/references/components/swagger.md +1 -1
- package/wiki/references/helpers/inversion.md +8 -3
- package/wiki/references/src-details/core.md +6 -5
- package/wiki/references/src-details/inversion.md +4 -4
- package/wiki/references/utilities/request.md +16 -7
|
@@ -9,7 +9,7 @@ Controllers handle HTTP requests and return responses - they're your API endpoin
|
|
|
9
9
|
Extend `BaseController` and use decorators to define routes:
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
|
-
import { BaseController, controller, get, jsonResponse, z } from '@venizia/ignis';
|
|
12
|
+
import { BaseController, controller, get, jsonResponse, z, HTTP } from '@venizia/ignis';
|
|
13
13
|
import { Context } from 'hono';
|
|
14
14
|
|
|
15
15
|
@controller({ path: '/users' })
|
|
@@ -29,7 +29,7 @@ export class UserController extends BaseController {
|
|
|
29
29
|
},
|
|
30
30
|
})
|
|
31
31
|
getAllUsers(c: Context) {
|
|
32
|
-
return c.json([{ id: '1', name: 'John Doe' }]);
|
|
32
|
+
return c.json([{ id: '1', name: 'John Doe' }], HTTP.ResultCodes.RS_2.Ok);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
```
|
|
@@ -177,7 +177,7 @@ const GetUsersRoute = {
|
|
|
177
177
|
this.defineRoute({
|
|
178
178
|
configs: GetUsersRoute,
|
|
179
179
|
handler: (c: TRouteContext<typeof GetUsersRoute>) => { // Return type is automatically inferred
|
|
180
|
-
return c.json([{ id: 1, name: 'John Doe' }]);
|
|
180
|
+
return c.json([{ id: 1, name: 'John Doe' }], HTTP.ResultCodes.RS_2.Ok);
|
|
181
181
|
},
|
|
182
182
|
});
|
|
183
183
|
```
|
|
@@ -187,7 +187,7 @@ this.defineRoute({
|
|
|
187
187
|
This method offers a fluent API for defining routes, similar to `defineRoute`, but structured for chaining. It also benefits from `TRouteContext` for type safety.
|
|
188
188
|
|
|
189
189
|
```typescript
|
|
190
|
-
import { jsonResponse, z, TRouteContext } from '@venizia/ignis';
|
|
190
|
+
import { jsonResponse, z, TRouteContext, HTTP } from '@venizia/ignis';
|
|
191
191
|
|
|
192
192
|
// ... inside the binding() method
|
|
193
193
|
|
|
@@ -205,7 +205,7 @@ this.bindRoute({
|
|
|
205
205
|
}).to({
|
|
206
206
|
handler: (c: TRouteContext<typeof GetUserByIdRoute>) => { // Return type is automatically inferred
|
|
207
207
|
const { id } = c.req.param();
|
|
208
|
-
return c.json({ id: id, name: 'John Doe' });
|
|
208
|
+
return c.json({ id: id, name: 'John Doe' }, HTTP.ResultCodes.RS_2.Ok);
|
|
209
209
|
},
|
|
210
210
|
});
|
|
211
211
|
```
|
|
@@ -255,17 +255,17 @@ export class ConfigurationController extends _Controller {
|
|
|
255
255
|
```
|
|
256
256
|
The `ControllerFactory.defineCrudController` method automatically sets up the following routes based on your entity schema:
|
|
257
257
|
|
|
258
|
-
| Name | Method | Path | Description |
|
|
258
|
+
| Route Name | Method | Path | Description |
|
|
259
259
|
| :--- | :--- | :--- | :--- |
|
|
260
260
|
| `count` | `GET` | `/count` | Get the number of records matching a filter. |
|
|
261
261
|
| `find` | `GET` | `/` | Retrieve all records matching a filter. |
|
|
262
262
|
| `findById` | `GET` | `/:id` | Retrieve a single record by its ID. |
|
|
263
263
|
| `findOne` | `GET` | `/find-one` | Retrieve a single record matching a filter. |
|
|
264
264
|
| `create` | `POST` | `/` | Create a new record. |
|
|
265
|
-
| `updateById` | `PATCH` | `/:id` | Update a record by its ID. |
|
|
266
|
-
| `
|
|
267
|
-
| `deleteById` | `DELETE` | `/:id` | Delete a record by its ID. |
|
|
268
|
-
| `
|
|
265
|
+
| `updateById` | `PATCH` | `/:id` | Update a single record by its ID. |
|
|
266
|
+
| `updateBy` | `PATCH` | `/` | Update multiple records matching a `where` filter. |
|
|
267
|
+
| `deleteById` | `DELETE` | `/:id` | Delete a single record by its ID. |
|
|
268
|
+
| `deleteBy` | `DELETE` | `/` | Delete multiple records matching a `where` filter. |
|
|
269
269
|
|
|
270
270
|
:::info Customization
|
|
271
271
|
The `ControllerFactory` is highly customizable. You can override the Zod schemas for any of the generated routes to add, remove, or modify fields for request validation and response shapes. You can also configure other behaviors, like making delete operations return the deleted records.
|
|
@@ -368,7 +368,7 @@ When you define Zod schemas in your route's `request` configuration (whether wit
|
|
|
368
368
|
|
|
369
369
|
```typescript
|
|
370
370
|
import { z } from '@hono/zod-openapi';
|
|
371
|
-
import { jsonContent, put } from '@venizia/ignis';
|
|
371
|
+
import { jsonContent, put, HTTP } from '@venizia/ignis';
|
|
372
372
|
|
|
373
373
|
// ... inside a controller class
|
|
374
374
|
|
|
@@ -397,7 +397,7 @@ updateUser(c: Context) {
|
|
|
397
397
|
console.log('Notification is enabled.');
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
return c.json({ success: true, id, ...userUpdateData });
|
|
400
|
+
return c.json({ success: true, id, ...userUpdateData }, HTTP.ResultCodes.RS_2.Ok);
|
|
401
401
|
}
|
|
402
402
|
```
|
|
403
403
|
|
|
@@ -405,7 +405,7 @@ Using `c.req.valid()` is the recommended way to access request data as it ensure
|
|
|
405
405
|
|
|
406
406
|
```typescript
|
|
407
407
|
import { z } from '@hono/zod-openapi';
|
|
408
|
-
import { jsonContent, put, TRouteContext } from '@venizia/ignis';
|
|
408
|
+
import { jsonContent, put, TRouteContext, HTTP } from '@venizia/ignis';
|
|
409
409
|
|
|
410
410
|
// ... inside a controller class
|
|
411
411
|
|
|
@@ -434,7 +434,7 @@ updateUser(c: TRouteContext<typeof updateUserConfig>) {
|
|
|
434
434
|
console.log('Notification is enabled.');
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
-
return c.json({ success: true, id, ...userUpdateData });
|
|
437
|
+
return c.json({ success: true, id, ...userUpdateData }, HTTP.ResultCodes.RS_2.Ok);
|
|
438
438
|
}
|
|
439
439
|
```
|
|
440
440
|
|
|
@@ -45,12 +45,10 @@ export type TUser = TTableObject<TUserSchema>;
|
|
|
45
45
|
|
|
46
46
|
// 4. Create the Entity class, decorated with @model
|
|
47
47
|
@model({ type: 'entity' })
|
|
48
|
-
export class User extends BaseEntity<
|
|
49
|
-
static
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
super({ name: User.name, schema: userTable });
|
|
53
|
-
}
|
|
48
|
+
export class User extends BaseEntity<typeof User.schema> {
|
|
49
|
+
static override schema = userTable;
|
|
50
|
+
static override relations = () => userRelations.definitions;
|
|
51
|
+
static override TABLE_NAME = 'User';
|
|
54
52
|
}
|
|
55
53
|
```
|
|
56
54
|
|
|
@@ -158,12 +156,10 @@ export type TPostSchema = typeof postTable;
|
|
|
158
156
|
export type TPost = TTableObject<TPostSchema>;
|
|
159
157
|
|
|
160
158
|
@model({ type: 'entity' })
|
|
161
|
-
export class Post extends BaseEntity<
|
|
162
|
-
static
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
super({ name: Post.TABLE_NAME, schema: postTable });
|
|
166
|
-
}
|
|
159
|
+
export class Post extends BaseEntity<typeof Post.schema> {
|
|
160
|
+
static override schema = postTable;
|
|
161
|
+
static override relations = () => postRelations.definitions;
|
|
162
|
+
static override TABLE_NAME = 'Post';
|
|
167
163
|
}
|
|
168
164
|
```
|
|
169
165
|
|
|
@@ -286,12 +282,10 @@ export type TConfigurationSchema = typeof configurationTable;
|
|
|
286
282
|
export type TConfiguration = TTableObject<TConfigurationSchema>;
|
|
287
283
|
|
|
288
284
|
@model({ type: 'entity' })
|
|
289
|
-
export class Configuration extends BaseEntity<
|
|
290
|
-
static
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
super({ name: Configuration.TABLE_NAME, schema: configurationTable });
|
|
294
|
-
}
|
|
285
|
+
export class Configuration extends BaseEntity<typeof Configuration.schema> {
|
|
286
|
+
static override schema = configurationTable;
|
|
287
|
+
static override relations = () => configurationRelations.definitions;
|
|
288
|
+
static override TABLE_NAME = 'Configuration';
|
|
295
289
|
}
|
|
296
290
|
```
|
|
297
291
|
**Key Concepts:**
|
|
@@ -309,34 +303,14 @@ A DataSource is a class responsible for managing the connection to your database
|
|
|
309
303
|
|
|
310
304
|
### Creating and Configuring a DataSource
|
|
311
305
|
|
|
312
|
-
A `DataSource` must be decorated with `@datasource`.
|
|
313
|
-
|
|
314
|
-
#### ⚠️ Understanding Schema Merging (CRITICAL CONCEPT)
|
|
315
|
-
|
|
316
|
-
This is one of the most important concepts in Ignis. If you don't get this right, your relations won't work.
|
|
317
|
-
|
|
318
|
-
**The Problem:**
|
|
319
|
-
Drizzle ORM needs to know about:
|
|
320
|
-
1. Your table structures (e.g., `userTable`, `configurationTable`)
|
|
321
|
-
2. The relationships between them (e.g., "Configuration belongs to User")
|
|
306
|
+
A `DataSource` must be decorated with `@datasource`. The framework now supports **schema auto-discovery**, which means you no longer need to manually merge tables and relations!
|
|
322
307
|
|
|
323
|
-
|
|
324
|
-
You must merge both into a single `schema` object in your DataSource constructor.
|
|
308
|
+
### Pattern 1: Auto-Discovery (Recommended)
|
|
325
309
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
Let's say you have two models: `User` and `Configuration`. Here's how to set up the DataSource:
|
|
310
|
+
With auto-discovery, the schema is automatically built from your `@repository` decorators:
|
|
329
311
|
|
|
330
312
|
```typescript
|
|
331
313
|
// src/datasources/postgres.datasource.ts
|
|
332
|
-
import {
|
|
333
|
-
Configuration,
|
|
334
|
-
configurationTable, // The table structure
|
|
335
|
-
configurationRelations, // The relationships
|
|
336
|
-
User,
|
|
337
|
-
userTable, // The table structure
|
|
338
|
-
userRelations, // The relationships
|
|
339
|
-
} from '@/models/entities';
|
|
340
314
|
import {
|
|
341
315
|
BaseDataSource,
|
|
342
316
|
datasource,
|
|
@@ -346,7 +320,6 @@ import {
|
|
|
346
320
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
347
321
|
import { Pool } from 'pg';
|
|
348
322
|
|
|
349
|
-
// Configuration interface for database connection
|
|
350
323
|
interface IDSConfigs {
|
|
351
324
|
connection: {
|
|
352
325
|
host?: string;
|
|
@@ -357,12 +330,11 @@ interface IDSConfigs {
|
|
|
357
330
|
};
|
|
358
331
|
}
|
|
359
332
|
|
|
360
|
-
@datasource()
|
|
333
|
+
@datasource({ driver: 'node-postgres' })
|
|
361
334
|
export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
|
|
362
335
|
constructor() {
|
|
363
336
|
super({
|
|
364
337
|
name: PostgresDataSource.name,
|
|
365
|
-
driver: 'node-postgres',
|
|
366
338
|
config: {
|
|
367
339
|
connection: {
|
|
368
340
|
host: process.env.APP_ENV_POSTGRES_HOST,
|
|
@@ -372,27 +344,14 @@ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, I
|
|
|
372
344
|
database: process.env.APP_ENV_POSTGRES_DATABASE,
|
|
373
345
|
},
|
|
374
346
|
},
|
|
375
|
-
|
|
376
|
-
// 🔥 CRITICAL: This is where you merge tables and relations
|
|
377
|
-
schema: Object.assign(
|
|
378
|
-
{},
|
|
379
|
-
// Step 1: Add your table schemas
|
|
380
|
-
{
|
|
381
|
-
[User.TABLE_NAME]: userTable,
|
|
382
|
-
[Configuration.TABLE_NAME]: configurationTable,
|
|
383
|
-
},
|
|
384
|
-
// Step 2: Spread the relations objects
|
|
385
|
-
userRelations.relations,
|
|
386
|
-
configurationRelations.relations,
|
|
387
|
-
),
|
|
347
|
+
// No schema needed - auto-discovered from @repository decorators!
|
|
388
348
|
});
|
|
389
349
|
}
|
|
390
350
|
|
|
391
351
|
override configure(): ValueOrPromise<void> {
|
|
392
|
-
// Pass the merged schema to Drizzle
|
|
393
352
|
this.connector = drizzle({
|
|
394
353
|
client: new Pool(this.settings.connection),
|
|
395
|
-
schema: this.schema, //
|
|
354
|
+
schema: this.schema, // Auto-discovered schema
|
|
396
355
|
});
|
|
397
356
|
}
|
|
398
357
|
|
|
@@ -407,34 +366,24 @@ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, I
|
|
|
407
366
|
}
|
|
408
367
|
```
|
|
409
368
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
**Without the relations merged in:**
|
|
413
|
-
```typescript
|
|
414
|
-
// ❌ WRONG - Relations won't work!
|
|
415
|
-
schema: {
|
|
416
|
-
[User.TABLE_NAME]: userTable,
|
|
417
|
-
[Configuration.TABLE_NAME]: configurationTable,
|
|
418
|
-
}
|
|
419
|
-
```
|
|
369
|
+
**How auto-discovery works:**
|
|
420
370
|
|
|
421
|
-
|
|
371
|
+
When you define repositories with both `model` and `dataSource`:
|
|
422
372
|
|
|
423
|
-
**With tables and relations merged:**
|
|
424
373
|
```typescript
|
|
425
|
-
|
|
426
|
-
schema
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
[Configuration.TABLE_NAME]: configurationTable,
|
|
431
|
-
},
|
|
432
|
-
userRelations.relations,
|
|
433
|
-
configurationRelations.relations,
|
|
434
|
-
)
|
|
374
|
+
@repository({ model: User, dataSource: PostgresDataSource })
|
|
375
|
+
export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {}
|
|
376
|
+
|
|
377
|
+
@repository({ model: Configuration, dataSource: PostgresDataSource })
|
|
378
|
+
export class ConfigurationRepository extends DefaultCRUDRepository<typeof Configuration.schema> {}
|
|
435
379
|
```
|
|
436
380
|
|
|
437
|
-
|
|
381
|
+
The framework automatically:
|
|
382
|
+
1. Registers each model-datasource binding
|
|
383
|
+
2. Builds the combined schema (tables + relations) when `getSchema()` is called
|
|
384
|
+
3. Makes all registered models available for relational queries
|
|
385
|
+
|
|
386
|
+
**Result:** You can use `include` queries without any manual schema configuration:
|
|
438
387
|
```typescript
|
|
439
388
|
const config = await configRepo.findOne({
|
|
440
389
|
filter: {
|
|
@@ -445,53 +394,55 @@ const config = await configRepo.findOne({
|
|
|
445
394
|
console.log(config.creator.name); // Access related User data
|
|
446
395
|
```
|
|
447
396
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
Every time you create a new model, you need to:
|
|
397
|
+
### Pattern 2: Manual Schema (Full Control)
|
|
451
398
|
|
|
452
|
-
|
|
453
|
-
2. Add them to the schema object
|
|
399
|
+
If you need explicit control over the schema, you can still provide it manually:
|
|
454
400
|
|
|
455
|
-
**Example - Adding a `Post` model:**
|
|
456
401
|
```typescript
|
|
457
402
|
import {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
postRelations,
|
|
461
|
-
// ... other models
|
|
403
|
+
Configuration, configurationTable, configurationRelations,
|
|
404
|
+
User, userTable, userRelations,
|
|
462
405
|
} from '@/models/entities';
|
|
463
406
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
{
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
407
|
+
@datasource({ driver: 'node-postgres' })
|
|
408
|
+
export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
|
|
409
|
+
constructor() {
|
|
410
|
+
super({
|
|
411
|
+
name: PostgresDataSource.name,
|
|
412
|
+
config: { /* ... */ },
|
|
413
|
+
// Manually merge tables and relations using spread syntax
|
|
414
|
+
schema: {
|
|
415
|
+
[User.TABLE_NAME]: userTable,
|
|
416
|
+
[Configuration.TABLE_NAME]: configurationTable,
|
|
417
|
+
...userRelations.relations,
|
|
418
|
+
...configurationRelations.relations,
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
override configure(): ValueOrPromise<void> {
|
|
424
|
+
this.connector = drizzle({
|
|
425
|
+
client: new Pool(this.settings.connection),
|
|
426
|
+
schema: this.schema,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
476
430
|
```
|
|
477
431
|
|
|
478
|
-
|
|
432
|
+
### @datasource Decorator
|
|
479
433
|
|
|
480
434
|
```typescript
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Usage:
|
|
488
|
-
schema: buildSchema([
|
|
489
|
-
{ name: User.TABLE_NAME, table: userTable, relations: userRelations },
|
|
490
|
-
{ name: Configuration.TABLE_NAME, table: configurationTable, relations: configurationRelations },
|
|
491
|
-
{ name: Post.TABLE_NAME, table: postTable, relations: postRelations },
|
|
492
|
-
])
|
|
435
|
+
@datasource({
|
|
436
|
+
driver: 'node-postgres', // Required - database driver
|
|
437
|
+
autoDiscovery?: true // Optional - defaults to true
|
|
438
|
+
})
|
|
493
439
|
```
|
|
494
440
|
|
|
441
|
+
| Option | Type | Default | Description |
|
|
442
|
+
|--------|------|---------|-------------|
|
|
443
|
+
| `driver` | `TDataSourceDriver` | - | Database driver name |
|
|
444
|
+
| `autoDiscovery` | `boolean` | `true` | Enable/disable schema auto-discovery |
|
|
445
|
+
|
|
495
446
|
### Registering a DataSource
|
|
496
447
|
|
|
497
448
|
Finally, register your `DataSource` with the application in `src/application.ts`.
|
|
@@ -520,29 +471,58 @@ Repositories abstract the data access logic. They use the configured `DataSource
|
|
|
520
471
|
|
|
521
472
|
### Creating a Repository
|
|
522
473
|
|
|
523
|
-
A repository extends `DefaultCRUDRepository` (for full read/write operations)
|
|
474
|
+
A repository extends `DefaultCRUDRepository` (for full read/write operations) and is decorated with `@repository`.
|
|
475
|
+
|
|
476
|
+
**IMPORTANT:** Both `model` AND `dataSource` are required in `@repository` for schema auto-discovery. Without both, the model won't be registered and relational queries will fail.
|
|
477
|
+
|
|
478
|
+
#### Pattern 1: Zero Boilerplate (Recommended)
|
|
479
|
+
|
|
480
|
+
The simplest approach - dataSource is auto-injected from metadata:
|
|
524
481
|
|
|
525
482
|
```typescript
|
|
526
483
|
// src/repositories/configuration.repository.ts
|
|
527
|
-
import {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
TConfigurationSchema,
|
|
531
|
-
} from '@/models/entities';
|
|
532
|
-
import { IDataSource, inject, repository, DefaultCRUDRepository } from '@venizia/ignis';
|
|
484
|
+
import { Configuration, TConfigurationSchema } from '@/models/entities';
|
|
485
|
+
import { PostgresDataSource } from '@/datasources';
|
|
486
|
+
import { repository, DefaultCRUDRepository } from '@venizia/ignis';
|
|
533
487
|
|
|
534
|
-
|
|
535
|
-
@repository({})
|
|
488
|
+
@repository({ model: Configuration, dataSource: PostgresDataSource })
|
|
536
489
|
export class ConfigurationRepository extends DefaultCRUDRepository<TConfigurationSchema> {
|
|
490
|
+
// No constructor needed - datasource auto-injected!
|
|
491
|
+
|
|
492
|
+
// Add custom methods as needed
|
|
493
|
+
async findByCode(code: string) {
|
|
494
|
+
return this.findOne({ filter: { where: { code } } });
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
#### Pattern 2: Explicit @inject
|
|
500
|
+
|
|
501
|
+
When you need constructor control (e.g., for read-only repositories or custom initialization):
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
// src/repositories/user.repository.ts
|
|
505
|
+
import { User } from '@/models/entities';
|
|
506
|
+
import { PostgresDataSource } from '@/datasources';
|
|
507
|
+
import { inject, repository, ReadableRepository } from '@venizia/ignis';
|
|
508
|
+
|
|
509
|
+
@repository({ model: User, dataSource: PostgresDataSource })
|
|
510
|
+
export class UserRepository extends ReadableRepository<typeof User.schema> {
|
|
537
511
|
constructor(
|
|
538
|
-
|
|
539
|
-
|
|
512
|
+
@inject({ key: 'datasources.PostgresDataSource' })
|
|
513
|
+
dataSource: PostgresDataSource, // Must be concrete DataSource type, NOT 'any'
|
|
540
514
|
) {
|
|
541
|
-
|
|
542
|
-
|
|
515
|
+
super(dataSource);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async findByRealm(realm: string) {
|
|
519
|
+
return this.findOne({ filter: { where: { realm } } });
|
|
543
520
|
}
|
|
544
521
|
}
|
|
545
522
|
```
|
|
523
|
+
|
|
524
|
+
**Note:** When `@inject` is at param index 0, auto-injection is skipped (your `@inject` takes precedence).
|
|
525
|
+
|
|
546
526
|
You would then register this repository in your `application.ts`: `this.repository(ConfigurationRepository);`
|
|
547
527
|
|
|
548
528
|
### Querying Data
|
|
@@ -265,7 +265,7 @@ export class HelloController extends BaseController {
|
|
|
265
265
|
})
|
|
266
266
|
sayHello(c: Context) {
|
|
267
267
|
// This looks just like Hono! Because it IS Hono under the hood.
|
|
268
|
-
return c.json({ message: 'Hello, World!' });
|
|
268
|
+
return c.json({ message: 'Hello, World!' }, HTTP.ResultCodes.RS_2.Ok);
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
// You can add more routes here:
|
|
@@ -65,7 +65,7 @@ export class MyFeatureController extends BaseController {
|
|
|
65
65
|
|
|
66
66
|
@api({ configs: MyRouteConfig })
|
|
67
67
|
getData(c: TRouteContext<typeof MyRouteConfig>) { // Return type is automatically inferred and validated
|
|
68
|
-
return c.json({ success: true });
|
|
68
|
+
return c.json({ success: true }, HTTP.ResultCodes.RS_2.Ok);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
```
|
|
@@ -83,7 +83,7 @@ For convenience, `Ignis` provides decorator shortcuts for each HTTP method: Thes
|
|
|
83
83
|
**Example using `@get` and `@post` with type inference:**
|
|
84
84
|
|
|
85
85
|
```typescript
|
|
86
|
-
import { get, post, z, jsonContent, jsonResponse, Authentication, TRouteContext } from '@venizia/ignis';
|
|
86
|
+
import { get, post, z, jsonContent, jsonResponse, Authentication, TRouteContext, HTTP } from '@venizia/ignis';
|
|
87
87
|
|
|
88
88
|
// Define route configs as const for full type inference
|
|
89
89
|
const USER_ROUTES = {
|
|
@@ -125,20 +125,20 @@ const USER_ROUTES = {
|
|
|
125
125
|
|
|
126
126
|
@get({ configs: USER_ROUTES.listUsers })
|
|
127
127
|
getAllUsers(c: TRouteContext<typeof USER_ROUTES.listUsers>) { // Return type is automatically inferred
|
|
128
|
-
return c.json([{ id: '1', name: 'John Doe' }]);
|
|
128
|
+
return c.json([{ id: '1', name: 'John Doe' }], HTTP.ResultCodes.RS_2.Ok);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
@get({ configs: USER_ROUTES.getUser })
|
|
132
132
|
getUserById(c: TRouteContext<typeof USER_ROUTES.getUser>) { // Return type is automatically inferred
|
|
133
133
|
const { id } = c.req.valid('param'); // id is typed as string
|
|
134
|
-
return c.json({ id, name: 'John Doe' });
|
|
134
|
+
return c.json({ id, name: 'John Doe' }, HTTP.ResultCodes.RS_2.Ok);
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
@post({ configs: USER_ROUTES.createUser })
|
|
138
138
|
createUser(c: TRouteContext<typeof USER_ROUTES.createUser>) { // Return type is automatically inferred
|
|
139
139
|
const { name } = c.req.valid('json'); // name is typed as string
|
|
140
140
|
const newUser = { id: '2', name };
|
|
141
|
-
return c.json(newUser,
|
|
141
|
+
return c.json(newUser, HTTP.ResultCodes.RS_2.Created); // Return type is validated
|
|
142
142
|
}
|
|
143
143
|
```
|
|
144
144
|
|
|
@@ -171,17 +171,21 @@ export class HealthCheckController extends BaseController {
|
|
|
171
171
|
@api({ configs: HEALTH_CHECK_ROUTES['/ping'] })
|
|
172
172
|
ping(c: TRouteContext<typeof HEALTH_CHECK_ROUTES['/ping']>) { // Return type is automatically inferred
|
|
173
173
|
const { message } = c.req.valid('json');
|
|
174
|
-
return c.json({ pong: message });
|
|
174
|
+
return c.json({ pong: message }, HTTP.ResultCodes.RS_2.Ok);
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
```
|
|
178
178
|
|
|
179
179
|
### Manual Route Definition Methods
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
For advanced use cases or when you prefer a non-decorator approach, you can define routes manually using `defineRoute` and `bindRoute` methods inside the `binding()` method.
|
|
182
182
|
|
|
183
|
-
:::tip
|
|
184
|
-
|
|
183
|
+
:::tip When to Use Manual Definition
|
|
184
|
+
Manual route definition is useful for:
|
|
185
|
+
- Dynamically generating routes based on configuration
|
|
186
|
+
- Conditional route registration (feature flags)
|
|
187
|
+
- Developers who prefer non-decorator syntax (coming from Express/Fastify)
|
|
188
|
+
- Complex routing logic that benefits from programmatic control
|
|
185
189
|
:::
|
|
186
190
|
|
|
187
191
|
#### `defineRoute`
|
|
@@ -291,17 +295,17 @@ This factory method returns a `BaseController` class that is already set up with
|
|
|
291
295
|
|
|
292
296
|
**Note:** The returned class is dynamically named using `controller.name` from the options. This ensures that when registered with `app.controller()`, the class has a proper name for binding keys and debugging (e.g., `ConfigurationController` instead of an anonymous class).
|
|
293
297
|
|
|
294
|
-
| Name | Method | Path | Description |
|
|
298
|
+
| Route Name | Method | Path | Description |
|
|
295
299
|
| :--- | :--- | :--- | :--- |
|
|
296
300
|
| `count` | `GET` | `/count` | Get the number of records matching a filter. |
|
|
297
301
|
| `find` | `GET` | `/` | Retrieve all records matching a filter. |
|
|
298
302
|
| `findById` | `GET` | `/:id` | Retrieve a single record by its ID. |
|
|
299
303
|
| `findOne` | `GET` | `/find-one` | Retrieve a single record matching a filter. |
|
|
300
304
|
| `create` | `POST` | `/` | Create a new record. |
|
|
301
|
-
| `updateById` | `PATCH` | `/:id` | Update a record by its ID. |
|
|
302
|
-
| `
|
|
303
|
-
| `deleteById` | `DELETE` | `/:id` | Delete a record by its ID. |
|
|
304
|
-
| `
|
|
305
|
+
| `updateById` | `PATCH` | `/:id` | Update a single record by its ID. |
|
|
306
|
+
| `updateBy` | `PATCH` | `/` | Update multiple records matching a `where` filter. |
|
|
307
|
+
| `deleteById` | `DELETE` | `/:id` | Delete a single record by its ID. |
|
|
308
|
+
| `deleteBy` | `DELETE` | `/` | Delete multiple records matching a `where` filter. |
|
|
305
309
|
|
|
306
310
|
### `ICrudControllerOptions<EntitySchema>`
|
|
307
311
|
|
|
@@ -311,10 +315,30 @@ This factory method returns a `BaseController` class that is already set up with
|
|
|
311
315
|
| `repository.name` | `string` | The binding key name of the repository associated with this entity (e.g., `'ConfigurationRepository'`). |
|
|
312
316
|
| `controller.name` | `string` | A unique name for the generated controller (e.g., `'ConfigurationController'`). |
|
|
313
317
|
| `controller.basePath`| `string` | The base path for all routes in this CRUD controller (e.g., `'/configurations'`). |
|
|
318
|
+
| `controller.readonly` | `boolean` | If `true`, only read operations (find, findOne, findById, count) are generated. Write operations are excluded. Defaults to `false`. |
|
|
314
319
|
| `controller.isStrict` | `boolean` | If `true`, query parameters like `where` will be strictly validated. Defaults to `true`. |
|
|
315
320
|
| `controller.defaultLimit`| `number` | The default limit for `find` operations. Defaults to `10`. |
|
|
316
|
-
| `schema` | `object` | An optional object to override the default Zod schemas for specific CRUD endpoints
|
|
317
|
-
| `doDeleteWithReturn` | `boolean` | If `true`, the `deleteById` and `
|
|
321
|
+
| `schema` | `object` | An optional object to override the default Zod schemas for specific CRUD endpoints. See schema options below. |
|
|
322
|
+
| `doDeleteWithReturn` | `boolean` | If `true`, the `deleteById` and `deleteBy` endpoints will return the deleted record(s) in the response body. Defaults to `false`. |
|
|
323
|
+
|
|
324
|
+
### Schema Override Options
|
|
325
|
+
|
|
326
|
+
The `schema` option allows fine-grained control over request/response validation and OpenAPI documentation:
|
|
327
|
+
|
|
328
|
+
| Schema Option | Description |
|
|
329
|
+
| :--- | :--- |
|
|
330
|
+
| `count` | Override response schema for count endpoint |
|
|
331
|
+
| `find` | Override response schema for find endpoint |
|
|
332
|
+
| `findOne` | Override response schema for findOne endpoint |
|
|
333
|
+
| `findById` | Override response schema for findById endpoint |
|
|
334
|
+
| `create` | Override response schema for create endpoint |
|
|
335
|
+
| `createRequestBody` | Override request body schema for create endpoint |
|
|
336
|
+
| `updateById` | Override response schema for updateById endpoint |
|
|
337
|
+
| `updateByIdRequestBody` | Override request body schema for updateById endpoint |
|
|
338
|
+
| `updateBy` | Override response schema for updateBy (bulk update) endpoint |
|
|
339
|
+
| `updateByRequestBody` | Override request body schema for updateBy endpoint |
|
|
340
|
+
| `deleteById` | Override response schema for deleteById endpoint |
|
|
341
|
+
| `deleteBy` | Override response schema for deleteBy (bulk delete) endpoint |
|
|
318
342
|
|
|
319
343
|
### Example
|
|
320
344
|
|