@venizia/ignis-docs 0.2.0 → 0.2.1-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +24 -13
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +8 -4
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +247 -153
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +109 -323
  26. package/content/extensions/components/authentication/api.md +489 -602
  27. package/content/extensions/components/authentication/errors.md +135 -498
  28. package/content/extensions/components/authentication/index.md +89 -801
  29. package/content/extensions/components/authentication/usage.md +231 -955
  30. package/content/extensions/components/authorization/api.md +991 -644
  31. package/content/extensions/components/authorization/errors.md +204 -208
  32. package/content/extensions/components/authorization/getting-started.md +227 -0
  33. package/content/extensions/components/authorization/index.md +88 -795
  34. package/content/extensions/components/authorization/usage.md +219 -528
  35. package/content/extensions/components/health-check.md +77 -243
  36. package/content/extensions/components/index.md +24 -90
  37. package/content/extensions/components/mail/api.md +553 -285
  38. package/content/extensions/components/mail/errors.md +76 -62
  39. package/content/extensions/components/mail/index.md +111 -463
  40. package/content/extensions/components/mail/usage.md +139 -173
  41. package/content/extensions/components/request-tracker.md +70 -173
  42. package/content/extensions/components/socket-io/api.md +462 -785
  43. package/content/extensions/components/socket-io/errors.md +49 -51
  44. package/content/extensions/components/socket-io/index.md +69 -372
  45. package/content/extensions/components/socket-io/usage.md +212 -105
  46. package/content/extensions/components/static-asset/api.md +461 -141
  47. package/content/extensions/components/static-asset/errors.md +121 -53
  48. package/content/extensions/components/static-asset/index.md +84 -606
  49. package/content/extensions/components/static-asset/usage.md +184 -299
  50. package/content/extensions/components/template/index.md +3 -3
  51. package/content/extensions/components/websocket/api.md +311 -404
  52. package/content/extensions/components/websocket/errors.md +47 -56
  53. package/content/extensions/components/websocket/index.md +75 -407
  54. package/content/extensions/components/websocket/usage.md +120 -338
  55. package/content/extensions/helpers/cron/index.md +52 -160
  56. package/content/extensions/helpers/crypto/index.md +67 -480
  57. package/content/extensions/helpers/crypto/reference.md +528 -0
  58. package/content/extensions/helpers/env/index.md +64 -178
  59. package/content/extensions/helpers/error/index.md +286 -196
  60. package/content/extensions/helpers/index.md +61 -47
  61. package/content/extensions/helpers/inversion/index.md +76 -550
  62. package/content/extensions/helpers/inversion/reference.md +530 -0
  63. package/content/extensions/helpers/kafka/admin.md +23 -3
  64. package/content/extensions/helpers/kafka/compile-binary.md +83 -52
  65. package/content/extensions/helpers/kafka/consumer.md +65 -28
  66. package/content/extensions/helpers/kafka/examples.md +46 -253
  67. package/content/extensions/helpers/kafka/index.md +55 -617
  68. package/content/extensions/helpers/kafka/producer.md +156 -22
  69. package/content/extensions/helpers/kafka/schema-registry.md +59 -79
  70. package/content/extensions/helpers/logger/hf-logger.md +220 -0
  71. package/content/extensions/helpers/logger/index.md +78 -552
  72. package/content/extensions/helpers/logger/pino.md +105 -0
  73. package/content/extensions/helpers/logger/reference.md +937 -0
  74. package/content/extensions/helpers/network/api.md +276 -195
  75. package/content/extensions/helpers/network/index.md +87 -524
  76. package/content/extensions/helpers/queue/index.md +80 -897
  77. package/content/extensions/helpers/queue/reference.md +494 -0
  78. package/content/extensions/helpers/redis/index.md +86 -640
  79. package/content/extensions/helpers/redis/reference.md +784 -0
  80. package/content/extensions/helpers/secrets/index.md +136 -0
  81. package/content/extensions/helpers/socket-io/api.md +325 -204
  82. package/content/extensions/helpers/socket-io/index.md +79 -429
  83. package/content/extensions/helpers/storage/api.md +584 -464
  84. package/content/extensions/helpers/storage/index.md +80 -573
  85. package/content/extensions/helpers/types/index.md +75 -495
  86. package/content/extensions/helpers/types/reference.md +689 -0
  87. package/content/extensions/helpers/uid/index.md +176 -189
  88. package/content/extensions/helpers/websocket/api.md +366 -214
  89. package/content/extensions/helpers/websocket/index.md +68 -503
  90. package/content/extensions/helpers/worker-thread/index.md +62 -396
  91. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  92. package/content/extensions/index.md +38 -39
  93. package/content/extensions/src-details/mcp-server.md +96 -548
  94. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  95. package/content/guides/core-concepts/persistent/index.md +5 -1
  96. package/content/guides/core-concepts/persistent/models.md +1 -1
  97. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  98. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  99. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  100. package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
  101. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  102. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  103. package/content/guides/core-concepts/secrets-vault.md +177 -0
  104. package/content/guides/core-concepts/services.md +1 -1
  105. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  106. package/content/guides/get-started/philosophy.md +135 -670
  107. package/content/guides/get-started/setup.md +53 -74
  108. package/content/guides/migrations/redis-helpers-migration.md +5 -4
  109. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  110. package/content/guides/migrations/unified-connectors-migration.md +8 -7
  111. package/content/guides/tutorials/ecommerce-api.md +3 -8
  112. package/content/guides/tutorials/realtime-chat.md +1 -1
  113. package/content/references/base/application.md +64 -22
  114. package/content/references/base/components.md +3 -3
  115. package/content/references/base/connectors.md +79 -136
  116. package/content/references/base/controllers.md +12 -12
  117. package/content/references/base/datasources-reference.md +600 -0
  118. package/content/references/base/datasources.md +84 -444
  119. package/content/references/base/dependency-injection.md +25 -46
  120. package/content/references/base/filter-system/application-usage.md +95 -123
  121. package/content/references/base/filter-system/array-operators.md +33 -43
  122. package/content/references/base/filter-system/comparison-operators.md +55 -63
  123. package/content/references/base/filter-system/default-filter.md +144 -350
  124. package/content/references/base/filter-system/fields-order-pagination.md +116 -148
  125. package/content/references/base/filter-system/index.md +114 -253
  126. package/content/references/base/filter-system/json-filtering.md +52 -181
  127. package/content/references/base/filter-system/list-operators.md +28 -44
  128. package/content/references/base/filter-system/logical-operators.md +69 -114
  129. package/content/references/base/filter-system/null-operators.md +41 -98
  130. package/content/references/base/filter-system/pattern-matching.md +48 -51
  131. package/content/references/base/filter-system/quick-reference.md +93 -196
  132. package/content/references/base/filter-system/range-operators.md +22 -38
  133. package/content/references/base/filter-system/tips.md +70 -133
  134. package/content/references/base/filter-system/use-cases.md +173 -232
  135. package/content/references/base/grpc-controllers.md +53 -17
  136. package/content/references/base/index.md +5 -3
  137. package/content/references/base/middlewares.md +43 -28
  138. package/content/references/base/models-reference.md +886 -0
  139. package/content/references/base/models.md +81 -1452
  140. package/content/references/base/providers.md +8 -8
  141. package/content/references/base/repositories/advanced.md +281 -419
  142. package/content/references/base/repositories/index.md +84 -644
  143. package/content/references/base/repositories/mixins.md +25 -21
  144. package/content/references/base/repositories/relations.md +194 -375
  145. package/content/references/base/repositories/soft-deletable.md +67 -55
  146. package/content/references/base/secrets.md +267 -0
  147. package/content/references/base/services.md +8 -6
  148. package/content/references/configuration/environment-variables.md +73 -21
  149. package/content/references/configuration/index.md +51 -31
  150. package/content/references/index.md +1 -1
  151. package/content/references/quick-reference.md +3 -16
  152. package/content/references/utilities/crypto.md +35 -76
  153. package/content/references/utilities/date.md +33 -73
  154. package/content/references/utilities/duration.md +85 -0
  155. package/content/references/utilities/index.md +6 -2
  156. package/content/references/utilities/jsx-reference.md +298 -0
  157. package/content/references/utilities/jsx.md +82 -525
  158. package/content/references/utilities/module.md +81 -61
  159. package/content/references/utilities/parse.md +34 -64
  160. package/content/references/utilities/performance.md +33 -58
  161. package/content/references/utilities/promise.md +28 -62
  162. package/content/references/utilities/request.md +58 -218
  163. package/content/references/utilities/retry.md +139 -0
  164. package/content/references/utilities/schema.md +43 -137
  165. package/content/references/utilities/statuses-reference.md +361 -0
  166. package/content/references/utilities/statuses.md +63 -667
  167. package/dist/mcp-server/index.js +0 -0
  168. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  169. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  171. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  172. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  173. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  174. package/package.json +24 -23
@@ -3,22 +3,22 @@
3
3
  IGNIS streamlines data modeling with Drizzle ORM by providing powerful helpers and "enrichers" that reduce boilerplate code for common schema patterns.
4
4
 
5
5
  > [!NOTE] Scope: PostgreSQL connector
6
- > This page covers Drizzle-backed models (`BasePostgresEntity`; the legacy `BaseEntity` export is a compatibility alias for the same class) and enrichers, which are specific to relational tables. Search documents (typesense connector) use `defineSearchCollection` instead - see [Search & Typesense](/guides/core-concepts/persistent/search-typesense). See [Connectors](/references/base/connectors) for how the engine-neutral `AbstractEntity` relates to each connector's concrete entity class.
6
+ > This page covers Drizzle-backed models (`BaseRelationalEntity`; the legacy `BasePostgresEntity` and `BaseEntity` exports are compatibility aliases for the same class) and enrichers, which are specific to relational tables. Search documents (typesense connector) use `defineSearchCollection` instead - see [Search & Typesense](/guides/core-concepts/persistent/search-typesense). See [Connectors](/references/base/connectors) for how the engine-neutral `AbstractEntity` relates to each connector's concrete entity class.
7
7
 
8
8
  ## 1. Base Entity
9
9
 
10
- All PostgreSQL entity models should extend `BasePostgresEntity`. This provides integration with the framework's repository layer and automatic schema generation support.
10
+ All PostgreSQL entity models should extend `BaseRelationalEntity`. This provides integration with the framework's repository layer and automatic schema generation support.
11
11
 
12
12
  The recommended pattern is to define the schema and relations as **static properties** on the class. This keeps the definition self-contained and enables powerful type inference.
13
13
 
14
14
  **Example (`src/models/entities/user.model.ts`):**
15
15
 
16
16
  ```typescript
17
- import { BasePostgresEntity, extraUserColumns, generateIdColumnDefs, model } from '@venizia/ignis';
17
+ import { BaseRelationalEntity, extraUserColumns, generateIdColumnDefs, model } from '@venizia/ignis';
18
18
  import { pgTable } from 'drizzle-orm/pg-core';
19
19
 
20
20
  @model({ type: 'entity' })
21
- export class User extends BasePostgresEntity<typeof User.schema> {
21
+ export class User extends BaseRelationalEntity<typeof User.schema> {
22
22
  // 1. Define schema as a static property
23
23
  static override schema = pgTable('User', {
24
24
  ...generateIdColumnDefs({ id: { dataType: 'string' } }),
@@ -40,10 +40,10 @@ Instead of manually defining common columns like primary keys, timestamps, or au
40
40
  |----------|-------------|---------------|
41
41
  | `generateIdColumnDefs` | Adds a Primary Key | `id` (text, number, or big-number) |
42
42
  | `generatePrincipalColumnDefs` | Adds polymorphic relation fields | `{discriminator}Id`, `{discriminator}Type` |
43
- | `generateTzColumnDefs` | Adds timestamps | `createdAt`, `modifiedAt` (auto-updating) |
43
+ | `generateTzColumnDefs` | Adds timestamps | `createdAt`, `modifiedAt` (auto-updating); `deletedAt` via `{ deleted: { enable: true, ... } }` |
44
44
  | `generateUserAuditColumnDefs` | Adds audit fields | `createdBy`, `modifiedBy` (supports `allowAnonymous` option) |
45
- | `generateDataTypeColumnDefs` | Adds generic value fields | `nValue` (number), `tValue` (text), `jValue` (json), etc. |
46
- | `extraUserColumns` | Comprehensive user fields | Combines audit, timestamps, status, and type fields |
45
+ | `generateDataTypeColumnDefs` | Adds generic value fields | `dataType`, `nValue` (number), `tValue` (text), `bValue` (bytea), `jValue` (jsonb), `boValue` (boolean) |
46
+ | `extraUserColumns` | Auth user fields | `realm`, `status`, `type`, `activatedAt`, `lastLoginAt`, `parentId` |
47
47
 
48
48
  **Usage Example:**
49
49
 
@@ -90,8 +90,8 @@ The `generateIdColumnDefs` enricher supports multiple ID strategies:
90
90
  |-----------|-----------------|-----------------|----------|
91
91
  | `string` | `TEXT` | `string` | UUIDs, custom IDs, distributed systems |
92
92
  | `number` | `INTEGER GENERATED ALWAYS AS IDENTITY` | `number` | Auto-increment, simple sequences |
93
- | `big-number` (mode: `number`) | `BIGINT GENERATED ALWAYS AS IDENTITY` | `number` | Large sequences (up to 2^53) |
94
- | `big-number` (mode: `bigint`) | `BIGINT GENERATED ALWAYS AS IDENTITY` | `bigint` | Very large sequences (up to 2^64) |
93
+ | `big-number` (`numberMode: 'number'`, default) | `BIGINT GENERATED ALWAYS AS IDENTITY` | `number` | Large sequences (up to 2^53) |
94
+ | `big-number` (`numberMode: 'bigint'`) | `BIGINT GENERATED ALWAYS AS IDENTITY` | `bigint` | Very large sequences (up to 2^64) |
95
95
 
96
96
  **Examples:**
97
97
 
@@ -189,11 +189,11 @@ Relations are defined using the `TRelationConfig` structure within the static `r
189
189
 
190
190
  **One-to-One (belongsTo):**
191
191
  ```typescript
192
- import { BasePostgresEntity, model, RelationTypes, TRelationConfig } from '@venizia/ignis';
192
+ import { BaseRelationalEntity, model, RelationTypes, TRelationConfig } from '@venizia/ignis';
193
193
  import { User } from './user.model';
194
194
 
195
195
  @model({ type: 'entity' })
196
- export class Configuration extends BasePostgresEntity<typeof Configuration.schema> {
196
+ export class Configuration extends BaseRelationalEntity<typeof Configuration.schema> {
197
197
  static override schema = pgTable('Configuration', {
198
198
  ...generateIdColumnDefs({ id: { dataType: 'string' } }),
199
199
  createdBy: text('created_by'),
@@ -216,32 +216,45 @@ export class Configuration extends BasePostgresEntity<typeof Configuration.schem
216
216
  ```
217
217
 
218
218
  **One-to-Many (hasMany):**
219
+
220
+ The foreign key lives on the **`ONE` side only**. A `MANY` relation carries no `fields`/`references` - Drizzle's `many()` takes just `relationName`, pointing back at the `ONE` relation that owns the key.
221
+
219
222
  ```typescript
223
+ // Owning side: Post declares the FK
220
224
  @model({ type: 'entity' })
221
- export class User extends BasePostgresEntity<typeof User.schema> {
222
- static override schema = pgTable('User', {
225
+ export class Post extends BaseRelationalEntity<typeof Post.schema> {
226
+ static override schema = pgTable('Post', {
223
227
  ...generateIdColumnDefs({ id: { dataType: 'string' } }),
224
- name: text('name').notNull(),
228
+ authorId: text('author_id').notNull(),
225
229
  });
226
230
 
227
231
  static override relations = (): TRelationConfig[] => [
228
232
  {
229
- name: 'posts', // User.posts
230
- type: RelationTypes.MANY, // One User → Many Posts
231
- schema: Post.schema,
233
+ name: 'author',
234
+ type: RelationTypes.ONE,
235
+ schema: User.schema,
232
236
  metadata: {
233
- fields: [User.schema.id],
234
- references: [Post.schema.authorId],
237
+ fields: [Post.schema.authorId],
238
+ references: [User.schema.id],
235
239
  },
236
240
  },
241
+ ];
242
+ }
243
+
244
+ // Inverse side: User just names the relation back
245
+ @model({ type: 'entity' })
246
+ export class User extends BaseRelationalEntity<typeof User.schema> {
247
+ static override schema = pgTable('User', {
248
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
249
+ name: text('name').notNull(),
250
+ });
251
+
252
+ static override relations = (): TRelationConfig[] => [
237
253
  {
238
- name: 'comments', // User.comments
239
- type: RelationTypes.MANY,
240
- schema: Comment.schema,
241
- metadata: {
242
- fields: [User.schema.id],
243
- references: [Comment.schema.userId],
244
- },
254
+ name: 'posts', // User.posts
255
+ type: RelationTypes.MANY, // One User -> Many Posts
256
+ schema: Post.schema,
257
+ metadata: { relationName: 'author' }, // The ONE relation on Post
245
258
  },
246
259
  ];
247
260
  }
@@ -295,7 +308,7 @@ DataSources automatically discover their schema from the repositories that bind
295
308
  ```typescript
296
309
  // src/datasources/postgres.datasource.ts
297
310
  import { datasource, ValueOrPromise } from '@venizia/ignis';
298
- import { BasePostgresDataSource } from '@venizia/ignis/postgres';
311
+ import { BaseRelationalDataSource } from '@venizia/ignis/postgres';
299
312
  import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
300
313
  import { Pool } from 'pg';
301
314
 
@@ -308,7 +321,7 @@ interface IDataSourceConfigs {
308
321
  }
309
322
 
310
323
  @datasource({ driver: NodePostgresDriver })
311
- export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfigs> {
324
+ export class PostgresDataSource extends BaseRelationalDataSource<IDataSourceConfigs> {
312
325
  constructor() {
313
326
  super({
314
327
  name: PostgresDataSource.name,
@@ -343,7 +356,7 @@ For most repositories, you don't need a constructor. The DataSource is automatic
343
356
 
344
357
  ```typescript
345
358
  @repository({ model: Configuration, dataSource: PostgresDataSource })
346
- export class ConfigurationRepository extends DefaultCRUDRepository<typeof Configuration.schema> {
359
+ export class ConfigurationRepository extends DefaultRelationalRepository<typeof Configuration.schema> {
347
360
  // No constructor needed!
348
361
  }
349
362
  ```
@@ -380,7 +393,7 @@ Protect sensitive data by configuring properties that are excluded at the SQL le
380
393
  hiddenProperties: ['password', 'secret'],
381
394
  },
382
395
  })
383
- export class User extends BasePostgresEntity<typeof User.schema> {
396
+ export class User extends BaseRelationalEntity<typeof User.schema> {
384
397
  static override schema = pgTable('User', {
385
398
  ...generateIdColumnDefs({ id: { dataType: 'string' } }),
386
399
  email: text('email').notNull(),
@@ -411,7 +424,7 @@ Declare your model's authorization principal in `@model` settings to make the mo
411
424
  hiddenProperties: ['password'],
412
425
  },
413
426
  })
414
- export class User extends BasePostgresEntity<typeof User.schema> {
427
+ export class User extends BaseRelationalEntity<typeof User.schema> {
415
428
  static override schema = pgTable('User', {
416
429
  ...generateIdColumnDefs({ id: { dataType: 'string' } }),
417
430
  email: text('email').notNull(),
@@ -437,17 +450,27 @@ Drizzle Kit handles schema migrations. Follow these best practices for safe migr
437
450
 
438
451
  ### Generate Migrations
439
452
 
440
- ```bash
441
- # Generate migration from schema changes
442
- bun run db:generate
453
+ Wire these as scripts in your app's `package.json`, pointing at your Drizzle config:
443
454
 
444
- # Apply migrations to database
445
- bun run db:migrate
455
+ ```json
456
+ {
457
+ "scripts": {
458
+ "migrate:generate": "drizzle-kit generate --config=src/migration.ts",
459
+ "migrate:dev": "drizzle-kit migrate --config=src/migration.ts",
460
+ "migrate:push": "drizzle-kit push --config=src/migration.ts"
461
+ }
462
+ }
463
+ ```
446
464
 
447
- # Push schema directly (development only)
448
- bun run db:push
465
+ ```bash
466
+ bun run migrate:generate # Generate migration from schema changes
467
+ bun run migrate:dev # Apply pending migrations
468
+ bun run migrate:push # Push schema directly (development only)
449
469
  ```
450
470
 
471
+ > [!NOTE]
472
+ > Boot does nothing to your schema. Migrations are an explicit step you run - never a side effect of starting the application.
473
+
451
474
  ### Migration Best Practices
452
475
 
453
476
  | Practice | Description |
@@ -484,33 +507,26 @@ ALTER TABLE "User" RENAME COLUMN "old_name" TO "new_name";
484
507
 
485
508
  ### Custom Migration SQL
486
509
 
487
- For complex migrations, use custom SQL:
510
+ Drizzle Kit emits plain `.sql` files. For anything it cannot infer - backfills, concurrent indexes, data reshaping - edit the generated file or add your own:
488
511
 
489
- ```typescript
490
- // drizzle/migrations/0005_custom_migration.ts
491
- import { sql } from 'drizzle-orm';
492
-
493
- export async function up(db: DrizzleDB) {
494
- // Add index for performance
495
- await db.execute(sql`
496
- CREATE INDEX CONCURRENTLY idx_user_email
497
- ON "User" (email)
498
- WHERE status = 'ACTIVE'
499
- `);
500
-
501
- // Backfill data
502
- await db.execute(sql`
503
- UPDATE "User"
504
- SET normalized_email = LOWER(email)
505
- WHERE normalized_email IS NULL
506
- `);
507
- }
508
-
509
- export async function down(db: DrizzleDB) {
510
- await db.execute(sql`DROP INDEX IF EXISTS idx_user_email`);
511
- }
512
+ ```sql
513
+ -- drizzle/migrations/0005_custom_migration.sql
514
+
515
+ -- Backfill before enforcing the constraint
516
+ UPDATE "User"
517
+ SET normalized_email = LOWER(email)
518
+ WHERE normalized_email IS NULL;
519
+ --> statement-breakpoint
520
+
521
+ -- CONCURRENTLY cannot run inside a transaction - keep it in its own migration
522
+ CREATE INDEX CONCURRENTLY idx_user_email
523
+ ON "User" (email)
524
+ WHERE status = 'ACTIVE';
512
525
  ```
513
526
 
527
+ > [!WARNING]
528
+ > Drizzle wraps each migration in a transaction. `CREATE INDEX CONCURRENTLY` is rejected inside one - isolate it in its own migration and apply it out of band.
529
+
514
530
  ### Migration Checklist
515
531
 
516
532
  | Step | Action |
@@ -23,12 +23,17 @@ Use environment variables for all configuration - never hard-code.
23
23
  **Production Environment Variables:**
24
24
  | Variable | Value | Purpose |
25
25
  |----------|-------|---------|
26
- | `NODE_ENV` | `production` | Enables performance optimizations |
26
+ | `NODE_ENV` | `production` | Enables performance optimizations. Error responses are fail-closed: an unset or unrecognized value is treated as production |
27
27
  | `APP_ENV_APPLICATION_SECRET` | Strong random string | Application secret |
28
28
  | `APP_ENV_JWT_SECRET` | Strong random string | JWT signing key |
29
- | `APP_ENV_POSTGRES_*` | Production DB credentials | Database connection |
29
+ | `APP_ENV_POSTGRES_HOST` / `_PORT` / `_USERNAME` / `_PASSWORD` / `_DATABASE` | Production DB credentials | Database connection |
30
30
  | `APP_ENV_SERVER_HOST` | `0.0.0.0` | Accept connections from any IP |
31
- | `APP_ENV_SERVER_PORT` | `3000` or cloud-assigned | Server port |
31
+ | `APP_ENV_SERVER_PORT` | `3000` or cloud-assigned | Server port (`PORT` is also honoured) |
32
+ | `APP_ENV_LOGGER_LEVEL` | `info` | Logger floor - `debug` \| `info` \| `warn` \| `error` \| `emerg` |
33
+ | `APP_ENV_LOGGER_FOLDER_PATH` | Writable path | Opt-in rotating file output; unset means console only |
34
+
35
+ > [!WARNING]
36
+ > Leave `APP_ENV_LOGGER_DO_REDACT` unset in production. Setting it to `false` disables secret-key redaction in logs - a local-debugging switch only.
32
37
 
33
38
  **Where to store:**
34
39
  - **Docker:** Use environment variables in `docker-compose.yml` or secrets
@@ -40,27 +45,33 @@ Use environment variables for all configuration - never hard-code.
40
45
  ### Docker Deployment (Recommended)
41
46
 
42
47
  **Dockerfile:**
48
+ Two stages: the build needs devDependencies (`typescript`, `tsc-alias`), the runtime does not.
49
+
43
50
  ```dockerfile
44
- FROM oven/bun:1-slim
51
+ # --- Build stage ---
52
+ FROM oven/bun:1-slim AS builder
45
53
 
46
54
  WORKDIR /usr/src/app
47
55
 
48
- # Copy dependency files
49
56
  COPY package.json bun.lock ./
57
+ # All deps, including dev - `--production` would omit the compiler and the build would fail
58
+ RUN bun install --frozen-lockfile
50
59
 
51
- # Install production dependencies
52
- RUN bun install --production --frozen-lockfile
53
-
54
- # Copy source code
55
60
  COPY . .
56
-
57
- # Build TypeScript
58
61
  RUN bun run build
59
62
 
60
- # Expose port
63
+ # --- Runtime stage ---
64
+ FROM oven/bun:1-slim
65
+
66
+ WORKDIR /usr/src/app
67
+
68
+ COPY package.json bun.lock ./
69
+ RUN bun install --production --frozen-lockfile
70
+
71
+ COPY --from=builder /usr/src/app/dist ./dist
72
+
61
73
  EXPOSE 3000
62
74
 
63
- # Start server
64
75
  CMD [ "bun", "run", "server:prod" ]
65
76
  ```
66
77
 
@@ -161,13 +172,27 @@ docker-compose up -d --scale app=3
161
172
  Compile your app into a single standalone binary:
162
173
 
163
174
  ```bash
164
- bun build --compile --minify --target=bun-linux-x64 \
175
+ bun build --compile --minify --sourcemap --target=bun-linux-x64 \
165
176
  ./src/index.ts --outfile ./dist/my-app
166
177
  ```
167
178
 
168
179
  **Pros:** No dependencies needed on server, simple deployment
169
180
  **Cons:** Platform-specific, newer technology (test thoroughly)
170
181
 
182
+ > [!IMPORTANT]
183
+ > **A compiled binary MUST register a logger provider explicitly.** Logger providers are sub-path exports and `winston` is an optional peer, so the default provider is reached through a runtime `require` that the bundler cannot see. Without this line the binary throws on its first log call.
184
+ >
185
+ > ```typescript
186
+ > // src/index.ts - before the application boots
187
+ > import { LoggerFactory } from '@venizia/ignis-helpers';
188
+ > import { WinstonLogger } from '@venizia/ignis-helpers/winston';
189
+ > // or: import { PinoLogger } from '@venizia/ignis-helpers/pino';
190
+ >
191
+ > LoggerFactory.use({ provider: WinstonLogger });
192
+ > ```
193
+ >
194
+ > The same rule applies to any optional peer: only a value import of the class carries it into the bundle. Naming a driver class in `@datasource({ driver: NodePostgresDriver })` is what pulls `pg` in.
195
+
171
196
  **Deploy:**
172
197
  ```bash
173
198
  # Copy executable and .env to server
@@ -652,16 +677,28 @@ this.component(HealthCheckComponent);
652
677
  Configure structured logging:
653
678
 
654
679
  ```typescript
655
- import { LoggerFactory } from '@venizia/ignis-helpers';
680
+ import type { ILogger } from '@venizia/ignis-helpers';
681
+ import { ApplicationLogger, LoggerFactory } from '@venizia/ignis-helpers';
682
+ import { PinoLogger } from '@venizia/ignis-helpers/pino';
656
683
 
657
- // Debug-level logs are gated by the DEBUG environment variable (DEBUG=true)
658
- // and the current NODE_ENV. File output is configured via APP_ENV_LOGGER_*
659
- // variables (e.g. APP_ENV_LOGGER_FOLDER_PATH, APP_ENV_LOGGER_FORMAT).
684
+ // One line at the entrypoint selects the provider. Omit it and winston is loaded on
685
+ // first use - which only works when winston is installed and the app is not compiled.
686
+ LoggerFactory.use({ provider: PinoLogger });
660
687
 
661
- const logger = LoggerFactory.getLogger(['MyService']);
688
+ const logger: ILogger = ApplicationLogger.get('MyService');
662
689
  logger.info('Service started | port: %d | env: %s', 3000, 'production');
690
+
691
+ // Method-scoped child logger
692
+ logger.for('start').warn('Slow boot | took: %d ms', 1420);
663
693
  ```
664
694
 
695
+ **Operational notes:**
696
+ - Five levels, each a direct method: `debug`, `info`, `warn`, `error`, `emerg`
697
+ - `debug()` is compiled out at module load unless `DEBUG` is truthy and `NODE_ENV` is a development env - restart to change it
698
+ - Log an `Error` with `%s`; `%j` swallows the stack
699
+ - Secret-looking keys redact to `[REDACTED]` automatically
700
+ - File output is opt-in: without `APP_ENV_LOGGER_FOLDER_PATH` nothing is written to disk
701
+
665
702
  ### Metrics Collection
666
703
 
667
704
  Add Prometheus metrics endpoint: