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.
- package/README.md +2 -0
- package/endpoints/Endpoint.js +9 -22
- package/endpoints/EntryEndpoint.js +3 -14
- package/endpoints/generic/ETagEndpointBase.js +27 -42
- package/endpoints/generic/ElementEndpoint.js +39 -60
- package/endpoints/generic/GenericCollectionEndpoint.js +70 -44
- package/endpoints/generic/GenericCollectionEndpoint.test.ts +123 -0
- package/endpoints/generic/GenericCollectionEndpoint.ts +54 -1
- package/endpoints/generic/PartialResponse.js +33 -0
- package/endpoints/generic/PartialResponse.ts +28 -0
- package/endpoints/generic/index.js +2 -1
- package/endpoints/generic/index.ts +1 -0
- package/endpoints/index.js +3 -2
- package/endpoints/index.ts +1 -0
- package/endpoints/raw/BlobEndpoint.js +10 -27
- package/endpoints/raw/UploadEndpoint.js +11 -22
- package/endpoints/reactive/Delay.js +27 -0
- package/endpoints/reactive/Delay.ts +26 -0
- package/endpoints/reactive/GenericStreamingCollectionEndpoint.js +79 -0
- package/endpoints/reactive/GenericStreamingCollectionEndpoint.ts +73 -0
- package/endpoints/reactive/PollingEndpoint.js +67 -0
- package/endpoints/reactive/PollingEndpoint.test.ts +84 -0
- package/endpoints/reactive/PollingEndpoint.ts +66 -0
- package/endpoints/reactive/SseStreamingEndpoint.js +120 -0
- package/endpoints/reactive/SseStreamingEndpoint.test.ts +169 -0
- package/endpoints/reactive/SseStreamingEndpoint.ts +115 -0
- package/endpoints/reactive/StreamingCollectionEndpoint.js +23 -0
- package/endpoints/reactive/StreamingCollectionEndpoint.test.ts +110 -0
- package/endpoints/reactive/StreamingCollectionEndpoint.ts +20 -0
- package/endpoints/reactive/StreamingEndpoint.js +56 -0
- package/endpoints/reactive/StreamingEndpoint.test.ts +130 -0
- package/endpoints/reactive/StreamingEndpoint.ts +50 -0
- package/endpoints/reactive/index.js +22 -0
- package/endpoints/reactive/index.ts +5 -0
- package/endpoints/rpc/ActionEndpoint.js +3 -14
- package/endpoints/rpc/ConsumerEndpoint.js +5 -16
- package/endpoints/rpc/FunctionEndpoint.js +6 -17
- package/endpoints/rpc/ProducerEndpoint.js +4 -15
- package/endpoints/rpc/RpcEndpointBase.js +3 -14
- package/errors/DefaultErrorHandler.js +19 -27
- package/errors/DefaultErrorHandler.ts +4 -1
- package/errors/Errors.js +8 -2
- package/errors/Errors.ts +5 -0
- package/http/EntityStream.js +42 -0
- package/http/EntityStream.ts +40 -0
- package/http/HttpContentRange.js +39 -0
- package/http/HttpContentRange.ts +41 -0
- package/http/HttpHeader.js +6 -1
- package/http/HttpHeader.ts +6 -1
- package/http/HttpStatusCode.js +4 -1
- package/http/HttpStatusCode.ts +4 -1
- package/http/ResponseCache.js +4 -15
- package/http/RetryAfter.js +24 -0
- package/http/RetryAfter.ts +19 -0
- package/http/ServerSentEvents.js +140 -0
- package/http/ServerSentEvents.ts +145 -0
- package/http/index.js +5 -1
- package/http/index.ts +4 -0
- package/links/AggregateLinkExtractor.js +7 -18
- package/links/HalLinkExtractor.js +9 -20
- package/links/HeaderLinkExtractor.js +4 -15
- 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,
|
|
23
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsb0RBQWtDO0FBQ2xDLHFEQUFtQztBQUNuQyxvREFBa0M7QUFDbEMsOERBQTRDO0FBQzVDLHVEQUFxQztBQUNyQyxvREFBa0MifQ==
|
package/endpoints/index.js
CHANGED
|
@@ -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,
|
|
47
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxxREFBcUM7QUFDckMsNkNBQTZCO0FBQzdCLDZDQUE2QjtBQUM3Qix1REFBdUM7QUFFdkMsNkNBQTJCO0FBQzNCLGtEQUFnQztBQUNoQyxvREFBa0MifQ==
|
package/endpoints/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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,
|
|
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
|
+
}
|