facility-core 2.2.0 → 2.3.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "facility-core",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Common code for the Facility API Framework.",
5
5
  "scripts": {
6
6
  "build": "tsc && eslint src --ext .ts",
@@ -27,7 +27,7 @@
27
27
  "eslint": "^7.28.0",
28
28
  "eslint-config-prettier": "^8.3.0",
29
29
  "eslint-plugin-prettier": "^3.4.0",
30
- "mocha": "^8.4.0",
30
+ "mocha": "^10.0.0",
31
31
  "prettier": "^2.3.1",
32
32
  "typescript": "~3.9.9"
33
33
  }
@@ -41,9 +41,11 @@ export declare namespace HttpClientUtility {
41
41
  /** The minimal fetch response. */
42
42
  interface IFetchResponse {
43
43
  status: number;
44
+ ok?: boolean;
44
45
  headers: {
45
46
  get(name: string): string | null;
46
47
  };
48
+ body?: IWebReadableStream | INodeReadableStream | null;
47
49
  json(): Promise<unknown>;
48
50
  }
49
51
  /** A fetch response with any fetched content. */
@@ -53,10 +55,30 @@ export declare namespace HttpClientUtility {
53
55
  /** The fetched JSON, if any. */
54
56
  json?: unknown;
55
57
  }
58
+ /** Web Streams API ReadableStream (browser). */
59
+ interface IWebReadableStream {
60
+ getReader(): {
61
+ read(): Promise<{
62
+ done: boolean;
63
+ value: Uint8Array;
64
+ }>;
65
+ cancel(): Promise<void>;
66
+ };
67
+ }
68
+ /** Node.js ReadableStream. */
69
+ interface INodeReadableStream {
70
+ on(event: 'data', listener: (chunk: Uint8Array | ArrayBuffer) => void): void;
71
+ on(event: 'end', listener: () => void): void;
72
+ on(event: 'error', listener: (err: Error) => void): void;
73
+ off?(event: string, listener: (...args: unknown[]) => void): void;
74
+ removeAllListeners?(): void;
75
+ }
56
76
  /** Fetch JSON using the specified fetch, URI, and request. */
57
77
  function fetchResponse(fetch: IFetch, uri: string, request: IFetchRequest, context?: unknown): Promise<IFetchedResponseWithContent>;
58
78
  /** Creates an error result for the specified response. */
59
79
  function createResponseError(status: number, json?: unknown): IServiceResultBase;
60
80
  /** Creates an error result for a required request field. */
61
81
  function createRequiredRequestFieldError(name: string): IServiceResultBase;
82
+ /** Creates an async iterable stream from a fetch SSE response. */
83
+ function createFetchEventStream<T>(fetchFunc: IFetch, url: string, fetchRequest: IFetchRequest, context?: unknown): Promise<IServiceResult<AsyncIterable<IServiceResult<T>>>>;
62
84
  }
@@ -63,12 +63,162 @@ var HttpClientUtility;
63
63
  return {
64
64
  error: {
65
65
  code: 'InvalidRequest',
66
- message: `The request field '${name}' is required.`,
66
+ message: `'${name}' is required.`,
67
67
  },
68
68
  };
69
69
  }
70
70
  HttpClientUtility.createRequiredRequestFieldError = createRequiredRequestFieldError;
71
+ /** Creates an async iterable stream from a fetch SSE response. */
72
+ function createFetchEventStream(fetchFunc, url, fetchRequest, context) {
73
+ return new Promise((outerResolve, outerReject) => {
74
+ fetchFunc(url, fetchRequest, context)
75
+ .then((response) => {
76
+ if (!response.ok) {
77
+ outerReject(new Error(`HTTP error! status: ${response.status}`));
78
+ return;
79
+ }
80
+ if (!response.body) {
81
+ outerReject(new Error('Response body is null'));
82
+ return;
83
+ }
84
+ const body = response.body;
85
+ if (!isWebReadableStream(body) && !isNodeReadableStream(body)) {
86
+ outerReject(new Error('Response body is not a readable stream'));
87
+ return;
88
+ }
89
+ const asyncIterable = {
90
+ [Symbol.asyncIterator]() {
91
+ const queue = [];
92
+ let resolveNext = null;
93
+ let isDone = false;
94
+ let buffer = '';
95
+ const decoder = new TextDecoder();
96
+ const processLine = (line) => {
97
+ if (line.startsWith('data: ')) {
98
+ const data = line.slice(6);
99
+ try {
100
+ const parsed = JSON.parse(data);
101
+ const result = { value: parsed };
102
+ if (resolveNext) {
103
+ resolveNext({ value: result, done: false });
104
+ resolveNext = null;
105
+ }
106
+ else {
107
+ queue.push(result);
108
+ }
109
+ }
110
+ catch (parseError) {
111
+ const errorResult = {
112
+ error: { code: 'InvalidResponse', message: 'Failed to parse SSE data' },
113
+ };
114
+ if (resolveNext) {
115
+ resolveNext({ value: errorResult, done: false });
116
+ resolveNext = null;
117
+ }
118
+ else {
119
+ queue.push(errorResult);
120
+ }
121
+ }
122
+ }
123
+ };
124
+ const processChunk = (chunk) => {
125
+ buffer += decoder.decode(chunk, { stream: true });
126
+ const lines = buffer.split('\n');
127
+ buffer = lines.pop() || '';
128
+ for (const line of lines) {
129
+ processLine(line);
130
+ }
131
+ };
132
+ const handleError = () => {
133
+ isDone = true;
134
+ if (resolveNext) {
135
+ resolveNext({
136
+ value: {
137
+ error: { code: 'InternalError', message: 'Stream read error' },
138
+ },
139
+ done: false,
140
+ });
141
+ }
142
+ };
143
+ const handleEnd = () => {
144
+ isDone = true;
145
+ if (resolveNext) {
146
+ resolveNext({ value: undefined, done: true });
147
+ }
148
+ };
149
+ if (isWebReadableStream(body)) {
150
+ // Handle Web Streams (browser)
151
+ const reader = body.getReader();
152
+ const readStream = () => {
153
+ reader
154
+ .read()
155
+ .then(({ done, value }) => {
156
+ if (done) {
157
+ handleEnd();
158
+ return;
159
+ }
160
+ processChunk(value);
161
+ readStream();
162
+ })
163
+ .catch(handleError);
164
+ };
165
+ readStream();
166
+ }
167
+ else if (isNodeReadableStream(body)) {
168
+ // Handle Node.js Streams
169
+ body.on('data', (chunk) => {
170
+ const uint8Array = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
171
+ processChunk(uint8Array);
172
+ });
173
+ body.on('end', handleEnd);
174
+ body.on('error', handleError);
175
+ }
176
+ return {
177
+ async next() {
178
+ if (queue.length > 0) {
179
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
180
+ return { value: queue.shift(), done: false };
181
+ }
182
+ if (isDone) {
183
+ return { value: undefined, done: true };
184
+ }
185
+ return new Promise((res) => {
186
+ resolveNext = res;
187
+ });
188
+ },
189
+ async return() {
190
+ isDone = true;
191
+ if (isWebReadableStream(body)) {
192
+ const reader = body.getReader();
193
+ reader.cancel();
194
+ }
195
+ else if (isNodeReadableStream(body)) {
196
+ if (body.removeAllListeners) {
197
+ body.removeAllListeners();
198
+ }
199
+ }
200
+ return { value: undefined, done: true };
201
+ },
202
+ };
203
+ },
204
+ };
205
+ outerResolve({ value: asyncIterable });
206
+ })
207
+ .catch((err) => {
208
+ outerReject(err);
209
+ });
210
+ });
211
+ }
212
+ HttpClientUtility.createFetchEventStream = createFetchEventStream;
71
213
  })(HttpClientUtility = exports.HttpClientUtility || (exports.HttpClientUtility = {}));
214
+ /** Type guard to check if a stream is a Web ReadableStream. */
215
+ function isWebReadableStream(stream) {
216
+ return typeof stream.getReader === 'function';
217
+ }
218
+ /** Type guard to check if a stream is a Node.js ReadableStream. */
219
+ function isNodeReadableStream(stream) {
220
+ return typeof stream.on === 'function';
221
+ }
72
222
  function isServiceError(json) {
73
223
  return typeof json === 'object' && json != null && typeof json.code === 'string';
74
224
  }
@@ -1 +1 @@
1
- {"version":3,"file":"facilityCore.js","sourceRoot":"","sources":["facilityCore.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"facilityCore.js","sourceRoot":"","sources":["facilityCore.ts"],"names":[],"mappings":"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"}
@@ -48,9 +48,11 @@ export namespace HttpClientUtility {
48
48
  /** The minimal fetch response. */
49
49
  export interface IFetchResponse {
50
50
  status: number;
51
+ ok?: boolean;
51
52
  headers: {
52
53
  get(name: string): string | null;
53
54
  };
55
+ body?: IWebReadableStream | INodeReadableStream | null;
54
56
  json(): Promise<unknown>;
55
57
  }
56
58
 
@@ -62,6 +64,23 @@ export namespace HttpClientUtility {
62
64
  json?: unknown;
63
65
  }
64
66
 
67
+ /** Web Streams API ReadableStream (browser). */
68
+ export interface IWebReadableStream {
69
+ getReader(): {
70
+ read(): Promise<{ done: boolean; value: Uint8Array }>;
71
+ cancel(): Promise<void>;
72
+ };
73
+ }
74
+
75
+ /** Node.js ReadableStream. */
76
+ export interface INodeReadableStream {
77
+ on(event: 'data', listener: (chunk: Uint8Array | ArrayBuffer) => void): void;
78
+ on(event: 'end', listener: () => void): void;
79
+ on(event: 'error', listener: (err: Error) => void): void;
80
+ off?(event: string, listener: (...args: unknown[]) => void): void;
81
+ removeAllListeners?(): void;
82
+ }
83
+
65
84
  const standardErrorCodes: { [index: number]: string } = {
66
85
  '304': 'NotModified',
67
86
  '400': 'InvalidRequest',
@@ -127,10 +146,171 @@ export namespace HttpClientUtility {
127
146
  return {
128
147
  error: {
129
148
  code: 'InvalidRequest',
130
- message: `The request field '${name}' is required.`,
149
+ message: `'${name}' is required.`,
131
150
  },
132
151
  };
133
152
  }
153
+
154
+ /** Creates an async iterable stream from a fetch SSE response. */
155
+ export function createFetchEventStream<T>(
156
+ fetchFunc: IFetch,
157
+ url: string,
158
+ fetchRequest: IFetchRequest,
159
+ context?: unknown
160
+ ): Promise<IServiceResult<AsyncIterable<IServiceResult<T>>>> {
161
+ return new Promise((outerResolve, outerReject) => {
162
+ fetchFunc(url, fetchRequest, context)
163
+ .then((response) => {
164
+ if (!response.ok) {
165
+ outerReject(new Error(`HTTP error! status: ${response.status}`));
166
+ return;
167
+ }
168
+ if (!response.body) {
169
+ outerReject(new Error('Response body is null'));
170
+ return;
171
+ }
172
+
173
+ const body = response.body;
174
+
175
+ if (!isWebReadableStream(body) && !isNodeReadableStream(body)) {
176
+ outerReject(new Error('Response body is not a readable stream'));
177
+ return;
178
+ }
179
+
180
+ const asyncIterable: AsyncIterable<IServiceResult<T>> = {
181
+ [Symbol.asyncIterator]() {
182
+ const queue: Array<IServiceResult<T>> = [];
183
+ let resolveNext: ((value: IteratorResult<IServiceResult<T>>) => void) | null = null;
184
+ let isDone = false;
185
+ let buffer = '';
186
+ const decoder = new TextDecoder();
187
+
188
+ const processLine = (line: string) => {
189
+ if (line.startsWith('data: ')) {
190
+ const data = line.slice(6);
191
+ try {
192
+ const parsed = JSON.parse(data) as T;
193
+ const result: IServiceResult<T> = { value: parsed };
194
+ if (resolveNext) {
195
+ resolveNext({ value: result, done: false });
196
+ resolveNext = null;
197
+ } else {
198
+ queue.push(result);
199
+ }
200
+ } catch (parseError) {
201
+ const errorResult: IServiceResult<T> = {
202
+ error: { code: 'InvalidResponse', message: 'Failed to parse SSE data' },
203
+ };
204
+ if (resolveNext) {
205
+ resolveNext({ value: errorResult, done: false });
206
+ resolveNext = null;
207
+ } else {
208
+ queue.push(errorResult);
209
+ }
210
+ }
211
+ }
212
+ };
213
+
214
+ const processChunk = (chunk: Uint8Array) => {
215
+ buffer += decoder.decode(chunk, { stream: true });
216
+ const lines = buffer.split('\n');
217
+ buffer = lines.pop() || '';
218
+ for (const line of lines) {
219
+ processLine(line);
220
+ }
221
+ };
222
+
223
+ const handleError = () => {
224
+ isDone = true;
225
+ if (resolveNext) {
226
+ resolveNext({
227
+ value: {
228
+ error: { code: 'InternalError', message: 'Stream read error' },
229
+ },
230
+ done: false,
231
+ });
232
+ }
233
+ };
234
+
235
+ const handleEnd = () => {
236
+ isDone = true;
237
+ if (resolveNext) {
238
+ resolveNext({ value: undefined, done: true });
239
+ }
240
+ };
241
+
242
+ if (isWebReadableStream(body)) {
243
+ // Handle Web Streams (browser)
244
+ const reader = body.getReader();
245
+ const readStream = (): void => {
246
+ reader
247
+ .read()
248
+ .then(({ done, value }: { done: boolean; value: Uint8Array }) => {
249
+ if (done) {
250
+ handleEnd();
251
+ return;
252
+ }
253
+ processChunk(value);
254
+ readStream();
255
+ })
256
+ .catch(handleError);
257
+ };
258
+ readStream();
259
+ } else if (isNodeReadableStream(body)) {
260
+ // Handle Node.js Streams
261
+ body.on('data', (chunk: Uint8Array | ArrayBuffer) => {
262
+ const uint8Array = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
263
+ processChunk(uint8Array);
264
+ });
265
+ body.on('end', handleEnd);
266
+ body.on('error', handleError);
267
+ }
268
+
269
+ return {
270
+ async next() {
271
+ if (queue.length > 0) {
272
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
273
+ return { value: queue.shift()!, done: false };
274
+ }
275
+ if (isDone) {
276
+ return { value: undefined, done: true };
277
+ }
278
+ return new Promise((res) => {
279
+ resolveNext = res;
280
+ });
281
+ },
282
+ async return() {
283
+ isDone = true;
284
+ if (isWebReadableStream(body)) {
285
+ const reader = body.getReader();
286
+ reader.cancel();
287
+ } else if (isNodeReadableStream(body)) {
288
+ if (body.removeAllListeners) {
289
+ body.removeAllListeners();
290
+ }
291
+ }
292
+ return { value: undefined, done: true };
293
+ },
294
+ };
295
+ },
296
+ };
297
+ outerResolve({ value: asyncIterable });
298
+ })
299
+ .catch((err) => {
300
+ outerReject(err);
301
+ });
302
+ });
303
+ }
304
+ }
305
+
306
+ /** Type guard to check if a stream is a Web ReadableStream. */
307
+ function isWebReadableStream(stream: unknown): stream is HttpClientUtility.IWebReadableStream {
308
+ return typeof (stream as HttpClientUtility.IWebReadableStream).getReader === 'function';
309
+ }
310
+
311
+ /** Type guard to check if a stream is a Node.js ReadableStream. */
312
+ function isNodeReadableStream(stream: unknown): stream is HttpClientUtility.INodeReadableStream {
313
+ return typeof (stream as HttpClientUtility.INodeReadableStream).on === 'function';
134
314
  }
135
315
 
136
316
  function isServiceError(json: unknown): json is IServiceError {