@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.
- package/README.md +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +24 -13
- package/content/best-practices/architecture-decisions.md +29 -16
- package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
- package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
- package/content/best-practices/code-style-standards/control-flow.md +33 -1
- package/content/best-practices/code-style-standards/documentation.md +28 -8
- package/content/best-practices/code-style-standards/function-patterns.md +15 -4
- package/content/best-practices/code-style-standards/index.md +7 -2
- package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
- package/content/best-practices/code-style-standards/route-definitions.md +9 -5
- package/content/best-practices/code-style-standards/tooling.md +14 -1
- package/content/best-practices/code-style-standards/type-safety.md +39 -6
- package/content/best-practices/common-pitfalls.md +59 -35
- package/content/best-practices/contribution-workflow.md +8 -4
- package/content/best-practices/data-modeling.md +78 -62
- package/content/best-practices/deployment-strategies.md +56 -19
- package/content/best-practices/error-handling.md +247 -153
- package/content/best-practices/index.md +6 -0
- package/content/best-practices/performance-optimization.md +26 -13
- package/content/best-practices/security-guidelines.md +35 -9
- package/content/best-practices/testing-strategies.md +7 -2
- package/content/best-practices/troubleshooting-tips.md +27 -17
- package/content/extensions/components/api-reference.md +109 -323
- package/content/extensions/components/authentication/api.md +489 -602
- package/content/extensions/components/authentication/errors.md +135 -498
- package/content/extensions/components/authentication/index.md +89 -801
- package/content/extensions/components/authentication/usage.md +231 -955
- package/content/extensions/components/authorization/api.md +991 -644
- package/content/extensions/components/authorization/errors.md +204 -208
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +88 -795
- package/content/extensions/components/authorization/usage.md +219 -528
- package/content/extensions/components/health-check.md +77 -243
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +553 -285
- package/content/extensions/components/mail/errors.md +76 -62
- package/content/extensions/components/mail/index.md +111 -463
- package/content/extensions/components/mail/usage.md +139 -173
- package/content/extensions/components/request-tracker.md +70 -173
- package/content/extensions/components/socket-io/api.md +462 -785
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +69 -372
- package/content/extensions/components/socket-io/usage.md +212 -105
- package/content/extensions/components/static-asset/api.md +461 -141
- package/content/extensions/components/static-asset/errors.md +121 -53
- package/content/extensions/components/static-asset/index.md +84 -606
- package/content/extensions/components/static-asset/usage.md +184 -299
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +311 -404
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +75 -407
- package/content/extensions/components/websocket/usage.md +120 -338
- package/content/extensions/helpers/cron/index.md +52 -160
- package/content/extensions/helpers/crypto/index.md +67 -480
- package/content/extensions/helpers/crypto/reference.md +528 -0
- package/content/extensions/helpers/env/index.md +64 -178
- package/content/extensions/helpers/error/index.md +286 -196
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +76 -550
- package/content/extensions/helpers/inversion/reference.md +530 -0
- package/content/extensions/helpers/kafka/admin.md +23 -3
- package/content/extensions/helpers/kafka/compile-binary.md +83 -52
- package/content/extensions/helpers/kafka/consumer.md +65 -28
- package/content/extensions/helpers/kafka/examples.md +46 -253
- package/content/extensions/helpers/kafka/index.md +55 -617
- package/content/extensions/helpers/kafka/producer.md +156 -22
- package/content/extensions/helpers/kafka/schema-registry.md +59 -79
- package/content/extensions/helpers/logger/hf-logger.md +220 -0
- package/content/extensions/helpers/logger/index.md +78 -552
- package/content/extensions/helpers/logger/pino.md +105 -0
- package/content/extensions/helpers/logger/reference.md +937 -0
- package/content/extensions/helpers/network/api.md +276 -195
- package/content/extensions/helpers/network/index.md +87 -524
- package/content/extensions/helpers/queue/index.md +80 -897
- package/content/extensions/helpers/queue/reference.md +494 -0
- package/content/extensions/helpers/redis/index.md +86 -640
- package/content/extensions/helpers/redis/reference.md +784 -0
- package/content/extensions/helpers/secrets/index.md +136 -0
- package/content/extensions/helpers/socket-io/api.md +325 -204
- package/content/extensions/helpers/socket-io/index.md +79 -429
- package/content/extensions/helpers/storage/api.md +584 -464
- package/content/extensions/helpers/storage/index.md +80 -573
- package/content/extensions/helpers/types/index.md +75 -495
- package/content/extensions/helpers/types/reference.md +689 -0
- package/content/extensions/helpers/uid/index.md +176 -189
- package/content/extensions/helpers/websocket/api.md +366 -214
- package/content/extensions/helpers/websocket/index.md +68 -503
- package/content/extensions/helpers/worker-thread/index.md +62 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/extensions/index.md +38 -39
- package/content/extensions/src-details/mcp-server.md +96 -548
- package/content/guides/core-concepts/persistent/datasources.md +2 -2
- package/content/guides/core-concepts/persistent/index.md +5 -1
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/pglite.md +218 -0
- package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
- package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
- package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/get-started/5-minute-quickstart.md +59 -206
- package/content/guides/get-started/philosophy.md +135 -670
- package/content/guides/get-started/setup.md +53 -74
- package/content/guides/migrations/redis-helpers-migration.md +5 -4
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +8 -7
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +64 -22
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +600 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +25 -46
- package/content/references/base/filter-system/application-usage.md +95 -123
- package/content/references/base/filter-system/array-operators.md +33 -43
- package/content/references/base/filter-system/comparison-operators.md +55 -63
- package/content/references/base/filter-system/default-filter.md +144 -350
- package/content/references/base/filter-system/fields-order-pagination.md +116 -148
- package/content/references/base/filter-system/index.md +114 -253
- package/content/references/base/filter-system/json-filtering.md +52 -181
- package/content/references/base/filter-system/list-operators.md +28 -44
- package/content/references/base/filter-system/logical-operators.md +69 -114
- package/content/references/base/filter-system/null-operators.md +41 -98
- package/content/references/base/filter-system/pattern-matching.md +48 -51
- package/content/references/base/filter-system/quick-reference.md +93 -196
- package/content/references/base/filter-system/range-operators.md +22 -38
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +173 -232
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +43 -28
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +81 -1452
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +281 -419
- package/content/references/base/repositories/index.md +84 -644
- package/content/references/base/repositories/mixins.md +25 -21
- package/content/references/base/repositories/relations.md +194 -375
- package/content/references/base/repositories/soft-deletable.md +67 -55
- package/content/references/base/secrets.md +267 -0
- package/content/references/base/services.md +8 -6
- package/content/references/configuration/environment-variables.md +73 -21
- package/content/references/configuration/index.md +51 -31
- package/content/references/index.md +1 -1
- package/content/references/quick-reference.md +3 -16
- package/content/references/utilities/crypto.md +35 -76
- package/content/references/utilities/date.md +33 -73
- package/content/references/utilities/duration.md +85 -0
- package/content/references/utilities/index.md +6 -2
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +81 -61
- package/content/references/utilities/parse.md +34 -64
- package/content/references/utilities/performance.md +33 -58
- package/content/references/utilities/promise.md +28 -62
- package/content/references/utilities/request.md +58 -218
- package/content/references/utilities/retry.md +139 -0
- package/content/references/utilities/schema.md +43 -137
- package/content/references/utilities/statuses-reference.md +361 -0
- package/content/references/utilities/statuses.md +63 -667
- package/dist/mcp-server/index.js +0 -0
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
- 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 (`
|
|
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 `
|
|
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 {
|
|
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
|
|
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` (
|
|
46
|
-
| `extraUserColumns` |
|
|
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` (
|
|
94
|
-
| `big-number` (
|
|
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 {
|
|
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
|
|
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
|
|
222
|
-
static override schema = pgTable('
|
|
225
|
+
export class Post extends BaseRelationalEntity<typeof Post.schema> {
|
|
226
|
+
static override schema = pgTable('Post', {
|
|
223
227
|
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
224
|
-
|
|
228
|
+
authorId: text('author_id').notNull(),
|
|
225
229
|
});
|
|
226
230
|
|
|
227
231
|
static override relations = (): TRelationConfig[] => [
|
|
228
232
|
{
|
|
229
|
-
name: '
|
|
230
|
-
type: RelationTypes.
|
|
231
|
-
schema:
|
|
233
|
+
name: 'author',
|
|
234
|
+
type: RelationTypes.ONE,
|
|
235
|
+
schema: User.schema,
|
|
232
236
|
metadata: {
|
|
233
|
-
fields: [
|
|
234
|
-
references: [
|
|
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: '
|
|
239
|
-
type: RelationTypes.MANY,
|
|
240
|
-
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
445
|
-
|
|
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
|
-
|
|
448
|
-
bun run
|
|
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
|
|
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
|
-
```
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
-
| `
|
|
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
|
-
|
|
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
|
-
#
|
|
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 {
|
|
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
|
-
//
|
|
658
|
-
//
|
|
659
|
-
|
|
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 =
|
|
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:
|