Skip to content

Commit 67995d1

Browse files
Copilothotlong
andcommitted
Add runtime core documentation and demo example
- Add ARCHITECTURE.md explaining design decisions and patterns - Add demo.ts showing real-world usage - Verify all features work correctly (plugin ordering, waterfall, lifecycle) Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 965cf39 commit 67995d1

2 files changed

Lines changed: 303 additions & 0 deletions

File tree

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# Runtime Core Architecture
2+
3+
## Overview
4+
5+
The `@objectql/runtime-core` package implements the core plugin system and query pipeline for ObjectQL, following the principle of **Protocol/Spec vs Runtime/Implementation** separation.
6+
7+
## Architecture Principles
8+
9+
### 1. Protocol Layer (from `@objectql/types`)
10+
- **BasePlugin**: Interface defining plugin structure with metadata and lifecycle
11+
- **QueryProcessorPlugin**: Interface for plugins that process queries
12+
- **PluginMetadata**: Standardized plugin information including dependencies
13+
14+
### 2. Runtime Layer (this package)
15+
- **PluginManager**: Implements dependency resolution and lifecycle management
16+
- **QueryPipeline**: Implements async series waterfall query processing
17+
- **Runtime**: Orchestrates plugins and provides query execution
18+
19+
## Key Components
20+
21+
### PluginManager
22+
23+
**Responsibilities:**
24+
- Register plugins
25+
- Resolve dependencies using topological sort
26+
- Boot plugins in dependency order
27+
- Manage plugin lifecycle (setup/teardown)
28+
29+
**Algorithm: Topological Sort**
30+
```typescript
31+
// Ensures dependencies are initialized before dependents
32+
// Detects circular dependencies
33+
// Throws errors for missing dependencies
34+
```
35+
36+
**Example:**
37+
```typescript
38+
const manager = new PluginManager();
39+
manager.register(pluginA); // No dependencies
40+
manager.register(pluginB); // Depends on A
41+
manager.register(pluginC); // Depends on B
42+
43+
await manager.boot(runtime);
44+
// Execution order: A → B → C
45+
```
46+
47+
### QueryPipeline
48+
49+
**Responsibilities:**
50+
- Execute queries through registered processors
51+
- Implement async series waterfall pattern
52+
- Validate queries before execution
53+
- Transform queries and results through plugin chain
54+
55+
**Execution Flow:**
56+
```
57+
1. validateQuery (all plugins)
58+
59+
2. beforeQuery (waterfall: plugin1 → plugin2 → ...)
60+
61+
3. execute (driver)
62+
63+
4. afterQuery (waterfall: plugin1 → plugin2 → ...)
64+
65+
5. return results
66+
```
67+
68+
**Waterfall Pattern:**
69+
- Each plugin receives output from previous plugin
70+
- Plugins can transform queries/results
71+
- Final output is returned to caller
72+
73+
**Example:**
74+
```typescript
75+
// Plugin 1 adds field
76+
beforeQuery(query) {
77+
return { ...query, fields: ['id', 'name'] };
78+
}
79+
80+
// Plugin 2 adds filter (receives plugin 1's output)
81+
beforeQuery(query) {
82+
return { ...query, filters: [['active', '=', true]] };
83+
}
84+
85+
// Final query: { fields: ['id', 'name'], filters: [['active', '=', true]] }
86+
```
87+
88+
### Runtime
89+
90+
**Responsibilities:**
91+
- Provide factory function `createRuntime()`
92+
- Manage plugin manager and query pipeline
93+
- Expose simple API for query execution
94+
- Handle initialization and shutdown
95+
96+
**API:**
97+
```typescript
98+
interface Runtime {
99+
pluginManager: PluginManager;
100+
init(): Promise<void>;
101+
query(object, query, context): Promise<any[]>;
102+
shutdown(): Promise<void>;
103+
setQueryExecutor(executor): void;
104+
}
105+
```
106+
107+
## Usage Pattern
108+
109+
```typescript
110+
// 1. Define plugins
111+
const myPlugin: BasePlugin = {
112+
metadata: {
113+
name: 'my-plugin',
114+
dependencies: ['base-plugin']
115+
},
116+
async setup(runtime) {
117+
// Initialize plugin
118+
}
119+
};
120+
121+
// 2. Create runtime
122+
const runtime = createRuntime({
123+
plugins: [myPlugin]
124+
});
125+
126+
// 3. Set executor
127+
runtime.setQueryExecutor(async (object, query) => {
128+
// Execute query against database
129+
});
130+
131+
// 4. Initialize
132+
await runtime.init();
133+
134+
// 5. Execute queries
135+
const results = await runtime.query('project', {
136+
filters: [['status', '=', 'active']]
137+
});
138+
139+
// 6. Shutdown
140+
await runtime.shutdown();
141+
```
142+
143+
## Design Decisions
144+
145+
### 1. Separation of Concerns
146+
- **Types** define interfaces (what)
147+
- **Runtime** implements logic (how)
148+
- No circular dependencies between packages
149+
150+
### 2. Topological Sort for Dependencies
151+
- Ensures correct initialization order
152+
- Detects circular dependencies early
153+
- Provides clear error messages
154+
155+
### 3. Async Series Waterfall
156+
- Allows plugins to transform data sequentially
157+
- Each plugin sees previous plugin's changes
158+
- Enables powerful composition patterns
159+
160+
### 4. Error Handling
161+
- Custom error types (PluginError, PipelineError)
162+
- Include plugin name in errors for debugging
163+
- Graceful shutdown even if teardown fails
164+
165+
## Testing
166+
167+
The package includes 39 tests covering:
168+
- Plugin registration and lifecycle
169+
- Dependency resolution (simple, complex, diamond, circular)
170+
- Query pipeline execution (validation, waterfall, errors)
171+
- Integration scenarios
172+
173+
Run tests:
174+
```bash
175+
pnpm test
176+
```
177+
178+
## Future Enhancements
179+
180+
Potential improvements:
181+
1. Plugin versioning and compatibility checking
182+
2. Hot plugin reload
183+
3. Plugin communication via events
184+
4. Performance monitoring hooks
185+
5. Plugin sandboxing for security

packages/runtime/core/test/demo.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/**
2+
* ObjectQL Runtime Core - Example Usage
3+
*
4+
* This example demonstrates the key features:
5+
* 1. Plugin dependency resolution
6+
* 2. Query pipeline with waterfall processing
7+
* 3. Runtime lifecycle management
8+
*/
9+
10+
import { createRuntime } from '../src/index';
11+
import type { BasePlugin, QueryProcessorPlugin } from '@objectql/types';
12+
13+
// Example 1: Basic plugin with dependencies
14+
const loggerPlugin: BasePlugin = {
15+
metadata: {
16+
name: 'logger',
17+
version: '1.0.0',
18+
type: 'extension'
19+
},
20+
async setup(runtime) {
21+
console.log('[Logger] Plugin initialized');
22+
}
23+
};
24+
25+
const cachePlugin: BasePlugin = {
26+
metadata: {
27+
name: 'cache',
28+
version: '1.0.0',
29+
type: 'extension',
30+
dependencies: ['logger'] // Depends on logger
31+
},
32+
async setup(runtime) {
33+
console.log('[Cache] Plugin initialized (after logger)');
34+
}
35+
};
36+
37+
// Example 2: Query processor plugin
38+
const securityPlugin: QueryProcessorPlugin = {
39+
metadata: {
40+
name: 'security',
41+
version: '1.0.0',
42+
type: 'query_processor',
43+
dependencies: ['logger']
44+
},
45+
async setup(runtime) {
46+
console.log('[Security] Plugin initialized');
47+
},
48+
async validateQuery(query, context) {
49+
// Validate user has permission
50+
if (!context.user) {
51+
throw new Error('Authentication required');
52+
}
53+
},
54+
async beforeQuery(query, context) {
55+
console.log(`[Security] User ${context.user?.id} executing query`);
56+
// Add tenant filter automatically
57+
return {
58+
...query,
59+
filters: [
60+
...(query.filters || []),
61+
['tenant_id', '=', context.user?.tenant_id]
62+
]
63+
};
64+
}
65+
};
66+
67+
// Example 3: Demo runtime usage
68+
async function demo() {
69+
console.log('=== ObjectQL Runtime Core Demo ===\n');
70+
71+
// Create runtime with plugins
72+
const runtime = createRuntime({
73+
plugins: [
74+
securityPlugin, // Registered in any order
75+
cachePlugin,
76+
loggerPlugin
77+
]
78+
});
79+
80+
// Set query executor (mock)
81+
runtime.setQueryExecutor(async (objectName, query) => {
82+
console.log(`[Driver] Executing query on ${objectName}:`, query);
83+
return [
84+
{ id: 1, name: 'Project 1', tenant_id: 'tenant-1' }
85+
];
86+
});
87+
88+
// Initialize (plugins will be setup in dependency order)
89+
console.log('\n1. Initializing runtime...');
90+
await runtime.init();
91+
92+
// Execute query through pipeline
93+
console.log('\n2. Executing query through pipeline...');
94+
const results = await runtime.query('project', {
95+
fields: ['id', 'name'],
96+
filters: [['status', '=', 'active']]
97+
}, {
98+
user: {
99+
id: 'user-123',
100+
tenant_id: 'tenant-1'
101+
}
102+
});
103+
104+
console.log('\n3. Results:', results);
105+
106+
// Shutdown
107+
console.log('\n4. Shutting down runtime...');
108+
await runtime.shutdown();
109+
110+
console.log('\n=== Demo Complete ===');
111+
}
112+
113+
// Run demo if this file is executed directly
114+
if (require.main === module) {
115+
demo().catch(console.error);
116+
}
117+
118+
export { demo };

0 commit comments

Comments
 (0)