@rest-vir/define-service 1.6.0 → 1.7.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.
@@ -160,6 +160,67 @@ export declare function fetchEndpoint<const EndpointToFetch extends Readonly<Sel
160
160
  serviceName: true;
161
161
  };
162
162
  }>, ...params: CollapsedFetchEndpointParams<EndpointToFetch>): Promise<FetchEndpointOutput<EndpointToFetch>>;
163
+ /**
164
+ * Type safe output from sending a stream request to an endpoint definition. Used by
165
+ * {@link fetchStreamEndpoint}.
166
+ *
167
+ * @category Internal
168
+ * @category Package : @rest-vir/define-service
169
+ * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
170
+ */
171
+ export type FetchStreamEndpointOutput = Readonly<{
172
+ ok: true;
173
+ stream: ReadableStream<Uint8Array>;
174
+ response: Readonly<Response>;
175
+ }> | Readonly<{
176
+ ok: false;
177
+ data: string | undefined;
178
+ response: Readonly<Response>;
179
+ }>;
180
+ /**
181
+ * Send a request to an endpoint definition and return a `ReadableStream` instead of parsing the
182
+ * response body. This is useful for consuming SSE (Server-Sent Events) endpoints from frontend
183
+ * clients.
184
+ *
185
+ * Uses the same request-building and validation flow as {@link fetchEndpoint}, but skips response
186
+ * body and JSON validation.
187
+ *
188
+ * @category Client (Frontend) Connection
189
+ * @category Package : @rest-vir/define-service
190
+ * @example
191
+ *
192
+ * ```ts
193
+ * import {fetchStreamEndpoint} from '@rest-vir/define-service';
194
+ *
195
+ * const result = await fetchStreamEndpoint(myService.endpoints['/my-sse-endpoint']);
196
+ *
197
+ * if (result.ok) {
198
+ * const reader = result.stream.getReader();
199
+ * }
200
+ * ```
201
+ *
202
+ * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
203
+ */
204
+ export declare function fetchStreamEndpoint<const EndpointToFetch extends Readonly<SelectFrom<EndpointDefinition, {
205
+ requestDataShape: true;
206
+ path: true;
207
+ responseDataShape: true;
208
+ methods: true;
209
+ service: {
210
+ serviceOrigin: true;
211
+ serviceName: true;
212
+ };
213
+ }>> | NoParam>(endpoint: EndpointToFetch extends EndpointDefinition ? EndpointToFetch : SelectFrom<EndpointDefinition, {
214
+ requestDataShape: true;
215
+ path: true;
216
+ responseDataShape: true;
217
+ searchParamsShape: true;
218
+ methods: true;
219
+ service: {
220
+ serviceOrigin: true;
221
+ serviceName: true;
222
+ };
223
+ }>, ...params: CollapsedFetchEndpointParams<EndpointToFetch>): Promise<FetchStreamEndpointOutput>;
163
224
  /**
164
225
  * Build request init and URL for fetching an endpoint. Used in {@link fetchEndpoint}.
165
226
  *
@@ -97,6 +97,64 @@ export async function fetchEndpoint(endpoint, ...params) {
97
97
  };
98
98
  }
99
99
  }
100
+ /**
101
+ * Send a request to an endpoint definition and return a `ReadableStream` instead of parsing the
102
+ * response body. This is useful for consuming SSE (Server-Sent Events) endpoints from frontend
103
+ * clients.
104
+ *
105
+ * Uses the same request-building and validation flow as {@link fetchEndpoint}, but skips response
106
+ * body and JSON validation.
107
+ *
108
+ * @category Client (Frontend) Connection
109
+ * @category Package : @rest-vir/define-service
110
+ * @example
111
+ *
112
+ * ```ts
113
+ * import {fetchStreamEndpoint} from '@rest-vir/define-service';
114
+ *
115
+ * const result = await fetchStreamEndpoint(myService.endpoints['/my-sse-endpoint']);
116
+ *
117
+ * if (result.ok) {
118
+ * const reader = result.stream.getReader();
119
+ * }
120
+ * ```
121
+ *
122
+ * @package [`@rest-vir/define-service`](https://www.npmjs.com/package/@rest-vir/define-service)
123
+ */
124
+ export async function fetchStreamEndpoint(endpoint, ...params) {
125
+ const { requestData, fetch } = params[0] || {};
126
+ if (requestData) {
127
+ if (endpoint.requestDataShape) {
128
+ assertValidShape(requestData, endpoint.requestDataShape, {
129
+ allowExtraKeys: true,
130
+ });
131
+ }
132
+ else {
133
+ throw new Error(`Request data was given but endpoint '${endpoint.path}' is not expecting any request data.`);
134
+ }
135
+ }
136
+ const { requestInit, url } = buildEndpointRequestInit(endpoint, ...params);
137
+ /* node:coverage ignore next: all tests mock fetch so we're never going to have a fallback here. */
138
+ const response = await (fetch || defaultFetch)(url, requestInit, endpoint);
139
+ if (response.ok) {
140
+ if (!response.body) {
141
+ throw new Error(`Endpoint '${endpoint.path}' returned an ok response with no body to stream.`);
142
+ }
143
+ return {
144
+ ok: true,
145
+ stream: response.body,
146
+ response,
147
+ };
148
+ }
149
+ else {
150
+ const responseText = await response.clone().text();
151
+ return {
152
+ ok: false,
153
+ data: responseText || undefined,
154
+ response,
155
+ };
156
+ }
157
+ }
100
158
  /**
101
159
  * Build request init and URL for fetching an endpoint. Used in {@link fetchEndpoint}.
102
160
  *
@@ -5,7 +5,7 @@ import { type CommonWebSocket } from '../web-socket/common-web-socket.js';
5
5
  import { type ClientWebSocket, type GenericConnectWebSocketParams } from '../web-socket/overwrite-web-socket-methods.js';
6
6
  import { type GenericWebSocketDefinition } from '../web-socket/web-socket-definition.js';
7
7
  import { type CollapsedConnectWebSocketParams } from './connect-web-socket.js';
8
- import { buildEndpointUrl, type CollapsedFetchEndpointParams, type FetchEndpointOutput, type GenericFetchEndpointParams } from './fetch-endpoint.js';
8
+ import { buildEndpointUrl, type CollapsedFetchEndpointParams, type FetchEndpointOutput, type FetchStreamEndpointOutput, type GenericFetchEndpointParams } from './fetch-endpoint.js';
9
9
  /**
10
10
  * An API generated by {@link generateApi}. This can be easily mocked by wrapping this API in
11
11
  * {@link makeMockApi}.
@@ -23,6 +23,12 @@ export declare class RestVirApi<SpecificService extends ServiceDefinition> {
23
23
  [EndpointPath in keyof SpecificService['endpoints']]: SpecificService['endpoints'][EndpointPath] extends GenericEndpointDefinition ? SpecificService['endpoints'][EndpointPath] & {
24
24
  /** Send a fetch request to this endpoint. */
25
25
  fetch(...params: CollapsedFetchEndpointParams<SpecificService['endpoints'][EndpointPath]>): Promise<FetchEndpointOutput<SpecificService['endpoints'][EndpointPath]>>;
26
+ /**
27
+ * Send a fetch request to this endpoint and return a `ReadableStream` instead of
28
+ * parsing the response body. Useful for consuming SSE (Server-Sent Events)
29
+ * endpoints.
30
+ */
31
+ fetchStream(...params: CollapsedFetchEndpointParams<SpecificService['endpoints'][EndpointPath]>): Promise<FetchStreamEndpointOutput>;
26
32
  buildUrl(params: Parameters<typeof buildEndpointUrl<SpecificService['endpoints'][EndpointPath]>>[1]): string;
27
33
  } : never;
28
34
  };
@@ -1,6 +1,6 @@
1
1
  import { mapObjectValues, } from '@augment-vir/common';
2
2
  import { connectWebSocket } from './connect-web-socket.js';
3
- import { buildEndpointUrl, fetchEndpoint, } from './fetch-endpoint.js';
3
+ import { buildEndpointUrl, fetchEndpoint, fetchStreamEndpoint, } from './fetch-endpoint.js';
4
4
  /**
5
5
  * An API generated by {@link generateApi}. This can be easily mocked by wrapping this API in
6
6
  * {@link makeMockApi}.
@@ -32,16 +32,23 @@ export class RestVirApi {
32
32
  constructor(service, { endpointFetch, webSocketConnect, serviceOrigin } = {}) {
33
33
  this.serviceOrigin = serviceOrigin || service.serviceOrigin;
34
34
  this.endpoints = mapObjectValues(service.endpoints, (endpointPath, endpointDefinition) => {
35
+ const endpointWithOrigin = () => ({
36
+ ...endpointDefinition,
37
+ service: {
38
+ ...endpointDefinition.service,
39
+ serviceOrigin: this.serviceOrigin,
40
+ },
41
+ });
35
42
  return {
36
43
  ...endpointDefinition,
37
44
  fetch: (...params) => {
38
- return fetchEndpoint({
39
- ...endpointDefinition,
40
- service: {
41
- ...endpointDefinition.service,
42
- serviceOrigin: this.serviceOrigin,
43
- },
44
- }, {
45
+ return fetchEndpoint(endpointWithOrigin(), {
46
+ ...endpointFetch,
47
+ ...params[0],
48
+ });
49
+ },
50
+ fetchStream: (...params) => {
51
+ return fetchStreamEndpoint(endpointWithOrigin(), {
45
52
  ...endpointFetch,
46
53
  ...params[0],
47
54
  });
@@ -119,6 +126,12 @@ export function makeMockApi(api, mocks) {
119
126
  ...params[0],
120
127
  });
121
128
  },
129
+ fetchStream: (...params) => {
130
+ return fetchStreamEndpoint(endpointDefinition, {
131
+ fetch: mocks.fetch,
132
+ ...params[0],
133
+ });
134
+ },
122
135
  };
123
136
  }),
124
137
  webSockets: mapObjectValues(api.webSockets, (webSocketPath, webSocketDefinition) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rest-vir/define-service",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Define an connect to a declarative and type safe REST and WebSocket service.",
5
5
  "keywords": [
6
6
  "rest",