api-ape 2.2.3 → 3.0.0

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/index.d.ts CHANGED
@@ -50,7 +50,7 @@ export interface ControllerContext {
50
50
  os: { name?: string; version?: string }
51
51
  device: { type?: string; vendor?: string; model?: string }
52
52
  }
53
- /** Custom embedded values from onConnent */
53
+ /** Custom embedded values from onConnect */
54
54
  [key: string]: any
55
55
  }
56
56
 
@@ -63,7 +63,7 @@ export type ControllerFunction<T = any, R = any> = (
63
63
  ) => R | Promise<R>
64
64
 
65
65
  /**
66
- * Send function provided to onConnent
66
+ * Send function provided to onConnect
67
67
  */
68
68
  export type SendFunction = {
69
69
  (type: string, data: any): void
@@ -76,7 +76,7 @@ export type SendFunction = {
76
76
  export type AfterHook = (err: Error | null, result: any) => void
77
77
 
78
78
  /**
79
- * Connection lifecycle hooks returned from onConnent
79
+ * Connection lifecycle hooks returned from onConnect
80
80
  */
81
81
  export interface ConnectionHandlers {
82
82
  /** Values to embed into controller context */
@@ -88,11 +88,11 @@ export interface ConnectionHandlers {
88
88
  /** Called on error */
89
89
  onError?: (errorString: string) => void
90
90
  /** Called when client disconnects */
91
- onDisconnent?: () => void
91
+ onDisconnect?: () => void
92
92
  }
93
93
 
94
94
  /**
95
- * onConnent callback signature
95
+ * onConnect callback signature
96
96
  */
97
97
  export type OnConnectCallback = (
98
98
  socket: ApeWebSocket,
@@ -107,36 +107,225 @@ export interface ApeServerOptions {
107
107
  /** Directory containing controller files */
108
108
  where: string
109
109
  /** Connection lifecycle hook */
110
- onConnent?: OnConnectCallback
110
+ onConnect?: OnConnectCallback
111
+ }
112
+
113
+ // =============================================================================
114
+ // 🌲 FOREST - DISTRIBUTED MESH TYPES
115
+ // =============================================================================
116
+
117
+ /**
118
+ * Supported database client types for Forest adapters
119
+ */
120
+ export type ForestDatabaseClient =
121
+ | RedisClient
122
+ | MongoClient
123
+ | PostgresPool
124
+ | SupabaseClient
125
+ | FirebaseDatabase
126
+ | ForestCustomAdapter
127
+
128
+ /** Redis client (node-redis or ioredis) */
129
+ export interface RedisClient {
130
+ duplicate(): RedisClient
131
+ publish(channel: string, message: string): Promise<number>
132
+ subscribe(channel: string): Promise<void>
133
+ on(event: string, handler: (...args: any[]) => void): void
134
+ }
135
+
136
+ /** MongoDB client */
137
+ export interface MongoClient {
138
+ db(name?: string): any
139
+ }
140
+
141
+ /** PostgreSQL pool (pg) */
142
+ export interface PostgresPool {
143
+ query(text: string, values?: any[]): Promise<any>
144
+ connect(): Promise<any>
145
+ }
146
+
147
+ /** Supabase client */
148
+ export interface SupabaseClient {
149
+ from(table: string): any
150
+ channel(name: string): any
151
+ removeChannel(channel: any): Promise<void>
152
+ }
153
+
154
+ /** Firebase Realtime Database */
155
+ export interface FirebaseDatabase {
156
+ ref(path: string): any
157
+ goOnline?(): void
158
+ app?: any
159
+ }
160
+
161
+ /**
162
+ * Forest adapter instance interface
163
+ */
164
+ export interface ForestAdapterInstance {
165
+ /** This server's unique ID */
166
+ readonly serverId: string
167
+
168
+ /** Join the distributed mesh */
169
+ join(serverId?: string): Promise<void>
170
+
171
+ /** Leave the mesh and cleanup */
172
+ leave(): Promise<void>
173
+
174
+ /** Client-to-server lookup operations */
175
+ lookup: {
176
+ /** Register a client on this server */
177
+ add(clientId: string): Promise<void>
178
+ /** Find which server owns a client */
179
+ read(clientId: string): Promise<string | null>
180
+ /** Remove a client mapping (must own it) */
181
+ remove(clientId: string): Promise<void>
182
+ }
183
+
184
+ /** Inter-server channel operations */
185
+ channels: {
186
+ /** Push message to a server's channel (empty string = broadcast) */
187
+ push(serverId: string, message: any): Promise<void>
188
+ /** Subscribe to a server's channel, returns unsubscribe function */
189
+ pull(serverId: string, handler: (message: any, senderServerId: string) => void): Promise<() => Promise<void>>
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Custom adapter interface for implementing your own Forest adapter
195
+ */
196
+ export interface ForestCustomAdapter {
197
+ join(serverId: string): Promise<void>
198
+ leave(): Promise<void>
199
+ lookup: {
200
+ add(clientId: string): Promise<void>
201
+ read(clientId: string): Promise<string | null>
202
+ remove(clientId: string): Promise<void>
203
+ }
204
+ channels: {
205
+ push(serverId: string, message: any): Promise<void>
206
+ pull(serverId: string, handler: (message: any, senderServerId: string) => void): Promise<() => Promise<void>>
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Options for joinVia()
212
+ */
213
+ export interface ForestOptions {
214
+ /** Prefix for keys/tables (default: 'ape') */
215
+ namespace?: string
216
+ /** Custom server ID (default: auto-generated) */
217
+ serverId?: string
111
218
  }
112
219
 
113
220
  /**
114
221
  * Initialize api-ape on a Node.js HTTP/HTTPS server
222
+ *
223
+ * V3 Breaking Change:
224
+ * Old: const ape = require('api-ape')
225
+ * New: const { ape } = require('api-ape')
115
226
  */
116
227
  declare function ape(server: HttpServer, options: ApeServerOptions): void
117
228
 
118
229
  declare namespace ape {
119
230
  /** Broadcast to all connected clients */
120
231
  export function broadcast(type: string, data: any, excludeClientId?: string): void
121
- /** Get count of connected clients */
122
- export function online(): number
123
- /** Get all connected client clientIds */
124
- export function getClients(): string[]
232
+
233
+ /**
234
+ * Read-only Map of connected clients
235
+ * Each ClientWrapper provides: clientId, sessionId, embed, agent, sendTo(type, data)
236
+ */
237
+ export const clients: ReadonlyMap<string, ClientWrapper>
238
+
239
+ // =========================================================================
240
+ // 🌲 FOREST - DISTRIBUTED MESH
241
+ // =========================================================================
242
+
243
+ /**
244
+ * Join the distributed mesh for multi-server coordination.
245
+ * Pass any supported database client - APE auto-detects the type.
246
+ *
247
+ * @example
248
+ * // Redis
249
+ * ape.joinVia(redisClient);
250
+ *
251
+ * // With options
252
+ * ape.joinVia(mongoClient, { namespace: 'myapp', serverId: 'srv-1' });
253
+ *
254
+ * // Custom adapter
255
+ * ape.joinVia({ join, leave, lookup, channels });
256
+ */
257
+ export function joinVia(client: ForestDatabaseClient, options?: ForestOptions): Promise<ForestAdapterInstance>
258
+
259
+ /**
260
+ * Leave the distributed mesh gracefully.
261
+ * Removes all client mappings and unsubscribes from channels.
262
+ */
263
+ export function leaveCluster(): Promise<void>
264
+
265
+ /**
266
+ * Current Forest adapter instance (null if not joined)
267
+ */
268
+ export const cluster: ForestAdapterInstance | null
269
+
270
+ /**
271
+ * This server's ID in the cluster (null if not joined)
272
+ */
273
+ export const serverId: string | null
125
274
 
126
275
  // Browser client methods (available when imported in browser context)
127
276
  /** Subscribe to broadcasts from the server */
128
277
  export function on<T = any>(type: string, handler: (message: { err?: Error; type: string; data: T }) => void): void
129
278
  /** Subscribe to connection state changes. Returns unsubscribe function. */
130
279
  export function onConnectionChange(handler: (state: 'offline' | 'walled' | 'disconnected' | 'connecting' | 'connected') => void): () => void
131
- /** Get current transport type */
132
- export function getTransport(): 'websocket' | 'polling' | null
280
+ /** Current transport type (read-only) */
281
+ export const transport: 'websocket' | 'polling' | null
133
282
 
134
283
  /** Call any server function dynamically (browser only) */
135
284
  export function message<T = any, R = any>(data?: T): Promise<R>
136
285
  }
137
286
 
138
- // Server-side default export (also works as browser client proxy)
139
- export default ape
287
+ // =============================================================================
288
+ // SERVER-SIDE CLIENT (api - same interface as browser)
289
+ // =============================================================================
290
+
291
+ /**
292
+ * Server-side client for connecting to other api-ape servers
293
+ * 100% identical interface to the browser client
294
+ *
295
+ * @example
296
+ * import api from 'api-ape'
297
+ *
298
+ * // Configure connection (or set APE_SERVER env)
299
+ * api.connect('ws://other-server:3000/api/ape')
300
+ *
301
+ * // Same usage as browser
302
+ * const result = await api.hello('World')
303
+ * api.on('message', (data) => console.log(data))
304
+ */
305
+ export interface ApeServerClient extends ApeSender {
306
+ /** Subscribe to broadcasts from the remote server */
307
+ on<T = any>(type: string, handler: MessageHandler<T>): void
308
+ on(handler: MessageHandler): void
309
+ /** Subscribe to connection state changes */
310
+ onConnectionChange(handler: (state: ConnectionState) => void): () => void
311
+ /** Connect to a server (or set APE_SERVER env) */
312
+ connect(url: string): void
313
+ /** Close the connection */
314
+ close(): void
315
+ /** Current transport type (read-only) */
316
+ readonly transport: 'websocket' | null
317
+ }
318
+
319
+ /**
320
+ * The api client - works identically on browser and server
321
+ */
322
+ declare const api: ApeServerClient
323
+
324
+ // Named export for V3 (Server usage: const { ape } = require('api-ape'))
325
+ export { ape, api }
326
+
327
+ // Default export: api client (same interface on browser and server)
328
+ export default api
140
329
 
141
330
  // =============================================================================
142
331
  // BROWSER CLIENT (Default export in browser context)
@@ -161,8 +350,8 @@ export interface ApeBrowserClient extends ApeSender {
161
350
  /** Subscribe to connection state changes. Returns unsubscribe function. */
162
351
  onConnectionChange(handler: (state: ConnectionState) => void): () => void
163
352
 
164
- /** Get current transport type ('websocket' | 'polling' | null) */
165
- getTransport(): TransportType | null
353
+ /** Current transport type (read-only) */
354
+ readonly transport: TransportType | null
166
355
  }
167
356
 
168
357
  // =============================================================================
@@ -221,11 +410,11 @@ export type SetOnReceiver = {
221
410
  */
222
411
  export interface ApeClient {
223
412
  sender: ApeSender
224
- setOnReciver: SetOnReceiver
413
+ setOnReceiver: SetOnReceiver
225
414
  /** Subscribe to connection state changes. Returns unsubscribe function. */
226
415
  onConnectionChange: (handler: (state: ConnectionState) => void) => () => void
227
- /** Get current transport type ('websocket' | 'polling' | null) */
228
- getTransport: () => TransportType | null
416
+ /** Current transport type (read-only) */
417
+ readonly transport: TransportType | null
229
418
  }
230
419
 
231
420
  /**
@@ -256,5 +445,24 @@ export { connectSocket }
256
445
  // =============================================================================
257
446
 
258
447
  export declare const broadcast: (type: string, data: any) => void
259
- export declare const online: () => number
260
- export declare const getClients: () => string[]
448
+ export declare const clients: ReadonlyMap<string, ClientWrapper>
449
+
450
+ /**
451
+ * Client wrapper providing client info and sendTo function
452
+ */
453
+ export interface ClientWrapper {
454
+ /** Unique client identifier */
455
+ readonly clientId: string
456
+ /** Session ID from cookie (set by outer framework) */
457
+ readonly sessionId: string | null
458
+ /** Embedded values from onConnect */
459
+ readonly embed: Record<string, any>
460
+ /** Parsed user-agent info */
461
+ readonly agent: {
462
+ browser: { name?: string; version?: string }
463
+ os: { name?: string; version?: string }
464
+ device: { type?: string; vendor?: string; model?: string }
465
+ }
466
+ /** Send a message to this specific client */
467
+ sendTo(type: string, data: any): void
468
+ }
package/index.js CHANGED
@@ -1,11 +1,26 @@
1
+ /**
2
+ * api-ape unified entry point
3
+ *
4
+ * V3 Server Usage:
5
+ * const api = require('api-ape') // Client factory (default)
6
+ * const { ape } = require('api-ape') // Server initializer (named)
7
+ * import api, { ape } from 'api-ape' // ESM both
8
+ *
9
+ * Browser Usage:
10
+ * import api from 'api-ape' // Auto-connecting client
11
+ */
1
12
 
2
13
  let apiApe;
3
14
 
4
15
  if ('undefined' === typeof window
5
16
  || 'undefined' === typeof window.document) {
17
+ // Server environment - exports: api (default), ape, broadcast, clients, createClient
6
18
  apiApe = require('./server');
7
19
  } else {
20
+ // Browser environment - client module has its own exports
8
21
  apiApe = require('./client');
9
22
  }
10
23
 
11
24
  module.exports = apiApe
25
+
26
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api-ape",
3
- "version": "2.2.3",
3
+ "version": "3.0.0",
4
4
  "description": "Remote Procedure Events (RPE) - A lightweight WebSocket framework for building real-time APIs. Call server functions from the browser like local methods with automatic reconnection, HTTP streaming fallback, and extended JSON encoding.",
5
5
  "main": "index.js",
6
6
  "browser": "./client/index.js",
@@ -69,4 +69,4 @@
69
69
  "esbuild": "^0.27.2",
70
70
  "jest": "^29.3.1"
71
71
  }
72
- }
72
+ }