@pikku/fetch 0.12.3 → 0.12.5

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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 0.12.5
2
+
3
+ ### Patch Changes
4
+
5
+ - 6f06813: Add `CorePikkuFetch.setHeader(name, value)` to set or clear an arbitrary request header (passing `null` removes it). Enables per-client headers such as admin impersonation (`x-pikku-impersonate-user-id`) without subclassing the fetch client.
6
+
7
+ ## 0.12.4
8
+
9
+ ### Patch Changes
10
+
11
+ - ade6f0b: `subscribeToSSE`: call `onError` when the stream closes cleanly without the caller having called `close()`. Previously a server-side connection drop (or any clean EOF before the terminal event) exited the read loop silently, leaving the caller's `runPhase` stuck at `'running'` indefinitely with no way to recover.
12
+
1
13
  ## 0.12.3
2
14
 
3
15
  ### Patch Changes
@@ -27,6 +27,7 @@ export type CorePikkuFetchOptions = {
27
27
  export declare class CorePikkuFetch {
28
28
  private options;
29
29
  private authHeaders;
30
+ private extraHeaders;
30
31
  /**
31
32
  * Constructs a new instance of the `CorePikkuFetch` class.
32
33
  *
@@ -39,6 +40,7 @@ export declare class CorePikkuFetch {
39
40
  * @returns {Record<string, string>} - The headers for the request.
40
41
  */
41
42
  private getHeaders;
43
+ setHeader(name: string, value: string | null): void;
42
44
  /**
43
45
  * Sets the server URL for subsequent requests.
44
46
  *
@@ -26,6 +26,7 @@ class CorePikkuFetch {
26
26
  constructor(options = {}) {
27
27
  this.options = options;
28
28
  this.authHeaders = {};
29
+ this.extraHeaders = {};
29
30
  this.authHeaders = options.authHeaders || {};
30
31
  }
31
32
  /**
@@ -34,9 +35,7 @@ class CorePikkuFetch {
34
35
  * @returns {Record<string, string>} - The headers for the request.
35
36
  */
36
37
  getHeaders() {
37
- const headers = {
38
- 'Content-Type': 'application/json',
39
- };
38
+ const headers = Object.assign({ 'Content-Type': 'application/json' }, this.extraHeaders);
40
39
  if (this.authHeaders.jwt) {
41
40
  headers.Authorization = `Bearer ${this.authHeaders.jwt}`;
42
41
  }
@@ -45,6 +44,14 @@ class CorePikkuFetch {
45
44
  }
46
45
  return headers;
47
46
  }
47
+ setHeader(name, value) {
48
+ if (value === null) {
49
+ delete this.extraHeaders[name];
50
+ }
51
+ else {
52
+ this.extraHeaders[name] = value;
53
+ }
54
+ }
48
55
  /**
49
56
  * Sets the server URL for subsequent requests.
50
57
  *
@@ -224,6 +231,9 @@ class CorePikkuFetch {
224
231
  handler(parsed);
225
232
  }
226
233
  }
234
+ // Clean EOF before caller called close() = unexpected stream termination.
235
+ if (!closed)
236
+ throw new Error('SSE stream closed unexpectedly');
227
237
  }
228
238
  catch (err) {
229
239
  if (!closed)
@@ -27,6 +27,7 @@ export type CorePikkuFetchOptions = {
27
27
  export declare class CorePikkuFetch {
28
28
  private options;
29
29
  private authHeaders;
30
+ private extraHeaders;
30
31
  /**
31
32
  * Constructs a new instance of the `CorePikkuFetch` class.
32
33
  *
@@ -39,6 +40,7 @@ export declare class CorePikkuFetch {
39
40
  * @returns {Record<string, string>} - The headers for the request.
40
41
  */
41
42
  private getHeaders;
43
+ setHeader(name: string, value: string | null): void;
42
44
  /**
43
45
  * Sets the server URL for subsequent requests.
44
46
  *
@@ -8,6 +8,7 @@ import { corePikkuFetch } from './pikku-fetch.js';
8
8
  export class CorePikkuFetch {
9
9
  options;
10
10
  authHeaders = {};
11
+ extraHeaders = {};
11
12
  /**
12
13
  * Constructs a new instance of the `CorePikkuFetch` class.
13
14
  *
@@ -25,6 +26,7 @@ export class CorePikkuFetch {
25
26
  getHeaders() {
26
27
  const headers = {
27
28
  'Content-Type': 'application/json',
29
+ ...this.extraHeaders,
28
30
  };
29
31
  if (this.authHeaders.jwt) {
30
32
  headers.Authorization = `Bearer ${this.authHeaders.jwt}`;
@@ -34,6 +36,14 @@ export class CorePikkuFetch {
34
36
  }
35
37
  return headers;
36
38
  }
39
+ setHeader(name, value) {
40
+ if (value === null) {
41
+ delete this.extraHeaders[name];
42
+ }
43
+ else {
44
+ this.extraHeaders[name] = value;
45
+ }
46
+ }
37
47
  /**
38
48
  * Sets the server URL for subsequent requests.
39
49
  *
@@ -202,6 +212,9 @@ export class CorePikkuFetch {
202
212
  handler(parsed);
203
213
  }
204
214
  }
215
+ // Clean EOF before caller called close() = unexpected stream termination.
216
+ if (!closed)
217
+ throw new Error('SSE stream closed unexpectedly');
205
218
  }
206
219
  catch (err) {
207
220
  if (!closed)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/fetch",
3
- "version": "0.12.3",
3
+ "version": "0.12.5",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,6 +32,7 @@ export type CorePikkuFetchOptions = {
32
32
  */
33
33
  export class CorePikkuFetch {
34
34
  private authHeaders: AuthHeaders = {}
35
+ private extraHeaders: Record<string, string> = {}
35
36
 
36
37
  /**
37
38
  * Constructs a new instance of the `CorePikkuFetch` class.
@@ -50,6 +51,7 @@ export class CorePikkuFetch {
50
51
  private getHeaders(): Record<string, string> {
51
52
  const headers: Record<string, string> = {
52
53
  'Content-Type': 'application/json',
54
+ ...this.extraHeaders,
53
55
  }
54
56
  if (this.authHeaders.jwt) {
55
57
  headers.Authorization = `Bearer ${this.authHeaders.jwt}`
@@ -59,6 +61,14 @@ export class CorePikkuFetch {
59
61
  return headers
60
62
  }
61
63
 
64
+ public setHeader(name: string, value: string | null): void {
65
+ if (value === null) {
66
+ delete this.extraHeaders[name]
67
+ } else {
68
+ this.extraHeaders[name] = value
69
+ }
70
+ }
71
+
62
72
  /**
63
73
  * Sets the server URL for subsequent requests.
64
74
  *
@@ -254,6 +264,8 @@ export class CorePikkuFetch {
254
264
  handler(parsed)
255
265
  }
256
266
  }
267
+ // Clean EOF before caller called close() = unexpected stream termination.
268
+ if (!closed) throw new Error('SSE stream closed unexpectedly')
257
269
  } catch (err) {
258
270
  if (!closed) onError?.(err)
259
271
  }
package/src/index.test.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import assert from 'node:assert/strict'
2
- import { describe, test } from 'node:test'
2
+ import { describe, test, afterEach } from 'node:test'
3
3
 
4
4
  import { CorePikkuFetch, corePikkuFetch } from './index.js'
5
5
 
@@ -9,3 +9,46 @@ describe('@pikku/fetch', () => {
9
9
  assert.equal(typeof corePikkuFetch, 'function')
10
10
  })
11
11
  })
12
+
13
+ describe('CorePikkuFetch.setHeader', () => {
14
+ const realFetch = globalThis.fetch
15
+ afterEach(() => {
16
+ globalThis.fetch = realFetch
17
+ })
18
+
19
+ const capture = () => {
20
+ const seen: { headers?: Record<string, string> } = {}
21
+ globalThis.fetch = (async (_input: any, init?: any) => {
22
+ seen.headers = (init?.headers ?? {}) as Record<string, string>
23
+ return new Response('{}', {
24
+ status: 200,
25
+ headers: { 'content-type': 'application/json' },
26
+ })
27
+ }) as typeof fetch
28
+ return seen
29
+ }
30
+
31
+ test('sends a set header on every request', async () => {
32
+ const seen = capture()
33
+ const client = new CorePikkuFetch({ serverUrl: 'https://api.test' })
34
+ client.setHeader('x-pikku-impersonate-user-id', 'u_guest')
35
+ await client.fetch('/anything', 'GET', undefined)
36
+ assert.equal(seen.headers?.['x-pikku-impersonate-user-id'], 'u_guest')
37
+ })
38
+
39
+ test('removes the header when set to null', async () => {
40
+ const seen = capture()
41
+ const client = new CorePikkuFetch({ serverUrl: 'https://api.test' })
42
+ client.setHeader('x-pikku-impersonate-user-id', 'u_guest')
43
+ client.setHeader('x-pikku-impersonate-user-id', null)
44
+ await client.fetch('/anything', 'GET', undefined)
45
+ assert.equal(seen.headers?.['x-pikku-impersonate-user-id'], undefined)
46
+ })
47
+
48
+ test('no header is sent by default (inherits the session)', async () => {
49
+ const seen = capture()
50
+ const client = new CorePikkuFetch({ serverUrl: 'https://api.test' })
51
+ await client.fetch('/anything', 'GET', undefined)
52
+ assert.equal(seen.headers?.['x-pikku-impersonate-user-id'], undefined)
53
+ })
54
+ })