@venizia/ignis-docs 0.0.3 → 0.0.4-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 +1 -1
- package/package.json +4 -2
- package/wiki/best-practices/api-usage-examples.md +591 -0
- package/wiki/best-practices/architectural-patterns.md +415 -0
- package/wiki/best-practices/architecture-decisions.md +488 -0
- package/wiki/{get-started/best-practices → best-practices}/code-style-standards.md +406 -17
- package/wiki/{get-started/best-practices → best-practices}/common-pitfalls.md +109 -4
- package/wiki/{get-started/best-practices → best-practices}/contribution-workflow.md +34 -7
- package/wiki/best-practices/data-modeling.md +376 -0
- package/wiki/best-practices/deployment-strategies.md +698 -0
- package/wiki/best-practices/index.md +27 -0
- package/wiki/best-practices/performance-optimization.md +196 -0
- package/wiki/best-practices/security-guidelines.md +218 -0
- package/wiki/{get-started/best-practices → best-practices}/troubleshooting-tips.md +97 -1
- package/wiki/changelogs/2025-12-16-initial-architecture.md +1 -1
- package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +1 -1
- package/wiki/changelogs/2025-12-17-refactor.md +1 -1
- package/wiki/changelogs/2025-12-18-performance-optimizations.md +5 -5
- package/wiki/changelogs/2025-12-18-repository-validation-security.md +13 -7
- package/wiki/changelogs/2025-12-26-nested-relations-and-generics.md +2 -2
- package/wiki/changelogs/2025-12-29-dynamic-binding-registration.md +104 -0
- package/wiki/changelogs/2025-12-29-snowflake-uid-helper.md +100 -0
- package/wiki/changelogs/2025-12-30-repository-enhancements.md +214 -0
- package/wiki/changelogs/2025-12-31-json-path-filtering-array-operators.md +214 -0
- package/wiki/changelogs/2025-12-31-string-id-custom-generator.md +137 -0
- package/wiki/changelogs/2026-01-02-default-filter-and-repository-mixins.md +418 -0
- package/wiki/changelogs/index.md +6 -0
- package/wiki/changelogs/planned-schema-migrator.md +0 -8
- package/wiki/{get-started/core-concepts → guides/core-concepts/application}/bootstrapping.md +18 -5
- package/wiki/{get-started/core-concepts/application.md → guides/core-concepts/application/index.md} +47 -104
- package/wiki/guides/core-concepts/components-guide.md +509 -0
- package/wiki/{get-started → guides}/core-concepts/components.md +24 -17
- package/wiki/{get-started → guides}/core-concepts/controllers.md +30 -13
- package/wiki/{get-started → guides}/core-concepts/dependency-injection.md +97 -0
- package/wiki/guides/core-concepts/persistent/datasources.md +179 -0
- package/wiki/guides/core-concepts/persistent/index.md +119 -0
- package/wiki/guides/core-concepts/persistent/models.md +241 -0
- package/wiki/guides/core-concepts/persistent/repositories.md +219 -0
- package/wiki/guides/core-concepts/persistent/transactions.md +170 -0
- package/wiki/{get-started → guides}/core-concepts/services.md +26 -3
- package/wiki/{get-started → guides/get-started}/5-minute-quickstart.md +59 -14
- package/wiki/guides/get-started/philosophy.md +682 -0
- package/wiki/guides/get-started/setup.md +157 -0
- package/wiki/guides/index.md +89 -0
- package/wiki/guides/reference/glossary.md +243 -0
- package/wiki/{get-started → guides/reference}/mcp-docs-server.md +0 -10
- package/wiki/{get-started → guides/tutorials}/building-a-crud-api.md +134 -132
- package/wiki/{get-started/quickstart.md → guides/tutorials/complete-installation.md} +107 -71
- package/wiki/guides/tutorials/ecommerce-api.md +1399 -0
- package/wiki/guides/tutorials/realtime-chat.md +1261 -0
- package/wiki/guides/tutorials/testing.md +723 -0
- package/wiki/index.md +176 -37
- package/wiki/references/base/application.md +27 -0
- package/wiki/references/base/bootstrapping.md +30 -26
- package/wiki/references/base/components.md +24 -7
- package/wiki/references/base/controllers.md +51 -20
- package/wiki/references/base/datasources.md +30 -0
- package/wiki/references/base/dependency-injection.md +39 -3
- package/wiki/references/base/filter-system/application-usage.md +224 -0
- package/wiki/references/base/filter-system/array-operators.md +132 -0
- package/wiki/references/base/filter-system/comparison-operators.md +109 -0
- package/wiki/references/base/filter-system/default-filter.md +428 -0
- package/wiki/references/base/filter-system/fields-order-pagination.md +155 -0
- package/wiki/references/base/filter-system/index.md +127 -0
- package/wiki/references/base/filter-system/json-filtering.md +197 -0
- package/wiki/references/base/filter-system/list-operators.md +71 -0
- package/wiki/references/base/filter-system/logical-operators.md +156 -0
- package/wiki/references/base/filter-system/null-operators.md +58 -0
- package/wiki/references/base/filter-system/pattern-matching.md +108 -0
- package/wiki/references/base/filter-system/quick-reference.md +431 -0
- package/wiki/references/base/filter-system/range-operators.md +63 -0
- package/wiki/references/base/filter-system/tips.md +190 -0
- package/wiki/references/base/filter-system/use-cases.md +452 -0
- package/wiki/references/base/index.md +90 -0
- package/wiki/references/base/middlewares.md +602 -0
- package/wiki/references/base/models.md +215 -23
- package/wiki/references/base/providers.md +732 -0
- package/wiki/references/base/repositories/advanced.md +555 -0
- package/wiki/references/base/repositories/index.md +228 -0
- package/wiki/references/base/repositories/mixins.md +331 -0
- package/wiki/references/base/repositories/relations.md +486 -0
- package/wiki/references/base/repositories.md +40 -635
- package/wiki/references/base/services.md +28 -4
- package/wiki/references/components/authentication.md +22 -2
- package/wiki/references/components/health-check.md +12 -0
- package/wiki/references/components/index.md +23 -0
- package/wiki/references/components/mail.md +687 -0
- package/wiki/references/components/request-tracker.md +16 -0
- package/wiki/references/components/socket-io.md +18 -0
- package/wiki/references/components/static-asset.md +14 -26
- package/wiki/references/components/swagger.md +17 -0
- package/wiki/references/configuration/environment-variables.md +427 -0
- package/wiki/references/configuration/index.md +73 -0
- package/wiki/references/helpers/cron.md +14 -0
- package/wiki/references/helpers/crypto.md +15 -0
- package/wiki/references/helpers/env.md +16 -0
- package/wiki/references/helpers/error.md +17 -0
- package/wiki/references/helpers/index.md +14 -0
- package/wiki/references/helpers/inversion.md +24 -4
- package/wiki/references/helpers/logger.md +19 -0
- package/wiki/references/helpers/network.md +11 -0
- package/wiki/references/helpers/queue.md +19 -0
- package/wiki/references/helpers/redis.md +21 -0
- package/wiki/references/helpers/socket-io.md +24 -5
- package/wiki/references/helpers/storage.md +18 -10
- package/wiki/references/helpers/testing.md +18 -0
- package/wiki/references/helpers/types.md +16 -0
- package/wiki/references/helpers/uid.md +167 -0
- package/wiki/references/helpers/worker-thread.md +16 -0
- package/wiki/references/index.md +177 -0
- package/wiki/references/quick-reference.md +634 -0
- package/wiki/references/src-details/boot.md +3 -3
- package/wiki/references/src-details/dev-configs.md +0 -4
- package/wiki/references/src-details/docs.md +2 -2
- package/wiki/references/src-details/index.md +86 -0
- package/wiki/references/src-details/inversion.md +1 -6
- package/wiki/references/src-details/mcp-server.md +3 -15
- package/wiki/references/utilities/index.md +86 -10
- package/wiki/references/utilities/jsx.md +577 -0
- package/wiki/references/utilities/request.md +0 -2
- package/wiki/references/utilities/statuses.md +740 -0
- package/wiki/get-started/best-practices/api-usage-examples.md +0 -266
- package/wiki/get-started/best-practices/architectural-patterns.md +0 -170
- package/wiki/get-started/best-practices/data-modeling.md +0 -177
- package/wiki/get-started/best-practices/deployment-strategies.md +0 -121
- package/wiki/get-started/best-practices/performance-optimization.md +0 -97
- package/wiki/get-started/best-practices/security-guidelines.md +0 -99
- package/wiki/get-started/core-concepts/persistent.md +0 -539
- package/wiki/get-started/index.md +0 -65
- package/wiki/get-started/philosophy.md +0 -296
- 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)
|