@venizia/ignis-docs 0.0.4-0 → 0.0.4-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/wiki/best-practices/api-usage-examples.md +1 -0
- package/wiki/best-practices/code-style-standards/advanced-patterns.md +259 -0
- package/wiki/best-practices/code-style-standards/constants-configuration.md +225 -0
- package/wiki/best-practices/code-style-standards/control-flow.md +245 -0
- package/wiki/best-practices/code-style-standards/documentation.md +221 -0
- package/wiki/best-practices/code-style-standards/function-patterns.md +142 -0
- package/wiki/best-practices/code-style-standards/index.md +110 -0
- package/wiki/best-practices/code-style-standards/naming-conventions.md +174 -0
- package/wiki/best-practices/code-style-standards/route-definitions.md +150 -0
- package/wiki/best-practices/code-style-standards/tooling.md +155 -0
- package/wiki/best-practices/code-style-standards/type-safety.md +165 -0
- package/wiki/best-practices/common-pitfalls.md +164 -3
- package/wiki/best-practices/contribution-workflow.md +1 -1
- package/wiki/best-practices/data-modeling.md +102 -2
- package/wiki/best-practices/error-handling.md +468 -0
- package/wiki/best-practices/index.md +204 -21
- package/wiki/best-practices/performance-optimization.md +180 -0
- package/wiki/best-practices/security-guidelines.md +249 -0
- package/wiki/best-practices/testing-strategies.md +620 -0
- package/wiki/changelogs/2026-01-05-range-queries-content-range.md +184 -0
- package/wiki/changelogs/2026-01-06-basic-authentication.md +103 -0
- package/wiki/changelogs/2026-01-07-controller-route-customization.md +209 -0
- package/wiki/changelogs/index.md +3 -0
- package/wiki/guides/core-concepts/components-guide.md +1 -1
- package/wiki/guides/core-concepts/persistent/models.md +10 -0
- package/wiki/guides/tutorials/complete-installation.md +1 -1
- package/wiki/guides/tutorials/testing.md +1 -1
- package/wiki/references/base/bootstrapping.md +4 -3
- package/wiki/references/base/components.md +47 -29
- package/wiki/references/base/controllers.md +220 -24
- package/wiki/references/base/filter-system/fields-order-pagination.md +84 -0
- package/wiki/references/base/middlewares.md +37 -3
- package/wiki/references/base/models.md +40 -2
- package/wiki/references/base/providers.md +1 -2
- package/wiki/references/base/repositories/index.md +3 -1
- package/wiki/references/base/services.md +2 -2
- package/wiki/references/components/authentication.md +261 -247
- package/wiki/references/helpers/index.md +1 -1
- package/wiki/references/helpers/socket-io.md +1 -1
- package/wiki/references/quick-reference.md +2 -2
- package/wiki/references/src-details/core.md +1 -1
- package/wiki/references/utilities/statuses.md +4 -4
- package/wiki/best-practices/code-style-standards.md +0 -1193
|
@@ -54,8 +54,8 @@ export class Application extends BaseApplication {
|
|
|
54
54
|
}),
|
|
55
55
|
})
|
|
56
56
|
|
|
57
|
-
// ❌ BAD - typo in string
|
|
58
|
-
@inject({ key: 'repositories.
|
|
57
|
+
// ❌ BAD - typo in string (note: "Repository" is misspelled)
|
|
58
|
+
@inject({ key: 'repositories.ConfigurationRepository' })
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
## 3. Business Logic in Controllers
|
|
@@ -240,4 +240,165 @@ try {
|
|
|
240
240
|
});
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
|
-
```
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## 9. Circular Dependency Issues
|
|
246
|
+
|
|
247
|
+
**Problem:** Application fails to start with `Cannot access 'X' before initialization` or similar errors.
|
|
248
|
+
|
|
249
|
+
**Cause:** Two or more modules import each other directly, creating a circular reference that JavaScript cannot resolve.
|
|
250
|
+
|
|
251
|
+
**Solution:** Use lazy imports or restructure your modules:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// ❌ BAD - Direct import causes circular dependency
|
|
255
|
+
import { UserService } from './user.service';
|
|
256
|
+
|
|
257
|
+
@model({ type: 'entity' })
|
|
258
|
+
export class Order extends BaseEntity<typeof Order.schema> {
|
|
259
|
+
static override relations = (): TRelationConfig[] => [
|
|
260
|
+
{ schema: User.schema, ... }, // User imports Order, Order imports User
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ✅ GOOD - Lazy import breaks the cycle
|
|
265
|
+
@model({ type: 'entity' })
|
|
266
|
+
export class Order extends BaseEntity<typeof Order.schema> {
|
|
267
|
+
static override relations = (): TRelationConfig[] => {
|
|
268
|
+
const { User } = require('./user.model'); // Lazy require
|
|
269
|
+
return [{ schema: User.schema, ... }];
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Alternative:** Restructure to have a shared module that both import from.
|
|
275
|
+
|
|
276
|
+
## 10. Transaction Not Rolling Back
|
|
277
|
+
|
|
278
|
+
**Problem:** Errors occur but database changes are still persisted.
|
|
279
|
+
|
|
280
|
+
**Cause:** Transaction not properly wrapped in try-catch, or rollback not called on error.
|
|
281
|
+
|
|
282
|
+
**Solution:** Always wrap transactions in try-catch with explicit rollback:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// ❌ BAD - No error handling
|
|
286
|
+
const tx = await repo.beginTransaction();
|
|
287
|
+
await repo.create({ data, options: { transaction: tx } });
|
|
288
|
+
await tx.commit(); // If create fails, commit is never called but neither is rollback
|
|
289
|
+
|
|
290
|
+
// ✅ GOOD - Proper transaction handling
|
|
291
|
+
const tx = await repo.beginTransaction();
|
|
292
|
+
try {
|
|
293
|
+
await repo.create({ data, options: { transaction: tx } });
|
|
294
|
+
await otherRepo.update({ data: other, options: { transaction: tx } });
|
|
295
|
+
await tx.commit();
|
|
296
|
+
} catch (error) {
|
|
297
|
+
await tx.rollback();
|
|
298
|
+
throw error; // Re-throw to let caller handle
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## 11. Fire-and-Forget Promises Losing Context
|
|
303
|
+
|
|
304
|
+
**Problem:** `getCurrentUserId()` or other context-dependent functions return `null` in background tasks.
|
|
305
|
+
|
|
306
|
+
**Cause:** When you fire-and-forget a promise, it runs outside the original async context where the user was authenticated.
|
|
307
|
+
|
|
308
|
+
**Solution:** Pass required context data explicitly to background tasks:
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
// ❌ BAD - Context lost in fire-and-forget
|
|
312
|
+
@post({ configs: RouteConfigs.CREATE_ORDER })
|
|
313
|
+
async createOrder(c: Context) {
|
|
314
|
+
const data = c.req.valid('json');
|
|
315
|
+
const order = await this.orderService.create(data);
|
|
316
|
+
|
|
317
|
+
// Fire-and-forget: sendNotification runs outside request context
|
|
318
|
+
this.notificationService.sendOrderConfirmation(order.id);
|
|
319
|
+
// Inside sendOrderConfirmation, getCurrentUserId() returns null!
|
|
320
|
+
|
|
321
|
+
return c.json(order);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ✅ GOOD - Pass user ID explicitly
|
|
325
|
+
@post({ configs: RouteConfigs.CREATE_ORDER })
|
|
326
|
+
async createOrder(c: Context) {
|
|
327
|
+
const data = c.req.valid('json');
|
|
328
|
+
const userId = c.get(Authentication.AUDIT_USER_ID);
|
|
329
|
+
const order = await this.orderService.create(data);
|
|
330
|
+
|
|
331
|
+
// Pass userId explicitly to background task
|
|
332
|
+
this.notificationService.sendOrderConfirmation(order.id, userId);
|
|
333
|
+
|
|
334
|
+
return c.json(order);
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
> [!WARNING]
|
|
339
|
+
> This is especially important when using `allowAnonymous: false` in user audit columns. The enricher will throw an error if it cannot find the user context.
|
|
340
|
+
|
|
341
|
+
## 12. Incorrect Relation Configuration
|
|
342
|
+
|
|
343
|
+
**Problem:** Relations return empty arrays or `null` unexpectedly.
|
|
344
|
+
|
|
345
|
+
**Cause:** Mismatch between `fields` and `references` in relation metadata.
|
|
346
|
+
|
|
347
|
+
**Solution:** Double-check that foreign keys point to the correct columns:
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
// ❌ BAD - fields and references swapped
|
|
351
|
+
static override relations = (): TRelationConfig[] => [
|
|
352
|
+
{
|
|
353
|
+
name: 'posts',
|
|
354
|
+
type: RelationTypes.MANY,
|
|
355
|
+
schema: Post.schema,
|
|
356
|
+
metadata: {
|
|
357
|
+
fields: [Post.schema.authorId], // Wrong! This should be User.schema.id
|
|
358
|
+
references: [User.schema.id], // Wrong! This should be Post.schema.authorId
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
];
|
|
362
|
+
|
|
363
|
+
// ✅ GOOD - Correct configuration
|
|
364
|
+
// "User has many Posts where User.id = Post.authorId"
|
|
365
|
+
static override relations = (): TRelationConfig[] => [
|
|
366
|
+
{
|
|
367
|
+
name: 'posts',
|
|
368
|
+
type: RelationTypes.MANY,
|
|
369
|
+
schema: Post.schema,
|
|
370
|
+
metadata: {
|
|
371
|
+
fields: [User.schema.id], // Parent's key
|
|
372
|
+
references: [Post.schema.authorId], // Child's foreign key
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
];
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**Rule of thumb:** `fields` is the key on the current entity, `references` is the key on the related entity.
|
|
379
|
+
|
|
380
|
+
## 13. Overwriting Data with Partial Updates
|
|
381
|
+
|
|
382
|
+
**Problem:** PATCH endpoint replaces entire record instead of merging fields.
|
|
383
|
+
|
|
384
|
+
**Cause:** Using `create()` or full `update()` instead of partial update methods.
|
|
385
|
+
|
|
386
|
+
**Solution:** Use `updateById()` which only updates provided fields:
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
// ❌ BAD - Overwrites all fields (if using raw insert/update)
|
|
390
|
+
await db.update(users).set(data).where(eq(users.id, id));
|
|
391
|
+
// If data = { name: 'New' }, email and other fields might be set to undefined
|
|
392
|
+
|
|
393
|
+
// ✅ GOOD - Repository updateById only updates provided fields
|
|
394
|
+
await userRepository.updateById({
|
|
395
|
+
id: userId,
|
|
396
|
+
data: { name: 'New Name' }, // Only updates 'name', leaves other fields intact
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## See Also
|
|
401
|
+
|
|
402
|
+
- [Troubleshooting Tips](./troubleshooting-tips) - Debug common issues
|
|
403
|
+
- [Error Handling](./error-handling) - Proper error handling patterns
|
|
404
|
+
- [Architecture Decisions](./architecture-decisions) - Avoid design mistakes
|
|
@@ -125,7 +125,7 @@ git checkout -b feature/your-feature-name
|
|
|
125
125
|
### Step 2: Make Changes
|
|
126
126
|
|
|
127
127
|
**Checklist:**
|
|
128
|
-
- ✅ Follow [Code Style Standards](./code-style-standards
|
|
128
|
+
- ✅ Follow [Code Style Standards](./code-style-standards/)
|
|
129
129
|
- ✅ Follow [Architectural Patterns](./architectural-patterns.md)
|
|
130
130
|
- ✅ Add tests for new features/fixes
|
|
131
131
|
- ✅ Update docs in `packages/docs/wiki` if needed
|
|
@@ -38,7 +38,7 @@ Instead of manually defining common columns like primary keys, timestamps, or au
|
|
|
38
38
|
| `generateIdColumnDefs` | Adds a Primary Key | `id` (text, number, or big-number) |
|
|
39
39
|
| `generatePrincipalColumnDefs` | Adds polymorphic relation fields | `{discriminator}Id`, `{discriminator}Type` |
|
|
40
40
|
| `generateTzColumnDefs` | Adds timestamps | `createdAt`, `modifiedAt` (auto-updating) |
|
|
41
|
-
| `generateUserAuditColumnDefs` | Adds audit fields | `createdBy`, `modifiedBy` |
|
|
41
|
+
| `generateUserAuditColumnDefs` | Adds audit fields | `createdBy`, `modifiedBy` (supports `allowAnonymous` option) |
|
|
42
42
|
| `generateDataTypeColumnDefs` | Adds generic value fields | `nValue` (number), `tValue` (text), `jValue` (json), etc. |
|
|
43
43
|
| `extraUserColumns` | Comprehensive user fields | Combines audit, timestamps, status, and type fields |
|
|
44
44
|
|
|
@@ -373,4 +373,104 @@ export class User extends BaseEntity<typeof User.schema> {
|
|
|
373
373
|
- Hidden properties are **recursively excluded** from included relations
|
|
374
374
|
- Use the connector directly when you need to access hidden data (e.g., password verification)
|
|
375
375
|
|
|
376
|
-
> **Reference:** See [Hidden Properties](../references/base/models.md#hidden-properties) for complete documentation.
|
|
376
|
+
> **Reference:** See [Hidden Properties](../references/base/models.md#hidden-properties) for complete documentation.
|
|
377
|
+
|
|
378
|
+
## 6. Database Migrations
|
|
379
|
+
|
|
380
|
+
Drizzle Kit handles schema migrations. Follow these best practices for safe migrations.
|
|
381
|
+
|
|
382
|
+
### Generate Migrations
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
# Generate migration from schema changes
|
|
386
|
+
bun run db:generate
|
|
387
|
+
|
|
388
|
+
# Apply migrations to database
|
|
389
|
+
bun run db:migrate
|
|
390
|
+
|
|
391
|
+
# Push schema directly (development only)
|
|
392
|
+
bun run db:push
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Migration Best Practices
|
|
396
|
+
|
|
397
|
+
| Practice | Description |
|
|
398
|
+
|----------|-------------|
|
|
399
|
+
| **One change per migration** | Keep migrations focused and reversible |
|
|
400
|
+
| **Never edit applied migrations** | Create new migration instead |
|
|
401
|
+
| **Test on staging first** | Always test migrations before production |
|
|
402
|
+
| **Backup before migrate** | `pg_dump` before running in production |
|
|
403
|
+
| **Use transactions** | Drizzle wraps migrations in transactions by default |
|
|
404
|
+
|
|
405
|
+
### Safe Schema Changes
|
|
406
|
+
|
|
407
|
+
**Adding columns (safe):**
|
|
408
|
+
```typescript
|
|
409
|
+
// Add with default value to avoid nulls in existing rows
|
|
410
|
+
newField: text('new_field').default(''),
|
|
411
|
+
|
|
412
|
+
// Or allow null initially, then backfill and set notNull
|
|
413
|
+
newField: text('new_field'), // Initially nullable
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Renaming columns (requires care):**
|
|
417
|
+
```sql
|
|
418
|
+
-- In custom migration SQL
|
|
419
|
+
ALTER TABLE "User" RENAME COLUMN "old_name" TO "new_name";
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Dropping columns (dangerous):**
|
|
423
|
+
```typescript
|
|
424
|
+
// 1. First, remove all code references
|
|
425
|
+
// 2. Deploy code changes
|
|
426
|
+
// 3. Then drop in separate migration
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Custom Migration SQL
|
|
430
|
+
|
|
431
|
+
For complex migrations, use custom SQL:
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
// drizzle/migrations/0005_custom_migration.ts
|
|
435
|
+
import { sql } from 'drizzle-orm';
|
|
436
|
+
|
|
437
|
+
export async function up(db: DrizzleDB) {
|
|
438
|
+
// Add index for performance
|
|
439
|
+
await db.execute(sql`
|
|
440
|
+
CREATE INDEX CONCURRENTLY idx_user_email
|
|
441
|
+
ON "User" (email)
|
|
442
|
+
WHERE status = 'ACTIVE'
|
|
443
|
+
`);
|
|
444
|
+
|
|
445
|
+
// Backfill data
|
|
446
|
+
await db.execute(sql`
|
|
447
|
+
UPDATE "User"
|
|
448
|
+
SET normalized_email = LOWER(email)
|
|
449
|
+
WHERE normalized_email IS NULL
|
|
450
|
+
`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export async function down(db: DrizzleDB) {
|
|
454
|
+
await db.execute(sql`DROP INDEX IF EXISTS idx_user_email`);
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Migration Checklist
|
|
459
|
+
|
|
460
|
+
| Step | Action |
|
|
461
|
+
|------|--------|
|
|
462
|
+
| 1 | Review generated SQL before applying |
|
|
463
|
+
| 2 | Test migration on staging database |
|
|
464
|
+
| 3 | Backup production database |
|
|
465
|
+
| 4 | Run during low-traffic period |
|
|
466
|
+
| 5 | Monitor for errors after migration |
|
|
467
|
+
| 6 | Have rollback plan ready |
|
|
468
|
+
|
|
469
|
+
> [!WARNING]
|
|
470
|
+
> Never run migrations directly on production without testing. Use staging environments that mirror production data structure.
|
|
471
|
+
|
|
472
|
+
## See Also
|
|
473
|
+
|
|
474
|
+
- [API Usage Examples](./api-usage-examples) - Query patterns
|
|
475
|
+
- [Performance Optimization](./performance-optimization) - Index design
|
|
476
|
+
- [Common Pitfalls](./common-pitfalls) - Migration mistakes to avoid
|