@webqit/node-live-response 0.1.7 → 0.1.9
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 +122 -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,29 @@ 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))
|
|
244
|
+
|
|
245
|
+
On the server:
|
|
193
246
|
|
|
194
247
|
```js
|
|
195
248
|
app.get('/chat', liveMode(), async (req, res) => {
|
|
196
249
|
const liveRes = new LiveResponse({ title: 'Chat' });
|
|
197
|
-
await res.send(liveRes);
|
|
250
|
+
await res.send(liveRes);
|
|
198
251
|
|
|
199
252
|
req.port.addEventListener('message', (e) => {
|
|
200
253
|
req.port.postMessage(e.data);
|
|
@@ -206,7 +259,7 @@ app.get('/chat', liveMode(), async (req, res) => {
|
|
|
206
259
|
});
|
|
207
260
|
```
|
|
208
261
|
|
|
209
|
-
|
|
262
|
+
On the client:
|
|
210
263
|
|
|
211
264
|
```html
|
|
212
265
|
<!doctype html>
|
|
@@ -241,48 +294,77 @@ Then on the client:
|
|
|
241
294
|
</body>
|
|
242
295
|
```
|
|
243
296
|
|
|
297
|
+
> [!TIP]
|
|
298
|
+
> This example can be previewed live by running:
|
|
299
|
+
>
|
|
300
|
+
> ```
|
|
301
|
+
> cd node-live-response
|
|
302
|
+
> node test/server3.js
|
|
303
|
+
> ```
|
|
304
|
+
> _Open localhost:3000 to view_
|
|
305
|
+
|
|
244
306
|
---
|
|
245
307
|
|
|
246
|
-
## What
|
|
308
|
+
## What This Library Adds
|
|
247
309
|
|
|
248
|
-
|
|
310
|
+
`@webqit/node-live-response` augments the standard Node/Express request lifecycle:
|
|
249
311
|
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
312
|
+
* Adds `req.port` for bidirectional messaging
|
|
313
|
+
* Adds `req.signal` for live session lifecycle tracking
|
|
314
|
+
* Patches `res.send()` / `res.end()` to accept `LiveResponse`
|
|
315
|
+
* Adds `res.die()` to explicitly terminate live interaction
|
|
253
316
|
|
|
254
317
|
---
|
|
255
318
|
|
|
256
|
-
## Lifecycle
|
|
319
|
+
## Lifecycle Contract
|
|
320
|
+
|
|
321
|
+
Live interaction has its own lifecycle, separate from the HTTP lifecycle.
|
|
257
322
|
|
|
258
|
-
###
|
|
323
|
+
### Interactivity starts
|
|
259
324
|
|
|
260
|
-
Interactivity begins
|
|
325
|
+
Interactivity begins when you send a `LiveResponse`:
|
|
261
326
|
|
|
262
327
|
```js
|
|
263
|
-
res.send(liveRes);
|
|
328
|
+
await res.send(liveRes);
|
|
264
329
|
```
|
|
265
330
|
|
|
266
331
|
That is the moment the client learns that the response is interactive and joins the live channel.
|
|
267
332
|
|
|
268
|
-
|
|
269
|
-
* Messages sent on `req.port` *before* this moment are queued
|
|
333
|
+
The `send()` method, this time, returns promise that resolves when the client has joined the live channel.
|
|
270
334
|
|
|
271
|
-
|
|
335
|
+
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.
|
|
272
336
|
|
|
273
|
-
|
|
337
|
+
The transition to an "open" connection may also be observed via:
|
|
274
338
|
|
|
275
|
-
|
|
339
|
+
```js
|
|
340
|
+
await req.port.readyStateChange('open');
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
### Interactivity ends
|
|
346
|
+
|
|
347
|
+
Live interaction ends when the LiveResponse port on the client side is closed or when you explicitly call:
|
|
276
348
|
|
|
277
349
|
```js
|
|
278
350
|
res.die();
|
|
279
351
|
```
|
|
280
352
|
|
|
281
|
-
|
|
353
|
+
on the server. This method aborts the request lifecycle signal exposed at `req.signal`.
|
|
354
|
+
|
|
355
|
+
Note that ending the HTTP response does **not** end the live session.
|
|
356
|
+
|
|
357
|
+
The HTTP lifecycle and the live lifecycle are independent.
|
|
358
|
+
|
|
359
|
+
The transition to a "closed" connection may be observed via:
|
|
360
|
+
|
|
361
|
+
```js
|
|
362
|
+
await req.port.readyStateChange('close');
|
|
363
|
+
```
|
|
282
364
|
|
|
283
365
|
---
|
|
284
366
|
|
|
285
|
-
## Learn
|
|
367
|
+
## Learn More
|
|
286
368
|
|
|
287
369
|
* [LiveResponse docs](https://github.com/webqit/fetch-plus#1-live-state-projection-via-mutable-response-bodies)
|
|
288
370
|
* [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.9",
|
|
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);
|