Skip to content

Commit 0e6395b

Browse files
Copilothotlong
andcommitted
Changes before error encountered
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent bf8b8bb commit 0e6395b

6 files changed

Lines changed: 1415 additions & 1 deletion

File tree

packages/core/API_REGISTRY.md

Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
# API Registry Implementation
2+
3+
## Overview
4+
5+
The API Registry is a centralized service in the ObjectStack kernel that manages API endpoint registration, discovery, and conflict resolution across different protocols and plugins.
6+
7+
## Features
8+
9+
**Multi-Protocol Support** - REST, GraphQL, OData, WebSocket, Plugin APIs, and more
10+
**Route Conflict Detection** - Configurable strategies (error, priority, first-wins, last-wins)
11+
**RBAC Integration** - Endpoints can specify required permissions
12+
**Dynamic Schema Linking** - Reference ObjectQL objects for auto-updating schemas
13+
**Protocol Extensions** - Support for gRPC, tRPC, and custom protocols
14+
**API Discovery** - Filter and search APIs by type, status, tags, and more
15+
16+
## Architecture
17+
18+
The API Registry follows the ObjectStack microkernel pattern:
19+
20+
```
21+
┌─────────────────────────────────────────────────────┐
22+
│ ObjectKernel (Core) │
23+
│ ┌───────────────────────────────────────────────┐ │
24+
│ │ Service Registry (DI Container) │ │
25+
│ │ ┌─────────────────────────────────────────┐ │ │
26+
│ │ │ API Registry Service │ │ │
27+
│ │ │ • registerApi() │ │ │
28+
│ │ │ • unregisterApi() │ │ │
29+
│ │ │ • findApis() │ │ │
30+
│ │ │ • getRegistry() │ │ │
31+
│ │ └─────────────────────────────────────────┘ │ │
32+
│ └───────────────────────────────────────────────┘ │
33+
└─────────────────────────────────────────────────────┘
34+
35+
┌─────────┴─────────┬──────────┬──────────┐
36+
│ │ │ │
37+
┌───▼────┐ ┌───────▼──┐ ┌──▼───┐ ┌───▼────┐
38+
│ REST │ │ GraphQL │ │WebSkt│ │ Plugin │
39+
│ Plugin │ │ Plugin │ │Plugin│ │ APIs │
40+
└────────┘ └──────────┘ └──────┘ └────────┘
41+
```
42+
43+
## Usage
44+
45+
### 1. Register the API Registry Plugin
46+
47+
```typescript
48+
import { ObjectKernel, createApiRegistryPlugin } from '@objectstack/core';
49+
50+
const kernel = new ObjectKernel();
51+
52+
// Register with default settings (error on conflicts)
53+
kernel.use(createApiRegistryPlugin());
54+
55+
// Or with custom configuration
56+
kernel.use(
57+
createApiRegistryPlugin({
58+
conflictResolution: 'priority', // priority, first-wins, last-wins
59+
version: '1.0.0',
60+
})
61+
);
62+
63+
await kernel.bootstrap();
64+
```
65+
66+
### 2. Register APIs in Plugins
67+
68+
```typescript
69+
import type { Plugin } from '@objectstack/core';
70+
import type { ApiRegistry } from '@objectstack/core';
71+
import type { ApiRegistryEntry } from '@objectstack/spec/api';
72+
73+
const myPlugin: Plugin = {
74+
name: 'my-plugin',
75+
version: '1.0.0',
76+
77+
init: async (ctx) => {
78+
// Get the API Registry service
79+
const registry = ctx.getService<ApiRegistry>('api-registry');
80+
81+
// Register your API
82+
const api: ApiRegistryEntry = {
83+
id: 'customer_api',
84+
name: 'Customer API',
85+
type: 'rest',
86+
version: 'v1',
87+
basePath: '/api/v1/customers',
88+
endpoints: [
89+
{
90+
id: 'get_customer',
91+
method: 'GET',
92+
path: '/api/v1/customers/:id',
93+
summary: 'Get customer by ID',
94+
requiredPermissions: ['customer.read'], // RBAC
95+
parameters: [
96+
{
97+
name: 'id',
98+
in: 'path',
99+
required: true,
100+
schema: { type: 'string', format: 'uuid' },
101+
},
102+
],
103+
responses: [
104+
{
105+
statusCode: 200,
106+
description: 'Customer found',
107+
schema: {
108+
$ref: {
109+
objectId: 'customer', // Dynamic ObjectQL reference
110+
excludeFields: ['password_hash'],
111+
},
112+
},
113+
},
114+
],
115+
},
116+
],
117+
metadata: {
118+
status: 'active',
119+
tags: ['customer', 'crm'],
120+
},
121+
};
122+
123+
registry.registerApi(api);
124+
},
125+
};
126+
```
127+
128+
### 3. Discover APIs
129+
130+
```typescript
131+
const registry = kernel.getService<ApiRegistry>('api-registry');
132+
133+
// Get all APIs
134+
const allApis = registry.getAllApis();
135+
136+
// Find REST APIs
137+
const restApis = registry.findApis({ type: 'rest' });
138+
139+
// Find active APIs with specific tags
140+
const crmApis = registry.findApis({
141+
status: 'active',
142+
tags: ['crm'],
143+
});
144+
145+
// Search by name
146+
const searchResults = registry.findApis({
147+
search: 'customer',
148+
});
149+
150+
// Get endpoint by route
151+
const endpoint = registry.findEndpointByRoute('GET', '/api/v1/customers/:id');
152+
console.log(endpoint?.api.name); // "Customer API"
153+
console.log(endpoint?.endpoint.summary); // "Get customer by ID"
154+
```
155+
156+
### 4. Get Registry Snapshot
157+
158+
```typescript
159+
const registry = kernel.getService<ApiRegistry>('api-registry');
160+
const snapshot = registry.getRegistry();
161+
162+
console.log(`Total APIs: ${snapshot.totalApis}`);
163+
console.log(`Total Endpoints: ${snapshot.totalEndpoints}`);
164+
console.log(`Conflict Resolution: ${snapshot.conflictResolution}`);
165+
166+
// APIs grouped by type
167+
snapshot.byType?.rest.forEach((api) => {
168+
console.log(`REST API: ${api.name}`);
169+
});
170+
171+
// APIs grouped by status
172+
snapshot.byStatus?.active.forEach((api) => {
173+
console.log(`Active API: ${api.name}`);
174+
});
175+
```
176+
177+
## Conflict Resolution Strategies
178+
179+
### 1. Error (Default)
180+
181+
Throws an error when a route conflict is detected.
182+
183+
```typescript
184+
kernel.use(createApiRegistryPlugin({ conflictResolution: 'error' }));
185+
```
186+
187+
**Best for:** Production environments where conflicts should be caught early.
188+
189+
### 2. Priority
190+
191+
Uses the `priority` field on endpoints to resolve conflicts. Higher priority wins.
192+
193+
```typescript
194+
kernel.use(createApiRegistryPlugin({ conflictResolution: 'priority' }));
195+
196+
// In your plugin
197+
registry.registerApi({
198+
endpoints: [
199+
{
200+
path: '/api/data/:object',
201+
priority: 900, // Core API (high priority)
202+
},
203+
],
204+
});
205+
```
206+
207+
**Priority Ranges:**
208+
- **900-1000**: Core system endpoints
209+
- **500-900**: Custom/override endpoints
210+
- **100-500**: Plugin endpoints
211+
- **0-100**: Fallback routes
212+
213+
### 3. First-Wins
214+
215+
First registered endpoint wins. Subsequent registrations are ignored.
216+
217+
```typescript
218+
kernel.use(createApiRegistryPlugin({ conflictResolution: 'first-wins' }));
219+
```
220+
221+
**Best for:** Stable, predictable routing where load order matters.
222+
223+
### 4. Last-Wins
224+
225+
Last registered endpoint wins. Previous registrations are overwritten.
226+
227+
```typescript
228+
kernel.use(createApiRegistryPlugin({ conflictResolution: 'last-wins' }));
229+
```
230+
231+
**Best for:** Development/testing where you want to override defaults.
232+
233+
## RBAC Integration
234+
235+
Endpoints can specify required permissions that are automatically validated at the gateway level:
236+
237+
```typescript
238+
{
239+
id: 'delete_customer',
240+
method: 'DELETE',
241+
path: '/api/v1/customers/:id',
242+
requiredPermissions: [
243+
'customer.delete',
244+
'api_enabled',
245+
],
246+
responses: [],
247+
}
248+
```
249+
250+
**Permission Format:**
251+
- **Object Permissions:** `<object>.<operation>` (e.g., `customer.read`, `order.delete`)
252+
- **System Permissions:** `<permission_name>` (e.g., `manage_users`, `api_enabled`)
253+
254+
## Dynamic Schema Linking
255+
256+
Reference ObjectQL objects instead of static schemas:
257+
258+
```typescript
259+
{
260+
statusCode: 200,
261+
description: 'Customer retrieved',
262+
schema: {
263+
$ref: {
264+
objectId: 'customer', // ObjectQL object name
265+
excludeFields: ['password_hash'], // Exclude sensitive fields
266+
includeFields: ['id', 'name'], // Or whitelist specific fields
267+
includeRelated: ['account'], // Include related objects
268+
},
269+
},
270+
}
271+
```
272+
273+
**Benefits:**
274+
- API documentation auto-updates when object schemas change
275+
- No schema duplication between API and data model
276+
- Consistent type definitions across API and database
277+
278+
## Protocol-Specific Configuration
279+
280+
Support custom protocols with `protocolConfig`:
281+
282+
### WebSocket
283+
284+
```typescript
285+
{
286+
id: 'customer_updates',
287+
path: '/ws/customers',
288+
protocolConfig: {
289+
subProtocol: 'websocket',
290+
eventName: 'customer.updated',
291+
direction: 'server-to-client',
292+
},
293+
}
294+
```
295+
296+
### gRPC
297+
298+
```typescript
299+
{
300+
id: 'grpc_method',
301+
path: '/grpc/CustomerService/GetCustomer',
302+
protocolConfig: {
303+
subProtocol: 'grpc',
304+
serviceName: 'CustomerService',
305+
methodName: 'GetCustomer',
306+
streaming: false,
307+
},
308+
}
309+
```
310+
311+
### tRPC
312+
313+
```typescript
314+
{
315+
id: 'trpc_query',
316+
path: '/trpc/customer.get',
317+
protocolConfig: {
318+
subProtocol: 'trpc',
319+
procedureType: 'query',
320+
router: 'customer',
321+
},
322+
}
323+
```
324+
325+
## API Registry Methods
326+
327+
### Registration
328+
329+
- `registerApi(api: ApiRegistryEntry): void` - Register an API
330+
- `unregisterApi(apiId: string): void` - Unregister an API
331+
332+
### Discovery
333+
334+
- `getApi(apiId: string): ApiRegistryEntry | undefined` - Get API by ID
335+
- `getAllApis(): ApiRegistryEntry[]` - Get all registered APIs
336+
- `findApis(query: ApiDiscoveryQuery): ApiDiscoveryResponse` - Search/filter APIs
337+
- `getEndpoint(apiId: string, endpointId: string): ApiEndpointRegistration | undefined` - Get specific endpoint
338+
- `findEndpointByRoute(method: string, path: string): { api, endpoint } | undefined` - Find endpoint by route
339+
340+
### Registry Info
341+
342+
- `getRegistry(): ApiRegistry` - Get complete registry snapshot
343+
- `getStats(): RegistryStats` - Get registry statistics
344+
- `clear(): void` - Clear all registered APIs (for testing)
345+
346+
## Examples
347+
348+
See [api-registry-example.ts](./examples/api-registry-example.ts) for comprehensive examples:
349+
350+
1. **Basic API Registration** - Simple REST API with CRUD endpoints
351+
2. **Multi-Plugin Discovery** - Multiple plugins registering different API types
352+
3. **Route Conflict Resolution** - Priority-based conflict handling
353+
4. **Custom Protocol Support** - WebSocket API with protocol config
354+
5. **Dynamic Schema Linking** - ObjectQL reference in API responses
355+
356+
## Testing
357+
358+
Run the API Registry tests:
359+
360+
```bash
361+
pnpm --filter @objectstack/core test api-registry.test.ts
362+
pnpm --filter @objectstack/core test api-registry-plugin.test.ts
363+
```
364+
365+
**Test Coverage:**
366+
- ✅ 32 tests for ApiRegistry service
367+
- ✅ 9 tests for API Registry plugin
368+
- ✅ All conflict resolution strategies
369+
- ✅ Multi-protocol support
370+
- ✅ API discovery and filtering
371+
- ✅ Integration with kernel lifecycle
372+
373+
## Next Steps
374+
375+
Based on [API_REGISTRY_ENHANCEMENTS.md](../../API_REGISTRY_ENHANCEMENTS.md), recommended next implementations:
376+
377+
1. **API Explorer Plugin** - UI to visualize the registry
378+
2. **Gateway Integration** - Implement permission checking in API gateway
379+
3. **Schema Resolution** - Build engine to resolve ObjectQL references to JSON schemas
380+
4. **Conflict Detection UI** - Visualization of route conflicts and priorities
381+
5. **Plugin Examples** - Reference implementations for gRPC and tRPC plugins
382+
383+
## Related Documentation
384+
385+
- [API Registry Schema](../spec/src/api/registry.zod.ts) - Zod schema definitions
386+
- [API Registry Tests](./src/api-registry.test.ts) - Comprehensive test suite
387+
- [Plugin System](./README.md) - ObjectStack plugin architecture
388+
- [Microkernel Design](../../ARCHITECTURE.md) - Overall architecture
389+
390+
## License
391+
392+
MIT

0 commit comments

Comments
 (0)