@venizia/ignis-docs 0.2.0 → 0.2.1-0
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 +22 -11
- 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 +26 -2
- 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 +6 -2
- 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 +182 -93
- 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 +107 -322
- package/content/extensions/components/authentication/api.md +454 -603
- package/content/extensions/components/authentication/errors.md +121 -498
- package/content/extensions/components/authentication/index.md +88 -801
- package/content/extensions/components/authentication/usage.md +207 -956
- package/content/extensions/components/authorization/api.md +736 -656
- package/content/extensions/components/authorization/errors.md +168 -206
- package/content/extensions/components/authorization/index.md +82 -797
- package/content/extensions/components/authorization/usage.md +194 -527
- package/content/extensions/components/health-check.md +71 -243
- package/content/extensions/components/mail/api.md +504 -287
- package/content/extensions/components/mail/errors.md +73 -61
- package/content/extensions/components/mail/index.md +96 -467
- package/content/extensions/components/mail/usage.md +130 -172
- package/content/extensions/components/request-tracker.md +66 -173
- package/content/extensions/components/socket-io/api.md +195 -13
- package/content/extensions/components/socket-io/errors.md +3 -3
- package/content/extensions/components/socket-io/index.md +50 -337
- package/content/extensions/components/socket-io/usage.md +143 -26
- package/content/extensions/components/static-asset/api.md +410 -142
- package/content/extensions/components/static-asset/errors.md +110 -53
- package/content/extensions/components/static-asset/index.md +79 -608
- package/content/extensions/components/static-asset/usage.md +180 -300
- package/content/extensions/components/websocket/api.md +275 -399
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +74 -407
- package/content/extensions/components/websocket/usage.md +110 -341
- package/content/extensions/helpers/cron/index.md +51 -160
- package/content/extensions/helpers/crypto/index.md +62 -483
- package/content/extensions/helpers/crypto/reference.md +456 -0
- package/content/extensions/helpers/env/index.md +60 -178
- package/content/extensions/helpers/error/index.md +221 -207
- package/content/extensions/helpers/inversion/index.md +65 -556
- package/content/extensions/helpers/inversion/reference.md +522 -0
- package/content/extensions/helpers/kafka/admin.md +20 -1
- package/content/extensions/helpers/kafka/compile-binary.md +41 -35
- package/content/extensions/helpers/kafka/consumer.md +54 -20
- package/content/extensions/helpers/kafka/examples.md +21 -16
- package/content/extensions/helpers/kafka/index.md +80 -610
- package/content/extensions/helpers/kafka/producer.md +134 -5
- package/content/extensions/helpers/kafka/schema-registry.md +45 -70
- package/content/extensions/helpers/logger/hf-logger.md +193 -0
- package/content/extensions/helpers/logger/index.md +64 -563
- package/content/extensions/helpers/logger/pino.md +85 -0
- package/content/extensions/helpers/logger/reference.md +746 -0
- package/content/extensions/helpers/network/api.md +241 -195
- package/content/extensions/helpers/network/index.md +72 -530
- package/content/extensions/helpers/queue/index.md +72 -900
- package/content/extensions/helpers/queue/reference.md +467 -0
- package/content/extensions/helpers/redis/index.md +73 -645
- package/content/extensions/helpers/redis/reference.md +727 -0
- package/content/extensions/helpers/secrets/index.md +66 -0
- package/content/extensions/helpers/socket-io/api.md +305 -203
- package/content/extensions/helpers/socket-io/index.md +66 -432
- package/content/extensions/helpers/storage/api.md +564 -462
- package/content/extensions/helpers/storage/index.md +77 -573
- package/content/extensions/helpers/types/index.md +66 -499
- package/content/extensions/helpers/types/reference.md +650 -0
- package/content/extensions/helpers/uid/index.md +58 -227
- package/content/extensions/helpers/websocket/api.md +329 -216
- package/content/extensions/helpers/websocket/index.md +65 -503
- package/content/extensions/helpers/worker-thread/index.md +58 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
- package/content/guides/core-concepts/persistent/transactions.md +1 -1
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/migrations/redis-helpers-migration.md +1 -1
- package/content/guides/migrations/unified-connectors-migration.md +2 -2
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/references/base/application.md +1 -1
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/datasources-reference.md +599 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +17 -39
- package/content/references/base/filter-system/application-usage.md +69 -121
- package/content/references/base/filter-system/array-operators.md +12 -0
- package/content/references/base/filter-system/comparison-operators.md +12 -0
- package/content/references/base/filter-system/default-filter.md +136 -348
- package/content/references/base/filter-system/fields-order-pagination.md +38 -16
- package/content/references/base/filter-system/index.md +106 -257
- package/content/references/base/filter-system/json-filtering.md +12 -2
- package/content/references/base/filter-system/list-operators.md +16 -2
- package/content/references/base/filter-system/logical-operators.md +13 -0
- package/content/references/base/filter-system/null-operators.md +13 -0
- package/content/references/base/filter-system/pattern-matching.md +12 -0
- package/content/references/base/filter-system/quick-reference.md +11 -2
- package/content/references/base/filter-system/range-operators.md +12 -0
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +156 -233
- package/content/references/base/middlewares.md +35 -21
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +80 -1452
- package/content/references/base/repositories/advanced.md +156 -192
- package/content/references/base/repositories/index.md +77 -650
- package/content/references/base/repositories/mixins.md +22 -18
- package/content/references/base/repositories/relations.md +123 -171
- package/content/references/base/repositories/soft-deletable.md +58 -56
- package/content/references/base/secrets.md +263 -0
- package/content/references/base/services.md +2 -2
- package/content/references/configuration/environment-variables.md +48 -4
- package/content/references/configuration/index.md +49 -31
- 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/index.md +1 -1
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +29 -62
- 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 +57 -218
- 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/package.json +8 -8
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Models - Full Reference
|
|
3
|
+
description: Complete reference for model architecture, the @model decorator, entity members, and schema enrichers
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Models - Full Reference
|
|
8
|
+
|
|
9
|
+
Exhaustive reference for the `@model` decorator, the entity class hierarchy, and every schema enricher. For a readable introduction and the common tasks, start with the [Models overview](/references/base/models).
|
|
10
|
+
|
|
11
|
+
**Files:**
|
|
12
|
+
|
|
13
|
+
- [`packages/core/src/base/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/models/base.ts) - neutral `AbstractEntity`
|
|
14
|
+
- [`packages/core/src/connectors/postgres/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/base.ts) - PostgreSQL entity (`BaseRelationalEntity`, aliases `BaseEntity`/`BasePostgresEntity`)
|
|
15
|
+
- [`packages/core/src/base/metadata/persistents.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/metadata/persistents.ts) - `@model` decorator
|
|
16
|
+
- [`packages/core/src/connectors/postgres/models/enrichers`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/enrichers) - schema enrichers
|
|
17
|
+
|
|
18
|
+
## `AbstractEntity` (neutral) vs. `BaseEntity` (connector)
|
|
19
|
+
|
|
20
|
+
IGNIS separates the engine-neutral entity root from the connector-specific implementation.
|
|
21
|
+
|
|
22
|
+
- **Engine-neutral root.** `AbstractEntity` has no Drizzle, no `pgTable`, and no `drizzle-zod`.
|
|
23
|
+
- **Minimal surface.** It carries only a `name`, an abstract `getSchema()`, a `getIdType(): TIdSchemaType` method (default `'string'`), and `toObject()`/`toJSON()`.
|
|
24
|
+
- **Everything else is connector-owned.** The Drizzle-backed entity, `drizzle-zod` schema generation, and all schema enrichers belong to the PostgreSQL connector, not the neutral base. See [Connectors](/references/base/connectors) for the full base-vs-connector architecture.
|
|
25
|
+
|
|
26
|
+
`Source ->` [`packages/core/src/base/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/models/base.ts)
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
export abstract class AbstractEntity<Schema = unknown> extends BaseHelper {
|
|
30
|
+
name: string;
|
|
31
|
+
|
|
32
|
+
constructor(opts: { name: string }) {
|
|
33
|
+
super({ scope: opts.name });
|
|
34
|
+
this.name = opts.name;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
abstract getSchema<T = Schema>(opts: { type: TSchemaType }): T;
|
|
38
|
+
|
|
39
|
+
getIdType(): TIdSchemaType {
|
|
40
|
+
return 'string';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
toObject() {
|
|
44
|
+
return { ...this };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
toJSON() {
|
|
48
|
+
return this.toObject();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
> [!TIP] Naming
|
|
54
|
+
> The canonical PostgreSQL class is `BaseRelationalEntity`. `BaseEntity` and `BasePostgresEntity` are compatibility aliases re-exporting the same class from `connectors/postgres/models/index.ts` - all three resolve to identical runtime behavior. Code samples use `BaseEntity`, the most common import today.
|
|
55
|
+
|
|
56
|
+
## The `@model` Decorator
|
|
57
|
+
|
|
58
|
+
Marks a class as a database entity and configures its behavior.
|
|
59
|
+
|
|
60
|
+
`Source ->` [`packages/core/src/base/metadata/persistents.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/metadata/persistents.ts)
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
@model({
|
|
64
|
+
type: 'entity' | 'view',
|
|
65
|
+
tableName?: string,
|
|
66
|
+
skipMigrate?: boolean,
|
|
67
|
+
settings?: {
|
|
68
|
+
hiddenProperties?: string[],
|
|
69
|
+
defaultFilter?: TFilter,
|
|
70
|
+
defaultLimit?: number,
|
|
71
|
+
authorize?: {
|
|
72
|
+
principal: string,
|
|
73
|
+
[extra: string | symbol]: any,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Options
|
|
80
|
+
|
|
81
|
+
| Option | Type | Description |
|
|
82
|
+
|--------|------|-------------|
|
|
83
|
+
| `type` | `'entity' \| 'view'` | Entity type - `'entity'` for tables, `'view'` for database views |
|
|
84
|
+
| `tableName` | `string` | Optional custom table name. Resolution order: `tableName` > static `TABLE_NAME` > class name |
|
|
85
|
+
| `skipMigrate` | `boolean` | Skip this model during schema migrations |
|
|
86
|
+
| `settings.hiddenProperties` | `string[]` | Property names excluded from all repository query results (at SQL level) |
|
|
87
|
+
| `settings.defaultFilter` | `TFilter` | Filter automatically applied to all repository queries (see [Default Filter](/references/base/filter-system/default-filter)) |
|
|
88
|
+
| `settings.defaultLimit` | `number` | Default row limit applied when a query omits `limit`. Must be a positive integer (validated at decoration time). Falls back to the global `DEFAULT_LIMIT` (10). See [Pagination](/references/base/filter-system/fields-order-pagination#default-limit) |
|
|
89
|
+
| `settings.authorize` | `IModelAuthorizeSettings` | Authorization settings - declares the model's authorization principal (see [Authorization](/extensions/components/authorization/usage#model-based-resource-references)) |
|
|
90
|
+
| `settings.authorize.principal` | `string` | The authorization subject name for this model. Auto-populates the static `AUTHORIZATION_SUBJECT` property |
|
|
91
|
+
|
|
92
|
+
### Behavior
|
|
93
|
+
|
|
94
|
+
When the `@model` decorator is applied:
|
|
95
|
+
|
|
96
|
+
1. If `settings.defaultLimit` is provided, it is validated to be a positive integer - otherwise the decorator throws at decoration (boot) time.
|
|
97
|
+
2. If `settings.authorize.principal` is provided and `AUTHORIZATION_SUBJECT` is not already an own property of the class, it auto-populates `AUTHORIZATION_SUBJECT` with the principal value.
|
|
98
|
+
3. The model is registered in the `MetadataRegistry` model registry, keyed by table name (resolved as `metadata.tableName` > static `TABLE_NAME` > class name).
|
|
99
|
+
4. The static `relations` property is stored as a resolver (not immediately resolved) to avoid circular dependency issues between models.
|
|
100
|
+
|
|
101
|
+
## `BaseEntity` (`BaseRelationalEntity`)
|
|
102
|
+
|
|
103
|
+
PostgreSQL connector entity class, wrapping a Drizzle ORM schema. Extends the neutral `AbstractEntity`.
|
|
104
|
+
|
|
105
|
+
`Source ->` [`packages/core/src/connectors/postgres/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/base.ts)
|
|
106
|
+
|
|
107
|
+
### Purpose
|
|
108
|
+
|
|
109
|
+
| Feature | Description |
|
|
110
|
+
|---------|-------------|
|
|
111
|
+
| Schema encapsulation | Holds the Drizzle `pgTable` schema for consistent repository access |
|
|
112
|
+
| Metadata | Works with the `@model` decorator to mark database entities |
|
|
113
|
+
| Schema generation | Uses `drizzle-zod` to generate Zod schemas (`select`, `create`, `update`) |
|
|
114
|
+
| Static properties | Supports static `schema`, `relations`, `TABLE_NAME`, and `AUTHORIZATION_SUBJECT` |
|
|
115
|
+
| Convenience | Inherits `toObject()` and `toJSON()` from `AbstractEntity` |
|
|
116
|
+
|
|
117
|
+
### Static properties
|
|
118
|
+
|
|
119
|
+
| Property | Type | Description |
|
|
120
|
+
|----------|------|-------------|
|
|
121
|
+
| `schema` | `TTableSchemaWithId` | Drizzle table schema defined with `pgTable()` |
|
|
122
|
+
| `relations` | `TValueOrResolver<Array<TRelationConfig>>` | Relation definitions (can be a function for lazy loading to avoid circular deps) |
|
|
123
|
+
| `TABLE_NAME` | `string \| undefined` | Optional table name (defaults to class name if not set) |
|
|
124
|
+
| `AUTHORIZATION_SUBJECT` | `string \| undefined` | Authorization principal name. Auto-populated from `@model` `settings.authorize.principal` |
|
|
125
|
+
|
|
126
|
+
### Instance members
|
|
127
|
+
|
|
128
|
+
| Member | Description |
|
|
129
|
+
|--------|-------------|
|
|
130
|
+
| `schema` | Instance copy of the Drizzle schema (`opts.schema` > static `schema`) |
|
|
131
|
+
| `getSchema({ type })` | Get a Zod schema for validation (`'select'`, `'create'`, `'update'`) |
|
|
132
|
+
| `getIdType()` | Maps the id column's Drizzle `dataType` to `'number'` (serial/integer) or `'string'` (everything else) |
|
|
133
|
+
| `toObject()` | Convert to a plain object (shallow spread of `this`) |
|
|
134
|
+
| `toJSON()` | Delegates to `toObject()` (used by `JSON.stringify`) |
|
|
135
|
+
|
|
136
|
+
### `IEntity` interface
|
|
137
|
+
|
|
138
|
+
Models implementing static properties conform to `IEntity`:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
interface IEntity<Schema extends TTableSchemaWithId = TTableSchemaWithId> {
|
|
142
|
+
TABLE_NAME?: string;
|
|
143
|
+
schema: Schema;
|
|
144
|
+
relations?: TValueOrResolver<Array<TRelationConfig>>;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### `getSchema` method
|
|
149
|
+
|
|
150
|
+
Generates a Zod validation schema from the Drizzle table schema using `drizzle-zod`.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
getSchema(opts: { type: TSchemaType }): ZodSchema
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The `type` parameter accepts lowercase string values defined in the `SchemaTypes` class:
|
|
157
|
+
|
|
158
|
+
| Type | Value | Zod schema generated | Description |
|
|
159
|
+
|------|-------|---------------------|-------------|
|
|
160
|
+
| `SchemaTypes.SELECT` | `'select'` | `createSelectSchema(schema)` | Schema for query results |
|
|
161
|
+
| `SchemaTypes.CREATE` | `'create'` | `createInsertSchema(schema)` | Schema for insert operations |
|
|
162
|
+
| `SchemaTypes.UPDATE` | `'update'` | `createUpdateSchema(schema)` | Schema for update operations |
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
const user = new User();
|
|
166
|
+
const createSchema = user.getSchema({ type: 'create' });
|
|
167
|
+
const selectSchema = user.getSchema({ type: 'select' });
|
|
168
|
+
const updateSchema = user.getSchema({ type: 'update' });
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
An invalid `type` throws. The `schemaFactory` is a static lazy singleton created via `drizzle-zod`'s `createSchemaFactory()`, shared across all entity instances to avoid per-entity overhead.
|
|
172
|
+
|
|
173
|
+
### Class definition
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
export class BaseRelationalEntity<Schema extends TTableSchemaWithId = TTableSchemaWithId>
|
|
177
|
+
extends AbstractEntity
|
|
178
|
+
implements IEntity<Schema>
|
|
179
|
+
{
|
|
180
|
+
schema: Schema;
|
|
181
|
+
|
|
182
|
+
static schema: TTableSchemaWithId;
|
|
183
|
+
static relations?: TValueOrResolver<Array<TRelationConfig>>;
|
|
184
|
+
static TABLE_NAME?: string;
|
|
185
|
+
static AUTHORIZATION_SUBJECT?: string;
|
|
186
|
+
|
|
187
|
+
private static _schemaFactory?: ReturnType<typeof createSchemaFactory>;
|
|
188
|
+
protected static get schemaFactory(): ReturnType<typeof createSchemaFactory> {
|
|
189
|
+
return (BaseRelationalEntity._schemaFactory ??= createSchemaFactory());
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
constructor(opts?: { name?: string; schema?: Schema }) {
|
|
193
|
+
const ctor = new.target as typeof BaseRelationalEntity;
|
|
194
|
+
const name = opts?.name ?? ctor.TABLE_NAME ?? ctor.name;
|
|
195
|
+
super({ name });
|
|
196
|
+
this.schema = opts?.schema ?? (ctor.schema as Schema);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
override getIdType(): TIdSchemaType {
|
|
200
|
+
return getIdType({ entity: this.schema }) === 'number' ? 'number' : 'string';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
getSchema<T = unknown>(opts: { type: TSchemaType }): T {
|
|
204
|
+
const factory = BaseRelationalEntity.schemaFactory;
|
|
205
|
+
switch (opts.type) {
|
|
206
|
+
case SchemaTypes.CREATE:
|
|
207
|
+
return factory.createInsertSchema(this.schema) as T;
|
|
208
|
+
case SchemaTypes.UPDATE:
|
|
209
|
+
return factory.createUpdateSchema(this.schema) as T;
|
|
210
|
+
case SchemaTypes.SELECT:
|
|
211
|
+
return factory.createSelectSchema(this.schema) as T;
|
|
212
|
+
default:
|
|
213
|
+
throw getError({ message: `[getSchema] Invalid schema type | type: ${opts.type}` });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Definition patterns
|
|
220
|
+
|
|
221
|
+
`BaseEntity` supports two patterns for defining models.
|
|
222
|
+
|
|
223
|
+
### Static properties (recommended)
|
|
224
|
+
|
|
225
|
+
Define schema and relations as static properties. Repositories auto-resolve them.
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
229
|
+
import { BaseEntity, model, generateIdColumnDefs, RelationTypes } from '@venizia/ignis';
|
|
230
|
+
import type { TRelationConfig } from '@venizia/ignis/postgres';
|
|
231
|
+
import { Comment } from './comment.model';
|
|
232
|
+
|
|
233
|
+
export const userTable = pgTable('User', {
|
|
234
|
+
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
235
|
+
name: text('name').notNull(),
|
|
236
|
+
email: text('email').notNull(),
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
@model({ type: 'entity' })
|
|
240
|
+
export class User extends BaseEntity<typeof User.schema> {
|
|
241
|
+
static override schema = userTable;
|
|
242
|
+
static override TABLE_NAME = 'User';
|
|
243
|
+
|
|
244
|
+
// A resolver returning a plain array of relation configs (lazy - avoids circular imports).
|
|
245
|
+
static override relations = (): TRelationConfig[] => [
|
|
246
|
+
{
|
|
247
|
+
name: 'comments',
|
|
248
|
+
type: RelationTypes.MANY,
|
|
249
|
+
schema: Comment.schema,
|
|
250
|
+
metadata: { relationName: 'author' },
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
- **Auto-resolved.** Schema and relations are auto-resolved by repositories.
|
|
257
|
+
- **Less ceremony.** No need to pass `relations` in a repository constructor.
|
|
258
|
+
- **Declarative.** Cleaner than wiring dependencies through a constructor.
|
|
259
|
+
|
|
260
|
+
### Constructor-based (legacy)
|
|
261
|
+
|
|
262
|
+
Pass schema in the constructor.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
@model({ type: 'entity' })
|
|
266
|
+
export class User extends BaseEntity<typeof userTable> {
|
|
267
|
+
constructor() {
|
|
268
|
+
super({ name: 'User', schema: userTable });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Hidden properties
|
|
274
|
+
|
|
275
|
+
Hidden properties are excluded at the SQL level - they are never fetched from the database when querying through repositories.
|
|
276
|
+
|
|
277
|
+
- **Security.** Sensitive data like passwords is never accidentally exposed.
|
|
278
|
+
- **Performance.** Less data is transferred.
|
|
279
|
+
- **Consistency.** Exclusion applies across all repository operations.
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
283
|
+
import { BaseEntity, model, generateIdColumnDefs } from '@venizia/ignis';
|
|
284
|
+
|
|
285
|
+
@model({
|
|
286
|
+
type: 'entity',
|
|
287
|
+
settings: { hiddenProperties: ['password', 'secret'] },
|
|
288
|
+
})
|
|
289
|
+
export class User extends BaseEntity<typeof User.schema> {
|
|
290
|
+
static override schema = pgTable('User', {
|
|
291
|
+
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
292
|
+
email: text('email').notNull(),
|
|
293
|
+
password: text('password'),
|
|
294
|
+
secret: text('secret'),
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Behavior matrix
|
|
300
|
+
|
|
301
|
+
| Operation | Hidden properties |
|
|
302
|
+
|-----------|-------------------|
|
|
303
|
+
| `find()`, `findOne()`, `findById()` | Excluded from SELECT |
|
|
304
|
+
| `create()`, `createAll()` | Excluded from RETURNING |
|
|
305
|
+
| `updateById()`, `updateAll()` | Excluded from RETURNING |
|
|
306
|
+
| `deleteById()`, `deleteAll()` | Excluded from RETURNING |
|
|
307
|
+
| `count()`, `existsWith()` | Can filter by hidden fields |
|
|
308
|
+
| Direct connector query | Included (bypasses repository) |
|
|
309
|
+
|
|
310
|
+
Notes:
|
|
311
|
+
|
|
312
|
+
- Hidden properties can still be used in `where` clauses for filtering.
|
|
313
|
+
- Data is still stored in the database - only excluded from query results.
|
|
314
|
+
- Use a direct connector query when you need to access hidden data:
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
// Repository query - password/secret NOT included
|
|
318
|
+
const user = await userRepository.findById({ id: '123' });
|
|
319
|
+
// user = { id: '123', email: 'john@example.com' }
|
|
320
|
+
|
|
321
|
+
// Direct connector query - ALL fields included
|
|
322
|
+
const connector = userRepository.connector;
|
|
323
|
+
const [fullUser] = await connector
|
|
324
|
+
.select()
|
|
325
|
+
.from(User.schema)
|
|
326
|
+
.where(eq(User.schema.id, '123'));
|
|
327
|
+
// fullUser = { id: '123', email: '...', password: 'hashed...', secret: '...' }
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Default filter
|
|
331
|
+
|
|
332
|
+
Default filters are automatically applied to all repository queries for a model. Useful for soft delete, multi-tenancy, active-record filtering, and query limits.
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
import { model, BaseEntity } from '@venizia/ignis';
|
|
336
|
+
import { postTable } from '@/schemas';
|
|
337
|
+
|
|
338
|
+
@model({
|
|
339
|
+
type: 'entity',
|
|
340
|
+
settings: {
|
|
341
|
+
defaultFilter: {
|
|
342
|
+
where: { isDeleted: false },
|
|
343
|
+
limit: 100,
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
})
|
|
347
|
+
export class Post extends BaseEntity<typeof Post.schema> {
|
|
348
|
+
static override schema = postTable;
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Behavior matrix
|
|
353
|
+
|
|
354
|
+
| Operation | Default filter |
|
|
355
|
+
|-----------|----------------|
|
|
356
|
+
| `find()`, `findOne()`, `findById()` | Applied to WHERE clause |
|
|
357
|
+
| `count()`, `existsWith()` | Applied to WHERE clause |
|
|
358
|
+
| `updateById()`, `updateAll()` | Applied to WHERE clause |
|
|
359
|
+
| `deleteById()`, `deleteAll()` | Applied to WHERE clause |
|
|
360
|
+
| `create()`, `createAll()` | Not applied |
|
|
361
|
+
|
|
362
|
+
### Bypassing
|
|
363
|
+
|
|
364
|
+
Use `shouldSkipDefaultFilter: true` to bypass it:
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
// Normal query - includes default filter
|
|
368
|
+
await postRepository.find({ filter: {} });
|
|
369
|
+
// WHERE isDeleted = false LIMIT 100
|
|
370
|
+
|
|
371
|
+
// Admin query - bypass default filter
|
|
372
|
+
await postRepository.find({
|
|
373
|
+
filter: {},
|
|
374
|
+
options: { shouldSkipDefaultFilter: true },
|
|
375
|
+
});
|
|
376
|
+
// No default WHERE clause (includes deleted)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
> [!TIP]
|
|
380
|
+
> See [Default Filter](/references/base/filter-system/default-filter) for full documentation including merge strategies and common patterns.
|
|
381
|
+
|
|
382
|
+
## Schema enrichers
|
|
383
|
+
|
|
384
|
+
- **What they are.** Helper functions in the PostgreSQL connector that return an object of Drizzle ORM column definitions.
|
|
385
|
+
- **How to use them.** Spread a `generate*` result into a `pgTable` definition to add common, standardized fields.
|
|
386
|
+
- **Convenience wrappers.** Each `enrich*` wrapper accepts an existing column definitions object as its first argument and merges the generated columns into it.
|
|
387
|
+
|
|
388
|
+
| Enricher function | Convenience wrapper | Purpose |
|
|
389
|
+
| :--- | :--- | :--- |
|
|
390
|
+
| `generateIdColumnDefs` | `enrichId` | Adds a primary key `id` column (string, integer, or big integer) |
|
|
391
|
+
| `generateTzColumnDefs` | `enrichTz` | Adds `createdAt`, `modifiedAt`, and optional `deletedAt` timestamp columns |
|
|
392
|
+
| `generateUserAuditColumnDefs` | `enrichUserAudit` | Adds `createdBy` and `modifiedBy` columns tracking user audit info |
|
|
393
|
+
| `generatePrincipalColumnDefs` | `enrichPrincipal` | Adds polymorphic principal columns (`{discriminator}Id` and `{discriminator}Type`) |
|
|
394
|
+
| `generateDataTypeColumnDefs` | `enrichDataTypes` | Adds generic value columns (`dataType`, `nValue`, `tValue`, `bValue`, `jValue`, `boValue`) |
|
|
395
|
+
|
|
396
|
+
### Combined example
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
400
|
+
import {
|
|
401
|
+
generateIdColumnDefs,
|
|
402
|
+
generateTzColumnDefs,
|
|
403
|
+
generateUserAuditColumnDefs,
|
|
404
|
+
} from '@venizia/ignis';
|
|
405
|
+
|
|
406
|
+
export const myTable = pgTable('MyTable', {
|
|
407
|
+
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
408
|
+
...generateTzColumnDefs(),
|
|
409
|
+
...generateUserAuditColumnDefs({
|
|
410
|
+
created: { dataType: 'string', columnName: 'created_by' },
|
|
411
|
+
modified: { dataType: 'string', columnName: 'modified_by' },
|
|
412
|
+
}),
|
|
413
|
+
name: text('name').notNull(),
|
|
414
|
+
});
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### `generateIdColumnDefs`
|
|
418
|
+
|
|
419
|
+
Adds a primary key `id` column with full TypeScript type inference.
|
|
420
|
+
|
|
421
|
+
`Source ->` [`packages/core/src/connectors/postgres/models/enrichers/id.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/enrichers/id.enricher.ts)
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
generateIdColumnDefs<Opts extends TIdEnricherOptions | undefined>(opts?: Opts): TIdColumnDef<Opts>
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Options (`TIdEnricherOptions`):
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
type TIdEnricherOptions = {
|
|
431
|
+
id?: { columnName?: string } & (
|
|
432
|
+
| { dataType: 'string'; generator?: () => string }
|
|
433
|
+
| { dataType: 'number'; sequenceOptions?: PgSequenceOptions }
|
|
434
|
+
| {
|
|
435
|
+
dataType: 'big-number';
|
|
436
|
+
numberMode: 'number' | 'bigint';
|
|
437
|
+
sequenceOptions?: PgSequenceOptions;
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
};
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Default: `id.dataType` defaults to `'number'` (auto-incrementing integer).
|
|
444
|
+
|
|
445
|
+
Emitted columns:
|
|
446
|
+
|
|
447
|
+
| Data type | Column type | Constraints | Description |
|
|
448
|
+
|-----------|------------|-------------|-------------|
|
|
449
|
+
| `'string'` | `text` | Primary key, default `crypto.randomUUID()` | Text column with customizable ID generator |
|
|
450
|
+
| `'number'` | `integer` | Primary key, `GENERATED ALWAYS AS IDENTITY` | Auto-incrementing integer |
|
|
451
|
+
| `'big-number'` | `bigint` | Primary key, `GENERATED ALWAYS AS IDENTITY` | Auto-incrementing big integer (mode `'number'` or `'bigint'`) |
|
|
452
|
+
|
|
453
|
+
Notes:
|
|
454
|
+
|
|
455
|
+
- For `dataType: 'string'`, a `text` column is used for maximum database compatibility. Provide a custom `generator` (nanoid, cuid, etc.); the default is `crypto.randomUUID()`.
|
|
456
|
+
- For `dataType: 'big-number'`, `numberMode` is required - `'number'` (values up to 2^53-1, safe for JavaScript numbers) or `'bigint'` (larger values, requires `BigInt`).
|
|
457
|
+
- `sequenceOptions` (available for `number` and `big-number`) customizes identity generation, e.g. `{ startWith: 1000, increment: 1 }`.
|
|
458
|
+
|
|
459
|
+
Convenience wrapper:
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
enrichId(baseColumns: TColumnDefinitions, opts?: TIdEnricherOptions): TColumnDefinitions
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### `generateTzColumnDefs`
|
|
466
|
+
|
|
467
|
+
Adds timestamp columns for creation, modification, and soft deletion.
|
|
468
|
+
|
|
469
|
+
`Source ->` [`packages/core/src/connectors/postgres/models/enrichers/tz.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/enrichers/tz.enricher.ts)
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
generateTzColumnDefs<Opts extends TTzEnricherOptions | undefined>(opts?: Opts): TTzEnricherResult<Opts>
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Options (`TTzEnricherOptions`):
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
type TTzEnricherOptions = {
|
|
479
|
+
created?: { columnName: string; withTimezone: boolean };
|
|
480
|
+
modified?: { enable: false } | { enable?: true; columnName: string; withTimezone: boolean };
|
|
481
|
+
deleted?: { enable: false } | { enable?: true; columnName: string; withTimezone: boolean };
|
|
482
|
+
};
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
The `modified` and `deleted` options use a discriminated union:
|
|
486
|
+
|
|
487
|
+
- **`enable: false`.** No other properties are needed.
|
|
488
|
+
- **`enable: true` (or omitted).** `columnName` and `withTimezone` are required.
|
|
489
|
+
|
|
490
|
+
Defaults:
|
|
491
|
+
|
|
492
|
+
- `created`: `{ columnName: 'created_at', withTimezone: true }`
|
|
493
|
+
- `modified`: `{ enable: true, columnName: 'modified_at', withTimezone: true }`
|
|
494
|
+
- `deleted`: `{ enable: false }` (disabled by default)
|
|
495
|
+
|
|
496
|
+
Emitted columns:
|
|
497
|
+
|
|
498
|
+
| Column | Type | Constraints | Default | Description |
|
|
499
|
+
|--------|------|-------------|---------|-------------|
|
|
500
|
+
| `createdAt` | timestamp | `NOT NULL` | `NOW()` | When the record was created (always included) |
|
|
501
|
+
| `modifiedAt` | timestamp | `NOT NULL` | `NOW()`, auto-updates via `$onUpdate(() => new Date().toISOString())` | When the record was last modified (enabled by default) |
|
|
502
|
+
| `deletedAt` | timestamp | nullable | none | When the record was soft-deleted (disabled by default) |
|
|
503
|
+
|
|
504
|
+
**Type inference**
|
|
505
|
+
|
|
506
|
+
- **`createdAt`.** Always present.
|
|
507
|
+
- **`modifiedAt`.** Present unless `modified: { enable: false }`.
|
|
508
|
+
- **`deletedAt`.** Absent unless `deleted: { enable: true, ... }`.
|
|
509
|
+
|
|
510
|
+
Convenience wrapper:
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
enrichTz(baseSchema: TColumnDefinitions, opts?: TTzEnricherOptions): TColumnDefinitions
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
<details>
|
|
517
|
+
<summary>Soft delete pattern</summary>
|
|
518
|
+
|
|
519
|
+
The `deletedAt` column enables soft delete - records are marked deleted rather than physically removed.
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
import { eq, isNull, isNotNull } from 'drizzle-orm';
|
|
523
|
+
|
|
524
|
+
// Soft delete: set deletedAt timestamp
|
|
525
|
+
await db.update(myTable).set({ deletedAt: new Date() }).where(eq(myTable.id, id));
|
|
526
|
+
|
|
527
|
+
// Query only active (non-deleted) records
|
|
528
|
+
const active = await db.select().from(myTable).where(isNull(myTable.deletedAt));
|
|
529
|
+
|
|
530
|
+
// Query deleted records
|
|
531
|
+
const deleted = await db.select().from(myTable).where(isNotNull(myTable.deletedAt));
|
|
532
|
+
|
|
533
|
+
// Restore a soft-deleted record
|
|
534
|
+
await db.update(myTable).set({ deletedAt: null }).where(eq(myTable.id, id));
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
</details>
|
|
538
|
+
|
|
539
|
+
### `generateUserAuditColumnDefs`
|
|
540
|
+
|
|
541
|
+
Adds `createdBy` and `modifiedBy` columns tracking which user created or modified a record.
|
|
542
|
+
|
|
543
|
+
`Source ->` [`packages/core/src/connectors/postgres/models/enrichers/user-audit.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/enrichers/user-audit.enricher.ts)
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
generateUserAuditColumnDefs(opts?: TUserAuditEnricherOptions): {
|
|
547
|
+
createdBy: PgIntegerBuilderInitial | PgTextBuilderInitial;
|
|
548
|
+
modifiedBy: PgIntegerBuilderInitial | PgTextBuilderInitial;
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
Options:
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
type TUserAuditColumnOpts = {
|
|
556
|
+
dataType: 'string' | 'number'; // Required - type of user ID
|
|
557
|
+
columnName: string; // Column name in database
|
|
558
|
+
allowAnonymous?: boolean; // Allow null user ID (default: true)
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
type TUserAuditEnricherOptions = {
|
|
562
|
+
created?: TUserAuditColumnOpts;
|
|
563
|
+
modified?: TUserAuditColumnOpts;
|
|
564
|
+
};
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
Defaults:
|
|
568
|
+
|
|
569
|
+
- `created`: `{ dataType: 'number', columnName: 'created_by', allowAnonymous: true }`
|
|
570
|
+
- `modified`: `{ dataType: 'number', columnName: 'modified_by', allowAnonymous: true }`
|
|
571
|
+
|
|
572
|
+
**How it works**
|
|
573
|
+
|
|
574
|
+
- **Retrieval.** The enricher uses Hono's `contextStorage` (via `tryGetContext()`) to retrieve the current user ID at insert/update time, reading it from the `Authentication.AUDIT_USER_ID` key.
|
|
575
|
+
- **`createdBy`.** Set via `$default()` - creation only.
|
|
576
|
+
- **`modifiedBy`.** Set via both `$default()` and `$onUpdate()` - creation and every modification.
|
|
577
|
+
|
|
578
|
+
Emitted columns:
|
|
579
|
+
|
|
580
|
+
| Column | Data type | Default column name | Description |
|
|
581
|
+
|--------|-----------|---------------------|-------------|
|
|
582
|
+
| `createdBy` | `integer` or `text` | `created_by` | User ID who created the record |
|
|
583
|
+
| `modifiedBy` | `integer` or `text` | `modified_by` | User ID who last modified the record |
|
|
584
|
+
|
|
585
|
+
`allowAnonymous` behavior:
|
|
586
|
+
|
|
587
|
+
| `allowAnonymous` | No context | No user ID | Has user ID |
|
|
588
|
+
|------------------|------------|------------|-------------|
|
|
589
|
+
| `true` (default) | Returns `null` | Returns `null` | Returns user ID |
|
|
590
|
+
| `false` | Throws error | Throws error | Returns user ID |
|
|
591
|
+
|
|
592
|
+
- **`allowAnonymous: false`.** Use for sensitive audit trails, tables that forbid anonymous operations, and compliance requirements mandating user attribution.
|
|
593
|
+
- **`allowAnonymous: true` (default).** Use for background jobs, migrations, seed scripts, and system-generated records.
|
|
594
|
+
|
|
595
|
+
The enricher validates `dataType` and throws for invalid values (only `'number'` and `'string'` are valid).
|
|
596
|
+
|
|
597
|
+
> [!WARNING]
|
|
598
|
+
> Fire-and-forget promises may run outside the async context, losing access to `AUDIT_USER_ID`. Ensure audit-critical operations complete within the request lifecycle.
|
|
599
|
+
|
|
600
|
+
Convenience wrapper:
|
|
601
|
+
|
|
602
|
+
```typescript
|
|
603
|
+
enrichUserAudit<ColumnDefinitions extends TColumnDefinitions>(
|
|
604
|
+
baseSchema: ColumnDefinitions,
|
|
605
|
+
opts?: TUserAuditEnricherOptions,
|
|
606
|
+
): TUserAuditEnricherResult<ColumnDefinitions>
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### `generatePrincipalColumnDefs`
|
|
610
|
+
|
|
611
|
+
Adds polymorphic principal columns for associating a record with different entity types (a comment can belong to a Post, User, or Product).
|
|
612
|
+
|
|
613
|
+
`Source ->` [`packages/core/src/connectors/postgres/models/enrichers/principal.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/enrichers/principal.enricher.ts)
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
generatePrincipalColumnDefs<
|
|
617
|
+
Discriminator extends string = 'principal',
|
|
618
|
+
IdType extends 'number' | 'string' = 'number',
|
|
619
|
+
Nullable extends boolean = false,
|
|
620
|
+
>(
|
|
621
|
+
opts: TPrincipalEnricherOptions<Discriminator, IdType, Nullable>,
|
|
622
|
+
): TPrincipalColumnDef<Discriminator, IdType, Nullable>
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
Options (`TPrincipalEnricherOptions`):
|
|
626
|
+
|
|
627
|
+
| Option | Type | Default | Description |
|
|
628
|
+
|--------|------|---------|-------------|
|
|
629
|
+
| `discriminator` | `string` | `'principal'` | Prefix for generated column names |
|
|
630
|
+
| `defaultPolymorphic` | `string` | `''` | Default value for the type discriminator column |
|
|
631
|
+
| `polymorphicIdType` | `'number' \| 'string'` | (required) | Data type of the ID column |
|
|
632
|
+
| `isNullableId` | `boolean` | `false` | When `true`, the ID column is nullable (omits `NOT NULL`) |
|
|
633
|
+
|
|
634
|
+
Emitted columns, given `discriminator = 'principal'`:
|
|
635
|
+
|
|
636
|
+
| Column | DB column name | Type | Constraints | Description |
|
|
637
|
+
|--------|---------------|------|-------------|-------------|
|
|
638
|
+
| `principalId` | `principal_id` | `integer` or `text` | `NOT NULL` (unless `isNullableId`) | ID of the associated entity |
|
|
639
|
+
| `principalType` | `principal_type` | `text` | `DEFAULT ''` (or `defaultPolymorphic`) | Type discriminator (e.g. `'User'`, `'Post'`) |
|
|
640
|
+
|
|
641
|
+
With a custom discriminator such as `'owner'`, columns become `ownerId` / `owner_id` and `ownerType` / `owner_type`.
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
645
|
+
import { generateIdColumnDefs, generatePrincipalColumnDefs } from '@venizia/ignis';
|
|
646
|
+
|
|
647
|
+
export const commentTable = pgTable('Comment', {
|
|
648
|
+
...generateIdColumnDefs(),
|
|
649
|
+
...generatePrincipalColumnDefs({ polymorphicIdType: 'number' }),
|
|
650
|
+
content: text('content').notNull(),
|
|
651
|
+
});
|
|
652
|
+
// principalId: integer('principal_id').notNull()
|
|
653
|
+
// principalType: text('principal_type').default('')
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
Convenience wrapper:
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
enrichPrincipal<ColumnDefinitions extends TColumnDefinitions>(
|
|
660
|
+
baseSchema: ColumnDefinitions,
|
|
661
|
+
opts: TPrincipalEnricherOptions,
|
|
662
|
+
): ColumnDefinitions & TPrincipalColumnDef
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### `generateDataTypeColumnDefs`
|
|
666
|
+
|
|
667
|
+
Adds polymorphic data storage columns for entities that store values of different types in a single table (key-value stores, settings tables).
|
|
668
|
+
|
|
669
|
+
`Source ->` [`packages/core/src/connectors/postgres/models/enrichers/data-type.enricher.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/enrichers/data-type.enricher.ts)
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
generateDataTypeColumnDefs(opts?: TDataTypeEnricherOptions): {
|
|
673
|
+
dataType: PgTextBuilderInitial;
|
|
674
|
+
nValue: PgDoublePrecisionBuilderInitial;
|
|
675
|
+
tValue: PgTextBuilderInitial;
|
|
676
|
+
bValue: PgCustomColumnBuilder<Buffer>;
|
|
677
|
+
jValue: PgJsonbBuilderInitial<Record<string, any>>;
|
|
678
|
+
boValue: PgBooleanBuilderInitial;
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
Options (`TDataTypeEnricherOptions`):
|
|
683
|
+
|
|
684
|
+
```typescript
|
|
685
|
+
type TDataTypeEnricherOptions = {
|
|
686
|
+
defaultValue: Partial<{
|
|
687
|
+
dataType: string;
|
|
688
|
+
nValue: number;
|
|
689
|
+
tValue: string;
|
|
690
|
+
bValue: Buffer;
|
|
691
|
+
jValue: object;
|
|
692
|
+
boValue: boolean;
|
|
693
|
+
}>;
|
|
694
|
+
};
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
Emitted columns:
|
|
698
|
+
|
|
699
|
+
| Column | SQL type | DB column name | TypeScript type | Purpose |
|
|
700
|
+
|--------|----------|----------------|-----------------|---------|
|
|
701
|
+
| `dataType` | `text` | `data_type` | `string` | Type discriminator (`'number'`, `'text'`, `'json'`) |
|
|
702
|
+
| `nValue` | `double precision` | `n_value` | `number` | Numeric values |
|
|
703
|
+
| `tValue` | `text` | `t_value` | `string` | Text values |
|
|
704
|
+
| `bValue` | `bytea` | `b_value` | `Buffer` | Binary values |
|
|
705
|
+
| `jValue` | `jsonb` | `j_value` | `Record<string, any>` | JSON values |
|
|
706
|
+
| `boValue` | `boolean` | `bo_value` | `boolean` | Boolean values |
|
|
707
|
+
|
|
708
|
+
All columns are nullable by default (no `NOT NULL`), since typically only one value column is populated per row depending on the `dataType` discriminator. Pass `defaultValue` to attach SQL defaults to individual columns.
|
|
709
|
+
|
|
710
|
+
```typescript
|
|
711
|
+
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
712
|
+
import { BaseEntity, model, generateIdColumnDefs, generateDataTypeColumnDefs } from '@venizia/ignis';
|
|
713
|
+
|
|
714
|
+
@model({ type: 'entity' })
|
|
715
|
+
export class AppConfig extends BaseEntity<typeof AppConfig.schema> {
|
|
716
|
+
static override schema = pgTable('AppConfig', {
|
|
717
|
+
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
718
|
+
...generateDataTypeColumnDefs(),
|
|
719
|
+
key: text('key').notNull().unique(),
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
// { key: 'max_retries', dataType: 'number', nValue: 3 }
|
|
723
|
+
// { key: 'feature_flags', dataType: 'json', jValue: { darkMode: true } }
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
Convenience wrapper:
|
|
727
|
+
|
|
728
|
+
```typescript
|
|
729
|
+
enrichDataTypes(baseSchema: TColumnDefinitions, opts?: TDataTypeEnricherOptions): TColumnDefinitions
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
## Key types
|
|
733
|
+
|
|
734
|
+
`Source ->` [`packages/core/src/connectors/postgres/models/common`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/common)
|
|
735
|
+
|
|
736
|
+
### `TTableSchemaWithId`
|
|
737
|
+
|
|
738
|
+
Ensures a Drizzle `PgTable` has an `id` column:
|
|
739
|
+
|
|
740
|
+
```typescript
|
|
741
|
+
type TTableSchemaWithId<TC extends TableConfig = TableConfig> = PgTable<TC> & {
|
|
742
|
+
id: TIdColumn;
|
|
743
|
+
};
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### `TTableObject` / `TTableInsert`
|
|
747
|
+
|
|
748
|
+
Infer the select (output) and insert (input) types from a table schema:
|
|
749
|
+
|
|
750
|
+
```typescript
|
|
751
|
+
type TTableObject<T extends TTableSchemaWithId> = T['$inferSelect'];
|
|
752
|
+
type TTableInsert<T extends TTableSchemaWithId> = T['$inferInsert'];
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### `TRelationConfig`
|
|
756
|
+
|
|
757
|
+
Configuration for entity relationships.
|
|
758
|
+
|
|
759
|
+
`Source ->` [`packages/core/src/connectors/postgres/repositories/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/repositories/common/types.ts)
|
|
760
|
+
|
|
761
|
+
```typescript
|
|
762
|
+
type TRelationConfig = {
|
|
763
|
+
name: string;
|
|
764
|
+
} & (
|
|
765
|
+
| { type: 'one'; schema: TTableSchemaWithId; metadata: /* Drizzle one() params */ }
|
|
766
|
+
| { type: 'many'; schema: TTableSchemaWithId; metadata: /* Drizzle many() params */ }
|
|
767
|
+
);
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
Relation types are defined in the `RelationTypes` class: `RelationTypes.ONE` (`'one'`, one-to-one or many-to-one) and `RelationTypes.MANY` (`'many'`, one-to-many). See [Relations](/references/base/repositories/relations).
|
|
771
|
+
|
|
772
|
+
### `TValueOrResolver`
|
|
773
|
+
|
|
774
|
+
From `@venizia/ignis-helpers`, enables lazy resolution to avoid circular dependencies:
|
|
775
|
+
|
|
776
|
+
```typescript
|
|
777
|
+
type TValueOrResolver<T> = T | TResolver<T>; // T or () => T
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
Used for `relations` on `BaseEntity` - store a function that returns the relations array, resolved lazily when `DataSource.buildSchema()` is called.
|
|
781
|
+
|
|
782
|
+
## Schema utilities
|
|
783
|
+
|
|
784
|
+
`Source ->` [`packages/core/src/base/models/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/models/common/types.ts)
|
|
785
|
+
|
|
786
|
+
### `idParamsSchema`
|
|
787
|
+
|
|
788
|
+
Generates a Zod schema for path parameters containing an `id` field, suitable for OpenAPI route definitions.
|
|
789
|
+
|
|
790
|
+
```typescript
|
|
791
|
+
idParamsSchema(opts?: { idType: TIdSchemaType }): z.ZodObject<{ id: z.ZodNumber | z.ZodString }>
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
| `idType` | Default | Zod type | Examples |
|
|
795
|
+
|----------|---------|----------|----------|
|
|
796
|
+
| `'number'` | Yes | `z.number()` | `[1, 2, 3]` |
|
|
797
|
+
| `'string'` | | `z.string()` | `['4651e634-...', 'some_unique_id']` |
|
|
798
|
+
|
|
799
|
+
Throws for invalid `idType` values.
|
|
800
|
+
|
|
801
|
+
### `jsonContent`
|
|
802
|
+
|
|
803
|
+
Creates an OpenAPI JSON content specification:
|
|
804
|
+
|
|
805
|
+
```typescript
|
|
806
|
+
jsonContent<T extends z.ZodType>(opts: {
|
|
807
|
+
schema: T;
|
|
808
|
+
description: string;
|
|
809
|
+
required?: boolean;
|
|
810
|
+
}): { description, content: { 'application/json': { schema } }, required? }
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
### `jsonResponse`
|
|
814
|
+
|
|
815
|
+
Creates a complete OpenAPI response specification with success and error responses:
|
|
816
|
+
|
|
817
|
+
```typescript
|
|
818
|
+
jsonResponse<ContentSchema, HeaderSchema>(opts: {
|
|
819
|
+
schema: ContentSchema;
|
|
820
|
+
description?: string; // Default: 'Success Response'
|
|
821
|
+
required?: boolean;
|
|
822
|
+
headers?: HeaderSchema;
|
|
823
|
+
}): {
|
|
824
|
+
200: { description, content, headers? },
|
|
825
|
+
'4xx | 5xx': { description: 'Error Response', content: ErrorSchema }
|
|
826
|
+
}
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
### `snakeToCamel`
|
|
830
|
+
|
|
831
|
+
Converts a Zod schema from snake_case to camelCase, transforming both the schema shape and runtime data. Useful when a database uses snake_case column names but you work in camelCase.
|
|
832
|
+
|
|
833
|
+
```typescript
|
|
834
|
+
snakeToCamel<T extends z.ZodRawShape>(shape: T): z.ZodEffects<...>
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
The resulting schema, in order:
|
|
838
|
+
|
|
839
|
+
1. Accepts snake_case input, validated against the original schema.
|
|
840
|
+
2. Transforms keys to camelCase at runtime.
|
|
841
|
+
3. Validates the transformed data against a camelCase schema.
|
|
842
|
+
|
|
843
|
+
```typescript
|
|
844
|
+
import { z } from 'zod';
|
|
845
|
+
import { snakeToCamel } from '@venizia/ignis';
|
|
846
|
+
|
|
847
|
+
const userCamelSchema = snakeToCamel({
|
|
848
|
+
user_id: z.number(),
|
|
849
|
+
first_name: z.string(),
|
|
850
|
+
created_at: z.date(),
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
const result = userCamelSchema.parse({ user_id: 123, first_name: 'John', created_at: new Date() });
|
|
854
|
+
// { userId: 123, firstName: 'John', createdAt: Date }
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
Built on `keysToCamel()` / `toCamel()` from `@venizia/ignis-helpers`; recursively handles nested objects and preserves arrays.
|
|
858
|
+
|
|
859
|
+
### `getIdType`
|
|
860
|
+
|
|
861
|
+
There are two distinct `getIdType`s in the framework - do not confuse them.
|
|
862
|
+
|
|
863
|
+
| | Neutral instance method | PostgreSQL utility function |
|
|
864
|
+
|---|---|---|
|
|
865
|
+
| Location | `AbstractEntity.getIdType()` ([`base/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/models/base.ts)) | `getIdType()` ([`connectors/postgres/models/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/connectors/postgres/models/common/types.ts)) |
|
|
866
|
+
| Signature | `getIdType(): TIdSchemaType` | `getIdType<T extends TTableSchemaWithId>(opts: { entity: T }): string` |
|
|
867
|
+
| Purpose | Neutral capability every engine's entity implements - returns `'string'` \| `'number'` at the entity level. Used by `idParamsSchema` to build the right Zod schema for path parameters. | PostgreSQL-specific - inspects a Drizzle table schema's `id` column and returns its `dataType`, or `'unknown'` if not determinable |
|
|
868
|
+
|
|
869
|
+
```typescript
|
|
870
|
+
// Neutral - instance method (default 'string'; BaseEntity overrides based on the column)
|
|
871
|
+
const entity = new User();
|
|
872
|
+
entity.getIdType(); // 'string' | 'number'
|
|
873
|
+
|
|
874
|
+
// PostgreSQL connector - standalone utility inspecting a raw Drizzle schema
|
|
875
|
+
import { getIdType } from '@venizia/ignis/postgres';
|
|
876
|
+
getIdType({ entity: User.schema }); // 'string' | 'number' | 'unknown'
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
## See also
|
|
880
|
+
|
|
881
|
+
- [Models overview](/references/base/models) - introduction and common tasks
|
|
882
|
+
- [Tutorial](/guides/core-concepts/persistent/models) - creating models step by step
|
|
883
|
+
- [Repositories](/references/base/repositories/) - the data access layer
|
|
884
|
+
- [Relations](/references/base/repositories/relations) - model relationships
|
|
885
|
+
- [Filter System](/references/base/filter-system/) - querying models
|
|
886
|
+
- [Connectors](/references/base/connectors) - the base-vs-connector architecture
|