ai-functions 0.4.0 → 2.0.1

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.
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Local RPC target for in-memory implementations
3
+ *
4
+ * Allows using the same RPC semantics (including promise pipelining)
5
+ * for local in-memory implementations, useful for:
6
+ * - Testing
7
+ * - Client-side caching
8
+ * - Offline support
9
+ * - Development without a server
10
+ */
11
+ import { RpcTarget } from 'capnweb';
12
+ /**
13
+ * Create a local RPC target that can be used with the same API
14
+ * as remote targets, but executes locally.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * class MyLocalAPI extends RpcTarget {
19
+ * async getData(id: string) {
20
+ * return this.localStore.get(id)
21
+ * }
22
+ * }
23
+ *
24
+ * const api = createLocalTarget(new MyLocalAPI())
25
+ * const data = await api.getData('123') // Executes locally
26
+ * ```
27
+ */
28
+ export declare function createLocalTarget<T extends RpcTarget>(target: T): T;
29
+ /**
30
+ * Base class for local implementations that mirror remote APIs
31
+ */
32
+ export declare abstract class LocalTarget extends RpcTarget {
33
+ /**
34
+ * Override to provide initialization logic
35
+ */
36
+ initialize(): Promise<void>;
37
+ /**
38
+ * Override to provide cleanup logic
39
+ */
40
+ dispose(): Promise<void>;
41
+ }
42
+ //# sourceMappingURL=local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/rpc/local.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAEnC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,SAAS,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAInE;AAED;;GAEG;AACH,8BAAsB,WAAY,SAAQ,SAAS;IACjD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Local RPC target for in-memory implementations
3
+ *
4
+ * Allows using the same RPC semantics (including promise pipelining)
5
+ * for local in-memory implementations, useful for:
6
+ * - Testing
7
+ * - Client-side caching
8
+ * - Offline support
9
+ * - Development without a server
10
+ */
11
+ import { RpcTarget } from 'capnweb';
12
+ /**
13
+ * Create a local RPC target that can be used with the same API
14
+ * as remote targets, but executes locally.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * class MyLocalAPI extends RpcTarget {
19
+ * async getData(id: string) {
20
+ * return this.localStore.get(id)
21
+ * }
22
+ * }
23
+ *
24
+ * const api = createLocalTarget(new MyLocalAPI())
25
+ * const data = await api.getData('123') // Executes locally
26
+ * ```
27
+ */
28
+ export function createLocalTarget(target) {
29
+ // For local targets, we can return the target directly
30
+ // since capnweb's RpcTarget already handles method calls properly
31
+ return target;
32
+ }
33
+ /**
34
+ * Base class for local implementations that mirror remote APIs
35
+ */
36
+ export class LocalTarget extends RpcTarget {
37
+ /**
38
+ * Override to provide initialization logic
39
+ */
40
+ async initialize() {
41
+ // Default: no initialization needed
42
+ }
43
+ /**
44
+ * Override to provide cleanup logic
45
+ */
46
+ async dispose() {
47
+ // Default: no cleanup needed
48
+ }
49
+ }
50
+ //# sourceMappingURL=local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/rpc/local.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAEnC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,iBAAiB,CAAsB,MAAS;IAC9D,uDAAuD;IACvD,kEAAkE;IAClE,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;GAEG;AACH,MAAM,OAAgB,WAAY,SAAQ,SAAS;IACjD;;OAEG;IACH,KAAK,CAAC,UAAU;QACd,oCAAoC;IACtC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO;QACX,6BAA6B;IAC/B,CAAC;CACF"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Hono RPC Server Handlers
3
+ *
4
+ * Provides RPC endpoints for:
5
+ * - HTTP batch requests (POST /rpc)
6
+ * - WebSocket persistent connections (GET /rpc/ws)
7
+ * - postMessage bridge for iframe communication (GET /rpc/bridge)
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ import type { Context, Hono, MiddlewareHandler } from 'hono';
12
+ import type { WSContext } from 'hono/ws';
13
+ /**
14
+ * RPC method handler function
15
+ */
16
+ export type RPCMethod<TContext = unknown> = (ctx: TContext, ...args: unknown[]) => unknown | Promise<unknown>;
17
+ /**
18
+ * Collection of RPC methods
19
+ */
20
+ export type RPCMethods<TContext = unknown> = Record<string, RPCMethod<TContext>>;
21
+ /**
22
+ * Options for creating an RPC handler
23
+ */
24
+ export interface RPCHandlerOptions<TContext = unknown> {
25
+ /** Available RPC methods */
26
+ methods: RPCMethods<TContext>;
27
+ /** Create context from request (for auth, etc) */
28
+ createContext?: (c: Context) => TContext | Promise<TContext>;
29
+ /** Called before each method invocation */
30
+ onCall?: (method: string, params: unknown[], ctx: TContext) => void | Promise<void>;
31
+ /** Called after each method invocation */
32
+ onResult?: (method: string, result: unknown, ctx: TContext) => void | Promise<void>;
33
+ /** Called on errors */
34
+ onError?: (method: string, error: Error, ctx: TContext) => void | Promise<void>;
35
+ /** Allowed origins for CORS (default: '*') */
36
+ allowedOrigins?: string[] | '*';
37
+ /** Allowed origins for postMessage bridge */
38
+ bridgeOrigins?: string[];
39
+ }
40
+ /**
41
+ * Create HTTP batch RPC handler
42
+ */
43
+ export declare function createHTTPHandler<TContext>(options: RPCHandlerOptions<TContext>): MiddlewareHandler;
44
+ /**
45
+ * Create WebSocket RPC handler for Hono
46
+ *
47
+ * Usage with hono/ws:
48
+ * ```ts
49
+ * import { upgradeWebSocket } from 'hono/cloudflare-workers' // or hono/bun, etc
50
+ *
51
+ * app.get('/rpc/ws', upgradeWebSocket(createWSHandler({ methods })))
52
+ * ```
53
+ */
54
+ export declare function createWSHandler<TContext>(options: RPCHandlerOptions<TContext>): (c: Context) => {
55
+ onOpen(_evt: Event, ws: WSContext): Promise<void>;
56
+ onMessage(evt: MessageEvent, ws: WSContext): Promise<void>;
57
+ onClose(_evt: CloseEvent, _ws: WSContext): void;
58
+ onError(evt: Event, _ws: WSContext): void;
59
+ };
60
+ /**
61
+ * Generate the postMessage bridge HTML/JS
62
+ *
63
+ * This creates a lightweight page that:
64
+ * 1. Listens for postMessage from parent window
65
+ * 2. Makes authenticated fetch/WS calls to the RPC endpoint
66
+ * 3. Posts results back to parent
67
+ */
68
+ export declare function createBridgeHandler<TContext>(options: RPCHandlerOptions<TContext> & {
69
+ /** RPC endpoint URL (default: '/rpc') */
70
+ rpcUrl?: string;
71
+ /** WebSocket URL (default: derived from rpcUrl) */
72
+ wsUrl?: string;
73
+ }): MiddlewareHandler;
74
+ /**
75
+ * Create a complete RPC middleware that handles all transports
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * import { Hono } from 'hono'
80
+ * import { upgradeWebSocket } from 'hono/cloudflare-workers'
81
+ * import { createRPCMiddleware } from 'ai-functions/rpc'
82
+ *
83
+ * const app = new Hono()
84
+ *
85
+ * const rpc = createRPCMiddleware({
86
+ * methods: {
87
+ * greet: (ctx, name) => `Hello, ${name}!`,
88
+ * getUser: async (ctx, id) => db.users.get(id),
89
+ * },
90
+ * createContext: (c) => ({
91
+ * user: c.get('user'),
92
+ * db: c.get('db'),
93
+ * }),
94
+ * bridgeOrigins: ['https://myapp.com', 'https://app.myapp.com'],
95
+ * })
96
+ *
97
+ * // Single /rpc endpoint for both HTTP POST and WebSocket GET
98
+ * app.post('/rpc', rpc.http)
99
+ * app.get('/rpc', upgradeWebSocket(rpc.ws))
100
+ *
101
+ * // postMessage bridge HTML (embed in iframe for cross-origin auth)
102
+ * app.get('/rpc.html', rpc.bridge)
103
+ * ```
104
+ */
105
+ export declare function createRPCMiddleware<TContext>(options: RPCHandlerOptions<TContext> & {
106
+ rpcUrl?: string;
107
+ wsUrl?: string;
108
+ }): {
109
+ /** HTTP batch handler */
110
+ http: MiddlewareHandler;
111
+ /** WebSocket handler (use with upgradeWebSocket) */
112
+ ws: (c: Context) => {
113
+ onOpen(_evt: Event, ws: WSContext): Promise<void>;
114
+ onMessage(evt: MessageEvent, ws: WSContext): Promise<void>;
115
+ onClose(_evt: CloseEvent, _ws: WSContext): void;
116
+ onError(evt: Event, _ws: WSContext): void;
117
+ };
118
+ /** postMessage bridge handler */
119
+ bridge: MiddlewareHandler;
120
+ /** The methods for direct access */
121
+ methods: RPCMethods<TContext>;
122
+ };
123
+ /**
124
+ * Mount all RPC routes on a Hono app
125
+ *
126
+ * Single endpoint handles both HTTP POST and WebSocket upgrade:
127
+ * - POST /rpc → HTTP batch requests
128
+ * - GET /rpc (with Upgrade header) → WebSocket connection
129
+ * - GET /rpc.html → postMessage bridge for iframe embedding
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * import { Hono } from 'hono'
134
+ * import { upgradeWebSocket } from 'hono/cloudflare-workers'
135
+ * import { mountRPC } from 'ai-functions/rpc'
136
+ *
137
+ * const app = new Hono()
138
+ *
139
+ * mountRPC(app, '/rpc', {
140
+ * methods: { ... },
141
+ * upgradeWebSocket,
142
+ * })
143
+ * ```
144
+ */
145
+ export declare function mountRPC<TContext>(app: Hono, basePath: string, options: RPCHandlerOptions<TContext> & {
146
+ rpcUrl?: string;
147
+ wsUrl?: string;
148
+ /** WebSocket upgrade function from hono adapter */
149
+ upgradeWebSocket?: (handler: ReturnType<typeof createWSHandler>) => MiddlewareHandler;
150
+ }): {
151
+ /** HTTP batch handler */
152
+ http: MiddlewareHandler;
153
+ /** WebSocket handler (use with upgradeWebSocket) */
154
+ ws: (c: Context) => {
155
+ onOpen(_evt: Event, ws: WSContext): Promise<void>;
156
+ onMessage(evt: MessageEvent, ws: WSContext): Promise<void>;
157
+ onClose(_evt: CloseEvent, _ws: WSContext): void;
158
+ onError(evt: Event, _ws: WSContext): void;
159
+ };
160
+ /** postMessage bridge handler */
161
+ bridge: MiddlewareHandler;
162
+ /** The methods for direct access */
163
+ methods: RPCMethods<TContext>;
164
+ };
165
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/rpc/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,405 @@
1
+ /**
2
+ * Hono RPC Server Handlers
3
+ *
4
+ * Provides RPC endpoints for:
5
+ * - HTTP batch requests (POST /rpc)
6
+ * - WebSocket persistent connections (GET /rpc/ws)
7
+ * - postMessage bridge for iframe communication (GET /rpc/bridge)
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ import { applyChain } from './deferred.js';
12
+ // =============================================================================
13
+ // HTTP Batch Handler
14
+ // =============================================================================
15
+ /**
16
+ * Process a single RPC call
17
+ */
18
+ async function processCall(call, methods, ctx, options) {
19
+ const { method, params = [], chain, id } = call;
20
+ if (!method) {
21
+ return {
22
+ id,
23
+ type: 'error',
24
+ error: { message: 'Method name required', code: 'INVALID_REQUEST' },
25
+ };
26
+ }
27
+ const handler = methods[method];
28
+ if (!handler) {
29
+ return {
30
+ id,
31
+ type: 'error',
32
+ error: { message: `Unknown method: ${method}`, code: 'METHOD_NOT_FOUND' },
33
+ };
34
+ }
35
+ try {
36
+ // Pre-call hook
37
+ await options.onCall?.(method, params, ctx);
38
+ // Execute the method
39
+ let result = await handler(ctx, ...params);
40
+ // Apply chain operations (promise pipelining)
41
+ if (chain && chain.length > 0) {
42
+ result = applyChain(result, chain);
43
+ }
44
+ // Post-call hook
45
+ await options.onResult?.(method, result, ctx);
46
+ return { id, type: 'result', result };
47
+ }
48
+ catch (error) {
49
+ const err = error instanceof Error ? error : new Error(String(error));
50
+ await options.onError?.(method, err, ctx);
51
+ return {
52
+ id,
53
+ type: 'error',
54
+ error: {
55
+ message: err.message,
56
+ code: 'INTERNAL_ERROR',
57
+ stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
58
+ },
59
+ };
60
+ }
61
+ }
62
+ /**
63
+ * Create HTTP batch RPC handler
64
+ */
65
+ export function createHTTPHandler(options) {
66
+ const { methods, createContext, allowedOrigins = '*' } = options;
67
+ return async (c) => {
68
+ // Handle CORS
69
+ const origin = c.req.header('origin');
70
+ if (allowedOrigins === '*') {
71
+ c.header('Access-Control-Allow-Origin', '*');
72
+ }
73
+ else if (origin && allowedOrigins.includes(origin)) {
74
+ c.header('Access-Control-Allow-Origin', origin);
75
+ }
76
+ c.header('Access-Control-Allow-Methods', 'POST, OPTIONS');
77
+ c.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
78
+ if (c.req.method === 'OPTIONS') {
79
+ return new Response(null, { status: 204 });
80
+ }
81
+ // Parse request body
82
+ let calls;
83
+ try {
84
+ const body = await c.req.json();
85
+ calls = Array.isArray(body) ? body : [body];
86
+ }
87
+ catch {
88
+ return c.json({ error: { message: 'Invalid JSON', code: 'PARSE_ERROR' } }, 400);
89
+ }
90
+ // Create context
91
+ const ctx = createContext ? await createContext(c) : {};
92
+ // Process all calls
93
+ const results = await Promise.all(calls.map((call) => processCall(call, methods, ctx, options)));
94
+ return c.json(results);
95
+ };
96
+ }
97
+ // =============================================================================
98
+ // WebSocket Handler
99
+ // =============================================================================
100
+ /**
101
+ * Create WebSocket RPC handler for Hono
102
+ *
103
+ * Usage with hono/ws:
104
+ * ```ts
105
+ * import { upgradeWebSocket } from 'hono/cloudflare-workers' // or hono/bun, etc
106
+ *
107
+ * app.get('/rpc/ws', upgradeWebSocket(createWSHandler({ methods })))
108
+ * ```
109
+ */
110
+ export function createWSHandler(options) {
111
+ const { methods, createContext } = options;
112
+ return (c) => {
113
+ let ctx;
114
+ // Server-side callback registry for this connection
115
+ const callbackRegistry = {
116
+ callbacks: new Map(),
117
+ invoke: async (id, args) => {
118
+ const fn = callbackRegistry.callbacks.get(id);
119
+ if (!fn)
120
+ throw new Error(`Callback not found: ${id}`);
121
+ return fn(...args);
122
+ },
123
+ };
124
+ return {
125
+ async onOpen(_evt, ws) {
126
+ ctx = createContext ? await createContext(c) : {};
127
+ },
128
+ async onMessage(evt, ws) {
129
+ let message;
130
+ try {
131
+ message = JSON.parse(evt.data);
132
+ }
133
+ catch {
134
+ ws.send(JSON.stringify({
135
+ type: 'error',
136
+ error: { message: 'Invalid JSON', code: 'PARSE_ERROR' },
137
+ }));
138
+ return;
139
+ }
140
+ // Handle ping
141
+ if (message.type === 'ping') {
142
+ ws.send(JSON.stringify({ type: 'pong' }));
143
+ return;
144
+ }
145
+ // Handle callback response
146
+ if (message.type === 'result' && message.callbackId) {
147
+ // Client is responding to a callback we invoked
148
+ // This would be handled by pending callback promises
149
+ return;
150
+ }
151
+ // Handle RPC call
152
+ if (message.type === 'call') {
153
+ const result = await processCall(message, methods, ctx, options);
154
+ ws.send(JSON.stringify(result));
155
+ return;
156
+ }
157
+ // Handle cancel (for streaming)
158
+ if (message.type === 'cancel') {
159
+ // Could implement stream cancellation here
160
+ return;
161
+ }
162
+ },
163
+ onClose(_evt, _ws) {
164
+ // Cleanup callback registry
165
+ callbackRegistry.callbacks.clear();
166
+ },
167
+ onError(evt, _ws) {
168
+ console.error('WebSocket error:', evt);
169
+ },
170
+ };
171
+ };
172
+ }
173
+ // =============================================================================
174
+ // postMessage Bridge
175
+ // =============================================================================
176
+ /**
177
+ * Generate the postMessage bridge HTML/JS
178
+ *
179
+ * This creates a lightweight page that:
180
+ * 1. Listens for postMessage from parent window
181
+ * 2. Makes authenticated fetch/WS calls to the RPC endpoint
182
+ * 3. Posts results back to parent
183
+ */
184
+ export function createBridgeHandler(options) {
185
+ const { bridgeOrigins = [], rpcUrl = '/rpc', wsUrl } = options;
186
+ // Generate allowed origins check
187
+ const originsCheck = bridgeOrigins.length === 0
188
+ ? 'true' // Allow all in dev
189
+ : `[${bridgeOrigins.map((o) => `'${o}'`).join(',')}].includes(event.origin)`;
190
+ const bridgeScript = `
191
+ <!DOCTYPE html>
192
+ <html>
193
+ <head>
194
+ <title>RPC Bridge</title>
195
+ <script>
196
+ (function() {
197
+ const RPC_URL = '${rpcUrl}';
198
+ const WS_URL = ${wsUrl ? `'${wsUrl}'` : 'null'};
199
+ const pendingRequests = new Map();
200
+ let ws = null;
201
+ let wsReady = false;
202
+ let wsQueue = [];
203
+
204
+ // Connect WebSocket if available
205
+ function connectWS() {
206
+ if (!WS_URL) return;
207
+
208
+ ws = new WebSocket(WS_URL);
209
+
210
+ ws.onopen = () => {
211
+ wsReady = true;
212
+ // Flush queued messages
213
+ wsQueue.forEach(msg => ws.send(JSON.stringify(msg)));
214
+ wsQueue = [];
215
+ };
216
+
217
+ ws.onmessage = (event) => {
218
+ const msg = JSON.parse(event.data);
219
+ const pending = pendingRequests.get(msg.id);
220
+ if (pending) {
221
+ pendingRequests.delete(msg.id);
222
+ pending.source.postMessage({
223
+ type: 'rpc-response',
224
+ id: msg.id,
225
+ result: msg.result,
226
+ error: msg.error
227
+ }, pending.origin);
228
+ }
229
+ };
230
+
231
+ ws.onclose = () => {
232
+ wsReady = false;
233
+ // Reconnect after delay
234
+ setTimeout(connectWS, 1000);
235
+ };
236
+ }
237
+
238
+ // Make HTTP request
239
+ async function httpRequest(calls, source, origin) {
240
+ try {
241
+ const response = await fetch(RPC_URL, {
242
+ method: 'POST',
243
+ headers: { 'Content-Type': 'application/json' },
244
+ credentials: 'include', // Include cookies!
245
+ body: JSON.stringify(calls)
246
+ });
247
+
248
+ const results = await response.json();
249
+
250
+ results.forEach(result => {
251
+ source.postMessage({
252
+ type: 'rpc-response',
253
+ id: result.id,
254
+ result: result.result,
255
+ error: result.error
256
+ }, origin);
257
+ });
258
+ } catch (error) {
259
+ calls.forEach(call => {
260
+ source.postMessage({
261
+ type: 'rpc-response',
262
+ id: call.id,
263
+ error: { message: error.message }
264
+ }, origin);
265
+ });
266
+ }
267
+ }
268
+
269
+ // Handle incoming messages
270
+ window.addEventListener('message', (event) => {
271
+ // Validate origin
272
+ if (!(${originsCheck})) {
273
+ console.warn('RPC Bridge: Rejected message from', event.origin);
274
+ return;
275
+ }
276
+
277
+ const { type, calls, useWebSocket } = event.data;
278
+
279
+ if (type !== 'rpc-request') return;
280
+
281
+ // Store pending requests for response routing
282
+ calls.forEach(call => {
283
+ pendingRequests.set(call.id, {
284
+ source: event.source,
285
+ origin: event.origin
286
+ });
287
+ });
288
+
289
+ // Route to WebSocket or HTTP
290
+ if (useWebSocket && ws && wsReady) {
291
+ calls.forEach(call => ws.send(JSON.stringify(call)));
292
+ } else if (useWebSocket && ws) {
293
+ // Queue for when WS is ready
294
+ calls.forEach(call => wsQueue.push(call));
295
+ } else {
296
+ httpRequest(calls, event.source, event.origin);
297
+ }
298
+ });
299
+
300
+ // Initialize
301
+ connectWS();
302
+
303
+ // Signal ready
304
+ if (window.parent !== window) {
305
+ window.parent.postMessage({ type: 'rpc-bridge-ready' }, '*');
306
+ }
307
+ })();
308
+ </script>
309
+ </head>
310
+ <body></body>
311
+ </html>
312
+ `;
313
+ return async (c) => {
314
+ c.header('Content-Type', 'text/html');
315
+ // Security headers for iframe
316
+ c.header('X-Frame-Options', 'ALLOWALL'); // Allow embedding
317
+ c.header('Content-Security-Policy', "default-src 'self'; script-src 'unsafe-inline'; connect-src 'self' wss://*.apis.do https://*.apis.do");
318
+ return c.html(bridgeScript);
319
+ };
320
+ }
321
+ // =============================================================================
322
+ // Unified Middleware
323
+ // =============================================================================
324
+ /**
325
+ * Create a complete RPC middleware that handles all transports
326
+ *
327
+ * @example
328
+ * ```ts
329
+ * import { Hono } from 'hono'
330
+ * import { upgradeWebSocket } from 'hono/cloudflare-workers'
331
+ * import { createRPCMiddleware } from 'ai-functions/rpc'
332
+ *
333
+ * const app = new Hono()
334
+ *
335
+ * const rpc = createRPCMiddleware({
336
+ * methods: {
337
+ * greet: (ctx, name) => `Hello, ${name}!`,
338
+ * getUser: async (ctx, id) => db.users.get(id),
339
+ * },
340
+ * createContext: (c) => ({
341
+ * user: c.get('user'),
342
+ * db: c.get('db'),
343
+ * }),
344
+ * bridgeOrigins: ['https://myapp.com', 'https://app.myapp.com'],
345
+ * })
346
+ *
347
+ * // Single /rpc endpoint for both HTTP POST and WebSocket GET
348
+ * app.post('/rpc', rpc.http)
349
+ * app.get('/rpc', upgradeWebSocket(rpc.ws))
350
+ *
351
+ * // postMessage bridge HTML (embed in iframe for cross-origin auth)
352
+ * app.get('/rpc.html', rpc.bridge)
353
+ * ```
354
+ */
355
+ export function createRPCMiddleware(options) {
356
+ return {
357
+ /** HTTP batch handler */
358
+ http: createHTTPHandler(options),
359
+ /** WebSocket handler (use with upgradeWebSocket) */
360
+ ws: createWSHandler(options),
361
+ /** postMessage bridge handler */
362
+ bridge: createBridgeHandler(options),
363
+ /** The methods for direct access */
364
+ methods: options.methods,
365
+ };
366
+ }
367
+ /**
368
+ * Mount all RPC routes on a Hono app
369
+ *
370
+ * Single endpoint handles both HTTP POST and WebSocket upgrade:
371
+ * - POST /rpc → HTTP batch requests
372
+ * - GET /rpc (with Upgrade header) → WebSocket connection
373
+ * - GET /rpc.html → postMessage bridge for iframe embedding
374
+ *
375
+ * @example
376
+ * ```ts
377
+ * import { Hono } from 'hono'
378
+ * import { upgradeWebSocket } from 'hono/cloudflare-workers'
379
+ * import { mountRPC } from 'ai-functions/rpc'
380
+ *
381
+ * const app = new Hono()
382
+ *
383
+ * mountRPC(app, '/rpc', {
384
+ * methods: { ... },
385
+ * upgradeWebSocket,
386
+ * })
387
+ * ```
388
+ */
389
+ export function mountRPC(app, basePath, options) {
390
+ const rpc = createRPCMiddleware({
391
+ ...options,
392
+ rpcUrl: options.rpcUrl ?? basePath,
393
+ wsUrl: options.wsUrl ?? basePath.replace(/^http/, 'ws'),
394
+ });
395
+ // HTTP batch (POST)
396
+ app.post(basePath, rpc.http);
397
+ // WebSocket upgrade (GET with Upgrade header)
398
+ if (options.upgradeWebSocket) {
399
+ app.get(basePath, options.upgradeWebSocket(rpc.ws));
400
+ }
401
+ // Bridge HTML page (for iframe embedding)
402
+ app.get(`${basePath}.html`, rpc.bridge);
403
+ return rpc;
404
+ }
405
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/rpc/server.ts"],"names":[],"mappings":"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"}