@olane/o-server 0.7.2
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 +721 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/interfaces/response.interface.d.ts +14 -0
- package/dist/src/interfaces/response.interface.d.ts.map +1 -0
- package/dist/src/interfaces/response.interface.js +1 -0
- package/dist/src/interfaces/server-config.interface.d.ts +31 -0
- package/dist/src/interfaces/server-config.interface.d.ts.map +1 -0
- package/dist/src/interfaces/server-config.interface.js +1 -0
- package/dist/src/middleware/auth.d.ts +11 -0
- package/dist/src/middleware/auth.d.ts.map +1 -0
- package/dist/src/middleware/auth.js +18 -0
- package/dist/src/middleware/error-handler.d.ts +8 -0
- package/dist/src/middleware/error-handler.d.ts.map +1 -0
- package/dist/src/middleware/error-handler.js +13 -0
- package/dist/src/o-server.d.ts +3 -0
- package/dist/src/o-server.d.ts.map +1 -0
- package/dist/src/o-server.js +195 -0
- package/dist/src/utils/logger.d.ts +8 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +16 -0
- package/dist/test/ai.spec.d.ts +2 -0
- package/dist/test/ai.spec.d.ts.map +1 -0
- package/dist/test/ai.spec.js +19 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
# @olane/o-server
|
|
2
|
+
|
|
3
|
+
HTTP server entrypoint for Olane OS nodes. Exposes a node's `use` functionality via REST API.
|
|
4
|
+
|
|
5
|
+
**TL;DR**: Add HTTP/REST endpoints to any Olane node. Perfect for web frontends, mobile apps, or external services that need HTTP access to your node's capabilities.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @olane/o-server
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
Add HTTP endpoints to your Olane node:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { oServer } from '@olane/o-server';
|
|
19
|
+
import { oLaneTool } from '@olane/o-lane';
|
|
20
|
+
import { oAddress } from '@olane/o-core';
|
|
21
|
+
|
|
22
|
+
// Create your node
|
|
23
|
+
const myNode = new oLaneTool({
|
|
24
|
+
address: new oAddress('o://my-node'),
|
|
25
|
+
// ... other config
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
await myNode.start();
|
|
29
|
+
|
|
30
|
+
// Add HTTP server
|
|
31
|
+
const server = oServer({
|
|
32
|
+
node: myNode,
|
|
33
|
+
port: 3000
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
await server.start();
|
|
37
|
+
// Server running on http://localhost:3000/api/v1
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Now make HTTP calls to your node:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Call the node's 'use' method via HTTP
|
|
44
|
+
curl -X POST http://localhost:3000/api/v1/use \
|
|
45
|
+
-H "Content-Type: application/json" \
|
|
46
|
+
-d '{
|
|
47
|
+
"address": "o://analytics",
|
|
48
|
+
"method": "calculate_revenue",
|
|
49
|
+
"params": {"startDate": "2024-01-01", "endDate": "2024-03-31"}
|
|
50
|
+
}'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## How It Works
|
|
54
|
+
|
|
55
|
+
`o-server` is a **simple HTTP wrapper** around a node's `use` method:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
┌────────────────────────┐
|
|
59
|
+
│ HTTP Client │
|
|
60
|
+
│ (Web, Mobile, etc.) │
|
|
61
|
+
└────────────────────────┘
|
|
62
|
+
⬇ HTTP POST
|
|
63
|
+
┌────────────────────────┐
|
|
64
|
+
│ o-server │
|
|
65
|
+
│ • Translates request │
|
|
66
|
+
│ • Calls node.use() │
|
|
67
|
+
│ • Returns JSON │
|
|
68
|
+
└────────────────────────┘
|
|
69
|
+
⬇ node.use(address, data)
|
|
70
|
+
┌────────────────────────┐
|
|
71
|
+
│ Your Olane Node │
|
|
72
|
+
│ • Routes to target │
|
|
73
|
+
│ • Executes method │
|
|
74
|
+
│ • Returns result │
|
|
75
|
+
└────────────────────────┘
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Key Point**: `o-server` does NOT contain or manage Olane OS. It's just an HTTP entrypoint into a node that participates in Olane OS.
|
|
79
|
+
|
|
80
|
+
## API Endpoints
|
|
81
|
+
|
|
82
|
+
### POST `/api/v1/use`
|
|
83
|
+
|
|
84
|
+
Primary endpoint - calls the node's `use` method directly.
|
|
85
|
+
|
|
86
|
+
**Request Body:**
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"address": "o://target/node",
|
|
90
|
+
"method": "method_name",
|
|
91
|
+
"params": { ... },
|
|
92
|
+
"id": "optional-request-id"
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Example:**
|
|
97
|
+
```bash
|
|
98
|
+
curl -X POST http://localhost:3000/api/v1/use \
|
|
99
|
+
-H "Content-Type: application/json" \
|
|
100
|
+
-d '{
|
|
101
|
+
"address": "o://calculator",
|
|
102
|
+
"method": "add",
|
|
103
|
+
"params": {"a": 5, "b": 3}
|
|
104
|
+
}'
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Response:**
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"success": true,
|
|
111
|
+
"data": {
|
|
112
|
+
"result": 8
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### POST `/api/v1/:address/:method`
|
|
120
|
+
|
|
121
|
+
Convenience endpoint - REST-style URL structure.
|
|
122
|
+
|
|
123
|
+
**Parameters:**
|
|
124
|
+
- `address` (path): Target node address (without `o://` prefix)
|
|
125
|
+
- `method` (path): Method name to call
|
|
126
|
+
- `params` (body): Method parameters as JSON
|
|
127
|
+
|
|
128
|
+
**Example:**
|
|
129
|
+
```bash
|
|
130
|
+
curl -X POST http://localhost:3000/api/v1/calculator/add \
|
|
131
|
+
-H "Content-Type: application/json" \
|
|
132
|
+
-d '{"a": 5, "b": 3}'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Response:**
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"success": true,
|
|
139
|
+
"data": {
|
|
140
|
+
"result": 8
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### POST `/api/v1/use/stream`
|
|
148
|
+
|
|
149
|
+
Streaming endpoint (Server-Sent Events).
|
|
150
|
+
|
|
151
|
+
**Request Body:**
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"address": "o://target/node",
|
|
155
|
+
"method": "method_name",
|
|
156
|
+
"params": { ... }
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Example:**
|
|
161
|
+
```bash
|
|
162
|
+
curl -X POST http://localhost:3000/api/v1/use/stream \
|
|
163
|
+
-H "Content-Type: application/json" \
|
|
164
|
+
-d '{
|
|
165
|
+
"address": "o://analytics",
|
|
166
|
+
"method": "generate_report",
|
|
167
|
+
"params": {"format": "pdf"}
|
|
168
|
+
}'
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Response (SSE):**
|
|
172
|
+
```
|
|
173
|
+
data: {"type":"complete","result":{...}}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
### GET `/api/v1/health`
|
|
179
|
+
|
|
180
|
+
Health check endpoint.
|
|
181
|
+
|
|
182
|
+
**Example:**
|
|
183
|
+
```bash
|
|
184
|
+
curl http://localhost:3000/api/v1/health
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Response:**
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"success": true,
|
|
191
|
+
"data": {
|
|
192
|
+
"status": "healthy",
|
|
193
|
+
"timestamp": 1704067200000,
|
|
194
|
+
"uptime": 3600.5
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Configuration
|
|
200
|
+
|
|
201
|
+
### Basic Configuration
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { oServer } from '@olane/o-server';
|
|
205
|
+
|
|
206
|
+
const server = oServer({
|
|
207
|
+
// Required: Your Olane node
|
|
208
|
+
node: myNode,
|
|
209
|
+
|
|
210
|
+
// Optional: Server port (default: 3000)
|
|
211
|
+
port: 8080,
|
|
212
|
+
|
|
213
|
+
// Optional: Base path (default: '/api/v1')
|
|
214
|
+
basePath: '/api/v2',
|
|
215
|
+
|
|
216
|
+
// Optional: CORS settings
|
|
217
|
+
cors: {
|
|
218
|
+
origin: 'https://example.com',
|
|
219
|
+
credentials: true
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
// Optional: Authentication middleware
|
|
223
|
+
authenticate: async (req) => {
|
|
224
|
+
const token = req.headers.authorization;
|
|
225
|
+
return validateToken(token);
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
// Optional: Enable debug logging (default: false)
|
|
229
|
+
debug: true
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
await server.start();
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Authentication
|
|
236
|
+
|
|
237
|
+
Protect your endpoints with authentication:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
const server = oServer({
|
|
241
|
+
node: myNode,
|
|
242
|
+
port: 3000,
|
|
243
|
+
authenticate: async (req) => {
|
|
244
|
+
// Validate JWT token
|
|
245
|
+
const token = req.headers.authorization?.split(' ')[1];
|
|
246
|
+
|
|
247
|
+
if (!token) {
|
|
248
|
+
throw new Error('No token provided');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const user = await verifyJWT(token);
|
|
252
|
+
return { userId: user.id, roles: user.roles };
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Now all requests require valid authentication
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### CORS Configuration
|
|
260
|
+
|
|
261
|
+
Enable cross-origin requests:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
const server = oServer({
|
|
265
|
+
node: myNode,
|
|
266
|
+
port: 3000,
|
|
267
|
+
cors: {
|
|
268
|
+
origin: 'http://localhost:5173', // Your frontend URL
|
|
269
|
+
credentials: true,
|
|
270
|
+
allowedHeaders: ['Content-Type', 'Authorization']
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Custom Routes
|
|
276
|
+
|
|
277
|
+
Add custom routes alongside auto-generated endpoints:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const server = oServer({ node: myNode, port: 3000 });
|
|
281
|
+
|
|
282
|
+
// Add custom route
|
|
283
|
+
server.app.get('/status', (req, res) => {
|
|
284
|
+
res.json({ status: 'operational' });
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Add custom middleware
|
|
288
|
+
server.app.use((req, res, next) => {
|
|
289
|
+
console.log(`${req.method} ${req.path}`);
|
|
290
|
+
next();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
await server.start();
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Common Use Cases
|
|
297
|
+
|
|
298
|
+
### Use Case 1: Web Frontend Integration
|
|
299
|
+
|
|
300
|
+
Expose your node to a React/Vue/Angular app:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// backend/server.ts
|
|
304
|
+
import { oServer } from '@olane/o-server';
|
|
305
|
+
import { oLaneTool } from '@olane/o-lane';
|
|
306
|
+
import { oAddress } from '@olane/o-core';
|
|
307
|
+
|
|
308
|
+
const analyticsNode = new oLaneTool({
|
|
309
|
+
address: new oAddress('o://analytics'),
|
|
310
|
+
// ... config
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
await analyticsNode.start();
|
|
314
|
+
|
|
315
|
+
const server = oServer({
|
|
316
|
+
node: analyticsNode,
|
|
317
|
+
port: 3000,
|
|
318
|
+
cors: { origin: 'http://localhost:5173' } // Vite dev server
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
await server.start();
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// frontend/src/api.ts
|
|
326
|
+
export async function analyzeRevenue(startDate: string, endDate: string) {
|
|
327
|
+
const response = await fetch('http://localhost:3000/api/v1/use', {
|
|
328
|
+
method: 'POST',
|
|
329
|
+
headers: { 'Content-Type': 'application/json' },
|
|
330
|
+
body: JSON.stringify({
|
|
331
|
+
address: 'o://analytics',
|
|
332
|
+
method: 'calculate_revenue',
|
|
333
|
+
params: { startDate, endDate }
|
|
334
|
+
})
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const result = await response.json();
|
|
338
|
+
return result.data;
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Use Case 2: Mobile App Backend
|
|
343
|
+
|
|
344
|
+
Create a REST API for iOS/Android apps:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
import { oServer } from '@olane/o-server';
|
|
348
|
+
|
|
349
|
+
const server = oServer({
|
|
350
|
+
node: myNode,
|
|
351
|
+
port: 8080,
|
|
352
|
+
authenticate: async (req) => {
|
|
353
|
+
// Validate Firebase auth token
|
|
354
|
+
const token = req.headers.authorization?.split(' ')[1];
|
|
355
|
+
const decodedToken = await admin.auth().verifyIdToken(token);
|
|
356
|
+
return { userId: decodedToken.uid };
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
await server.start();
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Use Case 3: Webhook Handler
|
|
364
|
+
|
|
365
|
+
Receive webhooks from external services:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import { oServer } from '@olane/o-server';
|
|
369
|
+
import { oAddress } from '@olane/o-core';
|
|
370
|
+
|
|
371
|
+
const server = oServer({ node: myNode, port: 3000 });
|
|
372
|
+
|
|
373
|
+
// Stripe webhook
|
|
374
|
+
server.app.post('/webhooks/stripe', async (req, res) => {
|
|
375
|
+
const event = req.body;
|
|
376
|
+
|
|
377
|
+
// Process via your node
|
|
378
|
+
await myNode.use(
|
|
379
|
+
new oAddress('o://payments/processor'),
|
|
380
|
+
{
|
|
381
|
+
method: 'handle_payment',
|
|
382
|
+
params: { event }
|
|
383
|
+
}
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
res.json({ received: true });
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
await server.start();
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Use Case 4: Internal API Gateway
|
|
393
|
+
|
|
394
|
+
Gateway node that routes to other nodes:
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
import { oServer } from '@olane/o-server';
|
|
398
|
+
import { oLaneTool } from '@olane/o-lane';
|
|
399
|
+
import { oAddress } from '@olane/o-core';
|
|
400
|
+
|
|
401
|
+
// Create gateway node connected to network
|
|
402
|
+
const gateway = new oLaneTool({
|
|
403
|
+
address: new oAddress('o://gateway'),
|
|
404
|
+
leader: new oAddress('o://leader'),
|
|
405
|
+
// ... config
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
await gateway.start();
|
|
409
|
+
|
|
410
|
+
// Expose gateway via HTTP
|
|
411
|
+
const server = oServer({
|
|
412
|
+
node: gateway,
|
|
413
|
+
port: 3000
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
await server.start();
|
|
417
|
+
|
|
418
|
+
// Now clients can call ANY node in the network via HTTP
|
|
419
|
+
// curl -X POST http://localhost:3000/api/v1/use \
|
|
420
|
+
// -d '{"address": "o://any/node/in/network", "method": "...", "params": {...}}'
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## Error Handling
|
|
424
|
+
|
|
425
|
+
`o-server` provides consistent error responses:
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
// Success response
|
|
429
|
+
{
|
|
430
|
+
"success": true,
|
|
431
|
+
"data": { ... }
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Error response
|
|
435
|
+
{
|
|
436
|
+
"success": false,
|
|
437
|
+
"error": {
|
|
438
|
+
"code": "EXECUTION_ERROR",
|
|
439
|
+
"message": "Error message",
|
|
440
|
+
"details": { ... }
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Common error codes:**
|
|
446
|
+
- `INVALID_PARAMS` - Missing or invalid request parameters
|
|
447
|
+
- `NODE_NOT_FOUND` - Target node address not found
|
|
448
|
+
- `TOOL_NOT_FOUND` - Method doesn't exist on target node
|
|
449
|
+
- `EXECUTION_ERROR` - Error during method execution
|
|
450
|
+
- `TIMEOUT` - Request exceeded timeout limit
|
|
451
|
+
- `UNAUTHORIZED` - Authentication failed
|
|
452
|
+
|
|
453
|
+
**Custom error handling:**
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
const server = oServer({ node: myNode, port: 3000 });
|
|
457
|
+
|
|
458
|
+
server.app.use((err, req, res, next) => {
|
|
459
|
+
console.error('Server error:', err);
|
|
460
|
+
|
|
461
|
+
res.status(err.status || 500).json({
|
|
462
|
+
success: false,
|
|
463
|
+
error: {
|
|
464
|
+
code: err.code || 'INTERNAL_ERROR',
|
|
465
|
+
message: err.message,
|
|
466
|
+
details: process.env.NODE_ENV === 'development' ? err.stack : undefined
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
## Troubleshooting
|
|
473
|
+
|
|
474
|
+
### Error: "Node is not running"
|
|
475
|
+
|
|
476
|
+
**Cause:** Node not started before creating server.
|
|
477
|
+
|
|
478
|
+
**Solution:**
|
|
479
|
+
```typescript
|
|
480
|
+
const myNode = new oLaneTool({ ... });
|
|
481
|
+
await myNode.start(); // ← Make sure node is started
|
|
482
|
+
|
|
483
|
+
const server = oServer({ node: myNode, port: 3000 });
|
|
484
|
+
await server.start();
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Error: "Invalid address"
|
|
488
|
+
|
|
489
|
+
**Cause:** Address format is incorrect.
|
|
490
|
+
|
|
491
|
+
**Solution:**
|
|
492
|
+
```bash
|
|
493
|
+
# Correct - with o:// prefix
|
|
494
|
+
curl -X POST http://localhost:3000/api/v1/use \
|
|
495
|
+
-d '{"address": "o://target", ...}'
|
|
496
|
+
|
|
497
|
+
# Also correct - using convenience endpoint
|
|
498
|
+
curl -X POST http://localhost:3000/api/v1/target/method \
|
|
499
|
+
-d '{"params": {...}}'
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Error: "CORS policy blocked"
|
|
503
|
+
|
|
504
|
+
**Cause:** Cross-origin requests not configured.
|
|
505
|
+
|
|
506
|
+
**Solution:**
|
|
507
|
+
```typescript
|
|
508
|
+
const server = oServer({
|
|
509
|
+
node: myNode,
|
|
510
|
+
port: 3000,
|
|
511
|
+
cors: {
|
|
512
|
+
origin: 'http://localhost:5173', // Your frontend URL
|
|
513
|
+
credentials: true
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### High Latency
|
|
519
|
+
|
|
520
|
+
**Cause:** Network overhead or inefficient routing.
|
|
521
|
+
|
|
522
|
+
**Solutions:**
|
|
523
|
+
1. Deploy o-server on same machine as the node
|
|
524
|
+
2. Use o-server on a gateway node connected to the network
|
|
525
|
+
3. Enable caching for frequently accessed data
|
|
526
|
+
4. Consider using WebSocket transport for real-time needs
|
|
527
|
+
|
|
528
|
+
## TypeScript Support
|
|
529
|
+
|
|
530
|
+
Full TypeScript definitions included:
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
import { oServer, ServerConfig, ServerInstance } from '@olane/o-server';
|
|
534
|
+
import { oCore } from '@olane/o-core';
|
|
535
|
+
|
|
536
|
+
const config: ServerConfig = {
|
|
537
|
+
node: myNode,
|
|
538
|
+
port: 3000,
|
|
539
|
+
basePath: '/api/v1',
|
|
540
|
+
cors: {
|
|
541
|
+
origin: 'https://example.com'
|
|
542
|
+
},
|
|
543
|
+
authenticate: async (req) => {
|
|
544
|
+
return { userId: '123' };
|
|
545
|
+
},
|
|
546
|
+
debug: true
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
const server: ServerInstance = oServer(config);
|
|
550
|
+
await server.start();
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
## Production Deployment
|
|
554
|
+
|
|
555
|
+
### Environment Variables
|
|
556
|
+
|
|
557
|
+
```bash
|
|
558
|
+
# .env
|
|
559
|
+
PORT=8080
|
|
560
|
+
BASE_PATH=/api/v1
|
|
561
|
+
NODE_ENV=production
|
|
562
|
+
LOG_LEVEL=info
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### Docker Deployment
|
|
566
|
+
|
|
567
|
+
```dockerfile
|
|
568
|
+
# Dockerfile
|
|
569
|
+
FROM node:20-alpine
|
|
570
|
+
|
|
571
|
+
WORKDIR /app
|
|
572
|
+
|
|
573
|
+
COPY package*.json ./
|
|
574
|
+
RUN npm ci --only=production
|
|
575
|
+
|
|
576
|
+
COPY . .
|
|
577
|
+
RUN npm run build
|
|
578
|
+
|
|
579
|
+
EXPOSE 8080
|
|
580
|
+
|
|
581
|
+
CMD ["node", "dist/index.js"]
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Health Checks
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
const server = oServer({ node: myNode, port: 3000 });
|
|
588
|
+
|
|
589
|
+
// Health check endpoint is built-in
|
|
590
|
+
// GET /api/v1/health
|
|
591
|
+
|
|
592
|
+
// For Kubernetes/Docker health checks:
|
|
593
|
+
// livenessProbe:
|
|
594
|
+
// httpGet:
|
|
595
|
+
// path: /api/v1/health
|
|
596
|
+
// port: 3000
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
## Architecture Patterns
|
|
600
|
+
|
|
601
|
+
### Pattern 1: Node-Specific Server
|
|
602
|
+
|
|
603
|
+
One server per node (isolated services):
|
|
604
|
+
|
|
605
|
+
```
|
|
606
|
+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
607
|
+
│ o-server │ │ o-server │ │ o-server │
|
|
608
|
+
│ :3000 │ │ :3001 │ │ :3002 │
|
|
609
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
|
610
|
+
⬇ ⬇ ⬇
|
|
611
|
+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
612
|
+
│ Analytics │ │ CRM Node │ │ Payment │
|
|
613
|
+
│ Node │ │ │ │ Node │
|
|
614
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
**Best for:** Microservices architecture, team isolation
|
|
618
|
+
|
|
619
|
+
---
|
|
620
|
+
|
|
621
|
+
### Pattern 2: Gateway Server
|
|
622
|
+
|
|
623
|
+
One server on a gateway node (unified API):
|
|
624
|
+
|
|
625
|
+
```
|
|
626
|
+
┌─────────────┐
|
|
627
|
+
│ o-server │
|
|
628
|
+
│ :3000 │
|
|
629
|
+
└─────────────┘
|
|
630
|
+
⬇
|
|
631
|
+
┌─────────────┐
|
|
632
|
+
│ Gateway │
|
|
633
|
+
│ Node │
|
|
634
|
+
└─────────────┘
|
|
635
|
+
⬇ connected to network
|
|
636
|
+
┌──────────────┼──────────────┐
|
|
637
|
+
⬇ ⬇ ⬇
|
|
638
|
+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
639
|
+
│ Analytics │ │ CRM Node │ │ Payment │
|
|
640
|
+
│ Node │ │ │ │ Node │
|
|
641
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
**Best for:** Unified API, single entry point, centralized auth
|
|
645
|
+
|
|
646
|
+
---
|
|
647
|
+
|
|
648
|
+
### Pattern 3: Load Balanced
|
|
649
|
+
|
|
650
|
+
Multiple servers for high availability:
|
|
651
|
+
|
|
652
|
+
```
|
|
653
|
+
┌─────────────┐
|
|
654
|
+
│ Load │
|
|
655
|
+
│ Balancer │
|
|
656
|
+
└─────────────┘
|
|
657
|
+
⬇
|
|
658
|
+
┌──────────────┼──────────────┐
|
|
659
|
+
⬇ ⬇ ⬇
|
|
660
|
+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
661
|
+
│ o-server 1 │ │ o-server 2 │ │ o-server 3 │
|
|
662
|
+
│ :3000 │ │ :3000 │ │ :3000 │
|
|
663
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
|
664
|
+
└──────────────┼──────────────┘
|
|
665
|
+
⬇
|
|
666
|
+
┌─────────────────┐
|
|
667
|
+
│ Gateway Node │
|
|
668
|
+
│ (shared) │
|
|
669
|
+
└─────────────────┘
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
**Best for:** High traffic, 99.9%+ uptime requirements
|
|
673
|
+
|
|
674
|
+
## Related Packages
|
|
675
|
+
|
|
676
|
+
- **[@olane/o-core](/packages/o-core)** - Core types and `use` method
|
|
677
|
+
- **[@olane/o-node](/packages/o-node)** - Build tool nodes
|
|
678
|
+
- **[@olane/o-lane](/packages/o-lane)** - Intent-driven nodes
|
|
679
|
+
|
|
680
|
+
## Examples
|
|
681
|
+
|
|
682
|
+
Complete examples:
|
|
683
|
+
|
|
684
|
+
```typescript
|
|
685
|
+
// Basic server
|
|
686
|
+
const server = oServer({ node: myNode, port: 3000 });
|
|
687
|
+
await server.start();
|
|
688
|
+
|
|
689
|
+
// With authentication
|
|
690
|
+
const server = oServer({
|
|
691
|
+
node: myNode,
|
|
692
|
+
port: 3000,
|
|
693
|
+
authenticate: async (req) => {
|
|
694
|
+
const token = req.headers.authorization;
|
|
695
|
+
return await validateJWT(token);
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
// With rate limiting
|
|
700
|
+
import rateLimit from 'express-rate-limit';
|
|
701
|
+
|
|
702
|
+
const server = oServer({ node: myNode, port: 3000 });
|
|
703
|
+
|
|
704
|
+
const limiter = rateLimit({
|
|
705
|
+
windowMs: 15 * 60 * 1000,
|
|
706
|
+
max: 100
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
server.app.use('/api/v1', limiter);
|
|
710
|
+
await server.start();
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
## License
|
|
714
|
+
|
|
715
|
+
ISC
|
|
716
|
+
|
|
717
|
+
## Support
|
|
718
|
+
|
|
719
|
+
- **Documentation**: [https://docs.olane.com](https://docs.olane.com)
|
|
720
|
+
- **GitHub**: [https://github.com/olane-labs/olane](https://github.com/olane-labs/olane)
|
|
721
|
+
- **Issues**: [https://github.com/olane-labs/olane/issues](https://github.com/olane-labs/olane/issues)
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { oServer } from './o-server.js';
|
|
2
|
+
export { ServerConfig, ServerInstance, AuthenticateFunction, } from './interfaces/server-config.interface.js';
|
|
3
|
+
export { ErrorResponse, SuccessResponse, } from './interfaces/response.interface.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EACL,YAAY,EACZ,cAAc,EACd,oBAAoB,GACrB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EACL,aAAa,EACb,eAAe,GAChB,MAAM,oCAAoC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { oServer } from './o-server.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface SuccessResponse<T = any> {
|
|
2
|
+
success: true;
|
|
3
|
+
data: T;
|
|
4
|
+
}
|
|
5
|
+
export interface ErrorResponse {
|
|
6
|
+
success: false;
|
|
7
|
+
error: {
|
|
8
|
+
code: string;
|
|
9
|
+
message: string;
|
|
10
|
+
details?: any;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export type ApiResponse<T = any> = SuccessResponse<T> | ErrorResponse;
|
|
14
|
+
//# sourceMappingURL=response.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response.interface.d.ts","sourceRoot":"","sources":["../../../src/interfaces/response.interface.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,GAAG;IACtC,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE,CAAC,CAAC;CACT;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,GAAG,CAAC;KACf,CAAC;CACH;AAED,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,GAAG,IAAI,eAAe,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { oCore } from '@olane/o-core';
|
|
2
|
+
import { Request } from 'express';
|
|
3
|
+
import { CorsOptions } from 'cors';
|
|
4
|
+
export interface AuthUser {
|
|
5
|
+
userId?: string;
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
}
|
|
8
|
+
export type AuthenticateFunction = (req: Request) => Promise<AuthUser>;
|
|
9
|
+
export interface ServerConfig {
|
|
10
|
+
/** Node instance (any oCore-based node with 'use' method) */
|
|
11
|
+
node: oCore;
|
|
12
|
+
/** Server port (default: 3000) */
|
|
13
|
+
port?: number;
|
|
14
|
+
/** Base path for API routes (default: '/api/v1') */
|
|
15
|
+
basePath?: string;
|
|
16
|
+
/** CORS configuration */
|
|
17
|
+
cors?: CorsOptions;
|
|
18
|
+
/** Authentication middleware */
|
|
19
|
+
authenticate?: AuthenticateFunction;
|
|
20
|
+
/** Enable debug logging */
|
|
21
|
+
debug?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface ServerInstance {
|
|
24
|
+
/** Express app instance */
|
|
25
|
+
app: any;
|
|
26
|
+
/** Start the server */
|
|
27
|
+
start(): Promise<void>;
|
|
28
|
+
/** Stop the server */
|
|
29
|
+
stop(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=server-config.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-config.interface.d.ts","sourceRoot":"","sources":["../../../src/interfaces/server-config.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,MAAM,CAAC;AAEnC,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvE,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,IAAI,EAAE,KAAK,CAAC;IAEZ,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,yBAAyB;IACzB,IAAI,CAAC,EAAE,WAAW,CAAC;IAEnB,gCAAgC;IAChC,YAAY,CAAC,EAAE,oBAAoB,CAAC;IAEpC,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,2BAA2B;IAC3B,GAAG,EAAE,GAAG,CAAC;IAET,uBAAuB;IACvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB,sBAAsB;IACtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { AuthenticateFunction, AuthUser } from '../interfaces/server-config.interface.js';
|
|
3
|
+
declare global {
|
|
4
|
+
namespace Express {
|
|
5
|
+
interface Request {
|
|
6
|
+
user?: AuthUser;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export declare function authMiddleware(authenticate: AuthenticateFunction): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
11
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EACL,oBAAoB,EACpB,QAAQ,EACT,MAAM,0CAA0C,CAAC;AAElD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,IAAI,CAAC,EAAE,QAAQ,CAAC;SACjB;KACF;CACF;AAED,wBAAgB,cAAc,CAAC,YAAY,EAAE,oBAAoB,SAC5C,OAAO,OAAO,QAAQ,QAAQ,YAAY,mBAe9D"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function authMiddleware(authenticate) {
|
|
2
|
+
return async (req, res, next) => {
|
|
3
|
+
try {
|
|
4
|
+
const user = await authenticate(req);
|
|
5
|
+
req.user = user;
|
|
6
|
+
next();
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
res.status(401).json({
|
|
10
|
+
success: false,
|
|
11
|
+
error: {
|
|
12
|
+
code: 'UNAUTHORIZED',
|
|
13
|
+
message: error.message || 'Authentication failed',
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
export interface OlaneError extends Error {
|
|
3
|
+
code?: string;
|
|
4
|
+
status?: number;
|
|
5
|
+
details?: any;
|
|
6
|
+
}
|
|
7
|
+
export declare function errorHandler(err: OlaneError, req: Request, res: Response, next: NextFunction): void;
|
|
8
|
+
//# sourceMappingURL=error-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../../src/middleware/error-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG1D,MAAM,WAAW,UAAW,SAAQ,KAAK;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,GAAG,CAAC;CACf;AAED,wBAAgB,YAAY,CAC1B,GAAG,EAAE,UAAU,EACf,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,QAenB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function errorHandler(err, req, res, next) {
|
|
2
|
+
console.error('[o-server] Error:', err);
|
|
3
|
+
const status = err.status || 500;
|
|
4
|
+
const errorResponse = {
|
|
5
|
+
success: false,
|
|
6
|
+
error: {
|
|
7
|
+
code: err.code || 'INTERNAL_ERROR',
|
|
8
|
+
message: err.message || 'An internal error occurred',
|
|
9
|
+
details: process.env.NODE_ENV === 'development' ? err.details : undefined,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
res.status(status).json(errorResponse);
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"o-server.d.ts","sourceRoot":"","sources":["../../src/o-server.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,cAAc,EACf,MAAM,yCAAyC,CAAC;AAQjD,wBAAgB,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc,CA4O5D"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import { errorHandler } from './middleware/error-handler.js';
|
|
4
|
+
import { authMiddleware } from './middleware/auth.js';
|
|
5
|
+
import { ServerLogger } from './utils/logger.js';
|
|
6
|
+
import { oAddress } from '@olane/o-core';
|
|
7
|
+
export function oServer(config) {
|
|
8
|
+
const { node, port = 3000, basePath = '/api/v1', cors: corsConfig, authenticate, debug = false, } = config;
|
|
9
|
+
const app = express();
|
|
10
|
+
const logger = new ServerLogger(debug);
|
|
11
|
+
let server = null;
|
|
12
|
+
// Middleware
|
|
13
|
+
app.use(express.json());
|
|
14
|
+
if (corsConfig) {
|
|
15
|
+
app.use(cors(corsConfig));
|
|
16
|
+
}
|
|
17
|
+
// Optional authentication
|
|
18
|
+
if (authenticate) {
|
|
19
|
+
app.use(basePath, authMiddleware(authenticate));
|
|
20
|
+
}
|
|
21
|
+
// Health check endpoint
|
|
22
|
+
app.get(`${basePath}/health`, (req, res) => {
|
|
23
|
+
res.json({
|
|
24
|
+
success: true,
|
|
25
|
+
data: {
|
|
26
|
+
status: 'healthy',
|
|
27
|
+
timestamp: Date.now(),
|
|
28
|
+
uptime: process.uptime(),
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
// Primary endpoint - POST /api/v1/use
|
|
33
|
+
// This is the main entrypoint that wraps the node's 'use' method
|
|
34
|
+
app.post(`${basePath}/use`, async (req, res, next) => {
|
|
35
|
+
try {
|
|
36
|
+
const { address: addressStr, method, params, id } = req.body;
|
|
37
|
+
if (!addressStr) {
|
|
38
|
+
const error = new Error('Address is required');
|
|
39
|
+
error.code = 'INVALID_PARAMS';
|
|
40
|
+
error.status = 400;
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
logger.debugLog(`Calling use with address ${addressStr}, method: ${method}`);
|
|
44
|
+
const address = new oAddress(addressStr);
|
|
45
|
+
const result = await node.use(address, {
|
|
46
|
+
method,
|
|
47
|
+
params,
|
|
48
|
+
id,
|
|
49
|
+
});
|
|
50
|
+
const response = {
|
|
51
|
+
success: true,
|
|
52
|
+
data: result.result,
|
|
53
|
+
};
|
|
54
|
+
res.json(response);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
handleOlaneError(error, next);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
// Convenience endpoint for tool calls - POST /api/v1/:address/:method
|
|
61
|
+
// This provides a more REST-like interface but still uses the node's 'use' method
|
|
62
|
+
app.post(`${basePath}/:address/:method`, async (req, res, next) => {
|
|
63
|
+
try {
|
|
64
|
+
const { address: addressParam, method } = req.params;
|
|
65
|
+
const params = req.body;
|
|
66
|
+
logger.debugLog(`Calling method ${method} on ${addressParam} with params:`, params);
|
|
67
|
+
const address = new oAddress(`o://${addressParam}`);
|
|
68
|
+
const result = await node.use(address, {
|
|
69
|
+
method,
|
|
70
|
+
params,
|
|
71
|
+
});
|
|
72
|
+
const response = {
|
|
73
|
+
success: true,
|
|
74
|
+
data: result.result,
|
|
75
|
+
};
|
|
76
|
+
res.json(response);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
handleOlaneError(error, next);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
// Streaming endpoint - POST /api/v1/use/stream
|
|
83
|
+
app.post(`${basePath}/use/stream`, async (req, res, next) => {
|
|
84
|
+
try {
|
|
85
|
+
const { address: addressStr, method, params } = req.body;
|
|
86
|
+
if (!addressStr) {
|
|
87
|
+
const error = new Error('Address is required');
|
|
88
|
+
error.code = 'INVALID_PARAMS';
|
|
89
|
+
error.status = 400;
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
logger.debugLog(`Streaming use call to ${addressStr}, method: ${method}`);
|
|
93
|
+
// Set headers for Server-Sent Events
|
|
94
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
95
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
96
|
+
res.setHeader('Connection', 'keep-alive');
|
|
97
|
+
const address = new oAddress(addressStr);
|
|
98
|
+
try {
|
|
99
|
+
// TODO: Implement actual streaming support when available
|
|
100
|
+
// For now, execute and return result
|
|
101
|
+
const result = await node.use(address, {
|
|
102
|
+
method,
|
|
103
|
+
params,
|
|
104
|
+
});
|
|
105
|
+
res.write(`data: ${JSON.stringify({
|
|
106
|
+
type: 'complete',
|
|
107
|
+
result: result.result,
|
|
108
|
+
})}\n\n`);
|
|
109
|
+
res.end();
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
res.write(`data: ${JSON.stringify({
|
|
113
|
+
type: 'error',
|
|
114
|
+
error: {
|
|
115
|
+
code: error.code || 'EXECUTION_ERROR',
|
|
116
|
+
message: error.message,
|
|
117
|
+
},
|
|
118
|
+
})}\n\n`);
|
|
119
|
+
res.end();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
next(error);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
// Error handling middleware (must be last)
|
|
127
|
+
app.use(errorHandler);
|
|
128
|
+
// Helper function to convert Olane errors to HTTP errors
|
|
129
|
+
function handleOlaneError(error, next) {
|
|
130
|
+
const olaneError = new Error(error.message || 'Unknown error');
|
|
131
|
+
// Map common error scenarios
|
|
132
|
+
if (error.code === 'NODE_NOT_FOUND') {
|
|
133
|
+
olaneError.code = 'NODE_NOT_FOUND';
|
|
134
|
+
olaneError.status = 404;
|
|
135
|
+
}
|
|
136
|
+
else if (error.code === 'TOOL_NOT_FOUND') {
|
|
137
|
+
olaneError.code = 'TOOL_NOT_FOUND';
|
|
138
|
+
olaneError.status = 404;
|
|
139
|
+
}
|
|
140
|
+
else if (error.code === 'INVALID_PARAMS') {
|
|
141
|
+
olaneError.code = 'INVALID_PARAMS';
|
|
142
|
+
olaneError.status = 400;
|
|
143
|
+
}
|
|
144
|
+
else if (error.code === 'TIMEOUT') {
|
|
145
|
+
olaneError.code = 'TIMEOUT';
|
|
146
|
+
olaneError.status = 504;
|
|
147
|
+
}
|
|
148
|
+
else if (error.message?.includes('not found')) {
|
|
149
|
+
olaneError.code = 'NODE_NOT_FOUND';
|
|
150
|
+
olaneError.status = 404;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
olaneError.code = 'EXECUTION_ERROR';
|
|
154
|
+
olaneError.status = 500;
|
|
155
|
+
}
|
|
156
|
+
olaneError.details = error.details || error.stack;
|
|
157
|
+
next(olaneError);
|
|
158
|
+
}
|
|
159
|
+
// Server instance
|
|
160
|
+
const instance = {
|
|
161
|
+
app,
|
|
162
|
+
async start() {
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
try {
|
|
165
|
+
server = app.listen(port, () => {
|
|
166
|
+
logger.log(`Server running on http://localhost:${port}${basePath}`);
|
|
167
|
+
resolve();
|
|
168
|
+
});
|
|
169
|
+
server.on('error', reject);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
reject(error);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
async stop() {
|
|
177
|
+
return new Promise((resolve, reject) => {
|
|
178
|
+
if (!server) {
|
|
179
|
+
resolve();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
server.close((err) => {
|
|
183
|
+
if (err) {
|
|
184
|
+
reject(err);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
logger.log('Server stopped');
|
|
188
|
+
resolve();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
return instance;
|
|
195
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAAU;gBAEX,KAAK,GAAE,OAAe;IAIlC,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE;IAIlB,KAAK,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE;IAIpB,QAAQ,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE;CAKxB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export class ServerLogger {
|
|
2
|
+
constructor(debug = false) {
|
|
3
|
+
this.debug = debug;
|
|
4
|
+
}
|
|
5
|
+
log(...args) {
|
|
6
|
+
console.log('[o-server]', ...args);
|
|
7
|
+
}
|
|
8
|
+
error(...args) {
|
|
9
|
+
console.error('[o-server ERROR]', ...args);
|
|
10
|
+
}
|
|
11
|
+
debugLog(...args) {
|
|
12
|
+
if (this.debug) {
|
|
13
|
+
console.log('[o-server DEBUG]', ...args);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.spec.d.ts","sourceRoot":"","sources":["../../test/ai.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { NodeState, oAddress } from '@olane/o-core';
|
|
2
|
+
import { oLaneTool } from '@olane/o-lane';
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
describe('in-process @memory', () => {
|
|
5
|
+
it('should be able to start a single node with no leader', async () => {
|
|
6
|
+
const node = new oLaneTool({
|
|
7
|
+
address: new oAddress('o://test'),
|
|
8
|
+
leader: null,
|
|
9
|
+
parent: null,
|
|
10
|
+
});
|
|
11
|
+
await node.start();
|
|
12
|
+
expect(node.state).to.equal(NodeState.RUNNING);
|
|
13
|
+
const transports = node.transports;
|
|
14
|
+
// expect(transports.length).to.equal(1);
|
|
15
|
+
// expect(transports[0].toString()).to.contain('/memory');
|
|
16
|
+
await node.stop();
|
|
17
|
+
expect(node.state).to.equal(NodeState.STOPPED);
|
|
18
|
+
});
|
|
19
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@olane/o-server",
|
|
3
|
+
"version": "0.7.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/src/index.js",
|
|
6
|
+
"types": "dist/src/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/src/index.d.ts",
|
|
10
|
+
"default": "./dist/src/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist/**/*",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "aegir test",
|
|
20
|
+
"test:node": "aegir test -t node",
|
|
21
|
+
"test:browser": "aegir test -t browser",
|
|
22
|
+
"dev": "DEBUG=o-protocol:* npx tsx src/tests/index.ts",
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"deep:clean": "rm -rf node_modules && rm package-lock.json",
|
|
25
|
+
"start:prod": "node dist/index.js",
|
|
26
|
+
"prepublishOnly": "npm run build",
|
|
27
|
+
"update:lib": "npm install @olane/o-core@latest",
|
|
28
|
+
"update:peers": "npm install @olane/o-core@latest @olane/o-config@latest @olane/o-protocol@latest @olane/o-tool@latest --save-peer",
|
|
29
|
+
"lint": "eslint src/**/*.ts"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/olane-labs/olane.git"
|
|
34
|
+
},
|
|
35
|
+
"author": "oLane Inc.",
|
|
36
|
+
"license": "ISC",
|
|
37
|
+
"description": "HTTP server entrypoint for Olane OS nodes - exposes node's 'use' functionality via REST API",
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
40
|
+
"@eslint/js": "^9.29.0",
|
|
41
|
+
"@tsconfig/node20": "^20.1.6",
|
|
42
|
+
"@types/cors": "^2.8.17",
|
|
43
|
+
"@types/express": "^4.17.21",
|
|
44
|
+
"@types/jest": "^30.0.0",
|
|
45
|
+
"@types/node": "^20.14.0",
|
|
46
|
+
"@typescript-eslint/eslint-plugin": "^8.34.1",
|
|
47
|
+
"@typescript-eslint/parser": "^8.34.1",
|
|
48
|
+
"aegir": "^47.0.21",
|
|
49
|
+
"eslint": "^9.29.0",
|
|
50
|
+
"eslint-config-prettier": "^10.1.6",
|
|
51
|
+
"eslint-plugin-prettier": "^5.5.0",
|
|
52
|
+
"globals": "^16.2.0",
|
|
53
|
+
"jest": "^30.0.0",
|
|
54
|
+
"prettier": "^3.5.3",
|
|
55
|
+
"ts-jest": "^29.4.0",
|
|
56
|
+
"ts-node": "^10.9.2",
|
|
57
|
+
"tsconfig-paths": "^4.2.0",
|
|
58
|
+
"tsx": "^4.20.3",
|
|
59
|
+
"typescript": "5.4.5"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"@olane/o-config": "^0.7.1",
|
|
63
|
+
"@olane/o-core": "^0.7.1",
|
|
64
|
+
"@olane/o-protocol": "^0.7.1",
|
|
65
|
+
"@olane/o-tool": "^0.7.1"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"cors": "^2.8.5",
|
|
69
|
+
"debug": "^4.4.1",
|
|
70
|
+
"dotenv": "^16.5.0",
|
|
71
|
+
"express": "^4.19.2"
|
|
72
|
+
}
|
|
73
|
+
}
|