@blueprint-ts/core 3.0.0 → 4.0.0-beta.10

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 (211) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +25 -1
  3. package/docs/.vitepress/config.ts +81 -23
  4. package/docs/index.md +6 -63
  5. package/docs/{services/laravel → laravel}/pagination.md +19 -6
  6. package/docs/{services/laravel → laravel}/requests.md +2 -2
  7. package/docs/services/pagination/index.md +46 -0
  8. package/docs/services/pagination/infinite-scroller.md +20 -0
  9. package/docs/services/pagination/page-aware.md +51 -0
  10. package/docs/services/pagination/state-pagination.md +77 -0
  11. package/docs/services/pagination/updating-rows.md +52 -0
  12. package/docs/services/persistence/index.md +46 -0
  13. package/docs/services/requests/abort-requests.md +29 -0
  14. package/docs/services/requests/bulk-requests.md +70 -0
  15. package/docs/services/requests/concurrency.md +58 -0
  16. package/docs/services/requests/drivers.md +50 -0
  17. package/docs/services/requests/error-handling.md +153 -0
  18. package/docs/services/requests/events.md +31 -0
  19. package/docs/services/requests/getting-started.md +201 -0
  20. package/docs/services/requests/headers.md +40 -0
  21. package/docs/services/requests/loading.md +63 -0
  22. package/docs/services/requests/request-bodies.md +59 -0
  23. package/docs/services/requests/responses.md +34 -0
  24. package/docs/services/support/deferred-promise.md +63 -0
  25. package/docs/services/support/helpers.md +77 -0
  26. package/docs/services/support/index.md +6 -0
  27. package/docs/upgrading/v1-to-v2.md +64 -0
  28. package/docs/upgrading/v2-to-v3.md +52 -0
  29. package/docs/upgrading/v3-to-v4.md +214 -0
  30. package/docs/upgrading.md +0 -0
  31. package/docs/vue/composables/use-confirm-dialog.md +96 -0
  32. package/docs/vue/composables/use-global-checkbox.md +73 -0
  33. package/docs/vue/composables/use-is-empty.md +26 -0
  34. package/docs/vue/composables/use-is-open-from-var.md +32 -0
  35. package/docs/vue/composables/use-is-open.md +28 -0
  36. package/docs/vue/composables/use-model-wrapper.md +29 -0
  37. package/docs/vue/composables/use-on-open.md +26 -0
  38. package/docs/vue/forms/arrays.md +102 -0
  39. package/docs/vue/forms/errors.md +52 -0
  40. package/docs/vue/forms/index.md +99 -0
  41. package/docs/vue/forms/payloads.md +99 -0
  42. package/docs/vue/forms/persistence.md +19 -0
  43. package/docs/vue/forms/state-and-properties.md +26 -0
  44. package/docs/vue/forms/utilities.md +27 -0
  45. package/docs/vue/forms/validation.md +189 -0
  46. package/docs/vue/requests/loading.md +51 -0
  47. package/docs/vue/{requests → router}/route-resource-binding.md +33 -27
  48. package/docs/vue/state.md +27 -11
  49. package/package.json +12 -13
  50. package/release-tool.json +22 -3
  51. package/src/{service/bulkRequests → bulkRequests}/BulkRequestSender.ts +29 -17
  52. package/src/{service/bulkRequests → bulkRequests}/BulkRequestWrapper.ts +5 -5
  53. package/src/laravel/pagination/dataDrivers/RequestDriver.ts +30 -0
  54. package/src/laravel/pagination/index.ts +6 -0
  55. package/src/pagination/BasePaginator.ts +94 -0
  56. package/src/{service/pagination → pagination}/InfiniteScroller.ts +1 -0
  57. package/src/{service/pagination → pagination}/PageAwarePaginator.ts +34 -26
  58. package/src/{service/pagination → pagination}/StatePaginator.ts +2 -8
  59. package/src/{service/pagination → pagination}/index.ts +1 -3
  60. package/src/{service/requests → requests}/BaseRequest.ts +89 -4
  61. package/src/requests/ErrorHandler.ts +144 -0
  62. package/src/requests/RequestConcurrencyMode.enum.ts +6 -0
  63. package/src/requests/RequestErrorRouter.ts +89 -0
  64. package/src/{service/requests → requests}/bodies/FormDataBody.ts +10 -6
  65. package/src/{service/requests → requests}/contracts/BaseRequestContract.ts +3 -0
  66. package/src/requests/exceptions/BadGatewayException.ts +3 -0
  67. package/src/requests/exceptions/BadRequestException.ts +3 -0
  68. package/src/requests/exceptions/ConflictException.ts +3 -0
  69. package/src/requests/exceptions/ForbiddenException.ts +3 -0
  70. package/src/requests/exceptions/GatewayTimeoutException.ts +3 -0
  71. package/src/requests/exceptions/GoneException.ts +3 -0
  72. package/src/requests/exceptions/InvalidJsonException.ts +15 -0
  73. package/src/requests/exceptions/LockedException.ts +3 -0
  74. package/src/requests/exceptions/MethodNotAllowedException.ts +3 -0
  75. package/src/requests/exceptions/NotImplementedException.ts +3 -0
  76. package/src/requests/exceptions/PayloadTooLargeException.ts +3 -0
  77. package/src/requests/exceptions/PreconditionFailedException.ts +3 -0
  78. package/src/requests/exceptions/RequestTimeoutException.ts +3 -0
  79. package/src/requests/exceptions/ServiceUnavailableException.ts +3 -0
  80. package/src/requests/exceptions/StaleResponseException.ts +13 -0
  81. package/src/requests/exceptions/TooManyRequestsException.ts +3 -0
  82. package/src/requests/exceptions/UnsupportedMediaTypeException.ts +3 -0
  83. package/src/requests/exceptions/index.ts +51 -0
  84. package/src/requests/factories/FormDataFactory.ts +17 -0
  85. package/src/{service/requests → requests}/index.ts +9 -3
  86. package/src/requests/types/RequestConcurrencyOptions.ts +6 -0
  87. package/src/{service/support → support}/DeferredPromise.ts +1 -1
  88. package/src/support/index.ts +4 -0
  89. package/src/vue/composables/useConfirmDialog.ts +5 -1
  90. package/src/vue/composables/useModelWrapper.ts +3 -0
  91. package/src/vue/forms/BaseForm.ts +512 -399
  92. package/src/vue/forms/PropertyAwareArray.ts +6 -2
  93. package/src/vue/forms/index.ts +4 -4
  94. package/src/vue/forms/validation/index.ts +5 -2
  95. package/src/vue/forms/validation/rules/ConfirmedRule.ts +3 -3
  96. package/src/vue/forms/validation/rules/EmailRule.ts +23 -0
  97. package/src/vue/forms/validation/rules/JsonRule.ts +28 -0
  98. package/src/vue/forms/validation/types/BidirectionalRule.ts +2 -2
  99. package/src/vue/forms/validation/types/ValidationRules.ts +15 -0
  100. package/src/vue/index.ts +3 -3
  101. package/src/vue/requests/factories/VueRequestLoaderFactory.ts +3 -2
  102. package/src/vue/requests/loaders/VueRequestBatchLoader.ts +6 -1
  103. package/src/vue/requests/loaders/VueRequestLoader.ts +1 -1
  104. package/src/vue/router/routeResourceBinding/types.ts +3 -3
  105. package/src/vue/state/State.ts +38 -50
  106. package/tests/service/helpers/mergeDeep.test.ts +1 -1
  107. package/tests/service/laravel/pagination/dataDrivers/RequestDriver.test.ts +3 -3
  108. package/tests/service/laravel/requests/JsonBaseRequest.test.ts +4 -4
  109. package/tests/service/laravel/requests/PaginationJsonBaseRequest.test.ts +3 -3
  110. package/tests/service/laravel/requests/responses/JsonResponse.test.ts +2 -2
  111. package/tests/service/laravel/requests/responses/PaginationResponse.test.ts +2 -2
  112. package/tests/service/pagination/dtos/PaginationDataDto.test.ts +1 -1
  113. package/tests/service/pagination/factories/VuePaginationDriverFactory.test.ts +2 -2
  114. package/tests/service/pagination/frontendDrivers/VuePaginationDriver.test.ts +1 -1
  115. package/tests/service/requests/ErrorHandler.test.ts +61 -58
  116. package/tests/service/requests/FormDataBody.test.ts +1 -1
  117. package/tests/vue/forms/BaseForm.behavior.test.ts +98 -0
  118. package/docs/.vitepress/theme/Layout.vue +0 -14
  119. package/docs/.vitepress/theme/components/VersionSelector.vue +0 -64
  120. package/docs/.vitepress/theme/index.js +0 -13
  121. package/docs/services/requests/index.md +0 -74
  122. package/docs/vue/forms.md +0 -477
  123. package/examples/files/7z2404-x64.exe +0 -0
  124. package/examples/index.html +0 -14
  125. package/examples/js/app.js +0 -8
  126. package/examples/js/router.js +0 -22
  127. package/examples/js/view/App.vue +0 -49
  128. package/examples/js/view/layout/DemoPage.vue +0 -28
  129. package/examples/js/view/pagination/Pagination.vue +0 -28
  130. package/examples/js/view/pagination/components/errorPagination/ErrorPagination.vue +0 -71
  131. package/examples/js/view/pagination/components/errorPagination/GetProductsRequest.ts +0 -54
  132. package/examples/js/view/pagination/components/infiniteScrolling/GetProductsRequest.ts +0 -50
  133. package/examples/js/view/pagination/components/infiniteScrolling/InfiniteScrolling.vue +0 -57
  134. package/examples/js/view/pagination/components/tablePagination/GetProductsRequest.ts +0 -50
  135. package/examples/js/view/pagination/components/tablePagination/TablePagination.vue +0 -63
  136. package/examples/js/view/requests/Requests.vue +0 -34
  137. package/examples/js/view/requests/components/abortableRequest/AbortableRequest.vue +0 -36
  138. package/examples/js/view/requests/components/abortableRequest/GetProductsRequest.ts +0 -25
  139. package/examples/js/view/requests/components/fileDownloadRequest/DownloadFileRequest.ts +0 -15
  140. package/examples/js/view/requests/components/fileDownloadRequest/FileDownloadRequest.vue +0 -44
  141. package/examples/js/view/requests/components/getRequestWithDynamicParams/GetProductsRequest.ts +0 -34
  142. package/examples/js/view/requests/components/getRequestWithDynamicParams/GetRequestWithDynamicParams.vue +0 -59
  143. package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.ts +0 -21
  144. package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.vue +0 -53
  145. package/src/service/laravel/pagination/contracts/PaginationParamsContract.ts +0 -4
  146. package/src/service/laravel/pagination/dataDrivers/RequestDriver.ts +0 -32
  147. package/src/service/laravel/pagination/index.ts +0 -7
  148. package/src/service/pagination/BasePaginator.ts +0 -36
  149. package/src/service/pagination/Paginator.ts +0 -11
  150. package/src/service/requests/ErrorHandler.ts +0 -64
  151. package/src/service/requests/exceptions/index.ts +0 -19
  152. package/src/service/requests/factories/FormDataFactory.ts +0 -9
  153. package/src/service/support/index.ts +0 -3
  154. /package/src/{service/bulkRequests → bulkRequests}/BulkRequestEvent.enum.ts +0 -0
  155. /package/src/{service/bulkRequests → bulkRequests}/index.ts +0 -0
  156. /package/src/{service/laravel → laravel}/pagination/contracts/PaginationResponseBodyContract.ts +0 -0
  157. /package/src/{service/laravel → laravel}/requests/JsonBaseRequest.ts +0 -0
  158. /package/src/{service/laravel → laravel}/requests/PaginationJsonBaseRequest.ts +0 -0
  159. /package/src/{service/laravel → laravel}/requests/index.ts +0 -0
  160. /package/src/{service/laravel → laravel}/requests/responses/JsonResponse.ts +0 -0
  161. /package/src/{service/laravel → laravel}/requests/responses/PaginationResponse.ts +0 -0
  162. /package/src/{service/pagination → pagination}/contracts/BaseViewDriverContract.ts +0 -0
  163. /package/src/{service/pagination → pagination}/contracts/BaseViewDriverFactoryContract.ts +0 -0
  164. /package/src/{service/pagination → pagination}/contracts/PaginateableRequestContract.ts +0 -0
  165. /package/src/{service/pagination → pagination}/contracts/PaginationDataDriverContract.ts +0 -0
  166. /package/src/{service/pagination → pagination}/contracts/PaginationResponseContract.ts +0 -0
  167. /package/src/{service/pagination → pagination}/contracts/PaginatorLoadDataOptions.ts +0 -0
  168. /package/src/{service/pagination → pagination}/contracts/StatePaginationDataDriverContract.ts +0 -0
  169. /package/src/{service/pagination → pagination}/contracts/ViewDriverContract.ts +0 -0
  170. /package/src/{service/pagination → pagination}/contracts/ViewDriverFactoryContract.ts +0 -0
  171. /package/src/{service/pagination → pagination}/dataDrivers/ArrayDriver.ts +0 -0
  172. /package/src/{service/pagination → pagination}/dtos/PaginationDataDto.ts +0 -0
  173. /package/src/{service/pagination → pagination}/dtos/StatePaginationDataDto.ts +0 -0
  174. /package/src/{service/pagination → pagination}/factories/VueBaseViewDriverFactory.ts +0 -0
  175. /package/src/{service/pagination → pagination}/factories/VuePaginationDriverFactory.ts +0 -0
  176. /package/src/{service/pagination → pagination}/frontendDrivers/VueBaseViewDriver.ts +0 -0
  177. /package/src/{service/pagination → pagination}/frontendDrivers/VuePaginationDriver.ts +0 -0
  178. /package/src/{service/persistenceDrivers → persistenceDrivers}/LocalStorageDriver.ts +0 -0
  179. /package/src/{service/persistenceDrivers → persistenceDrivers}/NonPersistentDriver.ts +0 -0
  180. /package/src/{service/persistenceDrivers → persistenceDrivers}/SessionStorageDriver.ts +0 -0
  181. /package/src/{service/persistenceDrivers → persistenceDrivers}/index.ts +0 -0
  182. /package/src/{service/persistenceDrivers → persistenceDrivers}/types/PersistenceDriver.ts +0 -0
  183. /package/src/{service/requests → requests}/RequestEvents.enum.ts +0 -0
  184. /package/src/{service/requests → requests}/RequestMethod.enum.ts +0 -0
  185. /package/src/{service/requests → requests}/bodies/JsonBody.ts +0 -0
  186. /package/src/{service/requests → requests}/contracts/AbortableRequestContract.ts +0 -0
  187. /package/src/{service/requests → requests}/contracts/BodyContract.ts +0 -0
  188. /package/src/{service/requests → requests}/contracts/BodyFactoryContract.ts +0 -0
  189. /package/src/{service/requests → requests}/contracts/DriverConfigContract.ts +0 -0
  190. /package/src/{service/requests → requests}/contracts/HeadersContract.ts +0 -0
  191. /package/src/{service/requests → requests}/contracts/RequestDriverContract.ts +0 -0
  192. /package/src/{service/requests → requests}/contracts/RequestLoaderContract.ts +0 -0
  193. /package/src/{service/requests → requests}/contracts/RequestLoaderFactoryContract.ts +0 -0
  194. /package/src/{service/requests → requests}/contracts/ResponseContract.ts +0 -0
  195. /package/src/{service/requests → requests}/drivers/contracts/ResponseHandlerContract.ts +0 -0
  196. /package/src/{service/requests → requests}/drivers/fetch/FetchDriver.ts +0 -0
  197. /package/src/{service/requests → requests}/drivers/fetch/FetchResponse.ts +0 -0
  198. /package/src/{service/requests → requests}/exceptions/NoResponseReceivedException.ts +0 -0
  199. /package/src/{service/requests → requests}/exceptions/NotFoundException.ts +0 -0
  200. /package/src/{service/requests → requests}/exceptions/PageExpiredException.ts +0 -0
  201. /package/src/{service/requests → requests}/exceptions/ResponseBodyException.ts +0 -0
  202. /package/src/{service/requests → requests}/exceptions/ResponseException.ts +0 -0
  203. /package/src/{service/requests → requests}/exceptions/ServerErrorException.ts +0 -0
  204. /package/src/{service/requests → requests}/exceptions/UnauthorizedException.ts +0 -0
  205. /package/src/{service/requests → requests}/exceptions/ValidationException.ts +0 -0
  206. /package/src/{service/requests → requests}/factories/JsonBodyFactory.ts +0 -0
  207. /package/src/{service/requests → requests}/responses/BaseResponse.ts +0 -0
  208. /package/src/{service/requests → requests}/responses/BlobResponse.ts +0 -0
  209. /package/src/{service/requests → requests}/responses/JsonResponse.ts +0 -0
  210. /package/src/{service/requests → requests}/responses/PlainTextResponse.ts +0 -0
  211. /package/src/{helpers.ts → support/helpers.ts} +0 -0
@@ -0,0 +1,46 @@
1
+ # Persistence
2
+
3
+ This service provides simple persistence drivers that implement a common `PersistenceDriver` interface. It is used by `BaseForm`, but can also be used directly.
4
+
5
+ ## Available Drivers
6
+
7
+ - `NonPersistentDriver` — no persistence (default for `BaseForm`)
8
+ - `SessionStorageDriver` — uses `sessionStorage`
9
+ - `LocalStorageDriver` — uses `localStorage`
10
+
11
+ All drivers are exported from `@blueprint-ts/core/persistenceDrivers`.
12
+
13
+ ## Using A Driver
14
+
15
+ ```ts
16
+ import { LocalStorageDriver } from '@blueprint-ts/core/persistenceDrivers'
17
+
18
+ const driver = new LocalStorageDriver('optional-suffix')
19
+
20
+ driver.set('my-key', { value: 123 })
21
+ const value = driver.get<{ value: number }>('my-key')
22
+ ```
23
+
24
+ ## Implementing A Custom Driver
25
+
26
+ Implement the `PersistenceDriver` interface:
27
+
28
+ ```ts
29
+ import { type PersistenceDriver } from '@blueprint-ts/core/persistenceDrivers'
30
+
31
+ export class MemoryDriver implements PersistenceDriver {
32
+ private store = new Map<string, unknown>()
33
+
34
+ get<T>(key: string): T | null {
35
+ return (this.store.get(key) as T) ?? null
36
+ }
37
+
38
+ set<T>(key: string, state: T): void {
39
+ this.store.set(key, state)
40
+ }
41
+
42
+ remove(key: string): void {
43
+ this.store.delete(key)
44
+ }
45
+ }
46
+ ```
@@ -0,0 +1,29 @@
1
+ # Aborting Requests
2
+
3
+ Requests can be aborted by passing an `AbortSignal` to the request.
4
+
5
+ If you want the request library to abort previous in-flight requests automatically, see [Concurrency](/services/requests/concurrency).
6
+
7
+ ## Using AbortController
8
+
9
+ ```typescript
10
+ const controller = new AbortController()
11
+
12
+ const request = new ExpenseIndexRequest()
13
+ .setAbortSignal(controller.signal)
14
+
15
+ const promise = request.send()
16
+
17
+ // Later, when you want to abort:
18
+ controller.abort()
19
+ ```
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
+
23
+ ## Bulk Requests
24
+
25
+ `BulkRequestSender` internally manages an `AbortController` for its requests. You can abort the entire bulk operation:
26
+
27
+ ```typescript
28
+ bulkRequestSenderInstance.abort()
29
+ ```
@@ -0,0 +1,70 @@
1
+ # Bulk Requests
2
+
3
+ Bulk requests let you send many requests together with a shared execution mode and retry policy.
4
+
5
+ ## Basic Usage
6
+
7
+ Wrap each request in a `BulkRequestWrapper`, then send them with `BulkRequestSender`:
8
+
9
+ ```typescript
10
+ import {
11
+ BulkRequestExecutionMode,
12
+ BulkRequestSender,
13
+ BulkRequestWrapper,
14
+ BulkRequestEventEnum
15
+ } from '@blueprint-ts/core/bulkRequests'
16
+
17
+ const requests = items.map((item) =>
18
+ new BulkRequestWrapper(new DeleteRequest(item.id))
19
+ )
20
+
21
+ const sender = new BulkRequestSender(requests, BulkRequestExecutionMode.PARALLEL, 1)
22
+
23
+ await sender
24
+ .on(BulkRequestEventEnum.REQUEST_SUCCESSFUL, () => {
25
+ // handle success
26
+ })
27
+ .on(BulkRequestEventEnum.REQUEST_FAILED, () => {
28
+ // handle failure
29
+ })
30
+ .send()
31
+ ```
32
+
33
+ ## Wrapper State
34
+
35
+ `BulkRequestWrapper` tracks per-request state so you can inspect individual results:
36
+
37
+ - `wasSent()`
38
+ - `hasError()`
39
+ - `getError()`
40
+ - `getResponse()`
41
+
42
+ ## Sequential vs Parallel
43
+
44
+ - `BulkRequestExecutionMode.PARALLEL` sends all requests at once.
45
+ - `BulkRequestExecutionMode.SEQUENTIAL` sends requests one after another.
46
+
47
+ ## Retries
48
+
49
+ Pass a retry count to the sender to retry failed requests:
50
+
51
+ ```typescript
52
+ const sender = new BulkRequestSender(requests, BulkRequestExecutionMode.SEQUENTIAL, 2)
53
+ ```
54
+
55
+ ## Results
56
+
57
+ `send()` resolves with a summary object:
58
+
59
+ - `getSuccessCount()`
60
+ - `getErrorCount()`
61
+ - `getSuccessfulResponses()`
62
+ - `getFailedResponses()`
63
+
64
+ ## Reusing a Sender
65
+
66
+ You can reuse a sender instance with a new set of requests:
67
+
68
+ ```typescript
69
+ sender.setRequests(nextRequests)
70
+ ```
@@ -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
+ ```
@@ -0,0 +1,50 @@
1
+ # Drivers
2
+
3
+ Requests are executed by a request driver. The library includes a default fetch-based driver and lets you provide your
4
+ own by implementing `RequestDriverContract`.
5
+
6
+ ## Default Fetch Driver
7
+
8
+ ```typescript
9
+ import { BaseRequest, FetchDriver } from '@blueprint-ts/core/requests'
10
+
11
+ BaseRequest.setRequestDriver(new FetchDriver())
12
+ ```
13
+
14
+ The `FetchDriver` supports:
15
+
16
+ - Global headers
17
+ - `corsWithCredentials` configuration
18
+ - `AbortSignal` via request config
19
+
20
+ ## Custom Driver
21
+
22
+ To implement your own driver, implement `RequestDriverContract` and return a `ResponseHandlerContract`:
23
+
24
+ ```typescript
25
+ import { type RequestDriverContract } from '@blueprint-ts/core/requests'
26
+ import { type ResponseHandlerContract } from '@blueprint-ts/core/requests'
27
+ import { type RequestMethodEnum } from '@blueprint-ts/core/requests'
28
+ import { type HeadersContract } from '@blueprint-ts/core/requests'
29
+ import { type BodyContract } from '@blueprint-ts/core/requests'
30
+ import { type DriverConfigContract } from '@blueprint-ts/core/requests'
31
+
32
+ class CustomDriver implements RequestDriverContract {
33
+ public async send(
34
+ url: URL | string,
35
+ method: RequestMethodEnum,
36
+ headers: HeadersContract,
37
+ body?: BodyContract,
38
+ requestConfig?: DriverConfigContract
39
+ ): Promise<ResponseHandlerContract> {
40
+ // Implement your transport here and return a ResponseHandlerContract.
41
+ throw new Error('Not implemented')
42
+ }
43
+ }
44
+ ```
45
+
46
+ Register your driver during app boot:
47
+
48
+ ```typescript
49
+ BaseRequest.setRequestDriver(new CustomDriver())
50
+ ```
@@ -0,0 +1,153 @@
1
+ # Error Handling
2
+
3
+ When a request fails, `BaseRequest.send()` routes error responses through the request error handler and throws a typed exception.
4
+
5
+ ## Flow Overview
6
+
7
+ - The request driver throws a `ResponseException` when it receives a non-OK response.
8
+ - `BaseRequest.send()` catches that `ResponseException` and delegates to `ErrorHandler`.
9
+ - `ErrorHandler` parses the response body with `response.json()` into the request's `ResponseErrorBody` generic.
10
+ - If the parsed body is `undefined`, `NoResponseReceivedException` is thrown.
11
+ - Otherwise, the handler maps the HTTP status to a specific exception and throws it.
12
+ - If the error is not a `ResponseException`, `BaseRequest.send()` rethrows the original error.
13
+
14
+ ## Catching Errors
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
+
32
+ - `400` -> `BadRequestException`
33
+ - `401` -> `UnauthorizedException`
34
+ - `403` -> `ForbiddenException`
35
+ - `404` -> `NotFoundException`
36
+ - `405` -> `MethodNotAllowedException`
37
+ - `408` -> `RequestTimeoutException`
38
+ - `409` -> `ConflictException`
39
+ - `410` -> `GoneException`
40
+ - `412` -> `PreconditionFailedException`
41
+ - `413` -> `PayloadTooLargeException`
42
+ - `415` -> `UnsupportedMediaTypeException`
43
+ - `419` -> `PageExpiredException`
44
+ - `422` -> `ValidationException`
45
+ - `423` -> `LockedException`
46
+ - `429` -> `TooManyRequestsException`
47
+ - `500` -> `ServerErrorException`
48
+ - `501` -> `NotImplementedException`
49
+ - `502` -> `BadGatewayException`
50
+ - `503` -> `ServiceUnavailableException`
51
+ - `504` -> `GatewayTimeoutException`
52
+ - Any other status -> `ResponseException`
53
+
54
+ All mapped exceptions extend `ResponseBodyException`, so they provide both `getResponse()` and `getBody()` accessors. `ResponseException` only exposes `getResponse()`.
55
+ Error handling assumes error responses are JSON; if JSON parsing fails, an `InvalidJsonException` is thrown before status mapping runs.
56
+
57
+ When handling errors, treat the caught error as `unknown` and narrow with `instanceof`:
58
+
59
+ ```typescript
60
+ import {
61
+ ResponseException,
62
+ ValidationException,
63
+ UnauthorizedException
64
+ } from '@blueprint-ts/core/requests/exceptions'
65
+
66
+ try {
67
+ await request.send()
68
+ } catch (error: unknown) {
69
+ if (error instanceof ValidationException) {
70
+ const response = error.getResponse()
71
+ const body = error.getBody()
72
+ // Handle validation errors using response/body.
73
+ } else if (error instanceof UnauthorizedException) {
74
+ const response = error.getResponse()
75
+ const body = error.getBody()
76
+ // Handle auth errors using response/body.
77
+ } else if (error instanceof ResponseException) {
78
+ const response = error.getResponse()
79
+ // Handle other response errors.
80
+ }
81
+ }
82
+ ```
83
+
84
+ If you prefer to avoid manual `instanceof` checks, use the fluent `RequestErrorRouter`:
85
+
86
+ ```typescript
87
+ import { RequestErrorRouter } from '@blueprint-ts/core/requests'
88
+ import {
89
+ ResponseException,
90
+ ValidationException,
91
+ UnauthorizedException
92
+ } from '@blueprint-ts/core/requests/exceptions'
93
+
94
+ try {
95
+ await request.send()
96
+ } catch (error: unknown) {
97
+ await new RequestErrorRouter()
98
+ .on(ValidationException, (exception) => {
99
+ const response = exception.getResponse()
100
+ const body = exception.getBody()
101
+ // Handle validation errors using response/body.
102
+ })
103
+ .on(UnauthorizedException, (exception) => {
104
+ const response = exception.getResponse()
105
+ const body = exception.getBody()
106
+ // Handle auth errors using response/body.
107
+ })
108
+ .on(ResponseException, (exception) => {
109
+ const response = exception.getResponse()
110
+ // Handle other response errors.
111
+ })
112
+ .otherwise((exception) => {
113
+ // Handle non-response errors or rethrow.
114
+ throw exception
115
+ })
116
+ .handle(error)
117
+ }
118
+ ```
119
+
120
+ Handlers run in the order they are registered. Register specific exceptions before base types like `ResponseException`.
121
+ `RequestErrorRouter.handle()` returns `true` when a handler ran and `false` when no handler matched, so you can rethrow or fall back if needed.
122
+
123
+ ## Global Error Handling
124
+
125
+ You can register a global handler that runs before the normal error mapping:
126
+
127
+ ```typescript
128
+ import { ErrorHandler } from '@blueprint-ts/core/requests'
129
+
130
+ ErrorHandler.registerHandler((response) => {
131
+ // Inspect response here.
132
+ // Return false to indicate that normal handling should be skipped.
133
+ })
134
+ ```
135
+
136
+ Note: the handler only aborts when it explicitly returns `false`. Returning `true`, `undefined`, or nothing continues normal error mapping.
137
+
138
+ Example: redirect to login on `401` responses:
139
+
140
+ ```typescript
141
+ import { ErrorHandler } from '@blueprint-ts/core/requests'
142
+ import { type ResponseHandlerContract } from '@blueprint-ts/core/requests'
143
+
144
+ ErrorHandler.registerHandler((response: ResponseHandlerContract) => {
145
+ if (response.getStatusCode() !== 401) {
146
+ return
147
+ }
148
+
149
+ auth.logout()
150
+
151
+ router.push({ name: 'login' })
152
+ })
153
+ ```
@@ -0,0 +1,31 @@
1
+ # Events
2
+
3
+ Requests can emit lifecycle events via `BaseRequest.on(...)`.
4
+
5
+ ## Available Events
6
+
7
+ - `RequestEvents.LOADING`: Emits `true` when a request starts and `false` when it finishes.
8
+
9
+ ## Loading Event
10
+
11
+ Use the `RequestEvents.LOADING` event to track request loading state:
12
+
13
+ ```typescript
14
+ import { RequestEvents } from '@blueprint-ts/core/requests'
15
+
16
+ const request = new ExpenseIndexRequest()
17
+
18
+ request.on(RequestEvents.LOADING, (isLoading: boolean) => {
19
+ // Handle loading state
20
+ })
21
+
22
+ request.send()
23
+ ```
24
+
25
+ You can also pass the event payload type explicitly via the generic:
26
+
27
+ ```typescript
28
+ request.on<boolean>(RequestEvents.LOADING, (isLoading) => {
29
+ // isLoading is typed as boolean
30
+ })
31
+ ```
@@ -0,0 +1,201 @@
1
+ # Getting Started
2
+
3
+ Each API endpoint is represented as a separate class that extends `BaseRequest`. This class specifies the HTTP Method,
4
+ URL, and the expected request/response types.
5
+
6
+ ## Request Handling
7
+
8
+ The library leverages a fetch-based driver to perform HTTP requests. The following sections explain how to initialize
9
+ the request driver and define custom requests.
10
+
11
+ ## Initializing the Request Driver
12
+
13
+ Before making any requests, you must initialize the appropriate request driver. This is done during your application's
14
+ boot process by using the static `setRequestDriver` method.
15
+
16
+ ### Using the Fetch Driver
17
+
18
+ To set up the fetch driver, import `BaseRequest` and `FetchDriver` from '@blueprint-ts/core/requests' and initialize
19
+ the driver as shown:
20
+
21
+ ```typescript
22
+ import { BaseRequest, FetchDriver } from '@blueprint-ts/core/requests'
23
+
24
+ BaseRequest.setRequestDriver(new FetchDriver())
25
+ ```
26
+
27
+ ### Enabling Credential Support
28
+
29
+ If your requests need to include credentials (e.g., cookies for cross-origin requests), enable credential support as
30
+ follows:
31
+
32
+ ```typescript
33
+ BaseRequest.setRequestDriver(new FetchDriver({
34
+ corsWithCredentials: true,
35
+ }))
36
+ ```
37
+
38
+ ### Adding Global Headers
39
+
40
+ To include headers such as a CSRF token with every request, define them globally:
41
+
42
+ ```typescript
43
+ BaseRequest.setRequestDriver(new FetchDriver({
44
+ headers: {
45
+ 'X-XSRF-TOKEN': "<token>",
46
+ },
47
+ }))
48
+ ```
49
+
50
+ Sometimes you want to refetch the header when the request is sent. You may specify a callback for this:
51
+
52
+ ```typescript
53
+ BaseRequest.setRequestDriver(new FetchDriver({
54
+ headers: {
55
+ 'X-XSRF-TOKEN': () => getCookie('XSRF-TOKEN')
56
+ },
57
+ }))
58
+ ```
59
+
60
+ ### Specifying a Base URL
61
+
62
+ In case your backend lives on a separate domain, you may specify a default base url, which is prepended to every request url:
63
+
64
+ ```typescript
65
+ BaseRequest.setDefaultBaseUrl('https://example.com')
66
+ ```
67
+
68
+ ## Example: Expense Index Request
69
+
70
+ The following example demonstrates how to define a GET request to the `/api/v1/expenses` endpoint:
71
+
72
+ ```typescript
73
+ import { BaseRequest, RequestMethodEnum, JsonResponse } from '@blueprint-ts/core/requests'
74
+
75
+ export interface GenericResponseErrorInterface {
76
+ message: string
77
+ }
78
+
79
+ export interface ExpenseIndexRequestParams {
80
+ filter?: {
81
+ search_text?: string
82
+ };
83
+ }
84
+
85
+ export interface ExpenseResource {
86
+ id: string;
87
+ // other data fields
88
+ }
89
+
90
+ export interface ExpenseIndexRequestResponseBody {
91
+ data: ExpenseResource[]
92
+ }
93
+
94
+ export class ExpenseIndexRequest extends BaseRequest<
95
+ boolean, // Generic RequestLoaderLoadingType
96
+ GenericResponseErrorInterface, // Generic ResponseErrorBody
97
+ ExpenseIndexRequestResponseBody, // Generic ResponseBodyInterface
98
+ JsonResponse<ExpenseIndexRequestResponseBody>, // Generic ResponseClass
99
+ undefined, // Generic RequestBodyInterface
100
+ ExpenseIndexRequestParams // RequestParamsInterface
101
+ > {
102
+ public method(): RequestMethodEnum {
103
+ return RequestMethodEnum.GET
104
+ }
105
+
106
+ public url(): string {
107
+ return '/api/v1/expenses'
108
+ }
109
+ }
110
+ ```
111
+
112
+ ### Explanation
113
+
114
+ - **HTTP Method**: Uses `GET` to retrieve data from the `/api/v1/expenses` endpoint.
115
+ - **Error Handling**: On failure (4XX/5XX status codes), the response will conform to `GenericResponseErrorInterface`.
116
+ - **Success Response**: A successful response is expected to follow the `ExpenseIndexRequestResponseBody` interface.
117
+ - **Response Format**: The response is of type JSON, as indicated by `JsonResponse`.
118
+ - **Request Body**: Since this is a GET request, the body is `undefined`.
119
+ - **Query Parameters**: Accepts query parameters that match the `ExpenseIndexRequestParams` interface.
120
+
121
+ ### Sending the Request
122
+
123
+ Once the request is defined, you can send it using the following code:
124
+
125
+ ```typescript
126
+ const request = new ExpenseIndexRequest()
127
+
128
+ // The response type and body are inferred automatically.
129
+ const response: JsonResponse<ExpenseIndexRequestResponseBody> = await request.send()
130
+
131
+ const body = response.getBody() // Type: ExpenseIndexRequestResponseBody
132
+ ```
133
+
134
+ ## Example: Create Expense Request (POST)
135
+
136
+ This example demonstrates a POST request that sends a JSON body by overriding `getRequestBodyFactory()`:
137
+
138
+ ```typescript
139
+ import {
140
+ BaseRequest,
141
+ RequestMethodEnum,
142
+ JsonResponse,
143
+ JsonBodyFactory
144
+ } from '@blueprint-ts/core/requests'
145
+
146
+ export interface CreateExpensePayload {
147
+ title: string
148
+ amount: number
149
+ }
150
+
151
+ export interface CreateExpenseResponseBody {
152
+ id: string
153
+ }
154
+
155
+ export class CreateExpenseRequest extends BaseRequest<
156
+ boolean,
157
+ GenericResponseErrorInterface,
158
+ CreateExpenseResponseBody,
159
+ JsonResponse<CreateExpenseResponseBody>,
160
+ CreateExpensePayload
161
+ > {
162
+ public method(): RequestMethodEnum {
163
+ return RequestMethodEnum.POST
164
+ }
165
+
166
+ public url(): string {
167
+ return '/api/v1/expenses'
168
+ }
169
+
170
+ public getResponse(): JsonResponse<CreateExpenseResponseBody> {
171
+ return new JsonResponse<CreateExpenseResponseBody>()
172
+ }
173
+
174
+ public override getRequestBodyFactory() {
175
+ return new JsonBodyFactory<CreateExpensePayload>()
176
+ }
177
+ }
178
+ ```
179
+
180
+ ### Explanation
181
+
182
+ - **HTTP Method**: Uses `POST` to create a new expense.
183
+ - **Error Handling**: On failure (4XX/5XX status codes), the response will conform to `GenericResponseErrorInterface`.
184
+ - **Success Response**: A successful response is expected to follow the `CreateExpenseResponseBody` interface.
185
+ - **Response Format**: The response is of type JSON, as indicated by `JsonResponse`.
186
+ - **Request Body**: Uses `JsonBodyFactory` to send JSON with `Content-Type: application/json`.
187
+
188
+ ### Sending the Request
189
+
190
+ ```typescript
191
+ const request = new CreateExpenseRequest()
192
+
193
+ const response = await request.setBody({
194
+ title: 'Office supplies',
195
+ amount: 42
196
+ }).send()
197
+
198
+ const body = response.getBody() // Type: CreateExpenseResponseBody
199
+ ```
200
+
201
+ Note: If you use Laravel or an API that wraps payloads under a `data` key, consider using `JsonBaseRequest` from the Laravel integration.
@@ -0,0 +1,40 @@
1
+ # Headers
2
+
3
+ Requests assemble headers from multiple sources before sending:
4
+
5
+ 1. **Driver defaults** (global headers set on the driver)
6
+ 2. **Request headers** returned by `requestHeaders()`
7
+ 3. **Body headers** from the request body factory (e.g., `Content-Type`)
8
+
9
+ Later sources override earlier ones. Header values can be strings or callbacks that resolve at send time.
10
+
11
+ ## Global Headers (Driver)
12
+
13
+ Set headers once when you configure the driver:
14
+
15
+ ```typescript
16
+ BaseRequest.setRequestDriver(new FetchDriver({
17
+ headers: {
18
+ 'X-XSRF-TOKEN': () => getCookie('XSRF-TOKEN')
19
+ },
20
+ }))
21
+ ```
22
+
23
+ ## Per-Request Headers
24
+
25
+ Override `requestHeaders()` on a request to add headers per request:
26
+
27
+ ```typescript
28
+ import { type HeadersContract } from '@blueprint-ts/core/requests'
29
+
30
+ public override requestHeaders(): HeadersContract {
31
+ return {
32
+ Authorization: `Bearer ${this.accessToken}`
33
+ }
34
+ }
35
+ ```
36
+
37
+ ## Body Headers
38
+
39
+ Request body factories can set headers such as `Content-Type`. For example, `JsonBodyFactory` sets
40
+ `Content-Type: application/json`.