@venizia/ignis-docs 0.0.1-1 → 0.0.1-10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/LICENSE.md +102 -0
  2. package/README.md +459 -0
  3. package/dist/mcp-server/common/config.d.ts +64 -0
  4. package/dist/mcp-server/common/config.d.ts.map +1 -0
  5. package/dist/mcp-server/common/config.js +82 -0
  6. package/dist/mcp-server/common/config.js.map +1 -0
  7. package/dist/mcp-server/common/index.d.ts +3 -0
  8. package/dist/mcp-server/common/index.d.ts.map +1 -0
  9. package/dist/mcp-server/common/index.js.map +1 -0
  10. package/dist/mcp-server/common/paths.d.ts.map +1 -0
  11. package/{mcp-server/dist → dist/mcp-server}/common/paths.js +13 -11
  12. package/dist/mcp-server/common/paths.js.map +1 -0
  13. package/{mcp-server/dist → dist/mcp-server}/helpers/docs.helper.d.ts +5 -5
  14. package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -0
  15. package/{mcp-server/dist → dist/mcp-server}/helpers/docs.helper.js +38 -34
  16. package/dist/mcp-server/helpers/docs.helper.js.map +1 -0
  17. package/dist/mcp-server/helpers/github.helper.d.ts +37 -0
  18. package/dist/mcp-server/helpers/github.helper.d.ts.map +1 -0
  19. package/dist/mcp-server/helpers/github.helper.js +100 -0
  20. package/dist/mcp-server/helpers/github.helper.js.map +1 -0
  21. package/dist/mcp-server/helpers/index.d.ts +4 -0
  22. package/dist/mcp-server/helpers/index.d.ts.map +1 -0
  23. package/{mcp-server/dist → dist/mcp-server}/helpers/index.js +1 -0
  24. package/dist/mcp-server/helpers/index.js.map +1 -0
  25. package/dist/mcp-server/helpers/logger.helper.d.ts.map +1 -0
  26. package/dist/mcp-server/helpers/logger.helper.js.map +1 -0
  27. package/{mcp-server/dist → dist/mcp-server}/index.d.ts.map +1 -1
  28. package/dist/mcp-server/index.js +90 -0
  29. package/dist/mcp-server/index.js.map +1 -0
  30. package/{mcp-server/dist → dist/mcp-server}/tools/base.tool.d.ts +8 -12
  31. package/dist/mcp-server/tools/base.tool.d.ts.map +1 -0
  32. package/{mcp-server/dist → dist/mcp-server}/tools/base.tool.js +3 -5
  33. package/dist/mcp-server/tools/base.tool.js.map +1 -0
  34. package/{mcp-server/dist/tools/get-doc-content.tool.d.ts → dist/mcp-server/tools/docs/get-document-content.tool.d.ts} +13 -17
  35. package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts.map +1 -0
  36. package/{mcp-server/dist/tools/get-doc-content.tool.js → dist/mcp-server/tools/docs/get-document-content.tool.js} +20 -22
  37. package/dist/mcp-server/tools/docs/get-document-content.tool.js.map +1 -0
  38. package/{mcp-server/dist/tools/get-doc-metadata.tool.d.ts → dist/mcp-server/tools/docs/get-document-metadata.tool.d.ts} +21 -25
  39. package/dist/mcp-server/tools/docs/get-document-metadata.tool.d.ts.map +1 -0
  40. package/{mcp-server/dist/tools/get-doc-metadata.tool.js → dist/mcp-server/tools/docs/get-document-metadata.tool.js} +35 -27
  41. package/dist/mcp-server/tools/docs/get-document-metadata.tool.js.map +1 -0
  42. package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts +50 -0
  43. package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts.map +1 -0
  44. package/dist/mcp-server/tools/docs/get-package-overview.tool.js +221 -0
  45. package/dist/mcp-server/tools/docs/get-package-overview.tool.js.map +1 -0
  46. package/dist/mcp-server/tools/docs/index.d.ts +7 -0
  47. package/dist/mcp-server/tools/docs/index.d.ts.map +1 -0
  48. package/dist/mcp-server/tools/docs/index.js +23 -0
  49. package/dist/mcp-server/tools/docs/index.js.map +1 -0
  50. package/{mcp-server/dist/tools → dist/mcp-server/tools/docs}/list-categories.tool.d.ts +3 -3
  51. package/dist/mcp-server/tools/docs/list-categories.tool.d.ts.map +1 -0
  52. package/{mcp-server/dist/tools → dist/mcp-server/tools/docs}/list-categories.tool.js +10 -9
  53. package/dist/mcp-server/tools/docs/list-categories.tool.js.map +1 -0
  54. package/{mcp-server/dist/tools/list-docs.tool.d.ts → dist/mcp-server/tools/docs/list-documents.tool.d.ts} +5 -5
  55. package/dist/mcp-server/tools/docs/list-documents.tool.d.ts.map +1 -0
  56. package/{mcp-server/dist/tools/list-docs.tool.js → dist/mcp-server/tools/docs/list-documents.tool.js} +15 -14
  57. package/dist/mcp-server/tools/docs/list-documents.tool.js.map +1 -0
  58. package/{mcp-server/dist/tools/search-docs.tool.d.ts → dist/mcp-server/tools/docs/search-documents.tool.d.ts} +23 -19
  59. package/dist/mcp-server/tools/docs/search-documents.tool.d.ts.map +1 -0
  60. package/{mcp-server/dist/tools/search-docs.tool.js → dist/mcp-server/tools/docs/search-documents.tool.js} +29 -25
  61. package/dist/mcp-server/tools/docs/search-documents.tool.js.map +1 -0
  62. package/dist/mcp-server/tools/github/index.d.ts +5 -0
  63. package/dist/mcp-server/tools/github/index.d.ts.map +1 -0
  64. package/dist/mcp-server/tools/github/index.js +21 -0
  65. package/dist/mcp-server/tools/github/index.js.map +1 -0
  66. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +28 -0
  67. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -0
  68. package/dist/mcp-server/tools/github/list-project-files.tool.js +98 -0
  69. package/dist/mcp-server/tools/github/list-project-files.tool.js.map +1 -0
  70. package/dist/mcp-server/tools/github/search-code.tool.d.ts +42 -0
  71. package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -0
  72. package/dist/mcp-server/tools/github/search-code.tool.js +194 -0
  73. package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -0
  74. package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts +55 -0
  75. package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -0
  76. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +167 -0
  77. package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -0
  78. package/dist/mcp-server/tools/github/view-source-file.tool.d.ts +26 -0
  79. package/dist/mcp-server/tools/github/view-source-file.tool.d.ts.map +1 -0
  80. package/dist/mcp-server/tools/github/view-source-file.tool.js +91 -0
  81. package/dist/mcp-server/tools/github/view-source-file.tool.js.map +1 -0
  82. package/dist/mcp-server/tools/index.d.ts +4 -0
  83. package/dist/mcp-server/tools/index.d.ts.map +1 -0
  84. package/dist/mcp-server/tools/index.js +22 -0
  85. package/dist/mcp-server/tools/index.js.map +1 -0
  86. package/package.json +46 -23
  87. package/wiki/changelogs/2025-12-16-initial-architecture.md +145 -0
  88. package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +300 -0
  89. package/wiki/changelogs/2025-12-17-refactor.md +90 -0
  90. package/wiki/changelogs/2025-12-18-performance-optimizations.md +130 -0
  91. package/wiki/changelogs/2025-12-18-repository-validation-security.md +249 -0
  92. package/wiki/changelogs/index.md +33 -0
  93. package/wiki/changelogs/planned-transaction-support.md +216 -0
  94. package/wiki/changelogs/template.md +123 -0
  95. package/wiki/get-started/5-minute-quickstart.md +1 -1
  96. package/wiki/get-started/best-practices/api-usage-examples.md +54 -8
  97. package/wiki/get-started/best-practices/architectural-patterns.md +43 -2
  98. package/wiki/get-started/best-practices/code-style-standards.md +41 -0
  99. package/wiki/get-started/best-practices/common-pitfalls.md +5 -3
  100. package/wiki/get-started/best-practices/contribution-workflow.md +40 -6
  101. package/wiki/get-started/best-practices/data-modeling.md +177 -0
  102. package/wiki/get-started/best-practices/security-guidelines.md +3 -1
  103. package/wiki/get-started/building-a-crud-api.md +63 -78
  104. package/wiki/get-started/core-concepts/components.md +4 -2
  105. package/wiki/get-started/core-concepts/controllers.md +14 -14
  106. package/wiki/get-started/core-concepts/dependency-injection.md +13 -1
  107. package/wiki/get-started/core-concepts/persistent.md +383 -431
  108. package/wiki/get-started/core-concepts/services.md +21 -27
  109. package/wiki/get-started/mcp-docs-server.md +130 -32
  110. package/wiki/get-started/philosophy.md +198 -25
  111. package/wiki/get-started/quickstart.md +1 -1
  112. package/wiki/public/logo.svg +1 -0
  113. package/wiki/references/base/application.md +5 -5
  114. package/wiki/references/base/components.md +1 -1
  115. package/wiki/references/base/controllers.md +43 -17
  116. package/wiki/references/base/datasources.md +195 -33
  117. package/wiki/references/base/dependency-injection.md +8 -7
  118. package/wiki/references/base/models.md +713 -25
  119. package/wiki/references/base/repositories.md +475 -22
  120. package/wiki/references/base/services.md +2 -2
  121. package/wiki/references/components/authentication.md +228 -10
  122. package/wiki/references/components/health-check.md +1 -1
  123. package/wiki/references/components/index.md +4 -2
  124. package/wiki/references/components/static-asset.md +1289 -0
  125. package/wiki/references/components/swagger.md +1 -1
  126. package/wiki/references/helpers/error.md +2 -2
  127. package/wiki/references/helpers/inversion.md +29 -14
  128. package/wiki/references/helpers/storage.md +538 -11
  129. package/wiki/references/src-details/core.md +21 -6
  130. package/wiki/references/src-details/docs.md +19 -9
  131. package/wiki/references/src-details/inversion.md +4 -4
  132. package/wiki/references/src-details/mcp-server.md +185 -234
  133. package/wiki/references/utilities/index.md +1 -1
  134. package/wiki/references/utilities/request.md +162 -3
  135. package/mcp-server/dist/common/config.d.ts +0 -27
  136. package/mcp-server/dist/common/config.d.ts.map +0 -1
  137. package/mcp-server/dist/common/config.js +0 -27
  138. package/mcp-server/dist/common/config.js.map +0 -1
  139. package/mcp-server/dist/common/index.d.ts +0 -3
  140. package/mcp-server/dist/common/index.d.ts.map +0 -1
  141. package/mcp-server/dist/common/index.js.map +0 -1
  142. package/mcp-server/dist/common/paths.d.ts.map +0 -1
  143. package/mcp-server/dist/common/paths.js.map +0 -1
  144. package/mcp-server/dist/helpers/docs.helper.d.ts.map +0 -1
  145. package/mcp-server/dist/helpers/docs.helper.js.map +0 -1
  146. package/mcp-server/dist/helpers/index.d.ts +0 -3
  147. package/mcp-server/dist/helpers/index.d.ts.map +0 -1
  148. package/mcp-server/dist/helpers/index.js.map +0 -1
  149. package/mcp-server/dist/helpers/logger.helper.d.ts.map +0 -1
  150. package/mcp-server/dist/helpers/logger.helper.js.map +0 -1
  151. package/mcp-server/dist/index.js +0 -62
  152. package/mcp-server/dist/index.js.map +0 -1
  153. package/mcp-server/dist/tools/base.tool.d.ts.map +0 -1
  154. package/mcp-server/dist/tools/base.tool.js.map +0 -1
  155. package/mcp-server/dist/tools/get-doc-content.tool.d.ts.map +0 -1
  156. package/mcp-server/dist/tools/get-doc-content.tool.js.map +0 -1
  157. package/mcp-server/dist/tools/get-doc-metadata.tool.d.ts.map +0 -1
  158. package/mcp-server/dist/tools/get-doc-metadata.tool.js.map +0 -1
  159. package/mcp-server/dist/tools/index.d.ts +0 -8
  160. package/mcp-server/dist/tools/index.d.ts.map +0 -1
  161. package/mcp-server/dist/tools/index.js +0 -18
  162. package/mcp-server/dist/tools/index.js.map +0 -1
  163. package/mcp-server/dist/tools/list-categories.tool.d.ts.map +0 -1
  164. package/mcp-server/dist/tools/list-categories.tool.js.map +0 -1
  165. package/mcp-server/dist/tools/list-docs.tool.d.ts.map +0 -1
  166. package/mcp-server/dist/tools/list-docs.tool.js.map +0 -1
  167. package/mcp-server/dist/tools/search-docs.tool.d.ts.map +0 -1
  168. package/mcp-server/dist/tools/search-docs.tool.js.map +0 -1
  169. /package/{mcp-server/dist → dist/mcp-server}/common/index.js +0 -0
  170. /package/{mcp-server/dist → dist/mcp-server}/common/paths.d.ts +0 -0
  171. /package/{mcp-server/dist → dist/mcp-server}/helpers/logger.helper.d.ts +0 -0
  172. /package/{mcp-server/dist → dist/mcp-server}/helpers/logger.helper.js +0 -0
  173. /package/{mcp-server/dist → dist/mcp-server}/index.d.ts +0 -0
@@ -26,49 +26,151 @@ Fundamental building block wrapping a Drizzle ORM schema.
26
26
  | **Schema Encapsulation** | Holds Drizzle `pgTable` schema for consistent repository access |
27
27
  | **Metadata** | Works with `@model` decorator to mark database entities |
28
28
  | **Schema Generation** | Uses `drizzle-zod` to generate Zod schemas (`SELECT`, `CREATE`, `UPDATE`) |
29
+ | **Static Properties** | Supports static `schema`, `relations`, and `TABLE_NAME` for cleaner syntax |
29
30
  | **Convenience** | Includes `toObject()` and `toJSON()` methods |
30
31
 
31
- ### Class Definition
32
+ ### Definition Patterns
33
+
34
+ `BaseEntity` supports two patterns for defining models:
35
+
36
+ #### Pattern 1: Static Properties (Recommended)
37
+
38
+ Define schema and relations as static properties:
32
39
 
33
40
  ```typescript
34
- import { createSchemaFactory } from 'drizzle-zod';
35
- import { BaseHelper } from '../helpers';
36
- import { SchemaTypes, TSchemaType, TTableSchemaWithId } from './common';
41
+ import { pgTable, text } from 'drizzle-orm/pg-core';
42
+ import { BaseEntity, model, generateIdColumnDefs, createRelations } from '@venizia/ignis';
37
43
 
38
- export class BaseEntity<Schema extends TTableSchemaWithId = TTableSchemaWithId> extends BaseHelper {
44
+ // Define table schema
45
+ export const userTable = pgTable('User', {
46
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
47
+ name: text('name').notNull(),
48
+ email: text('email').notNull(),
49
+ });
50
+
51
+ // Define relations
52
+ export const userRelations = createRelations({
53
+ source: userTable,
54
+ relations: [],
55
+ });
56
+
57
+ // Entity class with static properties
58
+ @model({ type: 'entity' })
59
+ export class User extends BaseEntity<typeof User.schema> {
60
+ static override schema = userTable;
61
+ static override relations = () => userRelations.definitions;
62
+ static override TABLE_NAME = 'User';
63
+ }
64
+ ```
65
+
66
+ **Benefits:**
67
+ - Schema and relations are auto-resolved by repositories
68
+ - No need to pass `relations` in repository constructor
69
+ - Cleaner, more declarative syntax
70
+
71
+ #### Pattern 2: Constructor-Based (Legacy)
72
+
73
+ Pass schema in constructor:
74
+
75
+ ```typescript
76
+ @model({ type: 'entity' })
77
+ export class User extends BaseEntity<typeof userTable> {
78
+ constructor() {
79
+ super({ name: 'User', schema: userTable });
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### Static Properties
85
+
86
+ | Property | Type | Description |
87
+ |----------|------|-------------|
88
+ | `schema` | `TTableSchemaWithId` | Drizzle table schema defined with `pgTable()` |
89
+ | `relations` | `TValueOrResolver<Array<TRelationConfig>>` | Relation definitions (can be a function for lazy loading) |
90
+ | `TABLE_NAME` | `string \| undefined` | Optional table name (defaults to class name if not set) |
91
+
92
+ ### IEntity Interface
93
+
94
+ Models implementing static properties conform to the `IEntity` interface:
95
+
96
+ ```typescript
97
+ interface IEntity<Schema extends TTableSchemaWithId = TTableSchemaWithId> {
98
+ TABLE_NAME?: string;
99
+ schema: Schema;
100
+ relations?: TValueOrResolver<Array<TRelationConfig>>;
101
+ }
102
+ ```
103
+
104
+ ### Instance Methods
105
+
106
+ | Method | Description |
107
+ |--------|-------------|
108
+ | `getSchema({ type })` | Get Zod schema for validation (`SELECT`, `CREATE`, `UPDATE`) |
109
+ | `toObject()` | Convert to plain object |
110
+ | `toJSON()` | Convert to JSON string |
111
+
112
+ ### Class Definition
113
+
114
+ ```typescript
115
+ export class BaseEntity<Schema extends TTableSchemaWithId = TTableSchemaWithId>
116
+ extends BaseHelper
117
+ implements IEntity<Schema>
118
+ {
119
+ // Instance properties
39
120
  name: string;
40
121
  schema: Schema;
41
- schemaFactory: ReturnType<typeof createSchemaFactory>;
42
122
 
43
- constructor(opts: { name: string; schema: Schema }) {
44
- super({ scope: opts.name });
45
- this.name = opts.name;
46
- this.schema = opts.schema;
47
- this.schemaFactory = createSchemaFactory();
123
+ // Static properties - override in subclass
124
+ static schema: TTableSchemaWithId;
125
+ static relations?: TValueOrResolver<Array<TRelationConfig>>;
126
+ static TABLE_NAME?: string; // Optional, defaults to class name
127
+
128
+ // Static singleton for schemaFactory - shared across all instances
129
+ // Performance optimization: avoids creating new factory per entity
130
+ private static _schemaFactory?: ReturnType<typeof createSchemaFactory>;
131
+ protected static get schemaFactory(): ReturnType<typeof createSchemaFactory> {
132
+ return (BaseEntity._schemaFactory ??= createSchemaFactory());
133
+ }
134
+
135
+ // Constructor supports both patterns
136
+ constructor(opts?: { name?: string; schema?: Schema }) {
137
+ const ctor = new.target as typeof BaseEntity;
138
+ // Use explicit TABLE_NAME if defined, otherwise fall back to class name
139
+ const name = opts?.name ?? ctor.TABLE_NAME ?? ctor.name;
140
+
141
+ super({ scope: name });
142
+
143
+ this.name = name;
144
+ this.schema = opts?.schema || (ctor.schema as Schema);
48
145
  }
49
146
 
50
147
  getSchema(opts: { type: TSchemaType }) {
148
+ const factory = BaseEntity.schemaFactory; // Uses static singleton
51
149
  switch (opts.type) {
52
- case SchemaTypes.CREATE: {
53
- return this.schemaFactory.createInsertSchema(this.schema);
54
- }
55
- case SchemaTypes.UPDATE: {
56
- return this.schemaFactory.createUpdateSchema(this.schema);
57
- }
58
- case SchemaTypes.SELECT: {
59
- return this.schemaFactory.createSelectSchema(this.schema);
60
- }
61
- default: {
150
+ case SchemaTypes.CREATE:
151
+ return factory.createInsertSchema(this.schema);
152
+ case SchemaTypes.UPDATE:
153
+ return factory.createUpdateSchema(this.schema);
154
+ case SchemaTypes.SELECT:
155
+ return factory.createSelectSchema(this.schema);
156
+ default:
62
157
  throw getError({
63
- message: `[getSchema] Invalid schema type | type: ${opts.type} | valid: ${[SchemaTypes.SELECT, SchemaTypes.UPDATE, SchemaTypes.CREATE]}`,
158
+ message: `[getSchema] Invalid schema type | type: ${opts.type}`,
64
159
  });
65
- }
66
160
  }
67
161
  }
162
+
163
+ toObject() {
164
+ return { ...this };
165
+ }
166
+
167
+ toJSON() {
168
+ return this.toObject();
169
+ }
68
170
  }
69
171
  ```
70
172
 
71
- When you define a model in your application, you extend `BaseEntity`, passing your Drizzle table schema to the `super` constructor.
173
+ **Performance Note:** The `schemaFactory` is implemented as a static lazy singleton, meaning it's created once and shared across all `BaseEntity` instances. This avoids the overhead of creating a new `drizzle-zod` schema factory for every entity instantiation.
72
174
 
73
175
  ## Schema Enrichers
74
176
 
@@ -79,7 +181,7 @@ Enrichers are helper functions located in `packages/core/src/base/models/enriche
79
181
  | Enricher Function | Purpose |
80
182
  | :--- | :--- |
81
183
  | **`generateIdColumnDefs`** | Adds a primary key `id` column (string UUID or numeric serial). |
82
- | **`generateTzColumnDefs`** | Adds `createdAt` and `modifiedAt` timestamp columns with timezone support. |
184
+ | **`generateTzColumnDefs`** | Adds `createdAt`, `modifiedAt`, and `deletedAt` timestamp columns with timezone support. |
83
185
  | **`generateUserAuditColumnDefs`** | Adds `createdBy` and `modifiedBy` columns to track user audit information. |
84
186
  | **`generateDataTypeColumnDefs`** | Adds generic data type columns (`dataType`, `nValue`, `tValue`, `bValue`, `jValue`, `boValue`) for flexible data storage. |
85
187
  | **`generatePrincipalColumnDefs`** | Adds polymorphic fields for associating with different principal types. |
@@ -102,3 +204,589 @@ export const myTable = pgTable('MyTable', {
102
204
  name: text('name').notNull(),
103
205
  });
104
206
  ```
207
+
208
+ ---
209
+
210
+ ## Detailed Enricher Reference
211
+
212
+ ### `generateIdColumnDefs`
213
+
214
+ Adds a primary key `id` column with support for string UUID, integer, or big integer types with full TypeScript type inference.
215
+
216
+ **File:** `packages/core/src/base/models/enrichers/id.enricher.ts`
217
+
218
+ #### Signature
219
+
220
+ ```typescript
221
+ generateIdColumnDefs<Opts extends TIdEnricherOptions | undefined>(
222
+ opts?: Opts,
223
+ ): TIdColumnDef<Opts>
224
+ ```
225
+
226
+ #### Options (`TIdEnricherOptions`)
227
+
228
+ ```typescript
229
+ type TIdEnricherOptions = {
230
+ id?: { columnName?: string } & (
231
+ | { dataType: 'string' }
232
+ | {
233
+ dataType: 'number';
234
+ sequenceOptions?: PgSequenceOptions;
235
+ }
236
+ | {
237
+ dataType: 'big-number';
238
+ numberMode: 'number' | 'bigint'; // Required for big-number
239
+ sequenceOptions?: PgSequenceOptions;
240
+ }
241
+ );
242
+ };
243
+ ```
244
+
245
+ **Default values:**
246
+ - `dataType`: `'number'` (auto-incrementing integer)
247
+ - `columnName`: `'id'`
248
+
249
+ #### Generated Columns
250
+
251
+ | Data Type | Column Type | Constraints | Description |
252
+ |-----------|------------|-------------|-------------|
253
+ | `'string'` | `uuid` | Primary Key, Default: `gen_random_uuid()` | Native PostgreSQL UUID (no extension required) |
254
+ | `'number'` | `integer` | Primary Key, `GENERATED ALWAYS AS IDENTITY` | Auto-incrementing integer |
255
+ | `'big-number'` | `bigint` | Primary Key, `GENERATED ALWAYS AS IDENTITY` | Auto-incrementing big integer (mode: 'number' or 'bigint') |
256
+
257
+ #### Type Inference
258
+
259
+ The function provides **full TypeScript type inference** based on the configuration options:
260
+
261
+ ```typescript
262
+ type TIdColumnDef<Opts extends TIdEnricherOptions | undefined> =
263
+ Opts extends { id: infer IdOpts }
264
+ ? IdOpts extends { dataType: 'string' }
265
+ ? { id: IsPrimaryKey<NotNull<HasDefault<PgUUIDBuilderInitial<'id'>>>> }
266
+ : IdOpts extends { dataType: 'number' }
267
+ ? { id: IsIdentity<IsPrimaryKey<NotNull<PgIntegerBuilderInitial<'id'>>>, 'always'> }
268
+ : IdOpts extends { dataType: 'big-number' }
269
+ ? IdOpts extends { numberMode: 'number' }
270
+ ? { id: IsIdentity<IsPrimaryKey<NotNull<PgBigInt53BuilderInitial<'id'>>>, 'always'> }
271
+ : { id: IsIdentity<IsPrimaryKey<NotNull<PgBigInt64BuilderInitial<'id'>>>, 'always'> }
272
+ : { id: IsIdentity<IsPrimaryKey<NotNull<PgIntegerBuilderInitial<'id'>>>, 'always'> }
273
+ : { id: IsIdentity<IsPrimaryKey<NotNull<PgIntegerBuilderInitial<'id'>>>, 'always'> };
274
+ ```
275
+
276
+ This ensures that TypeScript correctly infers the exact column type based on your configuration.
277
+
278
+ #### Usage Examples
279
+
280
+ **Default (auto-incrementing integer):**
281
+
282
+ ```typescript
283
+ import { pgTable, text } from 'drizzle-orm/pg-core';
284
+ import { generateIdColumnDefs } from '@venizia/ignis';
285
+
286
+ export const myTable = pgTable('MyTable', {
287
+ ...generateIdColumnDefs(),
288
+ name: text('name').notNull(),
289
+ });
290
+
291
+ // Generates: id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY
292
+ ```
293
+
294
+ **UUID-based string ID:**
295
+
296
+ ```typescript
297
+ export const myTable = pgTable('MyTable', {
298
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
299
+ name: text('name').notNull(),
300
+ });
301
+
302
+ // Generates: id uuid PRIMARY KEY DEFAULT gen_random_uuid()
303
+ // No extension required - built into PostgreSQL 13+
304
+ ```
305
+
306
+ **Auto-incrementing integer with sequence options:**
307
+
308
+ ```typescript
309
+ export const myTable = pgTable('MyTable', {
310
+ ...generateIdColumnDefs({
311
+ id: {
312
+ dataType: 'number',
313
+ sequenceOptions: { startWith: 1000, increment: 1 },
314
+ },
315
+ }),
316
+ name: text('name').notNull(),
317
+ });
318
+
319
+ // Generates: id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (START WITH 1000 INCREMENT BY 1)
320
+ ```
321
+
322
+ **Big number with JavaScript number mode (up to 2^53-1):**
323
+
324
+ ```typescript
325
+ export const myTable = pgTable('MyTable', {
326
+ ...generateIdColumnDefs({
327
+ id: {
328
+ dataType: 'big-number',
329
+ numberMode: 'number', // Required field
330
+ sequenceOptions: { startWith: 1, increment: 1 },
331
+ },
332
+ }),
333
+ name: text('name').notNull(),
334
+ });
335
+
336
+ // Generates: id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY
337
+ // Type-safe: Returns PgBigInt53BuilderInitial (safe for JavaScript numbers)
338
+ ```
339
+
340
+ **Big number with BigInt mode (for values > 2^53-1):**
341
+
342
+ ```typescript
343
+ export const myTable = pgTable('MyTable', {
344
+ ...generateIdColumnDefs({
345
+ id: {
346
+ dataType: 'big-number',
347
+ numberMode: 'bigint', // Required field
348
+ sequenceOptions: { startWith: 1, increment: 1 },
349
+ },
350
+ }),
351
+ name: text('name').notNull(),
352
+ });
353
+
354
+ // Generates: id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY
355
+ // Type-safe: Returns PgBigInt64BuilderInitial (requires BigInt in JavaScript)
356
+ ```
357
+
358
+ #### Important Notes
359
+
360
+ - **UUID Type:** When using `dataType: 'string'`, the native PostgreSQL `uuid` type is used with `gen_random_uuid()` - no extension required (built into PostgreSQL 13+). This is more efficient than `text` type (16 bytes vs 36 bytes) and provides better indexing performance.
361
+ - **Type Safety:** The return type is fully inferred based on your options, providing better autocomplete and type checking
362
+ - **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)
363
+ - **Sequence Options:** Available for `number` and `big-number` types to customize identity generation behavior
364
+
365
+ ---
366
+
367
+ ### `generateTzColumnDefs`
368
+
369
+ Adds timestamp columns for tracking entity creation, modification, and soft deletion.
370
+
371
+ **File:** `packages/core/src/base/models/enrichers/tz.enricher.ts`
372
+
373
+ #### Signature
374
+
375
+ ```typescript
376
+ generateTzColumnDefs(opts?: TTzEnricherOptions): TTzEnricherResult
377
+ ```
378
+
379
+ #### Options (`TTzEnricherOptions`)
380
+
381
+ ```typescript
382
+ type TTzEnricherOptions = {
383
+ created?: { columnName: string; withTimezone: boolean };
384
+ modified?: { enable: false } | { enable?: true; columnName: string; withTimezone: boolean };
385
+ deleted?: { enable: false } | { enable?: true; columnName: string; withTimezone: boolean };
386
+ };
387
+ ```
388
+
389
+ The `modified` and `deleted` options use a discriminated union pattern:
390
+ - When `enable: false`, no other properties are needed
391
+ - When `enable: true` (or omitted), `columnName` and `withTimezone` are required
392
+
393
+ **Default values:**
394
+ - `created`: `{ columnName: 'created_at', withTimezone: true }`
395
+ - `modified`: `{ enable: true, columnName: 'modified_at', withTimezone: true }`
396
+ - `deleted`: `{ enable: false }` (disabled by default)
397
+
398
+ #### Generated Columns
399
+
400
+ | Column | Type | Constraints | Default | Description |
401
+ |--------|------|-------------|---------|-------------|
402
+ | `createdAt` | `timestamp` | `NOT NULL` | `now()` | When the record was created (always included) |
403
+ | `modifiedAt` | `timestamp` | `NOT NULL` | `now()` | When the record was last modified (optional, enabled by default) |
404
+ | `deletedAt` | `timestamp` | nullable | `null` | When the record was soft-deleted (optional, **disabled by default**) |
405
+
406
+ #### Usage Examples
407
+
408
+ **Basic usage (default columns):**
409
+
410
+ ```typescript
411
+ import { pgTable, text } from 'drizzle-orm/pg-core';
412
+ import { generateTzColumnDefs } from '@venizia/ignis';
413
+
414
+ export const myTable = pgTable('MyTable', {
415
+ ...generateTzColumnDefs(),
416
+ name: text('name').notNull(),
417
+ });
418
+
419
+ // Generates: createdAt, modifiedAt (deletedAt is disabled by default)
420
+ ```
421
+
422
+ **Enable soft delete:**
423
+
424
+ ```typescript
425
+ export const myTable = pgTable('MyTable', {
426
+ ...generateTzColumnDefs({
427
+ deleted: { enable: true, columnName: 'deleted_at', withTimezone: true },
428
+ }),
429
+ name: text('name').notNull(),
430
+ });
431
+
432
+ // Generates: createdAt, modifiedAt, deletedAt
433
+ ```
434
+
435
+ **Custom column names:**
436
+
437
+ ```typescript
438
+ export const myTable = pgTable('MyTable', {
439
+ ...generateTzColumnDefs({
440
+ created: { columnName: 'created_date', withTimezone: true },
441
+ modified: { enable: true, columnName: 'updated_date', withTimezone: true },
442
+ deleted: { enable: true, columnName: 'removed_date', withTimezone: true },
443
+ }),
444
+ name: text('name').notNull(),
445
+ });
446
+ ```
447
+
448
+ **Without timezone:**
449
+
450
+ ```typescript
451
+ export const myTable = pgTable('MyTable', {
452
+ ...generateTzColumnDefs({
453
+ created: { columnName: 'created_at', withTimezone: false },
454
+ modified: { enable: true, columnName: 'modified_at', withTimezone: false },
455
+ deleted: { enable: true, columnName: 'deleted_at', withTimezone: false },
456
+ }),
457
+ name: text('name').notNull(),
458
+ });
459
+ ```
460
+
461
+
462
+
463
+ **Minimal setup (only createdAt):**
464
+
465
+ ```typescript
466
+ export const myTable = pgTable('MyTable', {
467
+ ...generateTzColumnDefs({
468
+ modified: { enable: false },
469
+ deleted: { enable: false },
470
+ }),
471
+ name: text('name').notNull(),
472
+ });
473
+
474
+ // Generates: createdAt only
475
+ ```
476
+
477
+ #### Soft Delete Pattern
478
+
479
+ The `deletedAt` column enables the soft delete pattern, where records are marked as deleted rather than physically removed from the database.
480
+
481
+ **Example soft delete query:**
482
+
483
+ ```typescript
484
+ import { eq, isNull } from 'drizzle-orm';
485
+
486
+ // Soft delete: set deletedAt timestamp
487
+ await db.update(myTable)
488
+ .set({ deletedAt: new Date() })
489
+ .where(eq(myTable.id, id));
490
+
491
+ // Query only active (non-deleted) records
492
+ const activeRecords = await db.select()
493
+ .from(myTable)
494
+ .where(isNull(myTable.deletedAt));
495
+
496
+ // Query deleted records
497
+ const deletedRecords = await db.select()
498
+ .from(myTable)
499
+ .where(isNotNull(myTable.deletedAt));
500
+
501
+ // Restore a soft-deleted record
502
+ await db.update(myTable)
503
+ .set({ deletedAt: null })
504
+ .where(eq(myTable.id, id));
505
+ ```
506
+
507
+ #### Type Inference
508
+
509
+ The enricher provides proper TypeScript type inference:
510
+
511
+ ```typescript
512
+ type TTzEnricherResult<ColumnDefinitions extends TColumnDefinitions = TColumnDefinitions> = {
513
+ createdAt: PgTimestampBuilderInitial<string> & NotNull & HasDefault;
514
+ modifiedAt?: PgTimestampBuilderInitial<string> & NotNull & HasDefault;
515
+ deletedAt?: PgTimestampBuilderInitial<string>;
516
+ };
517
+ ```
518
+
519
+ ---
520
+
521
+ ### `generateUserAuditColumnDefs`
522
+
523
+ Adds `createdBy` and `modifiedBy` columns to track which user created or modified a record.
524
+
525
+ **File:** `packages/core/src/base/models/enrichers/user-audit.enricher.ts`
526
+
527
+ #### Signature
528
+
529
+ ```typescript
530
+ generateUserAuditColumnDefs(opts?: TUserAuditEnricherOptions): {
531
+ createdBy: PgIntegerBuilderInitial | PgTextBuilderInitial;
532
+ modifiedBy: PgIntegerBuilderInitial | PgTextBuilderInitial;
533
+ }
534
+ ```
535
+
536
+ #### Options (`TUserAuditEnricherOptions`)
537
+
538
+ ```typescript
539
+ type TUserAuditColumnOpts = {
540
+ dataType: 'string' | 'number'; // Required - type of user ID
541
+ columnName: string; // Column name in database
542
+ };
543
+
544
+ type TUserAuditEnricherOptions = {
545
+ created?: TUserAuditColumnOpts;
546
+ modified?: TUserAuditColumnOpts;
547
+ };
548
+ ```
549
+
550
+ **Default values:**
551
+ - `created`: `{ dataType: 'number', columnName: 'created_by' }`
552
+ - `modified`: `{ dataType: 'number', columnName: 'modified_by' }`
553
+
554
+ #### Generated Columns
555
+
556
+ | Column | Data Type | Column Name | Description |
557
+ |--------|-----------|-------------|-------------|
558
+ | `createdBy` | `integer` or `text` | `created_by` | User ID who created the record |
559
+ | `modifiedBy` | `integer` or `text` | `modified_by` | User ID who last modified the record |
560
+
561
+ #### Validation
562
+
563
+ The enricher validates the `dataType` option and throws an error for invalid values:
564
+
565
+ ```typescript
566
+ // ✅ Valid
567
+ generateUserAuditColumnDefs({ created: { dataType: 'number', columnName: 'created_by' } });
568
+ generateUserAuditColumnDefs({ created: { dataType: 'string', columnName: 'created_by' } });
569
+
570
+ // ❌ Invalid - throws error
571
+ generateUserAuditColumnDefs({ created: { dataType: 'uuid', columnName: 'created_by' } });
572
+ // Error: [enrichUserAudit] Invalid dataType for 'createdBy' | value: uuid | valid: ['number', 'string']
573
+ ```
574
+
575
+ #### Usage Examples
576
+
577
+ **Default (integer user IDs):**
578
+
579
+ ```typescript
580
+ import { pgTable, text } from 'drizzle-orm/pg-core';
581
+ import { generateIdColumnDefs, generateUserAuditColumnDefs } from '@venizia/ignis';
582
+
583
+ export const myTable = pgTable('MyTable', {
584
+ ...generateIdColumnDefs(),
585
+ ...generateUserAuditColumnDefs(),
586
+ name: text('name').notNull(),
587
+ });
588
+
589
+ // Generates:
590
+ // createdBy: integer('created_by')
591
+ // modifiedBy: integer('modified_by')
592
+ ```
593
+
594
+ **String user IDs (UUID):**
595
+
596
+ ```typescript
597
+ export const myTable = pgTable('MyTable', {
598
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
599
+ ...generateUserAuditColumnDefs({
600
+ created: { dataType: 'string', columnName: 'created_by' },
601
+ modified: { dataType: 'string', columnName: 'modified_by' },
602
+ }),
603
+ name: text('name').notNull(),
604
+ });
605
+
606
+ // Generates:
607
+ // createdBy: text('created_by')
608
+ // modifiedBy: text('modified_by')
609
+ ```
610
+
611
+ **Custom column names:**
612
+
613
+ ```typescript
614
+ export const myTable = pgTable('MyTable', {
615
+ ...generateIdColumnDefs(),
616
+ ...generateUserAuditColumnDefs({
617
+ created: { dataType: 'number', columnName: 'author_id' },
618
+ modified: { dataType: 'number', columnName: 'editor_id' },
619
+ }),
620
+ name: text('name').notNull(),
621
+ });
622
+
623
+ // Generates:
624
+ // createdBy: integer('author_id')
625
+ // modifiedBy: integer('editor_id')
626
+ ```
627
+
628
+ ---
629
+
630
+ ## Schema Utilities
631
+
632
+ ### `snakeToCamel`
633
+
634
+ Converts a Zod schema from snake_case to camelCase, transforming both the schema shape and runtime data.
635
+
636
+ **File:** `packages/core/src/base/models/common/types.ts`
637
+
638
+ #### Signature
639
+
640
+ ```typescript
641
+ snakeToCamel<T extends z.ZodRawShape>(shape: T): z.ZodEffects<...>
642
+ ```
643
+
644
+ #### Purpose
645
+
646
+ 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:
647
+
648
+ 1. Accepts snake_case input (validates against original schema)
649
+ 2. Transforms the data to camelCase at runtime
650
+ 3. Validates the transformed data against a camelCase schema
651
+
652
+ #### Usage Example
653
+
654
+ ```typescript
655
+ import { z } from 'zod';
656
+ import { snakeToCamel } from '@venizia/ignis';
657
+
658
+ // Define schema with snake_case fields
659
+ const userSnakeSchema = {
660
+ user_id: z.number(),
661
+ first_name: z.string(),
662
+ last_name: z.string(),
663
+ created_at: z.date(),
664
+ is_active: z.boolean(),
665
+ };
666
+
667
+ // Convert to camelCase schema
668
+ const userCamelSchema = snakeToCamel(userSnakeSchema);
669
+
670
+ // Input data from database (snake_case)
671
+ const dbData = {
672
+ user_id: 123,
673
+ first_name: 'John',
674
+ last_name: 'Doe',
675
+ created_at: new Date(),
676
+ is_active: true,
677
+ };
678
+
679
+ // Parse and transform to camelCase
680
+ const result = userCamelSchema.parse(dbData);
681
+
682
+ // Result is automatically camelCase:
683
+ console.log(result);
684
+ // {
685
+ // userId: 123,
686
+ // firstName: 'John',
687
+ // lastName: 'Doe',
688
+ // createdAt: Date,
689
+ // isActive: true
690
+ // }
691
+ ```
692
+
693
+ #### Real-world Example
694
+
695
+ **Use case:** API endpoint that accepts snake_case but works with camelCase internally
696
+
697
+ ```typescript
698
+ import { BaseController, controller, snakeToCamel, HTTP } from '@venizia/ignis';
699
+ import { z } from '@hono/zod-openapi';
700
+
701
+ const createUserSchema = snakeToCamel({
702
+ first_name: z.string().min(1),
703
+ last_name: z.string().min(1),
704
+ email_address: z.string().email(),
705
+ phone_number: z.string().optional(),
706
+ });
707
+
708
+ @controller({ path: '/users' })
709
+ export class UserController extends BaseController {
710
+ override binding() {
711
+ this.bindRoute({
712
+ configs: {
713
+ path: '/',
714
+ method: 'post',
715
+ request: {
716
+ body: {
717
+ content: {
718
+ 'application/json': { schema: createUserSchema },
719
+ },
720
+ },
721
+ },
722
+ },
723
+ }).to({
724
+ handler: async (ctx) => {
725
+ // Request body is automatically camelCase
726
+ const data = ctx.req.valid('json');
727
+
728
+ // data = {
729
+ // firstName: string,
730
+ // lastName: string,
731
+ // emailAddress: string,
732
+ // phoneNumber?: string
733
+ // }
734
+
735
+ // Work with camelCase data
736
+ console.log(data.firstName); // ✅ TypeScript knows this exists
737
+ console.log(data.first_name); // ❌ TypeScript error
738
+
739
+ return ctx.json({ success: true }, HTTP.ResultCodes.RS_2.Ok);
740
+ },
741
+ });
742
+ }
743
+ }
744
+ ```
745
+
746
+ #### Type Transformation
747
+
748
+ The utility includes sophisticated TypeScript type transformation:
749
+
750
+ ```typescript
751
+ type TSnakeToCamelCase<S extends string> =
752
+ S extends `${infer T}_${infer U}`
753
+ ? `${T}${Capitalize<TSnakeToCamelCase<U>>}`
754
+ : S;
755
+
756
+ type TCamelCaseKeys<T extends z.ZodRawShape> = {
757
+ [K in keyof T as K extends string ? TSnakeToCamelCase<K> : K]:
758
+ T[K] extends z.ZodType<infer U> ? z.ZodType<U> : T[K];
759
+ };
760
+ ```
761
+
762
+ This ensures full type safety: TypeScript will know that `first_name` becomes `firstName`, `created_at` becomes `createdAt`, etc.
763
+
764
+ #### Validation
765
+
766
+ The schema validates twice for safety:
767
+
768
+ 1. **First validation:** Checks that input matches snake_case schema
769
+ 2. **Transformation:** Converts keys from snake_case to camelCase
770
+ 3. **Second validation:** Validates transformed data against camelCase schema
771
+
772
+ ```typescript
773
+ // If validation fails at any step, you get clear error messages
774
+ const invalidData = {
775
+ user_id: 'not-a-number', // ❌ Fails first validation
776
+ first_name: 'John',
777
+ last_name: 'Doe',
778
+ };
779
+
780
+ try {
781
+ userCamelSchema.parse(invalidData);
782
+ } catch (error) {
783
+ // ZodError with clear message about user_id expecting number
784
+ }
785
+ ```
786
+
787
+ #### Notes
788
+
789
+ - Built on top of `keysToCamel()` and `toCamel()` utilities from `@venizia/ignis-helpers`
790
+ - Recursively handles nested objects
791
+ - Preserves array structures
792
+ - Works seamlessly with Zod's other features (refinements, transforms, etc.)