@hile/message-modem 3.0.0 → 4.0.1
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/dist/index.d.ts +15 -4
- package/dist/index.js +278 -25
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -3,15 +3,27 @@ export * from './exception.js';
|
|
|
3
3
|
export declare enum MESSAGE_MODEM_TYPE {
|
|
4
4
|
REQUEST = 0,
|
|
5
5
|
RESPONSE = 1,
|
|
6
|
-
ABORT = 2
|
|
6
|
+
ABORT = 2,
|
|
7
|
+
STREAM_CREDIT = 3
|
|
7
8
|
}
|
|
8
9
|
export interface MessageTransferFormat<T = any> {
|
|
9
10
|
id: number;
|
|
10
11
|
mode: MESSAGE_MODEM_TYPE;
|
|
11
12
|
twoway: boolean;
|
|
12
13
|
stream?: boolean;
|
|
14
|
+
streamVersion?: 1;
|
|
15
|
+
streamWindow?: number;
|
|
13
16
|
data?: T;
|
|
14
17
|
}
|
|
18
|
+
export interface MessageStreamOptions {
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
/** Maximum total stream lifetime in milliseconds. */
|
|
21
|
+
timeout?: number;
|
|
22
|
+
/** Maximum time between valid stream responses in milliseconds. */
|
|
23
|
+
idleTimeout?: number;
|
|
24
|
+
/** Maximum number of produced but not yet consumed chunks. */
|
|
25
|
+
window?: number;
|
|
26
|
+
}
|
|
15
27
|
export interface MessageReturnFormat<T = any> {
|
|
16
28
|
status: string | number;
|
|
17
29
|
data: T;
|
|
@@ -28,6 +40,7 @@ export declare abstract class MessageModem {
|
|
|
28
40
|
private readonly aborts;
|
|
29
41
|
private readonly stacks;
|
|
30
42
|
private readonly streams;
|
|
43
|
+
private readonly streamProducers;
|
|
31
44
|
protected _dispose(): void;
|
|
32
45
|
/**
|
|
33
46
|
* 创建自增 ID
|
|
@@ -75,9 +88,7 @@ export declare abstract class MessageModem {
|
|
|
75
88
|
timeout?: number;
|
|
76
89
|
signal?: AbortSignal;
|
|
77
90
|
}): void;
|
|
78
|
-
protected _stream(data: any, options?:
|
|
79
|
-
signal?: AbortSignal;
|
|
80
|
-
}): Readable;
|
|
91
|
+
protected _stream(data: any, options?: MessageStreamOptions): Readable;
|
|
81
92
|
/**
|
|
82
93
|
* 写入消息
|
|
83
94
|
* @param data - 消息数据
|
package/dist/index.js
CHANGED
|
@@ -6,12 +6,54 @@ export var MESSAGE_MODEM_TYPE;
|
|
|
6
6
|
MESSAGE_MODEM_TYPE[MESSAGE_MODEM_TYPE["REQUEST"] = 0] = "REQUEST";
|
|
7
7
|
MESSAGE_MODEM_TYPE[MESSAGE_MODEM_TYPE["RESPONSE"] = 1] = "RESPONSE";
|
|
8
8
|
MESSAGE_MODEM_TYPE[MESSAGE_MODEM_TYPE["ABORT"] = 2] = "ABORT";
|
|
9
|
+
MESSAGE_MODEM_TYPE[MESSAGE_MODEM_TYPE["STREAM_CREDIT"] = 3] = "STREAM_CREDIT";
|
|
9
10
|
})(MESSAGE_MODEM_TYPE || (MESSAGE_MODEM_TYPE = {}));
|
|
11
|
+
const MAX_STREAM_WINDOW = 64;
|
|
12
|
+
const MAX_TIMER_DELAY = 2_147_483_647;
|
|
13
|
+
function streamLimit(value, name) {
|
|
14
|
+
if (value === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
17
|
+
throw new TypeError(`${name} must be a positive safe integer`);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
function streamWindow(value) {
|
|
22
|
+
const normalized = streamLimit(value, 'Stream window') ?? 1;
|
|
23
|
+
if (normalized > MAX_STREAM_WINDOW) {
|
|
24
|
+
throw new TypeError(`Stream window must not exceed ${MAX_STREAM_WINDOW}`);
|
|
25
|
+
}
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
function streamTimeout(value, name) {
|
|
29
|
+
const normalized = streamLimit(value, name);
|
|
30
|
+
if (normalized !== undefined && normalized > MAX_TIMER_DELAY) {
|
|
31
|
+
throw new TypeError(`${name} must not exceed ${MAX_TIMER_DELAY}`);
|
|
32
|
+
}
|
|
33
|
+
return normalized;
|
|
34
|
+
}
|
|
35
|
+
class CreditReadable extends Readable {
|
|
36
|
+
onConsumed;
|
|
37
|
+
constructor(onConsumed) {
|
|
38
|
+
super({ objectMode: true });
|
|
39
|
+
this.onConsumed = onConsumed;
|
|
40
|
+
}
|
|
41
|
+
_read() {
|
|
42
|
+
// Credits are tied to actual read() results, not Node's eager buffer filling.
|
|
43
|
+
}
|
|
44
|
+
read(size) {
|
|
45
|
+
const chunk = super.read(size);
|
|
46
|
+
if (chunk !== null)
|
|
47
|
+
this.onConsumed();
|
|
48
|
+
return chunk;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
10
51
|
export class MessageModem {
|
|
11
52
|
id = 0;
|
|
12
53
|
aborts = new Map();
|
|
13
54
|
stacks = new Map();
|
|
14
55
|
streams = new Map();
|
|
56
|
+
streamProducers = new Map();
|
|
15
57
|
_dispose() {
|
|
16
58
|
for (const { reject } of this.stacks.values()) {
|
|
17
59
|
reject(new AbortException());
|
|
@@ -19,12 +61,16 @@ export class MessageModem {
|
|
|
19
61
|
for (const controller of this.aborts.values()) {
|
|
20
62
|
controller.abort();
|
|
21
63
|
}
|
|
22
|
-
for (const stream of this.streams.values()) {
|
|
64
|
+
for (const { stream } of this.streams.values()) {
|
|
23
65
|
stream.destroy(new AbortException());
|
|
24
66
|
}
|
|
67
|
+
for (const producer of this.streamProducers.values()) {
|
|
68
|
+
producer.wake?.();
|
|
69
|
+
}
|
|
25
70
|
this.aborts.clear();
|
|
26
71
|
this.stacks.clear();
|
|
27
72
|
this.streams.clear();
|
|
73
|
+
this.streamProducers.clear();
|
|
28
74
|
}
|
|
29
75
|
/**
|
|
30
76
|
* 创建自增 ID
|
|
@@ -83,25 +129,105 @@ export class MessageModem {
|
|
|
83
129
|
signal: options?.signal,
|
|
84
130
|
});
|
|
85
131
|
}
|
|
86
|
-
_stream(data, options) {
|
|
132
|
+
_stream(data, options = {}) {
|
|
133
|
+
const window = streamWindow(options.window);
|
|
134
|
+
const timeout = streamTimeout(options.timeout, 'Stream timeout');
|
|
135
|
+
const idleTimeout = streamTimeout(options.idleTimeout, 'Stream idle timeout');
|
|
87
136
|
const state = this.createPostData(MESSAGE_MODEM_TYPE.REQUEST, data, true, true);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
137
|
+
state.streamVersion = 1;
|
|
138
|
+
if (window > 1)
|
|
139
|
+
state.streamWindow = window;
|
|
140
|
+
let consumer;
|
|
141
|
+
const stream = new CreditReadable(() => {
|
|
142
|
+
if (consumer.creditsOwed === 0 || consumer.completed || consumer.cancelled)
|
|
143
|
+
return;
|
|
144
|
+
consumer.creditsOwed--;
|
|
145
|
+
try {
|
|
146
|
+
this.post(this.createPostData(MESSAGE_MODEM_TYPE.STREAM_CREDIT, { id: state.id, seq: consumer.nextCreditSeq++ }, false));
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
consumer.cancelled = true;
|
|
150
|
+
stream.destroy(error);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
let totalTimer;
|
|
154
|
+
let idleTimer;
|
|
155
|
+
const clearTimers = () => {
|
|
156
|
+
if (totalTimer)
|
|
157
|
+
clearTimeout(totalTimer);
|
|
158
|
+
if (idleTimer)
|
|
159
|
+
clearTimeout(idleTimer);
|
|
160
|
+
totalTimer = undefined;
|
|
161
|
+
idleTimer = undefined;
|
|
162
|
+
};
|
|
163
|
+
const expire = (message) => {
|
|
164
|
+
if (consumer.completed || consumer.cancelled)
|
|
165
|
+
return;
|
|
166
|
+
sendAbort();
|
|
167
|
+
stream.destroy(new TimeoutException(message));
|
|
168
|
+
};
|
|
169
|
+
const touch = () => {
|
|
170
|
+
if (!idleTimeout || consumer.completed || consumer.cancelled)
|
|
171
|
+
return;
|
|
172
|
+
if (idleTimer)
|
|
173
|
+
clearTimeout(idleTimer);
|
|
174
|
+
idleTimer = setTimeout(() => expire('Stream idle timeout'), idleTimeout);
|
|
175
|
+
idleTimer.unref?.();
|
|
176
|
+
};
|
|
177
|
+
consumer = {
|
|
178
|
+
stream,
|
|
179
|
+
completed: false,
|
|
180
|
+
cancelled: false,
|
|
181
|
+
creditsOwed: 0,
|
|
182
|
+
maxCredits: window,
|
|
183
|
+
nextSeq: 0,
|
|
184
|
+
nextCreditSeq: 0,
|
|
185
|
+
touch,
|
|
186
|
+
clearTimers,
|
|
187
|
+
};
|
|
188
|
+
const sendAbort = () => {
|
|
189
|
+
if (consumer.completed || consumer.cancelled)
|
|
190
|
+
return;
|
|
191
|
+
consumer.cancelled = true;
|
|
192
|
+
try {
|
|
193
|
+
this.post(this.createPostData(MESSAGE_MODEM_TYPE.ABORT, state.id));
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// The transport may already be closed.
|
|
197
|
+
}
|
|
198
|
+
};
|
|
91
199
|
const onAbort = () => {
|
|
92
|
-
|
|
200
|
+
sendAbort();
|
|
93
201
|
stream.destroy(new AbortException());
|
|
94
|
-
this.streams.delete(state.id);
|
|
95
202
|
};
|
|
96
|
-
if (options?.signal) {
|
|
97
|
-
|
|
203
|
+
if (options?.signal?.aborted) {
|
|
204
|
+
consumer.cancelled = true;
|
|
205
|
+
queueMicrotask(() => stream.destroy(new AbortException()));
|
|
206
|
+
return stream;
|
|
207
|
+
}
|
|
208
|
+
this.streams.set(state.id, consumer);
|
|
209
|
+
options?.signal?.addEventListener('abort', onAbort, { once: true });
|
|
210
|
+
if (timeout) {
|
|
211
|
+
totalTimer = setTimeout(() => expire('Stream timeout'), timeout);
|
|
212
|
+
totalTimer.unref?.();
|
|
98
213
|
}
|
|
214
|
+
touch();
|
|
99
215
|
stream.on('close', () => {
|
|
216
|
+
sendAbort();
|
|
217
|
+
clearTimers();
|
|
100
218
|
if (this.streams.has(state.id)) {
|
|
101
219
|
this.streams.delete(state.id);
|
|
102
220
|
}
|
|
103
221
|
options?.signal?.removeEventListener('abort', onAbort);
|
|
104
222
|
});
|
|
223
|
+
try {
|
|
224
|
+
this.post(state);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
consumer.cancelled = true;
|
|
228
|
+
this.streams.delete(state.id);
|
|
229
|
+
queueMicrotask(() => stream.destroy(error));
|
|
230
|
+
}
|
|
105
231
|
return stream;
|
|
106
232
|
}
|
|
107
233
|
/**
|
|
@@ -116,26 +242,32 @@ export class MessageModem {
|
|
|
116
242
|
const signal = options?.signal;
|
|
117
243
|
// 创建请求消息数据
|
|
118
244
|
const state = this.createPostData(MESSAGE_MODEM_TYPE.REQUEST, data, twoway);
|
|
119
|
-
// 发送消息
|
|
120
|
-
this.post(state);
|
|
121
245
|
// 如果消息是单向的,则直接返回
|
|
122
|
-
if (!twoway)
|
|
246
|
+
if (!twoway) {
|
|
247
|
+
if (!signal?.aborted)
|
|
248
|
+
this.post(state);
|
|
123
249
|
return;
|
|
250
|
+
}
|
|
124
251
|
return new Promise((resolve, reject) => {
|
|
252
|
+
let timer;
|
|
253
|
+
let posted = false;
|
|
125
254
|
const clear = () => {
|
|
126
255
|
if (this.stacks.has(state.id)) {
|
|
127
256
|
this.stacks.delete(state.id);
|
|
128
257
|
}
|
|
129
258
|
};
|
|
130
259
|
const clean = () => {
|
|
131
|
-
|
|
260
|
+
if (timer)
|
|
261
|
+
clearTimeout(timer);
|
|
132
262
|
signal?.removeEventListener('abort', onAbort);
|
|
133
263
|
clear();
|
|
134
264
|
};
|
|
135
265
|
const onAbort = () => {
|
|
136
|
-
|
|
266
|
+
if (timer)
|
|
267
|
+
clearTimeout(timer);
|
|
137
268
|
try {
|
|
138
|
-
|
|
269
|
+
if (posted)
|
|
270
|
+
this.post(this.createPostData(MESSAGE_MODEM_TYPE.ABORT, state.id));
|
|
139
271
|
}
|
|
140
272
|
catch {
|
|
141
273
|
/* 例如 WebSocket 已关闭时 send 可能抛错 */
|
|
@@ -156,12 +288,23 @@ export class MessageModem {
|
|
|
156
288
|
clean();
|
|
157
289
|
reject(e);
|
|
158
290
|
};
|
|
159
|
-
const timer = setTimeout(() => _reject(new TimeoutException()), timeout).unref();
|
|
160
|
-
signal?.addEventListener('abort', onAbort);
|
|
161
291
|
this.stacks.set(state.id, {
|
|
162
292
|
resolve: _resolve,
|
|
163
293
|
reject: _reject,
|
|
164
294
|
});
|
|
295
|
+
timer = setTimeout(() => _reject(new TimeoutException()), timeout).unref();
|
|
296
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
297
|
+
if (signal?.aborted) {
|
|
298
|
+
onAbort();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
posted = true;
|
|
303
|
+
this.post(state);
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
_reject(error);
|
|
307
|
+
}
|
|
165
308
|
});
|
|
166
309
|
}
|
|
167
310
|
/**
|
|
@@ -238,30 +381,97 @@ export class MessageModem {
|
|
|
238
381
|
}
|
|
239
382
|
}
|
|
240
383
|
onStreamRequest(msg) {
|
|
384
|
+
if (msg.streamVersion !== 1) {
|
|
385
|
+
this.post({
|
|
386
|
+
id: msg.id,
|
|
387
|
+
mode: MESSAGE_MODEM_TYPE.RESPONSE,
|
|
388
|
+
stream: true,
|
|
389
|
+
streamVersion: 1,
|
|
390
|
+
data: { status: 400, seq: 0, payload: 'Unsupported stream protocol', final: true },
|
|
391
|
+
twoway: false,
|
|
392
|
+
});
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
let window;
|
|
396
|
+
try {
|
|
397
|
+
window = streamWindow(msg.streamWindow);
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
this.post({
|
|
401
|
+
id: msg.id,
|
|
402
|
+
mode: MESSAGE_MODEM_TYPE.RESPONSE,
|
|
403
|
+
stream: true,
|
|
404
|
+
streamVersion: 1,
|
|
405
|
+
data: {
|
|
406
|
+
status: 400,
|
|
407
|
+
seq: 0,
|
|
408
|
+
payload: error instanceof Error ? error.message : 'Invalid stream window',
|
|
409
|
+
final: true,
|
|
410
|
+
},
|
|
411
|
+
twoway: false,
|
|
412
|
+
});
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (this.streamProducers.has(msg.id) || this.streamProducers.size >= 128) {
|
|
416
|
+
this.post({
|
|
417
|
+
id: msg.id,
|
|
418
|
+
mode: MESSAGE_MODEM_TYPE.RESPONSE,
|
|
419
|
+
stream: true,
|
|
420
|
+
streamVersion: 1,
|
|
421
|
+
data: { status: 429, seq: 0, payload: 'Stream capacity exceeded', final: true },
|
|
422
|
+
twoway: false,
|
|
423
|
+
});
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
241
426
|
const controller = new AbortController();
|
|
427
|
+
const producer = {
|
|
428
|
+
credits: window,
|
|
429
|
+
maxCredits: window,
|
|
430
|
+
nextCreditSeq: 0,
|
|
431
|
+
};
|
|
432
|
+
let sequence = 0;
|
|
242
433
|
this.aborts.set(msg.id, controller);
|
|
434
|
+
this.streamProducers.set(msg.id, producer);
|
|
243
435
|
controller.signal.addEventListener('abort', () => {
|
|
436
|
+
producer.wake?.();
|
|
244
437
|
if (this.aborts.has(msg.id)) {
|
|
245
438
|
this.aborts.delete(msg.id);
|
|
246
439
|
}
|
|
247
440
|
});
|
|
441
|
+
const takeCredit = async () => {
|
|
442
|
+
while (producer.credits === 0 && !controller.signal.aborted) {
|
|
443
|
+
await new Promise((resolve) => {
|
|
444
|
+
producer.wake = resolve;
|
|
445
|
+
});
|
|
446
|
+
producer.wake = undefined;
|
|
447
|
+
}
|
|
448
|
+
if (controller.signal.aborted)
|
|
449
|
+
throw new AbortException();
|
|
450
|
+
producer.credits--;
|
|
451
|
+
};
|
|
248
452
|
this.exec(msg.data, controller.signal)
|
|
249
453
|
.then(async (value) => {
|
|
250
454
|
if (!isAsyncIterable(value)) {
|
|
251
455
|
throw new Exception(500, 'Invalid async iterable');
|
|
252
456
|
}
|
|
253
|
-
|
|
254
|
-
|
|
457
|
+
const iterator = value[Symbol.asyncIterator]();
|
|
458
|
+
producer.iterator = iterator;
|
|
459
|
+
while (!controller.signal.aborted) {
|
|
460
|
+
await takeCredit();
|
|
461
|
+
const next = await iterator.next();
|
|
255
462
|
if (controller.signal.aborted)
|
|
256
463
|
return;
|
|
464
|
+
if (next.done)
|
|
465
|
+
break;
|
|
257
466
|
this.post({
|
|
258
467
|
id: msg.id,
|
|
259
468
|
mode: MESSAGE_MODEM_TYPE.RESPONSE,
|
|
260
469
|
stream: true,
|
|
470
|
+
streamVersion: msg.streamVersion,
|
|
261
471
|
data: {
|
|
262
472
|
status: 200,
|
|
263
|
-
seq:
|
|
264
|
-
payload:
|
|
473
|
+
seq: sequence++,
|
|
474
|
+
payload: next.value,
|
|
265
475
|
final: false,
|
|
266
476
|
},
|
|
267
477
|
twoway: false,
|
|
@@ -273,9 +483,10 @@ export class MessageModem {
|
|
|
273
483
|
id: msg.id,
|
|
274
484
|
mode: MESSAGE_MODEM_TYPE.RESPONSE,
|
|
275
485
|
stream: true,
|
|
486
|
+
streamVersion: msg.streamVersion,
|
|
276
487
|
data: {
|
|
277
488
|
status: 200,
|
|
278
|
-
seq:
|
|
489
|
+
seq: sequence++,
|
|
279
490
|
payload: undefined,
|
|
280
491
|
final: true,
|
|
281
492
|
},
|
|
@@ -289,16 +500,22 @@ export class MessageModem {
|
|
|
289
500
|
id: msg.id,
|
|
290
501
|
mode: MESSAGE_MODEM_TYPE.RESPONSE,
|
|
291
502
|
stream: true,
|
|
503
|
+
streamVersion: msg.streamVersion,
|
|
292
504
|
data: {
|
|
293
505
|
status: e instanceof Exception ? e.status : 500,
|
|
294
|
-
seq:
|
|
295
|
-
payload: e instanceof
|
|
506
|
+
seq: sequence,
|
|
507
|
+
payload: e instanceof Error ? e.message : 'Unknown error',
|
|
296
508
|
final: true,
|
|
297
509
|
},
|
|
298
510
|
twoway: false,
|
|
299
511
|
});
|
|
300
512
|
})
|
|
301
513
|
.finally(() => {
|
|
514
|
+
if (controller.signal.aborted && producer.iterator?.return) {
|
|
515
|
+
void Promise.resolve(producer.iterator.return()).catch(() => { });
|
|
516
|
+
}
|
|
517
|
+
producer.wake?.();
|
|
518
|
+
this.streamProducers.delete(msg.id);
|
|
302
519
|
if (this.aborts.has(msg.id)) {
|
|
303
520
|
this.aborts.delete(msg.id);
|
|
304
521
|
}
|
|
@@ -309,22 +526,40 @@ export class MessageModem {
|
|
|
309
526
|
const res = msg.data;
|
|
310
527
|
// 如果栈中存在该消息,则处理响应消息
|
|
311
528
|
if (this.streams.has(id)) {
|
|
312
|
-
const
|
|
529
|
+
const consumer = this.streams.get(id);
|
|
530
|
+
const stream = consumer.stream;
|
|
313
531
|
if (res) {
|
|
532
|
+
consumer.touch();
|
|
533
|
+
if (!Number.isSafeInteger(res.seq) || res.seq !== consumer.nextSeq) {
|
|
534
|
+
stream.destroy(new Exception(409, `Invalid stream sequence: expected ${consumer.nextSeq}, received ${String(res.seq)}`));
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
consumer.nextSeq++;
|
|
314
538
|
if (res.status === 200) {
|
|
315
539
|
if (res.final) {
|
|
540
|
+
consumer.completed = true;
|
|
541
|
+
consumer.clearTimers();
|
|
316
542
|
stream.push(null);
|
|
317
543
|
}
|
|
318
544
|
else {
|
|
545
|
+
if (consumer.creditsOwed >= consumer.maxCredits) {
|
|
546
|
+
stream.destroy(new Exception(429, 'Stream window exceeded'));
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
consumer.creditsOwed++;
|
|
319
550
|
stream.push(res.payload);
|
|
320
551
|
}
|
|
321
552
|
}
|
|
322
553
|
else {
|
|
554
|
+
consumer.completed = true;
|
|
555
|
+
consumer.clearTimers();
|
|
323
556
|
const err = new Exception(res.status, res.payload);
|
|
324
557
|
setImmediate(() => stream.destroy(err));
|
|
325
558
|
}
|
|
326
559
|
}
|
|
327
560
|
else {
|
|
561
|
+
consumer.completed = true;
|
|
562
|
+
consumer.clearTimers();
|
|
328
563
|
const err = new Exception(404, 'Empty chunk data');
|
|
329
564
|
setImmediate(() => stream.destroy(err));
|
|
330
565
|
}
|
|
@@ -365,6 +600,24 @@ export class MessageModem {
|
|
|
365
600
|
}
|
|
366
601
|
}
|
|
367
602
|
break;
|
|
603
|
+
case MESSAGE_MODEM_TYPE.STREAM_CREDIT: {
|
|
604
|
+
const credit = msg.data;
|
|
605
|
+
if (!credit
|
|
606
|
+
|| !Number.isSafeInteger(credit.id)
|
|
607
|
+
|| credit.id < 0
|
|
608
|
+
|| !Number.isSafeInteger(credit.seq)
|
|
609
|
+
|| credit.seq < 0)
|
|
610
|
+
break;
|
|
611
|
+
const producer = this.streamProducers.get(credit.id);
|
|
612
|
+
if (producer
|
|
613
|
+
&& producer.credits < producer.maxCredits
|
|
614
|
+
&& credit.seq === producer.nextCreditSeq) {
|
|
615
|
+
producer.credits++;
|
|
616
|
+
producer.nextCreditSeq++;
|
|
617
|
+
producer.wake?.();
|
|
618
|
+
}
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
368
621
|
}
|
|
369
622
|
}
|
|
370
623
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hile/message-modem",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
"access": "public"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
+
"@types/node": "^26.2.0",
|
|
21
22
|
"fix-esm-import-path": "^1.10.3",
|
|
22
23
|
"vitest": "^4.0.18"
|
|
23
24
|
},
|
|
24
|
-
"gitHead": "
|
|
25
|
+
"gitHead": "fe98c6f8cca2860ccbb213c575cfda44588cbd06"
|
|
25
26
|
}
|