@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
|
@@ -1,1525 +1,153 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Models
|
|
3
|
-
description:
|
|
2
|
+
title: Models
|
|
3
|
+
description: Declare a database table's schema and behavior with a model class
|
|
4
4
|
difficulty: intermediate
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
#
|
|
7
|
+
# Models
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
A model is a class that declares a database table's schema and behavior in one place.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
> The engine-neutral root `AbstractEntity` (`packages/core/src/base/models/base.ts`) has no Drizzle, no `pgTable`, and no `drizzle-zod` - just a `name`, an abstract `getSchema()`, a `getIdType(): TIdSchemaType` method (default `'string'`), and `toObject()`/`toJSON()`. Everything described below - the Drizzle-backed `BaseEntity`, `drizzle-zod` schema generation, and all schema enrichers - belongs to the **PostgreSQL connector**'s `BasePostgresEntity`, not the neutral base. See [Connectors](./connectors) for the full base-vs-connectors architecture.
|
|
11
|
+
## In one example
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
- `packages/core/src/base/models/base.ts` (neutral `AbstractEntity`)
|
|
16
|
-
- `packages/core/src/connectors/postgres/models/base.ts` (PostgreSQL `BasePostgresEntity`)
|
|
17
|
-
- `packages/core/src/connectors/postgres/models/enrichers/*.ts`
|
|
18
|
-
|
|
19
|
-
## Quick Reference
|
|
20
|
-
|
|
21
|
-
| Component | Purpose | Key Features |
|
|
22
|
-
|-----------|---------|--------------|
|
|
23
|
-
| **BasePostgresEntity** (alias: `BaseEntity`) | Wraps Drizzle schema | Schema encapsulation, Zod generation, `toObject()`/`toJSON()` |
|
|
24
|
-
| **Schema Enrichers** | Add common columns to tables | `generateIdColumnDefs()`, `generateTzColumnDefs()`, etc. |
|
|
25
|
-
|
|
26
|
-
## `BasePostgresEntity` Class (alias: `BaseEntity`)
|
|
27
|
-
|
|
28
|
-
PostgreSQL connector's entity class, wrapping a Drizzle ORM schema. Extends the neutral `AbstractEntity`.
|
|
29
|
-
|
|
30
|
-
**File:** `packages/core/src/connectors/postgres/models/base.ts`
|
|
31
|
-
|
|
32
|
-
> [!TIP] Naming
|
|
33
|
-
> `BasePostgresEntity` is the canonical, engine-carrying name. `BaseEntity` is a compatibility alias re-exporting the same class from `connectors/postgres/models/index.ts` (`export { BasePostgresEntity as BaseEntity } from './base'`) - both resolve to identical runtime behavior. Code samples on this page use `BaseEntity` since it remains the most common import today.
|
|
34
|
-
|
|
35
|
-
### Purpose
|
|
36
|
-
|
|
37
|
-
| Feature | Description |
|
|
38
|
-
|---------|-------------|
|
|
39
|
-
| **Schema Encapsulation** | Holds Drizzle `pgTable` schema for consistent repository access |
|
|
40
|
-
| **Metadata** | Works with `@model` decorator to mark database entities |
|
|
41
|
-
| **Schema Generation** | Uses `drizzle-zod` to generate Zod schemas (`select`, `create`, `update`) |
|
|
42
|
-
| **Static Properties** | Supports static `schema`, `relations`, `TABLE_NAME`, and `AUTHORIZATION_SUBJECT` |
|
|
43
|
-
| **Convenience** | Includes `toObject()` and `toJSON()` methods |
|
|
44
|
-
|
|
45
|
-
### The `@model` Decorator
|
|
46
|
-
|
|
47
|
-
The `@model` decorator marks a class as a database entity and configures its behavior.
|
|
48
|
-
|
|
49
|
-
**File:** `packages/core/src/base/metadata/persistents.ts`
|
|
50
|
-
|
|
51
|
-
#### Decorator Options
|
|
52
|
-
|
|
53
|
-
```typescript
|
|
54
|
-
@model({
|
|
55
|
-
type: 'entity' | 'view',
|
|
56
|
-
tableName?: string,
|
|
57
|
-
skipMigrate?: boolean,
|
|
58
|
-
settings?: {
|
|
59
|
-
hiddenProperties?: string[], // Properties to exclude from query results
|
|
60
|
-
defaultFilter?: TFilter, // Filter applied to all repository queries
|
|
61
|
-
defaultLimit?: number, // Default row limit when a query omits `limit`
|
|
62
|
-
authorize?: { // Authorization settings
|
|
63
|
-
principal: string, // Authorization subject name
|
|
64
|
-
[extra: string | symbol]: any, // Extensible metadata
|
|
65
|
-
},
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
| Option | Type | Description |
|
|
71
|
-
|--------|------|-------------|
|
|
72
|
-
| `type` | `'entity' \| 'view'` | Entity type - `'entity'` for tables, `'view'` for database views |
|
|
73
|
-
| `tableName` | `string` | Optional custom table name. Resolution order: `tableName` > static `TABLE_NAME` > class name |
|
|
74
|
-
| `skipMigrate` | `boolean` | Skip this model during schema migrations |
|
|
75
|
-
| `settings.hiddenProperties` | `string[]` | Array of property names to exclude from all repository query results |
|
|
76
|
-
| `settings.defaultFilter` | `TFilter` | Filter automatically applied to all repository queries (see [Default Filter](/references/base/filter-system/default-filter)) |
|
|
77
|
-
| `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) |
|
|
78
|
-
| `settings.authorize` | `IModelAuthorizeSettings` | Authorization settings - declares the model's authorization principal (see [Authorization](/extensions/components/authorization/usage#model-based-resource-references)) |
|
|
79
|
-
| `settings.authorize.principal` | `string` | The authorization subject name for this model. Auto-populates `AUTHORIZATION_SUBJECT` static property |
|
|
80
|
-
|
|
81
|
-
#### `@model` Behavior
|
|
82
|
-
|
|
83
|
-
When the `@model` decorator is applied:
|
|
84
|
-
1. If `settings.defaultLimit` is provided, it is validated to be a positive integer - otherwise the decorator throws at decoration (boot) time
|
|
85
|
-
2. If `settings.authorize.principal` is provided and `AUTHORIZATION_SUBJECT` is not already defined on the class, it auto-populates `AUTHORIZATION_SUBJECT` with the principal value
|
|
86
|
-
3. The model is registered in the `MetadataRegistry` model registry, keyed by table name (resolved as: `metadata.tableName` > `static TABLE_NAME` > class name)
|
|
87
|
-
4. The static `relations` property is stored as a resolver (not immediately resolved) to avoid circular dependency issues between models
|
|
88
|
-
|
|
89
|
-
### Hidden Properties
|
|
90
|
-
|
|
91
|
-
Hidden properties are **excluded at the SQL level** - they are never fetched from the database when querying through repositories. This provides:
|
|
92
|
-
|
|
93
|
-
- **Security**: Sensitive data like passwords are never accidentally exposed
|
|
94
|
-
- **Performance**: Less data transferred from database
|
|
95
|
-
- **Consistency**: Hidden properties are excluded from ALL repository operations
|
|
13
|
+
The smallest real model: a Drizzle table wrapped in a class, registered with `@model`.
|
|
96
14
|
|
|
97
15
|
```typescript
|
|
98
16
|
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
99
17
|
import { BaseEntity, model, generateIdColumnDefs } from '@venizia/ignis';
|
|
100
18
|
|
|
101
|
-
@model({
|
|
102
|
-
type: 'entity',
|
|
103
|
-
settings: {
|
|
104
|
-
hiddenProperties: ['password', 'secret'], // Never returned via repository
|
|
105
|
-
},
|
|
106
|
-
})
|
|
19
|
+
@model({ type: 'entity' })
|
|
107
20
|
export class User extends BaseEntity<typeof User.schema> {
|
|
108
21
|
static override schema = pgTable('User', {
|
|
109
22
|
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
110
23
|
email: text('email').notNull(),
|
|
111
|
-
password: text('password'), // Hidden - never in query results
|
|
112
|
-
secret: text('secret'), // Hidden - never in query results
|
|
113
24
|
});
|
|
114
25
|
}
|
|
115
26
|
```
|
|
116
27
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
| Operation | Hidden Properties |
|
|
120
|
-
|-----------|-------------------|
|
|
121
|
-
| `find()`, `findOne()`, `findById()` | Excluded from SELECT |
|
|
122
|
-
| `create()`, `createAll()` | Excluded from RETURNING |
|
|
123
|
-
| `updateById()`, `updateAll()` | Excluded from RETURNING |
|
|
124
|
-
| `deleteById()`, `deleteAll()` | Excluded from RETURNING |
|
|
125
|
-
| `count()`, `existsWith()` | Can filter by hidden fields |
|
|
126
|
-
| Direct connector query | **Included** (bypasses repository) |
|
|
127
|
-
|
|
128
|
-
#### Important Notes
|
|
129
|
-
|
|
130
|
-
- Hidden properties can still be used in `where` clauses for filtering
|
|
131
|
-
- Data is still **stored** in the database - only excluded from query results
|
|
132
|
-
- Use direct connector queries when you need to access hidden data:
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
// Repository query - password/secret NOT included
|
|
136
|
-
const user = await userRepository.findById({ id: '123' });
|
|
137
|
-
// user = { id: '123', email: 'john@example.com' }
|
|
138
|
-
|
|
139
|
-
// Direct connector query - ALL fields included
|
|
140
|
-
const connector = userRepository.getConnector();
|
|
141
|
-
const [fullUser] = await connector
|
|
142
|
-
.select()
|
|
143
|
-
.from(User.schema)
|
|
144
|
-
.where(eq(User.schema.id, '123'));
|
|
145
|
-
// fullUser = { id: '123', email: 'john@example.com', password: 'hashed...', secret: '...' }
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Default Filter
|
|
149
|
-
|
|
150
|
-
Default filters are **automatically applied** to all repository queries for a model. This is useful for:
|
|
151
|
-
|
|
152
|
-
- **Soft Delete**: Automatically exclude deleted records
|
|
153
|
-
- **Multi-Tenancy**: Isolate data by tenant
|
|
154
|
-
- **Active Records**: Filter to active/non-expired records
|
|
155
|
-
- **Query Limits**: Prevent unbounded queries
|
|
156
|
-
|
|
157
|
-
```typescript
|
|
158
|
-
@model({
|
|
159
|
-
type: 'entity',
|
|
160
|
-
settings: {
|
|
161
|
-
defaultFilter: {
|
|
162
|
-
where: { isDeleted: false }, // Applied to all queries
|
|
163
|
-
limit: 100, // Prevents unbounded queries
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
})
|
|
167
|
-
export class Post extends BaseEntity<typeof Post.schema> {
|
|
168
|
-
static override schema = postTable;
|
|
169
|
-
}
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
#### Behavior
|
|
173
|
-
|
|
174
|
-
| Operation | Default Filter |
|
|
175
|
-
|-----------|----------------|
|
|
176
|
-
| `find()`, `findOne()`, `findById()` | Applied to WHERE clause |
|
|
177
|
-
| `count()`, `existsWith()` | Applied to WHERE clause |
|
|
178
|
-
| `updateById()`, `updateAll()` | Applied to WHERE clause |
|
|
179
|
-
| `deleteById()`, `deleteAll()` | Applied to WHERE clause |
|
|
180
|
-
| `create()`, `createAll()` | **Not applied** |
|
|
181
|
-
|
|
182
|
-
#### Bypassing
|
|
183
|
-
|
|
184
|
-
Use `shouldSkipDefaultFilter: true` to bypass:
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
// Normal query - includes default filter
|
|
188
|
-
await postRepository.find({ filter: {} });
|
|
189
|
-
// WHERE isDeleted = false LIMIT 100
|
|
190
|
-
|
|
191
|
-
// Admin query - bypass default filter
|
|
192
|
-
await postRepository.find({
|
|
193
|
-
filter: {},
|
|
194
|
-
options: { shouldSkipDefaultFilter: true }
|
|
195
|
-
});
|
|
196
|
-
// No WHERE clause (includes deleted)
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
> [!TIP]
|
|
200
|
-
> See [Default Filter](/references/base/filter-system/default-filter) for full documentation including merge strategies and common patterns.
|
|
201
|
-
|
|
202
|
-
### Definition Patterns
|
|
203
|
-
|
|
204
|
-
`BaseEntity` supports two patterns for defining models:
|
|
205
|
-
|
|
206
|
-
#### Pattern 1: Static Properties (Recommended)
|
|
207
|
-
|
|
208
|
-
Define schema and relations as static properties:
|
|
209
|
-
|
|
210
|
-
```typescript
|
|
211
|
-
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
212
|
-
import { BaseEntity, model, generateIdColumnDefs, createRelations } from '@venizia/ignis';
|
|
213
|
-
|
|
214
|
-
// Define table schema
|
|
215
|
-
export const userTable = pgTable('User', {
|
|
216
|
-
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
217
|
-
name: text('name').notNull(),
|
|
218
|
-
email: text('email').notNull(),
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
// Define relations
|
|
222
|
-
export const userRelations = createRelations({
|
|
223
|
-
source: userTable,
|
|
224
|
-
relations: [],
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
// Entity class with static properties
|
|
228
|
-
@model({ type: 'entity' })
|
|
229
|
-
export class User extends BaseEntity<typeof User.schema> {
|
|
230
|
-
static override schema = userTable;
|
|
231
|
-
static override relations = () => userRelations.definitions;
|
|
232
|
-
static override TABLE_NAME = 'User';
|
|
233
|
-
}
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
**Benefits:**
|
|
237
|
-
- Schema and relations are auto-resolved by repositories
|
|
238
|
-
- No need to pass `relations` in repository constructor
|
|
239
|
-
- Cleaner, more declarative syntax
|
|
240
|
-
|
|
241
|
-
#### Pattern 2: Constructor-Based (Legacy)
|
|
242
|
-
|
|
243
|
-
Pass schema in constructor:
|
|
244
|
-
|
|
245
|
-
```typescript
|
|
246
|
-
@model({ type: 'entity' })
|
|
247
|
-
export class User extends BaseEntity<typeof userTable> {
|
|
248
|
-
constructor() {
|
|
249
|
-
super({ name: 'User', schema: userTable });
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
### Static Properties
|
|
255
|
-
|
|
256
|
-
| Property | Type | Description |
|
|
257
|
-
|----------|------|-------------|
|
|
258
|
-
| `schema` | `TTableSchemaWithId` | Drizzle table schema defined with `pgTable()` |
|
|
259
|
-
| `relations` | `TValueOrResolver<Array<TRelationConfig>>` | Relation definitions (can be a function for lazy loading to avoid circular deps) |
|
|
260
|
-
| `TABLE_NAME` | `string \| undefined` | Optional table name (defaults to class name if not set) |
|
|
261
|
-
| `AUTHORIZATION_SUBJECT` | `string \| undefined` | Authorization principal name. Auto-populated from `@model` settings `authorize.principal` |
|
|
262
|
-
|
|
263
|
-
### IEntity Interface
|
|
264
|
-
|
|
265
|
-
Models implementing static properties conform to the `IEntity` interface:
|
|
266
|
-
|
|
267
|
-
```typescript
|
|
268
|
-
interface IEntity<Schema extends TTableSchemaWithId = TTableSchemaWithId> {
|
|
269
|
-
TABLE_NAME?: string;
|
|
270
|
-
schema: Schema;
|
|
271
|
-
relations?: TValueOrResolver<Array<TRelationConfig>>;
|
|
272
|
-
}
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### Instance Methods
|
|
276
|
-
|
|
277
|
-
| Method | Description |
|
|
278
|
-
|--------|-------------|
|
|
279
|
-
| `getSchema({ type })` | Get Zod schema for validation (`'select'`, `'create'`, `'update'`) |
|
|
280
|
-
| `toObject()` | Convert to plain object (shallow spread of `this`) |
|
|
281
|
-
| `toJSON()` | Delegates to `toObject()` - returns a plain object (used by `JSON.stringify`) |
|
|
282
|
-
|
|
283
|
-
### `getSchema` Method
|
|
284
|
-
|
|
285
|
-
Generates a Zod validation schema from the Drizzle table schema using `drizzle-zod`.
|
|
286
|
-
|
|
287
|
-
```typescript
|
|
288
|
-
getSchema(opts: { type: TSchemaType }): ZodSchema
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
The `type` parameter accepts lowercase string values defined in the `SchemaTypes` class:
|
|
292
|
-
|
|
293
|
-
| Type | Value | Zod Schema Generated | Description |
|
|
294
|
-
|------|-------|---------------------|-------------|
|
|
295
|
-
| `SchemaTypes.SELECT` | `'select'` | `createSelectSchema(schema)` | Schema for query results |
|
|
296
|
-
| `SchemaTypes.CREATE` | `'create'` | `createInsertSchema(schema)` | Schema for insert operations |
|
|
297
|
-
| `SchemaTypes.UPDATE` | `'update'` | `createUpdateSchema(schema)` | Schema for update operations |
|
|
298
|
-
|
|
299
|
-
```typescript
|
|
300
|
-
const user = new User();
|
|
301
|
-
|
|
302
|
-
// Get Zod schema for validating insert data
|
|
303
|
-
const createSchema = user.getSchema({ type: 'create' });
|
|
304
|
-
|
|
305
|
-
// Get Zod schema for validating query results
|
|
306
|
-
const selectSchema = user.getSchema({ type: 'select' });
|
|
307
|
-
|
|
308
|
-
// Get Zod schema for validating update data
|
|
309
|
-
const updateSchema = user.getSchema({ type: 'update' });
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
The `schemaFactory` is a static lazy singleton created via `drizzle-zod`'s `createSchemaFactory()`, shared across all `BaseEntity` instances to avoid per-entity overhead.
|
|
313
|
-
|
|
314
|
-
### Class Definition
|
|
315
|
-
|
|
316
|
-
```typescript
|
|
317
|
-
export class BasePostgresEntity<Schema extends TTableSchemaWithId = TTableSchemaWithId>
|
|
318
|
-
extends AbstractEntity
|
|
319
|
-
implements IEntity<Schema>
|
|
320
|
-
{
|
|
321
|
-
// Instance property (name, toObject(), toJSON() are inherited from AbstractEntity)
|
|
322
|
-
schema: Schema;
|
|
323
|
-
|
|
324
|
-
// Static properties - override in subclass
|
|
325
|
-
static schema: TTableSchemaWithId;
|
|
326
|
-
static relations?: TValueOrResolver<Array<TRelationConfig>>;
|
|
327
|
-
static TABLE_NAME?: string; // Optional, defaults to class name
|
|
328
|
-
static AUTHORIZATION_SUBJECT?: string; // Auto-set by @model decorator from authorize.principal
|
|
329
|
-
|
|
330
|
-
// Static singleton for schemaFactory - shared across all instances
|
|
331
|
-
// Performance optimization: avoids creating new factory per entity
|
|
332
|
-
private static _schemaFactory?: ReturnType<typeof createSchemaFactory>;
|
|
333
|
-
protected static get schemaFactory(): ReturnType<typeof createSchemaFactory> {
|
|
334
|
-
return (BasePostgresEntity._schemaFactory ??= createSchemaFactory());
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Constructor supports both patterns
|
|
338
|
-
constructor(opts?: { name?: string; schema?: Schema }) {
|
|
339
|
-
const ctor = new.target as typeof BasePostgresEntity;
|
|
340
|
-
// Resolution order: opts.name > static TABLE_NAME > class name
|
|
341
|
-
const name = opts?.name ?? ctor.TABLE_NAME ?? ctor.name;
|
|
342
|
-
|
|
343
|
-
super({ name });
|
|
344
|
-
|
|
345
|
-
this.schema = opts?.schema || (ctor.schema as Schema);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Maps the pgTable id column's Drizzle dataType to 'number' or 'string'
|
|
349
|
-
override getIdType(): TIdSchemaType {
|
|
350
|
-
return getIdType({ entity: this.schema }) === 'number' ? 'number' : 'string';
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
getSchema(opts: { type: TSchemaType }) {
|
|
354
|
-
const factory = BasePostgresEntity.schemaFactory; // Uses static singleton
|
|
355
|
-
switch (opts.type) {
|
|
356
|
-
case SchemaTypes.CREATE:
|
|
357
|
-
return factory.createInsertSchema(this.schema);
|
|
358
|
-
case SchemaTypes.UPDATE:
|
|
359
|
-
return factory.createUpdateSchema(this.schema);
|
|
360
|
-
case SchemaTypes.SELECT:
|
|
361
|
-
return factory.createSelectSchema(this.schema);
|
|
362
|
-
default:
|
|
363
|
-
throw getError({
|
|
364
|
-
message: `[getSchema] Invalid schema type | type: ${opts.type}`,
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
## Key Types
|
|
372
|
-
|
|
373
|
-
### `TTableSchemaWithId`
|
|
374
|
-
|
|
375
|
-
Ensures a Drizzle `PgTable` has an `id` column:
|
|
376
|
-
|
|
377
|
-
```typescript
|
|
378
|
-
type TTableSchemaWithId<TC extends TableConfig = TableConfig> = PgTable<TC> & {
|
|
379
|
-
id: TIdColumn;
|
|
380
|
-
};
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
### `TTableObject`
|
|
384
|
-
|
|
385
|
-
Infers the select (output) type from a table schema:
|
|
386
|
-
|
|
387
|
-
```typescript
|
|
388
|
-
type TTableObject<T extends TTableSchemaWithId> = T['$inferSelect'];
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
### `TTableInsert`
|
|
392
|
-
|
|
393
|
-
Infers the insert (input) type from a table schema:
|
|
394
|
-
|
|
395
|
-
```typescript
|
|
396
|
-
type TTableInsert<T extends TTableSchemaWithId> = T['$inferInsert'];
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
### `TGetIdType`
|
|
400
|
-
|
|
401
|
-
Extracts the `id` field type from a table schema:
|
|
402
|
-
|
|
403
|
-
```typescript
|
|
404
|
-
type TGetIdType<T extends TTableSchemaWithId> = TTableObject<T>['id'];
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
### `IdType`
|
|
408
|
-
|
|
409
|
-
Union of supported ID types:
|
|
410
|
-
|
|
411
|
-
```typescript
|
|
412
|
-
type NumberIdType = number;
|
|
413
|
-
type StringIdType = string;
|
|
414
|
-
type BigIntIdType = bigint;
|
|
415
|
-
type IdType = NumberIdType | StringIdType | BigIntIdType;
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
### `TRelationConfig`
|
|
419
|
-
|
|
420
|
-
Configuration for entity relationships:
|
|
421
|
-
|
|
422
|
-
```typescript
|
|
423
|
-
type TRelationConfig = {
|
|
424
|
-
name: string;
|
|
425
|
-
} & (
|
|
426
|
-
| { type: 'one'; schema: TTableSchemaWithId; metadata: /* Drizzle one() params */ }
|
|
427
|
-
| { type: 'many'; schema: TTableSchemaWithId; metadata: /* Drizzle many() params */ }
|
|
428
|
-
);
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
Relation types are defined in the `RelationTypes` class:
|
|
432
|
-
|
|
433
|
-
| Type | Value | Description |
|
|
434
|
-
|------|-------|-------------|
|
|
435
|
-
| `RelationTypes.ONE` | `'one'` | One-to-one or many-to-one relationship |
|
|
436
|
-
| `RelationTypes.MANY` | `'many'` | One-to-many relationship |
|
|
437
|
-
|
|
438
|
-
### `TValueOrResolver`
|
|
439
|
-
|
|
440
|
-
From `@venizia/ignis-helpers`, enables lazy resolution to avoid circular dependencies:
|
|
441
|
-
|
|
442
|
-
```typescript
|
|
443
|
-
type TValueOrResolver<T> = T | TResolver<T>; // T or () => T
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
Used for `relations` on `BaseEntity` - store a function that returns the relations array, resolved lazily when `DataSource.buildSchema()` is called.
|
|
447
|
-
|
|
448
|
-
## Schema Enrichers
|
|
449
|
-
|
|
450
|
-
Enrichers are helper functions located in `packages/core/src/connectors/postgres/models/enrichers/` that return an object of Drizzle ORM column definitions. They are designed to be spread into a `pgTable` definition to quickly add common, standardized fields to your models.
|
|
451
|
-
|
|
452
|
-
### Available Enrichers
|
|
453
|
-
|
|
454
|
-
| Enricher Function | Convenience Wrapper | Purpose |
|
|
455
|
-
| :--- | :--- | :--- |
|
|
456
|
-
| **`generateIdColumnDefs`** | `enrichId` | Adds a primary key `id` column (string UUID, numeric integer, or big integer). |
|
|
457
|
-
| **`generateTzColumnDefs`** | `enrichTz` | Adds `createdAt`, `modifiedAt`, and `deletedAt` timestamp columns with timezone support. |
|
|
458
|
-
| **`generateUserAuditColumnDefs`** | `enrichUserAudit` | Adds `createdBy` and `modifiedBy` columns to track user audit information. |
|
|
459
|
-
| **`generatePrincipalColumnDefs`** | `enrichPrincipal` | Adds polymorphic principal columns (`{discriminator}Id` and `{discriminator}Type`). |
|
|
460
|
-
| **`generateDataTypeColumnDefs`** | `enrichDataTypes` | Adds generic data type columns (`dataType`, `nValue`, `tValue`, `bValue`, `jValue`, `boValue`) for flexible data storage. |
|
|
461
|
-
| **`extraUserColumns`** | - | Adds common user fields (`realm`, `status`, `type`, `activatedAt`, `lastLoginAt`, `parentId`). Imported from `@venizia/ignis` (part of auth component). |
|
|
462
|
-
|
|
463
|
-
Each `generate*` function returns column definition objects for spreading into `pgTable`. The `enrich*` convenience wrappers accept an existing `TColumnDefinitions` object as the first argument and merge the generated columns into it.
|
|
464
|
-
|
|
465
|
-
### Example Usage
|
|
466
|
-
|
|
467
|
-
```typescript
|
|
468
|
-
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
469
|
-
import {
|
|
470
|
-
generateIdColumnDefs,
|
|
471
|
-
generateTzColumnDefs,
|
|
472
|
-
generateUserAuditColumnDefs,
|
|
473
|
-
} from '@venizia/ignis';
|
|
474
|
-
|
|
475
|
-
export const myTable = pgTable('MyTable', {
|
|
476
|
-
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
477
|
-
...generateTzColumnDefs(),
|
|
478
|
-
...generateUserAuditColumnDefs({
|
|
479
|
-
created: { dataType: 'string', columnName: 'created_by' },
|
|
480
|
-
modified: { dataType: 'string', columnName: 'modified_by' },
|
|
481
|
-
}),
|
|
482
|
-
name: text('name').notNull(),
|
|
483
|
-
});
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
## Detailed Enricher Reference
|
|
488
|
-
|
|
489
|
-
### `generateIdColumnDefs`
|
|
490
|
-
|
|
491
|
-
Adds a primary key `id` column with support for string UUID, integer, or big integer types with full TypeScript type inference.
|
|
492
|
-
|
|
493
|
-
**File:** `packages/core/src/connectors/postgres/models/enrichers/id.enricher.ts`
|
|
494
|
-
|
|
495
|
-
#### Signature
|
|
496
|
-
|
|
497
|
-
```typescript
|
|
498
|
-
generateIdColumnDefs<Opts extends TIdEnricherOptions | undefined>(
|
|
499
|
-
opts?: Opts,
|
|
500
|
-
): TIdColumnDef<Opts>
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
#### Options (`TIdEnricherOptions`)
|
|
504
|
-
|
|
505
|
-
```typescript
|
|
506
|
-
type TIdEnricherOptions = {
|
|
507
|
-
id?: { columnName?: string } & (
|
|
508
|
-
| { dataType: 'string'; generator?: () => string } // Optional custom ID generator
|
|
509
|
-
| {
|
|
510
|
-
dataType: 'number';
|
|
511
|
-
sequenceOptions?: PgSequenceOptions;
|
|
512
|
-
}
|
|
513
|
-
| {
|
|
514
|
-
dataType: 'big-number';
|
|
515
|
-
numberMode: 'number' | 'bigint'; // Required for big-number
|
|
516
|
-
sequenceOptions?: PgSequenceOptions;
|
|
517
|
-
}
|
|
518
|
-
);
|
|
519
|
-
};
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
**Default values:**
|
|
523
|
-
- `dataType`: `'number'` (auto-incrementing integer)
|
|
524
|
-
- `columnName`: `'id'`
|
|
525
|
-
|
|
526
|
-
#### Generated Columns
|
|
527
|
-
|
|
528
|
-
| Data Type | Column Type | Constraints | Description |
|
|
529
|
-
|-----------|------------|-------------|-------------|
|
|
530
|
-
| `'string'` | `text` | Primary Key, Default: `crypto.randomUUID()` | Text column with customizable ID generator (default: UUID) |
|
|
531
|
-
| `'number'` | `integer` | Primary Key, `GENERATED ALWAYS AS IDENTITY` | Auto-incrementing integer |
|
|
532
|
-
| `'big-number'` | `bigint` | Primary Key, `GENERATED ALWAYS AS IDENTITY` | Auto-incrementing big integer (mode: 'number' or 'bigint') |
|
|
533
|
-
|
|
534
|
-
#### Type Inference
|
|
535
|
-
|
|
536
|
-
The function provides **full TypeScript type inference** based on the configuration options:
|
|
537
|
-
|
|
538
|
-
```typescript
|
|
539
|
-
// Type aliases for readability
|
|
540
|
-
type TStringIdCol = HasRuntimeDefault<
|
|
541
|
-
HasDefault<IsPrimaryKey<NotNull<PgTextBuilderInitial<'id', [string, ...string[]]>>>>
|
|
542
|
-
>;
|
|
543
|
-
type TNumberIdCol = IsIdentity<IsPrimaryKey<NotNull<PgIntegerBuilderInitial<'id'>>>, 'always'>;
|
|
544
|
-
type TBigInt53IdCol = IsIdentity<IsPrimaryKey<NotNull<PgBigInt53BuilderInitial<'id'>>>, 'always'>;
|
|
545
|
-
type TBigInt64IdCol = IsIdentity<IsPrimaryKey<NotNull<PgBigInt64BuilderInitial<'id'>>>, 'always'>;
|
|
546
|
-
|
|
547
|
-
type TIdColumnDef<Opts extends TIdEnricherOptions | undefined> = Opts extends {
|
|
548
|
-
id: infer IdOpts;
|
|
549
|
-
}
|
|
550
|
-
? IdOpts extends { dataType: 'string' }
|
|
551
|
-
? { id: TStringIdCol }
|
|
552
|
-
: IdOpts extends { dataType: 'number' }
|
|
553
|
-
? { id: TNumberIdCol }
|
|
554
|
-
: IdOpts extends { dataType: 'big-number' }
|
|
555
|
-
? IdOpts extends { numberMode: 'number' }
|
|
556
|
-
? { id: TBigInt53IdCol }
|
|
557
|
-
: { id: TBigInt64IdCol }
|
|
558
|
-
: { id: TNumberIdCol }
|
|
559
|
-
: { id: TNumberIdCol };
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
This ensures that TypeScript correctly infers the exact column type based on your configuration.
|
|
563
|
-
|
|
564
|
-
#### Usage Examples
|
|
565
|
-
|
|
566
|
-
**Default (auto-incrementing integer):**
|
|
567
|
-
|
|
568
|
-
```typescript
|
|
569
|
-
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
570
|
-
import { generateIdColumnDefs } from '@venizia/ignis';
|
|
571
|
-
|
|
572
|
-
export const myTable = pgTable('MyTable', {
|
|
573
|
-
...generateIdColumnDefs(),
|
|
574
|
-
name: text('name').notNull(),
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
// Generates: id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
**Text-based string ID (UUID by default):**
|
|
581
|
-
|
|
582
|
-
```typescript
|
|
583
|
-
export const myTable = pgTable('MyTable', {
|
|
584
|
-
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
585
|
-
name: text('name').notNull(),
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
// Generates: id text PRIMARY KEY with $defaultFn(() => crypto.randomUUID())
|
|
589
|
-
// Uses text column for maximum database compatibility
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
**Custom ID generator (e.g., nanoid, cuid):**
|
|
593
|
-
|
|
594
|
-
```typescript
|
|
595
|
-
import { nanoid } from 'nanoid';
|
|
596
|
-
|
|
597
|
-
export const myTable = pgTable('MyTable', {
|
|
598
|
-
...generateIdColumnDefs({
|
|
599
|
-
id: {
|
|
600
|
-
dataType: 'string',
|
|
601
|
-
generator: () => nanoid(), // Custom generator function
|
|
602
|
-
},
|
|
603
|
-
}),
|
|
604
|
-
name: text('name').notNull(),
|
|
605
|
-
});
|
|
606
|
-
|
|
607
|
-
// Generates: id text PRIMARY KEY with $defaultFn(() => nanoid())
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
**Auto-incrementing integer with sequence options:**
|
|
611
|
-
|
|
612
|
-
```typescript
|
|
613
|
-
export const myTable = pgTable('MyTable', {
|
|
614
|
-
...generateIdColumnDefs({
|
|
615
|
-
id: {
|
|
616
|
-
dataType: 'number',
|
|
617
|
-
sequenceOptions: { startWith: 1000, increment: 1 },
|
|
618
|
-
},
|
|
619
|
-
}),
|
|
620
|
-
name: text('name').notNull(),
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
// Generates: id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (START WITH 1000 INCREMENT BY 1)
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
**Big number with JavaScript number mode (up to 2^53-1):**
|
|
627
|
-
|
|
628
|
-
```typescript
|
|
629
|
-
export const myTable = pgTable('MyTable', {
|
|
630
|
-
...generateIdColumnDefs({
|
|
631
|
-
id: {
|
|
632
|
-
dataType: 'big-number',
|
|
633
|
-
numberMode: 'number', // Required field
|
|
634
|
-
sequenceOptions: { startWith: 1, increment: 1 },
|
|
635
|
-
},
|
|
636
|
-
}),
|
|
637
|
-
name: text('name').notNull(),
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
// Generates: id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY
|
|
641
|
-
// Type-safe: Returns PgBigInt53BuilderInitial (safe for JavaScript numbers)
|
|
642
|
-
```
|
|
643
|
-
|
|
644
|
-
**Big number with BigInt mode (for values > 2^53-1):**
|
|
645
|
-
|
|
646
|
-
```typescript
|
|
647
|
-
export const myTable = pgTable('MyTable', {
|
|
648
|
-
...generateIdColumnDefs({
|
|
649
|
-
id: {
|
|
650
|
-
dataType: 'big-number',
|
|
651
|
-
numberMode: 'bigint', // Required field
|
|
652
|
-
sequenceOptions: { startWith: 1, increment: 1 },
|
|
653
|
-
},
|
|
654
|
-
}),
|
|
655
|
-
name: text('name').notNull(),
|
|
656
|
-
});
|
|
657
|
-
|
|
658
|
-
// Generates: id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY
|
|
659
|
-
// Type-safe: Returns PgBigInt64BuilderInitial (requires BigInt in JavaScript)
|
|
660
|
-
```
|
|
661
|
-
|
|
662
|
-
#### Important Notes
|
|
663
|
-
|
|
664
|
-
- **Text Column:** When using `dataType: 'string'`, a `text` column is used for maximum database compatibility. This allows you to use any ID format (UUID, nanoid, cuid, etc.) without database-specific constraints.
|
|
665
|
-
- **Custom Generator:** You can provide a custom `generator` function to generate IDs. Default is `crypto.randomUUID()`.
|
|
666
|
-
- **Type Safety:** The return type is fully inferred based on your options, providing better autocomplete and type checking
|
|
667
|
-
- **Big Number Mode:** For `dataType: 'big-number'`, the `numberMode` field is required to specify whether to use JavaScript `number` (up to 2^53-1) or `bigint` (for larger values)
|
|
668
|
-
- **Sequence Options:** Available for `number` and `big-number` types to customize identity generation behavior
|
|
669
|
-
|
|
670
|
-
#### Convenience Wrapper: `enrichId`
|
|
671
|
-
|
|
672
|
-
```typescript
|
|
673
|
-
enrichId(baseColumns: TColumnDefinitions, opts?: TIdEnricherOptions): TColumnDefinitions
|
|
674
|
-
```
|
|
675
|
-
|
|
676
|
-
Merges the generated ID column into an existing column definitions object:
|
|
677
|
-
|
|
678
|
-
```typescript
|
|
679
|
-
import { text } from 'drizzle-orm/pg-core';
|
|
680
|
-
import { enrichId } from '@venizia/ignis';
|
|
681
|
-
|
|
682
|
-
const columns = enrichId(
|
|
683
|
-
{ name: text('name').notNull() },
|
|
684
|
-
{ id: { dataType: 'string' } },
|
|
685
|
-
);
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
### `generateTzColumnDefs`
|
|
690
|
-
|
|
691
|
-
Adds timestamp columns for tracking entity creation, modification, and soft deletion.
|
|
692
|
-
|
|
693
|
-
**File:** `packages/core/src/connectors/postgres/models/enrichers/tz.enricher.ts`
|
|
694
|
-
|
|
695
|
-
#### Signature
|
|
696
|
-
|
|
697
|
-
```typescript
|
|
698
|
-
generateTzColumnDefs<Opts extends TTzEnricherOptions | undefined>(
|
|
699
|
-
opts?: Opts,
|
|
700
|
-
): TTzEnricherResult<Opts>
|
|
701
|
-
```
|
|
702
|
-
|
|
703
|
-
#### Options (`TTzEnricherOptions`)
|
|
704
|
-
|
|
705
|
-
```typescript
|
|
706
|
-
type TTzEnricherOptions = {
|
|
707
|
-
created?: { columnName: string; withTimezone: boolean };
|
|
708
|
-
modified?: { enable: false } | { enable?: true; columnName: string; withTimezone: boolean };
|
|
709
|
-
deleted?: { enable: false } | { enable?: true; columnName: string; withTimezone: boolean };
|
|
710
|
-
};
|
|
711
|
-
```
|
|
712
|
-
|
|
713
|
-
The `modified` and `deleted` options use a discriminated union pattern:
|
|
714
|
-
- When `enable: false`, no other properties are needed
|
|
715
|
-
- When `enable: true` (or omitted), `columnName` and `withTimezone` are required
|
|
716
|
-
|
|
717
|
-
**Default values:**
|
|
718
|
-
- `created`: `{ columnName: 'created_at', withTimezone: true }`
|
|
719
|
-
- `modified`: `{ enable: true, columnName: 'modified_at', withTimezone: true }`
|
|
720
|
-
- `deleted`: `{ enable: false }` (disabled by default)
|
|
721
|
-
|
|
722
|
-
#### Generated Columns
|
|
723
|
-
|
|
724
|
-
| Column | Type | Constraints | Default | Description |
|
|
725
|
-
|--------|------|-------------|---------|-------------|
|
|
726
|
-
| `createdAt` | `timestamp` | `NOT NULL` | `now()` | When the record was created (always included) |
|
|
727
|
-
| `modifiedAt` | `timestamp` | `NOT NULL` | `now()`, auto-updates via `$onUpdate(() => new Date().toISOString())` | When the record was last modified (optional, enabled by default) |
|
|
728
|
-
| `deletedAt` | `timestamp` | nullable | `null` | When the record was soft-deleted (optional, **disabled by default**) |
|
|
729
|
-
|
|
730
|
-
#### Usage Examples
|
|
731
|
-
|
|
732
|
-
**Basic usage (default columns):**
|
|
733
|
-
|
|
734
|
-
```typescript
|
|
735
|
-
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
736
|
-
import { generateTzColumnDefs } from '@venizia/ignis';
|
|
737
|
-
|
|
738
|
-
export const myTable = pgTable('MyTable', {
|
|
739
|
-
...generateTzColumnDefs(),
|
|
740
|
-
name: text('name').notNull(),
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
// Generates: createdAt, modifiedAt (deletedAt is disabled by default)
|
|
744
|
-
```
|
|
745
|
-
|
|
746
|
-
**Enable soft delete:**
|
|
747
|
-
|
|
748
|
-
```typescript
|
|
749
|
-
export const myTable = pgTable('MyTable', {
|
|
750
|
-
...generateTzColumnDefs({
|
|
751
|
-
deleted: { enable: true, columnName: 'deleted_at', withTimezone: true },
|
|
752
|
-
}),
|
|
753
|
-
name: text('name').notNull(),
|
|
754
|
-
});
|
|
755
|
-
|
|
756
|
-
// Generates: createdAt, modifiedAt, deletedAt
|
|
757
|
-
```
|
|
758
|
-
|
|
759
|
-
**Custom column names:**
|
|
760
|
-
|
|
761
|
-
```typescript
|
|
762
|
-
export const myTable = pgTable('MyTable', {
|
|
763
|
-
...generateTzColumnDefs({
|
|
764
|
-
created: { columnName: 'created_date', withTimezone: true },
|
|
765
|
-
modified: { enable: true, columnName: 'updated_date', withTimezone: true },
|
|
766
|
-
deleted: { enable: true, columnName: 'removed_date', withTimezone: true },
|
|
767
|
-
}),
|
|
768
|
-
name: text('name').notNull(),
|
|
769
|
-
});
|
|
770
|
-
```
|
|
771
|
-
|
|
772
|
-
**Without timezone:**
|
|
773
|
-
|
|
774
|
-
```typescript
|
|
775
|
-
export const myTable = pgTable('MyTable', {
|
|
776
|
-
...generateTzColumnDefs({
|
|
777
|
-
created: { columnName: 'created_at', withTimezone: false },
|
|
778
|
-
modified: { enable: true, columnName: 'modified_at', withTimezone: false },
|
|
779
|
-
deleted: { enable: true, columnName: 'deleted_at', withTimezone: false },
|
|
780
|
-
}),
|
|
781
|
-
name: text('name').notNull(),
|
|
782
|
-
});
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
**Minimal setup (only createdAt):**
|
|
788
|
-
|
|
789
|
-
```typescript
|
|
790
|
-
export const myTable = pgTable('MyTable', {
|
|
791
|
-
...generateTzColumnDefs({
|
|
792
|
-
modified: { enable: false },
|
|
793
|
-
deleted: { enable: false },
|
|
794
|
-
}),
|
|
795
|
-
name: text('name').notNull(),
|
|
796
|
-
});
|
|
797
|
-
|
|
798
|
-
// Generates: createdAt only
|
|
799
|
-
```
|
|
800
|
-
|
|
801
|
-
#### Soft Delete Pattern
|
|
802
|
-
|
|
803
|
-
The `deletedAt` column enables the soft delete pattern, where records are marked as deleted rather than physically removed from the database.
|
|
804
|
-
|
|
805
|
-
**Example soft delete query:**
|
|
806
|
-
|
|
807
|
-
```typescript
|
|
808
|
-
import { eq, isNull } from 'drizzle-orm';
|
|
809
|
-
|
|
810
|
-
// Soft delete: set deletedAt timestamp
|
|
811
|
-
await db.update(myTable)
|
|
812
|
-
.set({ deletedAt: new Date() })
|
|
813
|
-
.where(eq(myTable.id, id));
|
|
814
|
-
|
|
815
|
-
// Query only active (non-deleted) records
|
|
816
|
-
const activeRecords = await db.select()
|
|
817
|
-
.from(myTable)
|
|
818
|
-
.where(isNull(myTable.deletedAt));
|
|
819
|
-
|
|
820
|
-
// Query deleted records
|
|
821
|
-
const deletedRecords = await db.select()
|
|
822
|
-
.from(myTable)
|
|
823
|
-
.where(isNotNull(myTable.deletedAt));
|
|
824
|
-
|
|
825
|
-
// Restore a soft-deleted record
|
|
826
|
-
await db.update(myTable)
|
|
827
|
-
.set({ deletedAt: null })
|
|
828
|
-
.where(eq(myTable.id, id));
|
|
829
|
-
```
|
|
830
|
-
|
|
831
|
-
#### Type Inference
|
|
832
|
-
|
|
833
|
-
The enricher provides **conditional TypeScript type inference** based on the options:
|
|
834
|
-
|
|
835
|
-
```typescript
|
|
836
|
-
type TIsoTimestampColumn = ReturnType<typeof isoTimestamp>; // custom ISO 8601 timestamp column
|
|
837
|
-
|
|
838
|
-
type TTzEnricherResult<Opts extends TTzEnricherOptions | undefined = undefined> = {
|
|
839
|
-
createdAt: NotNull<HasDefault<TIsoTimestampColumn>>;
|
|
840
|
-
} & (/* modifiedAt included unless opts.modified.enable === false */)
|
|
841
|
-
& (/* deletedAt included only when opts.deleted.enable === true */);
|
|
842
|
-
```
|
|
843
|
-
|
|
844
|
-
- `createdAt` is always present
|
|
845
|
-
- `modifiedAt` is present by default; excluded only when `modified: { enable: false }`
|
|
846
|
-
- `deletedAt` is absent by default; included only when `deleted: { enable: true, ... }`
|
|
847
|
-
|
|
848
|
-
#### Convenience Wrapper: `enrichTz`
|
|
849
|
-
|
|
850
|
-
```typescript
|
|
851
|
-
enrichTz(baseSchema: TColumnDefinitions, opts?: TTzEnricherOptions): TColumnDefinitions
|
|
852
|
-
```
|
|
853
|
-
|
|
854
|
-
Merges timestamp columns into an existing column definitions object.
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
### `generateUserAuditColumnDefs`
|
|
858
|
-
|
|
859
|
-
Adds `createdBy` and `modifiedBy` columns to track which user created or modified a record.
|
|
860
|
-
|
|
861
|
-
**File:** `packages/core/src/connectors/postgres/models/enrichers/user-audit.enricher.ts`
|
|
862
|
-
|
|
863
|
-
#### Signature
|
|
864
|
-
|
|
865
|
-
```typescript
|
|
866
|
-
generateUserAuditColumnDefs(opts?: TUserAuditEnricherOptions): {
|
|
867
|
-
createdBy: PgIntegerBuilderInitial | PgTextBuilderInitial;
|
|
868
|
-
modifiedBy: PgIntegerBuilderInitial | PgTextBuilderInitial;
|
|
869
|
-
}
|
|
870
|
-
```
|
|
871
|
-
|
|
872
|
-
#### Options (`TUserAuditEnricherOptions`)
|
|
873
|
-
|
|
874
|
-
```typescript
|
|
875
|
-
type TUserAuditColumnOpts = {
|
|
876
|
-
dataType: 'string' | 'number'; // Required - type of user ID
|
|
877
|
-
columnName: string; // Column name in database
|
|
878
|
-
allowAnonymous?: boolean; // Allow null user ID (default: true)
|
|
879
|
-
};
|
|
880
|
-
|
|
881
|
-
type TUserAuditEnricherOptions = {
|
|
882
|
-
created?: TUserAuditColumnOpts;
|
|
883
|
-
modified?: TUserAuditColumnOpts;
|
|
884
|
-
};
|
|
885
|
-
```
|
|
886
|
-
|
|
887
|
-
**Default values:**
|
|
888
|
-
- `created`: `{ dataType: 'number', columnName: 'created_by', allowAnonymous: true }`
|
|
889
|
-
- `modified`: `{ dataType: 'number', columnName: 'modified_by', allowAnonymous: true }`
|
|
890
|
-
|
|
891
|
-
#### How It Works
|
|
892
|
-
|
|
893
|
-
The enricher uses Hono's `contextStorage` (via `tryGetContext()`) to automatically retrieve the current user ID from the request context at insert/update time:
|
|
894
|
-
|
|
895
|
-
- **`createdBy`**: Set via `$default()` - only populated on record creation
|
|
896
|
-
- **`modifiedBy`**: Set via both `$default()` and `$onUpdate()` - populated on creation and updated on every modification
|
|
897
|
-
|
|
898
|
-
The user ID is read from the `Authentication.AUDIT_USER_ID` key in the Hono context.
|
|
899
|
-
|
|
900
|
-
#### `allowAnonymous` Behavior
|
|
901
|
-
|
|
902
|
-
The `allowAnonymous` option controls whether the enricher requires an authenticated user context:
|
|
903
|
-
|
|
904
|
-
| `allowAnonymous` | No Context | No User ID | Has User ID |
|
|
905
|
-
|------------------|------------|------------|-------------|
|
|
906
|
-
| `true` (default) | Returns `null` | Returns `null` | Returns user ID |
|
|
907
|
-
| `false` | Throws error | Throws error | Returns user ID |
|
|
28
|
+
A `@repository` binds this model to a datasource, and the datasource auto-discovers the schema from that binding - no manual schema registration.
|
|
908
29
|
|
|
909
|
-
|
|
910
|
-
- Sensitive audit trails that must track the responsible user
|
|
911
|
-
- Tables where anonymous operations should be forbidden
|
|
912
|
-
- Compliance requirements that mandate user attribution
|
|
30
|
+
## How it works
|
|
913
31
|
|
|
914
|
-
**
|
|
915
|
-
-
|
|
916
|
-
-
|
|
917
|
-
-
|
|
32
|
+
- **Registration.** The `@model` decorator registers the class in the framework's metadata registry, keyed by table name (resolved as `tableName` > static `TABLE_NAME` > class name).
|
|
33
|
+
- **Validation at decoration time.** It validates `settings.defaultLimit` and, when you declare an authorization principal, copies it onto the static `AUTHORIZATION_SUBJECT` property.
|
|
34
|
+
- **Plain Drizzle schema.** The static `schema` is a plain Drizzle `pgTable`. Enrichers such as `generateIdColumnDefs` return column definitions you spread into that table, so common columns (id, timestamps, audit, principal) stay standardized across models.
|
|
35
|
+
- **Zod on demand.** `BaseEntity` generates Zod schemas from the Drizzle schema via `getSchema({ type })` - `'select'`, `'create'`, and `'update'` variants for validating query results, inserts, and updates. The generator is a shared lazy singleton, so there is no per-entity cost.
|
|
918
36
|
|
|
919
|
-
|
|
920
|
-
> 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.
|
|
37
|
+
**Two layers**
|
|
921
38
|
|
|
922
|
-
|
|
39
|
+
| Layer | Class | Carries |
|
|
40
|
+
|-------|-------|---------|
|
|
41
|
+
| Engine-neutral root | `AbstractEntity` | A name, `getSchema()`, `getIdType()`, and `toObject()`/`toJSON()` |
|
|
42
|
+
| PostgreSQL connector | `BaseEntity` (canonical class `BaseRelationalEntity`) | Adds the Drizzle-backed schema and Zod generation |
|
|
923
43
|
|
|
924
|
-
|
|
925
|
-
|--------|-----------|-------------|-------------|
|
|
926
|
-
| `createdBy` | `integer` or `text` | `created_by` | User ID who created the record |
|
|
927
|
-
| `modifiedBy` | `integer` or `text` | `modified_by` | User ID who last modified the record |
|
|
44
|
+
Everything on this page is the PostgreSQL connector - see the [Full reference](/references/base/models-reference) and [Connectors](/references/base/connectors) for the base-vs-connector split.
|
|
928
45
|
|
|
929
|
-
|
|
46
|
+
## Common tasks
|
|
930
47
|
|
|
931
|
-
|
|
48
|
+
### Add id and timestamp columns
|
|
932
49
|
|
|
933
|
-
|
|
934
|
-
// Valid
|
|
935
|
-
generateUserAuditColumnDefs({ created: { dataType: 'number', columnName: 'created_by' } });
|
|
936
|
-
generateUserAuditColumnDefs({ created: { dataType: 'string', columnName: 'created_by' } });
|
|
937
|
-
|
|
938
|
-
// Invalid - throws error
|
|
939
|
-
generateUserAuditColumnDefs({ created: { dataType: 'uuid', columnName: 'created_by' } });
|
|
940
|
-
// Error: [enrichUserAudit] Invalid dataType for 'createdBy' | value: uuid | valid: ['number', 'string']
|
|
941
|
-
```
|
|
942
|
-
|
|
943
|
-
#### Usage Examples
|
|
944
|
-
|
|
945
|
-
**Default (integer user IDs):**
|
|
50
|
+
Enrichers return column definitions - spread them into the `pgTable`. `generateIdColumnDefs` adds the primary key; `generateTzColumnDefs` adds `createdAt` and `modifiedAt`.
|
|
946
51
|
|
|
947
52
|
```typescript
|
|
948
53
|
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
949
|
-
import { generateIdColumnDefs,
|
|
950
|
-
|
|
951
|
-
export const myTable = pgTable('MyTable', {
|
|
952
|
-
...generateIdColumnDefs(),
|
|
953
|
-
...generateUserAuditColumnDefs(),
|
|
954
|
-
name: text('name').notNull(),
|
|
955
|
-
});
|
|
956
|
-
|
|
957
|
-
// Generates:
|
|
958
|
-
// createdBy: integer('created_by')
|
|
959
|
-
// modifiedBy: integer('modified_by')
|
|
960
|
-
```
|
|
961
|
-
|
|
962
|
-
**String user IDs (UUID):**
|
|
963
|
-
|
|
964
|
-
```typescript
|
|
965
|
-
export const myTable = pgTable('MyTable', {
|
|
966
|
-
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
967
|
-
...generateUserAuditColumnDefs({
|
|
968
|
-
created: { dataType: 'string', columnName: 'created_by' },
|
|
969
|
-
modified: { dataType: 'string', columnName: 'modified_by' },
|
|
970
|
-
}),
|
|
971
|
-
name: text('name').notNull(),
|
|
972
|
-
});
|
|
973
|
-
|
|
974
|
-
// Generates:
|
|
975
|
-
// createdBy: text('created_by')
|
|
976
|
-
// modifiedBy: text('modified_by')
|
|
977
|
-
```
|
|
978
|
-
|
|
979
|
-
**Custom column names:**
|
|
980
|
-
|
|
981
|
-
```typescript
|
|
982
|
-
export const myTable = pgTable('MyTable', {
|
|
983
|
-
...generateIdColumnDefs(),
|
|
984
|
-
...generateUserAuditColumnDefs({
|
|
985
|
-
created: { dataType: 'number', columnName: 'author_id' },
|
|
986
|
-
modified: { dataType: 'number', columnName: 'editor_id' },
|
|
987
|
-
}),
|
|
988
|
-
name: text('name').notNull(),
|
|
989
|
-
});
|
|
990
|
-
|
|
991
|
-
// Generates:
|
|
992
|
-
// createdBy: integer('author_id')
|
|
993
|
-
// modifiedBy: integer('editor_id')
|
|
994
|
-
```
|
|
995
|
-
|
|
996
|
-
**Requiring authenticated user (allowAnonymous: false):**
|
|
997
|
-
|
|
998
|
-
```typescript
|
|
999
|
-
// For sensitive tables that must track the responsible user
|
|
1000
|
-
export const auditLogTable = pgTable('AuditLog', {
|
|
1001
|
-
...generateIdColumnDefs(),
|
|
1002
|
-
...generateUserAuditColumnDefs({
|
|
1003
|
-
created: { dataType: 'number', columnName: 'created_by', allowAnonymous: false },
|
|
1004
|
-
modified: { dataType: 'number', columnName: 'modified_by', allowAnonymous: false },
|
|
1005
|
-
}),
|
|
1006
|
-
action: text('action').notNull(),
|
|
1007
|
-
details: text('details'),
|
|
1008
|
-
});
|
|
1009
|
-
|
|
1010
|
-
// If no authenticated user context is available, throws:
|
|
1011
|
-
// Error: [getCurrentUserId] Invalid request context to identify user | columnName: createdBy | allowAnonymous: false
|
|
1012
|
-
```
|
|
1013
|
-
|
|
1014
|
-
#### Convenience Wrapper: `enrichUserAudit`
|
|
1015
|
-
|
|
1016
|
-
```typescript
|
|
1017
|
-
enrichUserAudit<ColumnDefinitions extends TColumnDefinitions>(
|
|
1018
|
-
baseSchema: ColumnDefinitions,
|
|
1019
|
-
opts?: TUserAuditEnricherOptions,
|
|
1020
|
-
): TUserAuditEnricherResult<ColumnDefinitions>
|
|
1021
|
-
```
|
|
1022
|
-
|
|
1023
|
-
Merges user audit columns into an existing column definitions object with proper type inference.
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
### `generatePrincipalColumnDefs`
|
|
1027
|
-
|
|
1028
|
-
Adds polymorphic principal columns for associating a record with different entity types. This is the polymorphic association pattern where a row can belong to different parent types (e.g., a comment can belong to a Post, User, or Product).
|
|
1029
|
-
|
|
1030
|
-
**File:** `packages/core/src/connectors/postgres/models/enrichers/principal.enricher.ts`
|
|
1031
|
-
|
|
1032
|
-
#### Signature
|
|
1033
|
-
|
|
1034
|
-
```typescript
|
|
1035
|
-
generatePrincipalColumnDefs<
|
|
1036
|
-
Discriminator extends string = 'principal',
|
|
1037
|
-
IdType extends 'number' | 'string' = 'number',
|
|
1038
|
-
>(
|
|
1039
|
-
opts: TPrincipalEnricherOptions<Discriminator, IdType>,
|
|
1040
|
-
): TPrincipalColumnDef<Discriminator, IdType>
|
|
1041
|
-
```
|
|
1042
|
-
|
|
1043
|
-
#### Options (`TPrincipalEnricherOptions`)
|
|
1044
|
-
|
|
1045
|
-
```typescript
|
|
1046
|
-
type TPrincipalEnricherOptions<
|
|
1047
|
-
Discriminator extends string = string,
|
|
1048
|
-
IdType extends 'number' | 'string' = 'number' | 'string',
|
|
1049
|
-
> = {
|
|
1050
|
-
discriminator?: Discriminator; // Field name prefix (default: 'principal')
|
|
1051
|
-
defaultPolymorphic?: string; // Default value for the type column (default: '')
|
|
1052
|
-
polymorphicIdType: IdType; // Required - type of the principal ID column
|
|
1053
|
-
};
|
|
1054
|
-
```
|
|
1055
|
-
|
|
1056
|
-
| Option | Type | Default | Description |
|
|
1057
|
-
|--------|------|---------|-------------|
|
|
1058
|
-
| `discriminator` | `string` | `'principal'` | Prefix for generated column names |
|
|
1059
|
-
| `defaultPolymorphic` | `string` | `''` | Default value for the type discriminator column |
|
|
1060
|
-
| `polymorphicIdType` | `'number' \| 'string'` | (required) | Data type of the ID column |
|
|
1061
|
-
|
|
1062
|
-
#### Generated Columns
|
|
1063
|
-
|
|
1064
|
-
Given `discriminator = 'principal'` (default):
|
|
1065
|
-
|
|
1066
|
-
| Column | DB Column Name | Type | Constraints | Description |
|
|
1067
|
-
|--------|---------------|------|-------------|-------------|
|
|
1068
|
-
| `principalId` | `principal_id` | `integer` or `text` | `NOT NULL` | The ID of the associated entity |
|
|
1069
|
-
| `principalType` | `principal_type` | `text` | `DEFAULT ''` | The type discriminator (e.g., `'User'`, `'Post'`) |
|
|
1070
|
-
|
|
1071
|
-
With a custom discriminator (e.g., `discriminator: 'owner'`):
|
|
1072
|
-
|
|
1073
|
-
| Column | DB Column Name | Type |
|
|
1074
|
-
|--------|---------------|------|
|
|
1075
|
-
| `ownerId` | `owner_id` | `integer` or `text` |
|
|
1076
|
-
| `ownerType` | `owner_type` | `text` |
|
|
1077
|
-
|
|
1078
|
-
#### Usage Examples
|
|
1079
|
-
|
|
1080
|
-
**Default (polymorphic principal with numeric ID):**
|
|
1081
|
-
|
|
1082
|
-
```typescript
|
|
1083
|
-
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
1084
|
-
import { generateIdColumnDefs, generatePrincipalColumnDefs } from '@venizia/ignis';
|
|
1085
|
-
|
|
1086
|
-
export const commentTable = pgTable('Comment', {
|
|
1087
|
-
...generateIdColumnDefs(),
|
|
1088
|
-
...generatePrincipalColumnDefs({ polymorphicIdType: 'number' }),
|
|
1089
|
-
content: text('content').notNull(),
|
|
1090
|
-
});
|
|
1091
|
-
|
|
1092
|
-
// Generates:
|
|
1093
|
-
// principalId: integer('principal_id').notNull()
|
|
1094
|
-
// principalType: text('principal_type').default('')
|
|
1095
|
-
```
|
|
1096
|
-
|
|
1097
|
-
**Custom discriminator name:**
|
|
1098
|
-
|
|
1099
|
-
```typescript
|
|
1100
|
-
export const attachmentTable = pgTable('Attachment', {
|
|
1101
|
-
...generateIdColumnDefs(),
|
|
1102
|
-
...generatePrincipalColumnDefs({
|
|
1103
|
-
discriminator: 'owner',
|
|
1104
|
-
polymorphicIdType: 'string',
|
|
1105
|
-
defaultPolymorphic: 'User',
|
|
1106
|
-
}),
|
|
1107
|
-
filePath: text('file_path').notNull(),
|
|
1108
|
-
});
|
|
1109
|
-
|
|
1110
|
-
// Generates:
|
|
1111
|
-
// ownerId: text('owner_id').notNull()
|
|
1112
|
-
// ownerType: text('owner_type').default('User')
|
|
1113
|
-
```
|
|
1114
|
-
|
|
1115
|
-
**Polymorphic association pattern:**
|
|
1116
|
-
|
|
1117
|
-
```typescript
|
|
1118
|
-
// A notification can belong to different entity types
|
|
1119
|
-
export const notificationTable = pgTable('Notification', {
|
|
1120
|
-
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
1121
|
-
...generatePrincipalColumnDefs({
|
|
1122
|
-
discriminator: 'target',
|
|
1123
|
-
polymorphicIdType: 'string',
|
|
1124
|
-
}),
|
|
1125
|
-
message: text('message').notNull(),
|
|
1126
|
-
});
|
|
1127
|
-
|
|
1128
|
-
// Usage:
|
|
1129
|
-
// { targetId: 'user-123', targetType: 'User', message: 'Welcome!' }
|
|
1130
|
-
// { targetId: 'order-456', targetType: 'Order', message: 'Order shipped' }
|
|
1131
|
-
```
|
|
1132
|
-
|
|
1133
|
-
#### Convenience Wrapper: `enrichPrincipal`
|
|
1134
|
-
|
|
1135
|
-
```typescript
|
|
1136
|
-
enrichPrincipal<ColumnDefinitions extends TColumnDefinitions>(
|
|
1137
|
-
baseSchema: ColumnDefinitions,
|
|
1138
|
-
opts: TPrincipalEnricherOptions,
|
|
1139
|
-
): ColumnDefinitions & TPrincipalColumnDef
|
|
1140
|
-
```
|
|
1141
|
-
|
|
1142
|
-
Merges principal columns into an existing column definitions object.
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
### `generateDataTypeColumnDefs`
|
|
1146
|
-
|
|
1147
|
-
Adds polymorphic data storage columns for entities that need to store values of different types in a single table. This is useful for key-value stores, settings tables, or any schema where a row's value type is determined at runtime.
|
|
1148
|
-
|
|
1149
|
-
**File:** `packages/core/src/connectors/postgres/models/enrichers/data-type.enricher.ts`
|
|
1150
|
-
|
|
1151
|
-
#### Signature
|
|
1152
|
-
|
|
1153
|
-
```typescript
|
|
1154
|
-
generateDataTypeColumnDefs(opts?: TDataTypeEnricherOptions): {
|
|
1155
|
-
dataType: PgTextBuilderInitial;
|
|
1156
|
-
nValue: PgDoublePrecisionBuilderInitial;
|
|
1157
|
-
tValue: PgTextBuilderInitial;
|
|
1158
|
-
bValue: PgCustomColumnBuilder<Buffer>;
|
|
1159
|
-
jValue: PgJsonbBuilderInitial<Record<string, any>>;
|
|
1160
|
-
boValue: PgBooleanBuilderInitial;
|
|
1161
|
-
}
|
|
1162
|
-
```
|
|
1163
|
-
|
|
1164
|
-
#### Options (`TDataTypeEnricherOptions`)
|
|
1165
|
-
|
|
1166
|
-
```typescript
|
|
1167
|
-
type TDataTypeEnricherOptions = {
|
|
1168
|
-
defaultValue: Partial<{
|
|
1169
|
-
dataType: string;
|
|
1170
|
-
nValue: number;
|
|
1171
|
-
tValue: string;
|
|
1172
|
-
bValue: Buffer;
|
|
1173
|
-
jValue: object;
|
|
1174
|
-
boValue: boolean;
|
|
1175
|
-
}>;
|
|
1176
|
-
};
|
|
1177
|
-
```
|
|
1178
|
-
|
|
1179
|
-
#### Generated Columns
|
|
1180
|
-
|
|
1181
|
-
| Column | SQL Type | DB Column Name | TypeScript Type | Purpose |
|
|
1182
|
-
|--------|----------|----------------|-----------------|---------|
|
|
1183
|
-
| `dataType` | `text` | `data_type` | `string` | Type discriminator (e.g., `'number'`, `'text'`, `'json'`) |
|
|
1184
|
-
| `nValue` | `double precision` | `n_value` | `number` | Numeric values |
|
|
1185
|
-
| `tValue` | `text` | `t_value` | `string` | Text values |
|
|
1186
|
-
| `bValue` | `bytea` | `b_value` | `Buffer` | Binary values |
|
|
1187
|
-
| `jValue` | `jsonb` | `j_value` | `Record<string, any>` | JSON values |
|
|
1188
|
-
| `boValue` | `boolean` | `bo_value` | `boolean` | Boolean values |
|
|
1189
|
-
|
|
1190
|
-
All columns are **nullable** by default (no `NOT NULL` constraint), since only one value column is typically populated per row depending on the `dataType` discriminator.
|
|
1191
|
-
|
|
1192
|
-
#### Usage Examples
|
|
1193
|
-
|
|
1194
|
-
**Basic usage:**
|
|
1195
|
-
|
|
1196
|
-
```typescript
|
|
1197
|
-
import { pgTable } from 'drizzle-orm/pg-core';
|
|
1198
|
-
import { BaseEntity, model, generateIdColumnDefs, generateDataTypeColumnDefs } from '@venizia/ignis';
|
|
54
|
+
import { BaseEntity, model, generateIdColumnDefs, generateTzColumnDefs } from '@venizia/ignis';
|
|
1199
55
|
|
|
1200
56
|
@model({ type: 'entity' })
|
|
1201
|
-
export class
|
|
1202
|
-
static override schema = pgTable('
|
|
57
|
+
export class Article extends BaseEntity<typeof Article.schema> {
|
|
58
|
+
static override schema = pgTable('Article', {
|
|
1203
59
|
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
1204
|
-
...
|
|
60
|
+
...generateTzColumnDefs(),
|
|
61
|
+
title: text('title').notNull(),
|
|
1205
62
|
});
|
|
1206
63
|
}
|
|
1207
64
|
```
|
|
1208
65
|
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
```typescript
|
|
1212
|
-
export const settingTable = pgTable('Setting', {
|
|
1213
|
-
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
1214
|
-
...generateDataTypeColumnDefs({
|
|
1215
|
-
defaultValue: { dataType: 'text', tValue: '' },
|
|
1216
|
-
}),
|
|
1217
|
-
});
|
|
1218
|
-
|
|
1219
|
-
// Generates columns with SQL defaults:
|
|
1220
|
-
// data_type text DEFAULT 'text'
|
|
1221
|
-
// t_value text DEFAULT ''
|
|
1222
|
-
// nValue, bValue, jValue, boValue - no defaults
|
|
1223
|
-
```
|
|
66
|
+
### Hide a field
|
|
1224
67
|
|
|
1225
|
-
|
|
68
|
+
List a column in `settings.hiddenProperties`. Hidden columns are excluded at the SQL level - never selected or returned through repositories.
|
|
1226
69
|
|
|
1227
70
|
```typescript
|
|
1228
|
-
@model({
|
|
1229
|
-
|
|
1230
|
-
|
|
71
|
+
@model({
|
|
72
|
+
type: 'entity',
|
|
73
|
+
settings: { hiddenProperties: ['password'] },
|
|
74
|
+
})
|
|
75
|
+
export class User extends BaseEntity<typeof User.schema> {
|
|
76
|
+
static override schema = pgTable('User', {
|
|
1231
77
|
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
description: text('description'),
|
|
78
|
+
email: text('email').notNull(),
|
|
79
|
+
password: text('password'),
|
|
1235
80
|
});
|
|
1236
81
|
}
|
|
1237
|
-
|
|
1238
|
-
// Usage:
|
|
1239
|
-
// { key: 'max_retries', dataType: 'number', nValue: 3 }
|
|
1240
|
-
// { key: 'welcome_message', dataType: 'text', tValue: 'Hello!' }
|
|
1241
|
-
// { key: 'feature_flags', dataType: 'json', jValue: { darkMode: true } }
|
|
1242
|
-
// { key: 'is_maintenance', dataType: 'boolean', boValue: false }
|
|
1243
82
|
```
|
|
1244
83
|
|
|
1245
|
-
|
|
84
|
+
### Apply a default filter
|
|
1246
85
|
|
|
1247
|
-
|
|
1248
|
-
enrichDataTypes(
|
|
1249
|
-
baseSchema: TColumnDefinitions,
|
|
1250
|
-
opts?: TDataTypeEnricherOptions,
|
|
1251
|
-
): TColumnDefinitions
|
|
1252
|
-
```
|
|
1253
|
-
|
|
1254
|
-
Merges data type columns into an existing column definitions object:
|
|
86
|
+
`settings.defaultFilter` is merged into every repository read, count, update, and delete for the model. A common use is soft delete.
|
|
1255
87
|
|
|
1256
88
|
```typescript
|
|
1257
|
-
import {
|
|
1258
|
-
import {
|
|
1259
|
-
|
|
1260
|
-
const baseColumns = {
|
|
1261
|
-
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
1262
|
-
key: text('key').notNull(),
|
|
1263
|
-
};
|
|
1264
|
-
|
|
1265
|
-
// Merge data type columns into existing column definitions
|
|
1266
|
-
const allColumns = enrichDataTypes(baseColumns);
|
|
1267
|
-
|
|
1268
|
-
export const configTable = pgTable('Config', allColumns);
|
|
1269
|
-
```
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
## Schema Utilities
|
|
1273
|
-
|
|
1274
|
-
### `idParamsSchema`
|
|
1275
|
-
|
|
1276
|
-
Generates a Zod schema for path parameters containing an `id` field, suitable for OpenAPI route definitions.
|
|
1277
|
-
|
|
1278
|
-
**File:** `packages/core/src/base/models/common/types.ts`
|
|
1279
|
-
|
|
1280
|
-
#### Signature
|
|
1281
|
-
|
|
1282
|
-
```typescript
|
|
1283
|
-
idParamsSchema(opts?: { idType: TIdSchemaType }): z.ZodObject<{ id: z.ZodNumber | z.ZodString }>
|
|
1284
|
-
```
|
|
1285
|
-
|
|
1286
|
-
| `idType` | Default | Zod Type | Examples |
|
|
1287
|
-
|----------|---------|----------|----------|
|
|
1288
|
-
| `'number'` | Yes | `z.number()` | `[1, 2, 3]` |
|
|
1289
|
-
| `'string'` | | `z.string()` | `['4651e634-...', 'some_unique_id']` |
|
|
89
|
+
import { model, BaseEntity } from '@venizia/ignis';
|
|
90
|
+
import { postTable } from '@/schemas';
|
|
1290
91
|
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
```typescript
|
|
1298
|
-
jsonContent<T extends z.ZodType>(opts: {
|
|
1299
|
-
schema: T;
|
|
1300
|
-
description: string;
|
|
1301
|
-
required?: boolean;
|
|
1302
|
-
}): { description, content: { 'application/json': { schema } }, required? }
|
|
1303
|
-
```
|
|
1304
|
-
|
|
1305
|
-
### `jsonResponse`
|
|
1306
|
-
|
|
1307
|
-
Creates a complete OpenAPI response specification with success and error responses:
|
|
1308
|
-
|
|
1309
|
-
```typescript
|
|
1310
|
-
jsonResponse<ContentSchema, HeaderSchema>(opts: {
|
|
1311
|
-
schema: ContentSchema;
|
|
1312
|
-
description?: string; // Default: 'Success Response'
|
|
1313
|
-
required?: boolean;
|
|
1314
|
-
headers?: HeaderSchema;
|
|
1315
|
-
}): {
|
|
1316
|
-
200: { description, content, headers? },
|
|
1317
|
-
'4xx | 5xx': { description: 'Error Response', content: ErrorSchema }
|
|
92
|
+
@model({
|
|
93
|
+
type: 'entity',
|
|
94
|
+
settings: { defaultFilter: { where: { isDeleted: false } } },
|
|
95
|
+
})
|
|
96
|
+
export class Post extends BaseEntity<typeof Post.schema> {
|
|
97
|
+
static override schema = postTable;
|
|
1318
98
|
}
|
|
1319
99
|
```
|
|
1320
100
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
Converts a Zod schema from snake_case to camelCase, transforming both the schema shape and runtime data.
|
|
1324
|
-
|
|
1325
|
-
**File:** `packages/core/src/base/models/common/types.ts`
|
|
1326
|
-
|
|
1327
|
-
#### Signature
|
|
1328
|
-
|
|
1329
|
-
```typescript
|
|
1330
|
-
snakeToCamel<T extends z.ZodRawShape>(shape: T): z.ZodEffects<...>
|
|
1331
|
-
```
|
|
1332
|
-
|
|
1333
|
-
#### Purpose
|
|
1334
|
-
|
|
1335
|
-
This utility is useful when working with databases that use snake_case column names but you want to work with camelCase in your TypeScript code. It creates a Zod schema that:
|
|
1336
|
-
|
|
1337
|
-
1. Accepts snake_case input (validates against original schema)
|
|
1338
|
-
2. Transforms the data to camelCase at runtime
|
|
1339
|
-
3. Validates the transformed data against a camelCase schema
|
|
1340
|
-
|
|
1341
|
-
#### Usage Example
|
|
1342
|
-
|
|
1343
|
-
```typescript
|
|
1344
|
-
import { z } from 'zod';
|
|
1345
|
-
import { snakeToCamel } from '@venizia/ignis';
|
|
1346
|
-
|
|
1347
|
-
// Define schema with snake_case fields
|
|
1348
|
-
const userSnakeSchema = {
|
|
1349
|
-
user_id: z.number(),
|
|
1350
|
-
first_name: z.string(),
|
|
1351
|
-
last_name: z.string(),
|
|
1352
|
-
created_at: z.date(),
|
|
1353
|
-
is_active: z.boolean(),
|
|
1354
|
-
};
|
|
1355
|
-
|
|
1356
|
-
// Convert to camelCase schema
|
|
1357
|
-
const userCamelSchema = snakeToCamel(userSnakeSchema);
|
|
1358
|
-
|
|
1359
|
-
// Input data from database (snake_case)
|
|
1360
|
-
const dbData = {
|
|
1361
|
-
user_id: 123,
|
|
1362
|
-
first_name: 'John',
|
|
1363
|
-
last_name: 'Doe',
|
|
1364
|
-
created_at: new Date(),
|
|
1365
|
-
is_active: true,
|
|
1366
|
-
};
|
|
1367
|
-
|
|
1368
|
-
// Parse and transform to camelCase
|
|
1369
|
-
const result = userCamelSchema.parse(dbData);
|
|
1370
|
-
|
|
1371
|
-
// Result is automatically camelCase:
|
|
1372
|
-
console.log(result);
|
|
1373
|
-
// {
|
|
1374
|
-
// userId: 123,
|
|
1375
|
-
// firstName: 'John',
|
|
1376
|
-
// lastName: 'Doe',
|
|
1377
|
-
// createdAt: Date,
|
|
1378
|
-
// isActive: true
|
|
1379
|
-
// }
|
|
1380
|
-
```
|
|
101
|
+
Pass `options: { shouldSkipDefaultFilter: true }` on a query to bypass it. See [Default Filter](/references/base/filter-system/default-filter).
|
|
1381
102
|
|
|
1382
|
-
|
|
103
|
+
### Set a default limit
|
|
1383
104
|
|
|
1384
|
-
|
|
105
|
+
`settings.defaultLimit` caps queries that omit `limit`. It must be a positive integer (validated at decoration time) and falls back to the global `DEFAULT_LIMIT` (10).
|
|
1385
106
|
|
|
1386
107
|
```typescript
|
|
1387
|
-
import {
|
|
1388
|
-
import {
|
|
1389
|
-
import { z } from '@hono/zod-openapi';
|
|
1390
|
-
|
|
1391
|
-
const createUserSchema = snakeToCamel({
|
|
1392
|
-
first_name: z.string().min(1),
|
|
1393
|
-
last_name: z.string().min(1),
|
|
1394
|
-
email_address: z.string().email(),
|
|
1395
|
-
phone_number: z.string().optional(),
|
|
1396
|
-
});
|
|
1397
|
-
|
|
1398
|
-
@controller({ path: '/users' })
|
|
1399
|
-
export class UserController extends BaseRestController {
|
|
1400
|
-
override binding() {
|
|
1401
|
-
this.bindRoute({
|
|
1402
|
-
configs: {
|
|
1403
|
-
path: '/',
|
|
1404
|
-
method: 'post',
|
|
1405
|
-
request: {
|
|
1406
|
-
body: {
|
|
1407
|
-
content: {
|
|
1408
|
-
'application/json': { schema: createUserSchema },
|
|
1409
|
-
},
|
|
1410
|
-
},
|
|
1411
|
-
},
|
|
1412
|
-
},
|
|
1413
|
-
}).to({
|
|
1414
|
-
handler: async (ctx) => {
|
|
1415
|
-
// Request body is automatically camelCase
|
|
1416
|
-
const data = ctx.req.valid('json');
|
|
1417
|
-
|
|
1418
|
-
// data = {
|
|
1419
|
-
// firstName: string,
|
|
1420
|
-
// lastName: string,
|
|
1421
|
-
// emailAddress: string,
|
|
1422
|
-
// phoneNumber?: string
|
|
1423
|
-
// }
|
|
108
|
+
import { model, BaseEntity } from '@venizia/ignis';
|
|
109
|
+
import { eventTable } from '@/schemas';
|
|
1424
110
|
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
});
|
|
1432
|
-
}
|
|
111
|
+
@model({
|
|
112
|
+
type: 'entity',
|
|
113
|
+
settings: { defaultLimit: 50 },
|
|
114
|
+
})
|
|
115
|
+
export class Event extends BaseEntity<typeof Event.schema> {
|
|
116
|
+
static override schema = eventTable;
|
|
1433
117
|
}
|
|
1434
118
|
```
|
|
1435
119
|
|
|
1436
|
-
|
|
120
|
+
See [Pagination](/references/base/filter-system/fields-order-pagination#default-limit).
|
|
1437
121
|
|
|
1438
|
-
|
|
122
|
+
### Declare an authorization principal
|
|
1439
123
|
|
|
1440
|
-
|
|
1441
|
-
type TSnakeToCamelCase<S extends string> =
|
|
1442
|
-
S extends `${infer T}_${infer U}`
|
|
1443
|
-
? `${T}${Capitalize<TSnakeToCamelCase<U>>}`
|
|
1444
|
-
: S;
|
|
1445
|
-
|
|
1446
|
-
type TCamelCaseKeys<T extends z.ZodRawShape> = {
|
|
1447
|
-
[K in keyof T as K extends string ? TSnakeToCamelCase<K> : K]:
|
|
1448
|
-
T[K] extends z.ZodType<infer U> ? z.ZodType<U> : T[K];
|
|
1449
|
-
};
|
|
1450
|
-
```
|
|
1451
|
-
|
|
1452
|
-
This ensures full type safety: TypeScript will know that `first_name` becomes `firstName`, `created_at` becomes `createdAt`, etc.
|
|
1453
|
-
|
|
1454
|
-
#### Validation
|
|
1455
|
-
|
|
1456
|
-
The schema validates twice for safety:
|
|
1457
|
-
|
|
1458
|
-
1. **First validation:** Checks that input matches snake_case schema
|
|
1459
|
-
2. **Transformation:** Converts keys from snake_case to camelCase
|
|
1460
|
-
3. **Second validation:** Validates transformed data against camelCase schema
|
|
124
|
+
`settings.authorize.principal` names the model as an authorization subject. The decorator auto-populates the static `AUTHORIZATION_SUBJECT` from it.
|
|
1461
125
|
|
|
1462
126
|
```typescript
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
user_id: 'not-a-number', // Fails first validation
|
|
1466
|
-
first_name: 'John',
|
|
1467
|
-
last_name: 'Doe',
|
|
1468
|
-
};
|
|
127
|
+
import { model, BaseEntity } from '@venizia/ignis';
|
|
128
|
+
import { userTable } from '@/schemas';
|
|
1469
129
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
130
|
+
@model({
|
|
131
|
+
type: 'entity',
|
|
132
|
+
settings: { authorize: { principal: 'User' } },
|
|
133
|
+
})
|
|
134
|
+
export class User extends BaseEntity<typeof User.schema> {
|
|
135
|
+
static override schema = userTable;
|
|
1474
136
|
}
|
|
1475
137
|
```
|
|
1476
138
|
|
|
1477
|
-
|
|
139
|
+
See [Authorization](/extensions/components/authorization/usage#model-based-resource-references).
|
|
1478
140
|
|
|
1479
|
-
|
|
1480
|
-
- Recursively handles nested objects
|
|
1481
|
-
- Preserves array structures
|
|
1482
|
-
- Works seamlessly with Zod's other features (refinements, transforms, etc.)
|
|
141
|
+
## See also
|
|
1483
142
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
| **Location** | `AbstractEntity.getIdType()` (`packages/core/src/base/models/base.ts`) | `getIdType()` (`packages/core/src/connectors/postgres/models/common/types.ts`) |
|
|
1491
|
-
| **Signature** | `getIdType(): TIdSchemaType` | `getIdType<T extends TTableSchemaWithId>(opts: { entity: T }): string` |
|
|
1492
|
-
| **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` (e.g., `'number'`, `'string'`), or `'unknown'` if not determinable |
|
|
1493
|
-
|
|
1494
|
-
```typescript
|
|
1495
|
-
// Neutral - instance method every AbstractEntity subclass exposes (default 'string', BasePostgresEntity overrides based on the column)
|
|
1496
|
-
const entity = new User();
|
|
1497
|
-
entity.getIdType(); // 'string' | 'number'
|
|
143
|
+
- [Full reference](/references/base/models-reference) - every `@model` option, entity member, enricher, and edge case
|
|
144
|
+
- [Tutorial](/guides/core-concepts/persistent/models) - creating models step by step
|
|
145
|
+
- [Connectors](/references/base/connectors) - the base-vs-connector architecture
|
|
146
|
+
- [DataSources](/references/base/datasources) - binding a model's schema to a connection
|
|
147
|
+
- [Repositories](/references/base/repositories/) - the CRUD layer built on top of a model
|
|
148
|
+
- [Filter System](/references/base/filter-system/) - querying the model through a repository
|
|
1498
149
|
|
|
1499
|
-
|
|
1500
|
-
import { getIdType } from '@venizia/ignis/postgres';
|
|
1501
|
-
getIdType({ entity: User.schema }); // 'string' | 'number' | 'unknown'
|
|
1502
|
-
```
|
|
1503
|
-
|
|
1504
|
-
## See Also
|
|
1505
|
-
|
|
1506
|
-
- **Related Concepts:**
|
|
1507
|
-
- [Models Guide](/guides/core-concepts/persistent/models) - Creating models tutorial
|
|
1508
|
-
- [Repositories](/guides/core-concepts/persistent/repositories) - Using models in repositories
|
|
1509
|
-
- [DataSources](/guides/core-concepts/persistent/datasources) - Database connections
|
|
1510
|
-
|
|
1511
|
-
- **References:**
|
|
1512
|
-
- [Repositories API](/references/base/repositories/) - Data access layer
|
|
1513
|
-
- [Relations](/references/base/repositories/relations) - Model relationships
|
|
1514
|
-
- [Filter System](/references/base/filter-system/) - Querying models
|
|
1515
|
-
|
|
1516
|
-
- **External Resources:**
|
|
1517
|
-
- [Drizzle ORM Documentation](https://orm.drizzle.team/) - Schema definition guide
|
|
1518
|
-
- [PostgreSQL Data Types](https://www.postgresql.org/docs/current/datatype.html) - Column types
|
|
1519
|
-
|
|
1520
|
-
- **Best Practices:**
|
|
1521
|
-
- [Data Modeling](/best-practices/data-modeling) - Schema design patterns
|
|
150
|
+
**Files:**
|
|
1522
151
|
|
|
1523
|
-
-
|
|
1524
|
-
|
|
1525
|
-
- [E-commerce API](/guides/tutorials/ecommerce-api) - Models with relations
|
|
152
|
+
- [`packages/core/src/base/models/base.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/models/base.ts) - neutral `AbstractEntity`
|
|
153
|
+
- [`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 `BaseEntity`
|