@doist/twist-sdk 2.9.2 → 2.9.4

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.
@@ -1,8 +1,75 @@
1
1
  "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
24
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
2
49
  Object.defineProperty(exports, "__esModule", { value: true });
3
50
  exports.server = void 0;
4
51
  var node_1 = require("msw/node");
5
52
  var vitest_1 = require("vitest");
53
+ // In production the transport uses undici's own `fetch` (paired with the
54
+ // dispatcher) so the request client and dispatcher stay on one undici version.
55
+ // MSW intercepts the *global* `fetch`, not that separate undici instance, so
56
+ // force the transport onto the global `fetch` for every test in the suite;
57
+ // MSW then intercepts requests as usual. The real undici transport is covered
58
+ // directly by `http-dispatcher.test.ts` and `fetch-with-retry.test.ts`, which
59
+ // opt out of this seam.
60
+ vitest_1.vi.mock('../transport/http-dispatcher', function (importOriginal) { return __awaiter(void 0, void 0, void 0, function () {
61
+ var actual;
62
+ return __generator(this, function (_a) {
63
+ switch (_a.label) {
64
+ case 0: return [4 /*yield*/, importOriginal()];
65
+ case 1:
66
+ actual = _a.sent();
67
+ return [2 /*return*/, __assign(__assign({}, actual), { getDefaultTransport: vitest_1.vi.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
68
+ return [2 /*return*/, undefined];
69
+ }); }); }) })];
70
+ }
71
+ });
72
+ }); });
6
73
  // Create MSW server instance
7
74
  exports.server = (0, node_1.setupServer)();
8
75
  // Start server before all tests
@@ -137,23 +137,29 @@ function fetchWithRetry(url_1, options_1) {
137
137
  }
138
138
  function fetchWithDefaultTransport(url, options, signal) {
139
139
  return __awaiter(this, void 0, void 0, function () {
140
- var dispatcher, response, _a;
141
- return __generator(this, function (_b) {
142
- switch (_b.label) {
143
- case 0: return [4 /*yield*/, (0, http_dispatcher_1.getDefaultDispatcher)()];
140
+ var transport, fetchImpl, response, _a;
141
+ var _b;
142
+ return __generator(this, function (_c) {
143
+ switch (_c.label) {
144
+ case 0: return [4 /*yield*/, (0, http_dispatcher_1.getDefaultTransport)()
145
+ // undici's `fetch` and the global `fetch` are the same function at runtime
146
+ // but carry different (undici vs DOM) `RequestInit`/`Response` types. Call
147
+ // through the global signature, which matches the global-typed `options`.
148
+ ];
144
149
  case 1:
145
- dispatcher = _b.sent();
146
- if (!dispatcher) return [3 /*break*/, 3];
147
- return [4 /*yield*/, fetch(url, __assign(__assign({}, options), { signal: signal,
150
+ transport = _c.sent();
151
+ fetchImpl = ((_b = transport === null || transport === void 0 ? void 0 : transport.fetch) !== null && _b !== void 0 ? _b : fetch);
152
+ if (!(transport === null || transport === void 0 ? void 0 : transport.dispatcher)) return [3 /*break*/, 3];
153
+ return [4 /*yield*/, fetchImpl(url, __assign(__assign({}, options), { signal: signal,
148
154
  // @ts-expect-error - dispatcher is valid for Node.js fetch but not in TS types
149
- dispatcher: dispatcher }))];
155
+ dispatcher: transport.dispatcher }))];
150
156
  case 2:
151
- _a = _b.sent();
157
+ _a = _c.sent();
152
158
  return [3 /*break*/, 5];
153
- case 3: return [4 /*yield*/, fetch(url, __assign(__assign({}, options), { signal: signal }))];
159
+ case 3: return [4 /*yield*/, fetchImpl(url, __assign(__assign({}, options), { signal: signal }))];
154
160
  case 4:
155
- _a = _b.sent();
156
- _b.label = 5;
161
+ _a = _c.sent();
162
+ _c.label = 5;
157
163
  case 5:
158
164
  response = _a;
159
165
  return [2 /*return*/, convertResponseToCustomFetch(response)];
@@ -78,8 +78,10 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
78
78
  return to.concat(ar || Array.prototype.slice.call(from));
79
79
  };
80
80
  Object.defineProperty(exports, "__esModule", { value: true });
81
+ exports.getDefaultTransport = getDefaultTransport;
81
82
  exports.getDefaultDispatcher = getDefaultDispatcher;
82
83
  exports.resetDefaultDispatcherForTests = resetDefaultDispatcherForTests;
84
+ exports.getDefaultFetch = getDefaultFetch;
83
85
  exports.suppressExperimentalWarningsSync = suppressExperimentalWarningsSync;
84
86
  // Use effectively-disabled keep-alive so short-lived CLI processes do not stay
85
87
  // open waiting on idle sockets. Undici requires positive values, so we use 1ms.
@@ -87,41 +89,76 @@ var keepAliveOptions = {
87
89
  keepAliveTimeout: 1,
88
90
  keepAliveMaxTimeout: 1,
89
91
  };
90
- var defaultDispatcher;
91
- var defaultDispatcherPromise;
92
- function getDefaultDispatcher() {
92
+ var defaultTransport;
93
+ var defaultTransportPromise;
94
+ /**
95
+ * The default dispatcher and its paired `fetch`, as a single value so the two
96
+ * are always read consistently. Resolves to `undefined` outside Node (browser/
97
+ * edge), where callers use the global `fetch` with no dispatcher.
98
+ */
99
+ function getDefaultTransport() {
93
100
  return __awaiter(this, void 0, void 0, function () {
94
101
  return __generator(this, function (_a) {
95
- if (defaultDispatcher) {
96
- return [2 /*return*/, defaultDispatcher];
102
+ if (defaultTransport) {
103
+ return [2 /*return*/, defaultTransport];
97
104
  }
98
- if (!defaultDispatcherPromise) {
99
- defaultDispatcherPromise = createDefaultDispatcher()
100
- .then(function (dispatcher) {
101
- defaultDispatcher = dispatcher;
102
- return dispatcher;
105
+ if (!defaultTransportPromise) {
106
+ defaultTransportPromise = createDefaultTransport()
107
+ .then(function (transport) {
108
+ defaultTransport = transport;
109
+ return transport;
103
110
  })
104
111
  .catch(function (error) {
105
- defaultDispatcher = undefined;
106
- defaultDispatcherPromise = undefined;
112
+ defaultTransport = undefined;
113
+ defaultTransportPromise = undefined;
107
114
  throw error;
108
115
  });
109
116
  }
110
- return [2 /*return*/, defaultDispatcherPromise];
117
+ return [2 /*return*/, defaultTransportPromise];
118
+ });
119
+ });
120
+ }
121
+ function getDefaultDispatcher() {
122
+ return __awaiter(this, void 0, void 0, function () {
123
+ var _a;
124
+ return __generator(this, function (_b) {
125
+ switch (_b.label) {
126
+ case 0: return [4 /*yield*/, getDefaultTransport()];
127
+ case 1: return [2 /*return*/, (_a = (_b.sent())) === null || _a === void 0 ? void 0 : _a.dispatcher];
128
+ }
111
129
  });
112
130
  });
113
131
  }
114
132
  function resetDefaultDispatcherForTests() {
115
- defaultDispatcher = undefined;
116
- defaultDispatcherPromise = undefined;
133
+ defaultTransport = undefined;
134
+ defaultTransportPromise = undefined;
135
+ }
136
+ /**
137
+ * The `fetch` implementation that must be used with the default dispatcher.
138
+ * Returns undici's own `fetch` on the full-undici Node path, or `undefined`
139
+ * (meaning: use the global `fetch`) in the browser/edge/Bun paths.
140
+ *
141
+ * Node's global `fetch` is backed by whatever undici version ships inside that
142
+ * Node release (6.x on Node 22 … 8.x on Node 26). Our dispatcher — and its
143
+ * `decompress` interceptor — comes from the npm `undici` package, which is a
144
+ * different version. Handing an npm-undici dispatcher to a mismatched built-in
145
+ * client makes gzip responses fail mid-stream with `terminated`. Sourcing
146
+ * `fetch` from the same npm `undici` keeps the whole request path on one
147
+ * version and removes the split.
148
+ *
149
+ * Only meaningful after {@link getDefaultTransport} has resolved, which is the
150
+ * one place that populates it.
151
+ */
152
+ function getDefaultFetch() {
153
+ return defaultTransport === null || defaultTransport === void 0 ? void 0 : defaultTransport.fetch;
117
154
  }
118
155
  function isNodeEnvironment() {
119
156
  var _a;
120
157
  return typeof process !== 'undefined' && typeof ((_a = process.versions) === null || _a === void 0 ? void 0 : _a.node) === 'string';
121
158
  }
122
- function createDefaultDispatcher() {
159
+ function createDefaultTransport() {
123
160
  return __awaiter(this, void 0, void 0, function () {
124
- var _a, EnvHttpProxyAgent, interceptors, decompress;
161
+ var _a, EnvHttpProxyAgent, interceptors, undiciFetch, agent, decompress;
125
162
  return __generator(this, function (_b) {
126
163
  switch (_b.label) {
127
164
  case 0:
@@ -130,9 +167,27 @@ function createDefaultDispatcher() {
130
167
  }
131
168
  return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require('undici')); })];
132
169
  case 1:
133
- _a = _b.sent(), EnvHttpProxyAgent = _a.EnvHttpProxyAgent, interceptors = _a.interceptors;
170
+ _a = _b.sent(), EnvHttpProxyAgent = _a.EnvHttpProxyAgent, interceptors = _a.interceptors, undiciFetch = _a.fetch;
171
+ agent = new EnvHttpProxyAgent(keepAliveOptions);
172
+ // Some runtimes report `process.versions.node` (so `isNodeEnvironment()`
173
+ // passes) but ship only a partial undici: `interceptors.decompress` is
174
+ // absent and dispatchers have no `.compose`. Bun is the common case. There
175
+ // the proxy agent alone is enough — Bun's `fetch` decompresses
176
+ // gzip/deflate/br/zstd natively — so skip the interceptor instead of
177
+ // crashing on the missing API. Optional chaining also guards a runtime that
178
+ // omits the `interceptors` export entirely.
179
+ if (typeof (interceptors === null || interceptors === void 0 ? void 0 : interceptors.decompress) !== 'function') {
180
+ // Bun: pair the agent with the global `fetch` (undefined), which
181
+ // decompresses natively.
182
+ return [2 /*return*/, { dispatcher: agent, fetch: undefined }];
183
+ }
134
184
  decompress = suppressExperimentalWarningsSync(function () { return interceptors.decompress(); });
135
- return [2 /*return*/, new EnvHttpProxyAgent(keepAliveOptions).compose(decompress)];
185
+ // Pair undici's own `fetch` with this dispatcher so the request client and
186
+ // the dispatcher stay on one undici version (see `getDefaultFetch`). The
187
+ // global `fetch` is backed by a different, Node-bundled undici; mixing the
188
+ // two makes the decompress interceptor terminate gzip responses on some
189
+ // Node versions.
190
+ return [2 /*return*/, { dispatcher: agent.compose(decompress), fetch: undiciFetch }];
136
191
  }
137
192
  });
138
193
  });
@@ -1,5 +1,72 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
13
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
14
+ return new (P || (P = Promise))(function (resolve, reject) {
15
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
16
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
18
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
19
+ });
20
+ };
21
+ var __generator = (this && this.__generator) || function (thisArg, body) {
22
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
23
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
24
+ function verb(n) { return function (v) { return step([n, v]); }; }
25
+ function step(op) {
26
+ if (f) throw new TypeError("Generator is already executing.");
27
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
28
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
29
+ if (y = 0, t) op = [op[0] & 2, t.value];
30
+ switch (op[0]) {
31
+ case 0: case 1: t = op; break;
32
+ case 4: _.label++; return { value: op[1], done: false };
33
+ case 5: _.label++; y = op[1]; op = [0]; continue;
34
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
35
+ default:
36
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
37
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
38
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
39
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
40
+ if (t[2]) _.ops.pop();
41
+ _.trys.pop(); continue;
42
+ }
43
+ op = body.call(thisArg, _);
44
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
45
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
46
+ }
47
+ };
1
48
  import { setupServer } from 'msw/node';
2
- import { afterAll, afterEach, beforeAll } from 'vitest';
49
+ import { afterAll, afterEach, beforeAll, vi } from 'vitest';
50
+ // In production the transport uses undici's own `fetch` (paired with the
51
+ // dispatcher) so the request client and dispatcher stay on one undici version.
52
+ // MSW intercepts the *global* `fetch`, not that separate undici instance, so
53
+ // force the transport onto the global `fetch` for every test in the suite;
54
+ // MSW then intercepts requests as usual. The real undici transport is covered
55
+ // directly by `http-dispatcher.test.ts` and `fetch-with-retry.test.ts`, which
56
+ // opt out of this seam.
57
+ vi.mock('../transport/http-dispatcher', function (importOriginal) { return __awaiter(void 0, void 0, void 0, function () {
58
+ var actual;
59
+ return __generator(this, function (_a) {
60
+ switch (_a.label) {
61
+ case 0: return [4 /*yield*/, importOriginal()];
62
+ case 1:
63
+ actual = _a.sent();
64
+ return [2 /*return*/, __assign(__assign({}, actual), { getDefaultTransport: vi.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
65
+ return [2 /*return*/, undefined];
66
+ }); }); }) })];
67
+ }
68
+ });
69
+ }); });
3
70
  // Create MSW server instance
4
71
  export var server = setupServer();
5
72
  // Start server before all tests
@@ -48,7 +48,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
48
48
  import { TwistRequestError } from '../types/errors.js';
49
49
  import { camelCaseKeys } from '../utils/case-conversion.js';
50
50
  import { transformTimestamps } from '../utils/timestamp-conversion.js';
51
- import { getDefaultDispatcher } from './http-dispatcher.js';
51
+ import { getDefaultTransport } from './http-dispatcher.js';
52
52
  export function fetchWithRetry(url_1, options_1) {
53
53
  return __awaiter(this, arguments, void 0, function (url, options, maxRetries, customFetch) {
54
54
  var lastError, attempt, clearTimeoutFn, requestSignal, timeoutResult, response, _a, responseText, responseData, camelCased, transformed, error_1, delay;
@@ -134,23 +134,29 @@ export function fetchWithRetry(url_1, options_1) {
134
134
  }
135
135
  function fetchWithDefaultTransport(url, options, signal) {
136
136
  return __awaiter(this, void 0, void 0, function () {
137
- var dispatcher, response, _a;
138
- return __generator(this, function (_b) {
139
- switch (_b.label) {
140
- case 0: return [4 /*yield*/, getDefaultDispatcher()];
137
+ var transport, fetchImpl, response, _a;
138
+ var _b;
139
+ return __generator(this, function (_c) {
140
+ switch (_c.label) {
141
+ case 0: return [4 /*yield*/, getDefaultTransport()
142
+ // undici's `fetch` and the global `fetch` are the same function at runtime
143
+ // but carry different (undici vs DOM) `RequestInit`/`Response` types. Call
144
+ // through the global signature, which matches the global-typed `options`.
145
+ ];
141
146
  case 1:
142
- dispatcher = _b.sent();
143
- if (!dispatcher) return [3 /*break*/, 3];
144
- return [4 /*yield*/, fetch(url, __assign(__assign({}, options), { signal: signal,
147
+ transport = _c.sent();
148
+ fetchImpl = ((_b = transport === null || transport === void 0 ? void 0 : transport.fetch) !== null && _b !== void 0 ? _b : fetch);
149
+ if (!(transport === null || transport === void 0 ? void 0 : transport.dispatcher)) return [3 /*break*/, 3];
150
+ return [4 /*yield*/, fetchImpl(url, __assign(__assign({}, options), { signal: signal,
145
151
  // @ts-expect-error - dispatcher is valid for Node.js fetch but not in TS types
146
- dispatcher: dispatcher }))];
152
+ dispatcher: transport.dispatcher }))];
147
153
  case 2:
148
- _a = _b.sent();
154
+ _a = _c.sent();
149
155
  return [3 /*break*/, 5];
150
- case 3: return [4 /*yield*/, fetch(url, __assign(__assign({}, options), { signal: signal }))];
156
+ case 3: return [4 /*yield*/, fetchImpl(url, __assign(__assign({}, options), { signal: signal }))];
151
157
  case 4:
152
- _a = _b.sent();
153
- _b.label = 5;
158
+ _a = _c.sent();
159
+ _c.label = 5;
154
160
  case 5:
155
161
  response = _a;
156
162
  return [2 /*return*/, convertResponseToCustomFetch(response)];
@@ -49,59 +49,105 @@ var keepAliveOptions = {
49
49
  keepAliveTimeout: 1,
50
50
  keepAliveMaxTimeout: 1,
51
51
  };
52
- var defaultDispatcher;
53
- var defaultDispatcherPromise;
54
- export function getDefaultDispatcher() {
52
+ var defaultTransport;
53
+ var defaultTransportPromise;
54
+ /**
55
+ * The default dispatcher and its paired `fetch`, as a single value so the two
56
+ * are always read consistently. Resolves to `undefined` outside Node (browser/
57
+ * edge), where callers use the global `fetch` with no dispatcher.
58
+ */
59
+ export function getDefaultTransport() {
55
60
  return __awaiter(this, void 0, void 0, function () {
56
61
  return __generator(this, function (_a) {
57
- if (defaultDispatcher) {
58
- return [2 /*return*/, defaultDispatcher];
62
+ if (defaultTransport) {
63
+ return [2 /*return*/, defaultTransport];
59
64
  }
60
- if (!defaultDispatcherPromise) {
61
- defaultDispatcherPromise = createDefaultDispatcher()
62
- .then(function (dispatcher) {
63
- defaultDispatcher = dispatcher;
64
- return dispatcher;
65
+ if (!defaultTransportPromise) {
66
+ defaultTransportPromise = createDefaultTransport()
67
+ .then(function (transport) {
68
+ defaultTransport = transport;
69
+ return transport;
65
70
  })
66
71
  .catch(function (error) {
67
- defaultDispatcher = undefined;
68
- defaultDispatcherPromise = undefined;
72
+ defaultTransport = undefined;
73
+ defaultTransportPromise = undefined;
69
74
  throw error;
70
75
  });
71
76
  }
72
- return [2 /*return*/, defaultDispatcherPromise];
77
+ return [2 /*return*/, defaultTransportPromise];
78
+ });
79
+ });
80
+ }
81
+ export function getDefaultDispatcher() {
82
+ return __awaiter(this, void 0, void 0, function () {
83
+ var _a;
84
+ return __generator(this, function (_b) {
85
+ switch (_b.label) {
86
+ case 0: return [4 /*yield*/, getDefaultTransport()];
87
+ case 1: return [2 /*return*/, (_a = (_b.sent())) === null || _a === void 0 ? void 0 : _a.dispatcher];
88
+ }
73
89
  });
74
90
  });
75
91
  }
76
92
  export function resetDefaultDispatcherForTests() {
77
- defaultDispatcher = undefined;
78
- defaultDispatcherPromise = undefined;
93
+ defaultTransport = undefined;
94
+ defaultTransportPromise = undefined;
95
+ }
96
+ /**
97
+ * The `fetch` implementation that must be used with the default dispatcher.
98
+ * Returns undici's own `fetch` on the full-undici Node path, or `undefined`
99
+ * (meaning: use the global `fetch`) in the browser/edge/Bun paths.
100
+ *
101
+ * Node's global `fetch` is backed by whatever undici version ships inside that
102
+ * Node release (6.x on Node 22 … 8.x on Node 26). Our dispatcher — and its
103
+ * `decompress` interceptor — comes from the npm `undici` package, which is a
104
+ * different version. Handing an npm-undici dispatcher to a mismatched built-in
105
+ * client makes gzip responses fail mid-stream with `terminated`. Sourcing
106
+ * `fetch` from the same npm `undici` keeps the whole request path on one
107
+ * version and removes the split.
108
+ *
109
+ * Only meaningful after {@link getDefaultTransport} has resolved, which is the
110
+ * one place that populates it.
111
+ */
112
+ export function getDefaultFetch() {
113
+ return defaultTransport === null || defaultTransport === void 0 ? void 0 : defaultTransport.fetch;
79
114
  }
80
115
  function isNodeEnvironment() {
81
116
  var _a;
82
117
  return typeof process !== 'undefined' && typeof ((_a = process.versions) === null || _a === void 0 ? void 0 : _a.node) === 'string';
83
118
  }
84
- function createDefaultDispatcher() {
119
+ function createDefaultTransport() {
85
120
  return __awaiter(this, void 0, void 0, function () {
86
- var _a, EnvHttpProxyAgent, interceptors, decompress;
121
+ var _a, EnvHttpProxyAgent, interceptors, undiciFetch, agent, decompress;
87
122
  return __generator(this, function (_b) {
88
123
  switch (_b.label) {
89
124
  case 0:
90
125
  if (!isNodeEnvironment()) {
91
126
  return [2 /*return*/, undefined];
92
127
  }
93
- return [4 /*yield*/, import('undici')
94
- // Compose the response-decompression interceptor so gzip/deflate/br/zstd
95
- // bodies are decoded before consumers parse them. Required on Node 24+:
96
- // attaching any custom dispatcher to the global `fetch` strips the
97
- // `content-encoding` header but does not actually decompress the body,
98
- // so callers receive raw gzipped bytes and `JSON.parse` fails.
99
- // See https://github.com/Doist/todoist-cli/issues/318.
100
- ];
128
+ return [4 /*yield*/, import('undici')];
101
129
  case 1:
102
- _a = _b.sent(), EnvHttpProxyAgent = _a.EnvHttpProxyAgent, interceptors = _a.interceptors;
130
+ _a = _b.sent(), EnvHttpProxyAgent = _a.EnvHttpProxyAgent, interceptors = _a.interceptors, undiciFetch = _a.fetch;
131
+ agent = new EnvHttpProxyAgent(keepAliveOptions);
132
+ // Some runtimes report `process.versions.node` (so `isNodeEnvironment()`
133
+ // passes) but ship only a partial undici: `interceptors.decompress` is
134
+ // absent and dispatchers have no `.compose`. Bun is the common case. There
135
+ // the proxy agent alone is enough — Bun's `fetch` decompresses
136
+ // gzip/deflate/br/zstd natively — so skip the interceptor instead of
137
+ // crashing on the missing API. Optional chaining also guards a runtime that
138
+ // omits the `interceptors` export entirely.
139
+ if (typeof (interceptors === null || interceptors === void 0 ? void 0 : interceptors.decompress) !== 'function') {
140
+ // Bun: pair the agent with the global `fetch` (undefined), which
141
+ // decompresses natively.
142
+ return [2 /*return*/, { dispatcher: agent, fetch: undefined }];
143
+ }
103
144
  decompress = suppressExperimentalWarningsSync(function () { return interceptors.decompress(); });
104
- return [2 /*return*/, new EnvHttpProxyAgent(keepAliveOptions).compose(decompress)];
145
+ // Pair undici's own `fetch` with this dispatcher so the request client and
146
+ // the dispatcher stay on one undici version (see `getDefaultFetch`). The
147
+ // global `fetch` is backed by a different, Node-bundled undici; mixing the
148
+ // two makes the decompress interceptor terminate gzip responses on some
149
+ // Node versions.
150
+ return [2 /*return*/, { dispatcher: agent.compose(decompress), fetch: undiciFetch }];
105
151
  }
106
152
  });
107
153
  });
@@ -1,4 +1,40 @@
1
1
  import type { Dispatcher } from 'undici';
2
+ type UndiciFetch = typeof import('undici').fetch;
3
+ /**
4
+ * A dispatcher and the `fetch` that must be used with it. `fetch` is undici's
5
+ * own `fetch` on the full-undici Node path, or `undefined` (meaning: use the
6
+ * global `fetch`) on the Bun path. The two are cached together so callers can
7
+ * never observe a dispatcher paired with a mismatched `fetch` — see
8
+ * {@link getDefaultFetch} for why the pairing matters.
9
+ */
10
+ type DefaultTransport = {
11
+ dispatcher: Dispatcher;
12
+ fetch: UndiciFetch | undefined;
13
+ };
14
+ /**
15
+ * The default dispatcher and its paired `fetch`, as a single value so the two
16
+ * are always read consistently. Resolves to `undefined` outside Node (browser/
17
+ * edge), where callers use the global `fetch` with no dispatcher.
18
+ */
19
+ export declare function getDefaultTransport(): Promise<DefaultTransport | undefined>;
2
20
  export declare function getDefaultDispatcher(): Promise<Dispatcher | undefined>;
3
21
  export declare function resetDefaultDispatcherForTests(): void;
22
+ /**
23
+ * The `fetch` implementation that must be used with the default dispatcher.
24
+ * Returns undici's own `fetch` on the full-undici Node path, or `undefined`
25
+ * (meaning: use the global `fetch`) in the browser/edge/Bun paths.
26
+ *
27
+ * Node's global `fetch` is backed by whatever undici version ships inside that
28
+ * Node release (6.x on Node 22 … 8.x on Node 26). Our dispatcher — and its
29
+ * `decompress` interceptor — comes from the npm `undici` package, which is a
30
+ * different version. Handing an npm-undici dispatcher to a mismatched built-in
31
+ * client makes gzip responses fail mid-stream with `terminated`. Sourcing
32
+ * `fetch` from the same npm `undici` keeps the whole request path on one
33
+ * version and removes the split.
34
+ *
35
+ * Only meaningful after {@link getDefaultTransport} has resolved, which is the
36
+ * one place that populates it.
37
+ */
38
+ export declare function getDefaultFetch(): UndiciFetch | undefined;
4
39
  export declare function suppressExperimentalWarningsSync<T>(fn: () => T): T;
40
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doist/twist-sdk",
3
- "version": "2.9.2",
3
+ "version": "2.9.4",
4
4
  "description": "A TypeScript wrapper for the Twist REST API.",
5
5
  "author": "Doist developers",
6
6
  "homepage": "https://doist.github.io/twist-sdk-typescript/",