@venizia/ignis-docs 0.0.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.
Files changed (123) hide show
  1. package/mcp-server/dist/common/config.d.ts +27 -0
  2. package/mcp-server/dist/common/config.d.ts.map +1 -0
  3. package/mcp-server/dist/common/config.js +27 -0
  4. package/mcp-server/dist/common/config.js.map +1 -0
  5. package/mcp-server/dist/common/index.d.ts +3 -0
  6. package/mcp-server/dist/common/index.d.ts.map +1 -0
  7. package/mcp-server/dist/common/index.js +19 -0
  8. package/mcp-server/dist/common/index.js.map +1 -0
  9. package/mcp-server/dist/common/paths.d.ts +13 -0
  10. package/mcp-server/dist/common/paths.d.ts.map +1 -0
  11. package/mcp-server/dist/common/paths.js +23 -0
  12. package/mcp-server/dist/common/paths.js.map +1 -0
  13. package/mcp-server/dist/helpers/docs.helper.d.ts +81 -0
  14. package/mcp-server/dist/helpers/docs.helper.d.ts.map +1 -0
  15. package/mcp-server/dist/helpers/docs.helper.js +171 -0
  16. package/mcp-server/dist/helpers/docs.helper.js.map +1 -0
  17. package/mcp-server/dist/helpers/index.d.ts +3 -0
  18. package/mcp-server/dist/helpers/index.d.ts.map +1 -0
  19. package/mcp-server/dist/helpers/index.js +19 -0
  20. package/mcp-server/dist/helpers/index.js.map +1 -0
  21. package/mcp-server/dist/helpers/logger.helper.d.ts +7 -0
  22. package/mcp-server/dist/helpers/logger.helper.d.ts.map +1 -0
  23. package/mcp-server/dist/helpers/logger.helper.js +22 -0
  24. package/mcp-server/dist/helpers/logger.helper.js.map +1 -0
  25. package/mcp-server/dist/index.d.ts +3 -0
  26. package/mcp-server/dist/index.d.ts.map +1 -0
  27. package/mcp-server/dist/index.js +62 -0
  28. package/mcp-server/dist/index.js.map +1 -0
  29. package/mcp-server/dist/tools/base.tool.d.ts +98 -0
  30. package/mcp-server/dist/tools/base.tool.d.ts.map +1 -0
  31. package/mcp-server/dist/tools/base.tool.js +47 -0
  32. package/mcp-server/dist/tools/base.tool.js.map +1 -0
  33. package/mcp-server/dist/tools/get-doc-content.tool.d.ts +30 -0
  34. package/mcp-server/dist/tools/get-doc-content.tool.d.ts.map +1 -0
  35. package/mcp-server/dist/tools/get-doc-content.tool.js +127 -0
  36. package/mcp-server/dist/tools/get-doc-content.tool.js.map +1 -0
  37. package/mcp-server/dist/tools/get-doc-metadata.tool.d.ts +40 -0
  38. package/mcp-server/dist/tools/get-doc-metadata.tool.d.ts.map +1 -0
  39. package/mcp-server/dist/tools/get-doc-metadata.tool.js +121 -0
  40. package/mcp-server/dist/tools/get-doc-metadata.tool.js.map +1 -0
  41. package/mcp-server/dist/tools/index.d.ts +8 -0
  42. package/mcp-server/dist/tools/index.d.ts.map +1 -0
  43. package/mcp-server/dist/tools/index.js +18 -0
  44. package/mcp-server/dist/tools/index.js.map +1 -0
  45. package/mcp-server/dist/tools/list-categories.tool.d.ts +20 -0
  46. package/mcp-server/dist/tools/list-categories.tool.d.ts.map +1 -0
  47. package/mcp-server/dist/tools/list-categories.tool.js +105 -0
  48. package/mcp-server/dist/tools/list-categories.tool.js.map +1 -0
  49. package/mcp-server/dist/tools/list-docs.tool.d.ts +32 -0
  50. package/mcp-server/dist/tools/list-docs.tool.d.ts.map +1 -0
  51. package/mcp-server/dist/tools/list-docs.tool.js +121 -0
  52. package/mcp-server/dist/tools/list-docs.tool.js.map +1 -0
  53. package/mcp-server/dist/tools/search-docs.tool.d.ts +32 -0
  54. package/mcp-server/dist/tools/search-docs.tool.d.ts.map +1 -0
  55. package/mcp-server/dist/tools/search-docs.tool.js +120 -0
  56. package/mcp-server/dist/tools/search-docs.tool.js.map +1 -0
  57. package/package.json +102 -0
  58. package/wiki/get-started/5-minute-quickstart.md +266 -0
  59. package/wiki/get-started/best-practices/api-usage-examples.md +222 -0
  60. package/wiki/get-started/best-practices/architectural-patterns.md +129 -0
  61. package/wiki/get-started/best-practices/code-style-standards.md +122 -0
  62. package/wiki/get-started/best-practices/common-pitfalls.md +136 -0
  63. package/wiki/get-started/best-practices/contribution-workflow.md +145 -0
  64. package/wiki/get-started/best-practices/deployment-strategies.md +121 -0
  65. package/wiki/get-started/best-practices/performance-optimization.md +88 -0
  66. package/wiki/get-started/best-practices/security-guidelines.md +97 -0
  67. package/wiki/get-started/best-practices/troubleshooting-tips.md +100 -0
  68. package/wiki/get-started/building-a-crud-api.md +717 -0
  69. package/wiki/get-started/core-concepts/application.md +168 -0
  70. package/wiki/get-started/core-concepts/components.md +96 -0
  71. package/wiki/get-started/core-concepts/controllers.md +441 -0
  72. package/wiki/get-started/core-concepts/dependency-injection.md +160 -0
  73. package/wiki/get-started/core-concepts/persistent.md +591 -0
  74. package/wiki/get-started/core-concepts/services.md +88 -0
  75. package/wiki/get-started/index.md +65 -0
  76. package/wiki/get-started/mcp-docs-server.md +840 -0
  77. package/wiki/get-started/philosophy.md +123 -0
  78. package/wiki/get-started/prerequisites.md +113 -0
  79. package/wiki/get-started/quickstart.md +382 -0
  80. package/wiki/index.md +48 -0
  81. package/wiki/references/base/application.md +67 -0
  82. package/wiki/references/base/components.md +80 -0
  83. package/wiki/references/base/controllers.md +361 -0
  84. package/wiki/references/base/datasources.md +105 -0
  85. package/wiki/references/base/dependency-injection.md +83 -0
  86. package/wiki/references/base/models.md +104 -0
  87. package/wiki/references/base/repositories.md +118 -0
  88. package/wiki/references/base/services.md +97 -0
  89. package/wiki/references/components/authentication.md +224 -0
  90. package/wiki/references/components/health-check.md +190 -0
  91. package/wiki/references/components/index.md +61 -0
  92. package/wiki/references/components/request-tracker.md +35 -0
  93. package/wiki/references/components/socket-io.md +127 -0
  94. package/wiki/references/components/swagger.md +175 -0
  95. package/wiki/references/helpers/cron.md +94 -0
  96. package/wiki/references/helpers/crypto.md +117 -0
  97. package/wiki/references/helpers/env.md +67 -0
  98. package/wiki/references/helpers/error.md +80 -0
  99. package/wiki/references/helpers/index.md +21 -0
  100. package/wiki/references/helpers/inversion.md +141 -0
  101. package/wiki/references/helpers/logger.md +98 -0
  102. package/wiki/references/helpers/network.md +143 -0
  103. package/wiki/references/helpers/queue.md +131 -0
  104. package/wiki/references/helpers/redis.md +121 -0
  105. package/wiki/references/helpers/socket-io.md +103 -0
  106. package/wiki/references/helpers/storage.md +130 -0
  107. package/wiki/references/helpers/testing.md +115 -0
  108. package/wiki/references/helpers/worker-thread.md +162 -0
  109. package/wiki/references/src-details/core.md +249 -0
  110. package/wiki/references/src-details/dev-configs.md +302 -0
  111. package/wiki/references/src-details/docs.md +61 -0
  112. package/wiki/references/src-details/helpers.md +211 -0
  113. package/wiki/references/src-details/inversion.md +345 -0
  114. package/wiki/references/src-details/mcp-server.md +726 -0
  115. package/wiki/references/utilities/crypto.md +39 -0
  116. package/wiki/references/utilities/date.md +72 -0
  117. package/wiki/references/utilities/index.md +12 -0
  118. package/wiki/references/utilities/module.md +40 -0
  119. package/wiki/references/utilities/parse.md +68 -0
  120. package/wiki/references/utilities/performance.md +64 -0
  121. package/wiki/references/utilities/promise.md +83 -0
  122. package/wiki/references/utilities/request.md +66 -0
  123. package/wiki/references/utilities/schema.md +88 -0
@@ -0,0 +1,591 @@
1
+ # Persistent Layer: Models, DataSources, and Repositories
2
+
3
+ The persistent layer manages data using [Drizzle ORM](https://orm.drizzle.team/) for type-safe database access and the Repository pattern for data abstraction.
4
+
5
+ **Three main components:**
6
+ - **Models** - Define data structure (Drizzle schemas + Entity classes)
7
+ - **DataSources** - Manage database connections
8
+ - **Repositories** - Provide CRUD operations
9
+
10
+ ## 1. Models: Defining Your Data Structure
11
+
12
+ A model in `Ignis` consists of two parts: a **Drizzle schema** that defines the database table and an **Entity class** that wraps it for use within the framework.
13
+
14
+ ### Creating a Basic Model
15
+
16
+ Here's how to create a simple `User` model.
17
+
18
+ ```typescript
19
+ // src/models/entities/user.model.ts
20
+ import {
21
+ BaseEntity,
22
+ createRelations,
23
+ extraUserColumns,
24
+ generateIdColumnDefs,
25
+ model,
26
+ TTableObject,
27
+ } from '@venizia/ignis';
28
+ import { pgTable } from 'drizzle-orm/pg-core';
29
+
30
+ // 1. Define the Drizzle schema for the 'User' table
31
+ export const userTable = pgTable(User.name, {
32
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
33
+ ...extraUserColumns({ idType: 'string' }),
34
+ });
35
+
36
+ // 2. Define relations (empty for now, but required)
37
+ export const userRelations = createRelations({
38
+ source: userTable,
39
+ relations: [],
40
+ });
41
+
42
+ // 3. Define the TypeScript type for a User object
43
+ export type TUserSchema = typeof userTable;
44
+ export type TUser = TTableObject<TUserSchema>;
45
+
46
+ // 4. Create the Entity class, decorated with @model
47
+ @model({ type: 'entity' })
48
+ export class User extends BaseEntity<TUserSchema> {
49
+ static readonly TABLE_NAME = User.name;
50
+
51
+ constructor() {
52
+ super({ name: User.name, schema: userTable });
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### Understanding Enrichers: The Smart Column Generators
58
+
59
+ You might have noticed functions like `generateIdColumnDefs()` and `extraUserColumns()` in the model definition. These are **Enrichers**—powerful helper functions that generate common database columns automatically.
60
+
61
+ #### Why Enrichers Exist
62
+
63
+ **Without enrichers (the hard way):**
64
+ ```typescript
65
+ export const userTable = pgTable('User', {
66
+ // Manually define every common column in every table
67
+ id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
68
+ status: text('status').notNull().default('ACTIVE'),
69
+ type: text('type'),
70
+ createdBy: text('created_by'),
71
+ modifiedBy: text('modified_by'),
72
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
73
+ modifiedAt: timestamp('modified_at', { withTimezone: true }).notNull().defaultNow(),
74
+ // ... your actual user-specific fields
75
+ email: text('email').notNull(),
76
+ name: text('name'),
77
+ });
78
+ ```
79
+
80
+ **With enrichers (the smart way):**
81
+ ```typescript
82
+ export const userTable = pgTable('User', {
83
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }), // Adds: id (UUID)
84
+ ...extraUserColumns({ idType: 'string' }), // Adds: status, type, createdBy, modifiedBy, createdAt, modifiedAt
85
+ // Just your actual user-specific fields
86
+ email: text('email').notNull(),
87
+ name: text('name'),
88
+ });
89
+ ```
90
+
91
+ **Result:** Same table structure, but with:
92
+ - 7 fewer lines of code
93
+ - Guaranteed consistency across all tables
94
+ - Less chance of typos or mistakes
95
+ - Easier to maintain
96
+
97
+ #### Common Enrichers
98
+
99
+ | Enricher | What It Adds | Use Case |
100
+ | :--- | :--- | :--- |
101
+ | `generateIdColumnDefs()` | Primary key `id` column (UUID or number) | Every table needs an ID |
102
+ | `generateTzColumnDefs()` | `createdAt` and `modifiedAt` timestamps | Track when records are created/updated |
103
+ | `generateUserAuditColumnDefs()` | `createdBy` and `modifiedBy` foreign keys | Track which user created/updated records |
104
+ | `generateDataTypeColumnDefs()` | `dataType` and type-specific value columns (`tValue`, `nValue`, etc.) | Configuration tables with mixed data types |
105
+ | `extraUserColumns()` | Combination of audit + status + type fields | Full-featured entity tables |
106
+
107
+ #### Practical Example: Building a Post Model
108
+
109
+ Let's create a blog post model using enrichers:
110
+
111
+ ```typescript
112
+ // src/models/post.model.ts
113
+ import {
114
+ BaseEntity,
115
+ createRelations,
116
+ generateIdColumnDefs,
117
+ generateTzColumnDefs,
118
+ generateUserAuditColumnDefs,
119
+ model,
120
+ RelationTypes,
121
+ TTableObject,
122
+ } from '@venizia/ignis';
123
+ import { pgTable, text, boolean } from 'drizzle-orm/pg-core';
124
+ import { userTable } from './user.model';
125
+
126
+ export const postTable = pgTable('Post', {
127
+ // Use enrichers for common columns
128
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }), // id: UUID primary key
129
+ ...generateTzColumnDefs(), // createdAt, modifiedAt
130
+ ...generateUserAuditColumnDefs({ // createdBy, modifiedBy
131
+ created: { dataType: 'string', columnName: 'created_by' },
132
+ modified: { dataType: 'string', columnName: 'modified_by' },
133
+ }),
134
+
135
+ // Your post-specific fields
136
+ title: text('title').notNull(),
137
+ content: text('content').notNull(),
138
+ isPublished: boolean('is_published').default(false),
139
+ slug: text('slug').notNull().unique(),
140
+ });
141
+
142
+ export const postRelations = createRelations({
143
+ source: postTable,
144
+ relations: [
145
+ {
146
+ name: 'author',
147
+ type: RelationTypes.ONE,
148
+ schema: userTable,
149
+ metadata: {
150
+ fields: [postTable.createdBy],
151
+ references: [userTable.id],
152
+ },
153
+ },
154
+ ],
155
+ });
156
+
157
+ export type TPostSchema = typeof postTable;
158
+ export type TPost = TTableObject<TPostSchema>;
159
+
160
+ @model({ type: 'entity' })
161
+ export class Post extends BaseEntity<TPostSchema> {
162
+ static readonly TABLE_NAME = 'Post';
163
+
164
+ constructor() {
165
+ super({ name: Post.TABLE_NAME, schema: postTable });
166
+ }
167
+ }
168
+ ```
169
+
170
+ **What this gives you:**
171
+ ```typescript
172
+ interface Post {
173
+ id: string; // From generateIdColumnDefs
174
+ createdAt: Date; // From generateTzColumnDefs
175
+ modifiedAt: Date; // From generateTzColumnDefs
176
+ createdBy: string; // From generateUserAuditColumnDefs
177
+ modifiedBy: string; // From generateUserAuditColumnDefs
178
+ title: string; // Your field
179
+ content: string; // Your field
180
+ isPublished: boolean; // Your field
181
+ slug: string; // Your field
182
+ }
183
+ ```
184
+
185
+ #### When NOT to Use Enrichers
186
+
187
+ You can always define columns manually if:
188
+ - You need a custom ID strategy (e.g., integer auto-increment)
189
+ - You don't need audit fields for a specific table
190
+ - You have very specific timestamp requirements
191
+
192
+ ```typescript
193
+ // Mixing enrichers with manual columns is perfectly fine
194
+ export const simpleTable = pgTable('Simple', {
195
+ ...generateIdColumnDefs({ id: { dataType: 'number' } }), // Use enricher for ID
196
+ // But manually define everything else
197
+ name: text('name').notNull(),
198
+ value: integer('value'),
199
+ });
200
+ ```
201
+
202
+ :::tip
203
+ For a complete list of available enrichers and their options, see the [**Schema Enrichers Reference**](../../references/base/models.md#schema-enrichers).
204
+ :::
205
+
206
+ **Key Concepts:**
207
+ - **`pgTable`**: The standard function from Drizzle ORM to define a table schema.
208
+ - **Enrichers**: Ignis provides helper functions like `generateIdColumnDefs()` and `extraUserColumns()` that add common, pre-configured columns (like `id`, `status`, `type`, etc.) to your schema, reducing boilerplate.
209
+ - **`createRelations`**: A helper for defining relationships between models. Even if there are no relations, you must call it.
210
+ - **`BaseEntity`**: The class your model extends. It wraps the Drizzle schema and provides utilities for the framework.
211
+ - **`@model`**: A decorator that registers the class with the framework as a database model.
212
+
213
+ ### Creating a Model with Relations
214
+
215
+ Now, let's create a `Configuration` model that has a relationship with the `User` model.
216
+
217
+ ```typescript
218
+ // src/models/entities/configuration.model.ts
219
+ import {
220
+ BaseEntity,
221
+ createRelations, // Import createRelations
222
+ generateDataTypeColumnDefs,
223
+ generateIdColumnDefs,
224
+ generateTzColumnDefs,
225
+ generateUserAuditColumnDefs,
226
+ model,
227
+ RelationTypes,
228
+ TTableObject,
229
+ } from '@venizia/ignis';
230
+ import { foreignKey, index, pgTable, text, unique } from 'drizzle-orm/pg-core';
231
+ import { User, userTable } from './user.model';
232
+
233
+ // 1. Define the Drizzle schema for the 'Configuration' table
234
+ export const configurationTable = pgTable(
235
+ Configuration.name,
236
+ {
237
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
238
+ ...generateTzColumnDefs(),
239
+ ...generateDataTypeColumnDefs(),
240
+ ...generateUserAuditColumnDefs({
241
+ created: { dataType: 'string', columnName: 'created_by' },
242
+ modified: { dataType: 'string', columnName: 'modified_by' },
243
+ }),
244
+ code: text('code').notNull(),
245
+ description: text('description'),
246
+ group: text('group').notNull(),
247
+ },
248
+ (def) => [
249
+ unique(`UQ_${Configuration.name}_code`).on(def.code),
250
+ index(`IDX_${Configuration.name}_group`).on(def.group),
251
+ foreignKey({
252
+ columns: [def.createdBy],
253
+ foreignColumns: [userTable.id],
254
+ name: `FK_${Configuration.name}_createdBy_${User.name}_id`,
255
+ }),
256
+ ],
257
+ );
258
+
259
+ // 2. Define the relations using Ignis's `createRelations` helper
260
+ export const configurationRelations = createRelations({
261
+ source: configurationTable,
262
+ relations: [
263
+ {
264
+ name: 'creator',
265
+ type: RelationTypes.ONE,
266
+ schema: userTable,
267
+ metadata: {
268
+ fields: [configurationTable.createdBy],
269
+ references: [userTable.id],
270
+ },
271
+ },
272
+ {
273
+ name: 'modifier',
274
+ type: RelationTypes.ONE,
275
+ schema: userTable,
276
+ metadata: {
277
+ fields: [configurationTable.modifiedBy],
278
+ references: [userTable.id],
279
+ },
280
+ },
281
+ ],
282
+ });
283
+
284
+ // 3. Define types and the Entity class as before
285
+ export type TConfigurationSchema = typeof configurationTable;
286
+ export type TConfiguration = TTableObject<TConfigurationSchema>;
287
+
288
+ @model({ type: 'entity' })
289
+ export class Configuration extends BaseEntity<TConfigurationSchema> {
290
+ static readonly TABLE_NAME = Configuration.name;
291
+
292
+ constructor() {
293
+ super({ name: Configuration.TABLE_NAME, schema: configurationTable });
294
+ }
295
+ }
296
+ ```
297
+ **Key Concepts:**
298
+ - **`createRelations`**: This helper function from `Ignis` simplifies defining Drizzle ORM relations. It creates both a Drizzle `relations` object (for querying) and a `definitions` object (for repository configuration). Here, we define `creator` and `modifier` relations from `Configuration` to `User`. The names (`creator`, `modifier`) are important, as they will be used when querying.
299
+
300
+ > **Deep Dive:**
301
+ > - Explore the [**`BaseEntity`**](../../references/base/models.md#baseentity-class) class.
302
+ > - See all available [**Enrichers**](../../references/base/models.md#schema-enrichers) for schema generation.
303
+
304
+ ---
305
+
306
+ ## 2. DataSources: Connecting to Your Database
307
+
308
+ A DataSource is a class responsible for managing the connection to your database and making the Drizzle ORM instance available to your application.
309
+
310
+ ### Creating and Configuring a DataSource
311
+
312
+ A `DataSource` must be decorated with `@datasource`. **The most critical part** is correctly merging your table schemas and relations into a single object that Drizzle ORM can understand.
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")
322
+
323
+ **The Solution:**
324
+ You must merge both into a single `schema` object in your DataSource constructor.
325
+
326
+ #### Step-by-Step Example
327
+
328
+ Let's say you have two models: `User` and `Configuration`. Here's how to set up the DataSource:
329
+
330
+ ```typescript
331
+ // 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
+ import {
341
+ BaseDataSource,
342
+ datasource,
343
+ TNodePostgresConnector,
344
+ ValueOrPromise,
345
+ } from '@venizia/ignis';
346
+ import { drizzle } from 'drizzle-orm/node-postgres';
347
+ import { Pool } from 'pg';
348
+
349
+ // Configuration interface for database connection
350
+ interface IDSConfigs {
351
+ connection: {
352
+ host?: string;
353
+ port?: number;
354
+ user?: string;
355
+ password?: string;
356
+ database?: string;
357
+ };
358
+ }
359
+
360
+ @datasource()
361
+ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
362
+ constructor() {
363
+ super({
364
+ name: PostgresDataSource.name,
365
+ driver: 'node-postgres',
366
+ config: {
367
+ connection: {
368
+ host: process.env.APP_ENV_POSTGRES_HOST,
369
+ port: +(process.env.APP_ENV_POSTGRES_PORT ?? 5432),
370
+ user: process.env.APP_ENV_POSTGRES_USERNAME,
371
+ password: process.env.APP_ENV_POSTGRES_PASSWORD,
372
+ database: process.env.APP_ENV_POSTGRES_DATABASE,
373
+ },
374
+ },
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
+ ),
388
+ });
389
+ }
390
+
391
+ override configure(): ValueOrPromise<void> {
392
+ // Pass the merged schema to Drizzle
393
+ this.connector = drizzle({
394
+ client: new Pool(this.settings.connection),
395
+ schema: this.schema, // This now contains both tables AND relations
396
+ });
397
+ }
398
+
399
+ override async connect(): Promise<TNodePostgresConnector | undefined> {
400
+ await (this.connector.client as Pool).connect();
401
+ return this.connector;
402
+ }
403
+
404
+ override async disconnect(): Promise<void> {
405
+ await (this.connector.client as Pool).end();
406
+ }
407
+ }
408
+ ```
409
+
410
+ #### Why This Pattern?
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
+ ```
420
+
421
+ **Result:** Your repository's `include` queries will fail. You won't be able to fetch related data.
422
+
423
+ **With tables and relations merged:**
424
+ ```typescript
425
+ // ✅ CORRECT - Relations work perfectly!
426
+ schema: Object.assign(
427
+ {},
428
+ {
429
+ [User.TABLE_NAME]: userTable,
430
+ [Configuration.TABLE_NAME]: configurationTable,
431
+ },
432
+ userRelations.relations,
433
+ configurationRelations.relations,
434
+ )
435
+ ```
436
+
437
+ **Result:** You can now do:
438
+ ```typescript
439
+ const config = await configRepo.findOne({
440
+ filter: {
441
+ where: { id: '123' },
442
+ include: [{ relation: 'creator' }], // This works!
443
+ },
444
+ });
445
+ console.log(config.creator.name); // Access related User data
446
+ ```
447
+
448
+ #### Adding New Models to Your DataSource
449
+
450
+ Every time you create a new model, you need to:
451
+
452
+ 1. Import its table and relations
453
+ 2. Add them to the schema object
454
+
455
+ **Example - Adding a `Post` model:**
456
+ ```typescript
457
+ import {
458
+ Post,
459
+ postTable,
460
+ postRelations,
461
+ // ... other models
462
+ } from '@/models/entities';
463
+
464
+ // In your constructor:
465
+ schema: Object.assign(
466
+ {},
467
+ {
468
+ [User.TABLE_NAME]: userTable,
469
+ [Configuration.TABLE_NAME]: configurationTable,
470
+ [Post.TABLE_NAME]: postTable, // Add the table
471
+ },
472
+ userRelations.relations,
473
+ configurationRelations.relations,
474
+ postRelations.relations, // Add the relations
475
+ ),
476
+ ```
477
+
478
+ **Pro tip:** As your app grows, consider using a helper function to reduce boilerplate:
479
+
480
+ ```typescript
481
+ function buildSchema(models: Array<{ table: any; relations: any; name: string }>) {
482
+ const tables = models.reduce((acc, m) => ({ ...acc, [m.name]: m.table }), {});
483
+ const relations = models.map(m => m.relations.relations);
484
+ return Object.assign({}, tables, ...relations);
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
+ ])
493
+ ```
494
+
495
+ ### Registering a DataSource
496
+
497
+ Finally, register your `DataSource` with the application in `src/application.ts`.
498
+
499
+ ```typescript
500
+ // src/application.ts
501
+ import { PostgresDataSource } from './datasources';
502
+
503
+ export class Application extends BaseApplication {
504
+ // ...
505
+ preConfigure(): ValueOrPromise<void> {
506
+ this.dataSource(PostgresDataSource);
507
+ // ...
508
+ }
509
+ }
510
+ ```
511
+
512
+ > **Deep Dive:**
513
+ > - Explore the [**`BaseDataSource`**](../../references/base/datasources.md) class.
514
+
515
+ ---
516
+
517
+ ## 3. Repositories: The Data Access Layer
518
+
519
+ Repositories abstract the data access logic. They use the configured `DataSource` to perform type-safe queries against the database.
520
+
521
+ ### Creating a Repository
522
+
523
+ A repository extends `DefaultCRUDRepository` (for full read/write operations), is decorated with `@repository`, and injects the `DataSource`.
524
+
525
+ ```typescript
526
+ // src/repositories/configuration.repository.ts
527
+ import {
528
+ Configuration,
529
+ configurationRelations, // Import configurationRelations
530
+ TConfigurationSchema,
531
+ } from '@/models/entities';
532
+ import { IDataSource, inject, repository, DefaultCRUDRepository } from '@venizia/ignis';
533
+
534
+ // Decorator to mark this class as a repository for DI
535
+ @repository({})
536
+ export class ConfigurationRepository extends DefaultCRUDRepository<TConfigurationSchema> {
537
+ constructor(
538
+ // Inject the configured datasource
539
+ @inject({ key: 'datasources.PostgresDataSource' }) dataSource: IDataSource,
540
+ ) {
541
+ // Pass the datasource, the model's Entity class, AND the relations definitions to the super constructor
542
+ super({ dataSource, entityClass: Configuration, relations: configurationRelations.definitions });
543
+ }
544
+ }
545
+ ```
546
+ You would then register this repository in your `application.ts`: `this.repository(ConfigurationRepository);`
547
+
548
+ ### Querying Data
549
+
550
+ Repositories provide a full suite of type-safe methods for CRUD operations using a standardized `filter` object.
551
+
552
+ ```typescript
553
+ // Example usage in application.ts or a service
554
+ const repo = this.get<ConfigurationRepository>({ key: 'repositories.ConfigurationRepository' });
555
+
556
+ // Find multiple records
557
+ const someConfigs = await repo.find({
558
+ filter: {
559
+ where: { group: 'SYSTEM' },
560
+ limit: 10,
561
+ order: ['createdAt DESC'],
562
+ }
563
+ });
564
+
565
+ // Create a new record
566
+ const newConfig = await repo.create({
567
+ data: {
568
+ code: 'NEW_CODE',
569
+ group: 'SYSTEM',
570
+ // ... other fields
571
+ }
572
+ });
573
+ ```
574
+
575
+ ### Querying with Relations
576
+
577
+ To query related data, use the `include` property in the filter object. The `relation` name must match one of the names you defined in `createRelations` (e.g., `creator`).
578
+
579
+ ```typescript
580
+ const resultsWithCreator = await repo.find({
581
+ filter: {
582
+ where: { code: 'some_code' },
583
+ include: [{ relation: 'creator' }], // Fetch the related user
584
+ },
585
+ });
586
+
587
+ if (resultsWithCreator.length > 0) {
588
+ // `resultsWithCreator[0].creator` will contain the full User object
589
+ console.log('Configuration created by:', resultsWithCreator[0].creator.name);
590
+ }
591
+ ```
@@ -0,0 +1,88 @@
1
+ # Services
2
+
3
+ Services contain your application's business logic, orchestrating data flow and executing use cases.
4
+
5
+ > **Deep Dive:** See [Services Reference](../../references/base/services.md) for advanced patterns.
6
+
7
+ ## The Business Logic Layer
8
+
9
+ Services contain the core business logic of your application. They orchestrate the flow of data and execute the application's use cases. A service's primary responsibilities are:
10
+
11
+ - **Encapsulating Business Rules**: Centralizing logic such as calculations, data validation, and process workflows.
12
+ - **Coordinating Data Operations**: Using one or more repositories to fetch and persist data.
13
+ - **Isolating Controllers**: Keeping controllers thin by handling all the complex logic, so controllers are only responsible for handling the HTTP request and response.
14
+
15
+ ### Creating a Service
16
+
17
+ To create a service, you extend the `BaseService` class and inject the repositories or other services it depends on.
18
+
19
+ ```typescript
20
+ import { BaseService, inject } from '@venizia/ignis';
21
+ import { ConfigurationRepository } from '../repositories';
22
+ import { UserRepository } from '../repositories';
23
+ import { TConfiguration } from '../models/entities';
24
+
25
+ export class ConfigurationService extends BaseService {
26
+ constructor(
27
+ @inject({ key: 'repositories.ConfigurationRepository' })
28
+ private configurationRepository: ConfigurationRepository,
29
+ @inject({ key: 'repositories.UserRepository' })
30
+ private userRepository: UserRepository,
31
+ ) {
32
+ super({ scope: ConfigurationService.name });
33
+ }
34
+
35
+ async createConfigurationForUser(userId: string, data: Partial<TConfiguration>): Promise<TConfiguration> {
36
+ // Business logic: Check if the user exists
37
+ const user = await this.userRepository.findById({ id: userId });
38
+ if (!user) {
39
+ throw new Error(`User with ID ${userId} not found.`);
40
+ }
41
+
42
+ // Business logic: Check for duplicate configuration code
43
+ const existingConfig = await this.configurationRepository.findOne({ where: { code: data.code } });
44
+ if (existingConfig) {
45
+ throw new Error('Configuration with this code already exists.');
46
+ }
47
+
48
+ // Use the repository to create the configuration, assigning the creator
49
+ const newConfigData = { ...data, createdBy: userId, modifiedBy: userId };
50
+ return this.configurationRepository.create(newConfigData);
51
+ }
52
+ }
53
+ ```
54
+
55
+ ## How Services Fit into the Architecture
56
+
57
+ Services act as the intermediary between controllers and repositories, ensuring a clean separation of concerns.
58
+
59
+ ```mermaid
60
+ graph LR
61
+ A[Client Request] --> B(Controller);
62
+ B --> C{Service};
63
+ C --> D[Repository];
64
+ D --> E((Database));
65
+ E --> D;
66
+ D --> C;
67
+ C --> B;
68
+ B --> F[Client Response];
69
+
70
+ subgraph "Presentation Layer"
71
+ B
72
+ end
73
+ subgraph "Business Logic Layer"
74
+ C
75
+ end
76
+ subgraph "Data Access Layer"
77
+ D
78
+ end
79
+ subgraph "Data Store"
80
+ E
81
+ end
82
+ ```
83
+
84
+ This layered architecture makes your application:
85
+
86
+ - **More Organized:** Each layer has a clear and distinct responsibility.
87
+ - **Easier to Test:** You can test your business logic in isolation by providing mock repositories to your services, without needing a live database.
88
+ - **More Flexible:** You can change your database or data access implementation (e.g., switch from PostgreSQL to MySQL) by only changing the repository and datasource layer, with no changes to your business logic.