@deepseek-ai/dsh-timeout 0.2.1-alpha.1 → 0.2.1-alpha.3

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.i18n.yaml CHANGED
@@ -45,8 +45,8 @@
45
45
  en: ceea0b46e6b8e15f
46
46
  zh: de87dccf08764061
47
47
  /deepseek-ai-dsh-timeout/understand-the-implementation/why-an-idle-watchdog-rearms:
48
- en: 7200cfa587c53a9c
49
- zh: 09d6b67dc1a31c5e
48
+ en: f7cd0e8ba2937f0f
49
+ zh: d17d546c5587b04e
50
50
  /deepseek-ai-dsh-timeout/further-exploration:
51
51
  en: 4c29a947e28047ad
52
52
  zh: d80f5ecb50b16203
package/README.md CHANGED
@@ -103,7 +103,7 @@ The library is built on one boundary: share the timing and classification, keep
103
103
 
104
104
  ### Why an idle watchdog rearms
105
105
 
106
- `idleWatchdog` keeps one stable fused signal and arms the timer only while `next()` is outstanding; resolution disarms, later demand or `pulse()` rearms, disposal clears, and concurrent demand rejects. Only the transport observes the signal, so the provider's real read must listen to it — the DeepSeek and pi-ai adapters close their response body or SDK request on abort.
106
+ `idleWatchdog` keeps one stable fused signal and arms the timer only while `next()` is outstanding; resolution disarms, later demand or `pulse()` rearms, disposal clears, and concurrent demand rejects. Unlike `deadline()`, an idle watchdog settles its outstanding demand on its own deadline: a read the transport leaves pending after an abort rejects `next()` with the watchdog's `TimeoutReason`, so a transport that never observes the signal cannot extend the wait past the idle interval. The signal still tells the transport to release its own resources — the DeepSeek and pi-ai adapters close their response body or SDK request on abort.
107
107
 
108
108
  </details>
109
109
 
package/README.zh.md CHANGED
@@ -103,7 +103,7 @@ timer 只在某个迭代器 `next()` 尚未完成时启动,并会因不产生
103
103
 
104
104
  ### 空闲 watchdog 为何重新启动
105
105
 
106
- `idleWatchdog` 保持一个稳定的融合信号,只在 `next()` 尚未完成时启动 timer;完成后停止,后续需求或 `pulse()` 重新启动,dispose(资源释放)时清除,并发需求被拒绝。只有传输层观察该信号,因此提供方的真实读取必须监听它——DeepSeek 与 pi-ai 适配器会在中止时关闭响应正文或 SDK 请求。
106
+ `idleWatchdog` 保持一个稳定的融合信号,只在 `next()` 尚未完成时启动 timer;完成后停止,后续需求或 `pulse()` 重新启动,dispose(资源释放)时清除,并发需求被拒绝。与 `deadline()` 不同,空闲 watchdog 会按自己的截止时间结算尚未完成的 demand:传输层在 abort 之后仍挂起的读取会让 `next()` 以该 watchdog 的 `TimeoutReason` reject,因此完全不观察信号的传输层也无法把等待拖过空闲间隔。信号仍用于让传输层释放自身资源——DeepSeek 与 pi-ai 适配器会在中止时关闭响应正文或 SDK 请求。
107
107
 
108
108
  </details>
109
109
 
package/lib/index.js CHANGED
@@ -75,7 +75,8 @@ function deadline(upstream, timeoutMs, code) {
75
75
  * Create a rearmable idle watchdog for an async iterator. The timer exists only
76
76
  * while {@link IdleWatchdog.next} is outstanding, so consumer think time does
77
77
  * not count as provider idle time. The returned signal is stable for the whole
78
- * call and only notifies; the iterator must observe it to terminate its work.
78
+ * call and only notifies; the iterator must observe it to release the resources
79
+ * of a demand the deadline abandoned.
79
80
  *
80
81
  * @param upstream - caller cancellation fused into the stable signal.
81
82
  * @param timeoutMs - positive finite idle interval in milliseconds.
@@ -86,13 +87,14 @@ function idleWatchdog(upstream, timeoutMs, code) {
86
87
  assertTimerDelay(timeoutMs, "idleWatchdog timeoutMs");
87
88
  const timeout = new AbortController();
88
89
  const signal = upstream === void 0 ? timeout.signal : AbortSignal.any([upstream, timeout.signal]);
90
+ const reason = new TimeoutReason(code, timeoutMs);
89
91
  let timer;
90
92
  let outstanding = false;
91
93
  let disposed = false;
92
94
  const arm = () => {
93
95
  if (timer !== void 0) clearTimeout(timer);
94
96
  timer = setTimeout(() => {
95
- timeout.abort(new TimeoutReason(code, timeoutMs));
97
+ timeout.abort(reason);
96
98
  }, timeoutMs);
97
99
  };
98
100
  return {
@@ -102,8 +104,15 @@ function idleWatchdog(upstream, timeoutMs, code) {
102
104
  if (outstanding) throw new Error("idleWatchdog next is already outstanding");
103
105
  outstanding = true;
104
106
  arm();
107
+ const expired = Promise.withResolvers();
108
+ const onAbort = () => {
109
+ expired.reject(reason);
110
+ };
111
+ timeout.signal.addEventListener("abort", onAbort, { once: true });
105
112
  try {
106
- return await iterator.next();
113
+ return await Promise.race([iterator.next().finally(() => {
114
+ timeout.signal.removeEventListener("abort", onAbort);
115
+ }), expired.promise]);
107
116
  } finally {
108
117
  clearTimeout(timer);
109
118
  timer = void 0;
@@ -45,9 +45,17 @@ export interface IdleWatchdog {
45
45
  /** Stable signal aborted by upstream cancellation or this watchdog's timeout. */
46
46
  readonly signal: AbortSignal;
47
47
  /**
48
- * Await one iterator demand while the idle timer is armed.
48
+ * Await one iterator demand while the idle timer is armed. The deadline
49
+ * settles the demand even when the awaited work ignores its abort signal.
49
50
  * @param iterator - iterator whose next value represents provider progress.
50
51
  * @returns the iterator's next result.
52
+ * @throws The {@link TimeoutReason} of this watchdog when the idle interval
53
+ * elapses first. Only that deadline settles the demand: an upstream
54
+ * cancellation reaches the iterator through {@link IdleWatchdog.signal} and
55
+ * never shortens the wait. The abandoned demand stays the caller's to release
56
+ * through that signal; an async generator queues its own return behind it, so
57
+ * callers must not await it. Disposal clears the timer without settling an
58
+ * outstanding demand.
51
59
  */
52
60
  next<T>(iterator: AsyncIterator<T>): Promise<IteratorResult<T>>;
53
61
  /** Rearm an outstanding demand after transport activity that yields no iterator value; otherwise a no-op. */
@@ -70,7 +78,8 @@ export declare function deadline(upstream: AbortSignal | undefined, timeoutMs: n
70
78
  * Create a rearmable idle watchdog for an async iterator. The timer exists only
71
79
  * while {@link IdleWatchdog.next} is outstanding, so consumer think time does
72
80
  * not count as provider idle time. The returned signal is stable for the whole
73
- * call and only notifies; the iterator must observe it to terminate its work.
81
+ * call and only notifies; the iterator must observe it to release the resources
82
+ * of a demand the deadline abandoned.
74
83
  *
75
84
  * @param upstream - caller cancellation fused into the stable signal.
76
85
  * @param timeoutMs - positive finite idle interval in milliseconds.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-timeout",
3
3
  "description": "Zero-dependency timeout/deadline primitive: clampTimeout, deadline, timeoutOf, TimeoutReason (timing + classification only, no termination)",
4
- "version": "0.2.1-alpha.1",
4
+ "version": "0.2.1-alpha.3",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,9 +27,9 @@
27
27
  ],
28
28
  "license": "MIT",
29
29
  "peerDependencies": {
30
- "@deepseek-ai/cordis": "~4.0.5-alpha.1"
30
+ "@deepseek-ai/cordis": "~4.0.5-alpha.2"
31
31
  },
32
32
  "devDependencies": {
33
- "@deepseek-ai/cordis": "~4.0.5-alpha.1"
33
+ "@deepseek-ai/cordis": "~4.0.5-alpha.2"
34
34
  }
35
35
  }