@any-routing/core 0.1.0 → 1.0.0-rc.2

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 (45) hide show
  1. package/README.md +5 -1
  2. package/eslint.config.mjs +24 -0
  3. package/package.json +7 -28
  4. package/project.json +21 -0
  5. package/src/index.ts +4 -0
  6. package/src/lib/core.model.ts +209 -0
  7. package/src/lib/core.ts +572 -0
  8. package/src/lib/data-providers/errors/unauthorized.ts +6 -0
  9. package/src/lib/data-providers/index.ts +39 -0
  10. package/src/lib/utils/dispatcher.ts +37 -0
  11. package/src/lib/utils/{random.js → random.ts} +0 -1
  12. package/src/lib/utils/requester.ts +41 -0
  13. package/tsconfig.json +23 -0
  14. package/tsconfig.lib.json +28 -0
  15. package/tsconfig.spec.json +28 -0
  16. package/vite.config.mts +59 -0
  17. package/src/index.d.ts +0 -4
  18. package/src/index.js +0 -5
  19. package/src/index.js.map +0 -1
  20. package/src/lib/data-providers/errors/index.js +0 -2
  21. package/src/lib/data-providers/errors/index.js.map +0 -1
  22. package/src/lib/data-providers/errors/unauthorized.d.ts +0 -6
  23. package/src/lib/data-providers/errors/unauthorized.js +0 -8
  24. package/src/lib/data-providers/errors/unauthorized.js.map +0 -1
  25. package/src/lib/data-providers/index.d.ts +0 -44
  26. package/src/lib/data-providers/index.js +0 -2
  27. package/src/lib/data-providers/index.js.map +0 -1
  28. package/src/lib/libre-routing.d.ts +0 -43
  29. package/src/lib/libre-routing.js +0 -235
  30. package/src/lib/libre-routing.js.map +0 -1
  31. package/src/lib/libre-routing.model.d.ts +0 -108
  32. package/src/lib/libre-routing.model.js +0 -15
  33. package/src/lib/libre-routing.model.js.map +0 -1
  34. package/src/lib/utils/dispatcher.d.ts +0 -7
  35. package/src/lib/utils/dispatcher.js +0 -25
  36. package/src/lib/utils/dispatcher.js.map +0 -1
  37. package/src/lib/utils/index.js +0 -4
  38. package/src/lib/utils/index.js.map +0 -1
  39. package/src/lib/utils/random.d.ts +0 -1
  40. package/src/lib/utils/random.js.map +0 -1
  41. package/src/lib/utils/requester.d.ts +0 -11
  42. package/src/lib/utils/requester.js +0 -44
  43. package/src/lib/utils/requester.js.map +0 -1
  44. /package/src/lib/data-providers/errors/{index.d.ts → index.ts} +0 -0
  45. /package/src/lib/utils/{index.d.ts → index.ts} +0 -0
@@ -0,0 +1,572 @@
1
+ import { AnyRoutingDataResponse, RouteSummary } from './data-providers';
2
+ import { Dispatcher } from './utils/dispatcher';
3
+ import { randomId } from './utils/random';
4
+
5
+ import {
6
+ AnyRoutingOptions,
7
+ AnyRoutingState,
8
+ PluginFactory,
9
+ Waypoint as InputWaypoint,
10
+ AnyRoutingPlugin,
11
+ AnyRoutingProjector,
12
+ InternalWaypoint,
13
+ InternalWaypointC,
14
+ AnyRoutingGeocoder,
15
+ RoutingEvents,
16
+ } from './core.model';
17
+
18
+ export type SetStateOptions<R extends AnyRoutingDataResponse> = {
19
+ waypoints?: InputWaypoint[];
20
+ data?: R;
21
+ routesShapeGeojson?: R['routesShapeGeojson'];
22
+ selectedRouteId?: number | null;
23
+ };
24
+
25
+ export class AnyRouting<R extends AnyRoutingDataResponse = AnyRoutingDataResponse, P extends AnyRoutingProjector = AnyRoutingProjector> {
26
+ private readonly dispatcher = new Dispatcher<RoutingEvents<R>>();
27
+ private readonly geocoder?: AnyRoutingGeocoder;
28
+
29
+ private readonly _options: AnyRoutingOptions<P>;
30
+ private readonly _plugins: AnyRoutingPlugin[] = [];
31
+ private _projector?: P;
32
+
33
+ private initialized = false;
34
+ private removed = false;
35
+ private calculationId = 0;
36
+ private latestAppliedCalculationId = 0;
37
+ private _state: AnyRoutingState<R>;
38
+
39
+ public constructor(options: AnyRoutingOptions<P>) {
40
+ this._options = {
41
+ uniqueKey: randomId(),
42
+ ...options,
43
+ };
44
+
45
+ this._state = this.createInitialState();
46
+
47
+ this._plugins = (options.plugins ?? []).map((plugin) => this.resolvePlugin(plugin));
48
+ this._projector = options.projector;
49
+
50
+ if (this.options.waypointsSyncStrategy === 'geocodeFirst' && !this.options.geocoder) {
51
+ throw new Error('Geocoder is required when waypointsSyncStrategy is `geocodeFirst`');
52
+ }
53
+
54
+ this.geocoder =
55
+ this.options.geocoder && typeof this.options.geocoder === 'function'
56
+ ? { geocode: this.options.geocoder }
57
+ : (this.options.geocoder as AnyRoutingGeocoder | undefined);
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Public API
62
+ // ---------------------------------------------------------------------------
63
+
64
+ public get options(): AnyRoutingOptions {
65
+ return this._options;
66
+ }
67
+
68
+ public get state(): AnyRoutingState<R> {
69
+ return this._state;
70
+ }
71
+
72
+ public get data(): R | undefined {
73
+ return this._state.data;
74
+ }
75
+
76
+ public get dataProvider() {
77
+ return this.options.dataProvider;
78
+ }
79
+
80
+ public get projector(): P | undefined {
81
+ return this._projector;
82
+ }
83
+
84
+ public get selectedRouteId(): number | null | undefined {
85
+ return this._state.selectedRouteId;
86
+ }
87
+
88
+ public initialize(): void {
89
+ if (this.initialized || this.removed) {
90
+ return;
91
+ }
92
+
93
+ this.initialized = true;
94
+
95
+ this._plugins.forEach((plugin) => {
96
+ plugin.onAdd(this);
97
+ });
98
+
99
+ this._projector?.onAdd(this);
100
+ }
101
+
102
+ public setProjector(projector?: P): void {
103
+ if (this._projector === projector) {
104
+ return;
105
+ }
106
+
107
+ if (this.initialized && !this.removed) {
108
+ this._projector?.onRemove(this);
109
+ }
110
+
111
+ this._projector = projector;
112
+ this._options.projector = projector;
113
+
114
+ if (this.initialized && !this.removed) {
115
+ this._projector?.onAdd(this);
116
+ }
117
+ }
118
+
119
+ public addPlugin<P extends PluginFactory>(
120
+ plugin: P,
121
+ ): P extends new (...args: any[]) => infer I ? I : P {
122
+ const resolved = this.resolvePlugin(plugin);
123
+ this._plugins.push(resolved);
124
+
125
+ if (this.initialized && !this.removed) {
126
+ resolved.onAdd(this);
127
+ }
128
+
129
+ return resolved as P extends new (...args: any[]) => infer I ? I : P;
130
+ }
131
+
132
+ public removePlugin(plugin: AnyRoutingPlugin): void {
133
+ const index = this._plugins.indexOf(plugin);
134
+
135
+ if (index === -1) {
136
+ return;
137
+ }
138
+
139
+ this._plugins.splice(index, 1);
140
+
141
+ if (this.initialized && !this.removed) {
142
+ plugin.onRemove(this);
143
+ }
144
+ }
145
+
146
+ public setWaypointsSyncStrategy(strategy: AnyRoutingOptions['waypointsSyncStrategy']): void {
147
+ if (strategy === 'geocodeFirst' && !this.geocoder) {
148
+ throw new Error('Geocoder is required when waypointsSyncStrategy is `geocodeFirst`');
149
+ }
150
+
151
+ this._options.waypointsSyncStrategy = strategy;
152
+ }
153
+
154
+ public onRemove(): void {
155
+ if (this.removed) {
156
+ return;
157
+ }
158
+
159
+ this.removed = true;
160
+
161
+ if (this.initialized) {
162
+ this._plugins.forEach((plugin) => {
163
+ plugin.onRemove(this);
164
+ });
165
+ this._projector?.onRemove(this);
166
+ }
167
+
168
+ this.options.dataProvider?.destroy();
169
+ }
170
+
171
+ public on<E extends keyof RoutingEvents<R>>(
172
+ event: E,
173
+ callback: (event: RoutingEvents<R>[E]) => void,
174
+ ): void {
175
+ this.dispatcher.on(event, callback);
176
+ }
177
+
178
+ public off<E extends keyof RoutingEvents<R>>(
179
+ event: E,
180
+ callback: (event: RoutingEvents<R>[E]) => void,
181
+ ): void {
182
+ this.dispatcher.off(event, callback);
183
+ }
184
+
185
+ public setWaypoints(waypoints: InputWaypoint[]): void {
186
+ this.updateState(
187
+ {
188
+ waypoints: this.transformToInternalWaypoints(waypoints),
189
+ },
190
+ );
191
+ }
192
+
193
+ public getWaypoint(index: number): InternalWaypoint | undefined {
194
+ return this._state.waypoints[index];
195
+ }
196
+
197
+ public setState(patch: SetStateOptions<R>): void {
198
+ const statePatch: Partial<AnyRoutingState<R>> = {};
199
+
200
+ if (patch.waypoints !== undefined) {
201
+ statePatch.waypoints = this.transformToInternalWaypoints(patch.waypoints);
202
+ }
203
+
204
+ if (patch.data !== undefined) {
205
+ statePatch.data = patch.data;
206
+ statePatch.routesShapeGeojson = patch.data.routesShapeGeojson;
207
+ }
208
+
209
+ if (patch.routesShapeGeojson !== undefined) {
210
+ statePatch.routesShapeGeojson = patch.routesShapeGeojson;
211
+ }
212
+
213
+ if (patch.selectedRouteId !== undefined) {
214
+ statePatch.selectedRouteId = patch.selectedRouteId;
215
+ }
216
+
217
+ this.updateState(statePatch);
218
+ }
219
+
220
+ public applyCalculationResult(data: R): void {
221
+ const patchState: Partial<AnyRoutingState<R>> = {
222
+ data,
223
+ selectedRouteId: data.selectedRouteId,
224
+ routesShapeGeojson: data.routesShapeGeojson,
225
+ };
226
+
227
+ if (this.options.waypointsSyncStrategy === 'toPath') {
228
+ patchState.waypoints = this.syncWaypointsPositions(data.routes[0].waypoints);
229
+ }
230
+
231
+ this.updateState(patchState);
232
+
233
+ this.fire('routesFound', {
234
+ waypoints: this.state.waypoints,
235
+ data,
236
+ });
237
+ }
238
+
239
+ public reset(): void {
240
+ const previousState = this._state;
241
+
242
+ this._state = this.createInitialState();
243
+
244
+ const updatedProperties = Object.keys(previousState) as Array<keyof AnyRoutingState<R>>;
245
+
246
+ this.fire('stateUpdated', {
247
+ updatedProperties,
248
+ });
249
+
250
+ this.fire('waypointsChanged', {
251
+ waypoints: this._state.waypoints,
252
+ });
253
+
254
+ if (previousState.loading !== this._state.loading) {
255
+ this.fire('loadingChanged', {
256
+ loading: this._state.loading,
257
+ });
258
+ }
259
+ }
260
+
261
+ public getUniqueName(name: string): string {
262
+ return `${name}-${this.options.uniqueKey}`;
263
+ }
264
+
265
+ public async recalculateRoute(): Promise<R | undefined> {
266
+ if (this.waypoints.length < 2) {
267
+ return undefined;
268
+ }
269
+
270
+ const dataProvider = this.options.dataProvider;
271
+
272
+ if (!dataProvider) {
273
+ throw new Error('No data provider');
274
+ }
275
+
276
+ const calculationId = ++this.calculationId;
277
+
278
+ this.setLoading(true);
279
+ this.fire('calculationStarted', { waypoints: this.waypoints });
280
+
281
+ try {
282
+ const waypointsSynced = await this.syncWaypointsBeforeCalculation(calculationId);
283
+ if (!waypointsSynced || calculationId !== this.calculationId) {
284
+ return undefined;
285
+ }
286
+
287
+ const data = (await dataProvider.request(this._state.waypoints, {
288
+ mode: 'default',
289
+ })) as R;
290
+
291
+ if (calculationId <= this.latestAppliedCalculationId) {
292
+ return data;
293
+ }
294
+
295
+ this.latestAppliedCalculationId = calculationId;
296
+
297
+ const patchState: Partial<AnyRoutingState<R>> = {
298
+ data,
299
+ selectedRouteId: data.selectedRouteId,
300
+ routesShapeGeojson: data.routesShapeGeojson,
301
+ };
302
+
303
+ if (this.options.waypointsSyncStrategy === 'toPath') {
304
+ patchState.waypoints = this.syncWaypointsPositions(data.routes[0].waypoints);
305
+ }
306
+
307
+ this.updateState(patchState);
308
+
309
+ // Tylko ostatni rozpoczęty request może zakończyć loading.
310
+ if (calculationId === this.calculationId) {
311
+ this.setLoading(false);
312
+ }
313
+
314
+ this.fire('routesFound', {
315
+ waypoints: this.state.waypoints,
316
+ data,
317
+ });
318
+
319
+ return data;
320
+ } catch (error: unknown) {
321
+ if (calculationId !== this.calculationId) {
322
+ return undefined;
323
+ }
324
+
325
+ this.updateState({
326
+ data: undefined,
327
+ selectedRouteId: null,
328
+ routesShapeGeojson: undefined,
329
+ });
330
+
331
+ if (calculationId === this.calculationId) {
332
+ this.setLoading(false);
333
+ }
334
+
335
+ this.fire('calculationError', {
336
+ error: this.toError(error),
337
+ });
338
+
339
+ throw error;
340
+ }
341
+ }
342
+
343
+ public selectRoute(routeId: number): void {
344
+ const data = this.data;
345
+
346
+ if (!data) {
347
+ return;
348
+ }
349
+
350
+ const route = data.routes[routeId];
351
+
352
+ if (!route) {
353
+ throw new Error(`No route with id: ${routeId}`);
354
+ }
355
+
356
+ this.updateState({
357
+ selectedRouteId: routeId,
358
+ data: {
359
+ ...data,
360
+ selectedRouteId: routeId,
361
+ },
362
+ });
363
+
364
+ this.fire('routeSelected', {
365
+ route,
366
+ routeId,
367
+ });
368
+ }
369
+
370
+ public syncWaypointsPositions(waypoints: RouteSummary['waypoints']): InternalWaypoint[] {
371
+ return waypoints.map((position, index) => {
372
+ const currentWaypoint = this._state.waypoints[index];
373
+
374
+ if (!currentWaypoint) {
375
+ return {
376
+ position,
377
+ originalPosition: position,
378
+ properties: {
379
+ index,
380
+ isFirst: index === 0,
381
+ isLast: index === waypoints.length - 1,
382
+ },
383
+ geocoded: false,
384
+ index,
385
+ isFirst: index === 0,
386
+ isLast: index === waypoints.length - 1,
387
+ } as InternalWaypoint;
388
+ }
389
+
390
+ return {
391
+ ...currentWaypoint,
392
+ position,
393
+ originalPosition: currentWaypoint.originalPosition ?? currentWaypoint.position,
394
+ properties: currentWaypoint.properties,
395
+ };
396
+ });
397
+ }
398
+
399
+ // ---------------------------------------------------------------------------
400
+ // State
401
+ // ---------------------------------------------------------------------------
402
+
403
+ private createInitialState(): AnyRoutingState<R> {
404
+ return {
405
+ waypoints: [],
406
+ data: undefined,
407
+ loading: false,
408
+ selectedRouteId: null,
409
+ routesShapeGeojson: undefined,
410
+ };
411
+ }
412
+
413
+ private updateState(
414
+ patch: Partial<AnyRoutingState<R>>,
415
+ ): void {
416
+ const updatedProperties = Object.keys(patch) as Array<keyof AnyRoutingState<R>>;
417
+
418
+ if (updatedProperties.length === 0) {
419
+ return;
420
+ }
421
+
422
+ const previousState = { ...this._state };
423
+
424
+ this._state = {
425
+ ...previousState,
426
+ ...patch,
427
+ };
428
+
429
+ this.fire('stateUpdated', {
430
+ updatedProperties,
431
+ });
432
+
433
+ if ('waypoints' in patch) {
434
+ this.fire('waypointsChanged', {
435
+ waypoints: this._state.waypoints,
436
+ });
437
+ }
438
+
439
+ if ('loading' in patch) {
440
+ this.fire('loadingChanged', {
441
+ loading: this._state.loading,
442
+ });
443
+ }
444
+ }
445
+
446
+ private setLoading(loading: boolean): void {
447
+ if (this._state.loading === loading) {
448
+ return;
449
+ }
450
+
451
+ this.updateState({
452
+ loading,
453
+ });
454
+ }
455
+
456
+ // ---------------------------------------------------------------------------
457
+ // Calculation
458
+ // ---------------------------------------------------------------------------
459
+
460
+ private async syncWaypointsBeforeCalculation(
461
+ calculationId: number,
462
+ ): Promise<boolean> {
463
+ if (this.options.waypointsSyncStrategy !== 'geocodeFirst') {
464
+ return true;
465
+ }
466
+
467
+ const waypointsToGeocode = this._state.waypoints.filter((waypoint) => !waypoint.geocoded);
468
+
469
+ if (waypointsToGeocode.length === 0) {
470
+ return true;
471
+ }
472
+
473
+ const previousWaypoints = this._state.waypoints;
474
+
475
+ const waypoints = await this.geocodeWaypoints(previousWaypoints);
476
+
477
+ if (calculationId !== this.calculationId) {
478
+ return false;
479
+ }
480
+
481
+ this.updateState({
482
+ waypoints,
483
+ });
484
+
485
+ waypoints.forEach((waypoint, index) => {
486
+ if (previousWaypoints[index] && !previousWaypoints[index].geocoded && waypoint.geocoded) {
487
+ this.fire('waypointGeocoded', {
488
+ waypoint,
489
+ });
490
+ }
491
+ });
492
+ return true;
493
+ }
494
+
495
+ // ---------------------------------------------------------------------------
496
+ // Plugins
497
+ // ---------------------------------------------------------------------------
498
+
499
+ private resolvePlugin(plugin: PluginFactory): AnyRoutingPlugin {
500
+ if (typeof plugin === 'function') {
501
+ return new plugin();
502
+ }
503
+
504
+ return plugin;
505
+ }
506
+
507
+ // ---------------------------------------------------------------------------
508
+ // Events
509
+ // ---------------------------------------------------------------------------
510
+
511
+ private fire<E extends keyof RoutingEvents<R>>(
512
+ event: E,
513
+ data: Omit<RoutingEvents<R>[E], 'state'>,
514
+ ): void {
515
+ this.dispatcher.fire(event, {
516
+ ...data,
517
+ state: this._state,
518
+ } as RoutingEvents<R>[E]);
519
+ }
520
+
521
+ // ---------------------------------------------------------------------------
522
+ // Waypoints
523
+ // ---------------------------------------------------------------------------
524
+
525
+ private transformToInternalWaypoints(waypoints: InputWaypoint[]): InternalWaypoint[] {
526
+ return waypoints.map((waypoint, index) =>
527
+ InternalWaypointC.fromWaypoint(waypoint, {
528
+ index,
529
+ isFirst: index === 0,
530
+ isLast: index === waypoints.length - 1,
531
+ }),
532
+ );
533
+ }
534
+
535
+ private geocodeWaypoints(waypoints: InternalWaypoint[]): Promise<InternalWaypoint[]> {
536
+ const geocoder = this.geocoder;
537
+ if (!geocoder) {
538
+ return Promise.resolve(waypoints);
539
+ }
540
+
541
+ return Promise.all(
542
+ waypoints.map(async (waypoint) => {
543
+ if (waypoint.geocoded) {
544
+ return waypoint;
545
+ }
546
+
547
+ const geocodedWaypoint = await geocoder.geocode(waypoint);
548
+
549
+ return {
550
+ ...geocodedWaypoint,
551
+ geocoded: true,
552
+ };
553
+ }),
554
+ );
555
+ }
556
+
557
+ // ---------------------------------------------------------------------------
558
+ // Utils
559
+ // ---------------------------------------------------------------------------
560
+
561
+ private toError(error: unknown): Error {
562
+ if (error instanceof Error) {
563
+ return error;
564
+ }
565
+
566
+ return new Error(String(error));
567
+ }
568
+
569
+ private get waypoints(): InternalWaypoint[] {
570
+ return this._state.waypoints;
571
+ }
572
+ }
@@ -0,0 +1,6 @@
1
+ export class UnauthorizedError {
2
+ public readonly code = 401;
3
+ public readonly message = 'Unauthorized';
4
+
5
+ constructor(public readonly response: unknown) {}
6
+ }
@@ -0,0 +1,39 @@
1
+ import type { BBox, FeatureCollection, Geometry, LineString } from 'geojson';
2
+ import { Waypoint, LngLatPosition } from '../core.model';
3
+
4
+ export interface AnyRoutingDataProvider {
5
+ request: (waypoints: Waypoint[], opts: RequestOptions) => Promise<AnyRoutingDataResponse>;
6
+
7
+ destroy(): void;
8
+ hasPendingRequests(): Promise<boolean>;
9
+ abortAllRequests(): void;
10
+ }
11
+
12
+ export type RequestOptions = Record<string, unknown>;
13
+
14
+ export interface AnyRoutingDataResponse {
15
+ rawResponse: unknown;
16
+ routesShapeGeojson: FeatureCollection<Geometry, { routeId: number; waypoint: number }>;
17
+ routes: RouteSummary[];
18
+ selectedRouteId?: number | null;
19
+ routesShapeBounds?: BBox;
20
+ version: number;
21
+ latest: boolean;
22
+ }
23
+
24
+ export type RoutePath = LngLatPosition[];
25
+
26
+ export interface RouteSummary {
27
+ id: number;
28
+ label?: string;
29
+ path: RoutePath;
30
+ durationTime: number;
31
+ arriveTime: Date;
32
+ departureTime: Date;
33
+ distance: number;
34
+ cost?: number;
35
+ waypoints: { lat: number; lng: number }[];
36
+ shape: FeatureCollection<LineString, { routeId: number; waypoint: number }>;
37
+ }
38
+
39
+ export * from './errors';
@@ -0,0 +1,37 @@
1
+ export class Dispatcher<EventMap extends object> {
2
+ private callbacks: {
3
+ [E in keyof EventMap]?: Array<(event: EventMap[E]) => void>;
4
+ } = {};
5
+
6
+ public fire<E extends keyof EventMap>(
7
+ event: E,
8
+ data: EventMap[E],
9
+ ): void {
10
+ for (const callback of this.callbacks[event] ?? []) {
11
+ try {
12
+ callback(data);
13
+ } catch (error) {
14
+ console.error(error);
15
+ }
16
+ }
17
+ }
18
+
19
+ public on<E extends keyof EventMap>(
20
+ event: E,
21
+ callback: (event: EventMap[E]) => void,
22
+ ): void {
23
+ this.callbacks[event] = [
24
+ ...(this.callbacks[event] ?? []),
25
+ callback,
26
+ ];
27
+ }
28
+
29
+ public off<E extends keyof EventMap>(
30
+ event: E,
31
+ callback: (event: EventMap[E]) => void,
32
+ ): void {
33
+ this.callbacks[event] = (
34
+ this.callbacks[event] ?? []
35
+ ).filter((item) => item !== callback);
36
+ }
37
+ }
@@ -1,2 +1 @@
1
1
  export const randomId = () => (Math.random() + 1).toString(36).substring(7);
2
- //# sourceMappingURL=random.js.map
@@ -0,0 +1,41 @@
1
+ export class Requester {
2
+ private buffer: AbortController[] = [];
3
+ private readonly maxBuffer = 4;
4
+ public get hasPendingRequests(): boolean {
5
+ return this.buffer.length > 0;
6
+ }
7
+
8
+ public async request(url: string, params?: RequestInit) {
9
+ const controller = new AbortController();
10
+
11
+ if (this.buffer.length >= this.maxBuffer) {
12
+ this.buffer.shift()?.abort();
13
+ }
14
+
15
+ this.buffer.push(controller);
16
+
17
+ try {
18
+ const response = await fetch(url, {
19
+ ...params,
20
+ signal: controller.signal,
21
+ });
22
+
23
+ if (!response.ok) {
24
+ throw response;
25
+ }
26
+
27
+ return await response.json();
28
+ } finally {
29
+ this.cleanBuffer(controller);
30
+ }
31
+ }
32
+
33
+ public abortAllRequests(): void {
34
+ this.buffer.forEach((controller) => controller.abort());
35
+ this.buffer = [];
36
+ }
37
+
38
+ private cleanBuffer(controller: AbortController): void {
39
+ this.buffer = this.buffer.filter((ctrl) => ctrl !== controller);
40
+ }
41
+ }