@pikku/fetch 0.12.1 → 0.12.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/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## 0.12.3
2
+
3
+ ### Patch Changes
4
+
5
+ - 409ec80: feat(console): Tests page with live SSE streaming and function test harness
6
+ - `@pikku/addon-console`: add `streamFunctionTests` SSE function that runs the
7
+ cucumber/c8 test harness and streams structured per-scenario events
8
+ (scenario-start, step, scenario-done, done)
9
+ - `@pikku/console`: TestsPage live run view — renders scenario names and step
10
+ status in real time during a test run via SSE; adds `usePikkuSSE` hook and
11
+ `showRunButton` prop
12
+ - `@pikku/fetch`: add `subscribePikkuSSE` helper for typed server-sent event
13
+ streams
14
+ - `@pikku/cli`: wire SSE-returning functions through the console serialiser and
15
+ RPC wrapper so the stream route is included in generated clients
16
+
17
+ ## 0.12.2
18
+
19
+ ### Patch Changes
20
+
21
+ - 9060165: New realtime events system: `pikku realtime` generates a typed `PikkuRealtime` client that pairs with `PikkuRPC`. A `/events` channel can be scaffolded to fan out server events to subscribers over SSE. `pikku dev` wires `LocalEventHubService` automatically so realtime works out of the box locally. The React provider exposes `PikkuRealtime` alongside `PikkuRPC`.
22
+ - 9060165: Fix `@pikku/addon-graph` package exports so generated bootstrap files can be imported correctly. The Node.js HTTP server adapter is unified across dev, standalone, and container deployments. Next.js gains a worker-RPC transport. Date values in fetch responses now deserialise correctly.
23
+
1
24
  ## 0.12.0
2
25
 
3
26
  ## 0.12.1
@@ -45,6 +45,11 @@ export declare class CorePikkuFetch {
45
45
  * @param {string} serverUrl - The server URL to be set.
46
46
  */
47
47
  setServerUrl(serverUrl: string): void;
48
+ /**
49
+ * Returns the configured base server URL (without trailing slash), or
50
+ * undefined if it hasn't been set yet.
51
+ */
52
+ getServerUrl(): string | undefined;
48
53
  /**
49
54
  * Sets the JWT for authorization.
50
55
  *
@@ -93,6 +98,17 @@ export declare class CorePikkuFetch {
93
98
  * @throws {Response} - Throws the response if the status code is greater than 400.
94
99
  */
95
100
  api(uri: string, method: HTTPMethod, data: any, options?: RequestInit): Promise<any>;
101
+ /**
102
+ * Opens an SSE stream to the given path and calls `handler` for each parsed
103
+ * JSON event. Returns a handle with a `close()` method that aborts the stream.
104
+ *
105
+ * @param path - Server-relative path (e.g. `/function-tests/stream`)
106
+ * @param handler - Called with each decoded JSON event
107
+ * @param onError - Called once if the stream errors (and is not already closed)
108
+ */
109
+ subscribeToSSE<T = unknown>(path: string, handler: (event: T) => void, onError?: (err: unknown) => void): {
110
+ close: () => void;
111
+ };
96
112
  /**
97
113
  * Makes a raw fetch request with the specified URI, method, and data.
98
114
  *
@@ -57,6 +57,13 @@ class CorePikkuFetch {
57
57
  }
58
58
  this.options.serverUrl = serverUrl;
59
59
  }
60
+ /**
61
+ * Returns the configured base server URL (without trailing slash), or
62
+ * undefined if it hasn't been set yet.
63
+ */
64
+ getServerUrl() {
65
+ return this.options.serverUrl;
66
+ }
60
67
  /**
61
68
  * Sets the JWT for authorization.
62
69
  *
@@ -159,6 +166,78 @@ class CorePikkuFetch {
159
166
  }
160
167
  });
161
168
  }
169
+ /**
170
+ * Opens an SSE stream to the given path and calls `handler` for each parsed
171
+ * JSON event. Returns a handle with a `close()` method that aborts the stream.
172
+ *
173
+ * @param path - Server-relative path (e.g. `/function-tests/stream`)
174
+ * @param handler - Called with each decoded JSON event
175
+ * @param onError - Called once if the stream errors (and is not already closed)
176
+ */
177
+ subscribeToSSE(path, handler, onError) {
178
+ this.verifyServerUrlSet();
179
+ const url = path.startsWith('/')
180
+ ? `${this.options.serverUrl}${path}`
181
+ : `${this.options.serverUrl}/${path}`;
182
+ const controller = new AbortController();
183
+ let closed = false;
184
+ const run = () => __awaiter(this, void 0, void 0, function* () {
185
+ try {
186
+ const response = yield (0, pikku_fetch_js_1.corePikkuFetch)(url, null, {
187
+ method: 'GET',
188
+ mode: this.options.mode,
189
+ credentials: this.options.credentials,
190
+ headers: Object.assign(Object.assign({}, this.getHeaders()), { Accept: 'text/event-stream' }),
191
+ signal: controller.signal,
192
+ });
193
+ if (!response.ok || !response.body) {
194
+ throw new Error(`SSE request failed: ${response.status}`);
195
+ }
196
+ const reader = response.body
197
+ .pipeThrough(new TextDecoderStream())
198
+ .getReader();
199
+ let buffer = '';
200
+ while (!closed) {
201
+ const { done, value } = yield reader.read();
202
+ if (done)
203
+ break;
204
+ buffer += value;
205
+ let sep;
206
+ while ((sep = buffer.indexOf('\n\n')) !== -1) {
207
+ const raw = buffer.slice(0, sep);
208
+ buffer = buffer.slice(sep + 2);
209
+ const data = raw
210
+ .split('\n')
211
+ .filter((l) => l.startsWith('data:'))
212
+ .map((l) => l.slice(5).trimStart())
213
+ .join('\n');
214
+ if (!data)
215
+ continue;
216
+ let parsed;
217
+ try {
218
+ parsed = JSON.parse(data);
219
+ }
220
+ catch (_a) {
221
+ /* ignore malformed event */
222
+ continue;
223
+ }
224
+ handler(parsed);
225
+ }
226
+ }
227
+ }
228
+ catch (err) {
229
+ if (!closed)
230
+ onError === null || onError === void 0 ? void 0 : onError(err);
231
+ }
232
+ });
233
+ run();
234
+ return {
235
+ close: () => {
236
+ closed = true;
237
+ controller.abort();
238
+ },
239
+ };
240
+ }
162
241
  /**
163
242
  * Makes a raw fetch request with the specified URI, method, and data.
164
243
  *
@@ -198,7 +277,7 @@ class CorePikkuFetch {
198
277
  * @returns {any} - The transformed data.
199
278
  */
200
279
  transformDates(data) {
201
- if (!this.options.transformDate) {
280
+ if (this.options.transformDate === false) {
202
281
  return data;
203
282
  }
204
283
  return (0, transform_date_js_1.transformDates)(data);
@@ -6,5 +6,6 @@
6
6
  *
7
7
  * @module @pikku/fetch
8
8
  */
9
- export { CorePikkuFetch, CorePikkuFetchOptions, HTTPMethod, } from './core-pikku-fetch.js';
9
+ export { CorePikkuFetch } from './core-pikku-fetch.js';
10
+ export type { CorePikkuFetchOptions, HTTPMethod } from './core-pikku-fetch.js';
10
11
  export { corePikkuFetch } from './pikku-fetch.js';
@@ -45,6 +45,11 @@ export declare class CorePikkuFetch {
45
45
  * @param {string} serverUrl - The server URL to be set.
46
46
  */
47
47
  setServerUrl(serverUrl: string): void;
48
+ /**
49
+ * Returns the configured base server URL (without trailing slash), or
50
+ * undefined if it hasn't been set yet.
51
+ */
52
+ getServerUrl(): string | undefined;
48
53
  /**
49
54
  * Sets the JWT for authorization.
50
55
  *
@@ -93,6 +98,17 @@ export declare class CorePikkuFetch {
93
98
  * @throws {Response} - Throws the response if the status code is greater than 400.
94
99
  */
95
100
  api(uri: string, method: HTTPMethod, data: any, options?: RequestInit): Promise<any>;
101
+ /**
102
+ * Opens an SSE stream to the given path and calls `handler` for each parsed
103
+ * JSON event. Returns a handle with a `close()` method that aborts the stream.
104
+ *
105
+ * @param path - Server-relative path (e.g. `/function-tests/stream`)
106
+ * @param handler - Called with each decoded JSON event
107
+ * @param onError - Called once if the stream errors (and is not already closed)
108
+ */
109
+ subscribeToSSE<T = unknown>(path: string, handler: (event: T) => void, onError?: (err: unknown) => void): {
110
+ close: () => void;
111
+ };
96
112
  /**
97
113
  * Makes a raw fetch request with the specified URI, method, and data.
98
114
  *
@@ -46,6 +46,13 @@ export class CorePikkuFetch {
46
46
  }
47
47
  this.options.serverUrl = serverUrl;
48
48
  }
49
+ /**
50
+ * Returns the configured base server URL (without trailing slash), or
51
+ * undefined if it hasn't been set yet.
52
+ */
53
+ getServerUrl() {
54
+ return this.options.serverUrl;
55
+ }
49
56
  /**
50
57
  * Sets the JWT for authorization.
51
58
  *
@@ -137,6 +144,78 @@ export class CorePikkuFetch {
137
144
  return;
138
145
  }
139
146
  }
147
+ /**
148
+ * Opens an SSE stream to the given path and calls `handler` for each parsed
149
+ * JSON event. Returns a handle with a `close()` method that aborts the stream.
150
+ *
151
+ * @param path - Server-relative path (e.g. `/function-tests/stream`)
152
+ * @param handler - Called with each decoded JSON event
153
+ * @param onError - Called once if the stream errors (and is not already closed)
154
+ */
155
+ subscribeToSSE(path, handler, onError) {
156
+ this.verifyServerUrlSet();
157
+ const url = path.startsWith('/')
158
+ ? `${this.options.serverUrl}${path}`
159
+ : `${this.options.serverUrl}/${path}`;
160
+ const controller = new AbortController();
161
+ let closed = false;
162
+ const run = async () => {
163
+ try {
164
+ const response = await corePikkuFetch(url, null, {
165
+ method: 'GET',
166
+ mode: this.options.mode,
167
+ credentials: this.options.credentials,
168
+ headers: { ...this.getHeaders(), Accept: 'text/event-stream' },
169
+ signal: controller.signal,
170
+ });
171
+ if (!response.ok || !response.body) {
172
+ throw new Error(`SSE request failed: ${response.status}`);
173
+ }
174
+ const reader = response.body
175
+ .pipeThrough(new TextDecoderStream())
176
+ .getReader();
177
+ let buffer = '';
178
+ while (!closed) {
179
+ const { done, value } = await reader.read();
180
+ if (done)
181
+ break;
182
+ buffer += value;
183
+ let sep;
184
+ while ((sep = buffer.indexOf('\n\n')) !== -1) {
185
+ const raw = buffer.slice(0, sep);
186
+ buffer = buffer.slice(sep + 2);
187
+ const data = raw
188
+ .split('\n')
189
+ .filter((l) => l.startsWith('data:'))
190
+ .map((l) => l.slice(5).trimStart())
191
+ .join('\n');
192
+ if (!data)
193
+ continue;
194
+ let parsed;
195
+ try {
196
+ parsed = JSON.parse(data);
197
+ }
198
+ catch {
199
+ /* ignore malformed event */
200
+ continue;
201
+ }
202
+ handler(parsed);
203
+ }
204
+ }
205
+ }
206
+ catch (err) {
207
+ if (!closed)
208
+ onError?.(err);
209
+ }
210
+ };
211
+ run();
212
+ return {
213
+ close: () => {
214
+ closed = true;
215
+ controller.abort();
216
+ },
217
+ };
218
+ }
140
219
  /**
141
220
  * Makes a raw fetch request with the specified URI, method, and data.
142
221
  *
@@ -180,7 +259,7 @@ export class CorePikkuFetch {
180
259
  * @returns {any} - The transformed data.
181
260
  */
182
261
  transformDates(data) {
183
- if (!this.options.transformDate) {
262
+ if (this.options.transformDate === false) {
184
263
  return data;
185
264
  }
186
265
  return transformDates(data);
@@ -6,5 +6,6 @@
6
6
  *
7
7
  * @module @pikku/fetch
8
8
  */
9
- export { CorePikkuFetch, CorePikkuFetchOptions, HTTPMethod, } from './core-pikku-fetch.js';
9
+ export { CorePikkuFetch } from './core-pikku-fetch.js';
10
+ export type { CorePikkuFetchOptions, HTTPMethod } from './core-pikku-fetch.js';
10
11
  export { corePikkuFetch } from './pikku-fetch.js';
package/dist/esm/index.js CHANGED
@@ -6,5 +6,5 @@
6
6
  *
7
7
  * @module @pikku/fetch
8
8
  */
9
- export { CorePikkuFetch, } from './core-pikku-fetch.js';
9
+ export { CorePikkuFetch } from './core-pikku-fetch.js';
10
10
  export { corePikkuFetch } from './pikku-fetch.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/fetch",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -72,6 +72,14 @@ export class CorePikkuFetch {
72
72
  this.options.serverUrl = serverUrl
73
73
  }
74
74
 
75
+ /**
76
+ * Returns the configured base server URL (without trailing slash), or
77
+ * undefined if it hasn't been set yet.
78
+ */
79
+ public getServerUrl(): string | undefined {
80
+ return this.options.serverUrl
81
+ }
82
+
75
83
  /**
76
84
  * Sets the JWT for authorization.
77
85
  *
@@ -185,6 +193,81 @@ export class CorePikkuFetch {
185
193
  }
186
194
  }
187
195
 
196
+ /**
197
+ * Opens an SSE stream to the given path and calls `handler` for each parsed
198
+ * JSON event. Returns a handle with a `close()` method that aborts the stream.
199
+ *
200
+ * @param path - Server-relative path (e.g. `/function-tests/stream`)
201
+ * @param handler - Called with each decoded JSON event
202
+ * @param onError - Called once if the stream errors (and is not already closed)
203
+ */
204
+ public subscribeToSSE<T = unknown>(
205
+ path: string,
206
+ handler: (event: T) => void,
207
+ onError?: (err: unknown) => void
208
+ ): { close: () => void } {
209
+ this.verifyServerUrlSet()
210
+ const url = path.startsWith('/')
211
+ ? `${this.options.serverUrl}${path}`
212
+ : `${this.options.serverUrl}/${path}`
213
+
214
+ const controller = new AbortController()
215
+ let closed = false
216
+
217
+ const run = async () => {
218
+ try {
219
+ const response = await corePikkuFetch(url, null, {
220
+ method: 'GET',
221
+ mode: this.options.mode,
222
+ credentials: this.options.credentials,
223
+ headers: { ...this.getHeaders(), Accept: 'text/event-stream' },
224
+ signal: controller.signal,
225
+ })
226
+ if (!response.ok || !response.body) {
227
+ throw new Error(`SSE request failed: ${response.status}`)
228
+ }
229
+ const reader = response.body
230
+ .pipeThrough(new TextDecoderStream())
231
+ .getReader()
232
+ let buffer = ''
233
+ while (!closed) {
234
+ const { done, value } = await reader.read()
235
+ if (done) break
236
+ buffer += value
237
+ let sep: number
238
+ while ((sep = buffer.indexOf('\n\n')) !== -1) {
239
+ const raw = buffer.slice(0, sep)
240
+ buffer = buffer.slice(sep + 2)
241
+ const data = raw
242
+ .split('\n')
243
+ .filter((l) => l.startsWith('data:'))
244
+ .map((l) => l.slice(5).trimStart())
245
+ .join('\n')
246
+ if (!data) continue
247
+ let parsed: T
248
+ try {
249
+ parsed = JSON.parse(data) as T
250
+ } catch {
251
+ /* ignore malformed event */
252
+ continue
253
+ }
254
+ handler(parsed)
255
+ }
256
+ }
257
+ } catch (err) {
258
+ if (!closed) onError?.(err)
259
+ }
260
+ }
261
+
262
+ run()
263
+ return {
264
+ close: () => {
265
+ closed = true
266
+ controller.abort()
267
+ },
268
+ }
269
+ }
270
+
188
271
  /**
189
272
  * Makes a raw fetch request with the specified URI, method, and data.
190
273
  *
@@ -234,7 +317,7 @@ export class CorePikkuFetch {
234
317
  * @returns {any} - The transformed data.
235
318
  */
236
319
  private transformDates(data: any): any {
237
- if (!this.options.transformDate) {
320
+ if (this.options.transformDate === false) {
238
321
  return data
239
322
  }
240
323
  return transformDates(data)
@@ -0,0 +1,11 @@
1
+ import assert from 'node:assert/strict'
2
+ import { describe, test } from 'node:test'
3
+
4
+ import { CorePikkuFetch, corePikkuFetch } from './index.js'
5
+
6
+ describe('@pikku/fetch', () => {
7
+ test('exports the public fetch client API', () => {
8
+ assert.equal(typeof CorePikkuFetch, 'function')
9
+ assert.equal(typeof corePikkuFetch, 'function')
10
+ })
11
+ })
package/src/index.ts CHANGED
@@ -7,9 +7,6 @@
7
7
  * @module @pikku/fetch
8
8
  */
9
9
 
10
- export {
11
- CorePikkuFetch,
12
- CorePikkuFetchOptions,
13
- HTTPMethod,
14
- } from './core-pikku-fetch.js'
10
+ export { CorePikkuFetch } from './core-pikku-fetch.js'
11
+ export type { CorePikkuFetchOptions, HTTPMethod } from './core-pikku-fetch.js'
15
12
  export { corePikkuFetch } from './pikku-fetch.js'