Skip to content

Commit 3e161a8

Browse files
authored
Merge pull request #339 from objectstack-ai/copilot/create-core-foundation
2 parents 90eb0c5 + 6569e11 commit 3e161a8

File tree

5 files changed

+532
-7
lines changed

5 files changed

+532
-7
lines changed

packages/runtime/README.md

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ ObjectStack Core Runtime & Query Engine
66

77
The runtime package provides the `ObjectKernel` (MiniKernel) - a highly modular, plugin-based microkernel that orchestrates ObjectStack applications. It manages the application lifecycle through a standardized plugin system with dependency injection and event hooks.
88

9+
The package also defines **capability contract interfaces** (`IHttpServer`, `IDataEngine`) that enable plugins to interact with common services without depending on concrete implementations, following the **Dependency Inversion Principle**.
10+
911
### Architecture Highlights
1012

1113
- **MiniKernel Design**: Business logic is completely separated into plugins
14+
- **Capability Contracts**: Abstract interfaces for HTTP server and data persistence
1215
- **Dependency Injection**: Service registry for inter-plugin communication
1316
- **Event/Hook System**: Publish-subscribe mechanism for loose coupling
1417
- **Lifecycle Management**: Standardized init/start/destroy phases
@@ -119,6 +122,91 @@ new AppManifestPlugin(appConfig)
119122

120123
## API Reference
121124

125+
### Capability Contract Interfaces
126+
127+
#### IHttpServer
128+
129+
Abstract interface for HTTP server capabilities. Allows plugins to work with any HTTP framework (Express, Fastify, Hono, etc.) without tight coupling.
130+
131+
```typescript
132+
import { IHttpServer, IHttpRequest, IHttpResponse } from '@objectstack/runtime';
133+
134+
// In your HTTP server plugin
135+
class MyHttpServerPlugin implements Plugin {
136+
name = 'http-server';
137+
138+
async init(ctx: PluginContext) {
139+
const server: IHttpServer = createMyServer(); // Express, Hono, etc.
140+
ctx.registerService('http-server', server);
141+
}
142+
}
143+
144+
// In your API plugin
145+
class MyApiPlugin implements Plugin {
146+
name = 'api';
147+
dependencies = ['http-server'];
148+
149+
async start(ctx: PluginContext) {
150+
const server = ctx.getService<IHttpServer>('http-server');
151+
152+
// Register routes - works with any HTTP framework
153+
server.get('/api/users', async (req, res) => {
154+
res.json({ users: [] });
155+
});
156+
}
157+
}
158+
```
159+
160+
**Interface Methods:**
161+
- `get(path, handler)` - Register GET route
162+
- `post(path, handler)` - Register POST route
163+
- `put(path, handler)` - Register PUT route
164+
- `delete(path, handler)` - Register DELETE route
165+
- `patch(path, handler)` - Register PATCH route
166+
- `use(path, handler?)` - Register middleware
167+
- `listen(port)` - Start server
168+
- `close()` - Stop server (optional)
169+
170+
#### IDataEngine
171+
172+
Abstract interface for data persistence. Allows plugins to work with any data layer (ObjectQL, Prisma, TypeORM, etc.) without tight coupling.
173+
174+
```typescript
175+
import { IDataEngine } from '@objectstack/runtime';
176+
177+
// In your data plugin
178+
class MyDataPlugin implements Plugin {
179+
name = 'data';
180+
181+
async init(ctx: PluginContext) {
182+
const engine: IDataEngine = createMyDataEngine(); // ObjectQL, Prisma, etc.
183+
ctx.registerService('data-engine', engine);
184+
}
185+
}
186+
187+
// In your business logic plugin
188+
class MyBusinessPlugin implements Plugin {
189+
name = 'business';
190+
dependencies = ['data'];
191+
192+
async start(ctx: PluginContext) {
193+
const engine = ctx.getService<IDataEngine>('data-engine');
194+
195+
// CRUD operations - works with any data layer
196+
const user = await engine.insert('user', { name: 'John' });
197+
const users = await engine.find('user', { filter: { active: true } });
198+
await engine.update('user', user.id, { name: 'Jane' });
199+
await engine.delete('user', user.id);
200+
}
201+
}
202+
```
203+
204+
**Interface Methods:**
205+
- `insert(objectName, data)` - Create a record
206+
- `find(objectName, query?)` - Query records
207+
- `update(objectName, id, data)` - Update a record
208+
- `delete(objectName, id)` - Delete a record
209+
122210
### ObjectKernel
123211

124212
#### Methods
@@ -161,17 +249,18 @@ interface PluginContext {
161249
See the `examples/` directory for complete examples:
162250
- `examples/host/` - Full server setup with Hono
163251
- `examples/msw-react-crud/` - Browser-based setup with MSW
164-
- `examples/custom-objectql-example.ts` - Custom ObjectQL instance
165-
- `test-mini-kernel.ts` - Comprehensive test suite
252+
- `test-mini-kernel.ts` - Comprehensive kernel test suite
253+
- `packages/runtime/src/test-interfaces.ts` - Capability contract interface examples
166254

167255
## Benefits of MiniKernel
168256

169257
1. **True Modularity**: Each plugin is independent and reusable
170-
2. **Testability**: Mock services easily in tests
171-
3. **Flexibility**: Load plugins conditionally
172-
4. **Extensibility**: Add new plugins without modifying kernel
173-
5. **Clear Dependencies**: Explicit dependency declarations
174-
6. **Better Architecture**: Separation of concerns
258+
2. **Capability Contracts**: Plugins depend on interfaces, not implementations
259+
3. **Testability**: Mock services easily in tests
260+
4. **Flexibility**: Load plugins conditionally, swap implementations
261+
5. **Extensibility**: Add new plugins without modifying kernel
262+
6. **Clear Dependencies**: Explicit dependency declarations
263+
7. **Better Architecture**: Separation of concerns with Dependency Inversion
175264

176265
## Best Practices
177266

packages/runtime/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ export { ObjectStackRuntimeProtocol } from './protocol.js';
1515
// Export Types
1616
export * from './types.js';
1717

18+
// Export Interfaces (Capability Contracts)
19+
export * from './interfaces/http-server.js';
20+
export * from './interfaces/data-engine.js';
21+
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/**
2+
* IDataEngine - Standard Data Engine Interface
3+
*
4+
* Abstract interface for data persistence capabilities.
5+
* This allows plugins to interact with data engines without knowing
6+
* the underlying implementation (SQL, MongoDB, Memory, etc.).
7+
*
8+
* Follows Dependency Inversion Principle - plugins depend on this interface,
9+
* not on concrete database implementations.
10+
*/
11+
12+
/**
13+
* Query filter conditions
14+
*/
15+
export interface QueryFilter {
16+
[field: string]: any;
17+
}
18+
19+
/**
20+
* Query options for find operations
21+
*/
22+
export interface QueryOptions {
23+
/** Filter conditions */
24+
filter?: QueryFilter;
25+
/** Fields to select */
26+
select?: string[];
27+
/** Sort order */
28+
sort?: Record<string, 1 | -1 | 'asc' | 'desc'>;
29+
/** Limit number of results (alternative name for top, used by some drivers) */
30+
limit?: number;
31+
/** Skip number of results (for pagination) */
32+
skip?: number;
33+
/** Maximum number of results (OData-style, takes precedence over limit if both specified) */
34+
top?: number;
35+
}
36+
37+
/**
38+
* IDataEngine - Data persistence capability interface
39+
*
40+
* Defines the contract for data engine implementations.
41+
* Concrete implementations (ObjectQL, Prisma, TypeORM) should implement this interface.
42+
*/
43+
export interface IDataEngine {
44+
/**
45+
* Insert a new record
46+
*
47+
* @param objectName - Name of the object/table (e.g., 'user', 'order')
48+
* @param data - Data to insert
49+
* @returns Promise resolving to the created record (including generated ID)
50+
*
51+
* @example
52+
* ```ts
53+
* const user = await engine.insert('user', {
54+
* name: 'John Doe',
55+
* email: 'john@example.com'
56+
* });
57+
* console.log(user.id); // Auto-generated ID
58+
* ```
59+
*/
60+
insert(objectName: string, data: any): Promise<any>;
61+
62+
/**
63+
* Find records matching a query
64+
*
65+
* @param objectName - Name of the object/table
66+
* @param query - Query conditions (optional)
67+
* @returns Promise resolving to an array of matching records
68+
*
69+
* @example
70+
* ```ts
71+
* // Find all users
72+
* const allUsers = await engine.find('user');
73+
*
74+
* // Find with filter
75+
* const activeUsers = await engine.find('user', {
76+
* filter: { status: 'active' }
77+
* });
78+
*
79+
* // Find with limit and sort
80+
* const recentUsers = await engine.find('user', {
81+
* sort: { createdAt: -1 },
82+
* limit: 10
83+
* });
84+
* ```
85+
*/
86+
find(objectName: string, query?: QueryOptions): Promise<any[]>;
87+
88+
/**
89+
* Update a record by ID
90+
*
91+
* @param objectName - Name of the object/table
92+
* @param id - Record ID
93+
* @param data - Updated data (partial update)
94+
* @returns Promise resolving to the updated record
95+
*
96+
* @example
97+
* ```ts
98+
* const updatedUser = await engine.update('user', '123', {
99+
* name: 'Jane Doe',
100+
* email: 'jane@example.com'
101+
* });
102+
* ```
103+
*/
104+
update(objectName: string, id: any, data: any): Promise<any>;
105+
106+
/**
107+
* Delete a record by ID
108+
*
109+
* @param objectName - Name of the object/table
110+
* @param id - Record ID
111+
* @returns Promise resolving to true if deleted, false otherwise
112+
*
113+
* @example
114+
* ```ts
115+
* const deleted = await engine.delete('user', '123');
116+
* if (deleted) {
117+
* console.log('User deleted successfully');
118+
* }
119+
* ```
120+
*/
121+
delete(objectName: string, id: any): Promise<boolean>;
122+
}

0 commit comments

Comments
 (0)