api-ape 3.0.1 → 3.0.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 +2 -1
- package/client/connectSocket.js +11 -11
- package/client/transports/streaming.js +3 -2
- package/dist/ape.js +1 -1
- package/dist/ape.js.map +2 -2
- package/dist/api-ape.min.js +1 -1
- package/dist/api-ape.min.js.map +2 -2
- package/package.json +2 -2
- package/server/README.md +30 -1
- package/server/adapters/README.md +5 -5
- package/server/client.js +13 -10
- package/server/index.js +69 -15
- package/server/lib/longPolling.js +1 -3
- package/server/socket/receive.js +8 -9
|
@@ -5,15 +5,15 @@ Connect multiple api-ape server instances via a shared database for horizontal s
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
7
|
```js
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const { ape } = require('api-ape')
|
|
9
|
+
const { createClient } = require('redis')
|
|
10
10
|
|
|
11
11
|
// Connect to your database
|
|
12
|
-
const redis = createClient()
|
|
13
|
-
await redis.connect()
|
|
12
|
+
const redis = createClient()
|
|
13
|
+
await redis.connect()
|
|
14
14
|
|
|
15
15
|
// Join the cluster — APE creates its own namespace
|
|
16
|
-
ape.joinVia(redis)
|
|
16
|
+
ape.joinVia(redis)
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
That's it. APE will:
|
package/server/client.js
CHANGED
|
@@ -217,6 +217,12 @@ function onConnectionChange(handler) {
|
|
|
217
217
|
*/
|
|
218
218
|
const handler = {
|
|
219
219
|
get(target, prop) {
|
|
220
|
+
// First check if property exists on target (set via defineProperty)
|
|
221
|
+
// This allows named exports like 'ape' to be accessed directly
|
|
222
|
+
if (Reflect.has(target, prop)) {
|
|
223
|
+
return Reflect.get(target, prop)
|
|
224
|
+
}
|
|
225
|
+
|
|
220
226
|
// Reserved properties - same as browser
|
|
221
227
|
if (prop === 'on') return on
|
|
222
228
|
if (prop === 'onConnectionChange') return onConnectionChange
|
|
@@ -229,18 +235,13 @@ const handler = {
|
|
|
229
235
|
const wrapperFn = function (a, b) {
|
|
230
236
|
let path = joinKey + prop, body
|
|
231
237
|
// Two args: first is path segment (string), second is body
|
|
232
|
-
// One arg: it's the body (
|
|
233
|
-
if (arguments.length === 2) {
|
|
238
|
+
// One arg: it's always the body (matches browser client behavior)
|
|
239
|
+
if (arguments.length === 2 && typeof a === 'string') {
|
|
234
240
|
path += a
|
|
235
241
|
body = b
|
|
236
|
-
} else
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
path += a
|
|
240
|
-
body = undefined
|
|
241
|
-
} else {
|
|
242
|
-
body = a
|
|
243
|
-
}
|
|
242
|
+
} else {
|
|
243
|
+
// Single arg or non-string first arg: treat first arg as body
|
|
244
|
+
body = a
|
|
244
245
|
}
|
|
245
246
|
return queueOrSend(path, body)
|
|
246
247
|
}
|
|
@@ -305,4 +306,6 @@ module.exports.onConnectionChange = onConnectionChange
|
|
|
305
306
|
module.exports.connect = connect
|
|
306
307
|
module.exports.close = close
|
|
307
308
|
module.exports.ConnectionState = ConnectionState
|
|
309
|
+
// Internal: expose queueOrSend for ape dual-purpose function
|
|
310
|
+
module.exports._queueOrSend = queueOrSend
|
|
308
311
|
|
package/server/index.js
CHANGED
|
@@ -1,36 +1,90 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* api-ape server entry point
|
|
3
3
|
*
|
|
4
|
-
* V3 Usage
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* api.on('message', (data) => console.log(data))
|
|
4
|
+
* V3 Usage:
|
|
5
|
+
* const api = require('api-ape') // Get client proxy (default)
|
|
6
|
+
* const { ape } = require('api-ape') // Get server/API function
|
|
8
7
|
*
|
|
9
|
-
*
|
|
8
|
+
* // ESM
|
|
10
9
|
* import api, { ape } from 'api-ape'
|
|
11
|
-
* ape(server, { where: 'api' }) // Start your server
|
|
12
|
-
*
|
|
13
|
-
* // Connect to another server (set APE_SERVER env or call api.connect)
|
|
14
|
-
* api.connect('ws://other-server:3000/api/ape')
|
|
15
|
-
* api.hello('World')
|
|
16
10
|
*
|
|
17
|
-
*
|
|
11
|
+
* Server Setup:
|
|
12
|
+
* ape(server, { where: 'api' }) // First arg is HTTP server → setup
|
|
13
|
+
*
|
|
14
|
+
* API Call:
|
|
15
|
+
* api.ape({ data: 'foo' }) // Calls /ape endpoint
|
|
16
|
+
* // or equivalently:
|
|
17
|
+
* ape({ data: 'foo' }) // Also calls /ape (detects it's not a server)
|
|
18
|
+
*
|
|
19
|
+
* The ape function intelligently detects:
|
|
20
|
+
* - HTTP server (has .listen/.on) → Server setup mode
|
|
21
|
+
* - Anything else → API call to /ape
|
|
18
22
|
*/
|
|
19
23
|
|
|
20
|
-
const
|
|
24
|
+
const serverApe = require('./lib/main')
|
|
21
25
|
const { broadcast, clients } = require('./lib/broadcast')
|
|
22
26
|
const api = require('./client')
|
|
27
|
+
const { _queueOrSend } = require('./client')
|
|
28
|
+
|
|
29
|
+
// Attach broadcast utilities to the serverApe function
|
|
30
|
+
serverApe.broadcast = broadcast
|
|
31
|
+
serverApe.clients = clients
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if value looks like an HTTP server
|
|
35
|
+
*/
|
|
36
|
+
function isHttpServer(val) {
|
|
37
|
+
return val && typeof val === 'object' && (
|
|
38
|
+
typeof val.listen === 'function' ||
|
|
39
|
+
typeof val.on === 'function' ||
|
|
40
|
+
typeof val.address === 'function'
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Dual-purpose ape function:
|
|
46
|
+
* - Called with HTTP server → Setup server
|
|
47
|
+
* - Called with anything else → API call to /ape
|
|
48
|
+
*/
|
|
49
|
+
function ape(firstArg, ...rest) {
|
|
50
|
+
if (isHttpServer(firstArg)) {
|
|
51
|
+
// Server setup mode
|
|
52
|
+
return serverApe(firstArg, ...rest)
|
|
53
|
+
}
|
|
54
|
+
// API call mode - directly call the internal queueOrSend
|
|
55
|
+
return _queueOrSend('/ape', firstArg)
|
|
56
|
+
}
|
|
23
57
|
|
|
24
|
-
//
|
|
58
|
+
// Copy properties from serverApe to ape
|
|
25
59
|
ape.broadcast = broadcast
|
|
26
60
|
ape.clients = clients
|
|
27
61
|
|
|
28
|
-
//
|
|
62
|
+
// Store original serverApe for direct access if needed
|
|
63
|
+
ape._serverApe = serverApe
|
|
64
|
+
|
|
65
|
+
// Define ape on the proxy's target so it can be destructured
|
|
66
|
+
// The proxy handler checks Reflect.has first, so this will be found
|
|
67
|
+
Object.defineProperty(api, 'ape', {
|
|
68
|
+
value: ape,
|
|
69
|
+
writable: false,
|
|
70
|
+
enumerable: true,
|
|
71
|
+
configurable: false
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Default export: the proxy (so const api = require('api-ape') works)
|
|
29
75
|
module.exports = api
|
|
30
76
|
|
|
31
|
-
//
|
|
77
|
+
// Also export named exports for ESM compatibility
|
|
32
78
|
module.exports.ape = ape
|
|
33
79
|
module.exports.api = api
|
|
80
|
+
module.exports.broadcast = broadcast
|
|
81
|
+
module.exports.clients = clients
|
|
82
|
+
module.exports.default = api
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
34
88
|
|
|
35
89
|
|
|
36
90
|
|
|
@@ -201,9 +201,7 @@ function createLongPollingHandler(controllers, onConnect, fileTransfer) {
|
|
|
201
201
|
req,
|
|
202
202
|
broadcast: (t, d) => broadcast(t, d),
|
|
203
203
|
broadcastOthers: (t, d) => broadcast(t, d, clientId),
|
|
204
|
-
|
|
205
|
-
online: () => clients.size,
|
|
206
|
-
getClients: () => Array.from(clients.keys())
|
|
204
|
+
clients
|
|
207
205
|
}
|
|
208
206
|
|
|
209
207
|
// Execute controller
|
package/server/socket/receive.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const messageHash = require('../../utils/messageHash')
|
|
2
|
-
const { broadcast,
|
|
2
|
+
const { broadcast, clients } = require('../lib/broadcast')
|
|
3
3
|
const jss = require('../../utils/jss')
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -135,7 +135,7 @@ function getSessionId(req) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
module.exports = function receiveHandler(ape) {
|
|
138
|
-
const { send, checkReply, events, controllers, sharedValues,
|
|
138
|
+
const { send, checkReply, events, controllers, sharedValues, clientId, embedValues, fileTransfer } = ape
|
|
139
139
|
|
|
140
140
|
// Extract sessionId from request cookies (set by outer framework session management)
|
|
141
141
|
const sessionId = getSessionId(sharedValues.req)
|
|
@@ -147,10 +147,9 @@ module.exports = function receiveHandler(ape) {
|
|
|
147
147
|
...embedValues,
|
|
148
148
|
// api-ape utilities available via `this`
|
|
149
149
|
broadcast: (type, data) => broadcast(type, data),
|
|
150
|
-
broadcastOthers: (type, data) => broadcast(type, data,
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
hostId,
|
|
150
|
+
broadcastOthers: (type, data) => broadcast(type, data, clientId), // exclude self
|
|
151
|
+
clients,
|
|
152
|
+
clientId,
|
|
154
153
|
sessionId // Session ID from cookie (set by outer framework)
|
|
155
154
|
}
|
|
156
155
|
|
|
@@ -181,7 +180,7 @@ module.exports = function receiveHandler(ape) {
|
|
|
181
180
|
// Wait for all uploads
|
|
182
181
|
try {
|
|
183
182
|
await Promise.all(uploadTags.map(async ({ path, hash }) => {
|
|
184
|
-
const uploadData = await fileTransfer.registerUpload(queryId, hash,
|
|
183
|
+
const uploadData = await fileTransfer.registerUpload(queryId, hash, clientId)
|
|
185
184
|
setValueAtPath(processedData, path, uploadData)
|
|
186
185
|
}))
|
|
187
186
|
} catch (uploadErr) {
|
|
@@ -200,7 +199,7 @@ module.exports = function receiveHandler(ape) {
|
|
|
200
199
|
if (fileTags.length > 0) {
|
|
201
200
|
console.log(`📁 Registering ${fileTags.length} streaming file(s) for ${type}`)
|
|
202
201
|
fileTags.forEach(({ hash }) => {
|
|
203
|
-
fileTransfer.registerStreamingFile(hash,
|
|
202
|
+
fileTransfer.registerStreamingFile(hash, clientId)
|
|
204
203
|
})
|
|
205
204
|
}
|
|
206
205
|
}
|
|
@@ -233,7 +232,7 @@ module.exports = function receiveHandler(ape) {
|
|
|
233
232
|
|
|
234
233
|
} catch (err) {
|
|
235
234
|
const errMessage = err.message || err
|
|
236
|
-
events.onError(
|
|
235
|
+
events.onError(clientId, queryId, errMessage)
|
|
237
236
|
} // END catch
|
|
238
237
|
|
|
239
238
|
} // END onReceive
|