@blueprint-ts/core 4.0.0-beta.7 → 4.0.0-beta.9
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 +11 -0
- package/docs/.vitepress/config.ts +1 -0
- package/docs/services/pagination/infinite-scroller.md +4 -3
- package/docs/services/pagination/page-aware.md +5 -0
- package/docs/services/requests/abort-requests.md +4 -0
- package/docs/services/requests/concurrency.md +58 -0
- package/docs/services/requests/error-handling.md +16 -0
- package/docs/upgrading/v3-to-v4.md +16 -0
- package/package.json +4 -4
- package/src/pagination/BasePaginator.ts +4 -0
- package/src/pagination/PageAwarePaginator.ts +15 -5
- package/src/requests/BaseRequest.ts +87 -2
- package/src/requests/RequestConcurrencyMode.enum.ts +6 -0
- package/src/requests/contracts/BaseRequestContract.ts +3 -0
- package/src/requests/exceptions/StaleResponseException.ts +13 -0
- package/src/requests/index.ts +7 -1
- package/src/requests/types/RequestConcurrencyOptions.ts +6 -0
- package/src/vue/forms/PropertyAwareArray.ts +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## v4.0.0-beta.9 - 2026-02-28 (beta)
|
|
2
|
+
|
|
3
|
+
# [4.0.0-beta.9](/compare/v4.0.0-beta.8...v4.0.0-beta.9) (2026-02-28)
|
|
4
|
+
## v4.0.0-beta.8 - 2026-02-28 (beta)
|
|
5
|
+
|
|
6
|
+
# [4.0.0-beta.8](/compare/v4.0.0-beta.7...v4.0.0-beta.8) (2026-02-28)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **requests:** add concurrency policy, stale-response handling, and docs 5feb49d
|
|
1
12
|
## v4.0.0-beta.7 - 2026-02-27 (beta)
|
|
2
13
|
|
|
3
14
|
# [4.0.0-beta.7](/compare/v4.0.0-beta.6...v4.0.0-beta.7) (2026-02-27)
|
|
@@ -25,6 +25,7 @@ export default defineConfig({
|
|
|
25
25
|
{ text: 'Responses', link: '/services/requests/responses' },
|
|
26
26
|
{ text: 'Request Bodies', link: '/services/requests/request-bodies' },
|
|
27
27
|
{ text: 'Headers', link: '/services/requests/headers' },
|
|
28
|
+
{ text: 'Concurrency', link: '/services/requests/concurrency' },
|
|
28
29
|
{ text: 'Aborting Requests', link: '/services/requests/abort-requests' },
|
|
29
30
|
{ text: 'Events', link: '/services/requests/events' },
|
|
30
31
|
{ text: 'Bulk Requests', link: '/services/requests/bulk-requests' },
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Infinite Scroll
|
|
2
2
|
|
|
3
|
-
Use `InfiniteScroller` when you want to append pages to the existing list
|
|
3
|
+
Use `InfiniteScroller` when you want to append pages to the existing list. It extends `PageAwarePaginator`, so all
|
|
4
|
+
page-aware features and helpers are available.
|
|
4
5
|
|
|
5
6
|
```typescript
|
|
6
7
|
import { InfiniteScroller } from '@blueprint-ts/core/pagination'
|
|
@@ -11,8 +12,8 @@ await scroller.load()
|
|
|
11
12
|
await scroller.toNextPage()
|
|
12
13
|
```
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
`{ replace: true }` to
|
|
15
|
+
You can pass `{ flush: true }` to clear existing data before loading a page. In addition, `InfiniteScroller` supports
|
|
16
|
+
`{ replace: true }` to replace the current list instead of appending.
|
|
16
17
|
|
|
17
18
|
## Scroll Detection Helper
|
|
18
19
|
|
|
@@ -40,6 +40,11 @@ await paginator.setPageSize(25).load()
|
|
|
40
40
|
Page navigation helpers (`toNextPage`, `toPreviousPage`, `toFirstPage`, `toLastPage`) update the page number and load
|
|
41
41
|
the new page in one call.
|
|
42
42
|
|
|
43
|
+
## Concurrency
|
|
44
|
+
|
|
45
|
+
If the underlying request uses concurrency mode `LATEST` or `REPLACE_LATEST`, stale responses are ignored. In that case,
|
|
46
|
+
`load()` resolves with the current page data without updating the view, so older responses cannot overwrite newer ones.
|
|
47
|
+
|
|
43
48
|
|
|
44
49
|
## Updating Rows
|
|
45
50
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Requests can be aborted by passing an `AbortSignal` to the request.
|
|
4
4
|
|
|
5
|
+
If you want the request library to abort previous in-flight requests automatically, see [Concurrency](/services/requests/concurrency).
|
|
6
|
+
|
|
5
7
|
## Using AbortController
|
|
6
8
|
|
|
7
9
|
```typescript
|
|
@@ -16,6 +18,8 @@ const promise = request.send()
|
|
|
16
18
|
controller.abort()
|
|
17
19
|
```
|
|
18
20
|
|
|
21
|
+
Note: If you enable request concurrency with `REPLACE` or `REPLACE_LATEST`, the request will assign its own abort signal and override this one. See [Concurrency](/services/requests/concurrency) for details.
|
|
22
|
+
|
|
19
23
|
## Bulk Requests
|
|
20
24
|
|
|
21
25
|
`BulkRequestSender` internally manages an `AbortController` for its requests. You can abort the entire bulk operation:
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Request Concurrency
|
|
2
|
+
|
|
3
|
+
Concurrent requests can cause two common problems:
|
|
4
|
+
|
|
5
|
+
1. A slower, older response overwrites newer data ("stale results").
|
|
6
|
+
2. Loading state flickers because earlier requests finish after later ones.
|
|
7
|
+
|
|
8
|
+
To solve this, `BaseRequest` supports an optional concurrency policy that can abort older requests and/or ignore stale responses.
|
|
9
|
+
|
|
10
|
+
## Basic Usage
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { RequestConcurrencyMode } from '@blueprint-ts/core/requests'
|
|
14
|
+
|
|
15
|
+
const request = new ExpenseIndexRequest()
|
|
16
|
+
|
|
17
|
+
request.setConcurrency({
|
|
18
|
+
mode: RequestConcurrencyMode.REPLACE_LATEST,
|
|
19
|
+
key: 'expense-search'
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
request.send()
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Modes
|
|
26
|
+
|
|
27
|
+
- `ALLOW` (default): no aborts and no stale-response filtering.
|
|
28
|
+
- `REPLACE`: aborts any in-flight request with the same key.
|
|
29
|
+
- `LATEST`: ignores stale responses; only the most recent response is applied.
|
|
30
|
+
- `REPLACE_LATEST`: aborts older requests and ignores stale responses.
|
|
31
|
+
|
|
32
|
+
## Abort Signals
|
|
33
|
+
|
|
34
|
+
When using `REPLACE` or `REPLACE_LATEST`, the request creates and assigns its own `AbortController` for the concurrency key. This replaces any previously configured abort signal on that request instance. If you need to preserve a custom abort signal, apply it per request without using replace modes.
|
|
35
|
+
|
|
36
|
+
## Keys
|
|
37
|
+
|
|
38
|
+
The `key` lets you coordinate concurrency across multiple request instances. If you omit it, the request instance ID is used.
|
|
39
|
+
|
|
40
|
+
Use a shared key when multiple instances represent the same logical request stream (for example, a search box that creates new request objects).
|
|
41
|
+
|
|
42
|
+
## Stale Responses
|
|
43
|
+
|
|
44
|
+
When `LATEST` or `REPLACE_LATEST` is used, stale responses raise a `StaleResponseException` so the caller can ignore them safely.
|
|
45
|
+
|
|
46
|
+
If you don't want to handle it explicitly, catch and ignore it:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { StaleResponseException } from '@blueprint-ts/core/requests'
|
|
50
|
+
|
|
51
|
+
request.send().catch((error) => {
|
|
52
|
+
if (error instanceof StaleResponseException) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
throw error
|
|
57
|
+
})
|
|
58
|
+
```
|
|
@@ -13,6 +13,22 @@ When a request fails, `BaseRequest.send()` routes error responses through the re
|
|
|
13
13
|
|
|
14
14
|
## Catching Errors
|
|
15
15
|
|
|
16
|
+
When using request concurrency (see [Concurrency](/services/requests/concurrency)), `BaseRequest` can throw a `StaleResponseException` for outdated responses. These should usually be ignored:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { StaleResponseException } from '@blueprint-ts/core/requests'
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await request.send()
|
|
23
|
+
} catch (error: unknown) {
|
|
24
|
+
if (error instanceof StaleResponseException) {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
throw error
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
16
32
|
- `400` -> `BadRequestException`
|
|
17
33
|
- `401` -> `UnauthorizedException`
|
|
18
34
|
- `403` -> `ForbiddenException`
|
|
@@ -170,6 +170,22 @@ export class MyConfirmOptions implements ConfirmDialogOptions {
|
|
|
170
170
|
}
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
+
## BaseRequestContract Requires `setConcurrency`
|
|
174
|
+
|
|
175
|
+
`BaseRequestContract` now includes `setConcurrency(...)` so requests can opt into concurrency handling (e.g., latest-wins or replace-latest).
|
|
176
|
+
If you implemented `BaseRequestContract` directly (instead of extending `BaseRequest`), TypeScript will now require this method.
|
|
177
|
+
|
|
178
|
+
### How to Fix
|
|
179
|
+
|
|
180
|
+
Add the method to your custom request implementation:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
setConcurrency(options?: RequestConcurrencyOptions): this {
|
|
184
|
+
// no-op by default; use options if your implementation needs them
|
|
185
|
+
return this
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
173
189
|
## Laravel Packages Moved
|
|
174
190
|
|
|
175
191
|
Laravel modules moved from `@blueprint-ts/core/service/laravel/*` to `@blueprint-ts/core/laravel/*`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blueprint-ts/core",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.9",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
"vue-router": "^4.3.2"
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"lodash-es": "^4.17.
|
|
78
|
-
"qs": "^6.
|
|
79
|
-
"uuid": "^
|
|
77
|
+
"lodash-es": "^4.17.23",
|
|
78
|
+
"qs": "^6.15.0",
|
|
79
|
+
"uuid": "^13.0.0"
|
|
80
80
|
}
|
|
81
81
|
}
|
|
@@ -68,4 +68,8 @@ export abstract class BasePaginator<ResourceInterface, ViewDriver extends BaseVi
|
|
|
68
68
|
this.viewDriver.setTotal(dto.getTotal())
|
|
69
69
|
this.initialized = true
|
|
70
70
|
}
|
|
71
|
+
|
|
72
|
+
protected handleStaleResponse(): PaginationDataDto<ResourceInterface[]> {
|
|
73
|
+
return new PaginationDataDto<ResourceInterface[]>(this.viewDriver.getData(), this.viewDriver.getTotal())
|
|
74
|
+
}
|
|
71
75
|
}
|
|
@@ -4,6 +4,7 @@ import { type ViewDriverFactoryContract } from './contracts/ViewDriverFactoryCon
|
|
|
4
4
|
import { type PaginatorLoadDataOptions } from './contracts/PaginatorLoadDataOptions'
|
|
5
5
|
import { type PaginationDataDriverContract } from './contracts/PaginationDataDriverContract'
|
|
6
6
|
import { BasePaginator } from './BasePaginator'
|
|
7
|
+
import { StaleResponseException } from '../requests/exceptions/StaleResponseException'
|
|
7
8
|
|
|
8
9
|
export interface PageAwarePaginatorOptions {
|
|
9
10
|
viewDriverFactory?: ViewDriverFactoryContract
|
|
@@ -113,10 +114,19 @@ export class PageAwarePaginator<ResourceInterface> extends BasePaginator<Resourc
|
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
protected loadData(pageNumber: number, pageSize: number, options?: PaginatorLoadDataOptions): Promise<PaginationDataDto<ResourceInterface[]>> {
|
|
116
|
-
return this.dataDriver
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
return this.dataDriver
|
|
118
|
+
.get(pageNumber, pageSize)
|
|
119
|
+
.then((value: PaginationDataDto<ResourceInterface[]>) => {
|
|
120
|
+
this.passDataToViewDriver(value, options)
|
|
121
|
+
|
|
122
|
+
return value
|
|
123
|
+
})
|
|
124
|
+
.catch((error) => {
|
|
125
|
+
if (error instanceof StaleResponseException) {
|
|
126
|
+
return this.handleStaleResponse()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
throw error
|
|
130
|
+
})
|
|
121
131
|
}
|
|
122
132
|
}
|
|
@@ -4,6 +4,7 @@ import { RequestEvents } from './RequestEvents.enum'
|
|
|
4
4
|
import { RequestMethodEnum } from './RequestMethod.enum'
|
|
5
5
|
import { BaseResponse } from './responses/BaseResponse'
|
|
6
6
|
import { ResponseException } from './exceptions/ResponseException'
|
|
7
|
+
import { StaleResponseException } from './exceptions/StaleResponseException'
|
|
7
8
|
import { type DriverConfigContract } from './contracts/DriverConfigContract'
|
|
8
9
|
import { type BodyFactoryContract } from './contracts/BodyFactoryContract'
|
|
9
10
|
import { type RequestLoaderContract } from './contracts/RequestLoaderContract'
|
|
@@ -13,6 +14,8 @@ import { type BaseRequestContract, type EventHandlerCallback } from './contracts
|
|
|
13
14
|
import { type HeadersContract } from './contracts/HeadersContract'
|
|
14
15
|
import { type ResponseHandlerContract } from './drivers/contracts/ResponseHandlerContract'
|
|
15
16
|
import { type ResponseContract } from './contracts/ResponseContract'
|
|
17
|
+
import { type RequestConcurrencyOptions } from './types/RequestConcurrencyOptions'
|
|
18
|
+
import { RequestConcurrencyMode } from './RequestConcurrencyMode.enum'
|
|
16
19
|
import { mergeDeep } from '../support/helpers'
|
|
17
20
|
import { v4 as uuidv4 } from 'uuid'
|
|
18
21
|
|
|
@@ -29,6 +32,7 @@ export abstract class BaseRequest<
|
|
|
29
32
|
protected requestBody: RequestBodyInterface | undefined = undefined
|
|
30
33
|
protected requestLoader: RequestLoaderContract<RequestLoaderLoadingType> | undefined = undefined
|
|
31
34
|
protected abortSignal: AbortSignal | undefined = undefined
|
|
35
|
+
protected concurrencyOptions: RequestConcurrencyOptions | undefined = undefined
|
|
32
36
|
/* @ts-expect-error Ignore generics */
|
|
33
37
|
protected events: { [key in RequestEvents]?: EventHandlerCallback[] } = {}
|
|
34
38
|
|
|
@@ -36,6 +40,9 @@ export abstract class BaseRequest<
|
|
|
36
40
|
|
|
37
41
|
protected static requestDriver: RequestDriverContract
|
|
38
42
|
protected static requestLoaderFactory: RequestLoaderFactoryContract<unknown>
|
|
43
|
+
protected static concurrencySequenceByKey: Map<string, number> = new Map()
|
|
44
|
+
protected static concurrencyAbortControllerByKey: Map<string, AbortController> = new Map()
|
|
45
|
+
protected static concurrencyInFlightByKey: Map<string, number> = new Map()
|
|
39
46
|
|
|
40
47
|
public constructor() {
|
|
41
48
|
if (BaseRequest.requestLoaderFactory !== undefined) {
|
|
@@ -61,6 +68,12 @@ export abstract class BaseRequest<
|
|
|
61
68
|
return this
|
|
62
69
|
}
|
|
63
70
|
|
|
71
|
+
public setConcurrency(options?: RequestConcurrencyOptions): this {
|
|
72
|
+
this.concurrencyOptions = options
|
|
73
|
+
|
|
74
|
+
return this
|
|
75
|
+
}
|
|
76
|
+
|
|
64
77
|
public getRequestId(): string {
|
|
65
78
|
return this.requestId
|
|
66
79
|
}
|
|
@@ -124,6 +137,25 @@ export abstract class BaseRequest<
|
|
|
124
137
|
}
|
|
125
138
|
|
|
126
139
|
public async send(): Promise<ResponseClass> {
|
|
140
|
+
const concurrencyMode: RequestConcurrencyMode = this.concurrencyOptions?.mode ?? RequestConcurrencyMode.ALLOW
|
|
141
|
+
const concurrencyKey = this.concurrencyOptions?.key ?? this.requestId
|
|
142
|
+
const useReplace = concurrencyMode === RequestConcurrencyMode.REPLACE || concurrencyMode === RequestConcurrencyMode.REPLACE_LATEST
|
|
143
|
+
const useLatest = concurrencyMode === RequestConcurrencyMode.LATEST || concurrencyMode === RequestConcurrencyMode.REPLACE_LATEST
|
|
144
|
+
const sequence = this.bumpConcurrencySequence(concurrencyKey)
|
|
145
|
+
this.incrementConcurrencyInFlight(concurrencyKey)
|
|
146
|
+
|
|
147
|
+
if (useReplace) {
|
|
148
|
+
const previousController = BaseRequest.concurrencyAbortControllerByKey.get(concurrencyKey)
|
|
149
|
+
|
|
150
|
+
if (previousController) {
|
|
151
|
+
previousController.abort()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const controller = new AbortController()
|
|
155
|
+
BaseRequest.concurrencyAbortControllerByKey.set(concurrencyKey, controller)
|
|
156
|
+
this.setAbortSignal(controller.signal)
|
|
157
|
+
}
|
|
158
|
+
|
|
127
159
|
this.dispatch<boolean>(RequestEvents.LOADING, true)
|
|
128
160
|
|
|
129
161
|
this.requestLoader?.setLoading(true)
|
|
@@ -144,11 +176,23 @@ export abstract class BaseRequest<
|
|
|
144
176
|
this.getConfig()
|
|
145
177
|
)
|
|
146
178
|
.then(async (responseHandler: ResponseHandlerContract) => {
|
|
179
|
+
if (useLatest && !this.isLatestSequence(concurrencyKey, sequence)) {
|
|
180
|
+
throw new StaleResponseException()
|
|
181
|
+
}
|
|
182
|
+
|
|
147
183
|
await responseSkeleton.setResponse(responseHandler)
|
|
148
184
|
|
|
149
185
|
return responseSkeleton
|
|
150
186
|
})
|
|
151
187
|
.catch(async (error) => {
|
|
188
|
+
if (useLatest && !this.isLatestSequence(concurrencyKey, sequence)) {
|
|
189
|
+
throw new StaleResponseException('Stale response ignored', error)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (error instanceof StaleResponseException) {
|
|
193
|
+
throw error
|
|
194
|
+
}
|
|
195
|
+
|
|
152
196
|
if (error instanceof ResponseException) {
|
|
153
197
|
const handler = new ErrorHandler<ResponseErrorBody>(error.getResponse())
|
|
154
198
|
|
|
@@ -160,8 +204,14 @@ export abstract class BaseRequest<
|
|
|
160
204
|
throw error
|
|
161
205
|
})
|
|
162
206
|
.finally(() => {
|
|
163
|
-
this.
|
|
164
|
-
|
|
207
|
+
const isStale = useLatest && !this.isLatestSequence(concurrencyKey, sequence)
|
|
208
|
+
|
|
209
|
+
if (!isStale) {
|
|
210
|
+
this.dispatch<boolean>(RequestEvents.LOADING, false)
|
|
211
|
+
this.requestLoader?.setLoading(false)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
this.decrementConcurrencyInFlight(concurrencyKey)
|
|
165
215
|
})
|
|
166
216
|
}
|
|
167
217
|
|
|
@@ -185,6 +235,41 @@ export abstract class BaseRequest<
|
|
|
185
235
|
return this
|
|
186
236
|
}
|
|
187
237
|
|
|
238
|
+
protected bumpConcurrencySequence(key: string): number {
|
|
239
|
+
const next = (BaseRequest.concurrencySequenceByKey.get(key) ?? 0) + 1
|
|
240
|
+
BaseRequest.concurrencySequenceByKey.set(key, next)
|
|
241
|
+
|
|
242
|
+
return next
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
protected isLatestSequence(key: string, sequence: number): boolean {
|
|
246
|
+
return (BaseRequest.concurrencySequenceByKey.get(key) ?? 0) === sequence
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
protected incrementConcurrencyInFlight(key: string): void {
|
|
250
|
+
const next = (BaseRequest.concurrencyInFlightByKey.get(key) ?? 0) + 1
|
|
251
|
+
BaseRequest.concurrencyInFlightByKey.set(key, next)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
protected decrementConcurrencyInFlight(key: string): void {
|
|
255
|
+
const current = BaseRequest.concurrencyInFlightByKey.get(key)
|
|
256
|
+
|
|
257
|
+
if (current === undefined) {
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const next = current - 1
|
|
262
|
+
|
|
263
|
+
if (next <= 0) {
|
|
264
|
+
BaseRequest.concurrencyInFlightByKey.delete(key)
|
|
265
|
+
BaseRequest.concurrencySequenceByKey.delete(key)
|
|
266
|
+
BaseRequest.concurrencyAbortControllerByKey.delete(key)
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
BaseRequest.concurrencyInFlightByKey.set(key, next)
|
|
271
|
+
}
|
|
272
|
+
|
|
188
273
|
protected baseUrl(): undefined {
|
|
189
274
|
return undefined
|
|
190
275
|
}
|
|
@@ -2,6 +2,7 @@ import { RequestMethodEnum } from '../RequestMethod.enum'
|
|
|
2
2
|
import { RequestEvents } from '../RequestEvents.enum'
|
|
3
3
|
import { type BodyFactoryContract } from './BodyFactoryContract'
|
|
4
4
|
import { type HeadersContract } from './HeadersContract'
|
|
5
|
+
import { type RequestConcurrencyOptions } from '../types/RequestConcurrencyOptions'
|
|
5
6
|
|
|
6
7
|
export type EventHandlerCallback<T> = (value: T) => void
|
|
7
8
|
|
|
@@ -33,4 +34,6 @@ export interface BaseRequestContract<RequestLoaderLoadingType, RequestBodyInterf
|
|
|
33
34
|
getResponse(): ResponseClass
|
|
34
35
|
|
|
35
36
|
setAbortSignal(signal: AbortSignal): this
|
|
37
|
+
|
|
38
|
+
setConcurrency(options?: RequestConcurrencyOptions): this
|
|
36
39
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export class StaleResponseException extends Error {
|
|
2
|
+
public readonly cause: unknown
|
|
3
|
+
|
|
4
|
+
public constructor(message: string = 'Stale response ignored', cause?: unknown) {
|
|
5
|
+
super(message)
|
|
6
|
+
this.name = 'StaleResponseException'
|
|
7
|
+
this.cause = cause
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public getCause(): unknown {
|
|
11
|
+
return this.cause
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/requests/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { ErrorHandler } from './ErrorHandler'
|
|
|
8
8
|
import { RequestErrorRouter } from './RequestErrorRouter'
|
|
9
9
|
import { RequestEvents } from './RequestEvents.enum'
|
|
10
10
|
import { RequestMethodEnum } from './RequestMethod.enum'
|
|
11
|
+
import { RequestConcurrencyMode } from './RequestConcurrencyMode.enum'
|
|
11
12
|
import { JsonBodyFactory } from './factories/JsonBodyFactory'
|
|
12
13
|
import { FormDataFactory } from './factories/FormDataFactory'
|
|
13
14
|
import { type BodyContract } from './contracts/BodyContract'
|
|
@@ -19,7 +20,9 @@ import { type BodyFactoryContract } from './contracts/BodyFactoryContract'
|
|
|
19
20
|
import { type ResponseHandlerContract } from './drivers/contracts/ResponseHandlerContract'
|
|
20
21
|
import { type BaseRequestContract } from './contracts/BaseRequestContract'
|
|
21
22
|
import { ResponseException } from './exceptions/ResponseException'
|
|
23
|
+
import { StaleResponseException } from './exceptions/StaleResponseException'
|
|
22
24
|
import { type HeadersContract } from './contracts/HeadersContract'
|
|
25
|
+
import { type RequestConcurrencyOptions } from './types/RequestConcurrencyOptions'
|
|
23
26
|
|
|
24
27
|
export {
|
|
25
28
|
FetchDriver,
|
|
@@ -32,7 +35,9 @@ export {
|
|
|
32
35
|
RequestErrorRouter,
|
|
33
36
|
RequestEvents,
|
|
34
37
|
RequestMethodEnum,
|
|
38
|
+
RequestConcurrencyMode,
|
|
35
39
|
ResponseException,
|
|
40
|
+
StaleResponseException,
|
|
36
41
|
JsonBodyFactory,
|
|
37
42
|
FormDataFactory
|
|
38
43
|
}
|
|
@@ -46,5 +51,6 @@ export type {
|
|
|
46
51
|
BodyFactoryContract,
|
|
47
52
|
ResponseHandlerContract,
|
|
48
53
|
BaseRequestContract,
|
|
49
|
-
HeadersContract
|
|
54
|
+
HeadersContract,
|
|
55
|
+
RequestConcurrencyOptions
|
|
50
56
|
}
|
|
@@ -25,7 +25,6 @@ export type PropertyAware<T> = {
|
|
|
25
25
|
export class PropertyAwareArray<T = unknown> extends Array<T> {
|
|
26
26
|
// Private brand to prevent plain arrays from being assignable to PropertyAwareArray.
|
|
27
27
|
// This keeps conditional types from treating normal arrays as property-aware.
|
|
28
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-private-class-members
|
|
29
28
|
private readonly __propertyAwareArrayBrand!: void
|
|
30
29
|
/**
|
|
31
30
|
* Creates a new PropertyAwareArray instance
|