@nymphjs/client 1.0.0-beta.13 → 1.0.0-beta.130
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/CHANGELOG.md +546 -0
- package/README.md +12 -8
- package/asyncitertest.js +53 -0
- package/dist/Entity.d.ts +156 -0
- package/{lib → dist}/Entity.js +244 -118
- package/dist/Entity.js.map +1 -0
- package/dist/Entity.types.d.ts +216 -0
- package/dist/Entity.types.js +2 -0
- package/{lib → dist}/EntityWeakCache.d.ts +1 -1
- package/{lib → dist}/EntityWeakCache.js +5 -9
- package/dist/EntityWeakCache.js.map +1 -0
- package/{lib → dist}/HttpRequester.d.ts +18 -4
- package/dist/HttpRequester.js +365 -0
- package/dist/HttpRequester.js.map +1 -0
- package/{lib → dist}/Nymph.d.ts +48 -11
- package/{lib → dist}/Nymph.js +172 -56
- package/dist/Nymph.js.map +1 -0
- package/dist/Nymph.types.d.ts +227 -0
- package/dist/Nymph.types.js +45 -0
- package/dist/Nymph.types.js.map +1 -0
- package/{lib → dist}/PubSub.d.ts +16 -10
- package/{lib → dist}/PubSub.js +207 -135
- package/dist/PubSub.js.map +1 -0
- package/{lib → dist}/PubSub.types.d.ts +19 -6
- package/dist/PubSub.types.js +2 -0
- package/{lib → dist}/entityRefresh.d.ts +1 -1
- package/{lib → dist}/entityRefresh.js +21 -13
- package/dist/entityRefresh.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/{lib → dist}/utils.d.ts +2 -1
- package/dist/utils.js +145 -0
- package/dist/utils.js.map +1 -0
- package/jest.config.js +31 -5
- package/package.json +36 -32
- package/src/Entity.ts +229 -129
- package/src/Entity.types.ts +37 -57
- package/src/EntityWeakCache.ts +8 -6
- package/src/HttpRequester.ts +268 -31
- package/src/Nymph.ts +203 -96
- package/src/Nymph.types.ts +98 -2
- package/src/PubSub.ts +264 -181
- package/src/PubSub.types.ts +33 -19
- package/src/entityRefresh.ts +6 -6
- package/src/index.ts +10 -10
- package/src/utils.ts +67 -14
- package/tsconfig.json +7 -4
- package/typedoc.json +4 -0
- package/dist/index.js.LICENSE.txt +0 -8
- package/lib/Entity.d.ts +0 -51
- package/lib/Entity.js.map +0 -1
- package/lib/Entity.types.d.ts +0 -65
- package/lib/Entity.types.js +0 -3
- package/lib/EntityWeakCache.js.map +0 -1
- package/lib/HttpRequester.js +0 -190
- package/lib/HttpRequester.js.map +0 -1
- package/lib/Nymph.js.map +0 -1
- package/lib/Nymph.types.d.ts +0 -98
- package/lib/Nymph.types.js +0 -3
- package/lib/Nymph.types.js.map +0 -1
- package/lib/PubSub.js.map +0 -1
- package/lib/PubSub.types.js +0 -3
- package/lib/entityRefresh.js.map +0 -1
- package/lib/index.d.ts +0 -13
- package/lib/index.js +0 -34
- package/lib/index.js.map +0 -1
- package/lib/utils.js +0 -86
- package/lib/utils.js.map +0 -1
- package/webpack.config.js +0 -28
- /package/{lib → dist}/Entity.types.js.map +0 -0
- /package/{lib → dist}/PubSub.types.js.map +0 -0
package/src/Entity.types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type Nymph from './Nymph';
|
|
1
|
+
import type Nymph from './Nymph.js';
|
|
2
|
+
import type Entity from './Entity.js';
|
|
2
3
|
|
|
3
4
|
export type ServerCallResponse = {
|
|
4
5
|
return: any;
|
|
@@ -56,9 +57,9 @@ export interface DataObjectInterface {
|
|
|
56
57
|
/**
|
|
57
58
|
* Delete the object from storage.
|
|
58
59
|
*
|
|
59
|
-
*
|
|
60
|
+
* An error is thrown on failure.
|
|
60
61
|
*/
|
|
61
|
-
$delete(): Promise<
|
|
62
|
+
$delete(): Promise<void>;
|
|
62
63
|
/**
|
|
63
64
|
* Perform a more strict comparison of this object to another.
|
|
64
65
|
*
|
|
@@ -87,24 +88,22 @@ export interface DataObjectInterface {
|
|
|
87
88
|
/**
|
|
88
89
|
* Save the object's dirty data to storage.
|
|
89
90
|
*
|
|
90
|
-
*
|
|
91
|
+
* An error is thrown on failure.
|
|
91
92
|
*/
|
|
92
|
-
$patch(): Promise<
|
|
93
|
+
$patch(): Promise<void>;
|
|
93
94
|
/**
|
|
94
95
|
* Refresh the object from storage. (Bypasses Nymph's cache.)
|
|
95
96
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* @returns False if the data has not been saved, 0 if it can't be refreshed, true on success.
|
|
97
|
+
* @returns False if the data has not been saved, true on success. An error is
|
|
98
|
+
* thrown on failure.
|
|
100
99
|
*/
|
|
101
100
|
$refresh(): Promise<boolean | 0>;
|
|
102
101
|
/**
|
|
103
102
|
* Save the object to storage.
|
|
104
103
|
*
|
|
105
|
-
*
|
|
104
|
+
* An error is thrown on failure.
|
|
106
105
|
*/
|
|
107
|
-
$save(): Promise<
|
|
106
|
+
$save(): Promise<void>;
|
|
108
107
|
/**
|
|
109
108
|
* The object's data.
|
|
110
109
|
*/
|
|
@@ -155,6 +154,24 @@ export interface EntityInterface extends DataObjectInterface {
|
|
|
155
154
|
* @returns True or false.
|
|
156
155
|
*/
|
|
157
156
|
$hasTag(...tags: string[]): boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Check whether a property is dirty.
|
|
159
|
+
*
|
|
160
|
+
* To be a dirty property, it must have been set or deleted since the entity
|
|
161
|
+
* was initialized. A clean property existed on initialization and hasn't been
|
|
162
|
+
* set or deleted. An untracked property didn't exist on initialization and
|
|
163
|
+
* hasn't been set or deleted.
|
|
164
|
+
*
|
|
165
|
+
* Note that this doesn't necessarily mean the property has changed. It could
|
|
166
|
+
* have been set to the same value, or created and then deleted.
|
|
167
|
+
*
|
|
168
|
+
* Entities are initialized when they are pulled from the server or saved.
|
|
169
|
+
* This is done with the `$init` method.
|
|
170
|
+
*
|
|
171
|
+
* @param property The name of a property.
|
|
172
|
+
* @returns True if it's dirty, false if not, and null if it's not tracked.
|
|
173
|
+
*/
|
|
174
|
+
$isDirty(property: string): boolean | null;
|
|
158
175
|
/**
|
|
159
176
|
* Initialize this entity from a JSON representation.
|
|
160
177
|
*
|
|
@@ -166,14 +183,14 @@ export interface EntityInterface extends DataObjectInterface {
|
|
|
166
183
|
*
|
|
167
184
|
* @returns The entity.
|
|
168
185
|
*/
|
|
169
|
-
$
|
|
186
|
+
$wake(): Promise<EntityInterface>;
|
|
170
187
|
/**
|
|
171
188
|
* Ready this entity's data, and the data of entity's within this one's.
|
|
172
189
|
*
|
|
173
|
-
* @param level The number of levels deep to
|
|
190
|
+
* @param level The number of levels deep to wake. If undefined, it will keep going until there are no more entities. (Careful of infinite loops.)
|
|
174
191
|
* @returns The entity.
|
|
175
192
|
*/
|
|
176
|
-
$
|
|
193
|
+
$wakeAll(level?: number): Promise<EntityInterface>;
|
|
177
194
|
/**
|
|
178
195
|
* Remove one or more tags.
|
|
179
196
|
*
|
|
@@ -195,7 +212,7 @@ export interface EntityInterface extends DataObjectInterface {
|
|
|
195
212
|
$serverCall(
|
|
196
213
|
method: string,
|
|
197
214
|
params: Iterable<any>,
|
|
198
|
-
stateless: boolean
|
|
215
|
+
stateless: boolean,
|
|
199
216
|
): Promise<any>;
|
|
200
217
|
/**
|
|
201
218
|
* Return a Nymph Entity Reference for this entity.
|
|
@@ -208,46 +225,9 @@ export interface EntityInterface extends DataObjectInterface {
|
|
|
208
225
|
$toReference(): EntityReference | EntityInterface;
|
|
209
226
|
}
|
|
210
227
|
|
|
211
|
-
export type EntityConstructor
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* The lookup name for this entity.
|
|
218
|
-
*
|
|
219
|
-
* This is used for reference arrays (and sleeping references) and client
|
|
220
|
-
* requests.
|
|
221
|
-
*/
|
|
222
|
-
class: string;
|
|
223
|
-
/**
|
|
224
|
-
* Create a new entity instance.
|
|
225
|
-
*
|
|
226
|
-
* @param guid An optional GUID to retrieve.
|
|
227
|
-
*/
|
|
228
|
-
factory(guid?: string): Promise<EntityInterface>;
|
|
229
|
-
/**
|
|
230
|
-
* Create a new entity instance.
|
|
231
|
-
*
|
|
232
|
-
* @param guid An optional GUID to retrieve.
|
|
233
|
-
*/
|
|
234
|
-
factorySync(guid?: string): EntityInterface;
|
|
235
|
-
/**
|
|
236
|
-
* Create a new sleeping reference instance.
|
|
237
|
-
*
|
|
238
|
-
* Sleeping references won't retrieve their data from the server until they
|
|
239
|
-
* are readied with `ready()` or a parent's `readyAll()`.
|
|
240
|
-
*
|
|
241
|
-
* @param reference The Nymph Entity Reference to use to wake.
|
|
242
|
-
* @returns The new instance.
|
|
243
|
-
*/
|
|
244
|
-
factoryReference(reference: EntityReference): EntityInterface;
|
|
245
|
-
/**
|
|
246
|
-
* Call a static method on the server version of this entity.
|
|
247
|
-
*
|
|
248
|
-
* @param method The name of the method.
|
|
249
|
-
* @param params The parameters to call the method with.
|
|
250
|
-
* @returns The value that the method on the server returned.
|
|
251
|
-
*/
|
|
252
|
-
serverCallStatic(method: string, params: Iterable<any>): Promise<any>;
|
|
228
|
+
export type EntityConstructor<
|
|
229
|
+
D extends EntityData = EntityData,
|
|
230
|
+
E extends Entity<D> = Entity<D>,
|
|
231
|
+
> = (new (...args: any[]) => E) & {
|
|
232
|
+
[k in keyof typeof Entity]: (typeof Entity)[k];
|
|
253
233
|
};
|
package/src/EntityWeakCache.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import { EntityConstructor, EntityInterface } from './Entity.types';
|
|
1
|
+
import { EntityConstructor, EntityInterface } from './Entity.types.js';
|
|
2
2
|
|
|
3
3
|
export default class EntityWeakCache {
|
|
4
|
-
private references: WeakMap<
|
|
5
|
-
|
|
4
|
+
private references: WeakMap<
|
|
5
|
+
EntityConstructor,
|
|
6
|
+
{ [k: string]: WeakRef<EntityInterface> }
|
|
7
|
+
> = new WeakMap();
|
|
6
8
|
|
|
7
9
|
get(EntityClass: EntityConstructor, guid: string): EntityInterface | null {
|
|
8
10
|
const classMap = this.references.get(EntityClass);
|
|
9
11
|
if (classMap && guid in classMap) {
|
|
10
12
|
const weakRef = classMap[guid];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
const deref = weakRef && weakRef.deref();
|
|
14
|
+
if (deref != null) {
|
|
15
|
+
return deref;
|
|
13
16
|
} else {
|
|
14
17
|
delete classMap[guid];
|
|
15
18
|
}
|
|
@@ -22,7 +25,6 @@ export default class EntityWeakCache {
|
|
|
22
25
|
return;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
// @ts-ignore TS doesn't know about WeakRef.
|
|
26
28
|
const weakRef = new WeakRef(entity);
|
|
27
29
|
|
|
28
30
|
const classMap = this.references.get(EntityClass) || {};
|
package/src/HttpRequester.ts
CHANGED
|
@@ -1,25 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fetchEventSource,
|
|
3
|
+
EventStreamContentType,
|
|
4
|
+
} from 'fetch-event-source-hperrin';
|
|
5
|
+
|
|
1
6
|
export type HttpRequesterEventType = 'request' | 'response';
|
|
2
7
|
export type HttpRequesterRequestCallback = (
|
|
3
8
|
requester: HttpRequester,
|
|
4
9
|
url: string,
|
|
5
|
-
options: RequestInit
|
|
10
|
+
options: RequestInit,
|
|
6
11
|
) => void;
|
|
7
12
|
export type HttpRequesterResponseCallback = (
|
|
8
13
|
requester: HttpRequester,
|
|
9
14
|
response: Response,
|
|
10
|
-
text: string
|
|
15
|
+
text: string,
|
|
16
|
+
) => void;
|
|
17
|
+
export type HttpRequesterIteratorCallback = (
|
|
18
|
+
requester: HttpRequester,
|
|
19
|
+
url: string,
|
|
20
|
+
headers: Record<string, string>,
|
|
11
21
|
) => void;
|
|
12
22
|
export type HttpRequesterRequestOptions = {
|
|
13
23
|
url: string;
|
|
24
|
+
headers?: { [k: string]: any };
|
|
14
25
|
data: { [k: string]: any };
|
|
15
26
|
dataType: string;
|
|
16
27
|
};
|
|
17
28
|
|
|
29
|
+
export interface AbortableAsyncIterator<
|
|
30
|
+
T extends any = any,
|
|
31
|
+
> extends AsyncIterable<T> {
|
|
32
|
+
abortController: AbortController;
|
|
33
|
+
}
|
|
34
|
+
|
|
18
35
|
export default class HttpRequester {
|
|
19
36
|
private fetch: WindowOrWorkerGlobalScope['fetch'];
|
|
20
|
-
private xsrfToken: string | null = null;
|
|
21
37
|
private requestCallbacks: HttpRequesterRequestCallback[] = [];
|
|
22
38
|
private responseCallbacks: HttpRequesterResponseCallback[] = [];
|
|
39
|
+
private iteratorCallbacks: HttpRequesterIteratorCallback[] = [];
|
|
23
40
|
|
|
24
41
|
static makeUrl(url: string, data: { [k: string]: any }) {
|
|
25
42
|
if (!data) {
|
|
@@ -45,14 +62,18 @@ export default class HttpRequester {
|
|
|
45
62
|
callback: T extends 'request'
|
|
46
63
|
? HttpRequesterRequestCallback
|
|
47
64
|
: T extends 'response'
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
? HttpRequesterResponseCallback
|
|
66
|
+
: T extends 'iterator'
|
|
67
|
+
? HttpRequesterIteratorCallback
|
|
68
|
+
: never,
|
|
50
69
|
) {
|
|
51
70
|
const prop = (event + 'Callbacks') as T extends 'request'
|
|
52
71
|
? 'requestCallbacks'
|
|
53
72
|
: T extends 'response'
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
? 'responseCallbacks'
|
|
74
|
+
: T extends 'iterator'
|
|
75
|
+
? 'iteratorCallbacks'
|
|
76
|
+
: never;
|
|
56
77
|
if (!(prop in this)) {
|
|
57
78
|
throw new Error('Invalid event type.');
|
|
58
79
|
}
|
|
@@ -66,14 +87,18 @@ export default class HttpRequester {
|
|
|
66
87
|
callback: T extends 'request'
|
|
67
88
|
? HttpRequesterRequestCallback
|
|
68
89
|
: T extends 'response'
|
|
69
|
-
|
|
70
|
-
|
|
90
|
+
? HttpRequesterResponseCallback
|
|
91
|
+
: T extends 'iterator'
|
|
92
|
+
? HttpRequesterIteratorCallback
|
|
93
|
+
: never,
|
|
71
94
|
) {
|
|
72
95
|
const prop = (event + 'Callbacks') as T extends 'request'
|
|
73
96
|
? 'requestCallbacks'
|
|
74
97
|
: T extends 'response'
|
|
75
|
-
|
|
76
|
-
|
|
98
|
+
? 'responseCallbacks'
|
|
99
|
+
: T extends 'iterator'
|
|
100
|
+
? 'iteratorCallbacks'
|
|
101
|
+
: never;
|
|
77
102
|
if (!(prop in this)) {
|
|
78
103
|
return false;
|
|
79
104
|
}
|
|
@@ -86,10 +111,6 @@ export default class HttpRequester {
|
|
|
86
111
|
return true;
|
|
87
112
|
}
|
|
88
113
|
|
|
89
|
-
setXsrfToken(xsrfToken: string | null) {
|
|
90
|
-
this.xsrfToken = xsrfToken;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
114
|
async GET(opt: HttpRequesterRequestOptions) {
|
|
94
115
|
return await this._httpRequest('GET', opt);
|
|
95
116
|
}
|
|
@@ -98,6 +119,10 @@ export default class HttpRequester {
|
|
|
98
119
|
return await this._httpRequest('POST', opt);
|
|
99
120
|
}
|
|
100
121
|
|
|
122
|
+
async POST_ITERATOR(opt: HttpRequesterRequestOptions) {
|
|
123
|
+
return await this._iteratorRequest('POST', opt);
|
|
124
|
+
}
|
|
125
|
+
|
|
101
126
|
async PUT(opt: HttpRequesterRequestOptions) {
|
|
102
127
|
return await this._httpRequest('PUT', opt);
|
|
103
128
|
}
|
|
@@ -112,7 +137,7 @@ export default class HttpRequester {
|
|
|
112
137
|
|
|
113
138
|
async _httpRequest(
|
|
114
139
|
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
|
|
115
|
-
opt: HttpRequesterRequestOptions
|
|
140
|
+
opt: HttpRequesterRequestOptions,
|
|
116
141
|
) {
|
|
117
142
|
const dataString = JSON.stringify(opt.data);
|
|
118
143
|
let url = opt.url;
|
|
@@ -123,7 +148,7 @@ export default class HttpRequester {
|
|
|
123
148
|
}
|
|
124
149
|
const options: RequestInit = {
|
|
125
150
|
method,
|
|
126
|
-
headers: {},
|
|
151
|
+
headers: opt.headers ?? {},
|
|
127
152
|
credentials: 'include',
|
|
128
153
|
};
|
|
129
154
|
|
|
@@ -137,18 +162,13 @@ export default class HttpRequester {
|
|
|
137
162
|
this.requestCallbacks[i] && this.requestCallbacks[i](this, url, options);
|
|
138
163
|
}
|
|
139
164
|
|
|
140
|
-
if (this.xsrfToken !== null) {
|
|
141
|
-
(options.headers as Record<string, string>)['X-Xsrf-Token'] =
|
|
142
|
-
this.xsrfToken;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
165
|
const response = await this.fetch(url, options);
|
|
146
166
|
let text: string;
|
|
147
167
|
try {
|
|
148
168
|
text = await response.text();
|
|
149
169
|
} catch (e: any) {
|
|
150
170
|
throw new InvalidResponseError(
|
|
151
|
-
'Server response did not contain valid text body.'
|
|
171
|
+
'Server response did not contain valid text body.',
|
|
152
172
|
);
|
|
153
173
|
}
|
|
154
174
|
if (!response.ok) {
|
|
@@ -170,12 +190,12 @@ export default class HttpRequester {
|
|
|
170
190
|
throw response.status < 200
|
|
171
191
|
? new InformationalError(response, errObj)
|
|
172
192
|
: response.status < 300
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
193
|
+
? new SuccessError(response, errObj)
|
|
194
|
+
: response.status < 400
|
|
195
|
+
? new RedirectError(response, errObj)
|
|
196
|
+
: response.status < 500
|
|
197
|
+
? new ClientError(response, errObj)
|
|
198
|
+
: new ServerError(response, errObj);
|
|
179
199
|
}
|
|
180
200
|
for (let i = 0; i < this.responseCallbacks.length; i++) {
|
|
181
201
|
this.responseCallbacks[i] &&
|
|
@@ -192,13 +212,216 @@ export default class HttpRequester {
|
|
|
192
212
|
throw e;
|
|
193
213
|
}
|
|
194
214
|
throw new InvalidResponseError(
|
|
195
|
-
'Server response was invalid: ' + JSON.stringify(text)
|
|
215
|
+
'Server response was invalid: ' + JSON.stringify(text),
|
|
196
216
|
);
|
|
197
217
|
}
|
|
198
218
|
} else {
|
|
199
219
|
return text;
|
|
200
220
|
}
|
|
201
221
|
}
|
|
222
|
+
|
|
223
|
+
async _iteratorRequest(
|
|
224
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
|
|
225
|
+
opt: HttpRequesterRequestOptions,
|
|
226
|
+
): Promise<AbortableAsyncIterator> {
|
|
227
|
+
const dataString = JSON.stringify(opt.data);
|
|
228
|
+
let url = opt.url;
|
|
229
|
+
if (method === 'GET') {
|
|
230
|
+
// TODO: what should this size be?
|
|
231
|
+
// && dataString.length < 1) {
|
|
232
|
+
url = HttpRequester.makeUrl(opt.url, opt.data);
|
|
233
|
+
}
|
|
234
|
+
const hasBody = method !== 'GET' && opt.data;
|
|
235
|
+
const headers: Record<string, string> = opt.headers ?? {};
|
|
236
|
+
|
|
237
|
+
if (hasBody) {
|
|
238
|
+
headers['Content-Type'] = 'application/json';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (let i = 0; i < this.iteratorCallbacks.length; i++) {
|
|
242
|
+
this.iteratorCallbacks[i] &&
|
|
243
|
+
this.iteratorCallbacks[i](this, url, headers);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const responses: any[] = [];
|
|
247
|
+
let nextResponseResolve: (value: void) => void;
|
|
248
|
+
let nextResponseReadyPromise = new Promise<void>((res) => {
|
|
249
|
+
nextResponseResolve = res;
|
|
250
|
+
});
|
|
251
|
+
let responsesDone = false;
|
|
252
|
+
let serverResponse: Response;
|
|
253
|
+
|
|
254
|
+
const ctrl = new AbortController();
|
|
255
|
+
|
|
256
|
+
fetchEventSource(url, {
|
|
257
|
+
openWhenHidden: true,
|
|
258
|
+
fetch: this.fetch,
|
|
259
|
+
|
|
260
|
+
method,
|
|
261
|
+
headers,
|
|
262
|
+
credentials: 'include',
|
|
263
|
+
body: hasBody ? dataString : undefined,
|
|
264
|
+
signal: ctrl.signal,
|
|
265
|
+
|
|
266
|
+
async onopen(response) {
|
|
267
|
+
serverResponse = response;
|
|
268
|
+
if (response.ok) {
|
|
269
|
+
if (response.headers.get('content-type') === EventStreamContentType) {
|
|
270
|
+
throw new InvalidResponseError(
|
|
271
|
+
'Server response is not an event stream.',
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Response is ok, wait for messages.
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
let text: string = '';
|
|
280
|
+
try {
|
|
281
|
+
text = await response.text();
|
|
282
|
+
} catch (e: any) {
|
|
283
|
+
// Ignore error here.
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
let errObj;
|
|
287
|
+
try {
|
|
288
|
+
errObj = JSON.parse(text);
|
|
289
|
+
} catch (e: any) {
|
|
290
|
+
if (!(e instanceof SyntaxError)) {
|
|
291
|
+
throw e;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (typeof errObj !== 'object') {
|
|
296
|
+
errObj = {
|
|
297
|
+
textStatus: response.statusText,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
errObj.status = response.status;
|
|
301
|
+
throw response.status < 200
|
|
302
|
+
? new InformationalError(response, errObj)
|
|
303
|
+
: response.status < 300
|
|
304
|
+
? new SuccessError(response, errObj)
|
|
305
|
+
: response.status < 400
|
|
306
|
+
? new RedirectError(response, errObj)
|
|
307
|
+
: response.status < 500
|
|
308
|
+
? new ClientError(response, errObj)
|
|
309
|
+
: new ServerError(response, errObj);
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
onmessage(event) {
|
|
313
|
+
if (event.event === 'next') {
|
|
314
|
+
let text = event.data;
|
|
315
|
+
|
|
316
|
+
if (opt.dataType === 'json') {
|
|
317
|
+
if (!text.length) {
|
|
318
|
+
responses.push(
|
|
319
|
+
new InvalidResponseError('Server response was empty.'),
|
|
320
|
+
);
|
|
321
|
+
} else {
|
|
322
|
+
try {
|
|
323
|
+
responses.push(JSON.parse(text));
|
|
324
|
+
} catch (e: any) {
|
|
325
|
+
if (!(e instanceof SyntaxError)) {
|
|
326
|
+
responses.push(e);
|
|
327
|
+
} else {
|
|
328
|
+
responses.push(
|
|
329
|
+
new InvalidResponseError(
|
|
330
|
+
'Server response was invalid: ' + JSON.stringify(text),
|
|
331
|
+
),
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
responses.push(text);
|
|
338
|
+
}
|
|
339
|
+
} else if (event.event === 'error') {
|
|
340
|
+
let text = event.data;
|
|
341
|
+
|
|
342
|
+
let errObj;
|
|
343
|
+
try {
|
|
344
|
+
errObj = JSON.parse(text);
|
|
345
|
+
} catch (e: any) {
|
|
346
|
+
if (!(e instanceof SyntaxError)) {
|
|
347
|
+
throw e;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (typeof errObj !== 'object') {
|
|
352
|
+
errObj = {
|
|
353
|
+
status: 500,
|
|
354
|
+
textStatus: 'Iterator Error',
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
responses.push(
|
|
358
|
+
errObj.status < 200
|
|
359
|
+
? new InformationalError(serverResponse, errObj)
|
|
360
|
+
: errObj.status < 300
|
|
361
|
+
? new SuccessError(serverResponse, errObj)
|
|
362
|
+
: errObj.status < 400
|
|
363
|
+
? new RedirectError(serverResponse, errObj)
|
|
364
|
+
: errObj.status < 500
|
|
365
|
+
? new ClientError(serverResponse, errObj)
|
|
366
|
+
: new ServerError(serverResponse, errObj),
|
|
367
|
+
);
|
|
368
|
+
} else if (event.event === 'finished') {
|
|
369
|
+
responsesDone = true;
|
|
370
|
+
} else if (event.event === 'ping') {
|
|
371
|
+
// Ignore keep-alive pings.
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const resolve = nextResponseResolve;
|
|
376
|
+
if (!responsesDone) {
|
|
377
|
+
nextResponseReadyPromise = new Promise<void>((res) => {
|
|
378
|
+
nextResponseResolve = res;
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Resolve the promise to continue any waiting iterator.
|
|
383
|
+
resolve();
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
onclose() {
|
|
387
|
+
responses.push(
|
|
388
|
+
new ConnectionClosedUnexpectedlyError(
|
|
389
|
+
'The connection to the server was closed unexpectedly.',
|
|
390
|
+
),
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
responsesDone = true;
|
|
394
|
+
nextResponseResolve();
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
onerror(err) {
|
|
398
|
+
// Rethrow to stop the operation.
|
|
399
|
+
throw err;
|
|
400
|
+
},
|
|
401
|
+
}).catch((err) => {
|
|
402
|
+
responses.push(
|
|
403
|
+
new ConnectionError('The connection could not be established: ' + err),
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
responsesDone = true;
|
|
407
|
+
nextResponseResolve();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const iterator: AbortableAsyncIterator = {
|
|
411
|
+
abortController: ctrl,
|
|
412
|
+
async *[Symbol.asyncIterator]() {
|
|
413
|
+
do {
|
|
414
|
+
await nextResponseReadyPromise;
|
|
415
|
+
|
|
416
|
+
while (responses.length) {
|
|
417
|
+
yield responses.shift();
|
|
418
|
+
}
|
|
419
|
+
} while (!responsesDone);
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
return iterator;
|
|
424
|
+
}
|
|
202
425
|
}
|
|
203
426
|
|
|
204
427
|
export class InvalidResponseError extends Error {
|
|
@@ -208,6 +431,20 @@ export class InvalidResponseError extends Error {
|
|
|
208
431
|
}
|
|
209
432
|
}
|
|
210
433
|
|
|
434
|
+
export class ConnectionClosedUnexpectedlyError extends Error {
|
|
435
|
+
constructor(message: string) {
|
|
436
|
+
super(message);
|
|
437
|
+
this.name = 'ConnectionClosedUnexpectedlyError';
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export class ConnectionError extends Error {
|
|
442
|
+
constructor(message: string) {
|
|
443
|
+
super(message);
|
|
444
|
+
this.name = 'ConnectionError';
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
211
448
|
export class HttpError extends Error {
|
|
212
449
|
status: number;
|
|
213
450
|
statusText: string;
|
|
@@ -215,7 +452,7 @@ export class HttpError extends Error {
|
|
|
215
452
|
constructor(
|
|
216
453
|
name: string,
|
|
217
454
|
response: Response,
|
|
218
|
-
errObj: { textStatus: string }
|
|
455
|
+
errObj: { textStatus: string },
|
|
219
456
|
) {
|
|
220
457
|
super(errObj.textStatus);
|
|
221
458
|
this.name = name;
|