typedrest 0.4.1 → 0.5.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.
Files changed (62) hide show
  1. package/README.md +2 -0
  2. package/endpoints/Endpoint.js +9 -22
  3. package/endpoints/EntryEndpoint.js +3 -14
  4. package/endpoints/generic/ETagEndpointBase.js +27 -42
  5. package/endpoints/generic/ElementEndpoint.js +39 -60
  6. package/endpoints/generic/GenericCollectionEndpoint.js +70 -44
  7. package/endpoints/generic/GenericCollectionEndpoint.test.ts +123 -0
  8. package/endpoints/generic/GenericCollectionEndpoint.ts +54 -1
  9. package/endpoints/generic/PartialResponse.js +33 -0
  10. package/endpoints/generic/PartialResponse.ts +28 -0
  11. package/endpoints/generic/index.js +2 -1
  12. package/endpoints/generic/index.ts +1 -0
  13. package/endpoints/index.js +3 -2
  14. package/endpoints/index.ts +1 -0
  15. package/endpoints/raw/BlobEndpoint.js +10 -27
  16. package/endpoints/raw/UploadEndpoint.js +11 -22
  17. package/endpoints/reactive/Delay.js +27 -0
  18. package/endpoints/reactive/Delay.ts +26 -0
  19. package/endpoints/reactive/GenericStreamingCollectionEndpoint.js +79 -0
  20. package/endpoints/reactive/GenericStreamingCollectionEndpoint.ts +73 -0
  21. package/endpoints/reactive/PollingEndpoint.js +67 -0
  22. package/endpoints/reactive/PollingEndpoint.test.ts +84 -0
  23. package/endpoints/reactive/PollingEndpoint.ts +66 -0
  24. package/endpoints/reactive/SseStreamingEndpoint.js +120 -0
  25. package/endpoints/reactive/SseStreamingEndpoint.test.ts +169 -0
  26. package/endpoints/reactive/SseStreamingEndpoint.ts +115 -0
  27. package/endpoints/reactive/StreamingCollectionEndpoint.js +23 -0
  28. package/endpoints/reactive/StreamingCollectionEndpoint.test.ts +110 -0
  29. package/endpoints/reactive/StreamingCollectionEndpoint.ts +20 -0
  30. package/endpoints/reactive/StreamingEndpoint.js +56 -0
  31. package/endpoints/reactive/StreamingEndpoint.test.ts +130 -0
  32. package/endpoints/reactive/StreamingEndpoint.ts +50 -0
  33. package/endpoints/reactive/index.js +22 -0
  34. package/endpoints/reactive/index.ts +5 -0
  35. package/endpoints/rpc/ActionEndpoint.js +3 -14
  36. package/endpoints/rpc/ConsumerEndpoint.js +5 -16
  37. package/endpoints/rpc/FunctionEndpoint.js +6 -17
  38. package/endpoints/rpc/ProducerEndpoint.js +4 -15
  39. package/endpoints/rpc/RpcEndpointBase.js +3 -14
  40. package/errors/DefaultErrorHandler.js +19 -27
  41. package/errors/DefaultErrorHandler.ts +4 -1
  42. package/errors/Errors.js +8 -2
  43. package/errors/Errors.ts +5 -0
  44. package/http/EntityStream.js +42 -0
  45. package/http/EntityStream.ts +40 -0
  46. package/http/HttpContentRange.js +39 -0
  47. package/http/HttpContentRange.ts +41 -0
  48. package/http/HttpHeader.js +6 -1
  49. package/http/HttpHeader.ts +6 -1
  50. package/http/HttpStatusCode.js +4 -1
  51. package/http/HttpStatusCode.ts +4 -1
  52. package/http/ResponseCache.js +4 -15
  53. package/http/RetryAfter.js +24 -0
  54. package/http/RetryAfter.ts +19 -0
  55. package/http/ServerSentEvents.js +140 -0
  56. package/http/ServerSentEvents.ts +145 -0
  57. package/http/index.js +5 -1
  58. package/http/index.ts +4 -0
  59. package/links/AggregateLinkExtractor.js +7 -18
  60. package/links/HalLinkExtractor.js +9 -20
  61. package/links/HeaderLinkExtractor.js +4 -15
  62. package/package.json +8 -8
@@ -0,0 +1,123 @@
1
+ /**
2
+ * NOTE: The jsdom environment strips the "Range" request header, so these tests need the node environment.
3
+ * @jest-environment node
4
+ */
5
+
6
+ import fetchMock from 'jest-fetch-mock';
7
+ import { CollectionEndpoint } from '.';
8
+ import { EntryEndpoint } from '..';
9
+ import { HttpHeader, HttpStatusCode, HttpContentRange } from '../../http';
10
+ import { RangeNotSatisfiableError } from '../../errors';
11
+
12
+ class MockEntity {
13
+ constructor(public id: number, public name: string) { }
14
+ }
15
+
16
+ fetchMock.enableMocks();
17
+ let endpoint: CollectionEndpoint<MockEntity>;
18
+
19
+ beforeEach(() => {
20
+ fetchMock.resetMocks();
21
+ endpoint = new CollectionEndpoint(new EntryEndpoint('http://localhost/'), 'endpoint');
22
+ });
23
+
24
+ test('readRange', async () => {
25
+ fetchMock.mockOnceIf(
26
+ 'http://localhost/endpoint',
27
+ async req => {
28
+ expect(req.headers.get(HttpHeader.Range)).toBe('elements=1-2');
29
+ return {
30
+ body: '[{"id":6,"name":"test2"}, {"id":7,"name":"test3"}]',
31
+ status: HttpStatusCode.PartialContent,
32
+ headers: {
33
+ [HttpHeader.ContentRange]: 'elements 1-2/3'
34
+ }
35
+ };
36
+ }
37
+ );
38
+
39
+ const result = await endpoint.readRange(1, 2);
40
+ expect(result.elements).toEqual([new MockEntity(6, 'test2'), new MockEntity(7, 'test3')]);
41
+ expect(result.range).toEqual(new HttpContentRange('elements', 1, 2, 3));
42
+ expect(result.endReached).toBe(true);
43
+ });
44
+
45
+ test('readRangeOffset', async () => {
46
+ fetchMock.mockOnceIf(
47
+ 'http://localhost/endpoint',
48
+ async req => {
49
+ expect(req.headers.get(HttpHeader.Range)).toBe('elements=1-');
50
+ return {
51
+ body: '[{"id":6,"name":"test2"}]',
52
+ status: HttpStatusCode.PartialContent,
53
+ headers: {
54
+ [HttpHeader.ContentRange]: 'elements 1-1/*'
55
+ }
56
+ };
57
+ }
58
+ );
59
+
60
+ const result = await endpoint.readRange(1);
61
+ expect(result.elements).toEqual([new MockEntity(6, 'test2')]);
62
+ expect(result.range).toEqual(new HttpContentRange('elements', 1, 1, undefined));
63
+ expect(result.endReached).toBe(false);
64
+ });
65
+
66
+ test('readRangeTail', async () => {
67
+ fetchMock.mockOnceIf(
68
+ 'http://localhost/endpoint',
69
+ async req => {
70
+ expect(req.headers.get(HttpHeader.Range)).toBe('elements=-1');
71
+ return {
72
+ body: '[{"id":6,"name":"test2"}]',
73
+ status: HttpStatusCode.PartialContent
74
+ };
75
+ }
76
+ );
77
+
78
+ const result = await endpoint.readRange(undefined, 1);
79
+ expect(result.elements).toEqual([new MockEntity(6, 'test2')]);
80
+ expect(result.range).toBeUndefined();
81
+ expect(result.endReached).toBe(true);
82
+ });
83
+
84
+ test('readRangeNotSatisfiable', async () => {
85
+ fetchMock.mockOnceIf(
86
+ 'http://localhost/endpoint',
87
+ async () => ({ status: HttpStatusCode.RangeNotSatisfiable })
88
+ );
89
+
90
+ await expect(endpoint.readRange(1)).rejects.toThrow(RangeNotSatisfiableError);
91
+ });
92
+
93
+ test('readRangeAllowed', async () => {
94
+ expect(endpoint.readRangeAllowed).toBeUndefined();
95
+
96
+ fetchMock.mockOnceIf(
97
+ 'http://localhost/endpoint',
98
+ '[]',
99
+ {
100
+ headers: {
101
+ [HttpHeader.AcceptRanges]: 'elements'
102
+ }
103
+ }
104
+ );
105
+ await endpoint.readAll();
106
+
107
+ expect(endpoint.readRangeAllowed).toBe(true);
108
+ });
109
+
110
+ test('readRangeNotAllowed', async () => {
111
+ fetchMock.mockOnceIf(
112
+ 'http://localhost/endpoint',
113
+ '[]',
114
+ {
115
+ headers: {
116
+ [HttpHeader.AcceptRanges]: 'none'
117
+ }
118
+ }
119
+ );
120
+ await endpoint.readAll();
121
+
122
+ expect(endpoint.readRangeAllowed).toBe(false);
123
+ });
@@ -1,8 +1,9 @@
1
1
  import { ETagEndpointBase } from "./ETagEndpointBase";
2
2
  import { ElementEndpoint } from "./ElementEndpoint";
3
+ import { PartialResponse } from "./PartialResponse";
3
4
  import { Endpoint } from "../Endpoint";
4
5
  import { CachingEndpoint } from "../CachingEndpoint";
5
- import { HttpMethod, HttpHeader, ResponseCache } from "../../http";
6
+ import { HttpMethod, HttpHeader, HttpContentRange, ResponseCache } from "../../http";
6
7
 
7
8
  /**
8
9
  * Endpoint for a collection of `TEntity`s addressable as `TElementEndpoint`s.<br>
@@ -60,6 +61,58 @@ export class GenericCollectionEndpoint<TEntity, TElementEndpoint extends Element
60
61
  return this.serializer.deserialize<TEntity[]>(await this.getContent(signal));
61
62
  }
62
63
 
64
+ /**
65
+ * The unit used when requesting ranges of elements. Sent in the {@link http!HttpHeader.Range} header.
66
+ */
67
+ rangeUnit = "elements";
68
+
69
+ // NOTE: Only modified when the server reports its capabilities.
70
+ private acceptedRangeUnits: string[] = [];
71
+
72
+ /**
73
+ * @inheritDoc
74
+ */
75
+ protected handleCapabilities(response: Response) {
76
+ super.handleCapabilities(response);
77
+
78
+ const header = response.headers.get(HttpHeader.AcceptRanges);
79
+ if (header) {
80
+ this.acceptedRangeUnits = header.split(",").map(x => x.trim());
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Shows whether the server has indicated that {@link readRange} is currently allowed.
86
+ * Uses cached data from last response.
87
+ * @returns `true` if the method is allowed, `false` if the method is not allowed, `undefined` if no request has been sent yet or the server did not specify accepted range units.
88
+ */
89
+ get readRangeAllowed(): boolean | undefined {
90
+ if (this.acceptedRangeUnits.length === 0)
91
+ return undefined;
92
+
93
+ return this.acceptedRangeUnits.indexOf(this.rangeUnit) !== -1;
94
+ }
95
+
96
+ /**
97
+ * Returns a subset of the `TEntity`s in the collection.
98
+ * @param from The index of the first element to return. Leave unspecified to return the last `to` elements ("tail").
99
+ * @param to The index of the last element to return. Leave unspecified to return all elements starting at `from`.
100
+ * @param signal Used to cancel the request.
101
+ * @throws {@link errors!AuthenticationError}: {@link http!HttpStatusCode.Unauthorized}
102
+ * @throws {@link errors!AuthorizationError}: {@link http!HttpStatusCode.Forbidden}
103
+ * @throws {@link errors!RangeNotSatisfiableError}: {@link http!HttpStatusCode.RangeNotSatisfiable}, i.e. the requested range lies outside of the elements currently available
104
+ * @throws {@link errors!HttpError}: Other non-success status code
105
+ */
106
+ async readRange(from?: number, to?: number, signal?: AbortSignal): Promise<PartialResponse<TEntity>> {
107
+ const response = await this.send(HttpMethod.Get, signal, {
108
+ [HttpHeader.Range]: `${this.rangeUnit}=${from ?? ""}-${to ?? ""}`
109
+ });
110
+
111
+ return new PartialResponse<TEntity>(
112
+ this.serializer.deserialize<TEntity[]>(await response.text()),
113
+ HttpContentRange.from(response));
114
+ }
115
+
63
116
  /**
64
117
  * Shows whether the server has indicated that {@link create} is currently allowed.
65
118
  * Uses cached data from last response.
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PartialResponse = void 0;
4
+ /**
5
+ * Represents a subset of a set of elements.
6
+ * @typeParam TEntity The type of element the response contains.
7
+ */
8
+ class PartialResponse {
9
+ /**
10
+ * Creates a new partial response.
11
+ * @param elements The returned elements.
12
+ * @param range The range the `elements` come from.
13
+ */
14
+ constructor(elements, range) {
15
+ this.elements = elements;
16
+ this.range = range;
17
+ }
18
+ /**
19
+ * Indicates whether the response reaches the end of the elements available on the server.
20
+ */
21
+ get endReached() {
22
+ var _a;
23
+ // No range specified, must be complete response
24
+ if (((_a = this.range) === null || _a === void 0 ? void 0 : _a.to) === undefined)
25
+ return true;
26
+ // No length specified, can't be the end
27
+ if (this.range.length === undefined)
28
+ return false;
29
+ return this.range.to === this.range.length - 1;
30
+ }
31
+ }
32
+ exports.PartialResponse = PartialResponse;
33
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUGFydGlhbFJlc3BvbnNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiUGFydGlhbFJlc3BvbnNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUVBOzs7R0FHRztBQUNILE1BQWEsZUFBZTtJQUN4Qjs7OztPQUlHO0lBQ0gsWUFBNEIsUUFBbUIsRUFBa0IsS0FBd0I7UUFBN0QsYUFBUSxHQUFSLFFBQVEsQ0FBVztRQUFrQixVQUFLLEdBQUwsS0FBSyxDQUFtQjtJQUN6RixDQUFDO0lBRUQ7O09BRUc7SUFDSCxJQUFJLFVBQVU7O1FBQ1YsZ0RBQWdEO1FBQ2hELElBQUksQ0FBQSxNQUFBLElBQUksQ0FBQyxLQUFLLDBDQUFFLEVBQUUsTUFBSyxTQUFTO1lBQUUsT0FBTyxJQUFJLENBQUM7UUFFOUMsd0NBQXdDO1FBQ3hDLElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLEtBQUssU0FBUztZQUFFLE9BQU8sS0FBSyxDQUFDO1FBRWxELE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLEtBQUssSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDO0lBQ25ELENBQUM7Q0FDSjtBQXJCRCwwQ0FxQkMifQ==
@@ -0,0 +1,28 @@
1
+ import { HttpContentRange } from "../../http";
2
+
3
+ /**
4
+ * Represents a subset of a set of elements.
5
+ * @typeParam TEntity The type of element the response contains.
6
+ */
7
+ export class PartialResponse<TEntity> {
8
+ /**
9
+ * Creates a new partial response.
10
+ * @param elements The returned elements.
11
+ * @param range The range the `elements` come from.
12
+ */
13
+ constructor(public readonly elements: TEntity[], public readonly range?: HttpContentRange) {
14
+ }
15
+
16
+ /**
17
+ * Indicates whether the response reaches the end of the elements available on the server.
18
+ */
19
+ get endReached(): boolean {
20
+ // No range specified, must be complete response
21
+ if (this.range?.to === undefined) return true;
22
+
23
+ // No length specified, can't be the end
24
+ if (this.range.length === undefined) return false;
25
+
26
+ return this.range.to === this.range.length - 1;
27
+ }
28
+ }
@@ -16,7 +16,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./IndexerEndpoint"), exports);
18
18
  __exportStar(require("./ETagEndpointBase"), exports);
19
+ __exportStar(require("./PartialResponse"), exports);
19
20
  __exportStar(require("./GenericCollectionEndpoint"), exports);
20
21
  __exportStar(require("./CollectionEndpoint"), exports);
21
22
  __exportStar(require("./ElementEndpoint"), exports);
22
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsb0RBQWtDO0FBQ2xDLHFEQUFtQztBQUNuQyw4REFBNEM7QUFDNUMsdURBQXFDO0FBQ3JDLG9EQUFrQyJ9
23
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsb0RBQWtDO0FBQ2xDLHFEQUFtQztBQUNuQyxvREFBa0M7QUFDbEMsOERBQTRDO0FBQzVDLHVEQUFxQztBQUNyQyxvREFBa0MifQ==
@@ -1,5 +1,6 @@
1
1
  export * from "./IndexerEndpoint";
2
2
  export * from "./ETagEndpointBase";
3
+ export * from "./PartialResponse";
3
4
  export * from "./GenericCollectionEndpoint";
4
5
  export * from "./CollectionEndpoint";
5
6
  export * from "./ElementEndpoint";
@@ -36,11 +36,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
36
36
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.raw = exports.rpc = exports.generic = void 0;
39
+ exports.reactive = exports.raw = exports.rpc = exports.generic = void 0;
40
40
  exports.generic = __importStar(require("./generic"));
41
41
  exports.rpc = __importStar(require("./rpc"));
42
42
  exports.raw = __importStar(require("./raw"));
43
+ exports.reactive = __importStar(require("./reactive"));
43
44
  __exportStar(require("./Endpoint"), exports);
44
45
  __exportStar(require("./EntryEndpoint"), exports);
45
46
  __exportStar(require("./CachingEndpoint"), exports);
46
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxxREFBcUM7QUFDckMsNkNBQTZCO0FBQzdCLDZDQUE2QjtBQUU3Qiw2Q0FBMkI7QUFDM0Isa0RBQWdDO0FBQ2hDLG9EQUFrQyJ9
47
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxxREFBcUM7QUFDckMsNkNBQTZCO0FBQzdCLDZDQUE2QjtBQUM3Qix1REFBdUM7QUFFdkMsNkNBQTJCO0FBQzNCLGtEQUFnQztBQUNoQyxvREFBa0MifQ==
@@ -1,6 +1,7 @@
1
1
  export * as generic from "./generic";
2
2
  export * as rpc from "./rpc";
3
3
  export * as raw from "./raw";
4
+ export * as reactive from "./reactive";
4
5
 
5
6
  export * from "./Endpoint";
6
7
  export * from "./EntryEndpoint";
@@ -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.BlobEndpoint = void 0;
13
4
  const Endpoint_1 = require("../Endpoint");
@@ -32,10 +23,8 @@ class BlobEndpoint extends Endpoint_1.Endpoint {
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
- probe(signal) {
36
- return __awaiter(this, void 0, void 0, function* () {
37
- yield this.send(http_1.HttpMethod.Options, signal);
38
- });
26
+ async probe(signal) {
27
+ await this.send(http_1.HttpMethod.Options, signal);
39
28
  }
40
29
  /**
41
30
  * Shows whether the server has indicated that {@link download} is currently allowed.
@@ -52,11 +41,9 @@ class BlobEndpoint extends Endpoint_1.Endpoint {
52
41
  * @throws {@link errors!NotFoundError}: {@link http!HttpStatusCode.NotFound} or {@link http!HttpStatusCode.Gone}
53
42
  * @throws {@link errors!HttpError}: Other non-success status code
54
43
  */
55
- download(signal) {
56
- return __awaiter(this, void 0, void 0, function* () {
57
- const response = yield this.send(http_1.HttpMethod.Get, signal);
58
- return response.blob();
59
- });
44
+ async download(signal) {
45
+ const response = await this.send(http_1.HttpMethod.Get, signal);
46
+ return response.blob();
60
47
  }
61
48
  /**
62
49
  * Shows whether the server has indicated that {@link upload} is currently allowed.
@@ -73,10 +60,8 @@ class BlobEndpoint extends Endpoint_1.Endpoint {
73
60
  * @throws {@link errors!AuthorizationError}: {@link http!HttpStatusCode.Forbidden}
74
61
  * @throws {@link errors!HttpError}: Other non-success status code
75
62
  */
76
- uploadFrom(blob, signal) {
77
- return __awaiter(this, void 0, void 0, function* () {
78
- yield this.send(http_1.HttpMethod.Put, signal, { [http_1.HttpHeader.ContentType]: blob.type }, blob);
79
- });
63
+ async uploadFrom(blob, signal) {
64
+ await this.send(http_1.HttpMethod.Put, signal, { [http_1.HttpHeader.ContentType]: blob.type }, blob);
80
65
  }
81
66
  /**
82
67
  * Shows whether the server has indicated that {@link delete} is currently allowed.
@@ -93,11 +78,9 @@ class BlobEndpoint extends Endpoint_1.Endpoint {
93
78
  * @throws {@link errors!NotFoundError}: {@link http!HttpStatusCode.NotFound} or {@link http!HttpStatusCode.Gone}
94
79
  * @throws {@link errors!HttpError}: Other non-success status code
95
80
  */
96
- delete(signal) {
97
- return __awaiter(this, void 0, void 0, function* () {
98
- yield this.send(http_1.HttpMethod.Delete, signal);
99
- });
81
+ async delete(signal) {
82
+ await this.send(http_1.HttpMethod.Delete, signal);
100
83
  }
101
84
  }
102
85
  exports.BlobEndpoint = BlobEndpoint;
103
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQmxvYkVuZHBvaW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiQmxvYkVuZHBvaW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQUFBLDBDQUF1QztBQUN2QyxxQ0FBb0Q7QUFFcEQ7O0dBRUc7QUFDSCxNQUFhLFlBQWEsU0FBUSxtQkFBUTtJQUN0Qzs7OztPQUlHO0lBQ0gsWUFBWSxRQUFrQixFQUFFLFdBQXlCO1FBQ3JELEtBQUssQ0FBQyxRQUFRLEVBQUUsV0FBVyxDQUFDLENBQUM7SUFDakMsQ0FBQztJQUVEOzs7Ozs7O09BT0c7SUFDRyxLQUFLLENBQUMsTUFBb0I7O1lBQzVCLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxpQkFBVSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztRQUNoRCxDQUFDO0tBQUE7SUFFRDs7OztPQUlHO0lBQ0gsSUFBSSxlQUFlLEtBQUssT0FBTyxJQUFJLENBQUMsZUFBZSxDQUFDLGlCQUFVLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBRXRFOzs7Ozs7OztPQVFHO0lBQ0csUUFBUSxDQUFDLE1BQW9COztZQUMvQixNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsaUJBQVUsQ0FBQyxHQUFHLEVBQUUsTUFBTSxDQUFDLENBQUM7WUFDekQsT0FBTyxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDM0IsQ0FBQztLQUFBO0lBRUQ7Ozs7T0FJRztJQUNILElBQUksYUFBYSxLQUFLLE9BQU8sSUFBSSxDQUFDLGVBQWUsQ0FBQyxpQkFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUVwRTs7Ozs7Ozs7T0FRRztJQUNHLFVBQVUsQ0FBQyxJQUFVLEVBQUUsTUFBb0I7O1lBQzdDLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxpQkFBVSxDQUFDLEdBQUcsRUFBRSxNQUFNLEVBQUUsRUFBRSxDQUFDLGlCQUFVLENBQUMsV0FBVyxDQUFDLEVBQUUsSUFBSSxDQUFDLElBQUksRUFBRSxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQzNGLENBQUM7S0FBQTtJQUVEOzs7O09BSUc7SUFDSCxJQUFJLGFBQWEsS0FBSyxPQUFPLElBQUksQ0FBQyxlQUFlLENBQUMsaUJBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFdkU7Ozs7Ozs7O09BUUc7SUFDRyxNQUFNLENBQUMsTUFBb0I7O1lBQzdCLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxpQkFBVSxDQUFDLE1BQU0sRUFBRSxNQUFNLENBQUMsQ0FBQztRQUMvQyxDQUFDO0tBQUE7Q0FDSjtBQWxGRCxvQ0FrRkMifQ==
86
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQmxvYkVuZHBvaW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiQmxvYkVuZHBvaW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDBDQUF1QztBQUN2QyxxQ0FBb0Q7QUFFcEQ7O0dBRUc7QUFDSCxNQUFhLFlBQWEsU0FBUSxtQkFBUTtJQUN0Qzs7OztPQUlHO0lBQ0gsWUFBWSxRQUFrQixFQUFFLFdBQXlCO1FBQ3JELEtBQUssQ0FBQyxRQUFRLEVBQUUsV0FBVyxDQUFDLENBQUM7SUFDakMsQ0FBQztJQUVEOzs7Ozs7O09BT0c7SUFDSCxLQUFLLENBQUMsS0FBSyxDQUFDLE1BQW9CO1FBQzVCLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxpQkFBVSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztJQUNoRCxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILElBQUksZUFBZSxLQUFLLE9BQU8sSUFBSSxDQUFDLGVBQWUsQ0FBQyxpQkFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUV0RTs7Ozs7Ozs7T0FRRztJQUNILEtBQUssQ0FBQyxRQUFRLENBQUMsTUFBb0I7UUFDL0IsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsSUFBSSxDQUFDLGlCQUFVLENBQUMsR0FBRyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQ3pELE9BQU8sUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDO0lBQzNCLENBQUM7SUFFRDs7OztPQUlHO0lBQ0gsSUFBSSxhQUFhLEtBQUssT0FBTyxJQUFJLENBQUMsZUFBZSxDQUFDLGlCQUFVLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBRXBFOzs7Ozs7OztPQVFHO0lBQ0gsS0FBSyxDQUFDLFVBQVUsQ0FBQyxJQUFVLEVBQUUsTUFBb0I7UUFDN0MsTUFBTSxJQUFJLENBQUMsSUFBSSxDQUFDLGlCQUFVLENBQUMsR0FBRyxFQUFFLE1BQU0sRUFBRSxFQUFFLENBQUMsaUJBQVUsQ0FBQyxXQUFXLENBQUMsRUFBRSxJQUFJLENBQUMsSUFBSSxFQUFFLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDM0YsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxJQUFJLGFBQWEsS0FBSyxPQUFPLElBQUksQ0FBQyxlQUFlLENBQUMsaUJBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFdkU7Ozs7Ozs7O09BUUc7SUFDSCxLQUFLLENBQUMsTUFBTSxDQUFDLE1BQW9CO1FBQzdCLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxpQkFBVSxDQUFDLE1BQU0sRUFBRSxNQUFNLENBQUMsQ0FBQztJQUMvQyxDQUFDO0NBQ0o7QUFsRkQsb0NBa0ZDIn0=
@@ -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.UploadEndpoint = void 0;
13
4
  const Endpoint_1 = require("../Endpoint");
@@ -36,19 +27,17 @@ class UploadEndpoint extends Endpoint_1.Endpoint {
36
27
  * @throws {@link errors!NotFoundError}: {@link http!HttpStatusCode.NotFound} or {@link http!HttpStatusCode.Gone}
37
28
  * @throws {@link errors!HttpError}: Other non-success status code
38
29
  */
39
- uploadFrom(blob, signal) {
40
- return __awaiter(this, void 0, void 0, function* () {
41
- const fileName = blob instanceof File ? blob.name : undefined;
42
- if (this.formField) {
43
- const formData = new FormData();
44
- formData.set(this.formField, blob, fileName);
45
- yield this.send(http_1.HttpMethod.Post, signal, undefined, formData);
46
- }
47
- else {
48
- yield this.send(http_1.HttpMethod.Post, signal, { [http_1.HttpHeader.ContentType]: blob.type }, blob);
49
- }
50
- });
30
+ async uploadFrom(blob, signal) {
31
+ const fileName = blob instanceof File ? blob.name : undefined;
32
+ if (this.formField) {
33
+ const formData = new FormData();
34
+ formData.set(this.formField, blob, fileName);
35
+ await this.send(http_1.HttpMethod.Post, signal, undefined, formData);
36
+ }
37
+ else {
38
+ await this.send(http_1.HttpMethod.Post, signal, { [http_1.HttpHeader.ContentType]: blob.type }, blob);
39
+ }
51
40
  }
52
41
  }
53
42
  exports.UploadEndpoint = UploadEndpoint;
54
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVXBsb2FkRW5kcG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJVcGxvYWRFbmRwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7QUFBQSwwQ0FBdUM7QUFDdkMscUNBQW9EO0FBRXBEOztHQUVHO0FBQ0gsTUFBYSxjQUFlLFNBQVEsbUJBQVE7SUFDeEM7Ozs7O09BS0c7SUFDSCxZQUFZLFFBQWtCLEVBQUUsV0FBeUIsRUFBbUIsU0FBa0I7UUFDMUYsS0FBSyxDQUFDLFFBQVEsRUFBRSxXQUFXLENBQUMsQ0FBQztRQUQyQyxjQUFTLEdBQVQsU0FBUyxDQUFTO0lBRTlGLENBQUM7SUFFRDs7Ozs7Ozs7O09BU0c7SUFDRyxVQUFVLENBQUMsSUFBaUIsRUFBRSxNQUFvQjs7WUFDcEQsTUFBTSxRQUFRLEdBQUcsSUFBSSxZQUFZLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO1lBRTlELElBQUksSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDO2dCQUNqQixNQUFNLFFBQVEsR0FBRyxJQUFJLFFBQVEsRUFBRSxDQUFDO2dCQUNoQyxRQUFRLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsSUFBSSxFQUFFLFFBQVEsQ0FBQyxDQUFDO2dCQUM3QyxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsaUJBQVUsQ0FBQyxJQUFJLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxRQUFRLENBQUMsQ0FBQztZQUNsRSxDQUFDO2lCQUFNLENBQUM7Z0JBQ0osTUFBTSxJQUFJLENBQUMsSUFBSSxDQUFDLGlCQUFVLENBQUMsSUFBSSxFQUFFLE1BQU0sRUFBRSxFQUFFLENBQUMsaUJBQVUsQ0FBQyxXQUFXLENBQUMsRUFBRSxJQUFJLENBQUMsSUFBSSxFQUFFLEVBQUUsSUFBSSxDQUFDLENBQUM7WUFDNUYsQ0FBQztRQUNMLENBQUM7S0FBQTtDQUNKO0FBaENELHdDQWdDQyJ9
43
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVXBsb2FkRW5kcG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJVcGxvYWRFbmRwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwwQ0FBdUM7QUFDdkMscUNBQW9EO0FBRXBEOztHQUVHO0FBQ0gsTUFBYSxjQUFlLFNBQVEsbUJBQVE7SUFDeEM7Ozs7O09BS0c7SUFDSCxZQUFZLFFBQWtCLEVBQUUsV0FBeUIsRUFBbUIsU0FBa0I7UUFDMUYsS0FBSyxDQUFDLFFBQVEsRUFBRSxXQUFXLENBQUMsQ0FBQztRQUQyQyxjQUFTLEdBQVQsU0FBUyxDQUFTO0lBRTlGLENBQUM7SUFFRDs7Ozs7Ozs7O09BU0c7SUFDSCxLQUFLLENBQUMsVUFBVSxDQUFDLElBQWlCLEVBQUUsTUFBb0I7UUFDcEQsTUFBTSxRQUFRLEdBQUcsSUFBSSxZQUFZLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO1FBRTlELElBQUksSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQ2pCLE1BQU0sUUFBUSxHQUFHLElBQUksUUFBUSxFQUFFLENBQUM7WUFDaEMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQztZQUM3QyxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsaUJBQVUsQ0FBQyxJQUFJLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxRQUFRLENBQUMsQ0FBQztRQUNsRSxDQUFDO2FBQU0sQ0FBQztZQUNKLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxpQkFBVSxDQUFDLElBQUksRUFBRSxNQUFNLEVBQUUsRUFBRSxDQUFDLGlCQUFVLENBQUMsV0FBVyxDQUFDLEVBQUUsSUFBSSxDQUFDLElBQUksRUFBRSxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQzVGLENBQUM7SUFDTCxDQUFDO0NBQ0o7QUFoQ0Qsd0NBZ0NDIn0=
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.delay = delay;
4
+ /**
5
+ * Waits for a specific amount of time.
6
+ * @param ms The time to wait in milliseconds.
7
+ * @param signal Used to cancel the wait.
8
+ * @returns `true` if the time has elapsed; `false` if the wait was cancelled via the `signal`.
9
+ */
10
+ function delay(ms, signal) {
11
+ return new Promise(resolve => {
12
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
13
+ resolve(false);
14
+ return;
15
+ }
16
+ const onAbort = () => {
17
+ clearTimeout(timeout);
18
+ resolve(false);
19
+ };
20
+ const timeout = setTimeout(() => {
21
+ signal === null || signal === void 0 ? void 0 : signal.removeEventListener("abort", onAbort);
22
+ resolve(true);
23
+ }, ms);
24
+ signal === null || signal === void 0 ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
25
+ });
26
+ }
27
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRGVsYXkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJEZWxheS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQU1BLHNCQW1CQztBQXpCRDs7Ozs7R0FLRztBQUNILFNBQWdCLEtBQUssQ0FBQyxFQUFVLEVBQUUsTUFBb0I7SUFDbEQsT0FBTyxJQUFJLE9BQU8sQ0FBQyxPQUFPLENBQUMsRUFBRTtRQUN6QixJQUFJLE1BQU0sYUFBTixNQUFNLHVCQUFOLE1BQU0sQ0FBRSxPQUFPLEVBQUUsQ0FBQztZQUNsQixPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDZixPQUFPO1FBQ1gsQ0FBQztRQUVELE1BQU0sT0FBTyxHQUFHLEdBQUcsRUFBRTtZQUNqQixZQUFZLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDdEIsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ25CLENBQUMsQ0FBQztRQUVGLE1BQU0sT0FBTyxHQUFHLFVBQVUsQ0FBQyxHQUFHLEVBQUU7WUFDNUIsTUFBTSxhQUFOLE1BQU0sdUJBQU4sTUFBTSxDQUFFLG1CQUFtQixDQUFDLE9BQU8sRUFBRSxPQUFPLENBQUMsQ0FBQztZQUM5QyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDbEIsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBRVAsTUFBTSxhQUFOLE1BQU0sdUJBQU4sTUFBTSxDQUFFLGdCQUFnQixDQUFDLE9BQU8sRUFBRSxPQUFPLEVBQUUsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUMvRCxDQUFDLENBQUMsQ0FBQztBQUNQLENBQUMifQ==
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Waits for a specific amount of time.
3
+ * @param ms The time to wait in milliseconds.
4
+ * @param signal Used to cancel the wait.
5
+ * @returns `true` if the time has elapsed; `false` if the wait was cancelled via the `signal`.
6
+ */
7
+ export function delay(ms: number, signal?: AbortSignal): Promise<boolean> {
8
+ return new Promise(resolve => {
9
+ if (signal?.aborted) {
10
+ resolve(false);
11
+ return;
12
+ }
13
+
14
+ const onAbort = () => {
15
+ clearTimeout(timeout);
16
+ resolve(false);
17
+ };
18
+
19
+ const timeout = setTimeout(() => {
20
+ signal?.removeEventListener("abort", onAbort);
21
+ resolve(true);
22
+ }, ms);
23
+
24
+ signal?.addEventListener("abort", onAbort, { once: true });
25
+ });
26
+ }
@@ -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,73 @@
1
+ import { ElementEndpoint } from "../generic/ElementEndpoint";
2
+ import { GenericCollectionEndpoint } from "../generic/GenericCollectionEndpoint";
3
+ import { PartialResponse } from "../generic/PartialResponse";
4
+ import { delay } from "./Delay";
5
+ import { retryAfter } from "../../http";
6
+ import { RangeNotSatisfiableError } from "../../errors";
7
+
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
+ export class GenericStreamingCollectionEndpoint<TEntity, TElementEndpoint extends ElementEndpoint<TEntity>> extends GenericCollectionEndpoint<TEntity, TElementEndpoint> {
15
+ /**
16
+ * The interval in milliseconds to wait between polling requests.
17
+ * Automatically updated whenever the server sends a {@link http!HttpHeader.RetryAfter} header.
18
+ */
19
+ pollingInterval = 3000;
20
+
21
+ /**
22
+ * @inheritDoc
23
+ */
24
+ protected async handle(response: Response) {
25
+ await super.handle(response);
26
+ this.pollingInterval = retryAfter(response) ?? this.pollingInterval;
27
+ }
28
+
29
+ /**
30
+ * Provides a stream of `TEntity`s, polling the server for elements appended to the collection.
31
+ * @param startIndex The index of the first element to return. Use a negative value to start with the last `-startIndex` elements ("tail").
32
+ * @param signal Used to stop polling.
33
+ * @throws {@link errors!AuthenticationError}: {@link http!HttpStatusCode.Unauthorized}
34
+ * @throws {@link errors!AuthorizationError}: {@link http!HttpStatusCode.Forbidden}
35
+ * @throws {@link errors!HttpError}: Other non-success status code
36
+ */
37
+ async *stream(startIndex: number = 0, signal?: AbortSignal): AsyncIterableIterator<TEntity> {
38
+ let currentStartIndex = startIndex;
39
+
40
+ while (!signal?.aborted) {
41
+ let response: PartialResponse<TEntity>;
42
+ try {
43
+ response = (currentStartIndex >= 0)
44
+ // Offset
45
+ ? await this.readRange(currentStartIndex, undefined, signal)
46
+ // Tail
47
+ : await this.readRange(undefined, -currentStartIndex, signal);
48
+ } catch (err) {
49
+ // Aborting is a regular way of ending the stream, not an error
50
+ if (signal?.aborted) return;
51
+ if (!(err instanceof RangeNotSatisfiableError)) throw err;
52
+
53
+ // No new data available yet, keep polling
54
+ if (!await delay(this.pollingInterval, signal)) return;
55
+ continue;
56
+ }
57
+
58
+ yield* response.elements;
59
+ if (response.endReached) return;
60
+
61
+ // Continue polling for more data
62
+ if (response.range?.to === undefined) return;
63
+ currentStartIndex = response.range.to + 1;
64
+
65
+ if (!await delay(this.pollingInterval, signal)) return;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Provides a stream of `TEntity`s using {@link stream}.
71
+ */
72
+ [Symbol.asyncIterator]() { return this.stream(); }
73
+ }