@venizia/ignis-docs 0.0.1-8 → 0.0.1-9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/package.json +1 -1
  2. package/wiki/changelogs/2025-12-17-refactor.md +22 -0
  3. package/wiki/changelogs/2025-12-18-performance-optimizations.md +192 -0
  4. package/wiki/changelogs/2025-12-18-repository-validation-security.md +445 -0
  5. package/wiki/changelogs/index.md +22 -0
  6. package/wiki/changelogs/v0.0.1-7-initial-architecture.md +137 -0
  7. package/wiki/changelogs/v0.0.1-8-model-repo-datasource-refactor.md +278 -0
  8. package/wiki/get-started/5-minute-quickstart.md +1 -1
  9. package/wiki/get-started/best-practices/api-usage-examples.md +12 -8
  10. package/wiki/get-started/best-practices/common-pitfalls.md +2 -2
  11. package/wiki/get-started/best-practices/data-modeling.md +14 -20
  12. package/wiki/get-started/building-a-crud-api.md +60 -75
  13. package/wiki/get-started/core-concepts/controllers.md +14 -14
  14. package/wiki/get-started/core-concepts/persistent.md +110 -130
  15. package/wiki/get-started/quickstart.md +1 -1
  16. package/wiki/references/base/controllers.md +40 -16
  17. package/wiki/references/base/datasources.md +195 -33
  18. package/wiki/references/base/dependency-injection.md +5 -5
  19. package/wiki/references/base/models.md +398 -28
  20. package/wiki/references/base/repositories.md +475 -22
  21. package/wiki/references/components/authentication.md +224 -7
  22. package/wiki/references/components/health-check.md +1 -1
  23. package/wiki/references/components/swagger.md +1 -1
  24. package/wiki/references/helpers/inversion.md +8 -3
  25. package/wiki/references/src-details/core.md +6 -5
  26. package/wiki/references/src-details/inversion.md +4 -4
  27. package/wiki/references/utilities/request.md +16 -7
@@ -0,0 +1,137 @@
1
+ # v0.0.1-7 - Initial Architecture (Pre-Refactor)
2
+
3
+ **Release Date**: 2025-12-16
4
+ **Status**: Superseded by v0.0.1-8
5
+
6
+ ## Overview
7
+
8
+ This documents the original architecture of the Ignis framework before the Model-Repository-DataSource refactor. This version required manual schema registration and explicit constructor parameters.
9
+
10
+ ## Architecture Pattern
11
+
12
+ ### Model Definition
13
+
14
+ Models were defined in three separate steps:
15
+
16
+ ```typescript
17
+ // Step 1: Define table schema
18
+ const TABLE_NAME = 'Configuration';
19
+
20
+ export const configurationTable = pgTable(TABLE_NAME, {
21
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
22
+ ...generateTzColumnDefs(),
23
+ code: text('code').notNull(),
24
+ group: text('group').notNull(),
25
+ });
26
+
27
+ // Step 2: Define relations separately
28
+ export const configurationRelations = createRelations({
29
+ source: configurationTable,
30
+ relations: [
31
+ {
32
+ name: 'creator',
33
+ type: RelationTypes.ONE,
34
+ schema: userTable,
35
+ metadata: {
36
+ fields: [configurationTable.createdBy],
37
+ references: [userTable.id],
38
+ },
39
+ },
40
+ ],
41
+ });
42
+
43
+ // Step 3: Create model class
44
+ @model({ type: 'entity', skipMigrate: false })
45
+ export class Configuration extends BaseEntity<typeof configurationTable> {
46
+ static readonly TABLE_NAME = Configuration.name;
47
+
48
+ constructor() {
49
+ super({
50
+ name: Configuration.TABLE_NAME,
51
+ schema: configurationTable,
52
+ });
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### DataSource Definition
58
+
59
+ DataSources required manual schema registration:
60
+
61
+ ```typescript
62
+ @datasource({})
63
+ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
64
+ constructor() {
65
+ super({
66
+ name: PostgresDataSource.name,
67
+ driver: 'node-postgres',
68
+ config: { /* connection config */ },
69
+
70
+ // Manual schema registration - verbose and error-prone
71
+ schema: Object.assign(
72
+ {},
73
+ {
74
+ [User.TABLE_NAME]: userTable,
75
+ [Configuration.TABLE_NAME]: configurationTable,
76
+ },
77
+ {
78
+ userRelations: userRelations.relations,
79
+ configurationRelations: configurationRelations.relations,
80
+ },
81
+ ),
82
+ });
83
+ }
84
+ }
85
+ ```
86
+
87
+ ### Repository Definition
88
+
89
+ Repositories required explicit constructor injection:
90
+
91
+ ```typescript
92
+ @repository({})
93
+ export class ConfigurationRepository extends DefaultCRUDRepository<typeof configurationTable> {
94
+ constructor(@inject({ key: 'datasources.PostgresDataSource' }) dataSource: IDataSource) {
95
+ super({
96
+ dataSource,
97
+ entityClass: Configuration,
98
+ relations: configurationRelations.definitions,
99
+ });
100
+ }
101
+ }
102
+ ```
103
+
104
+ ## Pain Points
105
+
106
+ 1. **Verbose Model Definition**: Three separate declarations (table, relations, class) for each model
107
+ 2. **Manual Schema Registration**: DataSource required explicit registration of every model and relation
108
+ 3. **Unclear Repository Role**: Repository just wrapped datasource without defining the model-datasource binding
109
+ 4. **Declaration Order Issues**: Had to declare table before relations, relations before class
110
+ 5. **No Auto-Discovery**: Adding a new model required updates in multiple places
111
+ 6. **Tight Coupling**: Changes to model structure required updates in datasource configuration
112
+
113
+ ## File Structure
114
+
115
+ ```
116
+ src/
117
+ ├── models/
118
+ │ └── entities/
119
+ │ ├── user.model.ts # Table + Relations + Class
120
+ │ └── configuration.model.ts # Table + Relations + Class
121
+ ├── datasources/
122
+ │ └── postgres.datasource.ts # Manual schema assembly
123
+ └── repositories/
124
+ ├── user.repository.ts # Explicit constructor injection
125
+ └── configuration.repository.ts
126
+ ```
127
+
128
+ ## Dependencies
129
+
130
+ - `@venizia/ignis-helpers`: Core utilities and types
131
+ - `@venizia/ignis-inversion`: Dependency injection
132
+ - `drizzle-orm`: ORM layer
133
+ - `drizzle-zod`: Schema validation
134
+
135
+ ---
136
+
137
+ *This architecture was superseded by the Loopback 4-style refactor in v0.0.1-8*
@@ -0,0 +1,278 @@
1
+ # v0.0.1-8 - Model-Repository-DataSource Architecture Refactor
2
+
3
+ **Release Date**: 2025-12-16
4
+ **Status**: Current
5
+
6
+ ## Overview
7
+
8
+ Major refactor following Loopback 4's architecture pattern where:
9
+ - **Model** is self-contained with schema and relations
10
+ - **Repository** connects Model to DataSource (defines the binding)
11
+ - **DataSource** auto-discovers schemas from registered repositories
12
+
13
+ This eliminates manual schema registration and simplifies the development workflow significantly.
14
+
15
+ ## Breaking Changes
16
+
17
+ 1. **Model static properties now require `override` keyword**
18
+ - `static override schema = pgTable(...)`
19
+ - `static override relations = () => ({...})`
20
+
21
+ 2. **Repository constructor is now optional**
22
+ - Old: Required `entityClass`, `relations`, `dataSource` parameters
23
+ - New: Auto-resolved from `@repository` decorator metadata
24
+
25
+ 3. **DataSource schema is now optional**
26
+ - Old: Required manual `schema` property with all models
27
+ - New: Auto-discovered from `@repository` bindings
28
+
29
+ ## New Features
30
+
31
+ ### Self-Contained Models
32
+
33
+ Models now define schema and relations as static properties:
34
+
35
+ ```typescript
36
+ @model({ type: 'entity' })
37
+ export class Configuration extends BaseEntity<typeof Configuration.schema> {
38
+ static override schema = pgTable('Configuration', {
39
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
40
+ ...generateTzColumnDefs(),
41
+ code: text('code').notNull(),
42
+ group: text('group').notNull(),
43
+ });
44
+
45
+ static override relations = () => ({
46
+ creator: {
47
+ type: 'one' as const,
48
+ target: () => User,
49
+ fields: [Configuration.schema.createdBy],
50
+ references: () => [User.schema.id],
51
+ },
52
+ });
53
+ }
54
+ ```
55
+
56
+ ### Repository Auto-Resolution
57
+
58
+ Repositories now use `@repository` decorator for model-datasource binding:
59
+
60
+ ```typescript
61
+ @repository({
62
+ model: Configuration,
63
+ datasource: PostgresDataSource,
64
+ })
65
+ export class ConfigurationRepository extends DefaultCRUDRepository<typeof Configuration.schema> {
66
+ // No constructor needed!
67
+
68
+ async findByCode(code: string) {
69
+ return this.findOne({ filter: { where: { code } } });
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### DataSource Auto-Discovery
75
+
76
+ DataSources automatically discover their schema from repository bindings:
77
+
78
+ ```typescript
79
+ @datasource({ driver: 'node-postgres' })
80
+ export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
81
+ constructor() {
82
+ super({
83
+ name: PostgresDataSource.name,
84
+ driver: 'node-postgres',
85
+ config: { /* connection config */ },
86
+ // NO schema property - auto-discovered!
87
+ });
88
+ }
89
+
90
+ override configure(): ValueOrPromise<void> {
91
+ const schema = this.getSchema(); // Auto-discovers from @repository bindings
92
+ this.connector = drizzle({ client: new Pool(this.settings), schema });
93
+ }
94
+ }
95
+ ```
96
+
97
+ ## Files Changed
98
+
99
+ ### Core Package (`packages/core`)
100
+
101
+ | File | Changes |
102
+ |------|---------|
103
+ | `src/base/models/base.ts` | Added static `schema`, `relations`, `TABLE_NAME` |
104
+ | `src/base/datasources/base.ts` | Added auto-discovery via `buildAutoDiscoveredSchema()` |
105
+ | `src/base/repositories/core/base.ts` | Added lazy resolution, static container reference |
106
+ | `src/base/repositories/core/readable.ts` | Made constructor opts optional |
107
+ | `src/base/repositories/core/persistable.ts` | Made constructor opts optional |
108
+ | `src/base/repositories/core/default-crud.ts` | Added documentation |
109
+ | `src/base/metadata/persistents.ts` | Updated decorators for auto-discovery |
110
+ | `src/base/applications/base.ts` | Added `AbstractRepository.setContainer(this)` |
111
+ | `src/components/static-asset/models/base.model.ts` | Updated to new pattern |
112
+
113
+ ### Helpers Package (`packages/helpers`)
114
+
115
+ | File | Changes |
116
+ |------|---------|
117
+ | `src/helpers/inversion/common/types.ts` | Added `IModelMetadata`, `IRelationDefinition`, `IModelStatic`, `IRepositoryMetadata`, `IRepositoryBinding` |
118
+ | `src/helpers/inversion/registry.ts` | Added `registerModel`, `registerRepositoryBinding`, `buildDataSourceSchema` |
119
+
120
+ ### Examples (`examples/vert`)
121
+
122
+ | File | Changes |
123
+ |------|---------|
124
+ | `src/models/entities/user.model.ts` | Updated to static schema pattern |
125
+ | `src/models/entities/configuration.model.ts` | Updated to static schema pattern |
126
+ | `src/datasources/postgres.datasource.ts` | Removed manual schema registration |
127
+ | `src/repositories/user.repository.ts` | Updated to use `@repository` decorator |
128
+ | `src/repositories/configuration.repository.ts` | Updated to use `@repository` decorator |
129
+
130
+ ## Migration Guide
131
+
132
+ ### Step 1: Update Models
133
+
134
+ **Before:**
135
+ ```typescript
136
+ const userTable = pgTable('User', {...});
137
+ const userRelations = createRelations({...});
138
+
139
+ @model({ type: 'entity' })
140
+ export class User extends BaseEntity<typeof userTable> {
141
+ constructor() {
142
+ super({ name: 'User', schema: userTable });
143
+ }
144
+ }
145
+ ```
146
+
147
+ **After:**
148
+ ```typescript
149
+ @model({ type: 'entity' })
150
+ export class User extends BaseEntity<typeof User.schema> {
151
+ static override schema = pgTable('User', {...});
152
+ static override relations = () => ({...});
153
+ }
154
+ ```
155
+
156
+ ### Step 2: Update Repositories
157
+
158
+ **Before:**
159
+ ```typescript
160
+ @repository({})
161
+ export class UserRepository extends DefaultCRUDRepository<typeof userTable> {
162
+ constructor(@inject({ key: 'datasources.PostgresDataSource' }) dataSource: IDataSource) {
163
+ super({ dataSource, entityClass: User, relations: userRelations.definitions });
164
+ }
165
+ }
166
+ ```
167
+
168
+ **After:**
169
+ ```typescript
170
+ @repository({ model: User, datasource: PostgresDataSource })
171
+ export class UserRepository extends DefaultCRUDRepository<typeof User.schema> {
172
+ // No constructor needed!
173
+ }
174
+ ```
175
+
176
+ ### Step 3: Update DataSources
177
+
178
+ **Before:**
179
+ ```typescript
180
+ @datasource({})
181
+ export class PostgresDataSource extends BaseDataSource {
182
+ constructor() {
183
+ super({
184
+ name: PostgresDataSource.name,
185
+ driver: 'node-postgres',
186
+ config: {...},
187
+ schema: { User: userTable, userRelations: userRelations.relations, ... },
188
+ });
189
+ }
190
+ }
191
+ ```
192
+
193
+ **After:**
194
+ ```typescript
195
+ @datasource({ driver: 'node-postgres' })
196
+ export class PostgresDataSource extends BaseDataSource {
197
+ constructor() {
198
+ super({
199
+ name: PostgresDataSource.name,
200
+ driver: 'node-postgres',
201
+ config: {...},
202
+ // NO schema - auto-discovered!
203
+ });
204
+ }
205
+ }
206
+ ```
207
+
208
+ ## Architecture Diagram
209
+
210
+ ```
211
+ ┌─────────────────────────────────────────────────────────────────┐
212
+ │ Application │
213
+ │ ┌───────────────────────────────────────────────────────────┐ │
214
+ │ │ preConfigure() │ │
215
+ │ │ this.dataSource(PostgresDataSource) │ │
216
+ │ │ this.repository(UserRepository) ──┐ │ │
217
+ │ │ this.repository(ConfigurationRepository) │ Registers │ │
218
+ │ │ │ bindings │ │
219
+ │ └─────────────────────────────────────────────┼─────────────┘ │
220
+ │ │ │
221
+ │ ┌─────────────────────────────────────────────▼─────────────┐ │
222
+ │ │ MetadataRegistry │ │
223
+ │ │ modelRegistry: Map<tableName, {schema, relations}> │ │
224
+ │ │ repositoryBindings: Map<repoClass, {model, datasource}> │ │
225
+ │ │ datasourceModels: Map<datasource, Set<modelNames>> │ │
226
+ │ └─────────────────────────────────────────────┬─────────────┘ │
227
+ │ │ │
228
+ │ ┌─────────────────────────────────────────────▼─────────────┐ │
229
+ │ │ DataSource.configure() │ │
230
+ │ │ schema = this.getSchema() ◄── Auto-discovers from │ │
231
+ │ │ MetadataRegistry │ │
232
+ │ │ this.connector = drizzle({ schema }) │ │
233
+ │ └───────────────────────────────────────────────────────────┘ │
234
+ └─────────────────────────────────────────────────────────────────┘
235
+ ```
236
+
237
+ ## Benefits
238
+
239
+ 1. **Simplified Model Definition**: Single class with static properties
240
+ 2. **No Manual Schema Registration**: DataSource auto-discovers models
241
+ 3. **Clear Repository Role**: Explicitly binds model to datasource
242
+ 4. **Better Type Safety**: Types flow from `static schema` through the system
243
+ 5. **Reduced Boilerplate**: No constructor needed for basic repositories
244
+ 6. **Follows Loopback 4 Pattern**: Familiar architecture for developers
245
+
246
+ ## Technical Details
247
+
248
+ ### IRelationDefinition Interface
249
+
250
+ ```typescript
251
+ interface IRelationDefinition<TTarget = any> {
252
+ type: 'one' | 'many';
253
+ target: () => TClass<TTarget>; // Lazy to avoid circular imports
254
+ fields?: AnyColumn[];
255
+ references?: () => AnyColumn[]; // Lazy to avoid circular imports
256
+ relationName?: string;
257
+ }
258
+ ```
259
+
260
+ ### Repository Auto-Resolution Flow
261
+
262
+ 1. `@repository` decorator registers binding in `MetadataRegistry`
263
+ 2. When repository is instantiated, constructor checks for explicit opts
264
+ 3. If not provided, lazy getters resolve from `MetadataRegistry`:
265
+ - `entity` → resolved from `modelClass` in binding
266
+ - `relations` → built from model's `static relations()`
267
+ - `dataSource` → resolved from container using binding key
268
+
269
+ ### DataSource Auto-Discovery Flow
270
+
271
+ 1. `@repository` decorators register model-datasource bindings
272
+ 2. When `dataSource.configure()` is called, `getSchema()` is invoked
273
+ 3. `buildAutoDiscoveredSchema()` queries `MetadataRegistry` for all models bound to this datasource
274
+ 4. Schema is assembled from model schemas and relations
275
+
276
+ ---
277
+
278
+ *This refactor was inspired by Loopback 4's architecture pattern*
@@ -253,7 +253,7 @@ Open `http://localhost:3000/doc/explorer` to see interactive Swagger UI document
253
253
  })
254
254
  async greet(c: Context) {
255
255
  const { name } = await c.req.json();
256
- return c.json({ greeting: `Hello, ${name}!` });
256
+ return c.json({ greeting: `Hello, ${name}!` }, HTTP.ResultCodes.RS_2.Ok);
257
257
  }
258
258
  ```
259
259
 
@@ -52,6 +52,7 @@ import {
52
52
  get,
53
53
  post,
54
54
  TRouteContext,
55
+ HTTP,
55
56
  } from '@venizia/ignis';
56
57
  import { ROUTE_CONFIGS } from './definitions';
57
58
 
@@ -62,7 +63,7 @@ export class TestController extends BaseController {
62
63
  @get({ configs: ROUTE_CONFIGS['/4'] })
63
64
  getWithDecorator(context: TRouteContext<(typeof ROUTE_CONFIGS)['/4']>) {
64
65
  // context is fully typed!
65
- return context.json({ message: 'Hello from decorator', method: 'GET' });
66
+ return context.json({ message: 'Hello from decorator', method: 'GET' }, HTTP.ResultCodes.RS_2.Ok);
66
67
  }
67
68
 
68
69
  @post({ configs: ROUTE_CONFIGS['/5'] })
@@ -71,11 +72,14 @@ export class TestController extends BaseController {
71
72
  const body = context.req.valid('json');
72
73
 
73
74
  // The response is validated against the schema
74
- return context.json({
75
- id: crypto.randomUUID(),
76
- name: body.name,
77
- age: body.age,
78
- });
75
+ return context.json(
76
+ {
77
+ id: crypto.randomUUID(),
78
+ name: body.name,
79
+ age: body.age,
80
+ },
81
+ HTTP.ResultCodes.RS_2.Ok,
82
+ );
79
83
  }
80
84
  }
81
85
  ```
@@ -97,7 +101,7 @@ export class TestController extends BaseController {
97
101
  this.defineRoute({
98
102
  configs: ROUTE_CONFIGS['/1'],
99
103
  handler: context => {
100
- return context.json({ message: 'Hello' });
104
+ return context.json({ message: 'Hello' }, HTTP.ResultCodes.RS_2.Ok);
101
105
  },
102
106
  });
103
107
 
@@ -106,7 +110,7 @@ export class TestController extends BaseController {
106
110
  configs: ROUTE_CONFIGS['/3'],
107
111
  }).to({
108
112
  handler: context => {
109
- return context.json({ message: 'Hello 3' });
113
+ return context.json({ message: 'Hello 3' }, HTTP.ResultCodes.RS_2.Ok);
110
114
  },
111
115
  });
112
116
  }
@@ -79,7 +79,7 @@ export class Application extends BaseApplication {
79
79
  const company = await this.companyRepository.findOrCreate(companyName);
80
80
  const user = await this.userRepository.create({ name, email, companyId: company.id });
81
81
 
82
- return c.json(user);
82
+ return c.json(user, HTTP.ResultCodes.RS_2.Ok);
83
83
  }
84
84
  ```
85
85
  - **Good:**
@@ -89,7 +89,7 @@ export class Application extends BaseApplication {
89
89
  const userData = c.req.valid('json');
90
90
  // Delegate to the service
91
91
  const newUser = await this.userService.createUser(userData);
92
- return c.json(newUser);
92
+ return c.json(newUser, HTTP.ResultCodes.RS_2.Ok);
93
93
  }
94
94
 
95
95
  // In UserService
@@ -14,22 +14,18 @@ import {
14
14
  model,
15
15
  TTableObject,
16
16
  } from '@venizia/ignis';
17
- import { configurationTable } from './schema'; // Your Drizzle schema
17
+ import { configurationTable, configurationRelations } from './schema'; // Your Drizzle schema
18
18
 
19
19
  // Define types for TypeScript inference
20
20
  export type TConfigurationSchema = typeof configurationTable;
21
21
  export type TConfiguration = TTableObject<TConfigurationSchema>;
22
22
 
23
23
  @model({ type: 'entity', skipMigrate: false })
24
- export class Configuration extends BaseEntity<TConfigurationSchema> {
25
- static readonly TABLE_NAME = Configuration.name;
26
-
27
- constructor() {
28
- super({
29
- name: Configuration.TABLE_NAME,
30
- schema: configurationTable,
31
- });
32
- }
24
+ export class Configuration extends BaseEntity<typeof Configuration.schema> {
25
+ // Use static properties (recommended pattern)
26
+ static override schema = configurationTable;
27
+ static override relations = () => configurationRelations.definitions;
28
+ static override TABLE_NAME = 'Configuration';
33
29
  }
34
30
  ```
35
31
 
@@ -110,17 +106,15 @@ export const configurationRelations = createRelations({
110
106
  });
111
107
  ```
112
108
 
113
- This configuration is then passed directly to your Repository:
109
+ This configuration is automatically used when you define your Repository with the `@repository` decorator:
114
110
 
115
111
  ```typescript
116
- @repository({})
117
- export class ConfigurationRepository extends DefaultCRUDRepository<TConfigurationSchema> {
118
- constructor(@inject({ key: 'datasources.PostgresDataSource' }) dataSource: IDataSource) {
119
- super({
120
- dataSource,
121
- entityClass: Configuration,
122
- relations: configurationRelations.definitions, // <-- Injected here
123
- });
124
- }
112
+ import { PostgresDataSource } from '@/datasources';
113
+
114
+ // Both 'model' and 'dataSource' are required for schema auto-discovery
115
+ @repository({ model: Configuration, dataSource: PostgresDataSource })
116
+ export class ConfigurationRepository extends DefaultCRUDRepository<typeof Configuration.schema> {
117
+ // No constructor needed! DataSource and relations are auto-resolved
118
+ // from the @repository decorator and entity's static properties
125
119
  }
126
120
  ```