@webqit/node-live-response 0.1.7 → 0.1.8
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 +120 -40
- package/package.json +3 -5
- package/test/server1.js +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
# LiveResponse for Node.js & Express
|
|
2
2
|
|
|
3
|
-
This package brings **LiveResponse** to traditional Node.js and Express
|
|
3
|
+
This package brings **LiveResponse** to traditional Node.js and Express servers.
|
|
4
4
|
|
|
5
|
-
LiveResponse
|
|
5
|
+
LiveResponse extends the HTTP request/response model with **persistent interactivity**. You send a response — and keep it open as a live communication channel.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Instead of closing after delivery, the response becomes interactive.
|
|
8
|
+
|
|
9
|
+
For the full conceptual model, see the [LiveResponse docs](https://github.com/webqit/fetch-plus?tab=readme-ov-file#section-1-liveresponse).
|
|
10
|
+
|
|
11
|
+
If you’re building a system where live interactivity is a first-class architectural primitive, use **[Webflo](https://github.com/webqit/webflo)**. Live responses are native there.
|
|
12
|
+
This package exists for cases where you want LiveResponse inside an otherwise conventional Node.js or Express backend.
|
|
8
13
|
|
|
9
14
|
---
|
|
10
15
|
|
|
@@ -18,7 +23,7 @@ npm install @webqit/node-live-response
|
|
|
18
23
|
|
|
19
24
|
## One-Time Setup
|
|
20
25
|
|
|
21
|
-
The first thing you do is enable live mode
|
|
26
|
+
The first thing you do is enable live mode on your HTTP server instance. This installs the transport layer and request bookkeeping required for live sessions.
|
|
22
27
|
|
|
23
28
|
### Node.js HTTP server
|
|
24
29
|
|
|
@@ -48,9 +53,12 @@ const liveMode = enableLive(server);
|
|
|
48
53
|
|
|
49
54
|
## Usage
|
|
50
55
|
|
|
51
|
-
The returned `liveMode` function
|
|
56
|
+
The returned `liveMode` function enables live mode per route.
|
|
57
|
+
|
|
58
|
+
It works both as:
|
|
52
59
|
|
|
53
|
-
|
|
60
|
+
* a direct function (Node HTTP)
|
|
61
|
+
* an Express middleware
|
|
54
62
|
|
|
55
63
|
### Node.js HTTP server
|
|
56
64
|
|
|
@@ -59,12 +67,12 @@ async function handler(req, res) {
|
|
|
59
67
|
liveMode(req, res);
|
|
60
68
|
|
|
61
69
|
const liveRes = new LiveResponse('Hello world');
|
|
62
|
-
await res.send(liveRes); // resolves when live
|
|
70
|
+
await res.send(liveRes); // resolves when the live connection is established
|
|
63
71
|
|
|
64
|
-
//
|
|
72
|
+
// ---- interactive phase ----
|
|
65
73
|
|
|
66
74
|
setTimeout(() => {
|
|
67
|
-
res.die();
|
|
75
|
+
res.die(); // explicitly end live mode
|
|
68
76
|
}, 5000);
|
|
69
77
|
}
|
|
70
78
|
```
|
|
@@ -74,9 +82,9 @@ async function handler(req, res) {
|
|
|
74
82
|
```js
|
|
75
83
|
app.get('/counter', liveMode(), async (req, res) => {
|
|
76
84
|
const liveRes = new LiveResponse('Hello world');
|
|
77
|
-
await res.send(liveRes); // resolves when live
|
|
85
|
+
await res.send(liveRes); // resolves when the live connection is established
|
|
78
86
|
|
|
79
|
-
//
|
|
87
|
+
// ---- interactive phase ----
|
|
80
88
|
|
|
81
89
|
setTimeout(() => {
|
|
82
90
|
res.die();
|
|
@@ -86,15 +94,21 @@ app.get('/counter', liveMode(), async (req, res) => {
|
|
|
86
94
|
|
|
87
95
|
---
|
|
88
96
|
|
|
89
|
-
##
|
|
97
|
+
## Interaction Patterns
|
|
90
98
|
|
|
91
99
|
LiveResponse supports three core interaction patterns.
|
|
92
100
|
|
|
101
|
+
---
|
|
102
|
+
|
|
93
103
|
### 1. Live state projection
|
|
94
104
|
|
|
95
|
-
|
|
105
|
+
Send a mutable object as the response body.
|
|
106
|
+
|
|
107
|
+
Mutations on the server automatically propagate to the client.
|
|
96
108
|
|
|
97
|
-
|
|
109
|
+
(More in the [LiveResponse docs](https://github.com/webqit/fetch-plus#1-live-state-projection-via-mutable-response-bodies))
|
|
110
|
+
|
|
111
|
+
On the server:
|
|
98
112
|
|
|
99
113
|
```js
|
|
100
114
|
import { Observer } from '@webqit/observer';
|
|
@@ -103,7 +117,7 @@ app.get('/counter', liveMode(), async (req, res) => {
|
|
|
103
117
|
const state = { count: 0 };
|
|
104
118
|
|
|
105
119
|
const liveRes = new LiveResponse(state);
|
|
106
|
-
await res.send(liveRes);
|
|
120
|
+
await res.send(liveRes);
|
|
107
121
|
|
|
108
122
|
const interval = setInterval(() => {
|
|
109
123
|
Observer.set(state, 'count', state.count + 1);
|
|
@@ -116,7 +130,7 @@ app.get('/counter', liveMode(), async (req, res) => {
|
|
|
116
130
|
});
|
|
117
131
|
```
|
|
118
132
|
|
|
119
|
-
|
|
133
|
+
On the client:
|
|
120
134
|
|
|
121
135
|
```html
|
|
122
136
|
<!doctype html>
|
|
@@ -141,17 +155,41 @@ Then on the client:
|
|
|
141
155
|
</html>
|
|
142
156
|
```
|
|
143
157
|
|
|
158
|
+
> [!TIP]
|
|
159
|
+
> This example can be previewed live by running:
|
|
160
|
+
>
|
|
161
|
+
> ```
|
|
162
|
+
> cd node-live-response
|
|
163
|
+
> node test/server1.js
|
|
164
|
+
> ```
|
|
165
|
+
> _Open localhost:3000 to view_
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
144
169
|
### 2. Response swapping
|
|
145
170
|
|
|
146
|
-
|
|
171
|
+
Replace the current response with a new one — without issuing a new HTTP request.
|
|
172
|
+
|
|
173
|
+
This enables a multi-response architecture over a single request.
|
|
174
|
+
|
|
175
|
+
(More in the [LiveResponse docs](https://github.com/webqit/fetch-plus?tab=readme-ov-file#2-a-multi-response-architecture-via-response-swaps))
|
|
176
|
+
|
|
177
|
+
On the server:
|
|
147
178
|
|
|
148
179
|
```js
|
|
149
180
|
app.get('/news', liveMode(), async (req, res) => {
|
|
150
|
-
const liveRes = new LiveResponse(
|
|
151
|
-
|
|
181
|
+
const liveRes = new LiveResponse(
|
|
182
|
+
{ headline: 'Breaking: Hello World' },
|
|
183
|
+
{ done: false }
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
await res.send(liveRes);
|
|
152
187
|
|
|
153
188
|
setTimeout(() => {
|
|
154
|
-
liveRes.replaceWith(
|
|
189
|
+
liveRes.replaceWith(
|
|
190
|
+
{ headline: 'Update: Still Hello World' },
|
|
191
|
+
{ done: false }
|
|
192
|
+
);
|
|
155
193
|
}, 3_000);
|
|
156
194
|
|
|
157
195
|
setTimeout(() => {
|
|
@@ -164,7 +202,7 @@ app.get('/news', liveMode(), async (req, res) => {
|
|
|
164
202
|
});
|
|
165
203
|
```
|
|
166
204
|
|
|
167
|
-
|
|
205
|
+
On the client:
|
|
168
206
|
|
|
169
207
|
```html
|
|
170
208
|
<!doctype html>
|
|
@@ -187,14 +225,27 @@ Then on the client:
|
|
|
187
225
|
</body>
|
|
188
226
|
```
|
|
189
227
|
|
|
228
|
+
> [!TIP]
|
|
229
|
+
> This example can be previewed live by running:
|
|
230
|
+
>
|
|
231
|
+
> ```
|
|
232
|
+
> cd node-live-response
|
|
233
|
+
> node test/server2.js
|
|
234
|
+
> ```
|
|
235
|
+
> _Open localhost:3000 to view_
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
190
239
|
### 3. Bidirectional messaging
|
|
191
240
|
|
|
192
|
-
|
|
241
|
+
Exchange messages between client and server through a message port.
|
|
242
|
+
|
|
243
|
+
(More in the [LiveResponse docs](https://github.com/webqit/fetch-plus?tab=readme-ov-file#3-bidirectional-messaging-via-message-ports))
|
|
193
244
|
|
|
194
245
|
```js
|
|
195
246
|
app.get('/chat', liveMode(), async (req, res) => {
|
|
196
247
|
const liveRes = new LiveResponse({ title: 'Chat' });
|
|
197
|
-
await res.send(liveRes);
|
|
248
|
+
await res.send(liveRes);
|
|
198
249
|
|
|
199
250
|
req.port.addEventListener('message', (e) => {
|
|
200
251
|
req.port.postMessage(e.data);
|
|
@@ -206,7 +257,7 @@ app.get('/chat', liveMode(), async (req, res) => {
|
|
|
206
257
|
});
|
|
207
258
|
```
|
|
208
259
|
|
|
209
|
-
|
|
260
|
+
On the client:
|
|
210
261
|
|
|
211
262
|
```html
|
|
212
263
|
<!doctype html>
|
|
@@ -241,48 +292,77 @@ Then on the client:
|
|
|
241
292
|
</body>
|
|
242
293
|
```
|
|
243
294
|
|
|
295
|
+
> [!TIP]
|
|
296
|
+
> This example can be previewed live by running:
|
|
297
|
+
>
|
|
298
|
+
> ```
|
|
299
|
+
> cd node-live-response
|
|
300
|
+
> node test/server3.js
|
|
301
|
+
> ```
|
|
302
|
+
> _Open localhost:3000 to view_
|
|
303
|
+
|
|
244
304
|
---
|
|
245
305
|
|
|
246
|
-
## What
|
|
306
|
+
## What This Library Adds
|
|
247
307
|
|
|
248
|
-
|
|
308
|
+
`@webqit/node-live-response` augments the standard Node/Express request lifecycle:
|
|
249
309
|
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
310
|
+
* Adds `req.port` for bidirectional messaging
|
|
311
|
+
* Adds `req.signal` for live session lifecycle tracking
|
|
312
|
+
* Patches `res.send()` / `res.end()` to accept `LiveResponse`
|
|
313
|
+
* Adds `res.die()` to explicitly terminate live interaction
|
|
253
314
|
|
|
254
315
|
---
|
|
255
316
|
|
|
256
|
-
## Lifecycle
|
|
317
|
+
## Lifecycle Contract
|
|
318
|
+
|
|
319
|
+
Live interaction has its own lifecycle, separate from the HTTP lifecycle.
|
|
257
320
|
|
|
258
|
-
###
|
|
321
|
+
### Interactivity starts
|
|
259
322
|
|
|
260
|
-
Interactivity begins
|
|
323
|
+
Interactivity begins when you send a `LiveResponse`:
|
|
261
324
|
|
|
262
325
|
```js
|
|
263
|
-
res.send(liveRes);
|
|
326
|
+
await res.send(liveRes);
|
|
264
327
|
```
|
|
265
328
|
|
|
266
329
|
That is the moment the client learns that the response is interactive and joins the live channel.
|
|
267
330
|
|
|
268
|
-
|
|
269
|
-
|
|
331
|
+
The `send()` method, this time, returns promise that resolves when the client has joined the live channel.
|
|
332
|
+
|
|
333
|
+
You may await this promise where necessary, but messages or response swaps issued before the connection is fully established are automatically queued and flushed once live mode becomes active.
|
|
334
|
+
|
|
335
|
+
The transition to an "open" connection may also be observed via:
|
|
336
|
+
|
|
337
|
+
```js
|
|
338
|
+
await req.port.readyStateChange('open');
|
|
339
|
+
```
|
|
270
340
|
|
|
271
|
-
|
|
341
|
+
---
|
|
272
342
|
|
|
273
|
-
###
|
|
343
|
+
### Interactivity ends
|
|
274
344
|
|
|
275
|
-
Live
|
|
345
|
+
Live interaction ends when the LiveResponse port on the client side is closed or when you explicitly call:
|
|
276
346
|
|
|
277
347
|
```js
|
|
278
348
|
res.die();
|
|
279
349
|
```
|
|
280
350
|
|
|
281
|
-
|
|
351
|
+
on the server. This method aborts the request lifecycle signal exposed at `req.signal`.
|
|
352
|
+
|
|
353
|
+
Note that ending the HTTP response does **not** end the live session.
|
|
354
|
+
|
|
355
|
+
The HTTP lifecycle and the live lifecycle are independent.
|
|
356
|
+
|
|
357
|
+
The transition to a "closed" connection may be observed via:
|
|
358
|
+
|
|
359
|
+
```js
|
|
360
|
+
await req.port.readyStateChange('close');
|
|
361
|
+
```
|
|
282
362
|
|
|
283
363
|
---
|
|
284
364
|
|
|
285
|
-
## Learn
|
|
365
|
+
## Learn More
|
|
286
366
|
|
|
287
367
|
* [LiveResponse docs](https://github.com/webqit/fetch-plus#1-live-state-projection-via-mutable-response-bodies)
|
|
288
368
|
* [Webflo](https://github.com/webqit/webflo)
|
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"realtime",
|
|
16
16
|
"webqit"
|
|
17
17
|
],
|
|
18
|
-
"version": "0.1.
|
|
18
|
+
"version": "0.1.8",
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
@@ -36,10 +36,8 @@
|
|
|
36
36
|
"version:next": "npm version prerelease --preid=next"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@webqit/fetch-plus": "^0.1.
|
|
40
|
-
"@webqit/port-plus": "^0.1.
|
|
41
|
-
"i": "^0.3.7",
|
|
42
|
-
"npm": "^11.7.0",
|
|
39
|
+
"@webqit/fetch-plus": "^0.1.29",
|
|
40
|
+
"@webqit/port-plus": "^0.1.22",
|
|
43
41
|
"ws": "^8.19.0"
|
|
44
42
|
},
|
|
45
43
|
"devDependencies": {
|
package/test/server1.js
CHANGED
|
@@ -22,7 +22,7 @@ async function handler(req, res) {
|
|
|
22
22
|
const state = { count: 0 };
|
|
23
23
|
|
|
24
24
|
const liveRes = new LiveResponse(state);
|
|
25
|
-
|
|
25
|
+
res.send(liveRes); // resolves when live mode is established
|
|
26
26
|
|
|
27
27
|
const interval = setInterval(() => {
|
|
28
28
|
Observer.set(state, 'count', state.count + 1);
|