@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 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,3 @@
1
+ import { ServerConfig, ServerInstance } from './interfaces/server-config.interface.js';
2
+ export declare function oServer(config: ServerConfig): ServerInstance;
3
+ //# sourceMappingURL=o-server.d.ts.map
@@ -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,8 @@
1
+ export declare class ServerLogger {
2
+ private debug;
3
+ constructor(debug?: boolean);
4
+ log(...args: any[]): void;
5
+ error(...args: any[]): void;
6
+ debugLog(...args: any[]): void;
7
+ }
8
+ //# sourceMappingURL=logger.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ai.spec.d.ts.map
@@ -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
+ }