typedrest 0.4.1 → 0.6.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/README.md +42 -4
- package/endpoints/{CachingEndpoint.ts → CachingEndpoint.d.ts} +0 -1
- package/endpoints/Endpoint.d.ts +121 -0
- package/endpoints/Endpoint.js +9 -22
- package/endpoints/{EntryEndpoint.ts → EntryEndpoint.d.ts} +7 -27
- package/endpoints/EntryEndpoint.js +3 -14
- package/endpoints/generic/{CollectionEndpoint.ts → CollectionEndpoint.d.ts} +2 -5
- package/endpoints/generic/{ETagEndpointBase.ts → ETagEndpointBase.d.ts} +7 -38
- package/endpoints/generic/ETagEndpointBase.js +27 -42
- package/endpoints/generic/{ElementEndpoint.ts → ElementEndpoint.d.ts} +12 -70
- package/endpoints/generic/ElementEndpoint.js +39 -60
- package/endpoints/generic/{GenericCollectionEndpoint.ts → GenericCollectionEndpoint.d.ts} +43 -75
- package/endpoints/generic/GenericCollectionEndpoint.js +70 -44
- package/endpoints/generic/{IndexerEndpoint.ts → IndexerEndpoint.d.ts} +4 -11
- package/endpoints/generic/PartialResponse.d.ts +19 -0
- package/endpoints/generic/PartialResponse.js +33 -0
- package/endpoints/generic/{index.ts → index.d.ts} +1 -0
- package/endpoints/generic/index.js +2 -1
- package/endpoints/{index.ts → index.d.ts} +2 -2
- package/endpoints/index.js +3 -2
- package/endpoints/raw/{BlobEndpoint.ts → BlobEndpoint.d.ts} +9 -29
- package/endpoints/raw/BlobEndpoint.js +10 -27
- package/endpoints/raw/{UploadEndpoint.ts → UploadEndpoint.d.ts} +4 -18
- package/endpoints/raw/UploadEndpoint.js +11 -22
- package/endpoints/reactive/Delay.d.ts +7 -0
- package/endpoints/reactive/Delay.js +27 -0
- package/endpoints/reactive/GenericStreamingCollectionEndpoint.d.ts +32 -0
- package/endpoints/reactive/GenericStreamingCollectionEndpoint.js +79 -0
- package/endpoints/reactive/PollingEndpoint.d.ts +39 -0
- package/endpoints/reactive/PollingEndpoint.js +67 -0
- package/endpoints/reactive/SseStreamingEndpoint.d.ts +50 -0
- package/endpoints/reactive/SseStreamingEndpoint.js +120 -0
- package/endpoints/reactive/StreamingCollectionEndpoint.d.ts +17 -0
- package/endpoints/reactive/StreamingCollectionEndpoint.js +23 -0
- package/endpoints/reactive/StreamingEndpoint.d.ts +28 -0
- package/endpoints/reactive/StreamingEndpoint.js +56 -0
- package/endpoints/reactive/index.d.ts +5 -0
- package/endpoints/reactive/index.js +22 -0
- package/endpoints/rpc/{ActionEndpoint.ts → ActionEndpoint.d.ts} +3 -10
- package/endpoints/rpc/ActionEndpoint.js +3 -14
- package/endpoints/rpc/{ConsumerEndpoint.ts → ConsumerEndpoint.d.ts} +3 -12
- package/endpoints/rpc/ConsumerEndpoint.js +5 -16
- package/endpoints/rpc/{FunctionEndpoint.ts → FunctionEndpoint.d.ts} +3 -14
- package/endpoints/rpc/FunctionEndpoint.js +6 -17
- package/endpoints/rpc/{ProducerEndpoint.ts → ProducerEndpoint.d.ts} +3 -11
- package/endpoints/rpc/ProducerEndpoint.js +4 -15
- package/endpoints/rpc/{RpcEndpointBase.ts → RpcEndpointBase.d.ts} +4 -12
- package/endpoints/rpc/RpcEndpointBase.js +3 -14
- package/errors/DefaultErrorHandler.d.ts +21 -0
- package/errors/DefaultErrorHandler.js +19 -27
- package/errors/{Errors.ts → Errors.d.ts} +23 -19
- package/errors/Errors.js +8 -2
- package/http/EntityStream.d.ts +10 -0
- package/http/EntityStream.js +42 -0
- package/http/FetchHttpClient.d.ts +15 -0
- package/http/FetchHttpClient.js +1 -1
- package/http/{HttpClient.ts → HttpClient.d.ts} +1 -3
- package/http/HttpContentRange.d.ts +23 -0
- package/http/HttpContentRange.js +39 -0
- package/http/HttpHeader.d.ts +16 -0
- package/http/HttpHeader.js +6 -1
- package/http/{HttpMethod.ts → HttpMethod.d.ts} +1 -1
- package/http/{HttpStatusCode.ts → HttpStatusCode.d.ts} +5 -2
- package/http/HttpStatusCode.js +4 -1
- package/http/ResponseCache.d.ts +19 -0
- package/http/ResponseCache.js +4 -15
- package/http/RetryAfter.d.ts +6 -0
- package/http/RetryAfter.js +24 -0
- package/http/ServerSentEvents.d.ts +50 -0
- package/http/ServerSentEvents.js +140 -0
- package/http/{index.ts → index.d.ts} +4 -0
- package/http/index.js +5 -1
- package/links/AggregateLinkExtractor.d.ts +17 -0
- package/links/AggregateLinkExtractor.js +7 -18
- package/links/HalLinkExtractor.d.ts +13 -0
- package/links/HalLinkExtractor.js +9 -20
- package/links/HeaderLinkExtractor.d.ts +13 -0
- package/links/HeaderLinkExtractor.js +4 -15
- package/links/{Link.ts → Link.d.ts} +6 -2
- package/links/{LinkExtractor.ts → LinkExtractor.d.ts} +0 -1
- package/package.json +66 -16
- package/serializers/JsonSerializer.d.ts +18 -0
- package/serializers/{Serializer.ts → Serializer.d.ts} +0 -2
- package/endpoints/Endpoint.test.ts +0 -300
- package/endpoints/Endpoint.ts +0 -247
- package/endpoints/generic/CollectionEndpoint.test.ts +0 -215
- package/endpoints/generic/ElementEndpoint.test.ts +0 -262
- package/endpoints/generic/IndexerEndpoint.test.ts +0 -12
- package/endpoints/raw/BlobEndpoint.test.ts +0 -52
- package/endpoints/raw/UploadEndpoint.test.ts +0 -47
- package/endpoints/rpc/ActionEndpoint.test.ts +0 -36
- package/endpoints/rpc/ConsumerEndpoint.test.ts +0 -29
- package/endpoints/rpc/FunctionEndpoint.test.ts +0 -29
- package/endpoints/rpc/ProducerEndpoint.test.ts +0 -32
- package/errors/DefaultErrorHandler.ts +0 -62
- package/http/FetchHttpClient.ts +0 -26
- package/http/HttpHeader.ts +0 -11
- package/http/ResponseCache.ts +0 -25
- package/jest.config.js +0 -12
- package/links/AggregateLinkExtractor.ts +0 -28
- package/links/HalLinkExtractor.ts +0 -45
- package/links/HeaderLinkExtractor.ts +0 -56
- package/serializers/JsonSerializer.ts +0 -25
- /package/endpoints/raw/{index.ts → index.d.ts} +0 -0
- /package/endpoints/rpc/{index.ts → index.d.ts} +0 -0
- /package/errors/{ErrorHandler.ts → ErrorHandler.d.ts} +0 -0
- /package/errors/{index.ts → index.d.ts} +0 -0
- /package/{index.ts → index.d.ts} +0 -0
- /package/links/{index.ts → index.d.ts} +0 -0
- /package/serializers/{index.ts → index.d.ts} +0 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ElementEndpoint } from "../generic/ElementEndpoint";
|
|
2
|
+
import { GenericCollectionEndpoint } from "../generic/GenericCollectionEndpoint";
|
|
3
|
+
/**
|
|
4
|
+
* Endpoint for a collection of `TEntity`s observable as an append-only stream using long-polling.<br>
|
|
5
|
+
* Use {@link StreamingCollectionEndpoint} instead if you wish to use the default {@link ElementEndpoint} type.
|
|
6
|
+
* @typeParam TEntity The type of individual elements in the collection.
|
|
7
|
+
* @typeParam TElementEndpoint The type of {@link ElementEndpoint} to provide for individual `TEntity`s.
|
|
8
|
+
*/
|
|
9
|
+
export declare class GenericStreamingCollectionEndpoint<TEntity, TElementEndpoint extends ElementEndpoint<TEntity>> extends GenericCollectionEndpoint<TEntity, TElementEndpoint> {
|
|
10
|
+
/**
|
|
11
|
+
* The interval in milliseconds to wait between polling requests.
|
|
12
|
+
* Automatically updated whenever the server sends a {@link http!HttpHeader.RetryAfter} header.
|
|
13
|
+
*/
|
|
14
|
+
pollingInterval: number;
|
|
15
|
+
/**
|
|
16
|
+
* @inheritDoc
|
|
17
|
+
*/
|
|
18
|
+
protected handle(response: Response): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Provides a stream of `TEntity`s, polling the server for elements appended to the collection.
|
|
21
|
+
* @param startIndex The index of the first element to return. Use a negative value to start with the last `-startIndex` elements ("tail").
|
|
22
|
+
* @param signal Used to stop polling.
|
|
23
|
+
* @throws {@link errors!AuthenticationError}: {@link http!HttpStatusCode.Unauthorized}
|
|
24
|
+
* @throws {@link errors!AuthorizationError}: {@link http!HttpStatusCode.Forbidden}
|
|
25
|
+
* @throws {@link errors!HttpError}: Other non-success status code
|
|
26
|
+
*/
|
|
27
|
+
stream(startIndex?: number, signal?: AbortSignal): AsyncIterableIterator<TEntity>;
|
|
28
|
+
/**
|
|
29
|
+
* Provides a stream of `TEntity`s using {@link stream}.
|
|
30
|
+
*/
|
|
31
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<TEntity>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GenericStreamingCollectionEndpoint = void 0;
|
|
4
|
+
const GenericCollectionEndpoint_1 = require("../generic/GenericCollectionEndpoint");
|
|
5
|
+
const Delay_1 = require("./Delay");
|
|
6
|
+
const http_1 = require("../../http");
|
|
7
|
+
const errors_1 = require("../../errors");
|
|
8
|
+
/**
|
|
9
|
+
* Endpoint for a collection of `TEntity`s observable as an append-only stream using long-polling.<br>
|
|
10
|
+
* Use {@link StreamingCollectionEndpoint} instead if you wish to use the default {@link ElementEndpoint} type.
|
|
11
|
+
* @typeParam TEntity The type of individual elements in the collection.
|
|
12
|
+
* @typeParam TElementEndpoint The type of {@link ElementEndpoint} to provide for individual `TEntity`s.
|
|
13
|
+
*/
|
|
14
|
+
class GenericStreamingCollectionEndpoint extends GenericCollectionEndpoint_1.GenericCollectionEndpoint {
|
|
15
|
+
constructor() {
|
|
16
|
+
super(...arguments);
|
|
17
|
+
/**
|
|
18
|
+
* The interval in milliseconds to wait between polling requests.
|
|
19
|
+
* Automatically updated whenever the server sends a {@link http!HttpHeader.RetryAfter} header.
|
|
20
|
+
*/
|
|
21
|
+
this.pollingInterval = 3000;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* @inheritDoc
|
|
25
|
+
*/
|
|
26
|
+
async handle(response) {
|
|
27
|
+
var _a;
|
|
28
|
+
await super.handle(response);
|
|
29
|
+
this.pollingInterval = (_a = (0, http_1.retryAfter)(response)) !== null && _a !== void 0 ? _a : this.pollingInterval;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Provides a stream of `TEntity`s, polling the server for elements appended to the collection.
|
|
33
|
+
* @param startIndex The index of the first element to return. Use a negative value to start with the last `-startIndex` elements ("tail").
|
|
34
|
+
* @param signal Used to stop polling.
|
|
35
|
+
* @throws {@link errors!AuthenticationError}: {@link http!HttpStatusCode.Unauthorized}
|
|
36
|
+
* @throws {@link errors!AuthorizationError}: {@link http!HttpStatusCode.Forbidden}
|
|
37
|
+
* @throws {@link errors!HttpError}: Other non-success status code
|
|
38
|
+
*/
|
|
39
|
+
async *stream(startIndex = 0, signal) {
|
|
40
|
+
var _a;
|
|
41
|
+
let currentStartIndex = startIndex;
|
|
42
|
+
while (!(signal === null || signal === void 0 ? void 0 : signal.aborted)) {
|
|
43
|
+
let response;
|
|
44
|
+
try {
|
|
45
|
+
response = (currentStartIndex >= 0)
|
|
46
|
+
// Offset
|
|
47
|
+
? await this.readRange(currentStartIndex, undefined, signal)
|
|
48
|
+
// Tail
|
|
49
|
+
: await this.readRange(undefined, -currentStartIndex, signal);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
// Aborting is a regular way of ending the stream, not an error
|
|
53
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
54
|
+
return;
|
|
55
|
+
if (!(err instanceof errors_1.RangeNotSatisfiableError))
|
|
56
|
+
throw err;
|
|
57
|
+
// No new data available yet, keep polling
|
|
58
|
+
if (!await (0, Delay_1.delay)(this.pollingInterval, signal))
|
|
59
|
+
return;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
yield* response.elements;
|
|
63
|
+
if (response.endReached)
|
|
64
|
+
return;
|
|
65
|
+
// Continue polling for more data
|
|
66
|
+
if (((_a = response.range) === null || _a === void 0 ? void 0 : _a.to) === undefined)
|
|
67
|
+
return;
|
|
68
|
+
currentStartIndex = response.range.to + 1;
|
|
69
|
+
if (!await (0, Delay_1.delay)(this.pollingInterval, signal))
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Provides a stream of `TEntity`s using {@link stream}.
|
|
75
|
+
*/
|
|
76
|
+
[Symbol.asyncIterator]() { return this.stream(); }
|
|
77
|
+
}
|
|
78
|
+
exports.GenericStreamingCollectionEndpoint = GenericStreamingCollectionEndpoint;
|
|
79
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiR2VuZXJpY1N0cmVhbWluZ0NvbGxlY3Rpb25FbmRwb2ludC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIkdlbmVyaWNTdHJlYW1pbmdDb2xsZWN0aW9uRW5kcG9pbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQ0Esb0ZBQWlGO0FBRWpGLG1DQUFnQztBQUNoQyxxQ0FBd0M7QUFDeEMseUNBQXdEO0FBRXhEOzs7OztHQUtHO0FBQ0gsTUFBYSxrQ0FBK0YsU0FBUSxxREFBb0Q7SUFBeEs7O1FBQ0k7OztXQUdHO1FBQ0gsb0JBQWUsR0FBRyxJQUFJLENBQUM7SUFzRDNCLENBQUM7SUFwREc7O09BRUc7SUFDTyxLQUFLLENBQUMsTUFBTSxDQUFDLFFBQWtCOztRQUNyQyxNQUFNLEtBQUssQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDN0IsSUFBSSxDQUFDLGVBQWUsR0FBRyxNQUFBLElBQUEsaUJBQVUsRUFBQyxRQUFRLENBQUMsbUNBQUksSUFBSSxDQUFDLGVBQWUsQ0FBQztJQUN4RSxDQUFDO0lBRUQ7Ozs7Ozs7T0FPRztJQUNILEtBQUssQ0FBQyxDQUFDLE1BQU0sQ0FBQyxhQUFxQixDQUFDLEVBQUUsTUFBb0I7O1FBQ3RELElBQUksaUJBQWlCLEdBQUcsVUFBVSxDQUFDO1FBRW5DLE9BQU8sQ0FBQyxDQUFBLE1BQU0sYUFBTixNQUFNLHVCQUFOLE1BQU0sQ0FBRSxPQUFPLENBQUEsRUFBRSxDQUFDO1lBQ3RCLElBQUksUUFBa0MsQ0FBQztZQUN2QyxJQUFJLENBQUM7Z0JBQ0QsUUFBUSxHQUFHLENBQUMsaUJBQWlCLElBQUksQ0FBQyxDQUFDO29CQUMvQixTQUFTO29CQUNULENBQUMsQ0FBQyxNQUFNLElBQUksQ0FBQyxTQUFTLENBQUMsaUJBQWlCLEVBQUUsU0FBUyxFQUFFLE1BQU0sQ0FBQztvQkFDNUQsT0FBTztvQkFDUCxDQUFDLENBQUMsTUFBTSxJQUFJLENBQUMsU0FBUyxDQUFDLFNBQVMsRUFBRSxDQUFDLGlCQUFpQixFQUFFLE1BQU0sQ0FBQyxDQUFDO1lBQ3RFLENBQUM7WUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO2dCQUNYLCtEQUErRDtnQkFDL0QsSUFBSSxNQUFNLGFBQU4sTUFBTSx1QkFBTixNQUFNLENBQUUsT0FBTztvQkFBRSxPQUFPO2dCQUM1QixJQUFJLENBQUMsQ0FBQyxHQUFHLFlBQVksaUNBQXdCLENBQUM7b0JBQUUsTUFBTSxHQUFHLENBQUM7Z0JBRTFELDBDQUEwQztnQkFDMUMsSUFBSSxDQUFDLE1BQU0sSUFBQSxhQUFLLEVBQUMsSUFBSSxDQUFDLGVBQWUsRUFBRSxNQUFNLENBQUM7b0JBQUUsT0FBTztnQkFDdkQsU0FBUztZQUNiLENBQUM7WUFFRCxLQUFLLENBQUMsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDO1lBQ3pCLElBQUksUUFBUSxDQUFDLFVBQVU7Z0JBQUUsT0FBTztZQUVoQyxpQ0FBaUM7WUFDakMsSUFBSSxDQUFBLE1BQUEsUUFBUSxDQUFDLEtBQUssMENBQUUsRUFBRSxNQUFLLFNBQVM7Z0JBQUUsT0FBTztZQUM3QyxpQkFBaUIsR0FBRyxRQUFRLENBQUMsS0FBSyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUM7WUFFMUMsSUFBSSxDQUFDLE1BQU0sSUFBQSxhQUFLLEVBQUMsSUFBSSxDQUFDLGVBQWUsRUFBRSxNQUFNLENBQUM7Z0JBQUUsT0FBTztRQUMzRCxDQUFDO0lBQ0wsQ0FBQztJQUVEOztPQUVHO0lBQ0gsQ0FBQyxNQUFNLENBQUMsYUFBYSxDQUFDLEtBQUssT0FBTyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDO0NBQ3JEO0FBM0RELGdGQTJEQyJ9
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ElementEndpoint } from "../generic/ElementEndpoint";
|
|
2
|
+
import { Endpoint } from "../Endpoint";
|
|
3
|
+
/**
|
|
4
|
+
* Endpoint for a resource that can be polled for state changes.
|
|
5
|
+
* @typeParam TEntity The type of entity the endpoint represents.
|
|
6
|
+
*/
|
|
7
|
+
export declare class PollingEndpoint<TEntity> extends ElementEndpoint<TEntity> {
|
|
8
|
+
private readonly endCondition?;
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new polling endpoint.
|
|
11
|
+
* @param referrer The endpoint used to navigate to this one.
|
|
12
|
+
* @param relativeUri The URI of this endpoint relative to the `referrer`'s. Add a `./` prefix here to imply a trailing slash in the `referrer`'s URI.
|
|
13
|
+
* @param endCondition A check to determine whether the entity has reached its final state and no further polling is required.
|
|
14
|
+
*/
|
|
15
|
+
constructor(referrer: Endpoint, relativeUri: URL | string, endCondition?: ((entity: TEntity) => boolean) | undefined);
|
|
16
|
+
/**
|
|
17
|
+
* The interval in milliseconds to wait between polling requests.
|
|
18
|
+
* Automatically updated whenever the server sends a {@link http!HttpHeader.RetryAfter} header.
|
|
19
|
+
*/
|
|
20
|
+
pollingInterval: number;
|
|
21
|
+
/**
|
|
22
|
+
* @inheritDoc
|
|
23
|
+
*/
|
|
24
|
+
protected handle(response: Response): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Provides a stream of the `TEntity`'s states, polling the server for changes.
|
|
27
|
+
* States that are identical to the previously provided one are skipped.
|
|
28
|
+
* @param signal Used to stop polling.
|
|
29
|
+
* @throws {@link errors!AuthenticationError}: {@link http!HttpStatusCode.Unauthorized}
|
|
30
|
+
* @throws {@link errors!AuthorizationError}: {@link http!HttpStatusCode.Forbidden}
|
|
31
|
+
* @throws {@link errors!NotFoundError}: {@link http!HttpStatusCode.NotFound} or {@link http!HttpStatusCode.Gone}
|
|
32
|
+
* @throws {@link errors!HttpError}: Other non-success status code
|
|
33
|
+
*/
|
|
34
|
+
stream(signal?: AbortSignal): AsyncIterableIterator<TEntity>;
|
|
35
|
+
/**
|
|
36
|
+
* Provides a stream of the `TEntity`'s states using {@link stream}.
|
|
37
|
+
*/
|
|
38
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<TEntity>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PollingEndpoint = void 0;
|
|
4
|
+
const ElementEndpoint_1 = require("../generic/ElementEndpoint");
|
|
5
|
+
const Delay_1 = require("./Delay");
|
|
6
|
+
const http_1 = require("../../http");
|
|
7
|
+
/**
|
|
8
|
+
* Endpoint for a resource that can be polled for state changes.
|
|
9
|
+
* @typeParam TEntity The type of entity the endpoint represents.
|
|
10
|
+
*/
|
|
11
|
+
class PollingEndpoint extends ElementEndpoint_1.ElementEndpoint {
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new polling endpoint.
|
|
14
|
+
* @param referrer The endpoint used to navigate to this one.
|
|
15
|
+
* @param relativeUri The URI of this endpoint relative to the `referrer`'s. Add a `./` prefix here to imply a trailing slash in the `referrer`'s URI.
|
|
16
|
+
* @param endCondition A check to determine whether the entity has reached its final state and no further polling is required.
|
|
17
|
+
*/
|
|
18
|
+
constructor(referrer, relativeUri, endCondition) {
|
|
19
|
+
super(referrer, relativeUri);
|
|
20
|
+
this.endCondition = endCondition;
|
|
21
|
+
/**
|
|
22
|
+
* The interval in milliseconds to wait between polling requests.
|
|
23
|
+
* Automatically updated whenever the server sends a {@link http!HttpHeader.RetryAfter} header.
|
|
24
|
+
*/
|
|
25
|
+
this.pollingInterval = 3000;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* @inheritDoc
|
|
29
|
+
*/
|
|
30
|
+
async handle(response) {
|
|
31
|
+
var _a;
|
|
32
|
+
await super.handle(response);
|
|
33
|
+
this.pollingInterval = (_a = (0, http_1.retryAfter)(response)) !== null && _a !== void 0 ? _a : this.pollingInterval;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Provides a stream of the `TEntity`'s states, polling the server for changes.
|
|
37
|
+
* States that are identical to the previously provided one are skipped.
|
|
38
|
+
* @param signal Used to stop polling.
|
|
39
|
+
* @throws {@link errors!AuthenticationError}: {@link http!HttpStatusCode.Unauthorized}
|
|
40
|
+
* @throws {@link errors!AuthorizationError}: {@link http!HttpStatusCode.Forbidden}
|
|
41
|
+
* @throws {@link errors!NotFoundError}: {@link http!HttpStatusCode.NotFound} or {@link http!HttpStatusCode.Gone}
|
|
42
|
+
* @throws {@link errors!HttpError}: Other non-success status code
|
|
43
|
+
*/
|
|
44
|
+
async *stream(signal) {
|
|
45
|
+
var _a;
|
|
46
|
+
// NOTE: Compare the serialized form because every poll deserializes a new object instance.
|
|
47
|
+
let previous;
|
|
48
|
+
while (!(signal === null || signal === void 0 ? void 0 : signal.aborted)) {
|
|
49
|
+
const entity = await this.read(signal);
|
|
50
|
+
const serialized = this.serializer.serialize(entity);
|
|
51
|
+
if (serialized !== previous) {
|
|
52
|
+
previous = serialized;
|
|
53
|
+
yield entity;
|
|
54
|
+
}
|
|
55
|
+
if ((_a = this.endCondition) === null || _a === void 0 ? void 0 : _a.call(this, entity))
|
|
56
|
+
return;
|
|
57
|
+
if (!await (0, Delay_1.delay)(this.pollingInterval, signal))
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Provides a stream of the `TEntity`'s states using {@link stream}.
|
|
63
|
+
*/
|
|
64
|
+
[Symbol.asyncIterator]() { return this.stream(); }
|
|
65
|
+
}
|
|
66
|
+
exports.PollingEndpoint = PollingEndpoint;
|
|
67
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUG9sbGluZ0VuZHBvaW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiUG9sbGluZ0VuZHBvaW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLGdFQUE2RDtBQUU3RCxtQ0FBZ0M7QUFDaEMscUNBQXdDO0FBRXhDOzs7R0FHRztBQUNILE1BQWEsZUFBeUIsU0FBUSxpQ0FBd0I7SUFDbEU7Ozs7O09BS0c7SUFDSCxZQUFZLFFBQWtCLEVBQUUsV0FBeUIsRUFBbUIsWUFBMkM7UUFDbkgsS0FBSyxDQUFDLFFBQVEsRUFBRSxXQUFXLENBQUMsQ0FBQztRQUQyQyxpQkFBWSxHQUFaLFlBQVksQ0FBK0I7UUFJdkg7OztXQUdHO1FBQ0gsb0JBQWUsR0FBRyxJQUFJLENBQUM7SUFOdkIsQ0FBQztJQVFEOztPQUVHO0lBQ08sS0FBSyxDQUFDLE1BQU0sQ0FBQyxRQUFrQjs7UUFDckMsTUFBTSxLQUFLLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQzdCLElBQUksQ0FBQyxlQUFlLEdBQUcsTUFBQSxJQUFBLGlCQUFVLEVBQUMsUUFBUSxDQUFDLG1DQUFJLElBQUksQ0FBQyxlQUFlLENBQUM7SUFDeEUsQ0FBQztJQUVEOzs7Ozs7OztPQVFHO0lBQ0gsS0FBSyxDQUFDLENBQUMsTUFBTSxDQUFDLE1BQW9COztRQUM5QiwyRkFBMkY7UUFDM0YsSUFBSSxRQUE0QixDQUFDO1FBRWpDLE9BQU8sQ0FBQyxDQUFBLE1BQU0sYUFBTixNQUFNLHVCQUFOLE1BQU0sQ0FBRSxPQUFPLENBQUEsRUFBRSxDQUFDO1lBQ3RCLE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUV2QyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUNyRCxJQUFJLFVBQVUsS0FBSyxRQUFRLEVBQUUsQ0FBQztnQkFDMUIsUUFBUSxHQUFHLFVBQVUsQ0FBQztnQkFDdEIsTUFBTSxNQUFNLENBQUM7WUFDakIsQ0FBQztZQUVELElBQUksTUFBQSxJQUFJLENBQUMsWUFBWSxxREFBRyxNQUFNLENBQUM7Z0JBQUUsT0FBTztZQUN4QyxJQUFJLENBQUMsTUFBTSxJQUFBLGFBQUssRUFBQyxJQUFJLENBQUMsZUFBZSxFQUFFLE1BQU0sQ0FBQztnQkFBRSxPQUFPO1FBQzNELENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxDQUFDLE1BQU0sQ0FBQyxhQUFhLENBQUMsS0FBSyxPQUFPLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUM7Q0FDckQ7QUF4REQsMENBd0RDIn0=
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Endpoint } from "../Endpoint";
|
|
2
|
+
/**
|
|
3
|
+
* Endpoint for a stream of `TEntity`s using Server-Sent Events (SSE).<br>
|
|
4
|
+
* Sends `Accept: text/event-stream`. By default, transparently reconnects on connection drops or transient errors,
|
|
5
|
+
* honoring the server-supplied `retry:` interval and resuming via the {@link http!HttpHeader.LastEventId} header.
|
|
6
|
+
* @typeParam TEntity The type of individual elements in the stream.
|
|
7
|
+
*/
|
|
8
|
+
export declare class SseStreamingEndpoint<TEntity> extends Endpoint {
|
|
9
|
+
private readonly eventType?;
|
|
10
|
+
/**
|
|
11
|
+
* Creates a new SSE streaming endpoint.
|
|
12
|
+
* @param referrer The endpoint used to navigate to this one.
|
|
13
|
+
* @param relativeUri The URI of this endpoint relative to the `referrer`'s. Add a `./` prefix here to imply a trailing slash in the `referrer`'s URI.
|
|
14
|
+
* @param eventType If set, only events with this `event:` type are provided; others are ignored. Leave unspecified to provide all events.
|
|
15
|
+
*/
|
|
16
|
+
constructor(referrer: Endpoint, relativeUri: URL | string, eventType?: string | undefined);
|
|
17
|
+
/**
|
|
18
|
+
* Whether to transparently reconnect on connection drops, transient transport errors and 5xx responses.
|
|
19
|
+
*/
|
|
20
|
+
autoReconnect: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The reconnection interval in milliseconds used when the server has not (yet) supplied one via the SSE `retry:` field.
|
|
23
|
+
*/
|
|
24
|
+
defaultReconnectionInterval: number;
|
|
25
|
+
/**
|
|
26
|
+
* Provides a stream of `TEntity`s, reconnecting as needed unless {@link autoReconnect} is disabled.
|
|
27
|
+
* @param signal Used to close the connection.
|
|
28
|
+
* @throws {@link errors!AuthenticationError}: {@link http!HttpStatusCode.Unauthorized}
|
|
29
|
+
* @throws {@link errors!AuthorizationError}: {@link http!HttpStatusCode.Forbidden}
|
|
30
|
+
* @throws {@link errors!NotFoundError}: {@link http!HttpStatusCode.NotFound} or {@link http!HttpStatusCode.Gone}
|
|
31
|
+
* @throws {@link errors!HttpError}: Other non-success status code
|
|
32
|
+
*/
|
|
33
|
+
stream(signal?: AbortSignal): AsyncIterableIterator<TEntity>;
|
|
34
|
+
/**
|
|
35
|
+
* Provides a stream of `TEntity`s using {@link stream}.
|
|
36
|
+
*/
|
|
37
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<TEntity>;
|
|
38
|
+
/**
|
|
39
|
+
* Opens a connection and provides events until the stream ends.
|
|
40
|
+
* @param state Carries the last event id and the reconnection interval over to the next attempt.
|
|
41
|
+
* @param signal Used to close the connection.
|
|
42
|
+
* @returns `true` if another connection attempt should be made; `false` if the stream has ended for good.
|
|
43
|
+
*/
|
|
44
|
+
private consumeOnce;
|
|
45
|
+
/**
|
|
46
|
+
* Determines whether an error is a transport-level failure that may succeed on a retry.
|
|
47
|
+
* The Fetch API reports these as `TypeError`s, while responses the server actually sent become {@link errors!HttpError}s.
|
|
48
|
+
*/
|
|
49
|
+
private static isTransient;
|
|
50
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SseStreamingEndpoint = void 0;
|
|
4
|
+
const Endpoint_1 = require("../Endpoint");
|
|
5
|
+
const Delay_1 = require("./Delay");
|
|
6
|
+
const http_1 = require("../../http");
|
|
7
|
+
const eventStreamMediaType = "text/event-stream";
|
|
8
|
+
/**
|
|
9
|
+
* Endpoint for a stream of `TEntity`s using Server-Sent Events (SSE).<br>
|
|
10
|
+
* Sends `Accept: text/event-stream`. By default, transparently reconnects on connection drops or transient errors,
|
|
11
|
+
* honoring the server-supplied `retry:` interval and resuming via the {@link http!HttpHeader.LastEventId} header.
|
|
12
|
+
* @typeParam TEntity The type of individual elements in the stream.
|
|
13
|
+
*/
|
|
14
|
+
class SseStreamingEndpoint extends Endpoint_1.Endpoint {
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new SSE streaming endpoint.
|
|
17
|
+
* @param referrer The endpoint used to navigate to this one.
|
|
18
|
+
* @param relativeUri The URI of this endpoint relative to the `referrer`'s. Add a `./` prefix here to imply a trailing slash in the `referrer`'s URI.
|
|
19
|
+
* @param eventType If set, only events with this `event:` type are provided; others are ignored. Leave unspecified to provide all events.
|
|
20
|
+
*/
|
|
21
|
+
constructor(referrer, relativeUri, eventType) {
|
|
22
|
+
super(referrer, relativeUri);
|
|
23
|
+
this.eventType = eventType;
|
|
24
|
+
/**
|
|
25
|
+
* Whether to transparently reconnect on connection drops, transient transport errors and 5xx responses.
|
|
26
|
+
*/
|
|
27
|
+
this.autoReconnect = true;
|
|
28
|
+
/**
|
|
29
|
+
* The reconnection interval in milliseconds used when the server has not (yet) supplied one via the SSE `retry:` field.
|
|
30
|
+
*/
|
|
31
|
+
this.defaultReconnectionInterval = 3000;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Provides a stream of `TEntity`s, reconnecting as needed unless {@link autoReconnect} is disabled.
|
|
35
|
+
* @param signal Used to close the connection.
|
|
36
|
+
* @throws {@link errors!AuthenticationError}: {@link http!HttpStatusCode.Unauthorized}
|
|
37
|
+
* @throws {@link errors!AuthorizationError}: {@link http!HttpStatusCode.Forbidden}
|
|
38
|
+
* @throws {@link errors!NotFoundError}: {@link http!HttpStatusCode.NotFound} or {@link http!HttpStatusCode.Gone}
|
|
39
|
+
* @throws {@link errors!HttpError}: Other non-success status code
|
|
40
|
+
*/
|
|
41
|
+
async *stream(signal) {
|
|
42
|
+
const state = { lastEventId: undefined, reconnectionInterval: this.defaultReconnectionInterval };
|
|
43
|
+
while (!(signal === null || signal === void 0 ? void 0 : signal.aborted)) {
|
|
44
|
+
let reconnect;
|
|
45
|
+
try {
|
|
46
|
+
reconnect = yield* this.consumeOnce(state, signal);
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
// Aborting is a regular way of ending the stream, not an error
|
|
50
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
51
|
+
return;
|
|
52
|
+
// Transport-level failures are the ones worth retrying; anything else is the server telling us no
|
|
53
|
+
if (!this.autoReconnect || !SseStreamingEndpoint.isTransient(err))
|
|
54
|
+
throw err;
|
|
55
|
+
reconnect = true;
|
|
56
|
+
}
|
|
57
|
+
if (!reconnect)
|
|
58
|
+
return;
|
|
59
|
+
if (!await (0, Delay_1.delay)(state.reconnectionInterval, signal))
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Provides a stream of `TEntity`s using {@link stream}.
|
|
65
|
+
*/
|
|
66
|
+
[Symbol.asyncIterator]() { return this.stream(); }
|
|
67
|
+
/**
|
|
68
|
+
* Opens a connection and provides events until the stream ends.
|
|
69
|
+
* @param state Carries the last event id and the reconnection interval over to the next attempt.
|
|
70
|
+
* @param signal Used to close the connection.
|
|
71
|
+
* @returns `true` if another connection attempt should be made; `false` if the stream has ended for good.
|
|
72
|
+
*/
|
|
73
|
+
async *consumeOnce(state, signal) {
|
|
74
|
+
var _a;
|
|
75
|
+
// NOTE: Abort the request when the consumer stops iterating, not just when the caller's signal fires.
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
const onAbort = () => controller.abort();
|
|
78
|
+
signal === null || signal === void 0 ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
|
|
79
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
80
|
+
controller.abort();
|
|
81
|
+
try {
|
|
82
|
+
const headers = new Headers({ [http_1.HttpHeader.Accept]: eventStreamMediaType });
|
|
83
|
+
if (state.lastEventId)
|
|
84
|
+
headers.set(http_1.HttpHeader.LastEventId, state.lastEventId);
|
|
85
|
+
const response = await this.httpClient.send(this.uri, http_1.HttpMethod.Get, controller.signal, headers);
|
|
86
|
+
if (response.status === http_1.HttpStatusCode.NoContent)
|
|
87
|
+
return false;
|
|
88
|
+
if (this.autoReconnect && response.status >= 500)
|
|
89
|
+
return true;
|
|
90
|
+
await this.handle(response);
|
|
91
|
+
const reader = new http_1.SseReader(response);
|
|
92
|
+
try {
|
|
93
|
+
for await (const event of reader.events()) {
|
|
94
|
+
if (this.eventType !== undefined && event.type !== this.eventType)
|
|
95
|
+
continue;
|
|
96
|
+
yield this.serializer.deserialize(event.data);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
state.lastEventId = (_a = reader.lastEventId) !== null && _a !== void 0 ? _a : state.lastEventId;
|
|
101
|
+
if (reader.reconnectionInterval !== undefined)
|
|
102
|
+
state.reconnectionInterval = reader.reconnectionInterval;
|
|
103
|
+
}
|
|
104
|
+
return this.autoReconnect;
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
signal === null || signal === void 0 ? void 0 : signal.removeEventListener("abort", onAbort);
|
|
108
|
+
controller.abort();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Determines whether an error is a transport-level failure that may succeed on a retry.
|
|
113
|
+
* The Fetch API reports these as `TypeError`s, while responses the server actually sent become {@link errors!HttpError}s.
|
|
114
|
+
*/
|
|
115
|
+
static isTransient(err) {
|
|
116
|
+
return err instanceof TypeError;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
exports.SseStreamingEndpoint = SseStreamingEndpoint;
|
|
120
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU3NlU3RyZWFtaW5nRW5kcG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJTc2VTdHJlYW1pbmdFbmRwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwwQ0FBdUM7QUFDdkMsbUNBQWdDO0FBQ2hDLHFDQUErRTtBQUUvRSxNQUFNLG9CQUFvQixHQUFHLG1CQUFtQixDQUFDO0FBRWpEOzs7OztHQUtHO0FBQ0gsTUFBYSxvQkFBOEIsU0FBUSxtQkFBUTtJQUN2RDs7Ozs7T0FLRztJQUNILFlBQVksUUFBa0IsRUFBRSxXQUF5QixFQUFtQixTQUFrQjtRQUMxRixLQUFLLENBQUMsUUFBUSxFQUFFLFdBQVcsQ0FBQyxDQUFDO1FBRDJDLGNBQVMsR0FBVCxTQUFTLENBQVM7UUFJOUY7O1dBRUc7UUFDSCxrQkFBYSxHQUFHLElBQUksQ0FBQztRQUVyQjs7V0FFRztRQUNILGdDQUEyQixHQUFHLElBQUksQ0FBQztJQVZuQyxDQUFDO0lBWUQ7Ozs7Ozs7T0FPRztJQUNILEtBQUssQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFvQjtRQUM5QixNQUFNLEtBQUssR0FBRyxFQUFFLFdBQVcsRUFBRSxTQUErQixFQUFFLG9CQUFvQixFQUFFLElBQUksQ0FBQywyQkFBMkIsRUFBRSxDQUFDO1FBRXZILE9BQU8sQ0FBQyxDQUFBLE1BQU0sYUFBTixNQUFNLHVCQUFOLE1BQU0sQ0FBRSxPQUFPLENBQUEsRUFBRSxDQUFDO1lBQ3RCLElBQUksU0FBa0IsQ0FBQztZQUN2QixJQUFJLENBQUM7Z0JBQ0QsU0FBUyxHQUFHLEtBQUssQ0FBQyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1lBQ3ZELENBQUM7WUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO2dCQUNYLCtEQUErRDtnQkFDL0QsSUFBSSxNQUFNLGFBQU4sTUFBTSx1QkFBTixNQUFNLENBQUUsT0FBTztvQkFBRSxPQUFPO2dCQUU1QixrR0FBa0c7Z0JB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
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { GenericStreamingCollectionEndpoint } from "./GenericStreamingCollectionEndpoint";
|
|
2
|
+
import { ElementEndpoint } from "../generic/ElementEndpoint";
|
|
3
|
+
import { Endpoint } from "../Endpoint";
|
|
4
|
+
/**
|
|
5
|
+
* Endpoint for a collection of `TEntity`s observable as an append-only stream using long-polling,
|
|
6
|
+
* addressable as {@link ElementEndpoint}s.<br>
|
|
7
|
+
* Use {@link GenericStreamingCollectionEndpoint} instead if you wish to customize the element endpoint type.
|
|
8
|
+
* @typeParam TEntity The type of individual elements in the collection.
|
|
9
|
+
*/
|
|
10
|
+
export declare class StreamingCollectionEndpoint<TEntity> extends GenericStreamingCollectionEndpoint<TEntity, ElementEndpoint<TEntity>> {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new streaming collection endpoint.
|
|
13
|
+
* @param referrer The endpoint used to navigate to this one.
|
|
14
|
+
* @param relativeUri The URI of this endpoint relative to the `referrer`'s. Add a `./` prefix here to imply a trailing slash in the `referrer`'s URI.
|
|
15
|
+
*/
|
|
16
|
+
constructor(referrer: Endpoint, relativeUri: URL | string);
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StreamingCollectionEndpoint = void 0;
|
|
4
|
+
const GenericStreamingCollectionEndpoint_1 = require("./GenericStreamingCollectionEndpoint");
|
|
5
|
+
const ElementEndpoint_1 = require("../generic/ElementEndpoint");
|
|
6
|
+
/**
|
|
7
|
+
* Endpoint for a collection of `TEntity`s observable as an append-only stream using long-polling,
|
|
8
|
+
* addressable as {@link ElementEndpoint}s.<br>
|
|
9
|
+
* Use {@link GenericStreamingCollectionEndpoint} instead if you wish to customize the element endpoint type.
|
|
10
|
+
* @typeParam TEntity The type of individual elements in the collection.
|
|
11
|
+
*/
|
|
12
|
+
class StreamingCollectionEndpoint extends GenericStreamingCollectionEndpoint_1.GenericStreamingCollectionEndpoint {
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new streaming collection endpoint.
|
|
15
|
+
* @param referrer The endpoint used to navigate to this one.
|
|
16
|
+
* @param relativeUri The URI of this endpoint relative to the `referrer`'s. Add a `./` prefix here to imply a trailing slash in the `referrer`'s URI.
|
|
17
|
+
*/
|
|
18
|
+
constructor(referrer, relativeUri) {
|
|
19
|
+
super(referrer, relativeUri, ElementEndpoint_1.ElementEndpoint);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.StreamingCollectionEndpoint = StreamingCollectionEndpoint;
|
|
23
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU3RyZWFtaW5nQ29sbGVjdGlvbkVuZHBvaW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiU3RyZWFtaW5nQ29sbGVjdGlvbkVuZHBvaW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDZGQUEwRjtBQUMxRixnRUFBNkQ7QUFHN0Q7Ozs7O0dBS0c7QUFDSCxNQUFhLDJCQUFxQyxTQUFRLHVFQUFxRTtJQUMzSDs7OztPQUlHO0lBQ0gsWUFBWSxRQUFrQixFQUFFLFdBQXlCO1FBQ3JELEtBQUssQ0FBQyxRQUFRLEVBQUUsV0FBVyxFQUFFLGlDQUFlLENBQUMsQ0FBQztJQUNsRCxDQUFDO0NBQ0o7QUFURCxrRUFTQyJ9
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Endpoint } from "../Endpoint";
|
|
2
|
+
/**
|
|
3
|
+
* Endpoint for a stream of `TEntity`s using a persistent HTTP connection.
|
|
4
|
+
* @typeParam TEntity The type of individual elements in the stream.
|
|
5
|
+
*/
|
|
6
|
+
export declare class StreamingEndpoint<TEntity> extends Endpoint {
|
|
7
|
+
private readonly separator;
|
|
8
|
+
/**
|
|
9
|
+
* Creates a new streaming endpoint.
|
|
10
|
+
* @param referrer The endpoint used to navigate to this one.
|
|
11
|
+
* @param relativeUri The URI of this endpoint relative to the `referrer`'s. Add a `./` prefix here to imply a trailing slash in the `referrer`'s URI.
|
|
12
|
+
* @param separator The character sequence used to detect that a new element starts in the HTTP stream.
|
|
13
|
+
*/
|
|
14
|
+
constructor(referrer: Endpoint, relativeUri: URL | string, separator?: string);
|
|
15
|
+
/**
|
|
16
|
+
* Provides a stream of `TEntity`s, keeping the HTTP connection open until the server closes it.
|
|
17
|
+
* @param signal Used to close the connection.
|
|
18
|
+
* @throws {@link errors!AuthenticationError}: {@link http!HttpStatusCode.Unauthorized}
|
|
19
|
+
* @throws {@link errors!AuthorizationError}: {@link http!HttpStatusCode.Forbidden}
|
|
20
|
+
* @throws {@link errors!NotFoundError}: {@link http!HttpStatusCode.NotFound} or {@link http!HttpStatusCode.Gone}
|
|
21
|
+
* @throws {@link errors!HttpError}: Other non-success status code
|
|
22
|
+
*/
|
|
23
|
+
stream(signal?: AbortSignal): AsyncIterableIterator<TEntity>;
|
|
24
|
+
/**
|
|
25
|
+
* Provides a stream of `TEntity`s using {@link stream}.
|
|
26
|
+
*/
|
|
27
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<TEntity>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StreamingEndpoint = void 0;
|
|
4
|
+
const Endpoint_1 = require("../Endpoint");
|
|
5
|
+
const http_1 = require("../../http");
|
|
6
|
+
/**
|
|
7
|
+
* Endpoint for a stream of `TEntity`s using a persistent HTTP connection.
|
|
8
|
+
* @typeParam TEntity The type of individual elements in the stream.
|
|
9
|
+
*/
|
|
10
|
+
class StreamingEndpoint extends Endpoint_1.Endpoint {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new streaming endpoint.
|
|
13
|
+
* @param referrer The endpoint used to navigate to this one.
|
|
14
|
+
* @param relativeUri The URI of this endpoint relative to the `referrer`'s. Add a `./` prefix here to imply a trailing slash in the `referrer`'s URI.
|
|
15
|
+
* @param separator The character sequence used to detect that a new element starts in the HTTP stream.
|
|
16
|
+
*/
|
|
17
|
+
constructor(referrer, relativeUri, separator = "\n") {
|
|
18
|
+
super(referrer, relativeUri);
|
|
19
|
+
this.separator = separator;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Provides a stream of `TEntity`s, keeping the HTTP connection open until the server closes it.
|
|
23
|
+
* @param signal Used to close the connection.
|
|
24
|
+
* @throws {@link errors!AuthenticationError}: {@link http!HttpStatusCode.Unauthorized}
|
|
25
|
+
* @throws {@link errors!AuthorizationError}: {@link http!HttpStatusCode.Forbidden}
|
|
26
|
+
* @throws {@link errors!NotFoundError}: {@link http!HttpStatusCode.NotFound} or {@link http!HttpStatusCode.Gone}
|
|
27
|
+
* @throws {@link errors!HttpError}: Other non-success status code
|
|
28
|
+
*/
|
|
29
|
+
async *stream(signal) {
|
|
30
|
+
// NOTE: Abort the request when the consumer stops iterating, not just when the caller's signal fires.
|
|
31
|
+
const controller = new AbortController();
|
|
32
|
+
const onAbort = () => controller.abort();
|
|
33
|
+
signal === null || signal === void 0 ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
|
|
34
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
35
|
+
controller.abort();
|
|
36
|
+
try {
|
|
37
|
+
const response = await this.send(http_1.HttpMethod.Get, controller.signal);
|
|
38
|
+
yield* (0, http_1.entityStream)(response, this.serializer, this.separator);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
// Aborting is a regular way of ending the stream, not an error
|
|
42
|
+
if (!(signal === null || signal === void 0 ? void 0 : signal.aborted))
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
signal === null || signal === void 0 ? void 0 : signal.removeEventListener("abort", onAbort);
|
|
47
|
+
controller.abort();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Provides a stream of `TEntity`s using {@link stream}.
|
|
52
|
+
*/
|
|
53
|
+
[Symbol.asyncIterator]() { return this.stream(); }
|
|
54
|
+
}
|
|
55
|
+
exports.StreamingEndpoint = StreamingEndpoint;
|
|
56
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU3RyZWFtaW5nRW5kcG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJTdHJlYW1pbmdFbmRwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwwQ0FBdUM7QUFDdkMscUNBQXNEO0FBRXREOzs7R0FHRztBQUNILE1BQWEsaUJBQTJCLFNBQVEsbUJBQVE7SUFDcEQ7Ozs7O09BS0c7SUFDSCxZQUFZLFFBQWtCLEVBQUUsV0FBeUIsRUFBbUIsWUFBb0IsSUFBSTtRQUNoRyxLQUFLLENBQUMsUUFBUSxFQUFFLFdBQVcsQ0FBQyxDQUFDO1FBRDJDLGNBQVMsR0FBVCxTQUFTLENBQWU7SUFFcEcsQ0FBQztJQUVEOzs7Ozs7O09BT0c7SUFDSCxLQUFLLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBb0I7UUFDOUIsc0dBQXNHO1FBQ3RHLE1BQU0sVUFBVSxHQUFHLElBQUksZUFBZSxFQUFFLENBQUM7UUFDekMsTUFBTSxPQUFPLEdBQUcsR0FBRyxFQUFFLENBQUMsVUFBVSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ3pDLE1BQU0sYUFBTixNQUFNLHVCQUFOLE1BQU0sQ0FBRSxnQkFBZ0IsQ0FBQyxPQUFPLEVBQUUsT0FBTyxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7UUFDM0QsSUFBSSxNQUFNLGFBQU4sTUFBTSx1QkFBTixNQUFNLENBQUUsT0FBTztZQUFFLFVBQVUsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUV4QyxJQUFJLENBQUM7WUFDRCxNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsaUJBQVUsQ0FBQyxHQUFHLEVBQUUsVUFBVSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1lBQ3BFLEtBQUssQ0FBQyxDQUFDLElBQUEsbUJBQVksRUFBVSxRQUFRLEVBQUUsSUFBSSxDQUFDLFVBQVUsRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDNUUsQ0FBQztRQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7WUFDWCwrREFBK0Q7WUFDL0QsSUFBSSxDQUFDLENBQUEsTUFBTSxhQUFOLE1BQU0sdUJBQU4sTUFBTSxDQUFFLE9BQU8sQ0FBQTtnQkFBRSxNQUFNLEdBQUcsQ0FBQztRQUNwQyxDQUFDO2dCQUFTLENBQUM7WUFDUCxNQUFNLGFBQU4sTUFBTSx1QkFBTixNQUFNLENBQUUsbUJBQW1CLENBQUMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1lBQzlDLFVBQVUsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUN2QixDQUFDO0lBQ0wsQ0FBQztJQUVEOztPQUVHO0lBQ0gsQ0FBQyxNQUFNLENBQUMsYUFBYSxDQUFDLEtBQUssT0FBTyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDO0NBQ3JEO0FBMUNELDhDQTBDQyJ9
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./PollingEndpoint"), exports);
|
|
18
|
+
__exportStar(require("./StreamingEndpoint"), exports);
|
|
19
|
+
__exportStar(require("./SseStreamingEndpoint"), exports);
|
|
20
|
+
__exportStar(require("./GenericStreamingCollectionEndpoint"), exports);
|
|
21
|
+
__exportStar(require("./StreamingCollectionEndpoint"), exports);
|
|
22
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsb0RBQWtDO0FBQ2xDLHNEQUFvQztBQUNwQyx5REFBdUM7QUFDdkMsdUVBQXFEO0FBQ3JELGdFQUE4QyJ9
|
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
import { RpcEndpointBase } from "./RpcEndpointBase";
|
|
2
2
|
import { Endpoint } from "../Endpoint";
|
|
3
|
-
import { HttpMethod } from "../../http";
|
|
4
|
-
|
|
5
3
|
/**
|
|
6
4
|
* RPC endpoint that is invoked with no input or output.
|
|
7
5
|
*/
|
|
8
|
-
export class ActionEndpoint extends RpcEndpointBase {
|
|
6
|
+
export declare class ActionEndpoint extends RpcEndpointBase {
|
|
9
7
|
/**
|
|
10
8
|
* Creates a new action endpoint.
|
|
11
9
|
* @param referrer The endpoint used to navigate to this one.
|
|
12
10
|
* @param relativeUri The URI of this endpoint relative to the `referrer`'s. Add a `./` prefix here to imply a trailing slash in the `referrer`'s URI.
|
|
13
11
|
*/
|
|
14
|
-
constructor(referrer: Endpoint, relativeUri: URL | string)
|
|
15
|
-
super(referrer, relativeUri);
|
|
16
|
-
}
|
|
17
|
-
|
|
12
|
+
constructor(referrer: Endpoint, relativeUri: URL | string);
|
|
18
13
|
/**
|
|
19
14
|
* Invokes the action.
|
|
20
15
|
* @param signal Used to cancel the request.
|
|
@@ -23,7 +18,5 @@ export class ActionEndpoint extends RpcEndpointBase {
|
|
|
23
18
|
* @throws {@link errors!NotFoundError}: {@link http!HttpStatusCode.NotFound} or {@link http!HttpStatusCode.Gone}
|
|
24
19
|
* @throws {@link errors!HttpError}: Other non-success status code
|
|
25
20
|
*/
|
|
26
|
-
|
|
27
|
-
await this.send(HttpMethod.Post, signal);
|
|
28
|
-
}
|
|
21
|
+
invoke(signal?: AbortSignal): Promise<void>;
|
|
29
22
|
}
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.ActionEndpoint = void 0;
|
|
13
4
|
const RpcEndpointBase_1 = require("./RpcEndpointBase");
|
|
@@ -32,11 +23,9 @@ class ActionEndpoint extends RpcEndpointBase_1.RpcEndpointBase {
|
|
|
32
23
|
* @throws {@link errors!NotFoundError}: {@link http!HttpStatusCode.NotFound} or {@link http!HttpStatusCode.Gone}
|
|
33
24
|
* @throws {@link errors!HttpError}: Other non-success status code
|
|
34
25
|
*/
|
|
35
|
-
invoke(signal) {
|
|
36
|
-
|
|
37
|
-
yield this.send(http_1.HttpMethod.Post, signal);
|
|
38
|
-
});
|
|
26
|
+
async invoke(signal) {
|
|
27
|
+
await this.send(http_1.HttpMethod.Post, signal);
|
|
39
28
|
}
|
|
40
29
|
}
|
|
41
30
|
exports.ActionEndpoint = ActionEndpoint;
|
|
42
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
31
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQWN0aW9uRW5kcG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJBY3Rpb25FbmRwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSx1REFBb0Q7QUFFcEQscUNBQXdDO0FBRXhDOztHQUVHO0FBQ0gsTUFBYSxjQUFlLFNBQVEsaUNBQWU7SUFDL0M7Ozs7T0FJRztJQUNILFlBQVksUUFBa0IsRUFBRSxXQUF5QjtRQUNyRCxLQUFLLENBQUMsUUFBUSxFQUFFLFdBQVcsQ0FBQyxDQUFDO0lBQ2pDLENBQUM7SUFFRDs7Ozs7OztPQU9HO0lBQ0gsS0FBSyxDQUFDLE1BQU0sQ0FBQyxNQUFvQjtRQUM3QixNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsaUJBQVUsQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDN0MsQ0FBQztDQUNKO0FBckJELHdDQXFCQyJ9
|