Skip to content

Commit fecea08

Browse files
authored
Merge pull request #1143 from objectstack-ai/claude/use-different-data-sources
feat: add centralized datasource mapping for package/namespace-level routing
2 parents ee17e79 + f85f957 commit fecea08

File tree

7 files changed

+863
-21
lines changed

7 files changed

+863
-21
lines changed

DATASOURCE_MAPPING.md

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
# Datasource Mapping Feature
2+
3+
## Overview
4+
5+
The `datasourceMapping` feature provides a centralized mechanism to configure which datasources (drivers) are used by different parts of your application. Instead of configuring `datasource` on every individual object, you can define routing rules based on:
6+
7+
- **Namespace**: Route all objects from a package namespace to a specific datasource
8+
- **Package ID**: Route all objects from a specific package to a datasource
9+
- **Object Pattern**: Route objects matching a name pattern (glob-style) to a datasource
10+
- **Default**: Fallback rule for objects that don't match any other rules
11+
12+
This feature is inspired by industry-proven patterns from Django's Database Router and Kubernetes' StorageClass.
13+
14+
## Priority Resolution
15+
16+
The system resolves datasources in the following priority order (first match wins):
17+
18+
1. **Object's explicit `datasource` field** (if set and not 'default')
19+
2. **DatasourceMapping rules** (evaluated in order or by priority)
20+
3. **Package's `defaultDatasource`** (from manifest)
21+
4. **Global default driver**
22+
23+
## Configuration
24+
25+
### Basic Example
26+
27+
```typescript
28+
// apps/server/objectstack.config.ts
29+
import { defineStack } from '@objectstack/spec';
30+
import { DriverPlugin } from '@objectstack/runtime';
31+
import { TursoDriver } from '@objectstack/driver-turso';
32+
import { InMemoryDriver } from '@objectstack/driver-memory';
33+
import CrmApp from '../../examples/app-crm/objectstack.config';
34+
import TodoApp from '../../examples/app-todo/objectstack.config';
35+
36+
export default defineStack({
37+
manifest: {
38+
id: 'com.objectstack.server',
39+
name: 'ObjectStack Server',
40+
version: '1.0.0',
41+
},
42+
43+
plugins: [
44+
new ObjectQLPlugin(),
45+
new DriverPlugin(new TursoDriver({ url: 'file:./data/system.db' }), 'turso'),
46+
new DriverPlugin(new InMemoryDriver(), 'memory'),
47+
new AppPlugin(CrmApp), // namespace: 'crm'
48+
new AppPlugin(TodoApp), // namespace: 'todo'
49+
],
50+
51+
// 🎯 Centralized datasource routing configuration
52+
datasourceMapping: [
53+
// System core objects → Turso (persistent storage)
54+
{ objectPattern: 'sys_*', datasource: 'turso' },
55+
{ namespace: 'auth', datasource: 'turso' },
56+
57+
// CRM application → Memory (dev/test environment)
58+
{ namespace: 'crm', datasource: 'memory' },
59+
60+
// Todo application → Turso (production storage)
61+
{ namespace: 'todo', datasource: 'turso' },
62+
63+
// Temporary/cache objects → Memory
64+
{ objectPattern: 'temp_*', datasource: 'memory' },
65+
{ objectPattern: 'cache_*', datasource: 'memory' },
66+
67+
// Default fallback → Turso
68+
{ default: true, datasource: 'turso' },
69+
],
70+
});
71+
```
72+
73+
### Advanced: Priority-Based Rules
74+
75+
```typescript
76+
datasourceMapping: [
77+
// High priority rules (lower number = higher priority)
78+
{ objectPattern: 'sys_*', datasource: 'turso', priority: 10 },
79+
{ namespace: 'auth', datasource: 'turso', priority: 10 },
80+
81+
// Medium priority rules
82+
{ package: 'com.example.crm', datasource: 'memory', priority: 50 },
83+
{ namespace: 'crm', datasource: 'memory', priority: 50 },
84+
85+
// Low priority rules
86+
{ objectPattern: 'temp_*', datasource: 'memory', priority: 100 },
87+
88+
// Default fallback (lowest priority)
89+
{ default: true, datasource: 'turso', priority: 1000 },
90+
]
91+
```
92+
93+
### Package-Level Configuration
94+
95+
You can also set a default datasource at the package level:
96+
97+
```typescript
98+
// examples/app-crm/objectstack.config.ts
99+
export default defineStack({
100+
manifest: {
101+
id: 'com.example.crm',
102+
namespace: 'crm',
103+
version: '3.0.0',
104+
defaultDatasource: 'memory', // All CRM objects use memory by default
105+
},
106+
107+
objects: Object.values(objects), // All objects inherit 'memory'
108+
// ...
109+
});
110+
```
111+
112+
## Rule Types
113+
114+
### 1. Namespace Matching
115+
116+
Routes all objects from a specific namespace to a datasource:
117+
118+
```typescript
119+
{ namespace: 'crm', datasource: 'memory' }
120+
```
121+
122+
All objects in the `crm` namespace (e.g., `crm__account`, `crm__contact`) will use the `memory` datasource.
123+
124+
### 2. Package Matching
125+
126+
Routes all objects from a specific package to a datasource:
127+
128+
```typescript
129+
{ package: 'com.example.analytics', datasource: 'clickhouse' }
130+
```
131+
132+
All objects defined in the `com.example.analytics` package will use the `clickhouse` datasource.
133+
134+
### 3. Pattern Matching (Glob-Style)
135+
136+
Routes objects matching a name pattern to a datasource:
137+
138+
```typescript
139+
{ objectPattern: 'sys_*', datasource: 'turso' }
140+
{ objectPattern: 'temp_*', datasource: 'memory' }
141+
{ objectPattern: 'cache_*', datasource: 'redis' }
142+
```
143+
144+
Supports wildcards:
145+
- `*` matches any characters
146+
- `?` matches a single character
147+
148+
### 4. Default Fallback
149+
150+
Catches all objects that don't match any other rules:
151+
152+
```typescript
153+
{ default: true, datasource: 'turso' }
154+
```
155+
156+
## Use Cases
157+
158+
### 1. System vs Application Data Separation
159+
160+
```typescript
161+
datasourceMapping: [
162+
// System/core data → PostgreSQL (ACID, durable)
163+
{ objectPattern: 'sys_*', datasource: 'postgres' },
164+
{ namespace: 'auth', datasource: 'postgres' },
165+
166+
// Application data → Memory (fast, ephemeral)
167+
{ default: true, datasource: 'memory' },
168+
]
169+
```
170+
171+
### 2. Multi-Environment Setup
172+
173+
```typescript
174+
datasourceMapping: [
175+
// Development: use memory for speed
176+
{ namespace: 'crm', datasource: process.env.NODE_ENV === 'production' ? 'turso' : 'memory' },
177+
178+
// Production: persistent storage
179+
{ default: true, datasource: 'turso' },
180+
]
181+
```
182+
183+
### 3. Performance Optimization
184+
185+
```typescript
186+
datasourceMapping: [
187+
// Hot data → Redis (cache)
188+
{ objectPattern: 'cache_*', datasource: 'redis' },
189+
{ objectPattern: 'session_*', datasource: 'redis' },
190+
191+
// Analytics → ClickHouse (OLAP)
192+
{ namespace: 'analytics', datasource: 'clickhouse' },
193+
194+
// Regular data → PostgreSQL (OLTP)
195+
{ default: true, datasource: 'postgres' },
196+
]
197+
```
198+
199+
### 4. Testing Isolation
200+
201+
```typescript
202+
datasourceMapping: [
203+
// Test objects → In-memory (no persistence)
204+
{ objectPattern: 'test_*', datasource: 'memory' },
205+
206+
// Production objects → Turso
207+
{ default: true, datasource: 'turso' },
208+
]
209+
```
210+
211+
## Benefits
212+
213+
1. **Centralized Configuration**: All datasource routing in one place
214+
2. **No Object Modification**: Change datasources without touching object definitions
215+
3. **Environment-Specific**: Different datasources per environment (dev/test/prod)
216+
4. **Pattern-Based**: Flexible glob patterns for batch configuration
217+
5. **Explicit Override**: Objects can still override with explicit `datasource` field
218+
219+
## Migration from Individual Configuration
220+
221+
### Before (Manual Configuration)
222+
223+
```typescript
224+
// Every object needs datasource field
225+
const Account = defineObject({
226+
name: 'account',
227+
datasource: 'memory', // Repeated everywhere
228+
fields: { /* ... */ },
229+
});
230+
231+
const Contact = defineObject({
232+
name: 'contact',
233+
datasource: 'memory', // Repeated everywhere
234+
fields: { /* ... */ },
235+
});
236+
```
237+
238+
### After (Centralized Configuration)
239+
240+
```typescript
241+
// Configure once at stack level
242+
datasourceMapping: [
243+
{ namespace: 'crm', datasource: 'memory' },
244+
]
245+
246+
// Objects are clean
247+
const Account = defineObject({
248+
name: 'account',
249+
// No datasource field needed
250+
fields: { /* ... */ },
251+
});
252+
```
253+
254+
## Debugging
255+
256+
Enable debug logging to see datasource resolution:
257+
258+
```typescript
259+
// ObjectQL will log:
260+
// "Resolved datasource from mapping: object=crm__account, datasource=memory"
261+
// "Resolved datasource from package manifest: object=task, package=com.example.todo, datasource=turso"
262+
```
263+
264+
## Best Practices
265+
266+
1. **Use Specific Rules First**: Place high-priority rules at the top
267+
2. **Always Have a Default**: Include a default fallback rule
268+
3. **Group by Purpose**: Organize rules by function (system, cache, analytics, etc.)
269+
4. **Document Decisions**: Add comments explaining why each rule exists
270+
5. **Test Thoroughly**: Verify that objects route to expected datasources

0 commit comments

Comments
 (0)