@venizia/ignis-docs 0.0.3 → 0.0.4-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 (131) hide show
  1. package/README.md +1 -1
  2. package/package.json +4 -2
  3. package/wiki/best-practices/api-usage-examples.md +591 -0
  4. package/wiki/best-practices/architectural-patterns.md +415 -0
  5. package/wiki/best-practices/architecture-decisions.md +488 -0
  6. package/wiki/{get-started/best-practices → best-practices}/code-style-standards.md +406 -17
  7. package/wiki/{get-started/best-practices → best-practices}/common-pitfalls.md +109 -4
  8. package/wiki/{get-started/best-practices → best-practices}/contribution-workflow.md +34 -7
  9. package/wiki/best-practices/data-modeling.md +376 -0
  10. package/wiki/best-practices/deployment-strategies.md +698 -0
  11. package/wiki/best-practices/index.md +27 -0
  12. package/wiki/best-practices/performance-optimization.md +196 -0
  13. package/wiki/best-practices/security-guidelines.md +218 -0
  14. package/wiki/{get-started/best-practices → best-practices}/troubleshooting-tips.md +97 -1
  15. package/wiki/changelogs/2025-12-16-initial-architecture.md +1 -1
  16. package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +1 -1
  17. package/wiki/changelogs/2025-12-17-refactor.md +1 -1
  18. package/wiki/changelogs/2025-12-18-performance-optimizations.md +5 -5
  19. package/wiki/changelogs/2025-12-18-repository-validation-security.md +13 -7
  20. package/wiki/changelogs/2025-12-26-nested-relations-and-generics.md +2 -2
  21. package/wiki/changelogs/2025-12-29-dynamic-binding-registration.md +104 -0
  22. package/wiki/changelogs/2025-12-29-snowflake-uid-helper.md +100 -0
  23. package/wiki/changelogs/2025-12-30-repository-enhancements.md +214 -0
  24. package/wiki/changelogs/2025-12-31-json-path-filtering-array-operators.md +214 -0
  25. package/wiki/changelogs/2025-12-31-string-id-custom-generator.md +137 -0
  26. package/wiki/changelogs/2026-01-02-default-filter-and-repository-mixins.md +418 -0
  27. package/wiki/changelogs/index.md +6 -0
  28. package/wiki/changelogs/planned-schema-migrator.md +0 -8
  29. package/wiki/{get-started/core-concepts → guides/core-concepts/application}/bootstrapping.md +18 -5
  30. package/wiki/{get-started/core-concepts/application.md → guides/core-concepts/application/index.md} +47 -104
  31. package/wiki/guides/core-concepts/components-guide.md +509 -0
  32. package/wiki/{get-started → guides}/core-concepts/components.md +24 -17
  33. package/wiki/{get-started → guides}/core-concepts/controllers.md +30 -13
  34. package/wiki/{get-started → guides}/core-concepts/dependency-injection.md +97 -0
  35. package/wiki/guides/core-concepts/persistent/datasources.md +179 -0
  36. package/wiki/guides/core-concepts/persistent/index.md +119 -0
  37. package/wiki/guides/core-concepts/persistent/models.md +241 -0
  38. package/wiki/guides/core-concepts/persistent/repositories.md +219 -0
  39. package/wiki/guides/core-concepts/persistent/transactions.md +170 -0
  40. package/wiki/{get-started → guides}/core-concepts/services.md +26 -3
  41. package/wiki/{get-started → guides/get-started}/5-minute-quickstart.md +59 -14
  42. package/wiki/guides/get-started/philosophy.md +682 -0
  43. package/wiki/guides/get-started/setup.md +157 -0
  44. package/wiki/guides/index.md +89 -0
  45. package/wiki/guides/reference/glossary.md +243 -0
  46. package/wiki/{get-started → guides/reference}/mcp-docs-server.md +0 -10
  47. package/wiki/{get-started → guides/tutorials}/building-a-crud-api.md +134 -132
  48. package/wiki/{get-started/quickstart.md → guides/tutorials/complete-installation.md} +107 -71
  49. package/wiki/guides/tutorials/ecommerce-api.md +1399 -0
  50. package/wiki/guides/tutorials/realtime-chat.md +1261 -0
  51. package/wiki/guides/tutorials/testing.md +723 -0
  52. package/wiki/index.md +176 -37
  53. package/wiki/references/base/application.md +27 -0
  54. package/wiki/references/base/bootstrapping.md +31 -26
  55. package/wiki/references/base/components.md +24 -7
  56. package/wiki/references/base/controllers.md +50 -20
  57. package/wiki/references/base/datasources.md +30 -0
  58. package/wiki/references/base/dependency-injection.md +39 -3
  59. package/wiki/references/base/filter-system/application-usage.md +224 -0
  60. package/wiki/references/base/filter-system/array-operators.md +132 -0
  61. package/wiki/references/base/filter-system/comparison-operators.md +109 -0
  62. package/wiki/references/base/filter-system/default-filter.md +428 -0
  63. package/wiki/references/base/filter-system/fields-order-pagination.md +155 -0
  64. package/wiki/references/base/filter-system/index.md +127 -0
  65. package/wiki/references/base/filter-system/json-filtering.md +197 -0
  66. package/wiki/references/base/filter-system/list-operators.md +71 -0
  67. package/wiki/references/base/filter-system/logical-operators.md +156 -0
  68. package/wiki/references/base/filter-system/null-operators.md +58 -0
  69. package/wiki/references/base/filter-system/pattern-matching.md +108 -0
  70. package/wiki/references/base/filter-system/quick-reference.md +431 -0
  71. package/wiki/references/base/filter-system/range-operators.md +63 -0
  72. package/wiki/references/base/filter-system/tips.md +190 -0
  73. package/wiki/references/base/filter-system/use-cases.md +452 -0
  74. package/wiki/references/base/index.md +90 -0
  75. package/wiki/references/base/middlewares.md +604 -0
  76. package/wiki/references/base/models.md +215 -23
  77. package/wiki/references/base/providers.md +731 -0
  78. package/wiki/references/base/repositories/advanced.md +555 -0
  79. package/wiki/references/base/repositories/index.md +228 -0
  80. package/wiki/references/base/repositories/mixins.md +331 -0
  81. package/wiki/references/base/repositories/relations.md +486 -0
  82. package/wiki/references/base/repositories.md +40 -635
  83. package/wiki/references/base/services.md +28 -4
  84. package/wiki/references/components/authentication.md +22 -2
  85. package/wiki/references/components/health-check.md +12 -0
  86. package/wiki/references/components/index.md +23 -0
  87. package/wiki/references/components/mail.md +687 -0
  88. package/wiki/references/components/request-tracker.md +16 -0
  89. package/wiki/references/components/socket-io.md +18 -0
  90. package/wiki/references/components/static-asset.md +14 -26
  91. package/wiki/references/components/swagger.md +17 -0
  92. package/wiki/references/configuration/environment-variables.md +427 -0
  93. package/wiki/references/configuration/index.md +73 -0
  94. package/wiki/references/helpers/cron.md +14 -0
  95. package/wiki/references/helpers/crypto.md +15 -0
  96. package/wiki/references/helpers/env.md +16 -0
  97. package/wiki/references/helpers/error.md +17 -0
  98. package/wiki/references/helpers/index.md +14 -0
  99. package/wiki/references/helpers/inversion.md +24 -4
  100. package/wiki/references/helpers/logger.md +19 -0
  101. package/wiki/references/helpers/network.md +11 -0
  102. package/wiki/references/helpers/queue.md +19 -0
  103. package/wiki/references/helpers/redis.md +21 -0
  104. package/wiki/references/helpers/socket-io.md +24 -5
  105. package/wiki/references/helpers/storage.md +18 -10
  106. package/wiki/references/helpers/testing.md +18 -0
  107. package/wiki/references/helpers/types.md +16 -0
  108. package/wiki/references/helpers/uid.md +167 -0
  109. package/wiki/references/helpers/worker-thread.md +16 -0
  110. package/wiki/references/index.md +177 -0
  111. package/wiki/references/quick-reference.md +634 -0
  112. package/wiki/references/src-details/boot.md +3 -3
  113. package/wiki/references/src-details/dev-configs.md +0 -4
  114. package/wiki/references/src-details/docs.md +2 -2
  115. package/wiki/references/src-details/index.md +86 -0
  116. package/wiki/references/src-details/inversion.md +1 -6
  117. package/wiki/references/src-details/mcp-server.md +3 -15
  118. package/wiki/references/utilities/index.md +86 -10
  119. package/wiki/references/utilities/jsx.md +577 -0
  120. package/wiki/references/utilities/request.md +0 -2
  121. package/wiki/references/utilities/statuses.md +740 -0
  122. package/wiki/get-started/best-practices/api-usage-examples.md +0 -266
  123. package/wiki/get-started/best-practices/architectural-patterns.md +0 -170
  124. package/wiki/get-started/best-practices/data-modeling.md +0 -177
  125. package/wiki/get-started/best-practices/deployment-strategies.md +0 -121
  126. package/wiki/get-started/best-practices/performance-optimization.md +0 -97
  127. package/wiki/get-started/best-practices/security-guidelines.md +0 -99
  128. package/wiki/get-started/core-concepts/persistent.md +0 -539
  129. package/wiki/get-started/index.md +0 -65
  130. package/wiki/get-started/philosophy.md +0 -296
  131. package/wiki/get-started/prerequisites.md +0 -113
@@ -0,0 +1,241 @@
1
+ # Models
2
+
3
+ Models define your data structure using Drizzle ORM schemas. A model is a single class with static properties for schema and relations.
4
+
5
+ ## Creating a Basic Model
6
+
7
+ ```typescript
8
+ // src/models/entities/user.model.ts
9
+ import { BaseEntity, extraUserColumns, generateIdColumnDefs, model } from '@venizia/ignis';
10
+ import { pgTable } from 'drizzle-orm/pg-core';
11
+
12
+ @model({ type: 'entity' })
13
+ export class User extends BaseEntity<typeof User.schema> {
14
+ // Define schema as static property
15
+ static override schema = pgTable('User', {
16
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
17
+ ...extraUserColumns({ idType: 'string' }),
18
+ });
19
+
20
+ // Relations (empty array if none)
21
+ static override relations = () => [];
22
+ }
23
+ ```
24
+
25
+ **Key points:**
26
+
27
+ - Schema is defined inline as `static override schema`
28
+ - Relations are defined as `static override relations`
29
+ - No constructor needed - BaseEntity auto-discovers from static properties
30
+ - Type parameter uses `typeof User.schema` (self-referencing)
31
+
32
+ ## Creating a Model with Relations
33
+
34
+ ```typescript
35
+ // src/models/entities/configuration.model.ts
36
+ import {
37
+ BaseEntity,
38
+ generateDataTypeColumnDefs,
39
+ generateIdColumnDefs,
40
+ generateTzColumnDefs,
41
+ generateUserAuditColumnDefs,
42
+ model,
43
+ RelationTypes,
44
+ TRelationConfig,
45
+ } from '@venizia/ignis';
46
+ import { foreignKey, index, pgTable, text, unique } from 'drizzle-orm/pg-core';
47
+ import { User } from './user.model';
48
+
49
+ @model({ type: 'entity' })
50
+ export class Configuration extends BaseEntity<typeof Configuration.schema> {
51
+ static override schema = pgTable(
52
+ 'Configuration',
53
+ {
54
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
55
+ ...generateTzColumnDefs(),
56
+ ...generateDataTypeColumnDefs(),
57
+ ...generateUserAuditColumnDefs({
58
+ created: { dataType: 'string', columnName: 'created_by' },
59
+ modified: { dataType: 'string', columnName: 'modified_by' },
60
+ }),
61
+ code: text('code').notNull(),
62
+ description: text('description'),
63
+ group: text('group').notNull(),
64
+ },
65
+ def => [
66
+ unique('UQ_Configuration_code').on(def.code),
67
+ index('IDX_Configuration_group').on(def.group),
68
+ foreignKey({
69
+ columns: [def.createdBy],
70
+ foreignColumns: [User.schema.id], // Reference User.schema, not a separate variable
71
+ name: 'FK_Configuration_createdBy_User_id',
72
+ }),
73
+ ],
74
+ );
75
+
76
+ // Define relations using TRelationConfig array
77
+ static override relations = (): TRelationConfig[] => [
78
+ {
79
+ name: 'creator',
80
+ type: RelationTypes.ONE,
81
+ schema: User.schema,
82
+ metadata: {
83
+ fields: [Configuration.schema.createdBy],
84
+ references: [User.schema.id],
85
+ },
86
+ },
87
+ {
88
+ name: 'modifier',
89
+ type: RelationTypes.ONE,
90
+ schema: User.schema,
91
+ metadata: {
92
+ fields: [Configuration.schema.modifiedBy],
93
+ references: [User.schema.id],
94
+ },
95
+ },
96
+ ];
97
+ }
98
+ ```
99
+
100
+ **Key points:**
101
+
102
+ - Relations use `TRelationConfig[]` format directly
103
+ - Reference other models via `Model.schema` (e.g., `User.schema.id`)
104
+ - Relation names (`creator`, `modifier`) are used in queries with `include`
105
+
106
+ ## Understanding Enrichers
107
+
108
+ Enrichers are helper functions that generate common database columns automatically.
109
+
110
+ **Without enrichers:**
111
+
112
+ ```typescript
113
+ static override schema = pgTable('User', {
114
+ id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
115
+ status: text('status').notNull().default('ACTIVE'),
116
+ createdBy: text('created_by'),
117
+ modifiedBy: text('modified_by'),
118
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
119
+ modifiedAt: timestamp('modified_at', { withTimezone: true }).notNull().defaultNow(),
120
+ // ... your fields
121
+ });
122
+ ```
123
+
124
+ **With enrichers:**
125
+
126
+ ```typescript
127
+ static override schema = pgTable('User', {
128
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }), // id (text with UUID default)
129
+ ...extraUserColumns({ idType: 'string' }), // status, audit fields, timestamps
130
+ // ... your fields
131
+ });
132
+ ```
133
+
134
+ ### Available Enrichers
135
+
136
+ | Enricher | Columns Added | Use Case |
137
+ |----------|---------------|----------|
138
+ | `generateIdColumnDefs()` | `id` (text or number) | Every table |
139
+ | `generateTzColumnDefs()` | `createdAt`, `modifiedAt` | Track timestamps |
140
+ | `generateUserAuditColumnDefs()` | `createdBy`, `modifiedBy` | Track who created/updated |
141
+ | `generateDataTypeColumnDefs()` | `dataType`, `tValue`, `nValue`, etc. | Configuration tables |
142
+ | `extraUserColumns()` | Combines audit + status + type | Full-featured entities |
143
+
144
+ :::tip
145
+ For a complete list of enrichers and options, see the [Schema Enrichers Reference](../../../references/base/models.md#schema-enrichers).
146
+ :::
147
+
148
+ ## Hidden Properties
149
+
150
+ Protect sensitive data by configuring properties that are **never returned** through repository queries. Hidden properties are excluded at the SQL level for maximum security and performance.
151
+
152
+ ```typescript
153
+ import { BaseEntity, generateIdColumnDefs, model } from '@venizia/ignis';
154
+ import { pgTable, text } from 'drizzle-orm/pg-core';
155
+
156
+ @model({
157
+ type: 'entity',
158
+ settings: {
159
+ hiddenProperties: ['password', 'secret'], // Never returned via repository
160
+ },
161
+ })
162
+ export class User extends BaseEntity<typeof User.schema> {
163
+ static override schema = pgTable('User', {
164
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
165
+ email: text('email').notNull(),
166
+ password: text('password'), // Hidden from queries
167
+ secret: text('secret'), // Hidden from queries
168
+ });
169
+ }
170
+ ```
171
+
172
+ **Behavior:**
173
+
174
+ | Operation | Behavior |
175
+ |-----------|----------|
176
+ | `find()`, `findOne()`, `findById()` | Hidden excluded from SELECT |
177
+ | `create()`, `updateById()`, `deleteById()` | Hidden excluded from RETURNING |
178
+ | Where clause filtering | Hidden fields **can** be used in filters |
179
+ | Direct connector query | Hidden fields **included** (bypasses repository) |
180
+
181
+ When you need to access hidden data, use the connector directly:
182
+
183
+ ```typescript
184
+ // Repository query - excludes hidden
185
+ const user = await userRepo.findById({ id: '123' });
186
+ // { id: '123', email: 'john@example.com' }
187
+
188
+ // Connector query - includes all fields
189
+ const connector = userRepo.getConnector();
190
+ const [fullUser] = await connector
191
+ .select()
192
+ .from(User.schema)
193
+ .where(eq(User.schema.id, '123'));
194
+ // { id: '123', email: 'john@example.com', password: '...', secret: '...' }
195
+ ```
196
+
197
+ :::tip
198
+ For complete hidden properties documentation, see the [Models Reference](../../../references/base/models.md#hidden-properties).
199
+ :::
200
+
201
+ ## Model Template
202
+
203
+ ```typescript
204
+ import { BaseEntity, generateIdColumnDefs, model, TRelationConfig } from '@venizia/ignis';
205
+ import { pgTable, text } from 'drizzle-orm/pg-core';
206
+
207
+ @model({ type: 'entity' })
208
+ export class MyModel extends BaseEntity<typeof MyModel.schema> {
209
+ static override schema = pgTable('MyModel', {
210
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
211
+ name: text('name').notNull(),
212
+ });
213
+
214
+ static override relations = (): TRelationConfig[] => [];
215
+ }
216
+ ```
217
+
218
+ > **Deep Dive:** See [BaseEntity Reference](../../../references/base/models.md#baseentity-class) for advanced patterns.
219
+
220
+ ## See Also
221
+
222
+ - **Related Concepts:**
223
+ - [Repositories](/guides/core-concepts/persistent/repositories) - Data access layer using models
224
+ - [DataSources](/guides/core-concepts/persistent/datasources) - Database connections
225
+ - [Persistent Layer Overview](/guides/core-concepts/persistent/) - Architecture overview
226
+
227
+ - **References:**
228
+ - [Models & Enrichers API](/references/base/models) - Complete API reference
229
+ - [Relations](/references/base/repositories/relations) - Defining model relationships
230
+ - [Filter System](/references/base/filter-system/) - Querying models
231
+
232
+ - **External Resources:**
233
+ - [Drizzle ORM Documentation](https://orm.drizzle.team/) - Schema definition guide
234
+ - [PostgreSQL Data Types](https://www.postgresql.org/docs/current/datatype.html) - Column types reference
235
+
236
+ - **Best Practices:**
237
+ - [Data Modeling](/best-practices/data-modeling) - Schema design patterns
238
+
239
+ - **Tutorials:**
240
+ - [Building a CRUD API](/guides/tutorials/building-a-crud-api) - Model examples
241
+ - [E-commerce API](/guides/tutorials/ecommerce-api) - Models with relations
@@ -0,0 +1,219 @@
1
+ # Repositories
2
+
3
+ Repositories provide type-safe CRUD operations. Use `@repository` decorator with both `model` and `dataSource` for auto-discovery.
4
+
5
+ ## Pattern 1: Zero Boilerplate (Recommended)
6
+
7
+ The simplest approach - everything is auto-resolved:
8
+
9
+ ```typescript
10
+ // src/repositories/configuration.repository.ts
11
+ import { Configuration } from '@/models/entities';
12
+ import { PostgresDataSource } from '@/datasources/postgres.datasource';
13
+ import { DefaultCRUDRepository, repository } from '@venizia/ignis';
14
+
15
+ @repository({
16
+ model: Configuration,
17
+ dataSource: PostgresDataSource,
18
+ })
19
+ export class ConfigurationRepository extends DefaultCRUDRepository<typeof Configuration.schema> {
20
+ // No constructor needed!
21
+
22
+ async findByCode(opts: { code: string }) {
23
+ return this.findOne({ filter: { where: { code: opts.code } } });
24
+ }
25
+
26
+ async findByGroup(opts: { group: string }) {
27
+ return this.find({ filter: { where: { group: opts.group } } });
28
+ }
29
+ }
30
+ ```
31
+
32
+ ## Pattern 2: Explicit @inject
33
+
34
+ When you need constructor control (e.g., read-only repository or additional dependencies):
35
+
36
+ ```typescript
37
+ // src/repositories/user.repository.ts
38
+ import { User } from '@/models/entities';
39
+ import { PostgresDataSource } from '@/datasources/postgres.datasource';
40
+ import { inject, ReadableRepository, repository } from '@venizia/ignis';
41
+ import { CacheService } from '@/services/cache.service';
42
+
43
+ @repository({ model: User, dataSource: PostgresDataSource })
44
+ export class UserRepository extends ReadableRepository<typeof User.schema> {
45
+ constructor(
46
+ // First parameter MUST be DataSource injection
47
+ @inject({ key: 'datasources.PostgresDataSource' })
48
+ dataSource: PostgresDataSource, // Must be concrete type, not 'any'
49
+
50
+ // After first arg, you can inject any additional dependencies
51
+ @inject({ key: 'some.cache' })
52
+ private cache: SomeCache,
53
+ ) {
54
+ super(dataSource);
55
+ }
56
+
57
+ async findByRealm(opts: { realm: string }) {
58
+ // Use injected dependencies
59
+ const cached = await this.cache.get(`user:realm:${opts.realm}`);
60
+ if (cached) {
61
+ return cached;
62
+ }
63
+
64
+ return this.findOne({ filter: { where: { realm: opts.realm } } });
65
+ }
66
+ }
67
+ ```
68
+
69
+ > **Important:**
70
+ > - First constructor parameter **MUST** be the DataSource injection
71
+ > - After the first argument, you can inject any additional dependencies you need
72
+ > - When `@inject` is at param index 0, auto-injection is skipped
73
+
74
+ ## Repository Types
75
+
76
+ | Type | Description |
77
+ |------|-------------|
78
+ | `DefaultCRUDRepository` | Full read/write operations |
79
+ | `ReadableRepository` | Read-only operations |
80
+ | `PersistableRepository` | Write operations only |
81
+
82
+ ## Querying Data
83
+
84
+ For advanced filtering with operators like `gt`, `lt`, `like`, `in`, `between`, and more, see [Filter System](../../../references/base/filter-system/).
85
+
86
+ ```typescript
87
+ const repo = this.get<ConfigurationRepository>({ key: 'repositories.ConfigurationRepository' });
88
+
89
+ // Find multiple records
90
+ const configs = await repo.find({
91
+ filter: {
92
+ where: { group: 'SYSTEM' },
93
+ limit: 10,
94
+ order: ['createdAt DESC'],
95
+ }
96
+ });
97
+
98
+ // Find one record
99
+ const config = await repo.findOne({
100
+ filter: { where: { code: 'APP_NAME' } }
101
+ });
102
+
103
+ // Select specific fields (array format)
104
+ const configCodes = await repo.find({
105
+ filter: {
106
+ fields: ['id', 'code', 'group'], // Only these fields returned
107
+ limit: 100,
108
+ }
109
+ });
110
+
111
+ // Order by JSON/JSONB nested fields
112
+ const sorted = await repo.find({
113
+ filter: {
114
+ order: ['metadata.priority DESC', 'createdAt ASC'],
115
+ }
116
+ });
117
+
118
+ // Create a record
119
+ const newConfig = await repo.create({
120
+ data: {
121
+ code: 'NEW_SETTING',
122
+ group: 'SYSTEM',
123
+ description: 'A new setting',
124
+ }
125
+ });
126
+
127
+ // Update by ID
128
+ await repo.updateById({
129
+ id: 'uuid-here',
130
+ data: { description: 'Updated description' }
131
+ });
132
+
133
+ // Delete by ID
134
+ await repo.deleteById({ id: 'uuid-here' });
135
+ ```
136
+
137
+ ## Querying with Relations
138
+
139
+ Use `include` to fetch related data. The relation name must match what you defined in `static relations`:
140
+
141
+ ```typescript
142
+ const configWithCreator = await repo.findOne({
143
+ filter: {
144
+ where: { code: 'APP_NAME' },
145
+ include: [{ relation: 'creator' }],
146
+ },
147
+ });
148
+
149
+ console.log('Created by:', configWithCreator.creator.name);
150
+ ```
151
+
152
+ ## Registering Repositories
153
+
154
+ ```typescript
155
+ // src/application.ts
156
+ export class Application extends BaseApplication {
157
+ preConfigure(): ValueOrPromise<void> {
158
+ this.dataSource(PostgresDataSource);
159
+ this.repository(UserRepository);
160
+ this.repository(ConfigurationRepository);
161
+ }
162
+ }
163
+ ```
164
+
165
+ ## Repository Template
166
+
167
+ ```typescript
168
+ import { DefaultCRUDRepository, repository } from '@venizia/ignis';
169
+ import { MyModel } from '@/models/entities';
170
+ import { PostgresDataSource } from '@/datasources/postgres.datasource';
171
+
172
+ @repository({ model: MyModel, dataSource: PostgresDataSource })
173
+ export class MyModelRepository extends DefaultCRUDRepository<typeof MyModel.schema> {}
174
+ ```
175
+
176
+ ## Advanced Topics
177
+
178
+ ### Performance: Core API Optimization
179
+
180
+ Ignis automatically optimizes "flat" queries (no relations, no field selection) by using Drizzle's Core API. This provides **~15-20% faster** queries for simple reads.
181
+
182
+ ### Modular Persistence with Components
183
+
184
+ Bundle related persistence resources into Components for better organization:
185
+
186
+ ```typescript
187
+ export class UserManagementComponent extends BaseComponent {
188
+ override binding() {
189
+ this.application.dataSource(PostgresDataSource);
190
+ this.application.repository(UserRepository);
191
+ this.application.repository(ProfileRepository);
192
+ }
193
+ }
194
+ ```
195
+
196
+ > **Deep Dive:** See [Repository Reference](../../../references/base/repositories/) for filtering operators, relations, JSON path queries, and array operators.
197
+
198
+ ## See Also
199
+
200
+ - **Related Concepts:**
201
+ - [Models](/guides/core-concepts/persistent/models) - Entity definitions used by repositories
202
+ - [DataSources](/guides/core-concepts/persistent/datasources) - Database connections
203
+ - [Services](/guides/core-concepts/services) - Use repositories for data access
204
+ - [Transactions](/guides/core-concepts/persistent/transactions) - Multi-operation consistency
205
+
206
+ - **References:**
207
+ - [Repositories API](/references/base/repositories/) - Complete API reference
208
+ - [Filter System](/references/base/filter-system/) - Query operators and filtering
209
+ - [Relations & Includes](/references/base/repositories/relations) - Loading related data
210
+ - [Advanced Features](/references/base/repositories/advanced) - JSON queries, performance tuning
211
+ - [Repository Mixins](/references/base/repositories/mixins) - Soft delete and auditing
212
+
213
+ - **Best Practices:**
214
+ - [Data Modeling](/best-practices/data-modeling) - Repository design patterns
215
+ - [Performance Optimization](/best-practices/performance-optimization) - Query optimization
216
+
217
+ - **Tutorials:**
218
+ - [Building a CRUD API](/guides/tutorials/building-a-crud-api) - Repository examples
219
+ - [E-commerce API](/guides/tutorials/ecommerce-api) - Advanced queries and relations
@@ -0,0 +1,170 @@
1
+ # Transactions
2
+
3
+ Ignis supports explicit transaction objects that can be passed across multiple services and repositories, allowing for complex, multi-step business logic to be atomic.
4
+
5
+ ## Using Transactions
6
+
7
+ To use transactions, start one from a repository or datasource, and then pass it to subsequent operations via the `options` parameter.
8
+
9
+ ```typescript
10
+ // 1. Start a transaction
11
+ const tx = await userRepo.beginTransaction({
12
+ isolationLevel: 'SERIALIZABLE' // Optional, defaults to 'READ COMMITTED'
13
+ });
14
+
15
+ try {
16
+ // 2. Pass transaction to operations
17
+ // Create user
18
+ const user = await userRepo.create({
19
+ data: userData,
20
+ options: { transaction: tx }
21
+ });
22
+
23
+ // Create profile (using same transaction)
24
+ await profileRepo.create({
25
+ data: { userId: user.id, ...profileData },
26
+ options: { transaction: tx }
27
+ });
28
+
29
+ // Call a service method (passing the transaction)
30
+ await orderService.createInitialOrder({ userId: user.id, transaction: tx });
31
+
32
+ // 3. Commit the transaction
33
+ await tx.commit();
34
+ } catch (err) {
35
+ // 4. Rollback on error
36
+ await tx.rollback();
37
+ throw err;
38
+ }
39
+ ```
40
+
41
+ ## Isolation Levels
42
+
43
+ Ignis supports standard PostgreSQL isolation levels:
44
+
45
+ | Level | Description | Use Case |
46
+ |-------|-------------|----------|
47
+ | `READ COMMITTED` | (Default) Queries see only data committed before the query began. | General use, prevents dirty reads. |
48
+ | `REPEATABLE READ` | Queries see a snapshot as of the start of the transaction. | Reports, consistent reads across multiple queries. |
49
+ | `SERIALIZABLE` | Strictest level. Emulates serial execution. | Financial transactions, critical data integrity. |
50
+
51
+ ## Best Practices
52
+
53
+ 1. **Always use `try...catch...finally`**: Ensure `rollback()` is called on error to release the connection.
54
+ 2. **Keep it short**: Long-running transactions hold database locks and connections.
55
+ 3. **Pass explicit options**: When calling other services inside a transaction, ensure they accept and use the `transaction` option.
56
+
57
+ ```typescript
58
+ // Service method supporting transactions
59
+ async createInitialOrder(opts: { userId: string; transaction?: ITransaction }) {
60
+ return this.orderRepository.create({
61
+ data: { userId: opts.userId, status: 'PENDING' },
62
+ options: { transaction: opts.transaction } // Forward the transaction
63
+ });
64
+ }
65
+ ```
66
+
67
+ ## Transaction Pattern with Services
68
+
69
+ When building services that support transactions, follow this pattern:
70
+
71
+ ```typescript
72
+ export class OrderService extends BaseService {
73
+ constructor(
74
+ @inject({ key: 'repositories.OrderRepository' })
75
+ private _orderRepository: OrderRepository,
76
+ @inject({ key: 'repositories.OrderItemRepository' })
77
+ private _orderItemRepository: OrderItemRepository,
78
+ ) {
79
+ super({ scope: OrderService.name });
80
+ }
81
+
82
+ async createOrderWithItems(opts: {
83
+ orderData: TOrderCreate;
84
+ items: TOrderItemCreate[];
85
+ transaction?: ITransaction;
86
+ }) {
87
+ const { orderData, items, transaction } = opts;
88
+
89
+ // Create order
90
+ const order = await this._orderRepository.create({
91
+ data: orderData,
92
+ options: { transaction },
93
+ });
94
+
95
+ // Create order items
96
+ for (const item of items) {
97
+ await this._orderItemRepository.create({
98
+ data: { ...item, orderId: order.id },
99
+ options: { transaction },
100
+ });
101
+ }
102
+
103
+ return order;
104
+ }
105
+ }
106
+ ```
107
+
108
+ ## Using from Controllers
109
+
110
+ ```typescript
111
+ @controller({ path: '/orders' })
112
+ export class OrderController extends BaseController {
113
+ constructor(
114
+ @inject({ key: 'repositories.OrderRepository' })
115
+ private _orderRepository: OrderRepository,
116
+ @inject({ key: 'services.OrderService' })
117
+ private _orderService: OrderService,
118
+ ) {
119
+ super({ scope: OrderController.name, path: '/orders' });
120
+ }
121
+
122
+ @post({ configs: OrderRoutes.CREATE })
123
+ async createOrder(c: TRouteContext<typeof OrderRoutes.CREATE>) {
124
+ const body = c.req.valid('json');
125
+
126
+ const tx = await this._orderRepository.beginTransaction({
127
+ isolationLevel: 'SERIALIZABLE',
128
+ });
129
+
130
+ try {
131
+ const order = await this._orderService.createOrderWithItems({
132
+ orderData: body.order,
133
+ items: body.items,
134
+ transaction: tx,
135
+ });
136
+
137
+ await tx.commit();
138
+ return c.json(order, HTTP.ResultCodes.RS_2.Created);
139
+ } catch (err) {
140
+ await tx.rollback();
141
+ throw err;
142
+ }
143
+ }
144
+ }
145
+ ```
146
+
147
+ > **Deep Dive:** See [Repository Reference](../../../references/base/repositories/) for more transaction options and patterns.
148
+
149
+ ## See Also
150
+
151
+ - **Related Concepts:**
152
+ - [Repositories](/guides/core-concepts/persistent/repositories) - Provide transaction API
153
+ - [Services](/guides/core-concepts/services) - Orchestrate transactional operations
154
+ - [Controllers](/guides/core-concepts/controllers) - Initiate transactions from HTTP handlers
155
+ - [DataSources](/guides/core-concepts/persistent/datasources) - Database connections
156
+
157
+ - **References:**
158
+ - [Repositories API](/references/base/repositories/) - Transaction methods and options
159
+ - [BaseDataSource API](/references/base/datasources) - Connection and transaction management
160
+
161
+ - **External Resources:**
162
+ - [PostgreSQL Transactions](https://www.postgresql.org/docs/current/tutorial-transactions.html) - Transaction fundamentals
163
+ - [Isolation Levels](https://www.postgresql.org/docs/current/transaction-iso.html) - Understanding isolation levels
164
+
165
+ - **Best Practices:**
166
+ - [Data Modeling](/best-practices/data-modeling) - Transaction design patterns
167
+ - [Common Pitfalls](/best-practices/common-pitfalls) - Transaction anti-patterns
168
+
169
+ - **Tutorials:**
170
+ - [E-commerce API](/guides/tutorials/ecommerce-api) - Order creation with transactions
@@ -36,12 +36,15 @@ export class ConfigurationService extends BaseService {
36
36
  super({ scope: ConfigurationService.name });
37
37
  }
38
38
 
39
- async createConfigurationForUser(userId: string, data: Partial<TConfiguration>): Promise<TConfiguration> {
39
+ async createConfigurationForUser(opts: {
40
+ userId: string;
41
+ data: Partial<TConfiguration>;
42
+ }): Promise<TConfiguration> {
40
43
  // Call another service logic
41
- await this.loggingService.audit(`Creating config for user: ${userId}`);
44
+ await this.loggingService.audit({ message: `Creating config for user: ${opts.userId}` });
42
45
 
43
46
  // Business logic: Check if the user exists
44
- const user = await this.userRepository.findById({ id: userId });
47
+ const user = await this.userRepository.findById({ id: opts.userId });
45
48
  // ...
46
49
  ```
47
50
 
@@ -80,3 +83,23 @@ This layered architecture makes your application:
80
83
  - **More Organized:** Each layer has a clear and distinct responsibility.
81
84
  - **Easier to Test:** You can test your business logic in isolation by providing mock repositories to your services, without needing a live database.
82
85
  - **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.
86
+
87
+ ## See Also
88
+
89
+ - **Related Concepts:**
90
+ - [Controllers](/guides/core-concepts/controllers) - Call services to handle requests
91
+ - [Repositories](/guides/core-concepts/persistent/repositories) - Data access layer used by services
92
+ - [Dependency Injection](/guides/core-concepts/dependency-injection) - Injecting dependencies into services
93
+
94
+ - **References:**
95
+ - [BaseService API](/references/base/services) - Complete API reference
96
+ - [Providers](/references/base/providers) - Factory pattern for runtime instantiation
97
+ - [Logger Helper](/references/helpers/logger) - Logging in services
98
+
99
+ - **Best Practices:**
100
+ - [Architectural Patterns](/best-practices/architectural-patterns) - Service layer design
101
+ - [Testing](/guides/tutorials/testing) - Unit testing services
102
+
103
+ - **Tutorials:**
104
+ - [Building a CRUD API](/guides/tutorials/building-a-crud-api) - Service examples
105
+ - [E-commerce API](/guides/tutorials/ecommerce-api) - Complex business logic