@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
@@ -1,8 +1,8 @@
1
1
  import { type BaseRequestContract } from '../requests'
2
2
 
3
3
  export class BulkRequestWrapper<RequestLoaderLoadingType, RequestBodyInterface, ResponseClass, RequestParamsInterface extends object> {
4
- protected response: any = null
5
- protected error: any = null
4
+ protected response: ResponseClass | null = null
5
+ protected error: unknown | null = null
6
6
  protected sent: boolean = false
7
7
 
8
8
  public constructor(protected request: BaseRequestContract<RequestLoaderLoadingType, RequestBodyInterface, ResponseClass, RequestParamsInterface>) {}
@@ -27,11 +27,11 @@ export class BulkRequestWrapper<RequestLoaderLoadingType, RequestBodyInterface,
27
27
  return this.request.isLoading()
28
28
  }
29
29
 
30
- public getResponse() {
30
+ public getResponse(): ResponseClass | null {
31
31
  return this.response
32
32
  }
33
33
 
34
- public getError() {
34
+ public getError(): unknown | null {
35
35
  return this.error
36
36
  }
37
37
 
@@ -40,7 +40,7 @@ export class BulkRequestWrapper<RequestLoaderLoadingType, RequestBodyInterface,
40
40
  }
41
41
 
42
42
  public hasError(): boolean {
43
- return Boolean(this.getError())
43
+ return this.error !== null
44
44
  }
45
45
 
46
46
  public wasSent(): boolean {
@@ -0,0 +1,30 @@
1
+ import { PaginationDataDto } from '../../../pagination/dtos/PaginationDataDto'
2
+ import { PaginationResponse } from '../../requests/responses/PaginationResponse'
3
+ import { type PaginateableRequestContract } from '../../../pagination/contracts/PaginateableRequestContract'
4
+ import { type PaginationDataDriverContract } from '../../../pagination/contracts/PaginationDataDriverContract'
5
+
6
+ export class RequestDriver<
7
+ Resource,
8
+ TReq extends PaginateableRequestContract<unknown, PaginationResponse<Resource>, unknown, object> = PaginateableRequestContract<
9
+ unknown,
10
+ PaginationResponse<Resource>,
11
+ unknown,
12
+ object
13
+ >
14
+ > implements PaginationDataDriverContract<Resource> {
15
+ public constructor(protected request: TReq) {}
16
+
17
+ public get(pageNumber: number, pageSize: number): Promise<PaginationDataDto<Resource>> {
18
+ return this.request
19
+ .setPaginationParams(pageNumber, pageSize)
20
+ .send()
21
+ .then((response) => {
22
+ const paginationResponse = response as PaginationResponse<Resource>
23
+ return new PaginationDataDto<Resource>(paginationResponse.getData(), paginationResponse.getTotal())
24
+ })
25
+ }
26
+
27
+ public getRequest(): TReq {
28
+ return this.request
29
+ }
30
+ }
@@ -0,0 +1,6 @@
1
+ import { type PaginationResponseBodyContract } from './contracts/PaginationResponseBodyContract'
2
+ import { RequestDriver } from './dataDrivers/RequestDriver'
3
+
4
+ export type { PaginationResponseBodyContract }
5
+
6
+ export { RequestDriver }
@@ -0,0 +1,94 @@
1
+ import { PaginationDataDto } from './dtos/PaginationDataDto'
2
+ import { type BaseViewDriverContract } from './contracts/BaseViewDriverContract'
3
+ import { type PaginatorLoadDataOptions } from './contracts/PaginatorLoadDataOptions'
4
+
5
+ export abstract class BasePaginator<ResourceInterface, ViewDriver extends BaseViewDriverContract<ResourceInterface[]>> {
6
+ protected initialized: boolean = false
7
+
8
+ protected abstract viewDriver: ViewDriver
9
+
10
+ public constructor(protected dataDriver: unknown) {}
11
+
12
+ public isInitialized(): boolean {
13
+ return this.initialized
14
+ }
15
+
16
+ public flush(): void {
17
+ this.viewDriver.setData([])
18
+ }
19
+
20
+ public getPageData(): ResourceInterface[] {
21
+ return this.viewDriver.getData()
22
+ }
23
+
24
+ public updateRows(
25
+ predicate: (row: ResourceInterface, index: number, data: ResourceInterface[]) => boolean,
26
+ updater: (row: ResourceInterface, index: number, data: ResourceInterface[]) => ResourceInterface | void
27
+ ): number {
28
+ const data = this.viewDriver.getData()
29
+ let updated = 0
30
+
31
+ for (let i = 0; i < data.length; i++) {
32
+ const row = data[i]
33
+
34
+ if (row === undefined) {
35
+ continue
36
+ }
37
+
38
+ if (!predicate(row, i, data)) {
39
+ continue
40
+ }
41
+
42
+ const result = updater(row, i, data)
43
+
44
+ if (result !== undefined) {
45
+ data[i] = result as ResourceInterface
46
+ }
47
+
48
+ updated++
49
+ }
50
+
51
+ if (updated > 0) {
52
+ this.viewDriver.setData(data)
53
+ }
54
+
55
+ return updated
56
+ }
57
+
58
+ public removeRows(
59
+ predicate: (row: ResourceInterface, index: number, data: ResourceInterface[]) => boolean,
60
+ options?: { adjustTotal?: boolean }
61
+ ): number {
62
+ const data = this.viewDriver.getData()
63
+ const next = data.filter((row, index) => !predicate(row, index, data))
64
+ const removed = data.length - next.length
65
+
66
+ if (removed > 0) {
67
+ this.viewDriver.setData(next)
68
+
69
+ if (options?.adjustTotal ?? true) {
70
+ this.viewDriver.setTotal(Math.max(0, this.viewDriver.getTotal() - removed))
71
+ }
72
+ }
73
+
74
+ return removed
75
+ }
76
+
77
+ public getTotal(): number {
78
+ return this.viewDriver.getTotal()
79
+ }
80
+
81
+ protected passDataToViewDriver(dto: PaginationDataDto<ResourceInterface[]>, options?: PaginatorLoadDataOptions): void {
82
+ if (options?.flush) {
83
+ this.flush()
84
+ }
85
+
86
+ this.viewDriver.setData(dto.getData())
87
+ this.viewDriver.setTotal(dto.getTotal())
88
+ this.initialized = true
89
+ }
90
+
91
+ protected handleStaleResponse(): PaginationDataDto<ResourceInterface[]> {
92
+ return new PaginationDataDto<ResourceInterface[]>(this.viewDriver.getData(), this.viewDriver.getTotal())
93
+ }
94
+ }
@@ -17,5 +17,6 @@ export class InfiniteScroller<ResourceInterface> extends PageAwarePaginator<Reso
17
17
  }
18
18
 
19
19
  this.viewDriver.setTotal(dto.getTotal())
20
+ this.initialized = true
20
21
  }
21
22
  }
@@ -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
@@ -40,28 +41,18 @@ export class PageAwarePaginator<ResourceInterface> extends BasePaginator<Resourc
40
41
  return this.dataDriver
41
42
  }
42
43
 
43
- public init(pageNumber: number, pageSize: number): Promise<PaginationDataDto<ResourceInterface[]>> {
44
- this.initialized = true
45
-
46
- if (pageNumber && pageSize) {
47
- return this.loadData(pageNumber, pageSize)
48
- }
49
-
50
- return this.loadData(this.getCurrentPage(), this.getPageSize())
51
- }
52
-
53
- public refresh(pageNumber?: number, options?: PaginatorLoadDataOptions): Promise<PaginationDataDto<ResourceInterface[]>> {
44
+ public load(pageNumber?: number, options?: PaginatorLoadDataOptions): Promise<PaginationDataDto<ResourceInterface[]>> {
54
45
  if (pageNumber !== undefined) {
55
- return this.setPage(pageNumber, options)
46
+ this.setPageNumber(pageNumber)
56
47
  }
57
48
 
58
49
  return this.loadData(this.getCurrentPage(), this.getPageSize(), options)
59
50
  }
60
51
 
61
- public setPage(pageNumber: number, options?: PaginatorLoadDataOptions): Promise<PaginationDataDto<ResourceInterface[]>> {
52
+ public setPageNumber(pageNumber: number): this {
62
53
  this.viewDriver.setPage(pageNumber)
63
54
 
64
- return this.loadData(this.viewDriver.getCurrentPage(), this.viewDriver.getPageSize(), options)
55
+ return this
65
56
  }
66
57
 
67
58
  public getLastPage(): number {
@@ -69,19 +60,27 @@ export class PageAwarePaginator<ResourceInterface> extends BasePaginator<Resourc
69
60
  }
70
61
 
71
62
  public toNextPage(): Promise<PaginationDataDto<ResourceInterface[]>> {
72
- return this.setPage(this.getCurrentPage() + 1)
63
+ this.setPageNumber(this.getCurrentPage() + 1)
64
+
65
+ return this.load()
73
66
  }
74
67
 
75
68
  public toFirstPage(): Promise<PaginationDataDto<ResourceInterface[]>> {
76
- return this.setPage(1)
69
+ this.setPageNumber(1)
70
+
71
+ return this.load()
77
72
  }
78
73
 
79
74
  public toLastPage(): Promise<PaginationDataDto<ResourceInterface[]>> {
80
- return this.setPage(this.viewDriver.getLastPage())
75
+ this.setPageNumber(this.viewDriver.getLastPage())
76
+
77
+ return this.load()
81
78
  }
82
79
 
83
80
  public toPreviousPage(): Promise<PaginationDataDto<ResourceInterface[]>> {
84
- return this.setPage(this.getCurrentPage() - 1)
81
+ this.setPageNumber(this.getCurrentPage() - 1)
82
+
83
+ return this.load()
85
84
  }
86
85
 
87
86
  public getCurrentPage(): number {
@@ -100,14 +99,14 @@ export class PageAwarePaginator<ResourceInterface> extends BasePaginator<Resourc
100
99
  return this.viewDriver.getPageSize()
101
100
  }
102
101
 
103
- public setPageSize(pageSize: number): Promise<PaginationDataDto<ResourceInterface[]>> {
102
+ public setPageSize(pageSize: number): this {
104
103
  this.viewDriver.setPageSize(pageSize)
105
104
 
106
105
  if (this.getCurrentPage() * pageSize > this.getTotal()) {
107
- return this.setPage(1)
106
+ this.setPageNumber(1)
108
107
  }
109
108
 
110
- return this.loadData(this.viewDriver.getCurrentPage(), this.viewDriver.getPageSize())
109
+ return this
111
110
  }
112
111
 
113
112
  public getPages(): number[] {
@@ -115,10 +114,19 @@ export class PageAwarePaginator<ResourceInterface> extends BasePaginator<Resourc
115
114
  }
116
115
 
117
116
  protected loadData(pageNumber: number, pageSize: number, options?: PaginatorLoadDataOptions): Promise<PaginationDataDto<ResourceInterface[]>> {
118
- return this.dataDriver.get(pageNumber, pageSize).then((value: PaginationDataDto<ResourceInterface[]>) => {
119
- this.passDataToViewDriver(value, options)
120
-
121
- return value
122
- })
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
+ })
123
131
  }
124
132
  }
@@ -40,14 +40,7 @@ export class StatePaginator<ResourceInterface> extends BasePaginator<ResourceInt
40
40
  return this.dataDriver
41
41
  }
42
42
 
43
- public init(): Promise<StatePaginationDataDto<ResourceInterface[]>> {
44
- this.initialized = true
45
- this.currentState = null
46
-
47
- return this.loadData()
48
- }
49
-
50
- public refresh(options?: PaginatorLoadDataOptions): Promise<StatePaginationDataDto<ResourceInterface[]>> {
43
+ public load(options?: PaginatorLoadDataOptions): Promise<StatePaginationDataDto<ResourceInterface[]>> {
51
44
  this.currentState = null
52
45
 
53
46
  return this.loadData({ ...options, flush: true })
@@ -88,5 +81,6 @@ export class StatePaginator<ResourceInterface> extends BasePaginator<ResourceInt
88
81
  }
89
82
 
90
83
  this.viewDriver.setTotal(dto.getTotal())
84
+ this.initialized = true
91
85
  }
92
86
  }
@@ -2,7 +2,6 @@ import { PaginationDataDto } from './dtos/PaginationDataDto'
2
2
  import { StatePaginationDataDto } from './dtos/StatePaginationDataDto'
3
3
  import { VuePaginationDriver } from './frontendDrivers/VuePaginationDriver'
4
4
  import { VueBaseViewDriver } from './frontendDrivers/VueBaseViewDriver'
5
- import { Paginator } from './Paginator'
6
5
  import { BasePaginator } from './BasePaginator'
7
6
  import { PageAwarePaginator } from './PageAwarePaginator'
8
7
  import { StatePaginator } from './StatePaginator'
@@ -13,7 +12,7 @@ import { type PaginateableRequestContract } from './contracts/PaginateableReques
13
12
  import { type PaginationResponseContract } from './contracts/PaginationResponseContract'
14
13
  import { type PaginationDataDriverContract } from './contracts/PaginationDataDriverContract'
15
14
  import { type StatePaginationDataDriverContract } from './contracts/StatePaginationDataDriverContract'
16
- import { getDisplayablePages } from '../../helpers'
15
+ import { getDisplayablePages } from '../support/helpers'
17
16
  import { ArrayDriver } from './dataDrivers/ArrayDriver'
18
17
  import { type BaseViewDriverContract } from './contracts/BaseViewDriverContract'
19
18
  import { type ViewDriverContract } from './contracts/ViewDriverContract'
@@ -25,7 +24,6 @@ export {
25
24
  StatePaginationDataDto,
26
25
  VuePaginationDriver,
27
26
  VueBaseViewDriver,
28
- Paginator,
29
27
  BasePaginator,
30
28
  PageAwarePaginator,
31
29
  StatePaginator,
@@ -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,7 +14,9 @@ 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'
16
- import { mergeDeep } from '../../helpers'
17
+ import { type RequestConcurrencyOptions } from './types/RequestConcurrencyOptions'
18
+ import { RequestConcurrencyMode } from './RequestConcurrencyMode.enum'
19
+ import { mergeDeep } from '../support/helpers'
17
20
  import { v4 as uuidv4 } from 'uuid'
18
21
 
19
22
  export abstract class BaseRequest<
@@ -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,24 +176,42 @@ 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
 
155
199
  await handler.handle()
156
200
  }
157
201
 
158
- console.error('HankIT-UI: Unknown error received.', error)
202
+ console.error('@blueprint-ts/core: Unknown error received.', error)
159
203
 
160
204
  throw error
161
205
  })
162
206
  .finally(() => {
163
- this.dispatch<boolean>(RequestEvents.LOADING, false)
164
- this.requestLoader?.setLoading(false)
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
  }
@@ -0,0 +1,144 @@
1
+ import { PageExpiredException } from './exceptions/PageExpiredException'
2
+ import { NotFoundException } from './exceptions/NotFoundException'
3
+ import { UnauthorizedException } from './exceptions/UnauthorizedException'
4
+ import { ValidationException } from './exceptions/ValidationException'
5
+ import { ResponseException } from './exceptions/ResponseException'
6
+ import { NoResponseReceivedException } from './exceptions/NoResponseReceivedException'
7
+ import { ServerErrorException } from './exceptions/ServerErrorException'
8
+ import { type ResponseHandlerContract } from './drivers/contracts/ResponseHandlerContract'
9
+ import { ForbiddenException } from './exceptions/ForbiddenException'
10
+ import { TooManyRequestsException } from './exceptions/TooManyRequestsException'
11
+ import { LockedException } from './exceptions/LockedException'
12
+ import { NotImplementedException } from './exceptions/NotImplementedException'
13
+ import { ServiceUnavailableException } from './exceptions/ServiceUnavailableException'
14
+ import { MethodNotAllowedException } from './exceptions/MethodNotAllowedException'
15
+ import { RequestTimeoutException } from './exceptions/RequestTimeoutException'
16
+ import { ConflictException } from './exceptions/ConflictException'
17
+ import { GoneException } from './exceptions/GoneException'
18
+ import { PreconditionFailedException } from './exceptions/PreconditionFailedException'
19
+ import { PayloadTooLargeException } from './exceptions/PayloadTooLargeException'
20
+ import { UnsupportedMediaTypeException } from './exceptions/UnsupportedMediaTypeException'
21
+ import { BadGatewayException } from './exceptions/BadGatewayException'
22
+ import { GatewayTimeoutException } from './exceptions/GatewayTimeoutException'
23
+ import { BadRequestException } from './exceptions/BadRequestException'
24
+ import { InvalidJsonException } from './exceptions/InvalidJsonException'
25
+
26
+ export type ErrorHandlerCallback = ((response: ResponseHandlerContract) => boolean | void) | undefined
27
+
28
+ export class ErrorHandler<ResponseErrorBody> {
29
+ protected body: ResponseErrorBody | undefined = undefined
30
+ protected static handler: ErrorHandlerCallback = undefined
31
+
32
+ public constructor(protected response: ResponseHandlerContract) {}
33
+
34
+ public async handle() {
35
+ // Check if there is a global error handler set
36
+ if (ErrorHandler.handler !== undefined) {
37
+ // If handler returns false, we don't process the error further
38
+ if (ErrorHandler.handler(this.response) === false) {
39
+ console.debug('Skipping further error handling due to global handler returning false.')
40
+ return
41
+ }
42
+ }
43
+
44
+ try {
45
+ this.body = await this.response.json<ResponseErrorBody>()
46
+ } catch (error) {
47
+ throw new InvalidJsonException(this.response, error)
48
+ }
49
+
50
+ if (this.body === undefined) {
51
+ throw new NoResponseReceivedException(this.response)
52
+ }
53
+
54
+ this.handleResponseError(this.response, this.body)
55
+ }
56
+
57
+ public static registerHandler(callback: ErrorHandlerCallback) {
58
+ ErrorHandler.handler = callback
59
+ }
60
+
61
+ protected handleResponseError(response: ResponseHandlerContract, body: ResponseErrorBody) {
62
+ if (response.getStatusCode() === 400) {
63
+ throw new BadRequestException<ResponseErrorBody>(response, body)
64
+ }
65
+
66
+ if (response.getStatusCode() === 401) {
67
+ throw new UnauthorizedException<ResponseErrorBody>(response, body)
68
+ }
69
+
70
+ if (response.getStatusCode() === 403) {
71
+ throw new ForbiddenException<ResponseErrorBody>(response, body)
72
+ }
73
+
74
+ if (response.getStatusCode() === 404) {
75
+ throw new NotFoundException<ResponseErrorBody>(response, body)
76
+ }
77
+
78
+ if (response.getStatusCode() === 405) {
79
+ throw new MethodNotAllowedException<ResponseErrorBody>(response, body)
80
+ }
81
+
82
+ if (response.getStatusCode() === 408) {
83
+ throw new RequestTimeoutException<ResponseErrorBody>(response, body)
84
+ }
85
+
86
+ if (response.getStatusCode() === 409) {
87
+ throw new ConflictException<ResponseErrorBody>(response, body)
88
+ }
89
+
90
+ if (response.getStatusCode() === 410) {
91
+ throw new GoneException<ResponseErrorBody>(response, body)
92
+ }
93
+
94
+ if (response.getStatusCode() === 412) {
95
+ throw new PreconditionFailedException<ResponseErrorBody>(response, body)
96
+ }
97
+
98
+ if (response.getStatusCode() === 413) {
99
+ throw new PayloadTooLargeException<ResponseErrorBody>(response, body)
100
+ }
101
+
102
+ if (response.getStatusCode() === 415) {
103
+ throw new UnsupportedMediaTypeException<ResponseErrorBody>(response, body)
104
+ }
105
+
106
+ if (response.getStatusCode() === 419) {
107
+ throw new PageExpiredException<ResponseErrorBody>(response, body)
108
+ }
109
+
110
+ if (response.getStatusCode() === 422) {
111
+ throw new ValidationException<ResponseErrorBody>(response, body)
112
+ }
113
+
114
+ if (response.getStatusCode() === 423) {
115
+ throw new LockedException<ResponseErrorBody>(response, body)
116
+ }
117
+
118
+ if (response.getStatusCode() === 429) {
119
+ throw new TooManyRequestsException<ResponseErrorBody>(response, body)
120
+ }
121
+
122
+ if (response.getStatusCode() === 500) {
123
+ throw new ServerErrorException<ResponseErrorBody>(response, body)
124
+ }
125
+
126
+ if (response.getStatusCode() === 501) {
127
+ throw new NotImplementedException<ResponseErrorBody>(response, body)
128
+ }
129
+
130
+ if (response.getStatusCode() === 502) {
131
+ throw new BadGatewayException<ResponseErrorBody>(response, body)
132
+ }
133
+
134
+ if (response.getStatusCode() === 503) {
135
+ throw new ServiceUnavailableException<ResponseErrorBody>(response, body)
136
+ }
137
+
138
+ if (response.getStatusCode() === 504) {
139
+ throw new GatewayTimeoutException<ResponseErrorBody>(response, body)
140
+ }
141
+
142
+ throw new ResponseException(response)
143
+ }
144
+ }
@@ -0,0 +1,6 @@
1
+ export enum RequestConcurrencyMode {
2
+ ALLOW = 'allow',
3
+ REPLACE = 'replace',
4
+ LATEST = 'latest',
5
+ REPLACE_LATEST = 'replace-latest'
6
+ }