@nymphjs/client 1.0.0-beta.11 → 1.0.0-beta.111

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 (69) hide show
  1. package/CHANGELOG.md +456 -0
  2. package/README.md +6 -6
  3. package/asyncitertest.js +53 -0
  4. package/dist/Entity.d.ts +156 -0
  5. package/{lib → dist}/Entity.js +202 -104
  6. package/dist/Entity.js.map +1 -0
  7. package/dist/Entity.types.d.ts +218 -0
  8. package/dist/Entity.types.js +2 -0
  9. package/{lib → dist}/EntityWeakCache.d.ts +1 -1
  10. package/{lib → dist}/EntityWeakCache.js +5 -9
  11. package/dist/EntityWeakCache.js.map +1 -0
  12. package/{lib → dist}/HttpRequester.d.ts +18 -4
  13. package/dist/HttpRequester.js +365 -0
  14. package/dist/HttpRequester.js.map +1 -0
  15. package/{lib → dist}/Nymph.d.ts +48 -11
  16. package/{lib → dist}/Nymph.js +167 -51
  17. package/dist/Nymph.js.map +1 -0
  18. package/{lib → dist}/Nymph.types.d.ts +82 -2
  19. package/dist/Nymph.types.js +2 -0
  20. package/{lib → dist}/PubSub.d.ts +16 -10
  21. package/{lib → dist}/PubSub.js +172 -108
  22. package/dist/PubSub.js.map +1 -0
  23. package/{lib → dist}/PubSub.types.d.ts +8 -3
  24. package/dist/PubSub.types.js +2 -0
  25. package/{lib → dist}/entityRefresh.d.ts +1 -1
  26. package/{lib → dist}/entityRefresh.js +21 -13
  27. package/dist/entityRefresh.js.map +1 -0
  28. package/dist/index.d.ts +13 -0
  29. package/dist/index.js +13 -2
  30. package/dist/index.js.map +1 -1
  31. package/{lib → dist}/utils.d.ts +1 -1
  32. package/{lib → dist}/utils.js +28 -21
  33. package/dist/utils.js.map +1 -0
  34. package/jest.config.js +11 -2
  35. package/package.json +23 -27
  36. package/src/Entity.ts +173 -107
  37. package/src/Entity.types.ts +29 -47
  38. package/src/EntityWeakCache.ts +8 -6
  39. package/src/HttpRequester.ts +268 -31
  40. package/src/Nymph.ts +191 -88
  41. package/src/Nymph.types.ts +51 -2
  42. package/src/PubSub.ts +214 -141
  43. package/src/PubSub.types.ts +10 -5
  44. package/src/entityRefresh.ts +6 -6
  45. package/src/index.ts +10 -10
  46. package/src/utils.ts +12 -5
  47. package/tsconfig.json +6 -4
  48. package/typedoc.json +4 -0
  49. package/dist/index.js.LICENSE.txt +0 -8
  50. package/lib/Entity.d.ts +0 -51
  51. package/lib/Entity.js.map +0 -1
  52. package/lib/Entity.types.d.ts +0 -65
  53. package/lib/Entity.types.js +0 -3
  54. package/lib/EntityWeakCache.js.map +0 -1
  55. package/lib/HttpRequester.js +0 -190
  56. package/lib/HttpRequester.js.map +0 -1
  57. package/lib/Nymph.js.map +0 -1
  58. package/lib/Nymph.types.js +0 -3
  59. package/lib/PubSub.js.map +0 -1
  60. package/lib/PubSub.types.js +0 -3
  61. package/lib/entityRefresh.js.map +0 -1
  62. package/lib/index.d.ts +0 -13
  63. package/lib/index.js +0 -34
  64. package/lib/index.js.map +0 -1
  65. package/lib/utils.js.map +0 -1
  66. package/webpack.config.js +0 -28
  67. /package/{lib → dist}/Entity.types.js.map +0 -0
  68. /package/{lib → dist}/Nymph.types.js.map +0 -0
  69. /package/{lib → dist}/PubSub.types.js.map +0 -0
@@ -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
- ? HttpRequesterResponseCallback
49
- : never
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
- ? 'responseCallbacks'
55
- : never;
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
- ? HttpRequesterResponseCallback
70
- : never
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
- ? 'responseCallbacks'
76
- : never;
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
- ? new SuccessError(response, errObj)
174
- : response.status < 400
175
- ? new RedirectError(response, errObj)
176
- : response.status < 500
177
- ? new ClientError(response, errObj)
178
- : new ServerError(response, errObj);
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;