@venizia/ignis-docs 0.0.3 → 0.0.4-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +1 -1
  2. package/package.json +4 -2
  3. package/wiki/best-practices/api-usage-examples.md +591 -0
  4. package/wiki/best-practices/architectural-patterns.md +415 -0
  5. package/wiki/best-practices/architecture-decisions.md +488 -0
  6. package/wiki/{get-started/best-practices → best-practices}/code-style-standards.md +406 -17
  7. package/wiki/{get-started/best-practices → best-practices}/common-pitfalls.md +109 -4
  8. package/wiki/{get-started/best-practices → best-practices}/contribution-workflow.md +34 -7
  9. package/wiki/best-practices/data-modeling.md +376 -0
  10. package/wiki/best-practices/deployment-strategies.md +698 -0
  11. package/wiki/best-practices/index.md +27 -0
  12. package/wiki/best-practices/performance-optimization.md +196 -0
  13. package/wiki/best-practices/security-guidelines.md +218 -0
  14. package/wiki/{get-started/best-practices → best-practices}/troubleshooting-tips.md +97 -1
  15. package/wiki/changelogs/2025-12-16-initial-architecture.md +1 -1
  16. package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +1 -1
  17. package/wiki/changelogs/2025-12-17-refactor.md +1 -1
  18. package/wiki/changelogs/2025-12-18-performance-optimizations.md +5 -5
  19. package/wiki/changelogs/2025-12-18-repository-validation-security.md +13 -7
  20. package/wiki/changelogs/2025-12-26-nested-relations-and-generics.md +2 -2
  21. package/wiki/changelogs/2025-12-29-dynamic-binding-registration.md +104 -0
  22. package/wiki/changelogs/2025-12-29-snowflake-uid-helper.md +100 -0
  23. package/wiki/changelogs/2025-12-30-repository-enhancements.md +214 -0
  24. package/wiki/changelogs/2025-12-31-json-path-filtering-array-operators.md +214 -0
  25. package/wiki/changelogs/2025-12-31-string-id-custom-generator.md +137 -0
  26. package/wiki/changelogs/2026-01-02-default-filter-and-repository-mixins.md +418 -0
  27. package/wiki/changelogs/index.md +6 -0
  28. package/wiki/changelogs/planned-schema-migrator.md +0 -8
  29. package/wiki/{get-started/core-concepts → guides/core-concepts/application}/bootstrapping.md +18 -5
  30. package/wiki/{get-started/core-concepts/application.md → guides/core-concepts/application/index.md} +47 -104
  31. package/wiki/guides/core-concepts/components-guide.md +509 -0
  32. package/wiki/{get-started → guides}/core-concepts/components.md +24 -17
  33. package/wiki/{get-started → guides}/core-concepts/controllers.md +30 -13
  34. package/wiki/{get-started → guides}/core-concepts/dependency-injection.md +97 -0
  35. package/wiki/guides/core-concepts/persistent/datasources.md +179 -0
  36. package/wiki/guides/core-concepts/persistent/index.md +119 -0
  37. package/wiki/guides/core-concepts/persistent/models.md +241 -0
  38. package/wiki/guides/core-concepts/persistent/repositories.md +219 -0
  39. package/wiki/guides/core-concepts/persistent/transactions.md +170 -0
  40. package/wiki/{get-started → guides}/core-concepts/services.md +26 -3
  41. package/wiki/{get-started → guides/get-started}/5-minute-quickstart.md +59 -14
  42. package/wiki/guides/get-started/philosophy.md +682 -0
  43. package/wiki/guides/get-started/setup.md +157 -0
  44. package/wiki/guides/index.md +89 -0
  45. package/wiki/guides/reference/glossary.md +243 -0
  46. package/wiki/{get-started → guides/reference}/mcp-docs-server.md +0 -10
  47. package/wiki/{get-started → guides/tutorials}/building-a-crud-api.md +134 -132
  48. package/wiki/{get-started/quickstart.md → guides/tutorials/complete-installation.md} +107 -71
  49. package/wiki/guides/tutorials/ecommerce-api.md +1399 -0
  50. package/wiki/guides/tutorials/realtime-chat.md +1261 -0
  51. package/wiki/guides/tutorials/testing.md +723 -0
  52. package/wiki/index.md +176 -37
  53. package/wiki/references/base/application.md +27 -0
  54. package/wiki/references/base/bootstrapping.md +31 -26
  55. package/wiki/references/base/components.md +24 -7
  56. package/wiki/references/base/controllers.md +50 -20
  57. package/wiki/references/base/datasources.md +30 -0
  58. package/wiki/references/base/dependency-injection.md +39 -3
  59. package/wiki/references/base/filter-system/application-usage.md +224 -0
  60. package/wiki/references/base/filter-system/array-operators.md +132 -0
  61. package/wiki/references/base/filter-system/comparison-operators.md +109 -0
  62. package/wiki/references/base/filter-system/default-filter.md +428 -0
  63. package/wiki/references/base/filter-system/fields-order-pagination.md +155 -0
  64. package/wiki/references/base/filter-system/index.md +127 -0
  65. package/wiki/references/base/filter-system/json-filtering.md +197 -0
  66. package/wiki/references/base/filter-system/list-operators.md +71 -0
  67. package/wiki/references/base/filter-system/logical-operators.md +156 -0
  68. package/wiki/references/base/filter-system/null-operators.md +58 -0
  69. package/wiki/references/base/filter-system/pattern-matching.md +108 -0
  70. package/wiki/references/base/filter-system/quick-reference.md +431 -0
  71. package/wiki/references/base/filter-system/range-operators.md +63 -0
  72. package/wiki/references/base/filter-system/tips.md +190 -0
  73. package/wiki/references/base/filter-system/use-cases.md +452 -0
  74. package/wiki/references/base/index.md +90 -0
  75. package/wiki/references/base/middlewares.md +604 -0
  76. package/wiki/references/base/models.md +215 -23
  77. package/wiki/references/base/providers.md +731 -0
  78. package/wiki/references/base/repositories/advanced.md +555 -0
  79. package/wiki/references/base/repositories/index.md +228 -0
  80. package/wiki/references/base/repositories/mixins.md +331 -0
  81. package/wiki/references/base/repositories/relations.md +486 -0
  82. package/wiki/references/base/repositories.md +40 -635
  83. package/wiki/references/base/services.md +28 -4
  84. package/wiki/references/components/authentication.md +22 -2
  85. package/wiki/references/components/health-check.md +12 -0
  86. package/wiki/references/components/index.md +23 -0
  87. package/wiki/references/components/mail.md +687 -0
  88. package/wiki/references/components/request-tracker.md +16 -0
  89. package/wiki/references/components/socket-io.md +18 -0
  90. package/wiki/references/components/static-asset.md +14 -26
  91. package/wiki/references/components/swagger.md +17 -0
  92. package/wiki/references/configuration/environment-variables.md +427 -0
  93. package/wiki/references/configuration/index.md +73 -0
  94. package/wiki/references/helpers/cron.md +14 -0
  95. package/wiki/references/helpers/crypto.md +15 -0
  96. package/wiki/references/helpers/env.md +16 -0
  97. package/wiki/references/helpers/error.md +17 -0
  98. package/wiki/references/helpers/index.md +14 -0
  99. package/wiki/references/helpers/inversion.md +24 -4
  100. package/wiki/references/helpers/logger.md +19 -0
  101. package/wiki/references/helpers/network.md +11 -0
  102. package/wiki/references/helpers/queue.md +19 -0
  103. package/wiki/references/helpers/redis.md +21 -0
  104. package/wiki/references/helpers/socket-io.md +24 -5
  105. package/wiki/references/helpers/storage.md +18 -10
  106. package/wiki/references/helpers/testing.md +18 -0
  107. package/wiki/references/helpers/types.md +16 -0
  108. package/wiki/references/helpers/uid.md +167 -0
  109. package/wiki/references/helpers/worker-thread.md +16 -0
  110. package/wiki/references/index.md +177 -0
  111. package/wiki/references/quick-reference.md +634 -0
  112. package/wiki/references/src-details/boot.md +3 -3
  113. package/wiki/references/src-details/dev-configs.md +0 -4
  114. package/wiki/references/src-details/docs.md +2 -2
  115. package/wiki/references/src-details/index.md +86 -0
  116. package/wiki/references/src-details/inversion.md +1 -6
  117. package/wiki/references/src-details/mcp-server.md +3 -15
  118. package/wiki/references/utilities/index.md +86 -10
  119. package/wiki/references/utilities/jsx.md +577 -0
  120. package/wiki/references/utilities/request.md +0 -2
  121. package/wiki/references/utilities/statuses.md +740 -0
  122. package/wiki/get-started/best-practices/api-usage-examples.md +0 -266
  123. package/wiki/get-started/best-practices/architectural-patterns.md +0 -170
  124. package/wiki/get-started/best-practices/data-modeling.md +0 -177
  125. package/wiki/get-started/best-practices/deployment-strategies.md +0 -121
  126. package/wiki/get-started/best-practices/performance-optimization.md +0 -97
  127. package/wiki/get-started/best-practices/security-guidelines.md +0 -99
  128. package/wiki/get-started/core-concepts/persistent.md +0 -539
  129. package/wiki/get-started/index.md +0 -65
  130. package/wiki/get-started/philosophy.md +0 -296
  131. package/wiki/get-started/prerequisites.md +0 -113
@@ -0,0 +1,104 @@
1
+ ---
2
+ title: Dynamic Binding Registration Fix
3
+ description: Fix components registering datasources/controllers during configure() phase
4
+ ---
5
+
6
+ # Changelog - 2025-12-29
7
+
8
+ ## Dynamic Binding Registration Fix
9
+
10
+ Components can now register datasources and controllers during their `configure()` phase, and those bindings will be properly configured.
11
+
12
+ ## Overview
13
+
14
+ - **Simple Fix**: Re-run `registerDataSources()` after `registerComponents()`
15
+ - **Future Enhancement**: Multi-pass configuration loop for comprehensive dynamic registration
16
+
17
+ ## Problem
18
+
19
+ When components register datasources during `configure()`, those datasources were never configured because `registerDataSources()` had already completed.
20
+
21
+ **Initialization Order (Before):**
22
+ 1. `registerDataSources()` → configures initial datasources
23
+ 2. `registerComponents()` → component registers NEW datasource (too late!)
24
+ 3. `registerControllers()` → runs normally
25
+
26
+ The new datasource never gets its `configure()` called.
27
+
28
+ ## Solution (Simple Fix)
29
+
30
+ **File:** `packages/core/src/base/applications/base.ts`
31
+
32
+ Call `registerDataSources()` again after `registerComponents()`:
33
+
34
+ ```typescript
35
+ // Before:
36
+ await this.registerDataSources();
37
+ await this.registerComponents();
38
+ await this.registerControllers();
39
+
40
+ // After:
41
+ await this.registerDataSources();
42
+ await this.registerComponents();
43
+ await this.registerDataSources(); // Re-run for datasources added by components
44
+ await this.registerControllers();
45
+ ```
46
+
47
+ ## Files Changed
48
+
49
+ ### Core Package (`packages/core`)
50
+
51
+ | File | Changes |
52
+ |------|---------|
53
+ | `src/base/applications/base.ts` | Added second `registerDataSources()` call after `registerComponents()` |
54
+
55
+ ## No Breaking Changes
56
+
57
+ All changes are internal. Components that register datasources will now work correctly.
58
+
59
+ ## Future Enhancement
60
+
61
+ For comprehensive dynamic registration support (components registering other components, controllers registering datasources, etc.), a multi-pass configuration loop can be implemented:
62
+
63
+ ### Multi-Pass Configuration Loop (Future)
64
+
65
+ Instead of sequential registration, use a unified loop that continues until no new bindings are discovered:
66
+
67
+ ```typescript
68
+ protected async runConfigurationLoop(): Promise<void> {
69
+ const configured = {
70
+ datasources: new Set<string>(),
71
+ components: new Set<string>(),
72
+ controllers: new Set<string>(),
73
+ };
74
+
75
+ let hasNewBindings = true;
76
+ let iteration = 0;
77
+ const MAX_ITERATIONS = 100;
78
+
79
+ while (hasNewBindings && iteration < MAX_ITERATIONS) {
80
+ iteration++;
81
+ hasNewBindings = false;
82
+
83
+ // Order: datasources -> components -> controllers
84
+ const newDs = await this.configureNewBindings('datasources', configured.datasources);
85
+ const newComp = await this.configureNewBindings('components', configured.components);
86
+ const newCtrl = await this.configureNewBindings('controllers', configured.controllers);
87
+
88
+ hasNewBindings = newDs || newComp || newCtrl;
89
+ }
90
+ }
91
+ ```
92
+
93
+ **Benefits of Multi-Pass:**
94
+ - Handles arbitrary nesting depth
95
+ - Components can register datasources, controllers, or other components
96
+ - Datasources configured before components that depend on them
97
+ - MAX_ITERATIONS prevents infinite loops from circular dependencies
98
+
99
+ **LoopBack 4 Insights Applied:**
100
+ - Three-phase execution pattern (configure → discover → load)
101
+ - Observer ordering (datasources before components before controllers)
102
+ - Stabilization detection (loop until no new bindings)
103
+
104
+ See full implementation plan in architecture documentation.
@@ -0,0 +1,100 @@
1
+ ---
2
+ title: Snowflake UID Helper
3
+ description: New Snowflake ID generator with Base62 encoding for distributed systems
4
+ ---
5
+
6
+ # Changelog - 2025-12-29
7
+
8
+ ## Snowflake UID Helper
9
+
10
+ New unique ID generator using Twitter's Snowflake algorithm with Base62 encoding, suitable for distributed systems.
11
+
12
+ ## Overview
13
+
14
+ - **New Helper**: `SnowflakeUidHelper` for generating unique, time-sortable IDs
15
+ - **Base62 Encoding**: Compact string output (10-12 characters)
16
+ - **Configurable**: Optional `workerId` and `epoch` parameters
17
+ - **No Environment Variables**: Clean constructor-based configuration
18
+
19
+ ## New Features
20
+
21
+ ### SnowflakeUidHelper
22
+
23
+ **File:** `packages/helpers/src/helpers/uid/helper.ts`
24
+
25
+ **Problem:** Need unique, time-sortable IDs for distributed systems without external dependencies.
26
+
27
+ **Solution:** Snowflake ID generator with 70-bit structure (48-10-12):
28
+ - 48 bits: timestamp (~8,919 years from epoch)
29
+ - 10 bits: worker ID (1024 workers max)
30
+ - 12 bits: sequence (4096 per ms per worker)
31
+
32
+ ```typescript
33
+ import { SnowflakeUidHelper, SnowflakeConfig } from '@venizia/ignis-helpers';
34
+
35
+ // Use defaults (workerId: 199, epoch: 2025-01-01 00:00:00 UTC)
36
+ const generator = new SnowflakeUidHelper();
37
+
38
+ // Or with custom values
39
+ const customGenerator = new SnowflakeUidHelper({
40
+ workerId: 123,
41
+ epoch: BigInt(1735689600000),
42
+ });
43
+
44
+ // Generate IDs
45
+ const id = generator.nextId(); // Base62: "9du1sJXO88"
46
+ const snowflake = generator.nextSnowflake(); // BigInt: 130546360012247045n
47
+
48
+ // Parse IDs
49
+ const parsed = generator.parseId("9du1sJXO88");
50
+ // => { raw, timestamp, workerId, sequence }
51
+ ```
52
+
53
+ **Benefits:**
54
+ - High throughput: 4,096,000 IDs/second/worker
55
+ - Time-sortable: IDs are chronologically ordered
56
+ - Compact: 10-12 character Base62 strings
57
+ - No collisions: Worker ID + sequence ensures uniqueness
58
+ - Long lifespan: Until ~10,944 AD
59
+
60
+ ### Configuration Options
61
+
62
+ | Option | Type | Default | Description |
63
+ |--------|------|---------|-------------|
64
+ | `workerId` | `number` | `199` | Worker ID (0-1023) |
65
+ | `epoch` | `bigint` | `1735689600000n` | Custom epoch (2025-01-01 UTC) |
66
+
67
+ ### Available Methods
68
+
69
+ | Method | Returns | Description |
70
+ |--------|---------|-------------|
71
+ | `nextId()` | `string` | Generate Base62 encoded ID |
72
+ | `nextSnowflake()` | `bigint` | Generate raw Snowflake ID |
73
+ | `encodeBase62(num)` | `string` | Encode bigint to Base62 |
74
+ | `decodeBase62(str)` | `bigint` | Decode Base62 to bigint |
75
+ | `parseId(base62Id)` | `ISnowflakeParsedId` | Parse and extract components |
76
+ | `extractTimestamp(id)` | `Date` | Extract timestamp from ID |
77
+ | `extractWorkerId(id)` | `number` | Extract worker ID from ID |
78
+ | `extractSequence(id)` | `number` | Extract sequence from ID |
79
+ | `getWorkerId()` | `number` | Get current worker ID |
80
+
81
+ ## Files Changed
82
+
83
+ ### Helpers Package (`packages/helpers`)
84
+
85
+ | File | Changes |
86
+ |------|---------|
87
+ | `src/helpers/uid/helper.ts` | New Snowflake ID generator class |
88
+ | `src/helpers/uid/index.ts` | Export helper |
89
+ | `src/helpers/index.ts` | Added uid export |
90
+
91
+ ### Docs Package (`packages/docs`)
92
+
93
+ | File | Changes |
94
+ |------|---------|
95
+ | `wiki/references/helpers/uid.md` | New documentation |
96
+ | `wiki/references/helpers/index.md` | Added UID to helper list |
97
+
98
+ ## No Breaking Changes
99
+
100
+ This is a new feature addition. No existing APIs were modified.
@@ -0,0 +1,214 @@
1
+ ---
2
+ title: Repository Enhancements
3
+ description: Hidden properties, filter improvements, and code quality updates
4
+ ---
5
+
6
+ # Changelog - 2025-12-30
7
+
8
+ ## Repository Enhancements
9
+
10
+ This release introduces hidden properties configuration for models, array-based field selection, JSON path ordering, and several code quality improvements.
11
+
12
+ ## Overview
13
+
14
+ - **Hidden Properties**: Configure properties that are never returned through repository queries (SQL-level exclusion)
15
+ - **Array Fields Format**: Simpler syntax for field selection using arrays
16
+ - **JSON Path Ordering**: Order by nested fields within JSON/JSONB columns
17
+ - **Code Quality**: Refactored validation logic, improved caching patterns, consistent resolver pattern
18
+
19
+
20
+ ## Hidden Properties
21
+
22
+ ### Configuration
23
+
24
+ Configure hidden properties in the `@model` decorator. These properties are excluded at the SQL level, never leaving the database in repository operations.
25
+
26
+ ```typescript
27
+ import { pgTable, text } from 'drizzle-orm/pg-core';
28
+ import { BaseEntity, model, generateIdColumnDefs } from '@venizia/ignis';
29
+
30
+ @model({
31
+ type: 'entity',
32
+ settings: {
33
+ hiddenProperties: ['password', 'secret'],
34
+ },
35
+ })
36
+ export class User extends BaseEntity<typeof User.schema> {
37
+ static override schema = pgTable('User', {
38
+ ...generateIdColumnDefs({ id: { dataType: 'string' } }),
39
+ email: text('email').notNull(),
40
+ password: text('password'),
41
+ secret: text('secret'),
42
+ });
43
+ }
44
+ ```
45
+
46
+ ### Behavior
47
+
48
+ | Operation | Behavior |
49
+ |-----------|----------|
50
+ | `find()`, `findOne()`, `findById()` | Hidden excluded from SELECT |
51
+ | `create()`, `createAll()` | Hidden excluded from RETURNING |
52
+ | `updateById()`, `updateAll()` | Hidden excluded from RETURNING |
53
+ | `deleteById()`, `deleteAll()` | Hidden excluded from RETURNING |
54
+ | `count()`, `existsWith()` | Can filter by hidden fields |
55
+ | Where clause filtering | Hidden fields usable in filters |
56
+ | Direct connector query | Hidden fields **included** (bypasses repository) |
57
+
58
+ ### Accessing Hidden Data
59
+
60
+ When you need to access hidden properties, use the connector directly:
61
+
62
+ ```typescript
63
+ // Repository - excludes hidden
64
+ const user = await userRepo.findById({ id: '123' });
65
+ // { id: '123', email: 'john@example.com' }
66
+
67
+ // Connector - includes all fields
68
+ const connector = userRepo.getConnector();
69
+ const [fullUser] = await connector
70
+ .select()
71
+ .from(User.schema)
72
+ .where(eq(User.schema.id, '123'));
73
+ // { id: '123', email: 'john@example.com', password: '...', secret: '...' }
74
+ ```
75
+
76
+ ### Relations Support
77
+
78
+ Hidden properties are recursively excluded from included relations:
79
+
80
+ ```typescript
81
+ const post = await postRepo.findOne({
82
+ filter: {
83
+ include: [{ relation: 'author' }]
84
+ }
85
+ });
86
+ // post.author excludes password and secret if User model has them configured
87
+ ```
88
+
89
+
90
+ ## Array Fields Format
91
+
92
+ **Before:** Only object format was supported:
93
+ ```typescript
94
+ fields: { id: true, email: true, name: true }
95
+ ```
96
+
97
+ **After:** Array format is now supported (recommended):
98
+ ```typescript
99
+ fields: ['id', 'email', 'name']
100
+ ```
101
+
102
+ Both formats produce the same result. The array format is more concise and easier to read.
103
+
104
+ **Type Definition:**
105
+ ```typescript
106
+ type TFields<T> = Partial<{ [K in keyof T]: boolean }> | Array<keyof T>;
107
+ ```
108
+
109
+
110
+ ## JSON Path Ordering
111
+
112
+ Order by nested fields within JSON/JSONB columns using dot notation and array indices.
113
+
114
+ ```typescript
115
+ // Simple nested field
116
+ order: ['metadata.priority DESC']
117
+ // SQL: ORDER BY "metadata" #> '{priority}' DESC
118
+
119
+ // Deeply nested field
120
+ order: ['settings.display.theme ASC']
121
+ // SQL: ORDER BY "settings" #> '{display,theme}' ASC
122
+
123
+ // Array element
124
+ order: ['tags[0] ASC']
125
+ // SQL: ORDER BY "tags" #> '{0}' ASC
126
+
127
+ // Complex path with array
128
+ order: ['data.items[2].name DESC']
129
+ // SQL: ORDER BY "data" #> '{items,2,name}' DESC
130
+ ```
131
+
132
+ **JSONB Sort Order:**
133
+
134
+ | Type | Sort Order |
135
+ |------|------------|
136
+ | `null` | First (lowest) |
137
+ | `boolean` | `false` < `true` |
138
+ | `number` | Numeric order |
139
+ | `string` | Lexicographic |
140
+ | `array` | Element-wise |
141
+ | `object` | Key-value |
142
+
143
+ **Security:** Built-in SQL injection prevention via regex validation for path components.
144
+
145
+
146
+ ## Code Quality Improvements
147
+
148
+ ### 1. Consistent Resolver Pattern
149
+
150
+ Added `THiddenPropertiesResolver` type and renamed method to match `getRelationResolver()` pattern:
151
+
152
+ ```typescript
153
+ // Consistent pattern
154
+ relationResolver: this.getRelationResolver(),
155
+ hiddenPropertiesResolver: this.getHiddenPropertiesResolver(),
156
+ ```
157
+
158
+ ### 2. Early Return Pattern
159
+
160
+ Refactored `resolveConnector()` from nested conditionals to early return:
161
+
162
+ ```typescript
163
+ // Before
164
+ if (transaction) {
165
+ if (!transaction.isActive) { throw... }
166
+ return transaction.connector;
167
+ }
168
+ return this.dataSource.connector;
169
+
170
+ // After
171
+ if (!transaction) {
172
+ return this.dataSource.connector;
173
+ }
174
+ if (!transaction.isActive) { throw... }
175
+ return transaction.connector;
176
+ ```
177
+
178
+ ### 3. Extracted Validation Helper
179
+
180
+ Created shared `validateWhereCondition()` method to eliminate duplicate validation logic.
181
+
182
+ ### 4. Improved Caching Pattern
183
+
184
+ Fixed caching by initializing `_visibleColumns` to `null` as sentinel for "not computed yet".
185
+
186
+ ### 5. Added Null Check
187
+
188
+ Added defensive check for `connector.query` in `getQueryInterface()`.
189
+
190
+
191
+ ## Files Changed
192
+
193
+ ### Core Package (`packages/core`)
194
+
195
+ | File | Changes |
196
+ |------|---------|
197
+ | `src/helpers/inversion/common/types.ts` | Added `IModelSettings` with `hiddenProperties` |
198
+ | `src/base/repositories/common/types.ts` | Updated `TFields` to support array format |
199
+ | `src/base/repositories/core/base.ts` | Added `getHiddenPropertiesResolver()`, caching, refactored `resolveConnector` |
200
+ | `src/base/repositories/core/readable.ts` | Added null check in `getQueryInterface` |
201
+ | `src/base/repositories/core/persistable.ts` | Added `validateWhereCondition`, updated CRUD methods |
202
+ | `src/base/repositories/operators/filter.ts` | Added `THiddenPropertiesResolver`, JSON ordering, array fields |
203
+
204
+ ### Examples (`examples/vert`)
205
+
206
+ | File | Changes |
207
+ |------|---------|
208
+ | `src/models/entities/user.model.ts` | Added hiddenProperties config |
209
+ | `src/services/repository-test.service.ts` | Added 21 hidden properties test cases |
210
+
211
+
212
+ ## No Breaking Changes
213
+
214
+ All changes are additive. Existing code continues to work without modification.
@@ -0,0 +1,214 @@
1
+ ---
2
+ title: JSON Path Filtering & Array Operators
3
+ description: Added JSON/JSONB path filtering support and PostgreSQL array column operators
4
+ ---
5
+
6
+ # Changelog - 2025-12-31
7
+
8
+ ## JSON Path Filtering & PostgreSQL Array Operators
9
+
10
+ This release adds powerful query capabilities for JSON/JSONB columns and PostgreSQL array columns, enabling complex filtering patterns without raw SQL.
11
+
12
+ ## Overview
13
+
14
+ - **JSON Path Filtering**: Filter by nested JSON fields using dot notation (e.g., `'jValue.metadata.score': { gt: 80 }`)
15
+ - **Array Column Operators**: PostgreSQL-specific operators `contains`, `containedBy`, `overlaps` for array columns
16
+ - **Safe Numeric Casting**: Automatic type-safe numeric comparison for mixed-type JSON fields
17
+ - **NOT BETWEEN Operator**: Added `notBetween` operator for range exclusion queries
18
+ - **Code Refactoring**: Unified JSON path validation and extraction logic
19
+
20
+ ## New Features
21
+
22
+ ### JSON Path Filtering
23
+
24
+ **Files:**
25
+ - `packages/core/src/base/repositories/operators/filter.ts`
26
+ - `packages/core/src/base/repositories/operators/query.ts`
27
+
28
+ **Problem:** Filtering by nested JSON/JSONB fields required raw SQL or manual extraction, making queries complex and error-prone.
29
+
30
+ **Solution:** Detect JSON paths in filter keys (containing `.` or `[`) and automatically generate PostgreSQL `#>>` extraction expressions with proper type handling.
31
+
32
+ ```typescript
33
+ // Before: Not possible with standard filters
34
+
35
+ // After: Native JSON path support
36
+ await repo.find({
37
+ filter: {
38
+ where: {
39
+ // Simple nested field
40
+ 'jValue.priority': 3,
41
+
42
+ // Deep nesting
43
+ 'jValue.metadata.level': 'high',
44
+
45
+ // Array index access
46
+ 'jValue.tags[0]': 'important',
47
+
48
+ // With operators
49
+ 'jValue.metadata.score': { gte: 70, lte: 90 },
50
+
51
+ // Pattern matching
52
+ 'jValue.metadata.level': { ilike: '%igh%' }
53
+ }
54
+ }
55
+ });
56
+ ```
57
+
58
+ **Generated SQL:**
59
+ ```sql
60
+ WHERE "jValue" #>> '{priority}' = '3'
61
+ AND "jValue" #>> '{metadata,level}' = 'high'
62
+ AND "jValue" #>> '{tags,0}' = 'important'
63
+ AND CASE WHEN ("jValue" #>> '{metadata,score}') ~ '^-?[0-9]+'
64
+ THEN ("jValue" #>> '{metadata,score}')::numeric ELSE NULL END >= 70
65
+ AND CASE WHEN ("jValue" #>> '{metadata,score}') ~ '^-?[0-9]+'
66
+ THEN ("jValue" #>> '{metadata,score}')::numeric ELSE NULL END <= 90
67
+ AND "jValue" #>> '{metadata,level}' ILIKE '%igh%'
68
+ ```
69
+
70
+ **Benefits:**
71
+ - Intuitive dot notation for nested fields
72
+ - Automatic numeric casting for comparison operators
73
+ - Safe handling of mixed-type JSON (non-numeric values become NULL, not errors)
74
+ - SQL injection prevention via path component validation
75
+
76
+ ### Safe Numeric Casting for JSON
77
+
78
+ **Problem:** JSON fields can contain mixed types (`{ score: 85 }` vs `{ score: "high" }`). Casting to numeric would crash on non-numeric values.
79
+
80
+ **Solution:** Safe casting pattern that validates numeric format before casting:
81
+
82
+ ```sql
83
+ CASE WHEN ("jValue" #>> '{score}') ~ '^-?[0-9]+(\.[0-9]+)?$'
84
+ THEN ("jValue" #>> '{score}')::numeric
85
+ ELSE NULL
86
+ END
87
+ ```
88
+
89
+ This ensures:
90
+ - Numeric values: Compared correctly as numbers
91
+ - String values: Treated as NULL (excluded from numeric comparisons)
92
+ - No database errors on mixed-type JSON data
93
+
94
+ ### PostgreSQL Array Column Operators
95
+
96
+ **File:** `packages/core/src/base/repositories/operators/query.ts`
97
+
98
+ **Problem:** PostgreSQL array columns (`varchar[]`, `integer[]`, etc.) require special operators (`@>`, `<@`, `&&`) that weren't available in the filter builder.
99
+
100
+ **Solution:** Added three new operators with automatic type handling:
101
+
102
+ | Operator | PostgreSQL | Description |
103
+ |----------|------------|-------------|
104
+ | `contains` | `@>` | Array contains ALL specified elements |
105
+ | `containedBy` | `<@` | Array is subset of specified elements |
106
+ | `overlaps` | `&&` | Arrays share ANY common element |
107
+
108
+ ```typescript
109
+ // Schema: tags varchar(100)[]
110
+
111
+ // Find products with BOTH 'electronics' AND 'featured'
112
+ await repo.find({
113
+ filter: {
114
+ where: { tags: { contains: ['electronics', 'featured'] } }
115
+ }
116
+ });
117
+ // SQL: "tags"::text[] @> ARRAY['electronics', 'featured']::text[]
118
+
119
+ // Find products where ALL tags are in allowed list
120
+ await repo.find({
121
+ filter: {
122
+ where: { tags: { containedBy: ['sale', 'featured', 'new'] } }
123
+ }
124
+ });
125
+ // SQL: "tags"::text[] <@ ARRAY['sale', 'featured', 'new']::text[]
126
+
127
+ // Find products with 'sale' OR 'premium' tag
128
+ await repo.find({
129
+ filter: {
130
+ where: { tags: { overlaps: ['sale', 'premium'] } }
131
+ }
132
+ });
133
+ // SQL: "tags"::text[] && ARRAY['sale', 'premium']::text[]
134
+ ```
135
+
136
+ **Type Compatibility:**
137
+ - String arrays (`varchar[]`, `text[]`, `char[]`): Both column and value cast to `text[]`
138
+ - Numeric arrays (`integer[]`, `numeric[]`): No casting needed
139
+ - Boolean arrays (`boolean[]`): No casting needed
140
+
141
+ **Empty Array Behavior:**
142
+
143
+ | Operator | Empty `[]` | Result |
144
+ |----------|------------|--------|
145
+ | `contains` | `{ contains: [] }` | `true` - everything contains empty set |
146
+ | `containedBy` | `{ containedBy: [] }` | Only rows with empty arrays |
147
+ | `overlaps` | `{ overlaps: [] }` | `false` - nothing overlaps with empty |
148
+
149
+ ### NOT BETWEEN Operator
150
+
151
+ **File:** `packages/core/src/base/repositories/operators/query.ts`
152
+
153
+ **Problem:** No way to filter for values outside a range.
154
+
155
+ **Solution:** Added `notBetween` operator:
156
+
157
+ ```typescript
158
+ await repo.find({
159
+ filter: {
160
+ where: {
161
+ score: { notBetween: [40, 60] } // Scores < 40 OR > 60
162
+ }
163
+ }
164
+ });
165
+ // SQL: WHERE NOT (score BETWEEN 40 AND 60)
166
+ ```
167
+
168
+ ## Security Enhancements
169
+
170
+ ### JSON Path Validation
171
+
172
+ JSON path components are validated against a strict pattern to prevent SQL injection:
173
+
174
+ ```typescript
175
+ // Valid patterns (allowed)
176
+ /^[a-zA-Z_][a-zA-Z0-9_-]*$|^\d+$/
177
+
178
+ // Examples:
179
+ 'jValue.user_id' // ✅ Valid identifier
180
+ 'jValue.meta-data' // ✅ Kebab-case allowed
181
+ 'jValue.items[0]' // ✅ Array index
182
+ 'jValue.nested[2].field' // ✅ Mixed access
183
+
184
+ // Invalid (throws error)
185
+ 'jValue.field;DROP TABLE' // ❌ SQL injection attempt
186
+ 'jValue.123invalid' // ❌ Starts with number
187
+ ```
188
+
189
+ ## Files Changed
190
+
191
+ ### Core Package (`packages/core`)
192
+
193
+ | File | Changes |
194
+ |------|---------|
195
+ | `src/base/repositories/operators/filter.ts` | Added JSON path filtering (`buildJsonWhereCondition`, `validateJsonColumn`, `isJsonPath`, `isOperatorObject`), refactored `buildJsonOrderBy` to reuse validation |
196
+ | `src/base/repositories/operators/query.ts` | Added array operators (`contains`, `containedBy`, `overlaps`), `notBetween`, `hasNumericComparison` helper, `buildPgArrayComparison` helper |
197
+
198
+ ### Examples (`examples/vert`)
199
+
200
+ | File | Changes |
201
+ |------|---------|
202
+ | `src/models/entities/product.model.ts` | Added array column (`tags varchar(100)[]`) for testing |
203
+ | `src/services/tests/` | New test services for JSON and array operators |
204
+
205
+ ## No Breaking Changes
206
+
207
+ All changes are additive enhancements. Existing filter queries work unchanged.
208
+
209
+ ## Documentation
210
+
211
+ Full documentation with examples available at:
212
+ - [Repositories - JSON Path Filtering](/references/base/repositories#json-path-filtering)
213
+ - [Repositories - Array Column Operators](/references/base/repositories#array-column-operators-postgresql)
214
+ - [Repositories - Query Operators](/references/base/repositories#query-operators)