@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.
- package/README.md +1 -1
- package/package.json +4 -2
- package/wiki/best-practices/api-usage-examples.md +591 -0
- package/wiki/best-practices/architectural-patterns.md +415 -0
- package/wiki/best-practices/architecture-decisions.md +488 -0
- package/wiki/{get-started/best-practices → best-practices}/code-style-standards.md +406 -17
- package/wiki/{get-started/best-practices → best-practices}/common-pitfalls.md +109 -4
- package/wiki/{get-started/best-practices → best-practices}/contribution-workflow.md +34 -7
- package/wiki/best-practices/data-modeling.md +376 -0
- package/wiki/best-practices/deployment-strategies.md +698 -0
- package/wiki/best-practices/index.md +27 -0
- package/wiki/best-practices/performance-optimization.md +196 -0
- package/wiki/best-practices/security-guidelines.md +218 -0
- package/wiki/{get-started/best-practices → best-practices}/troubleshooting-tips.md +97 -1
- package/wiki/changelogs/2025-12-16-initial-architecture.md +1 -1
- package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +1 -1
- package/wiki/changelogs/2025-12-17-refactor.md +1 -1
- package/wiki/changelogs/2025-12-18-performance-optimizations.md +5 -5
- package/wiki/changelogs/2025-12-18-repository-validation-security.md +13 -7
- package/wiki/changelogs/2025-12-26-nested-relations-and-generics.md +2 -2
- package/wiki/changelogs/2025-12-29-dynamic-binding-registration.md +104 -0
- package/wiki/changelogs/2025-12-29-snowflake-uid-helper.md +100 -0
- package/wiki/changelogs/2025-12-30-repository-enhancements.md +214 -0
- package/wiki/changelogs/2025-12-31-json-path-filtering-array-operators.md +214 -0
- package/wiki/changelogs/2025-12-31-string-id-custom-generator.md +137 -0
- package/wiki/changelogs/2026-01-02-default-filter-and-repository-mixins.md +418 -0
- package/wiki/changelogs/index.md +6 -0
- package/wiki/changelogs/planned-schema-migrator.md +0 -8
- package/wiki/{get-started/core-concepts → guides/core-concepts/application}/bootstrapping.md +18 -5
- package/wiki/{get-started/core-concepts/application.md → guides/core-concepts/application/index.md} +47 -104
- package/wiki/guides/core-concepts/components-guide.md +509 -0
- package/wiki/{get-started → guides}/core-concepts/components.md +24 -17
- package/wiki/{get-started → guides}/core-concepts/controllers.md +30 -13
- package/wiki/{get-started → guides}/core-concepts/dependency-injection.md +97 -0
- package/wiki/guides/core-concepts/persistent/datasources.md +179 -0
- package/wiki/guides/core-concepts/persistent/index.md +119 -0
- package/wiki/guides/core-concepts/persistent/models.md +241 -0
- package/wiki/guides/core-concepts/persistent/repositories.md +219 -0
- package/wiki/guides/core-concepts/persistent/transactions.md +170 -0
- package/wiki/{get-started → guides}/core-concepts/services.md +26 -3
- package/wiki/{get-started → guides/get-started}/5-minute-quickstart.md +59 -14
- package/wiki/guides/get-started/philosophy.md +682 -0
- package/wiki/guides/get-started/setup.md +157 -0
- package/wiki/guides/index.md +89 -0
- package/wiki/guides/reference/glossary.md +243 -0
- package/wiki/{get-started → guides/reference}/mcp-docs-server.md +0 -10
- package/wiki/{get-started → guides/tutorials}/building-a-crud-api.md +134 -132
- package/wiki/{get-started/quickstart.md → guides/tutorials/complete-installation.md} +107 -71
- package/wiki/guides/tutorials/ecommerce-api.md +1399 -0
- package/wiki/guides/tutorials/realtime-chat.md +1261 -0
- package/wiki/guides/tutorials/testing.md +723 -0
- package/wiki/index.md +176 -37
- package/wiki/references/base/application.md +27 -0
- package/wiki/references/base/bootstrapping.md +31 -26
- package/wiki/references/base/components.md +24 -7
- package/wiki/references/base/controllers.md +50 -20
- package/wiki/references/base/datasources.md +30 -0
- package/wiki/references/base/dependency-injection.md +39 -3
- package/wiki/references/base/filter-system/application-usage.md +224 -0
- package/wiki/references/base/filter-system/array-operators.md +132 -0
- package/wiki/references/base/filter-system/comparison-operators.md +109 -0
- package/wiki/references/base/filter-system/default-filter.md +428 -0
- package/wiki/references/base/filter-system/fields-order-pagination.md +155 -0
- package/wiki/references/base/filter-system/index.md +127 -0
- package/wiki/references/base/filter-system/json-filtering.md +197 -0
- package/wiki/references/base/filter-system/list-operators.md +71 -0
- package/wiki/references/base/filter-system/logical-operators.md +156 -0
- package/wiki/references/base/filter-system/null-operators.md +58 -0
- package/wiki/references/base/filter-system/pattern-matching.md +108 -0
- package/wiki/references/base/filter-system/quick-reference.md +431 -0
- package/wiki/references/base/filter-system/range-operators.md +63 -0
- package/wiki/references/base/filter-system/tips.md +190 -0
- package/wiki/references/base/filter-system/use-cases.md +452 -0
- package/wiki/references/base/index.md +90 -0
- package/wiki/references/base/middlewares.md +604 -0
- package/wiki/references/base/models.md +215 -23
- package/wiki/references/base/providers.md +731 -0
- package/wiki/references/base/repositories/advanced.md +555 -0
- package/wiki/references/base/repositories/index.md +228 -0
- package/wiki/references/base/repositories/mixins.md +331 -0
- package/wiki/references/base/repositories/relations.md +486 -0
- package/wiki/references/base/repositories.md +40 -635
- package/wiki/references/base/services.md +28 -4
- package/wiki/references/components/authentication.md +22 -2
- package/wiki/references/components/health-check.md +12 -0
- package/wiki/references/components/index.md +23 -0
- package/wiki/references/components/mail.md +687 -0
- package/wiki/references/components/request-tracker.md +16 -0
- package/wiki/references/components/socket-io.md +18 -0
- package/wiki/references/components/static-asset.md +14 -26
- package/wiki/references/components/swagger.md +17 -0
- package/wiki/references/configuration/environment-variables.md +427 -0
- package/wiki/references/configuration/index.md +73 -0
- package/wiki/references/helpers/cron.md +14 -0
- package/wiki/references/helpers/crypto.md +15 -0
- package/wiki/references/helpers/env.md +16 -0
- package/wiki/references/helpers/error.md +17 -0
- package/wiki/references/helpers/index.md +14 -0
- package/wiki/references/helpers/inversion.md +24 -4
- package/wiki/references/helpers/logger.md +19 -0
- package/wiki/references/helpers/network.md +11 -0
- package/wiki/references/helpers/queue.md +19 -0
- package/wiki/references/helpers/redis.md +21 -0
- package/wiki/references/helpers/socket-io.md +24 -5
- package/wiki/references/helpers/storage.md +18 -10
- package/wiki/references/helpers/testing.md +18 -0
- package/wiki/references/helpers/types.md +16 -0
- package/wiki/references/helpers/uid.md +167 -0
- package/wiki/references/helpers/worker-thread.md +16 -0
- package/wiki/references/index.md +177 -0
- package/wiki/references/quick-reference.md +634 -0
- package/wiki/references/src-details/boot.md +3 -3
- package/wiki/references/src-details/dev-configs.md +0 -4
- package/wiki/references/src-details/docs.md +2 -2
- package/wiki/references/src-details/index.md +86 -0
- package/wiki/references/src-details/inversion.md +1 -6
- package/wiki/references/src-details/mcp-server.md +3 -15
- package/wiki/references/utilities/index.md +86 -10
- package/wiki/references/utilities/jsx.md +577 -0
- package/wiki/references/utilities/request.md +0 -2
- package/wiki/references/utilities/statuses.md +740 -0
- package/wiki/get-started/best-practices/api-usage-examples.md +0 -266
- package/wiki/get-started/best-practices/architectural-patterns.md +0 -170
- package/wiki/get-started/best-practices/data-modeling.md +0 -177
- package/wiki/get-started/best-practices/deployment-strategies.md +0 -121
- package/wiki/get-started/best-practices/performance-optimization.md +0 -97
- package/wiki/get-started/best-practices/security-guidelines.md +0 -99
- package/wiki/get-started/core-concepts/persistent.md +0 -539
- package/wiki/get-started/index.md +0 -65
- package/wiki/get-started/philosophy.md +0 -296
- 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(
|
|
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
|