@blueprint-ts/core 3.0.0 → 4.0.0-beta.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 (206) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +25 -1
  3. package/docs/.vitepress/config.ts +80 -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 +19 -0
  9. package/docs/services/pagination/page-aware.md +46 -0
  10. package/docs/services/pagination/state-pagination.md +77 -0
  11. package/docs/services/pagination/updating-rows.md +36 -0
  12. package/docs/services/persistence/index.md +46 -0
  13. package/docs/services/requests/abort-requests.md +25 -0
  14. package/docs/services/requests/bulk-requests.md +70 -0
  15. package/docs/services/requests/drivers.md +50 -0
  16. package/docs/services/requests/error-handling.md +137 -0
  17. package/docs/services/requests/events.md +31 -0
  18. package/docs/services/requests/getting-started.md +201 -0
  19. package/docs/services/requests/headers.md +40 -0
  20. package/docs/services/requests/loading.md +63 -0
  21. package/docs/services/requests/request-bodies.md +59 -0
  22. package/docs/services/requests/responses.md +34 -0
  23. package/docs/services/support/deferred-promise.md +63 -0
  24. package/docs/services/support/helpers.md +77 -0
  25. package/docs/services/support/index.md +6 -0
  26. package/docs/upgrading/v1-to-v2.md +64 -0
  27. package/docs/upgrading/v2-to-v3.md +52 -0
  28. package/docs/upgrading/v3-to-v4.md +171 -0
  29. package/docs/upgrading.md +0 -0
  30. package/docs/vue/composables/use-confirm-dialog.md +96 -0
  31. package/docs/vue/composables/use-global-checkbox.md +73 -0
  32. package/docs/vue/composables/use-is-empty.md +26 -0
  33. package/docs/vue/composables/use-is-open-from-var.md +32 -0
  34. package/docs/vue/composables/use-is-open.md +28 -0
  35. package/docs/vue/composables/use-model-wrapper.md +29 -0
  36. package/docs/vue/composables/use-on-open.md +26 -0
  37. package/docs/vue/forms/arrays.md +45 -0
  38. package/docs/vue/forms/errors.md +52 -0
  39. package/docs/vue/forms/index.md +99 -0
  40. package/docs/vue/forms/payloads.md +99 -0
  41. package/docs/vue/forms/persistence.md +19 -0
  42. package/docs/vue/forms/state-and-properties.md +26 -0
  43. package/docs/vue/forms/utilities.md +27 -0
  44. package/docs/vue/forms/validation.md +189 -0
  45. package/docs/vue/requests/loading.md +51 -0
  46. package/docs/vue/{requests → router}/route-resource-binding.md +33 -27
  47. package/docs/vue/state.md +27 -11
  48. package/package.json +9 -10
  49. package/release-tool.json +22 -3
  50. package/src/{service/bulkRequests → bulkRequests}/BulkRequestSender.ts +29 -17
  51. package/src/{service/bulkRequests → bulkRequests}/BulkRequestWrapper.ts +5 -5
  52. package/src/laravel/pagination/dataDrivers/RequestDriver.ts +30 -0
  53. package/src/laravel/pagination/index.ts +6 -0
  54. package/src/{service/pagination → pagination}/BasePaginator.ts +35 -0
  55. package/src/{service/pagination → pagination}/InfiniteScroller.ts +1 -0
  56. package/src/{service/pagination → pagination}/PageAwarePaginator.ts +19 -21
  57. package/src/{service/pagination → pagination}/StatePaginator.ts +2 -8
  58. package/src/{service/pagination → pagination}/index.ts +1 -1
  59. package/src/{service/requests → requests}/BaseRequest.ts +2 -2
  60. package/src/requests/ErrorHandler.ts +144 -0
  61. package/src/requests/RequestErrorRouter.ts +89 -0
  62. package/src/{service/requests → requests}/bodies/FormDataBody.ts +10 -6
  63. package/src/requests/exceptions/BadGatewayException.ts +3 -0
  64. package/src/requests/exceptions/BadRequestException.ts +3 -0
  65. package/src/requests/exceptions/ConflictException.ts +3 -0
  66. package/src/requests/exceptions/ForbiddenException.ts +3 -0
  67. package/src/requests/exceptions/GatewayTimeoutException.ts +3 -0
  68. package/src/requests/exceptions/GoneException.ts +3 -0
  69. package/src/requests/exceptions/InvalidJsonException.ts +15 -0
  70. package/src/requests/exceptions/LockedException.ts +3 -0
  71. package/src/requests/exceptions/MethodNotAllowedException.ts +3 -0
  72. package/src/requests/exceptions/NotImplementedException.ts +3 -0
  73. package/src/requests/exceptions/PayloadTooLargeException.ts +3 -0
  74. package/src/requests/exceptions/PreconditionFailedException.ts +3 -0
  75. package/src/requests/exceptions/RequestTimeoutException.ts +3 -0
  76. package/src/requests/exceptions/ServiceUnavailableException.ts +3 -0
  77. package/src/requests/exceptions/TooManyRequestsException.ts +3 -0
  78. package/src/requests/exceptions/UnsupportedMediaTypeException.ts +3 -0
  79. package/src/requests/exceptions/index.ts +51 -0
  80. package/src/requests/factories/FormDataFactory.ts +17 -0
  81. package/src/{service/requests → requests}/index.ts +2 -2
  82. package/src/{service/support → support}/DeferredPromise.ts +1 -1
  83. package/src/support/index.ts +4 -0
  84. package/src/vue/composables/useConfirmDialog.ts +5 -1
  85. package/src/vue/composables/useModelWrapper.ts +3 -0
  86. package/src/vue/forms/BaseForm.ts +491 -393
  87. package/src/vue/forms/PropertyAwareArray.ts +2 -2
  88. package/src/vue/forms/index.ts +4 -4
  89. package/src/vue/forms/validation/index.ts +5 -2
  90. package/src/vue/forms/validation/rules/ConfirmedRule.ts +3 -3
  91. package/src/vue/forms/validation/rules/EmailRule.ts +23 -0
  92. package/src/vue/forms/validation/rules/JsonRule.ts +28 -0
  93. package/src/vue/forms/validation/types/BidirectionalRule.ts +2 -2
  94. package/src/vue/forms/validation/types/ValidationRules.ts +15 -0
  95. package/src/vue/index.ts +3 -3
  96. package/src/vue/requests/factories/VueRequestLoaderFactory.ts +3 -2
  97. package/src/vue/requests/loaders/VueRequestBatchLoader.ts +6 -1
  98. package/src/vue/requests/loaders/VueRequestLoader.ts +1 -1
  99. package/src/vue/router/routeResourceBinding/types.ts +3 -3
  100. package/src/vue/state/State.ts +38 -50
  101. package/tests/service/helpers/mergeDeep.test.ts +1 -1
  102. package/tests/service/laravel/pagination/dataDrivers/RequestDriver.test.ts +3 -3
  103. package/tests/service/laravel/requests/JsonBaseRequest.test.ts +4 -4
  104. package/tests/service/laravel/requests/PaginationJsonBaseRequest.test.ts +3 -3
  105. package/tests/service/laravel/requests/responses/JsonResponse.test.ts +2 -2
  106. package/tests/service/laravel/requests/responses/PaginationResponse.test.ts +2 -2
  107. package/tests/service/pagination/dtos/PaginationDataDto.test.ts +1 -1
  108. package/tests/service/pagination/factories/VuePaginationDriverFactory.test.ts +2 -2
  109. package/tests/service/pagination/frontendDrivers/VuePaginationDriver.test.ts +1 -1
  110. package/tests/service/requests/ErrorHandler.test.ts +61 -58
  111. package/tests/service/requests/FormDataBody.test.ts +1 -1
  112. package/tests/vue/forms/BaseForm.behavior.test.ts +98 -0
  113. package/docs/.vitepress/theme/Layout.vue +0 -14
  114. package/docs/.vitepress/theme/components/VersionSelector.vue +0 -64
  115. package/docs/.vitepress/theme/index.js +0 -13
  116. package/docs/services/requests/index.md +0 -74
  117. package/docs/vue/forms.md +0 -477
  118. package/examples/files/7z2404-x64.exe +0 -0
  119. package/examples/index.html +0 -14
  120. package/examples/js/app.js +0 -8
  121. package/examples/js/router.js +0 -22
  122. package/examples/js/view/App.vue +0 -49
  123. package/examples/js/view/layout/DemoPage.vue +0 -28
  124. package/examples/js/view/pagination/Pagination.vue +0 -28
  125. package/examples/js/view/pagination/components/errorPagination/ErrorPagination.vue +0 -71
  126. package/examples/js/view/pagination/components/errorPagination/GetProductsRequest.ts +0 -54
  127. package/examples/js/view/pagination/components/infiniteScrolling/GetProductsRequest.ts +0 -50
  128. package/examples/js/view/pagination/components/infiniteScrolling/InfiniteScrolling.vue +0 -57
  129. package/examples/js/view/pagination/components/tablePagination/GetProductsRequest.ts +0 -50
  130. package/examples/js/view/pagination/components/tablePagination/TablePagination.vue +0 -63
  131. package/examples/js/view/requests/Requests.vue +0 -34
  132. package/examples/js/view/requests/components/abortableRequest/AbortableRequest.vue +0 -36
  133. package/examples/js/view/requests/components/abortableRequest/GetProductsRequest.ts +0 -25
  134. package/examples/js/view/requests/components/fileDownloadRequest/DownloadFileRequest.ts +0 -15
  135. package/examples/js/view/requests/components/fileDownloadRequest/FileDownloadRequest.vue +0 -44
  136. package/examples/js/view/requests/components/getRequestWithDynamicParams/GetProductsRequest.ts +0 -34
  137. package/examples/js/view/requests/components/getRequestWithDynamicParams/GetRequestWithDynamicParams.vue +0 -59
  138. package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.ts +0 -21
  139. package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.vue +0 -53
  140. package/src/service/laravel/pagination/contracts/PaginationParamsContract.ts +0 -4
  141. package/src/service/laravel/pagination/dataDrivers/RequestDriver.ts +0 -32
  142. package/src/service/laravel/pagination/index.ts +0 -7
  143. package/src/service/requests/ErrorHandler.ts +0 -64
  144. package/src/service/requests/exceptions/index.ts +0 -19
  145. package/src/service/requests/factories/FormDataFactory.ts +0 -9
  146. package/src/service/support/index.ts +0 -3
  147. /package/src/{service/bulkRequests → bulkRequests}/BulkRequestEvent.enum.ts +0 -0
  148. /package/src/{service/bulkRequests → bulkRequests}/index.ts +0 -0
  149. /package/src/{service/laravel → laravel}/pagination/contracts/PaginationResponseBodyContract.ts +0 -0
  150. /package/src/{service/laravel → laravel}/requests/JsonBaseRequest.ts +0 -0
  151. /package/src/{service/laravel → laravel}/requests/PaginationJsonBaseRequest.ts +0 -0
  152. /package/src/{service/laravel → laravel}/requests/index.ts +0 -0
  153. /package/src/{service/laravel → laravel}/requests/responses/JsonResponse.ts +0 -0
  154. /package/src/{service/laravel → laravel}/requests/responses/PaginationResponse.ts +0 -0
  155. /package/src/{service/pagination → pagination}/Paginator.ts +0 -0
  156. /package/src/{service/pagination → pagination}/contracts/BaseViewDriverContract.ts +0 -0
  157. /package/src/{service/pagination → pagination}/contracts/BaseViewDriverFactoryContract.ts +0 -0
  158. /package/src/{service/pagination → pagination}/contracts/PaginateableRequestContract.ts +0 -0
  159. /package/src/{service/pagination → pagination}/contracts/PaginationDataDriverContract.ts +0 -0
  160. /package/src/{service/pagination → pagination}/contracts/PaginationResponseContract.ts +0 -0
  161. /package/src/{service/pagination → pagination}/contracts/PaginatorLoadDataOptions.ts +0 -0
  162. /package/src/{service/pagination → pagination}/contracts/StatePaginationDataDriverContract.ts +0 -0
  163. /package/src/{service/pagination → pagination}/contracts/ViewDriverContract.ts +0 -0
  164. /package/src/{service/pagination → pagination}/contracts/ViewDriverFactoryContract.ts +0 -0
  165. /package/src/{service/pagination → pagination}/dataDrivers/ArrayDriver.ts +0 -0
  166. /package/src/{service/pagination → pagination}/dtos/PaginationDataDto.ts +0 -0
  167. /package/src/{service/pagination → pagination}/dtos/StatePaginationDataDto.ts +0 -0
  168. /package/src/{service/pagination → pagination}/factories/VueBaseViewDriverFactory.ts +0 -0
  169. /package/src/{service/pagination → pagination}/factories/VuePaginationDriverFactory.ts +0 -0
  170. /package/src/{service/pagination → pagination}/frontendDrivers/VueBaseViewDriver.ts +0 -0
  171. /package/src/{service/pagination → pagination}/frontendDrivers/VuePaginationDriver.ts +0 -0
  172. /package/src/{service/persistenceDrivers → persistenceDrivers}/LocalStorageDriver.ts +0 -0
  173. /package/src/{service/persistenceDrivers → persistenceDrivers}/NonPersistentDriver.ts +0 -0
  174. /package/src/{service/persistenceDrivers → persistenceDrivers}/SessionStorageDriver.ts +0 -0
  175. /package/src/{service/persistenceDrivers → persistenceDrivers}/index.ts +0 -0
  176. /package/src/{service/persistenceDrivers → persistenceDrivers}/types/PersistenceDriver.ts +0 -0
  177. /package/src/{service/requests → requests}/RequestEvents.enum.ts +0 -0
  178. /package/src/{service/requests → requests}/RequestMethod.enum.ts +0 -0
  179. /package/src/{service/requests → requests}/bodies/JsonBody.ts +0 -0
  180. /package/src/{service/requests → requests}/contracts/AbortableRequestContract.ts +0 -0
  181. /package/src/{service/requests → requests}/contracts/BaseRequestContract.ts +0 -0
  182. /package/src/{service/requests → requests}/contracts/BodyContract.ts +0 -0
  183. /package/src/{service/requests → requests}/contracts/BodyFactoryContract.ts +0 -0
  184. /package/src/{service/requests → requests}/contracts/DriverConfigContract.ts +0 -0
  185. /package/src/{service/requests → requests}/contracts/HeadersContract.ts +0 -0
  186. /package/src/{service/requests → requests}/contracts/RequestDriverContract.ts +0 -0
  187. /package/src/{service/requests → requests}/contracts/RequestLoaderContract.ts +0 -0
  188. /package/src/{service/requests → requests}/contracts/RequestLoaderFactoryContract.ts +0 -0
  189. /package/src/{service/requests → requests}/contracts/ResponseContract.ts +0 -0
  190. /package/src/{service/requests → requests}/drivers/contracts/ResponseHandlerContract.ts +0 -0
  191. /package/src/{service/requests → requests}/drivers/fetch/FetchDriver.ts +0 -0
  192. /package/src/{service/requests → requests}/drivers/fetch/FetchResponse.ts +0 -0
  193. /package/src/{service/requests → requests}/exceptions/NoResponseReceivedException.ts +0 -0
  194. /package/src/{service/requests → requests}/exceptions/NotFoundException.ts +0 -0
  195. /package/src/{service/requests → requests}/exceptions/PageExpiredException.ts +0 -0
  196. /package/src/{service/requests → requests}/exceptions/ResponseBodyException.ts +0 -0
  197. /package/src/{service/requests → requests}/exceptions/ResponseException.ts +0 -0
  198. /package/src/{service/requests → requests}/exceptions/ServerErrorException.ts +0 -0
  199. /package/src/{service/requests → requests}/exceptions/UnauthorizedException.ts +0 -0
  200. /package/src/{service/requests → requests}/exceptions/ValidationException.ts +0 -0
  201. /package/src/{service/requests → requests}/factories/JsonBodyFactory.ts +0 -0
  202. /package/src/{service/requests → requests}/responses/BaseResponse.ts +0 -0
  203. /package/src/{service/requests → requests}/responses/BlobResponse.ts +0 -0
  204. /package/src/{service/requests → requests}/responses/JsonResponse.ts +0 -0
  205. /package/src/{service/requests → requests}/responses/PlainTextResponse.ts +0 -0
  206. /package/src/{helpers.ts → support/helpers.ts} +0 -0
@@ -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,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,137 @@
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
+ - `400` -> `BadRequestException`
17
+ - `401` -> `UnauthorizedException`
18
+ - `403` -> `ForbiddenException`
19
+ - `404` -> `NotFoundException`
20
+ - `405` -> `MethodNotAllowedException`
21
+ - `408` -> `RequestTimeoutException`
22
+ - `409` -> `ConflictException`
23
+ - `410` -> `GoneException`
24
+ - `412` -> `PreconditionFailedException`
25
+ - `413` -> `PayloadTooLargeException`
26
+ - `415` -> `UnsupportedMediaTypeException`
27
+ - `419` -> `PageExpiredException`
28
+ - `422` -> `ValidationException`
29
+ - `423` -> `LockedException`
30
+ - `429` -> `TooManyRequestsException`
31
+ - `500` -> `ServerErrorException`
32
+ - `501` -> `NotImplementedException`
33
+ - `502` -> `BadGatewayException`
34
+ - `503` -> `ServiceUnavailableException`
35
+ - `504` -> `GatewayTimeoutException`
36
+ - Any other status -> `ResponseException`
37
+
38
+ All mapped exceptions extend `ResponseBodyException`, so they provide both `getResponse()` and `getBody()` accessors. `ResponseException` only exposes `getResponse()`.
39
+ Error handling assumes error responses are JSON; if JSON parsing fails, an `InvalidJsonException` is thrown before status mapping runs.
40
+
41
+ When handling errors, treat the caught error as `unknown` and narrow with `instanceof`:
42
+
43
+ ```typescript
44
+ import {
45
+ ResponseException,
46
+ ValidationException,
47
+ UnauthorizedException
48
+ } from '@blueprint-ts/core/requests/exceptions'
49
+
50
+ try {
51
+ await request.send()
52
+ } catch (error: unknown) {
53
+ if (error instanceof ValidationException) {
54
+ const response = error.getResponse()
55
+ const body = error.getBody()
56
+ // Handle validation errors using response/body.
57
+ } else if (error instanceof UnauthorizedException) {
58
+ const response = error.getResponse()
59
+ const body = error.getBody()
60
+ // Handle auth errors using response/body.
61
+ } else if (error instanceof ResponseException) {
62
+ const response = error.getResponse()
63
+ // Handle other response errors.
64
+ }
65
+ }
66
+ ```
67
+
68
+ If you prefer to avoid manual `instanceof` checks, use the fluent `RequestErrorRouter`:
69
+
70
+ ```typescript
71
+ import { RequestErrorRouter } from '@blueprint-ts/core/requests'
72
+ import {
73
+ ResponseException,
74
+ ValidationException,
75
+ UnauthorizedException
76
+ } from '@blueprint-ts/core/requests/exceptions'
77
+
78
+ try {
79
+ await request.send()
80
+ } catch (error: unknown) {
81
+ await new RequestErrorRouter()
82
+ .on(ValidationException, (exception) => {
83
+ const response = exception.getResponse()
84
+ const body = exception.getBody()
85
+ // Handle validation errors using response/body.
86
+ })
87
+ .on(UnauthorizedException, (exception) => {
88
+ const response = exception.getResponse()
89
+ const body = exception.getBody()
90
+ // Handle auth errors using response/body.
91
+ })
92
+ .on(ResponseException, (exception) => {
93
+ const response = exception.getResponse()
94
+ // Handle other response errors.
95
+ })
96
+ .otherwise((exception) => {
97
+ // Handle non-response errors or rethrow.
98
+ throw exception
99
+ })
100
+ .handle(error)
101
+ }
102
+ ```
103
+
104
+ Handlers run in the order they are registered. Register specific exceptions before base types like `ResponseException`.
105
+ `RequestErrorRouter.handle()` returns `true` when a handler ran and `false` when no handler matched, so you can rethrow or fall back if needed.
106
+
107
+ ## Global Error Handling
108
+
109
+ You can register a global handler that runs before the normal error mapping:
110
+
111
+ ```typescript
112
+ import { ErrorHandler } from '@blueprint-ts/core/requests'
113
+
114
+ ErrorHandler.registerHandler((response) => {
115
+ // Inspect response here.
116
+ // Return false to indicate that normal handling should be skipped.
117
+ })
118
+ ```
119
+
120
+ Note: the handler only aborts when it explicitly returns `false`. Returning `true`, `undefined`, or nothing continues normal error mapping.
121
+
122
+ Example: redirect to login on `401` responses:
123
+
124
+ ```typescript
125
+ import { ErrorHandler } from '@blueprint-ts/core/requests'
126
+ import { type ResponseHandlerContract } from '@blueprint-ts/core/requests'
127
+
128
+ ErrorHandler.registerHandler((response: ResponseHandlerContract) => {
129
+ if (response.getStatusCode() !== 401) {
130
+ return
131
+ }
132
+
133
+ auth.logout()
134
+
135
+ router.push({ name: 'login' })
136
+ })
137
+ ```
@@ -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`.
@@ -0,0 +1,63 @@
1
+ # Loading
2
+
3
+ Requests can expose loading state through a request loader. You register a loader factory once, and each `BaseRequest`
4
+ instance will ask the factory for a loader.
5
+
6
+ Note: For Vue apps, the library provides `VueRequestLoader` and `VueRequestLoaderFactory` in `@blueprint-ts/core/vue/requests`.
7
+
8
+ ## Registering a Loader Factory
9
+
10
+ Use `BaseRequest.setRequestLoaderFactory()` to register a factory that implements `RequestLoaderFactoryContract` and
11
+ returns a `RequestLoaderContract`:
12
+
13
+ ```typescript
14
+ import {
15
+ BaseRequest,
16
+ type RequestLoaderContract,
17
+ type RequestLoaderFactoryContract
18
+ } from '@blueprint-ts/core/requests'
19
+
20
+ class BooleanLoader implements RequestLoaderContract<boolean> {
21
+ private loading = false
22
+
23
+ public isLoading(): boolean {
24
+ return this.loading
25
+ }
26
+
27
+ public setLoading(value: boolean): void {
28
+ this.loading = value
29
+ }
30
+ }
31
+
32
+ class BooleanLoaderFactory implements RequestLoaderFactoryContract<boolean> {
33
+ public make(): RequestLoaderContract<boolean> {
34
+ return new BooleanLoader()
35
+ }
36
+ }
37
+
38
+ BaseRequest.setRequestLoaderFactory(new BooleanLoaderFactory())
39
+ ```
40
+
41
+ ## Reading Loading State
42
+
43
+ Once a loader factory is registered, every request created from `BaseRequest` can read loading state:
44
+
45
+ ```typescript
46
+ const request = new ExpenseIndexRequest()
47
+
48
+ request.send()
49
+
50
+ const isLoading = request.isLoading()
51
+ ```
52
+
53
+ ## Override Loader
54
+
55
+ You can override the loader per request with `setRequestLoader`:
56
+
57
+ ```typescript
58
+ const request = new ExpenseIndexRequest()
59
+
60
+ request.setRequestLoader(customLoader)
61
+ ```
62
+
63
+ This lets you create or share a loader before the request exists, which is useful when loading state must be wired up ahead of time.
@@ -0,0 +1,59 @@
1
+ # Request Bodies
2
+
3
+ Request body factories control how outgoing request bodies are serialized and which `Content-Type` header is sent.
4
+ They are separate from response parsing (response classes control `Accept` and how the response body is parsed).
5
+
6
+ ## How It Works
7
+
8
+ When you call `send()`, `BaseRequest` uses the request body factory to build a `BodyContract`:
9
+
10
+ - `getRequestBodyFactory()` returns a `BodyFactoryContract`
11
+ - `BodyFactoryContract.make()` returns a `BodyContract`
12
+ - `BodyContract.getHeaders()` provides headers (like `Content-Type`)
13
+ - `BodyContract.getContent()` provides the serialized body
14
+
15
+ If you call `setBody(...)` without providing a body factory, the body is not sent.
16
+
17
+ ## JSON Bodies
18
+
19
+ Use `JsonBodyFactory` to send JSON and set `Content-Type: application/json`:
20
+
21
+ ```typescript
22
+ import { BaseRequest, JsonBodyFactory, RequestMethodEnum } from '@blueprint-ts/core/requests'
23
+
24
+ class CreateExpenseRequest extends BaseRequest<boolean, GenericResponseErrorInterface, ExpenseResource, JsonResponse<ExpenseResource>, CreateExpensePayload> {
25
+ public method(): RequestMethodEnum {
26
+ return RequestMethodEnum.POST
27
+ }
28
+
29
+ public url(): string {
30
+ return '/api/v1/expenses'
31
+ }
32
+
33
+ public getResponse(): JsonResponse<ExpenseResource> {
34
+ return new JsonResponse<ExpenseResource>()
35
+ }
36
+
37
+ public override getRequestBodyFactory() {
38
+ return new JsonBodyFactory<CreateExpensePayload>()
39
+ }
40
+ }
41
+ ```
42
+
43
+ If you are using the Laravel integration, `JsonBaseRequest` already configures the JSON body factory for you.
44
+
45
+ ## FormData Bodies
46
+
47
+ Use `FormDataFactory` for multipart requests (uploads, mixed fields):
48
+
49
+ ```typescript
50
+ import { FormDataFactory } from '@blueprint-ts/core/requests'
51
+
52
+ public override getRequestBodyFactory() {
53
+ return new FormDataFactory<FormPayload>()
54
+ }
55
+ ```
56
+
57
+ ## Custom Body Factories
58
+
59
+ You can implement your own body factory by returning a `BodyContract` with custom headers and serialization logic.
@@ -0,0 +1,34 @@
1
+ # Responses
2
+
3
+ Requests return a response class that controls the `Accept` header and how the body is parsed.
4
+
5
+ ## JsonResponse
6
+
7
+ Use `JsonResponse<T>` for JSON APIs. It sets `Accept: application/json` and parses the body with `response.json()`:
8
+
9
+ ```typescript
10
+ import { JsonResponse } from '@blueprint-ts/core/requests'
11
+
12
+ // In your request generic parameters:
13
+ // JsonResponse<ExpenseIndexRequestResponseBody>
14
+ ```
15
+
16
+ ## PlainTextResponse
17
+
18
+ Use `PlainTextResponse` for endpoints that return plain text. It sets `Accept: text/plain` and parses the body with
19
+ `response.text()`:
20
+
21
+ ```typescript
22
+ import { PlainTextResponse } from '@blueprint-ts/core/requests'
23
+ ```
24
+
25
+ ## BlobResponse
26
+
27
+ Use `BlobResponse` for binary responses like files. It sets `Accept` to the provided MIME type (default
28
+ `application/octet-stream`) and parses the body with `response.blob()`:
29
+
30
+ ```typescript
31
+ import { BlobResponse } from '@blueprint-ts/core/requests'
32
+
33
+ const response = new BlobResponse('application/pdf')
34
+ ```