api-ape 1.0.1 → 1.1.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/README.md CHANGED
@@ -1,53 +1,25 @@
1
1
  # 🦍 api-ape
2
2
 
3
- **Remote Procedure Events (RPE)** — A lightweight WebSocket framework for real-time APIs.
3
+ [![npm version](https://img.shields.io/npm/v/api-ape.svg)](https://www.npmjs.com/package/api-ape)
4
+ [![license](https://img.shields.io/npm/l/api-ape.svg)](https://github.com/codemeasandwich/api-ape/blob/main/LICENSE)
5
+ [![GitHub issues](https://img.shields.io/github/issues/codemeasandwich/api-ape)](https://github.com/codemeasandwich/api-ape/issues)
4
6
 
5
- Call server functions from the browser like local methods. Get real-time broadcasts with zero setup.
6
-
7
- ```js
8
- // Client: call server function, get result
9
- const pets = await ape.pets.list()
10
-
11
- // Client: listen for broadcasts
12
- ape.on('newPet', ({ data }) => console.log('New pet:', data))
13
- ```
14
-
15
- file: api/pets/list.js
16
- ```js
17
- // Server: define function, broadcast to others
18
- module.exports = function list() {
19
- return getPetList()
20
- }
21
- ```
22
-
23
- file: api/pets/newPet.js
24
- ```js
25
- // Server: define function, broadcast to others
26
- module.exports = function newPet(data) {
27
- // broadcast to all other clients
28
- this.broadcastOthers('newPet', data)
29
- return savePet(data)
30
- }
31
- ```
32
-
33
- ---
34
-
35
- ## Features
36
-
37
- - **🔌 Auto-wiring** — Drop JS files in a folder, they become API endpoints
38
- - **📡 Real-time** — Built-in broadcast to all or other clients
39
- - **🔄 Reconnection** — Client auto-reconnects on disconnect
40
- - **📦 JJS Encoding** — Supports Date, RegExp, Error, Set, Map, undefined over the wire
41
- - **🎯 Simple API** — Promise-based calls with chainable paths
7
+ **Remote Procedure Events (RPE)** — A lightweight WebSocket framework for building real-time APIs. Call server functions from the browser like local methods. Get real-time broadcasts with zero setup.
42
8
 
43
9
  ---
44
10
 
45
- ## Installation
11
+ ## Install
46
12
 
47
13
  ```bash
48
14
  npm install api-ape
15
+ # or
16
+ pnpm add api-ape
17
+ # or
18
+ yarn add api-ape
49
19
  ```
50
20
 
21
+ **Requirements:** Node.js 14+ (for server), modern browsers (for client)
22
+
51
23
  ---
52
24
 
53
25
  ## Quick Start
@@ -66,9 +38,9 @@ ape(app, { where: 'api' })
66
38
  app.listen(3000)
67
39
  ```
68
40
 
69
- ### Controllers
41
+ ### Create a Controller
70
42
 
71
- Create files in your `api/` folder. Each export becomes an endpoint:
43
+ Drop a file in your `api/` folder it automatically becomes an endpoint:
72
44
 
73
45
  ```js
74
46
  // api/hello.js
@@ -77,24 +49,16 @@ module.exports = function(name) {
77
49
  }
78
50
  ```
79
51
 
80
- ```js
81
- // api/message.js
82
- module.exports = function(data) {
83
- // Broadcast to all OTHER connected clients
84
- this.broadcastOthers('message', data)
85
- return data
86
- }
87
- ```
88
-
89
52
  ### Client (Browser)
90
53
 
91
- Include the bundled client:
54
+ Include the bundled client and start calling:
92
55
 
93
56
  ```html
94
57
  <script src="/api/ape.js"></script>
95
58
  <script>
96
- // Call server functions
97
- ape.hello('World').then(result => console.log(result)) // "Hello, World!"
59
+ // Call server functions like local methods
60
+ const result = await ape.hello('World')
61
+ console.log(result) // "Hello, World!"
98
62
 
99
63
  // Listen for broadcasts
100
64
  ape.on('message', ({ data }) => {
@@ -103,6 +67,19 @@ Include the bundled client:
103
67
  </script>
104
68
  ```
105
69
 
70
+ **That's it!** Your server function is now callable from the browser.
71
+
72
+ ---
73
+
74
+ ## Key Concepts
75
+
76
+ * **Auto-wiring** — Drop JS files in a folder, they become API endpoints automatically
77
+ * **Real-time broadcasts** — Built-in `broadcast()` and `broadcastOthers()` methods for pushing to clients
78
+ * **Promise-based calls** — Chainable paths like `ape.users.list()` map to `api/users/list.js`
79
+ * **Automatic reconnection** — Client auto-reconnects on disconnect with exponential backoff
80
+ * **JJS Encoding** — Extended JSON supporting Date, RegExp, Error, Set, Map, undefined, and circular refs
81
+ * **Connection lifecycle hooks** — Customize behavior on connect, receive, send, error, and disconnect
82
+
106
83
  ---
107
84
 
108
85
  ## API Reference
@@ -115,8 +92,8 @@ Initialize api-ape on an Express app.
115
92
 
116
93
  | Option | Type | Description |
117
94
  |--------|------|-------------|
118
- | `where` | `string` | Directory containing controller files |
119
- | `onConnent` | `function` | Connection lifecycle hook (see below) |
95
+ | `where` | `string` | Directory containing controller files (default: `'api'`) |
96
+ | `onConnent` | `function` | Connection lifecycle hook (see [Connection Lifecycle](#connection-lifecycle)) |
120
97
 
121
98
  #### Controller Context (`this`)
122
99
 
@@ -124,8 +101,8 @@ Inside controller functions, `this` provides:
124
101
 
125
102
  | Property | Description |
126
103
  |----------|-------------|
127
- | `this.broadcast(type, data)` | Send to ALL connected clients |
128
- | `this.broadcastOthers(type, data)` | Send to all EXCEPT the caller |
104
+ | `this.broadcast(type, data)` | Send to **ALL** connected clients |
105
+ | `this.broadcastOthers(type, data)` | Send to all **EXCEPT** the caller |
129
106
  | `this.online()` | Get count of connected clients |
130
107
  | `this.getClients()` | Get array of connected hostIds |
131
108
  | `this.hostId` | Unique ID of the calling client |
@@ -133,17 +110,61 @@ Inside controller functions, `this` provides:
133
110
  | `this.socket` | WebSocket instance |
134
111
  | `this.agent` | Parsed user-agent (browser, OS, device) |
135
112
 
136
- #### Connection Lifecycle
113
+ ### Client
114
+
115
+ #### `ape.<path>.<method>(...args)`
116
+
117
+ Call a server function. Returns a Promise.
118
+
119
+ ```js
120
+ // Calls api/users/list.js
121
+ const users = await ape.users.list()
122
+
123
+ // Calls api/users/create.js with data
124
+ const user = await ape.users.create({ name: 'Alice' })
125
+
126
+ // Nested paths work too
127
+ // ape.admin.users -> api/admin/users.js
128
+ // ape.admin.users.delete -> api/admin/users/delete.js
129
+ await ape.admin.users.delete(userId)
130
+ ```
131
+
132
+ #### `ape.on(type, handler)`
133
+
134
+ Listen for server broadcasts.
135
+
136
+ ```js
137
+ ape.on('notification', ({ data, err, type }) => {
138
+ console.log('Received:', data)
139
+ })
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Configuration
145
+
146
+ ### Default Options
147
+
148
+ ```js
149
+ ape(app, {
150
+ where: 'api', // Controller directory
151
+ onConnent: undefined // Lifecycle hook (optional)
152
+ })
153
+ ```
154
+
155
+ ### Connection Lifecycle Hook
156
+
157
+ Customize behavior per connection:
137
158
 
138
159
  ```js
139
160
  ape(app, {
140
161
  where: 'api',
141
- onConnent(socket, req, send) {
162
+ onConnent(socket, req, hostId) {
142
163
  return {
143
164
  // Embed values into `this` for all controllers
144
165
  embed: {
145
166
  userId: req.session?.userId,
146
- clientId: send + '' // hostId as string
167
+ clientId: String(hostId)
147
168
  },
148
169
 
149
170
  // Before/after hooks
@@ -164,31 +185,81 @@ ape(app, {
164
185
  })
165
186
  ```
166
187
 
167
- ### Client
188
+ ---
168
189
 
169
- #### `ape.<path>.<method>(data)`
190
+ ## Common Recipes
170
191
 
171
- Call a server function. Returns a Promise.
192
+ ### Broadcast to Other Clients
172
193
 
173
194
  ```js
174
- // Calls api/users/list.js
175
- ape.users.list().then(users => ...)
195
+ // api/message.js
196
+ module.exports = function(data) {
197
+ // Broadcast to all OTHER connected clients (not the sender)
198
+ this.broadcastOthers('message', data)
199
+ return { success: true }
200
+ }
201
+ ```
176
202
 
177
- // Calls api/users/create.js with data
178
- ape.users.create({ name: 'Alice' }).then(user => ...)
203
+ ### Broadcast to All Clients
179
204
 
180
- // Nested paths work too
181
- ape.admin.users.delete(userId).then(() => ...)
205
+ ```js
206
+ // api/announcement.js
207
+ module.exports = function(announcement) {
208
+ // Broadcast to ALL connected clients including sender
209
+ this.broadcast('announcement', announcement)
210
+ return { sent: true }
211
+ }
182
212
  ```
183
213
 
184
- #### `ape.on(type, handler)`
214
+ ### Get Online Count
185
215
 
186
- Listen for server broadcasts.
216
+ ```js
217
+ // api/stats.js
218
+ module.exports = function() {
219
+ return {
220
+ online: this.online(),
221
+ clients: this.getClients()
222
+ }
223
+ }
224
+ ```
225
+
226
+ ### Access Request Data
187
227
 
188
228
  ```js
189
- ape.on('notification', ({ data, err, type }) => {
190
- console.log('Received:', data)
191
- })
229
+ // api/profile.js
230
+ module.exports = function() {
231
+ // Access original HTTP request
232
+ const userId = this.req.session?.userId
233
+ const userAgent = this.agent.browser.name
234
+
235
+ return { userId, userAgent }
236
+ }
237
+ ```
238
+
239
+ ### Error Handling
240
+
241
+ ```js
242
+ // api/data.js
243
+ module.exports = async function(id) {
244
+ try {
245
+ const data = await fetchData(id)
246
+ return data
247
+ } catch (err) {
248
+ // Errors are automatically sent to client
249
+ throw new Error(`Failed to fetch: ${err.message}`)
250
+ }
251
+ }
252
+ ```
253
+
254
+ ### Client-Side Error Handling
255
+
256
+ ```js
257
+ try {
258
+ const result = await ape.data.get(id)
259
+ console.log(result)
260
+ } catch (err) {
261
+ console.error('Server error:', err)
262
+ }
192
263
  ```
193
264
 
194
265
  ---
@@ -207,25 +278,177 @@ api-ape uses **JJS (JSON SuperSet)** encoding, which extends JSON to support:
207
278
  | `Map` | ✅ Preserved as Map |
208
279
  | Circular refs | ✅ Handled via pointers |
209
280
 
210
- This is automatic — send a Date, receive a Date.
281
+ This is automatic — send a Date, receive a Date. No configuration needed.
211
282
 
212
283
  ---
213
284
 
214
- ## Examples
285
+ ## Examples & Demos
286
+
287
+ The repository contains working examples:
215
288
 
216
- See the [`example/`](./example) folder:
289
+ * **`example/ExpressJs/`** — Simple real-time chat app
290
+ - Minimal setup with Express.js
291
+ - Broadcast messages to other clients
292
+ - Message history
217
293
 
218
- - **ExpressJs/**Simple chat app with broadcast
219
- - **NextJs/** — Integration with Next.js
294
+ * **`example/NextJs/`**Production-ready chat application
295
+ - Custom Next.js server integration
296
+ - React hooks integration
297
+ - User presence tracking
298
+ - Docker support
299
+ - Connection lifecycle hooks
220
300
 
221
- Run the Express example:
301
+ ### Run an Example
222
302
 
303
+ **ExpressJs:**
223
304
  ```bash
224
305
  cd example/ExpressJs
225
306
  npm install
226
307
  npm start
308
+ # Open http://localhost:3000
309
+ ```
310
+
311
+ **NextJs:**
312
+ ```bash
313
+ cd example/NextJs
314
+ npm install
315
+ npm run dev
316
+ # Open http://localhost:3000
317
+ ```
318
+
319
+ Or with Docker:
320
+ ```bash
321
+ cd example/NextJs
322
+ docker-compose up --build
323
+ ```
324
+
325
+ ---
326
+
327
+ ## Troubleshooting & FAQ
328
+
329
+ ### CORS Errors in Browser
330
+
331
+ Ensure your Express server allows WebSocket connections from your origin. api-ape uses `express-ws` which handles CORS automatically, but verify your Express CORS middleware allows WebSocket upgrade requests.
332
+
333
+ ### Controller Not Found
334
+
335
+ * Check that your controller file is in the `where` directory (default: `api/`)
336
+ * Ensure the file exports a function: `module.exports = function(...) { ... }`
337
+ * File paths map directly: `api/users/list.js` → `ape.users.list()`
338
+
339
+ ### Connection Drops Frequently
340
+
341
+ The client automatically reconnects with exponential backoff. If connections drop often:
342
+ * Check server WebSocket timeout settings
343
+ * Verify network stability
344
+ * Check server logs for errors
345
+
346
+ ### Binary Data / File Transfers
347
+
348
+ api-ape supports transparent binary file transfers. Simply return `Buffer` data from controllers:
349
+
350
+ ```js
351
+ // api/files/download.js
352
+ module.exports = function(filename) {
353
+ return {
354
+ name: filename,
355
+ data: fs.readFileSync(`./uploads/${filename}`) // Buffer
356
+ }
357
+ }
358
+ ```
359
+
360
+ The client receives `ArrayBuffer` automatically:
361
+
362
+ ```js
363
+ const result = await ape.files.download('image.png')
364
+ console.log(result.data) // ArrayBuffer
365
+
366
+ // Display as image
367
+ const blob = new Blob([result.data])
368
+ img.src = URL.createObjectURL(blob)
227
369
  ```
228
370
 
371
+ **Uploads work the same way:**
372
+
373
+ ```js
374
+ // Client
375
+ const arrayBuffer = await file.arrayBuffer()
376
+ await ape.files.upload({ name: file.name, data: arrayBuffer })
377
+
378
+ // Server (api/files/upload.js)
379
+ module.exports = function({ name, data }) {
380
+ fs.writeFileSync(`./uploads/${name}`, data) // data is Buffer
381
+ return { success: true }
382
+ }
383
+ ```
384
+
385
+ Binary data is transferred via temporary HTTP endpoints (`/api/ape/data/:hash`) with session verification and auto-cleanup.
386
+
387
+ ### TypeScript Support
388
+
389
+ Type definitions are included (`index.d.ts`). For full type safety, you may need to:
390
+ * Define interfaces for your controller parameters and return types
391
+ * Use type assertions when calling `ape.<path>.<method>()`
392
+
393
+ ---
394
+
395
+ ## Tests & CI
396
+
397
+ ```bash
398
+ npm test # Run test suite
399
+ npm run test:watch # Watch mode
400
+ npm run test:cover # Coverage report
401
+ ```
402
+
403
+ **Test Commands:**
404
+ - `npm test` — Run all tests
405
+ - `npm run test:watch` — Watch mode for development
406
+ - `npm run test:cover` — Generate coverage report
407
+ - `npm run test:update` — Update snapshots
408
+
409
+ **Supported:** Node.js 14+, modern browsers (Chrome, Firefox, Safari, Edge)
410
+
411
+ ---
412
+
413
+ ## Contributing
414
+
415
+ Contributions welcome! Here's how to help:
416
+
417
+ 1. **Fork the repository**
418
+ 2. **Create a branch:** `git checkout -b feature/your-feature-name`
419
+ 3. **Make your changes** and add tests
420
+ 4. **Run tests:** `npm test`
421
+ 5. **Commit:** Follow conventional commit messages
422
+ 6. **Push and open a PR** with a clear description
423
+
424
+ **Guidelines:**
425
+ * Add tests for new features
426
+ * Keep code style consistent
427
+ * Update documentation if needed
428
+ * Ensure all tests pass
429
+
430
+ ---
431
+
432
+ ## Releases / Changelog
433
+
434
+ Versioning follows [Semantic Versioning](https://semver.org/).
435
+
436
+ **Current version:** See `package.json` or npm registry
437
+
438
+ **Release notes:** Check [GitHub releases](https://github.com/codemeasandwich/api-ape/releases) for detailed changelog.
439
+
440
+ ---
441
+
442
+ ## Security
443
+
444
+ **Reporting vulnerabilities:** Please report security issues via [GitHub Security Advisories](https://github.com/codemeasandwich/api-ape/security/advisories) or email the maintainer.
445
+
446
+ **Security considerations:**
447
+ * Validate all input in controllers
448
+ * Use authentication/authorization in `onConnent` hooks
449
+ * Sanitize data before broadcasting
450
+ * Keep dependencies up to date
451
+
229
452
  ---
230
453
 
231
454
  ## Project Structure
@@ -245,7 +468,7 @@ api-ape/
245
468
  │ │ ├── receive.js # Incoming message handler
246
469
  │ │ └── send.js # Outgoing message handler
247
470
  │ └── security/
248
- │ └── reply.js # Duplicate request protection
471
+ │ └── reply.js # Duplicate request protection
249
472
  ├── utils/
250
473
  │ ├── jss.js # JSON SuperSet encoder/decoder
251
474
  │ └── messageHash.js # Request deduplication
@@ -256,6 +479,14 @@ api-ape/
256
479
 
257
480
  ---
258
481
 
259
- ## License
482
+ ## License & Authors
483
+
484
+ **License:** MIT
485
+
486
+ **Author:** [Brian Shannon](https://github.com/codemeasandwich)
487
+
488
+ **Repository:** [github.com/codemeasandwich/api-ape](https://github.com/codemeasandwich/api-ape)
489
+
490
+ ---
260
491
 
261
- MIT © [Brian Shannon](https://github.com/codemeasandwich)
492
+ **Made with 🦍 by the api-ape community**
package/client/README.md CHANGED
@@ -67,3 +67,35 @@ ape.configure({
67
67
  Default port detection:
68
68
  - Local (`localhost`, `127.0.0.1`): `9010`
69
69
  - Remote: Uses current page port or `443`/`80`
70
+
71
+ ## File Transfers
72
+
73
+ Binary data is automatically handled. The client fetches binary resources and uploads binary data transparently.
74
+
75
+ ### Receiving Binary Data
76
+
77
+ ```js
78
+ // Server returns Buffer, client receives ArrayBuffer
79
+ const result = await ape.files.download('image.png')
80
+ console.log(result.data) // ArrayBuffer
81
+
82
+ // Display as image
83
+ const blob = new Blob([result.data])
84
+ img.src = URL.createObjectURL(blob)
85
+ ```
86
+
87
+ ### Uploading Binary Data
88
+
89
+ ```js
90
+ const file = input.files[0]
91
+ const arrayBuffer = await file.arrayBuffer()
92
+
93
+ // Binary data is uploaded automatically
94
+ await ape.files.upload({
95
+ name: file.name,
96
+ data: arrayBuffer // Sent via HTTP PUT
97
+ })
98
+ ```
99
+
100
+ Binary transfers use `/api/ape/data/:hash` endpoints with session verification.
101
+