@pikku/fetch 0.12.2 → 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,19 @@
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
+
1
17
  ## 0.12.2
2
18
 
3
19
  ### Patch Changes
@@ -98,6 +98,17 @@ export declare class CorePikkuFetch {
98
98
  * @throws {Response} - Throws the response if the status code is greater than 400.
99
99
  */
100
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
+ };
101
112
  /**
102
113
  * Makes a raw fetch request with the specified URI, method, and data.
103
114
  *
@@ -166,6 +166,78 @@ class CorePikkuFetch {
166
166
  }
167
167
  });
168
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
+ }
169
241
  /**
170
242
  * Makes a raw fetch request with the specified URI, method, and data.
171
243
  *
@@ -98,6 +98,17 @@ export declare class CorePikkuFetch {
98
98
  * @throws {Response} - Throws the response if the status code is greater than 400.
99
99
  */
100
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
+ };
101
112
  /**
102
113
  * Makes a raw fetch request with the specified URI, method, and data.
103
114
  *
@@ -144,6 +144,78 @@ export class CorePikkuFetch {
144
144
  return;
145
145
  }
146
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
+ }
147
219
  /**
148
220
  * Makes a raw fetch request with the specified URI, method, and data.
149
221
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/fetch",
3
- "version": "0.12.2",
3
+ "version": "0.12.3",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -193,6 +193,81 @@ export class CorePikkuFetch {
193
193
  }
194
194
  }
195
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
+
196
271
  /**
197
272
  * Makes a raw fetch request with the specified URI, method, and data.
198
273
  *