@softwear/latestcollectioncore 1.0.161 → 1.0.163

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.
@@ -99,6 +99,28 @@ function buildAggregationEvaluator(aggregationExpression) {
99
99
  function prepareSubTotalAggKey(aggKey) {
100
100
  return aggKey.split('\t').slice(0, -1).join('\t').concat(' Σ');
101
101
  }
102
+ /** Same as filtrex std.coerceBoolean(customProp(...)) for bare join keys on the transaction */
103
+ function coerceJoinKeyValue(v) {
104
+ return typeof v === 'boolean' ? +v : v;
105
+ }
106
+ function getTrieLeaf(root, transaction, keys) {
107
+ const nrKeys = keys.length;
108
+ if (nrKeys === 0)
109
+ return root;
110
+ let node = root;
111
+ for (let i = 0; i < nrKeys; i++) {
112
+ const k = coerceJoinKeyValue(transaction[keys[i]]);
113
+ if (!node.children)
114
+ node.children = new Map();
115
+ let next = node.children.get(k);
116
+ if (!next) {
117
+ next = {};
118
+ node.children.set(k, next);
119
+ }
120
+ node = next;
121
+ }
122
+ return node;
123
+ }
102
124
  /*
103
125
  * A factory function that returns a function that can be used to aggregate transactions
104
126
  * The factory function is called once for each aggregationExpression
@@ -122,47 +144,36 @@ function prepareSubTotalAggKey(aggKey) {
122
144
  * @returns {Function} - The aggregator function
123
145
  */
124
146
  function aggregator({ beginTimestamp, endTimestamp, filterExpression, aggregationExpression, rawAggregations, maxRows, totals, subtotalsForOuterGrouper, parentGroupTotals, }) {
125
- const filterExpressionCache = {};
126
147
  let filter;
127
- let filterDependencyEvaluator;
148
+ let filterGranularity = [];
149
+ const filterTrieRoot = {};
128
150
  if (filterExpression) {
129
151
  filter = (0, filtrex_1.compileExpression)(filterExpression, filtrexOptions);
130
- // Maintain a cache of evaluated filterExpressions
131
- // The granularity of the cache is determined by the joined tables in the filterExpression
132
- const filterGranularity = Object.entries(globalJoinableTables)
152
+ filterGranularity = Object.entries(globalJoinableTables)
133
153
  .filter((join) => filterExpression.includes(join[0] + '.'))
134
154
  .map((join) => join[1]);
135
- filterDependencyEvaluator = (0, filtrex_1.compileExpression)(filterGranularity.join('+'), filtrexOptions);
136
155
  }
137
156
  const aggregationEvaluator = buildAggregationEvaluator(aggregationExpression);
138
- // Maintain a cache of evaluated aggregationExpressions
139
- // The granularity of the cache is determined by the joined tables in the aggregationExpression
140
- const aggregationExpressionCache = {};
141
157
  const aggregationGranularity = Object.entries(globalJoinableTables)
142
158
  .filter((join) => aggregationExpression.includes(join[0] + '.'))
143
159
  .map((join) => join[1]);
144
- let aggregationDependencyExpression = aggregationGranularity.join('+');
145
- if (!aggregationDependencyExpression)
146
- aggregationDependencyExpression = '"N.A."';
147
- const aggregationDependencyEvaluator = (0, filtrex_1.compileExpression)(aggregationDependencyExpression, filtrexOptions);
160
+ const aggregationTrieRoot = {};
148
161
  return function (transaction) {
149
162
  if (filter) {
150
- const filterDependency = filterDependencyEvaluator(transaction);
151
- const filterExpression = filterExpressionCache[filterDependency];
152
- if (filterExpression == -1)
163
+ const filterLeaf = getTrieLeaf(filterTrieRoot, transaction, filterGranularity);
164
+ if (filterLeaf.filterResult === -1)
153
165
  return;
154
- if (!filterExpression) {
155
- filterExpressionCache[filterDependency] = filter(transaction) ? 1 : -1;
156
- if (filterExpressionCache[filterDependency] == -1)
166
+ if (filterLeaf.filterResult === undefined) {
167
+ filterLeaf.filterResult = filter(transaction) ? 1 : -1;
168
+ if (filterLeaf.filterResult === -1)
157
169
  return;
158
170
  }
159
171
  }
160
- const aggregationDependency = aggregationDependencyEvaluator(transaction);
161
- let aggregateKey = aggregationExpressionCache[aggregationDependency];
162
- if (!aggregateKey) {
163
- aggregateKey = aggregationEvaluator(transaction);
164
- aggregationExpressionCache[aggregationDependency] = aggregateKey;
172
+ const aggregateKeyLeaf = getTrieLeaf(aggregationTrieRoot, transaction, aggregationGranularity);
173
+ if (!aggregateKeyLeaf.aggregateKey) {
174
+ aggregateKeyLeaf.aggregateKey = aggregationEvaluator(transaction);
165
175
  }
176
+ const aggregateKey = aggregateKeyLeaf.aggregateKey;
166
177
  // Prepare joined values for derived fields
167
178
  if (transaction.type == types_1.transactionTypeE.SALE) {
168
179
  const sku = globalRelations['sku'][transaction.ean];
package/dist/lcAxios.d.ts CHANGED
@@ -13,11 +13,15 @@ export interface AxiosRequestConfig<D = any> {
13
13
  params?: Record<string, any>;
14
14
  headers?: Record<string, string | undefined> | any;
15
15
  timeout?: number;
16
- responseType?: 'json' | 'text' | 'blob' | 'arraybuffer';
16
+ responseType?: 'json' | 'text' | 'blob' | 'arraybuffer' | 'stream';
17
17
  validateStatus?: (status: number) => boolean;
18
18
  transformRequest?: AxiosTransformer | AxiosTransformer[];
19
19
  transformResponse?: AxiosTransformer | AxiosTransformer[];
20
20
  withCredentials?: boolean;
21
+ auth?: {
22
+ username?: string;
23
+ password?: string;
24
+ };
21
25
  /** Axios-only; ignored — fetch handles Content-Encoding when reading the body */
22
26
  decompress?: boolean;
23
27
  }
package/dist/lcAxios.js CHANGED
@@ -15,6 +15,25 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
15
15
  step((generator = generator.apply(thisArg, _arguments || [])).next());
16
16
  });
17
17
  };
18
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
19
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
20
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
21
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
22
+ return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
23
+ function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
24
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
25
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
26
+ function fulfill(value) { resume("next", value); }
27
+ function reject(value) { resume("throw", value); }
28
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
29
+ };
30
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
31
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
32
+ var m = o[Symbol.asyncIterator], i;
33
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
34
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
35
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
36
+ };
18
37
  Object.defineProperty(exports, "__esModule", { value: true });
19
38
  exports.isAxiosError = exports.lcAxios = exports.createAxiosInstance = exports.axiosRetry = exports.exponentialDelay = void 0;
20
39
  function isAxiosError(payload) {
@@ -113,6 +132,72 @@ function flattenHeaders(h, method) {
113
132
  }
114
133
  return out;
115
134
  }
135
+ function encodeBase64(input) {
136
+ if (typeof Buffer !== 'undefined')
137
+ return Buffer.from(input, 'utf8').toString('base64');
138
+ if (typeof btoa !== 'undefined')
139
+ return btoa(input);
140
+ throw new Error('Base64 encoding unavailable');
141
+ }
142
+ function applyAuthHeader(headers, auth) {
143
+ if (!auth)
144
+ return;
145
+ const username = auth.username || '';
146
+ const password = auth.password || '';
147
+ headers['Authorization'] = `Basic ${encodeBase64(`${username}:${password}`)}`;
148
+ }
149
+ function createPipeableStream(body) {
150
+ return {
151
+ [Symbol.asyncIterator]() {
152
+ return __asyncGenerator(this, arguments, function* _a() {
153
+ if (!body)
154
+ return yield __await(void 0);
155
+ const reader = body.getReader();
156
+ try {
157
+ while (true) {
158
+ const { done, value } = yield __await(reader.read());
159
+ if (done)
160
+ break;
161
+ if (value)
162
+ yield yield __await(value);
163
+ }
164
+ }
165
+ finally {
166
+ reader.releaseLock();
167
+ }
168
+ });
169
+ },
170
+ pipe(destination) {
171
+ ;
172
+ (() => __awaiter(this, void 0, void 0, function* () {
173
+ var _a, e_1, _b, _c;
174
+ try {
175
+ for (var _d = true, _e = __asyncValues(this), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
176
+ _c = _f.value;
177
+ _d = false;
178
+ try {
179
+ const chunk = _c;
180
+ destination.write(chunk);
181
+ }
182
+ finally {
183
+ _d = true;
184
+ }
185
+ }
186
+ }
187
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
188
+ finally {
189
+ try {
190
+ if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
191
+ }
192
+ finally { if (e_1) throw e_1.error; }
193
+ }
194
+ if (destination.end)
195
+ destination.end();
196
+ }))();
197
+ return destination;
198
+ },
199
+ };
200
+ }
116
201
  function applyTransformRequest(fns, data, headers) {
117
202
  return __awaiter(this, void 0, void 0, function* () {
118
203
  if (!fns)
@@ -176,7 +261,11 @@ exports.axiosRetry = axiosRetry;
176
261
  const defaultValidateStatus = (status) => status >= 200 && status < 300;
177
262
  function getDefaultDefaults() {
178
263
  return {
179
- headers: { common: {} },
264
+ headers: {
265
+ common: {
266
+ Accept: 'application/json, text/plain, */*',
267
+ },
268
+ },
180
269
  transformRequest: [
181
270
  function defaultTransformRequest(data, headers) {
182
271
  var _a, _b;
@@ -221,6 +310,7 @@ function createAxiosInstance(initialDefaults) {
221
310
  const method = (merged.method || 'get').toUpperCase();
222
311
  const url = buildURL(merged.baseURL, merged.url, merged.params);
223
312
  const headers = Object.assign({}, flattenHeaders(merged.headers, method));
313
+ applyAuthHeader(headers, merged.auth);
224
314
  let data = merged.data;
225
315
  data = yield applyTransformRequest(merged.transformRequest, data, headers);
226
316
  const validateStatus = merged.validateStatus || defaultValidateStatus;
@@ -266,6 +356,9 @@ function createAxiosInstance(initialDefaults) {
266
356
  else if (rt === 'arraybuffer') {
267
357
  parsed = yield res.arrayBuffer();
268
358
  }
359
+ else if (rt === 'stream') {
360
+ parsed = createPipeableStream(res.body || null);
361
+ }
269
362
  else if (rt === 'text') {
270
363
  parsed = yield res.text();
271
364
  }
@@ -289,7 +382,9 @@ function createAxiosInstance(initialDefaults) {
289
382
  parsed = text;
290
383
  }
291
384
  }
292
- parsed = applyTransformResponse(merged.transformResponse, parsed, resHeaders);
385
+ if (rt !== 'stream') {
386
+ parsed = applyTransformResponse(merged.transformResponse, parsed, resHeaders);
387
+ }
293
388
  const response = {
294
389
  data: parsed,
295
390
  status: res.status,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwear/latestcollectioncore",
3
- "version": "1.0.161",
3
+ "version": "1.0.163",
4
4
  "description": "Core functions for LatestCollections applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -167,6 +167,31 @@ function prepareSubTotalAggKey(aggKey) {
167
167
  return aggKey.split('\t').slice(0, -1).join('\t').concat(' Σ')
168
168
  }
169
169
 
170
+ /** Same as filtrex std.coerceBoolean(customProp(...)) for bare join keys on the transaction */
171
+ function coerceJoinKeyValue(v: unknown): unknown {
172
+ return typeof v === 'boolean' ? +v : v
173
+ }
174
+
175
+ type AggregateKeyTrieNode = { aggregateKey?: string; children?: Map<unknown, AggregateKeyTrieNode> }
176
+ type FilterResultTrieNode = { filterResult?: 1 | -1; children?: Map<unknown, FilterResultTrieNode> }
177
+
178
+ function getTrieLeaf<T extends { children?: Map<unknown, T> }>(root: T, transaction: TransactionI, keys: string[]): T {
179
+ const nrKeys = keys.length
180
+ if (nrKeys === 0) return root
181
+ let node = root
182
+ for (let i = 0; i < nrKeys; i++) {
183
+ const k = coerceJoinKeyValue(transaction[keys[i] as keyof TransactionI] as unknown)
184
+ if (!node.children) node.children = new Map()
185
+ let next = node.children.get(k) as T | undefined
186
+ if (!next) {
187
+ next = {} as T
188
+ node.children.set(k, next)
189
+ }
190
+ node = next
191
+ }
192
+ return node
193
+ }
194
+
170
195
  /*
171
196
  * A factory function that returns a function that can be used to aggregate transactions
172
197
  * The factory function is called once for each aggregationExpression
@@ -200,46 +225,35 @@ function aggregator({
200
225
  subtotalsForOuterGrouper,
201
226
  parentGroupTotals,
202
227
  }: AggregatorFnI) {
203
- const filterExpressionCache = {}
204
-
205
- let filter
206
- let filterDependencyEvaluator
228
+ let filter: ((transaction: TransactionI) => unknown) | undefined
229
+ let filterGranularity: string[] = []
230
+ const filterTrieRoot: FilterResultTrieNode = {}
207
231
  if (filterExpression) {
208
232
  filter = compileExpression(filterExpression, filtrexOptions)
209
- // Maintain a cache of evaluated filterExpressions
210
- // The granularity of the cache is determined by the joined tables in the filterExpression
211
- const filterGranularity = Object.entries(globalJoinableTables)
233
+ filterGranularity = Object.entries(globalJoinableTables)
212
234
  .filter((join) => filterExpression.includes(join[0] + '.'))
213
235
  .map((join) => join[1])
214
- filterDependencyEvaluator = compileExpression(filterGranularity.join('+'), filtrexOptions)
215
236
  }
216
237
  const aggregationEvaluator = buildAggregationEvaluator(aggregationExpression)
217
- // Maintain a cache of evaluated aggregationExpressions
218
- // The granularity of the cache is determined by the joined tables in the aggregationExpression
219
- const aggregationExpressionCache = {}
220
238
  const aggregationGranularity = Object.entries(globalJoinableTables)
221
239
  .filter((join) => aggregationExpression.includes(join[0] + '.'))
222
240
  .map((join) => join[1])
241
+ const aggregationTrieRoot: AggregateKeyTrieNode = {}
223
242
 
224
- let aggregationDependencyExpression = aggregationGranularity.join('+')
225
- if (!aggregationDependencyExpression) aggregationDependencyExpression = '"N.A."'
226
- const aggregationDependencyEvaluator = compileExpression(aggregationDependencyExpression, filtrexOptions)
227
243
  return function (transaction: TransactionI): void {
228
244
  if (filter) {
229
- const filterDependency = filterDependencyEvaluator(transaction)
230
- const filterExpression = filterExpressionCache[filterDependency]
231
- if (filterExpression == -1) return
232
- if (!filterExpression) {
233
- filterExpressionCache[filterDependency] = filter(transaction) ? 1 : -1
234
- if (filterExpressionCache[filterDependency] == -1) return
245
+ const filterLeaf = getTrieLeaf<FilterResultTrieNode>(filterTrieRoot, transaction, filterGranularity)
246
+ if (filterLeaf.filterResult === -1) return
247
+ if (filterLeaf.filterResult === undefined) {
248
+ filterLeaf.filterResult = filter(transaction) ? 1 : -1
249
+ if (filterLeaf.filterResult === -1) return
235
250
  }
236
251
  }
237
- const aggregationDependency = aggregationDependencyEvaluator(transaction)
238
- let aggregateKey = aggregationExpressionCache[aggregationDependency]
239
- if (!aggregateKey) {
240
- aggregateKey = aggregationEvaluator(transaction)
241
- aggregationExpressionCache[aggregationDependency] = aggregateKey
252
+ const aggregateKeyLeaf = getTrieLeaf<AggregateKeyTrieNode>(aggregationTrieRoot, transaction, aggregationGranularity)
253
+ if (!aggregateKeyLeaf.aggregateKey) {
254
+ aggregateKeyLeaf.aggregateKey = aggregationEvaluator(transaction) as string
242
255
  }
256
+ const aggregateKey = aggregateKeyLeaf.aggregateKey as string
243
257
  // Prepare joined values for derived fields
244
258
  if (transaction.type == transactionTypeE.SALE) {
245
259
  const sku = globalRelations['sku'][transaction.ean]
package/src/lcAxios.ts CHANGED
@@ -14,11 +14,15 @@ export interface AxiosRequestConfig<D = any> {
14
14
  params?: Record<string, any>
15
15
  headers?: Record<string, string | undefined> | any
16
16
  timeout?: number
17
- responseType?: 'json' | 'text' | 'blob' | 'arraybuffer'
17
+ responseType?: 'json' | 'text' | 'blob' | 'arraybuffer' | 'stream'
18
18
  validateStatus?: (status: number) => boolean
19
19
  transformRequest?: AxiosTransformer | AxiosTransformer[]
20
20
  transformResponse?: AxiosTransformer | AxiosTransformer[]
21
21
  withCredentials?: boolean
22
+ auth?: {
23
+ username?: string
24
+ password?: string
25
+ }
22
26
  /** Axios-only; ignored — fetch handles Content-Encoding when reading the body */
23
27
  decompress?: boolean
24
28
  }
@@ -129,6 +133,44 @@ function flattenHeaders(h: any, method: string): Record<string, string> {
129
133
  return out
130
134
  }
131
135
 
136
+ function encodeBase64(input: string): string {
137
+ if (typeof Buffer !== 'undefined') return Buffer.from(input, 'utf8').toString('base64')
138
+ if (typeof btoa !== 'undefined') return btoa(input)
139
+ throw new Error('Base64 encoding unavailable')
140
+ }
141
+
142
+ function applyAuthHeader(headers: Record<string, string>, auth: AxiosRequestConfig['auth']) {
143
+ if (!auth) return
144
+ const username = auth.username || ''
145
+ const password = auth.password || ''
146
+ headers['Authorization'] = `Basic ${encodeBase64(`${username}:${password}`)}`
147
+ }
148
+
149
+ function createPipeableStream(body: ReadableStream<Uint8Array> | null) {
150
+ return {
151
+ async *[Symbol.asyncIterator]() {
152
+ if (!body) return
153
+ const reader = body.getReader()
154
+ try {
155
+ while (true) {
156
+ const { done, value } = await reader.read()
157
+ if (done) break
158
+ if (value) yield value
159
+ }
160
+ } finally {
161
+ reader.releaseLock()
162
+ }
163
+ },
164
+ pipe(destination: { write: (chunk: Uint8Array) => any; end?: () => any }) {
165
+ ;(async () => {
166
+ for await (const chunk of this) destination.write(chunk)
167
+ if (destination.end) destination.end()
168
+ })()
169
+ return destination
170
+ },
171
+ }
172
+ }
173
+
132
174
  async function applyTransformRequest(fns: AxiosTransformer | AxiosTransformer[] | undefined, data: any, headers: any): Promise<any> {
133
175
  if (!fns) return data
134
176
  const list = Array.isArray(fns) ? fns : [fns]
@@ -193,7 +235,11 @@ const defaultValidateStatus = (status: number) => status >= 200 && status < 300
193
235
 
194
236
  function getDefaultDefaults(): AxiosRequestConfig {
195
237
  return {
196
- headers: { common: {} },
238
+ headers: {
239
+ common: {
240
+ Accept: 'application/json, text/plain, */*',
241
+ },
242
+ },
197
243
  transformRequest: [
198
244
  function defaultTransformRequest(data: any, headers: any) {
199
245
  if (data == null) return data
@@ -231,6 +277,7 @@ export function createAxiosInstance(initialDefaults?: AxiosRequestConfig): any {
231
277
  const method = (merged.method || 'get').toUpperCase()
232
278
  const url = buildURL(merged.baseURL, merged.url, merged.params)
233
279
  const headers: any = { ...flattenHeaders(merged.headers, method) }
280
+ applyAuthHeader(headers, merged.auth)
234
281
 
235
282
  let data = merged.data
236
283
  data = await applyTransformRequest(merged.transformRequest, data, headers)
@@ -279,6 +326,8 @@ export function createAxiosInstance(initialDefaults?: AxiosRequestConfig): any {
279
326
  parsed = await res.blob()
280
327
  } else if (rt === 'arraybuffer') {
281
328
  parsed = await res.arrayBuffer()
329
+ } else if (rt === 'stream') {
330
+ parsed = createPipeableStream((res.body as ReadableStream<Uint8Array> | null) || null)
282
331
  } else if (rt === 'text') {
283
332
  parsed = await res.text()
284
333
  } else if (ct.includes('application/json')) {
@@ -299,7 +348,9 @@ export function createAxiosInstance(initialDefaults?: AxiosRequestConfig): any {
299
348
  }
300
349
  }
301
350
 
302
- parsed = applyTransformResponse(merged.transformResponse, parsed, resHeaders)
351
+ if (rt !== 'stream') {
352
+ parsed = applyTransformResponse(merged.transformResponse, parsed, resHeaders)
353
+ }
303
354
 
304
355
  const response: AxiosResponse = {
305
356
  data: parsed,
@@ -10,6 +10,14 @@ function jsonResponse(body: unknown, status = 200) {
10
10
  })
11
11
  }
12
12
 
13
+ function textResponse(body: string, status = 200) {
14
+ return new Response(body, {
15
+ status,
16
+ statusText: status === 200 ? 'OK' : 'Error',
17
+ headers: { 'Content-Type': 'text/plain' },
18
+ })
19
+ }
20
+
13
21
  describe('lcAxios', () => {
14
22
  beforeEach(() => {
15
23
  vi.stubGlobal(
@@ -34,6 +42,26 @@ describe('lcAxios', () => {
34
42
  expect(init?.body).toBeUndefined()
35
43
  })
36
44
 
45
+ it('GET sends axios-style default Accept header', async () => {
46
+ const client = axios.create()
47
+ await client.get('https://api.example.com/accept')
48
+
49
+ const [, init] = (fetch as ReturnType<typeof vi.fn>).mock.calls[0]
50
+ const headers = init?.headers as Record<string, string>
51
+ expect(headers.Accept).toBe('application/json, text/plain, */*')
52
+ })
53
+
54
+ it('auth config sets Authorization basic header', async () => {
55
+ const client = axios.create()
56
+ await client.get('https://api.example.com/auth', {
57
+ auth: { username: 'aladdin', password: 'opensesame' },
58
+ })
59
+
60
+ const [, init] = (fetch as ReturnType<typeof vi.fn>).mock.calls[0]
61
+ const headers = init?.headers as Record<string, string>
62
+ expect(headers.Authorization).toBe('Basic YWxhZGRpbjpvcGVuc2VzYW1l')
63
+ })
64
+
37
65
  it('GET with decompress: true (axios compat) still works; option not passed to fetch', async () => {
38
66
  const client = axios.create()
39
67
  await client.get('https://api.example.com/x', { decompress: true })
@@ -131,6 +159,30 @@ describe('lcAxios', () => {
131
159
  expect(res.data).toEqual({ missing: true })
132
160
  })
133
161
 
162
+ it('responseType stream returns a pipeable async iterable body', async () => {
163
+ vi.mocked(fetch).mockResolvedValueOnce(textResponse('hello stream'))
164
+
165
+ const client = axios.create()
166
+ const response = await client.get('https://api.example.com/stream', {
167
+ responseType: 'stream',
168
+ })
169
+
170
+ const chunks: Uint8Array[] = []
171
+ await new Promise<void>((resolve) => {
172
+ response.data.pipe({
173
+ write(chunk: Uint8Array) {
174
+ chunks.push(chunk)
175
+ },
176
+ end() {
177
+ resolve()
178
+ },
179
+ })
180
+ })
181
+
182
+ const text = Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))).toString('utf8')
183
+ expect(text).toBe('hello stream')
184
+ })
185
+
134
186
  it('axiosRetry retries when retryCondition passes and fetch then succeeds', async () => {
135
187
  vi.mocked(fetch).mockReset()
136
188
  vi.mocked(fetch).mockRejectedValueOnce(new TypeError('Failed to fetch'))