Let .extend() replace or remove inherited abort signals
Summary
After this change, .extend() callers can drop signals inherited from a parent instance. signal: replaceOption(newSignal) replaces the parent signals, and signal: undefined removes them. Signal merging also now applies only to the root signal option. A nested key such as json.signal no longer gets pulled into the request signal.
In deepMergeInternal, a root signal key that is a replace marker or undefined empties the collected signals array. AbortSignal collection now requires isRoot. The readme and the KyInstance.extend JSDoc document the new behavior. Tests cover replace, removal, the nested JSON case, and an extra check that instance signals still combine with request signals.
Changes
-
Modified source/utils/merge.ts +12 −7
deepMergeInternalnow unwrapsreplaceOptionbefore signal handling. It clearssignalswhen the rootsignalis a replace marker orundefined. It pushes AbortSignals only whenisRoot && key === 'signal'. A typo in a comment is also fixed.- What
deepMergeInternalnow unwrapsreplaceOptionbefore signal handling. It clearssignalswhen the rootsignalis a replace marker orundefined. It pushes AbortSignals only whenisRoot && key === 'signal'. A typo in a comment is also fixed.- Why
- Inherited signals can now be replaced or removed. Nested
signalproperties, for example insidejson, now go through the normal merge path instead of being hoisted into the request signal.
-
Modified test/main.ts +63 −0 Extends the signal-merge test so an instance signal aborts a request that has its own signal. Adds three tests: replace via
replaceOption, removal viasignal: undefined, and nestedjson.signalhandling.- What
- Extends the signal-merge test so an instance signal aborts a request that has its own signal. Adds three tests: replace via
replaceOption, removal viasignal: undefined, and nestedjson.signalhandling. - Why
- Covers the new clear and replace paths and the root-only scope.
-
Modified readme.md +2 −0 Adds a sentence on signal combining,
replaceOption(signal), andsignal: undefinedunder.extend().- What
- Adds a sentence on signal combining,
replaceOption(signal), andsignal: undefinedunder.extend(). - Why
- Documentation only.
-
Modified source/types/ky.ts +2 −0 Adds the same sentence to the
KyInstance.extend()JSDoc.- What
- Adds the same sentence to the
KyInstance.extend()JSDoc. - Why
- Documentation only; no type changes.
Flow
Rendering diagram…
.extend() and request options pass through deepMergeInternal. Root signal values are collected, cleared, or replaced, then combined into returnValue.signal.Components and connections
Components
- ky.extend / request call entry point
-
deepMergeInternal
function, changed
source/utils/merge.ts -
signals array
data, changed
source/utils/merge.ts -
returnValue.signal (AbortSignal.any or last)
data
source/utils/merge.ts
Connections
- ky.extend / request call → deepMergeInternal (option sources)
- deepMergeInternal → signals array (push / clear root signal)
- signals array → returnValue.signal (AbortSignal.any or last) (combine)
Risks
0 high · 1 medium · 0 low
-
Mediumsource/utils/merge.ts:227–230
Per-request
signal: undefinednow drops instance signalThe clear applies to any root-level merge, not only
.extend(). The existing test shows request options merge with instance signals through this path. A call likeapi.get(url, {signal: opts.signal})with an undefinedopts.signalnow emptiessignals. That silently discards the instance-level signal. The old code ignored a non-AbortSignal value here and kept the instance signal.