@venizia/ignis-docs 0.0.4-1 → 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.
Files changed (38) hide show
  1. package/package.json +1 -1
  2. package/wiki/best-practices/api-usage-examples.md +1 -0
  3. package/wiki/best-practices/code-style-standards/advanced-patterns.md +259 -0
  4. package/wiki/best-practices/code-style-standards/constants-configuration.md +225 -0
  5. package/wiki/best-practices/code-style-standards/control-flow.md +245 -0
  6. package/wiki/best-practices/code-style-standards/documentation.md +221 -0
  7. package/wiki/best-practices/code-style-standards/function-patterns.md +142 -0
  8. package/wiki/best-practices/code-style-standards/index.md +110 -0
  9. package/wiki/best-practices/code-style-standards/naming-conventions.md +174 -0
  10. package/wiki/best-practices/code-style-standards/route-definitions.md +150 -0
  11. package/wiki/best-practices/code-style-standards/tooling.md +155 -0
  12. package/wiki/best-practices/code-style-standards/type-safety.md +165 -0
  13. package/wiki/best-practices/common-pitfalls.md +164 -3
  14. package/wiki/best-practices/contribution-workflow.md +1 -1
  15. package/wiki/best-practices/data-modeling.md +102 -2
  16. package/wiki/best-practices/error-handling.md +468 -0
  17. package/wiki/best-practices/index.md +204 -21
  18. package/wiki/best-practices/performance-optimization.md +180 -0
  19. package/wiki/best-practices/security-guidelines.md +249 -0
  20. package/wiki/best-practices/testing-strategies.md +620 -0
  21. package/wiki/changelogs/2026-01-05-range-queries-content-range.md +184 -0
  22. package/wiki/changelogs/2026-01-06-basic-authentication.md +103 -0
  23. package/wiki/changelogs/2026-01-07-controller-route-customization.md +209 -0
  24. package/wiki/changelogs/index.md +3 -0
  25. package/wiki/guides/core-concepts/components-guide.md +1 -1
  26. package/wiki/guides/core-concepts/persistent/models.md +10 -0
  27. package/wiki/guides/tutorials/complete-installation.md +1 -1
  28. package/wiki/guides/tutorials/testing.md +1 -1
  29. package/wiki/references/base/components.md +47 -29
  30. package/wiki/references/base/controllers.md +215 -18
  31. package/wiki/references/base/filter-system/fields-order-pagination.md +84 -0
  32. package/wiki/references/base/middlewares.md +33 -1
  33. package/wiki/references/base/models.md +40 -2
  34. package/wiki/references/base/repositories/index.md +2 -0
  35. package/wiki/references/components/authentication.md +261 -247
  36. package/wiki/references/helpers/index.md +1 -1
  37. package/wiki/references/src-details/core.md +1 -1
  38. package/wiki/best-practices/code-style-standards.md +0 -1193
@@ -194,3 +194,183 @@ try {
194
194
 
195
195
  > [!WARNING]
196
196
  > Higher isolation levels reduce concurrency. Use `READ COMMITTED` unless you have specific consistency requirements.
197
+
198
+ ## 7. Database Connection Pooling
199
+
200
+ Connection pooling significantly improves performance by reusing database connections instead of creating new ones for each request.
201
+
202
+ **Configure in DataSource:**
203
+
204
+ ```typescript
205
+ import { Pool } from 'pg';
206
+ import { drizzle } from 'drizzle-orm/node-postgres';
207
+
208
+ export class PostgresDataSource extends AbstractDataSource {
209
+ override connect(): void {
210
+ const pool = new Pool({
211
+ host: this.settings.host,
212
+ port: this.settings.port,
213
+ user: this.settings.username,
214
+ password: this.settings.password,
215
+ database: this.settings.database,
216
+
217
+ // Connection pool settings
218
+ max: 20, // Maximum connections in pool
219
+ min: 5, // Minimum connections to maintain
220
+ idleTimeoutMillis: 30000, // Close idle connections after 30s
221
+ connectionTimeoutMillis: 5000, // Fail if can't connect in 5s
222
+ maxUses: 7500, // Close connection after 7500 queries
223
+ });
224
+
225
+ this.connector = drizzle({ client: pool, schema: this.schema });
226
+ }
227
+ }
228
+ ```
229
+
230
+ **Recommended Pool Sizes:**
231
+
232
+ | Server RAM | Concurrent Users | Max Pool Size | Min Pool Size |
233
+ |------------|------------------|---------------|---------------|
234
+ | < 2GB | < 100 | 10 | 2 |
235
+ | 2-4GB | 100-500 | 20 | 5 |
236
+ | 4-8GB | 500-1000 | 30 | 10 |
237
+ | > 8GB | > 1000 | 50+ | 15 |
238
+
239
+ **Formula:** `max_connections = (number_of_cores * 2) + effective_spindle_count`
240
+
241
+ For most applications: `max_connections = CPU_cores * 2 + 1`
242
+
243
+ **Monitoring Pool Health:**
244
+
245
+ ```typescript
246
+ // Log pool statistics periodically
247
+ const pool = new Pool({ /* ... */ });
248
+
249
+ setInterval(() => {
250
+ this.logger.info('[pool] Stats | total: %d | idle: %d | waiting: %d',
251
+ pool.totalCount,
252
+ pool.idleCount,
253
+ pool.waitingCount
254
+ );
255
+ }, 60000); // Every minute
256
+ ```
257
+
258
+ **Warning Signs:**
259
+ - `waitingCount > 0` consistently → Increase `max`
260
+ - `idleCount === totalCount` always → Decrease `max`
261
+ - Connection timeouts → Check network, increase `connectionTimeoutMillis`
262
+
263
+ ## 8. Query Optimization Tips
264
+
265
+ ### Use Indexes Strategically
266
+
267
+ ```typescript
268
+ // Create indexes on frequently queried columns
269
+ export const User = pgTable('User', {
270
+ id: text('id').primaryKey(),
271
+ email: text('email').notNull().unique(), // Implicit unique index
272
+ status: text('status').notNull(),
273
+ createdAt: timestamp('created_at').notNull(),
274
+ }, (table) => ({
275
+ // Composite index for common query patterns
276
+ statusCreatedIdx: index('idx_user_status_created').on(table.status, table.createdAt),
277
+ // Partial index for active users only
278
+ activeEmailIdx: index('idx_active_email').on(table.email).where(eq(table.status, 'ACTIVE')),
279
+ }));
280
+ ```
281
+
282
+ ### Avoid N+1 Queries
283
+
284
+ ```typescript
285
+ // ❌ BAD - N+1 queries
286
+ const users = await userRepo.find({ filter: { limit: 100 } });
287
+ for (const user of users.data) {
288
+ user.posts = await postRepo.find({ filter: { where: { authorId: user.id } } });
289
+ }
290
+
291
+ // ✅ GOOD - Single query with relations
292
+ const users = await userRepo.find({
293
+ filter: {
294
+ limit: 100,
295
+ include: [{ relation: 'posts' }],
296
+ },
297
+ });
298
+ ```
299
+
300
+ ### Batch Operations
301
+
302
+ ```typescript
303
+ // ❌ BAD - Many individual inserts
304
+ for (const item of items) {
305
+ await repo.create({ data: item });
306
+ }
307
+
308
+ // ✅ GOOD - Batch insert
309
+ await repo.createMany({ data: items });
310
+ ```
311
+
312
+ ## 9. Memory Management
313
+
314
+ ### Stream Large Datasets
315
+
316
+ ```typescript
317
+ // ❌ BAD - Load all records into memory
318
+ const allUsers = await userRepo.find({ filter: { limit: 100000 } });
319
+
320
+ // ✅ GOOD - Process in batches
321
+ const batchSize = 1000;
322
+ let offset = 0;
323
+ let hasMore = true;
324
+
325
+ while (hasMore) {
326
+ const batch = await userRepo.find({
327
+ filter: { limit: batchSize, offset },
328
+ });
329
+
330
+ for (const user of batch.data) {
331
+ await processUser(user);
332
+ }
333
+
334
+ hasMore = batch.data.length === batchSize;
335
+ offset += batchSize;
336
+ }
337
+ ```
338
+
339
+ ### Avoid Memory Leaks in Long-Running Processes
340
+
341
+ ```typescript
342
+ // ❌ BAD - Growing array in long-running process
343
+ const processedIds: string[] = [];
344
+ // This array grows forever!
345
+
346
+ // ✅ GOOD - Use Set with cleanup or external storage
347
+ const processedIds = new Set<string>();
348
+
349
+ // Periodically clear or use Redis
350
+ setInterval(() => {
351
+ if (processedIds.size > 10000) {
352
+ processedIds.clear();
353
+ }
354
+ }, 3600000); // Every hour
355
+ ```
356
+
357
+ ## Performance Checklist
358
+
359
+ | Category | Check | Impact |
360
+ |----------|-------|--------|
361
+ | **Database** | Connection pooling configured | High |
362
+ | **Database** | Indexes on WHERE/JOIN columns | High |
363
+ | **Database** | Limit on all queries | High |
364
+ | **Queries** | Using `fields` to select specific columns | Medium |
365
+ | **Queries** | Relations limited to 2 levels | Medium |
366
+ | **Queries** | Batch operations for bulk data | High |
367
+ | **Memory** | Large datasets processed in batches | High |
368
+ | **Caching** | Expensive queries cached | High |
369
+ | **Workers** | CPU-intensive tasks offloaded | High |
370
+ | **Monitoring** | Performance logging enabled | Low |
371
+
372
+ ## See Also
373
+
374
+ - [Data Modeling](./data-modeling) - Schema design for performance
375
+ - [Deployment Strategies](./deployment-strategies) - Production scaling
376
+ - [Common Pitfalls](./common-pitfalls) - Performance mistakes to avoid
@@ -216,3 +216,252 @@ bun update
216
216
  - `jose` - JWT handling
217
217
  - `drizzle-orm` - Database ORM
218
218
  - `@venizia/ignis` - Framework core
219
+
220
+ ## 7. CORS Configuration
221
+
222
+ Configure Cross-Origin Resource Sharing to control which domains can access your API.
223
+
224
+ **Default (Development):**
225
+ ```typescript
226
+ import { cors } from 'hono/cors';
227
+
228
+ // Allow all origins (ONLY for development)
229
+ this.server.use('*', cors());
230
+ ```
231
+
232
+ **Production (Restrictive):**
233
+ ```typescript
234
+ import { cors } from 'hono/cors';
235
+
236
+ this.server.use('/api/*', cors({
237
+ origin: ['https://yourdomain.com', 'https://app.yourdomain.com'],
238
+ allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
239
+ allowHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
240
+ exposeHeaders: ['X-Request-ID'],
241
+ credentials: true,
242
+ maxAge: 86400, // Cache preflight for 24 hours
243
+ }));
244
+ ```
245
+
246
+ **Dynamic Origin Validation:**
247
+ ```typescript
248
+ this.server.use('/api/*', cors({
249
+ origin: (origin) => {
250
+ const allowedDomains = ['yourdomain.com', 'yourdomain.io'];
251
+ try {
252
+ const url = new URL(origin);
253
+ return allowedDomains.some(domain => url.hostname.endsWith(domain))
254
+ ? origin
255
+ : null;
256
+ } catch {
257
+ return null;
258
+ }
259
+ },
260
+ }));
261
+ ```
262
+
263
+ > [!WARNING]
264
+ > Never use `origin: '*'` with `credentials: true` in production. This is a security vulnerability.
265
+
266
+ ## 8. Rate Limiting
267
+
268
+ Protect against brute force attacks and denial of service.
269
+
270
+ **Basic Rate Limiter:**
271
+ ```typescript
272
+ import { createMiddleware } from 'hono/factory';
273
+
274
+ const rateLimiter = (opts: { windowMs: number; max: number }) => {
275
+ const requests = new Map<string, { count: number; resetAt: number }>();
276
+
277
+ return createMiddleware(async (c, next) => {
278
+ const ip = c.req.header('x-forwarded-for') ?? c.req.header('x-real-ip') ?? 'unknown';
279
+ const now = Date.now();
280
+ const record = requests.get(ip);
281
+
282
+ if (!record || now > record.resetAt) {
283
+ requests.set(ip, { count: 1, resetAt: now + opts.windowMs });
284
+ } else if (record.count >= opts.max) {
285
+ return c.json({
286
+ statusCode: 429,
287
+ message: 'Too many requests. Please try again later.',
288
+ }, 429);
289
+ } else {
290
+ record.count++;
291
+ }
292
+
293
+ await next();
294
+ });
295
+ };
296
+
297
+ // Apply to sensitive endpoints
298
+ this.server.use('/api/auth/login', rateLimiter({ windowMs: 15 * 60 * 1000, max: 5 }));
299
+ this.server.use('/api/auth/register', rateLimiter({ windowMs: 60 * 60 * 1000, max: 10 }));
300
+ this.server.use('/api/*', rateLimiter({ windowMs: 60 * 1000, max: 100 }));
301
+ ```
302
+
303
+ **Recommended Limits:**
304
+
305
+ | Endpoint | Window | Max Requests | Reason |
306
+ |----------|--------|--------------|--------|
307
+ | `/auth/login` | 15 min | 5 | Prevent brute force |
308
+ | `/auth/register` | 1 hour | 10 | Prevent spam accounts |
309
+ | `/auth/forgot-password` | 1 hour | 3 | Prevent email flooding |
310
+ | `/api/*` (general) | 1 min | 100 | General protection |
311
+
312
+ **Production Recommendation:** Use Redis-backed rate limiting for distributed deployments:
313
+
314
+ ```typescript
315
+ import { RedisHelper } from '@venizia/ignis-helpers';
316
+
317
+ // Rate limiter with Redis for multi-instance deployments
318
+ const distributedRateLimiter = async (key: string, max: number, windowSec: number) => {
319
+ const redis = RedisHelper.getClient();
320
+ const current = await redis.incr(key);
321
+ if (current === 1) {
322
+ await redis.expire(key, windowSec);
323
+ }
324
+ return current <= max;
325
+ };
326
+ ```
327
+
328
+ ## 9. SQL Injection Prevention
329
+
330
+ Drizzle ORM automatically parameterizes queries, protecting against SQL injection. However, **raw queries require care**.
331
+
332
+ **Safe (Parameterized):**
333
+ ```typescript
334
+ // ✅ Repository methods are safe - queries are parameterized
335
+ await userRepository.find({
336
+ filter: { where: { email: userInput } },
337
+ });
338
+
339
+ // ✅ Drizzle query builder is safe
340
+ await db.select().from(users).where(eq(users.email, userInput));
341
+
342
+ // ✅ sql`` template with placeholders is safe
343
+ await db.execute(sql`SELECT * FROM users WHERE email = ${userInput}`);
344
+ ```
345
+
346
+ **Dangerous (String Interpolation):**
347
+ ```typescript
348
+ // ❌ NEVER use string interpolation in raw SQL
349
+ const query = `SELECT * FROM users WHERE email = '${userInput}'`;
350
+ await db.execute(sql.raw(query)); // Vulnerable to SQL injection!
351
+
352
+ // ❌ NEVER build WHERE clauses with string concatenation
353
+ const condition = `status = '${status}' AND role = '${role}'`;
354
+ ```
355
+
356
+ **If You Must Use Dynamic SQL:**
357
+ ```typescript
358
+ // Use parameterized queries with sql.raw only for table/column names
359
+ const tableName = allowedTables.includes(input) ? input : 'default_table';
360
+ await db.execute(sql`SELECT * FROM ${sql.identifier(tableName)} WHERE id = ${id}`);
361
+ ```
362
+
363
+ ## 10. Security Headers
364
+
365
+ Add security headers to protect against common attacks:
366
+
367
+ ```typescript
368
+ import { secureHeaders } from 'hono/secure-headers';
369
+
370
+ // Add security headers to all responses
371
+ this.server.use('*', secureHeaders({
372
+ // Prevent clickjacking
373
+ xFrameOptions: 'DENY',
374
+ // Prevent MIME type sniffing
375
+ xContentTypeOptions: 'nosniff',
376
+ // Enable XSS filter
377
+ xXssProtection: '1; mode=block',
378
+ // Control referrer information
379
+ referrerPolicy: 'strict-origin-when-cross-origin',
380
+ // Content Security Policy
381
+ contentSecurityPolicy: {
382
+ defaultSrc: ["'self'"],
383
+ scriptSrc: ["'self'"],
384
+ styleSrc: ["'self'", "'unsafe-inline'"],
385
+ imgSrc: ["'self'", 'data:', 'https:'],
386
+ },
387
+ }));
388
+ ```
389
+
390
+ ## 11. Request Size Limits
391
+
392
+ Prevent denial of service through large payloads:
393
+
394
+ ```typescript
395
+ import { bodyLimit } from 'hono/body-limit';
396
+
397
+ // Limit request body size
398
+ this.server.use('/api/*', bodyLimit({
399
+ maxSize: 1024 * 1024, // 1MB for general API
400
+ onError: (c) => c.json({ message: 'Request body too large' }, 413),
401
+ }));
402
+
403
+ // Allow larger uploads for file endpoints
404
+ this.server.use('/api/upload/*', bodyLimit({
405
+ maxSize: 50 * 1024 * 1024, // 50MB for file uploads
406
+ }));
407
+ ```
408
+
409
+ ## 12. Logging Security Events
410
+
411
+ Log security-relevant events for monitoring and forensics:
412
+
413
+ ```typescript
414
+ import { BaseService } from '@venizia/ignis';
415
+
416
+ export class AuthService extends BaseService {
417
+ async login(email: string, password: string, context: Context) {
418
+ const ip = context.req.header('x-forwarded-for') ?? 'unknown';
419
+ const userAgent = context.req.header('user-agent') ?? 'unknown';
420
+
421
+ const user = await this.userRepo.findByEmail(email);
422
+
423
+ if (!user || !await this.verifyPassword(password, user.password)) {
424
+ // Log failed attempt
425
+ this.logger.warn('[login] Failed login attempt | email: %s | ip: %s | userAgent: %s',
426
+ email, ip, userAgent);
427
+ throw getError({ statusCode: 401, message: 'Invalid credentials' });
428
+ }
429
+
430
+ // Log successful login
431
+ this.logger.info('[login] Successful login | userId: %s | ip: %s', user.id, ip);
432
+
433
+ return this.generateToken(user);
434
+ }
435
+ }
436
+ ```
437
+
438
+ **Events to Log:**
439
+ - Failed login attempts
440
+ - Successful logins
441
+ - Password changes
442
+ - Permission changes
443
+ - Suspicious activity (rate limit hits, invalid tokens)
444
+ - Admin actions
445
+
446
+ ## Security Checklist
447
+
448
+ Before deploying to production, verify:
449
+
450
+ | Category | Check |
451
+ |----------|-------|
452
+ | **Secrets** | All secrets in environment variables, not in code |
453
+ | **Auth** | JWT tokens have reasonable expiration (15min - 24h) |
454
+ | **Input** | All user input validated with Zod schemas |
455
+ | **CORS** | Specific origins configured, not `*` |
456
+ | **Rate Limiting** | Applied to auth endpoints and general API |
457
+ | **Headers** | Security headers configured |
458
+ | **Logging** | Security events logged for monitoring |
459
+ | **Dependencies** | No known vulnerabilities (`bun audit`) |
460
+ | **HTTPS** | TLS configured for production |
461
+ | **Hidden Data** | Sensitive fields use `hiddenProperties` |
462
+
463
+ ## See Also
464
+
465
+ - [Authentication Component](../references/components/authentication) - JWT setup and configuration
466
+ - [Common Pitfalls](./common-pitfalls) - Security-related mistakes to avoid
467
+ - [Deployment Strategies](./deployment-strategies) - Secure deployment practices