@blueprint-ts/core 1.0.0

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 (158) hide show
  1. package/.editorconfig +508 -0
  2. package/.eslintrc.cjs +15 -0
  3. package/.prettierrc.json +8 -0
  4. package/LICENSE +21 -0
  5. package/README.md +1 -0
  6. package/docker-compose.yaml +8 -0
  7. package/docs/.vitepress/config.ts +68 -0
  8. package/docs/.vitepress/theme/Layout.vue +14 -0
  9. package/docs/.vitepress/theme/components/VersionSelector.vue +64 -0
  10. package/docs/.vitepress/theme/index.js +13 -0
  11. package/docs/index.md +70 -0
  12. package/docs/services/laravel/pagination.md +54 -0
  13. package/docs/services/laravel/requests.md +62 -0
  14. package/docs/services/requests/index.md +74 -0
  15. package/docs/vue/forms.md +326 -0
  16. package/docs/vue/requests/route-model-binding.md +66 -0
  17. package/docs/vue/state.md +293 -0
  18. package/env.d.ts +1 -0
  19. package/eslint.config.js +15 -0
  20. package/examples/files/7z2404-x64.exe +0 -0
  21. package/examples/index.html +14 -0
  22. package/examples/js/app.js +8 -0
  23. package/examples/js/router.js +22 -0
  24. package/examples/js/view/App.vue +49 -0
  25. package/examples/js/view/layout/DemoPage.vue +28 -0
  26. package/examples/js/view/pagination/Pagination.vue +28 -0
  27. package/examples/js/view/pagination/components/errorPagination/ErrorPagination.vue +71 -0
  28. package/examples/js/view/pagination/components/errorPagination/GetProductsRequest.ts +54 -0
  29. package/examples/js/view/pagination/components/infiniteScrolling/GetProductsRequest.ts +50 -0
  30. package/examples/js/view/pagination/components/infiniteScrolling/InfiniteScrolling.vue +57 -0
  31. package/examples/js/view/pagination/components/tablePagination/GetProductsRequest.ts +50 -0
  32. package/examples/js/view/pagination/components/tablePagination/TablePagination.vue +63 -0
  33. package/examples/js/view/requests/Requests.vue +34 -0
  34. package/examples/js/view/requests/components/abortableRequest/AbortableRequest.vue +36 -0
  35. package/examples/js/view/requests/components/abortableRequest/GetProductsRequest.ts +25 -0
  36. package/examples/js/view/requests/components/fileDownloadRequest/DownloadFileRequest.ts +15 -0
  37. package/examples/js/view/requests/components/fileDownloadRequest/FileDownloadRequest.vue +44 -0
  38. package/examples/js/view/requests/components/getRequestWithDynamicParams/GetProductsRequest.ts +34 -0
  39. package/examples/js/view/requests/components/getRequestWithDynamicParams/GetRequestWithDynamicParams.vue +59 -0
  40. package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.ts +21 -0
  41. package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.vue +53 -0
  42. package/package.json +81 -0
  43. package/release-tool.json +7 -0
  44. package/src/helpers.ts +78 -0
  45. package/src/service/bulkRequests/BulkRequestEvent.enum.ts +4 -0
  46. package/src/service/bulkRequests/BulkRequestSender.ts +184 -0
  47. package/src/service/bulkRequests/BulkRequestWrapper.ts +49 -0
  48. package/src/service/bulkRequests/index.ts +6 -0
  49. package/src/service/laravel/pagination/contracts/PaginationParamsContract.ts +4 -0
  50. package/src/service/laravel/pagination/contracts/PaginationResponseBodyContract.ts +6 -0
  51. package/src/service/laravel/pagination/dataDrivers/RequestDriver.ts +32 -0
  52. package/src/service/laravel/pagination/index.ts +7 -0
  53. package/src/service/laravel/requests/JsonBaseRequest.ts +35 -0
  54. package/src/service/laravel/requests/PaginationJsonBaseRequest.ts +29 -0
  55. package/src/service/laravel/requests/index.ts +9 -0
  56. package/src/service/laravel/requests/responses/JsonResponse.ts +8 -0
  57. package/src/service/laravel/requests/responses/PaginationResponse.ts +16 -0
  58. package/src/service/pagination/InfiniteScroller.ts +21 -0
  59. package/src/service/pagination/Paginator.ts +149 -0
  60. package/src/service/pagination/contracts/PaginateableRequestContract.ts +13 -0
  61. package/src/service/pagination/contracts/PaginationDataDriverContract.ts +5 -0
  62. package/src/service/pagination/contracts/PaginationResponseContract.ts +7 -0
  63. package/src/service/pagination/contracts/PaginatorLoadDataOptions.ts +4 -0
  64. package/src/service/pagination/contracts/ViewDriverContract.ts +12 -0
  65. package/src/service/pagination/contracts/ViewDriverFactoryContract.ts +5 -0
  66. package/src/service/pagination/dataDrivers/ArrayDriver.ts +28 -0
  67. package/src/service/pagination/dtos/PaginationDataDto.ts +14 -0
  68. package/src/service/pagination/factories/VuePaginationDriverFactory.ts +9 -0
  69. package/src/service/pagination/frontendDrivers/VuePaginationDriver.ts +61 -0
  70. package/src/service/pagination/index.ts +16 -0
  71. package/src/service/persistenceDrivers/LocalStorageDriver.ts +22 -0
  72. package/src/service/persistenceDrivers/NonPersistentDriver.ts +12 -0
  73. package/src/service/persistenceDrivers/SessionStorageDriver.ts +22 -0
  74. package/src/service/persistenceDrivers/index.ts +8 -0
  75. package/src/service/persistenceDrivers/types/PersistenceDriver.ts +5 -0
  76. package/src/service/requests/BaseRequest.ts +197 -0
  77. package/src/service/requests/ErrorHandler.ts +64 -0
  78. package/src/service/requests/RequestEvents.enum.ts +3 -0
  79. package/src/service/requests/RequestMethod.enum.ts +8 -0
  80. package/src/service/requests/bodies/FormDataBody.ts +41 -0
  81. package/src/service/requests/bodies/JsonBody.ts +16 -0
  82. package/src/service/requests/contracts/AbortableRequestContract.ts +3 -0
  83. package/src/service/requests/contracts/BaseRequestContract.ts +36 -0
  84. package/src/service/requests/contracts/BodyContract.ts +7 -0
  85. package/src/service/requests/contracts/BodyFactoryContract.ts +5 -0
  86. package/src/service/requests/contracts/DriverConfigContract.ts +7 -0
  87. package/src/service/requests/contracts/HeadersContract.ts +5 -0
  88. package/src/service/requests/contracts/RequestDriverContract.ts +15 -0
  89. package/src/service/requests/contracts/RequestLoaderContract.ts +5 -0
  90. package/src/service/requests/contracts/RequestLoaderFactoryContract.ts +5 -0
  91. package/src/service/requests/contracts/ResponseContract.ts +7 -0
  92. package/src/service/requests/drivers/contracts/ResponseHandlerContract.ts +10 -0
  93. package/src/service/requests/drivers/fetch/FetchDriver.ts +115 -0
  94. package/src/service/requests/drivers/fetch/FetchResponse.ts +30 -0
  95. package/src/service/requests/exceptions/NoResponseReceivedException.ts +3 -0
  96. package/src/service/requests/exceptions/NotFoundException.ts +3 -0
  97. package/src/service/requests/exceptions/PageExpiredException.ts +3 -0
  98. package/src/service/requests/exceptions/ResponseBodyException.ts +15 -0
  99. package/src/service/requests/exceptions/ResponseException.ts +11 -0
  100. package/src/service/requests/exceptions/ServerErrorException.ts +3 -0
  101. package/src/service/requests/exceptions/UnauthorizedException.ts +3 -0
  102. package/src/service/requests/exceptions/ValidationException.ts +3 -0
  103. package/src/service/requests/exceptions/index.ts +19 -0
  104. package/src/service/requests/factories/FormDataFactory.ts +9 -0
  105. package/src/service/requests/factories/JsonBodyFactory.ts +9 -0
  106. package/src/service/requests/index.ts +50 -0
  107. package/src/service/requests/responses/BaseResponse.ts +41 -0
  108. package/src/service/requests/responses/BlobResponse.ts +19 -0
  109. package/src/service/requests/responses/JsonResponse.ts +15 -0
  110. package/src/service/requests/responses/PlainTextResponse.ts +15 -0
  111. package/src/service/support/DeferredPromise.ts +67 -0
  112. package/src/service/support/index.ts +3 -0
  113. package/src/vue/composables/useConfirmDialog.ts +59 -0
  114. package/src/vue/composables/useGlobalCheckbox.ts +145 -0
  115. package/src/vue/composables/useIsEmpty.ts +34 -0
  116. package/src/vue/composables/useIsOpen.ts +37 -0
  117. package/src/vue/composables/useIsOpenFromVar.ts +61 -0
  118. package/src/vue/composables/useModelWrapper.ts +24 -0
  119. package/src/vue/composables/useOnOpen.ts +34 -0
  120. package/src/vue/contracts/ModelValueOptions.ts +3 -0
  121. package/src/vue/contracts/ModelValueProps.ts +3 -0
  122. package/src/vue/forms/BaseForm.ts +1074 -0
  123. package/src/vue/forms/PropertyAwareArray.ts +78 -0
  124. package/src/vue/forms/index.ts +11 -0
  125. package/src/vue/forms/types/PersistedForm.ts +6 -0
  126. package/src/vue/forms/validation/ValidationMode.enum.ts +14 -0
  127. package/src/vue/forms/validation/index.ts +12 -0
  128. package/src/vue/forms/validation/rules/BaseRule.ts +7 -0
  129. package/src/vue/forms/validation/rules/ConfirmedRule.ts +39 -0
  130. package/src/vue/forms/validation/rules/MinRule.ts +61 -0
  131. package/src/vue/forms/validation/rules/RequiredRule.ts +19 -0
  132. package/src/vue/forms/validation/rules/UrlRule.ts +24 -0
  133. package/src/vue/forms/validation/types/BidirectionalRule.ts +11 -0
  134. package/src/vue/index.ts +14 -0
  135. package/src/vue/requests/factories/VueRequestLoaderFactory.ts +9 -0
  136. package/src/vue/requests/index.ts +5 -0
  137. package/src/vue/requests/loaders/VueRequestBatchLoader.ts +30 -0
  138. package/src/vue/requests/loaders/VueRequestLoader.ts +18 -0
  139. package/src/vue/router/routeModelBinding/RouteModelRequestResolver.ts +11 -0
  140. package/src/vue/router/routeModelBinding/defineRoute.ts +31 -0
  141. package/src/vue/router/routeModelBinding/index.ts +8 -0
  142. package/src/vue/router/routeModelBinding/installRouteInjection.ts +73 -0
  143. package/src/vue/router/routeModelBinding/types.ts +46 -0
  144. package/src/vue/state/State.ts +391 -0
  145. package/src/vue/state/index.ts +3 -0
  146. package/tests/service/helpers/mergeDeep.test.ts +53 -0
  147. package/tests/service/laravel/pagination/dataDrivers/RequestDriver.test.ts +84 -0
  148. package/tests/service/laravel/requests/JsonBaseRequest.test.ts +43 -0
  149. package/tests/service/laravel/requests/PaginationJsonBaseRequest.test.ts +58 -0
  150. package/tests/service/laravel/requests/responses/JsonResponse.test.ts +59 -0
  151. package/tests/service/laravel/requests/responses/PaginationResponse.test.ts +127 -0
  152. package/tests/service/pagination/dtos/PaginationDataDto.test.ts +35 -0
  153. package/tests/service/pagination/factories/VuePaginationDriverFactory.test.ts +32 -0
  154. package/tests/service/pagination/frontendDrivers/VuePaginationDriver.test.ts +66 -0
  155. package/tests/service/requests/ErrorHandler.test.ts +141 -0
  156. package/tsconfig.json +114 -0
  157. package/vite.config.ts +34 -0
  158. package/vitest.config.ts +14 -0
@@ -0,0 +1,4 @@
1
+ export interface PaginatorLoadDataOptions {
2
+ replace?: boolean
3
+ flush?: boolean
4
+ }
@@ -0,0 +1,12 @@
1
+ export interface ViewDriverContract<ResourceInterface> {
2
+ setData(data: ResourceInterface): void
3
+ setTotal(value: number): void
4
+ getData(): ResourceInterface
5
+ getCurrentPage(): number
6
+ setPage(value: number): void
7
+ setPageSize(value: number): void
8
+ getPageSize(): number
9
+ getLastPage(): number
10
+ getPages(): number[]
11
+ getTotal(): number
12
+ }
@@ -0,0 +1,5 @@
1
+ import { type ViewDriverContract } from './ViewDriverContract'
2
+
3
+ export interface ViewDriverFactoryContract {
4
+ make<ResourceInterface>(page: number, size: number): ViewDriverContract<ResourceInterface[]>
5
+ }
@@ -0,0 +1,28 @@
1
+ import { type PaginationDataDriverContract, PaginationDataDto } from '../index'
2
+
3
+ export class ArrayDriver<ResourceInterface> implements PaginationDataDriverContract<ResourceInterface[]> {
4
+ public constructor(protected data: ResourceInterface[]) {}
5
+
6
+ public get(page: number, size: number): Promise<PaginationDataDto<ResourceInterface[]>> {
7
+ return new Promise((resolve) => {
8
+ resolve(
9
+ new PaginationDataDto<ResourceInterface[]>(
10
+ this.data.slice(this.calculatedStart(page, size), this.calculatedEnd(page, size)),
11
+ this.data.length
12
+ )
13
+ )
14
+ })
15
+ }
16
+
17
+ public setData(data: ResourceInterface[]): void {
18
+ this.data = data
19
+ }
20
+
21
+ protected calculatedStart(currentPage: number, pageSize: number): number {
22
+ return (currentPage - 1) * pageSize
23
+ }
24
+
25
+ protected calculatedEnd(currentPage: number, pageSize: number): number {
26
+ return currentPage * pageSize
27
+ }
28
+ }
@@ -0,0 +1,14 @@
1
+ export class PaginationDataDto<ResourceInterface> {
2
+ public constructor(
3
+ protected data: ResourceInterface,
4
+ protected total: number
5
+ ) {}
6
+
7
+ public getData(): ResourceInterface {
8
+ return this.data
9
+ }
10
+
11
+ public getTotal(): number {
12
+ return this.total
13
+ }
14
+ }
@@ -0,0 +1,9 @@
1
+ import { VuePaginationDriver } from '../frontendDrivers/VuePaginationDriver'
2
+ import { type ViewDriverFactoryContract } from '../contracts/ViewDriverFactoryContract'
3
+ import { type ViewDriverContract } from '../contracts/ViewDriverContract'
4
+
5
+ export class VuePaginationDriverFactory implements ViewDriverFactoryContract {
6
+ public make<ResourceInterface>(pageNumber: number, pageSize: number): ViewDriverContract<ResourceInterface[]> {
7
+ return new VuePaginationDriver<ResourceInterface>(pageNumber, pageSize)
8
+ }
9
+ }
@@ -0,0 +1,61 @@
1
+ import { computed, ref, type Ref, type ComputedRef } from 'vue'
2
+ import { type ViewDriverContract } from '../contracts/ViewDriverContract'
3
+
4
+ export class VuePaginationDriver<ResourceInterface> implements ViewDriverContract<ResourceInterface[]> {
5
+ protected dataRef: Ref<ResourceInterface[]>
6
+ protected currentPageRef: Ref<number>
7
+ protected pageSizeRef: Ref<number>
8
+ protected totalRef: Ref<number>
9
+ protected totalPagesComputed: ComputedRef<number>
10
+ protected pagesComputed: ComputedRef<number[]>
11
+
12
+ public constructor(pageNumber: number, pageSize: number) {
13
+ this.dataRef = ref([]) as Ref<ResourceInterface[]>
14
+ this.currentPageRef = ref<number>(pageNumber)
15
+ this.pageSizeRef = ref<number>(pageSize)
16
+ this.totalRef = ref<number>(0)
17
+
18
+ this.totalPagesComputed = computed(() => Math.ceil(this.totalRef.value / this.pageSizeRef.value))
19
+ this.pagesComputed = computed(() => Array.from({ length: this.totalPagesComputed.value }, (_, i) => i + 1))
20
+ }
21
+
22
+ public setData(data: ResourceInterface[]): void {
23
+ this.dataRef.value = data
24
+ }
25
+
26
+ public setTotal(value: number): void {
27
+ this.totalRef.value = value
28
+ }
29
+
30
+ public getData(): ResourceInterface[] {
31
+ return this.dataRef.value
32
+ }
33
+
34
+ public getCurrentPage(): number {
35
+ return this.currentPageRef.value
36
+ }
37
+
38
+ public setPage(value: number): void {
39
+ this.currentPageRef.value = value
40
+ }
41
+
42
+ public setPageSize(value: number): void {
43
+ this.pageSizeRef.value = value
44
+ }
45
+
46
+ public getPageSize(): number {
47
+ return this.pageSizeRef.value
48
+ }
49
+
50
+ public getLastPage(): number {
51
+ return this.totalPagesComputed.value
52
+ }
53
+
54
+ public getPages(): number[] {
55
+ return this.pagesComputed.value
56
+ }
57
+
58
+ public getTotal(): number {
59
+ return this.totalRef.value
60
+ }
61
+ }
@@ -0,0 +1,16 @@
1
+ import { PaginationDataDto } from './dtos/PaginationDataDto'
2
+ import { VuePaginationDriver } from './frontendDrivers/VuePaginationDriver'
3
+ import { Paginator } from './Paginator'
4
+ import { InfiniteScroller } from './InfiniteScroller'
5
+ import { VuePaginationDriverFactory } from './factories/VuePaginationDriverFactory'
6
+ import { type PaginateableRequestContract } from './contracts/PaginateableRequestContract'
7
+ import { type PaginationResponseContract } from './contracts/PaginationResponseContract'
8
+ import { type PaginationDataDriverContract } from './contracts/PaginationDataDriverContract'
9
+ import { getDisplayablePages } from '../../helpers'
10
+ import { ArrayDriver } from './dataDrivers/ArrayDriver'
11
+ import { type ViewDriverContract } from './contracts/ViewDriverContract'
12
+ import { type ViewDriverFactoryContract } from './contracts/ViewDriverFactoryContract'
13
+
14
+ export { PaginationDataDto, VuePaginationDriver, Paginator, InfiniteScroller, VuePaginationDriverFactory, getDisplayablePages, ArrayDriver }
15
+
16
+ export type { PaginationDataDriverContract, PaginationResponseContract, PaginateableRequestContract, ViewDriverContract, ViewDriverFactoryContract }
@@ -0,0 +1,22 @@
1
+ import { type PersistenceDriver } from './types/PersistenceDriver'
2
+
3
+ export class LocalStorageDriver implements PersistenceDriver {
4
+ public constructor(protected suffix?: string) {}
5
+
6
+ private storageKey(key: string): string {
7
+ return this.suffix ? `state_${key}_${this.suffix}` : `state_${key}`
8
+ }
9
+
10
+ get<T>(key: string): T | null {
11
+ const data = localStorage.getItem(this.storageKey(key))
12
+
13
+ return data ? JSON.parse(data) : null
14
+ }
15
+ set<T>(key: string, state: T): void {
16
+ localStorage.setItem(this.storageKey(key), JSON.stringify(state))
17
+ }
18
+
19
+ remove(key: string): void {
20
+ localStorage.removeItem(this.storageKey(key))
21
+ }
22
+ }
@@ -0,0 +1,12 @@
1
+ import { type PersistenceDriver } from './types/PersistenceDriver'
2
+
3
+ /** Default driver: does not persist any data. */
4
+ export class NonPersistentDriver implements PersistenceDriver {
5
+ get(): null {
6
+ return null
7
+ }
8
+
9
+ set(): void {}
10
+
11
+ remove(): void {}
12
+ }
@@ -0,0 +1,22 @@
1
+ import { type PersistenceDriver } from './types/PersistenceDriver'
2
+
3
+ export class SessionStorageDriver implements PersistenceDriver {
4
+ public constructor(protected suffix?: string) {}
5
+
6
+ private storageKey(key: string): string {
7
+ return this.suffix ? `state_${key}_${this.suffix}` : `state_${key}`
8
+ }
9
+
10
+ get<T>(key: string): T | null {
11
+ const data = sessionStorage.getItem(this.storageKey(key))
12
+
13
+ return data ? JSON.parse(data) : null
14
+ }
15
+ set<T>(key: string, state: T): void {
16
+ sessionStorage.setItem(this.storageKey(key), JSON.stringify(state))
17
+ }
18
+
19
+ remove(key: string): void {
20
+ sessionStorage.removeItem(this.storageKey(key))
21
+ }
22
+ }
@@ -0,0 +1,8 @@
1
+ import { LocalStorageDriver } from './LocalStorageDriver'
2
+ import { NonPersistentDriver } from './NonPersistentDriver'
3
+ import { SessionStorageDriver } from './SessionStorageDriver'
4
+ import { type PersistenceDriver } from './types/PersistenceDriver'
5
+
6
+ export { LocalStorageDriver, NonPersistentDriver, SessionStorageDriver }
7
+
8
+ export type { PersistenceDriver }
@@ -0,0 +1,5 @@
1
+ export interface PersistenceDriver {
2
+ get<T>(key: string): T | null
3
+ set<T>(key: string, state: T): void
4
+ remove(key: string): void
5
+ }
@@ -0,0 +1,197 @@
1
+ import qs from 'qs'
2
+ import { ErrorHandler } from './ErrorHandler'
3
+ import { RequestEvents } from './RequestEvents.enum'
4
+ import { RequestMethodEnum } from './RequestMethod.enum'
5
+ import { BaseResponse } from './responses/BaseResponse'
6
+ import { ResponseException } from './exceptions/ResponseException'
7
+ import { type DriverConfigContract } from './contracts/DriverConfigContract'
8
+ import { type BodyFactoryContract } from './contracts/BodyFactoryContract'
9
+ import { type RequestLoaderContract } from './contracts/RequestLoaderContract'
10
+ import { type RequestDriverContract } from './contracts/RequestDriverContract'
11
+ import { type RequestLoaderFactoryContract } from './contracts/RequestLoaderFactoryContract'
12
+ import { type BaseRequestContract, type EventHandlerCallback } from './contracts/BaseRequestContract'
13
+ import { type HeadersContract } from './contracts/HeadersContract'
14
+ import { type ResponseHandlerContract } from './drivers/contracts/ResponseHandlerContract'
15
+ import { type ResponseContract } from './contracts/ResponseContract'
16
+ import { mergeDeep } from '../../helpers'
17
+ import { v4 as uuidv4 } from 'uuid'
18
+
19
+ export abstract class BaseRequest<
20
+ RequestLoaderLoadingType,
21
+ ResponseErrorBody,
22
+ ResponseBodyInterface = undefined,
23
+ ResponseClass extends ResponseContract<ResponseBodyInterface> = BaseResponse<ResponseBodyInterface>,
24
+ RequestBodyInterface = undefined,
25
+ RequestParamsInterface extends object = object
26
+ > implements BaseRequestContract<RequestLoaderLoadingType, RequestBodyInterface, ResponseClass, RequestParamsInterface> {
27
+ protected requestId: string = uuidv4()
28
+ protected params: RequestParamsInterface | undefined = undefined
29
+ protected requestBody: RequestBodyInterface | undefined = undefined
30
+ protected requestLoader: RequestLoaderContract<RequestLoaderLoadingType> | undefined = undefined
31
+ protected abortSignal: AbortSignal | undefined = undefined
32
+ /* @ts-expect-error Ignore generics */
33
+ protected events: { [key in RequestEvents]?: EventHandlerCallback[] } = {}
34
+
35
+ protected static defaultBaseUrl: string
36
+
37
+ protected static requestDriver: RequestDriverContract
38
+ protected static requestLoaderFactory: RequestLoaderFactoryContract<unknown>
39
+
40
+ public constructor() {
41
+ if (BaseRequest.requestLoaderFactory !== undefined) {
42
+ this.requestLoader = BaseRequest.requestLoaderFactory.make() as RequestLoaderContract<RequestLoaderLoadingType>
43
+ }
44
+ }
45
+
46
+ public static setRequestDriver(driver: RequestDriverContract) {
47
+ this.requestDriver = driver
48
+ }
49
+
50
+ public static setRequestLoaderFactory<T>(factory: RequestLoaderFactoryContract<T>): void {
51
+ this.requestLoaderFactory = factory
52
+ }
53
+
54
+ public static setDefaultBaseUrl(url: string) {
55
+ this.defaultBaseUrl = url
56
+ }
57
+
58
+ public setRequestLoader(loader: RequestLoaderContract<RequestLoaderLoadingType>): this {
59
+ this.requestLoader = loader
60
+
61
+ return this
62
+ }
63
+
64
+ public getRequestId(): string {
65
+ return this.requestId
66
+ }
67
+
68
+ public abstract method(): RequestMethodEnum
69
+
70
+ public abstract url(): URL | string
71
+
72
+ public setParams(params?: RequestParamsInterface): this {
73
+ this.params = params
74
+
75
+ return this
76
+ }
77
+
78
+ public withParams(params: RequestParamsInterface): this {
79
+ this.params = this.params === undefined ? params : (mergeDeep({}, this.params, params) as RequestParamsInterface)
80
+
81
+ return this
82
+ }
83
+
84
+ public getParams(): RequestParamsInterface | undefined {
85
+ return this.params
86
+ }
87
+
88
+ public setBody(requestBody: RequestBodyInterface): this {
89
+ this.requestBody = requestBody
90
+
91
+ return this
92
+ }
93
+
94
+ public getBody(): RequestBodyInterface | undefined {
95
+ return this.requestBody
96
+ }
97
+
98
+ public requestHeaders(): HeadersContract {
99
+ return {}
100
+ }
101
+
102
+ public buildUrl(): URL {
103
+ const url = this.params !== undefined && Object.keys(this.params).length === 0 ? this.url() : this.url() + '?' + qs.stringify(this.params)
104
+
105
+ return new URL(url, this.baseUrl() ?? BaseRequest.defaultBaseUrl)
106
+ }
107
+
108
+ public on<T>(event: RequestEvents, handler: EventHandlerCallback<T>): this {
109
+ if (!this.events[event]) {
110
+ this.events[event] = []
111
+ }
112
+
113
+ this.events[event].push(handler)
114
+
115
+ return this
116
+ }
117
+
118
+ protected dispatch<T>(event: RequestEvents, value: T) {
119
+ if (!this.events[event]) {
120
+ return
121
+ }
122
+
123
+ this.events[event].forEach((handler: EventHandlerCallback<T>) => handler(value))
124
+ }
125
+
126
+ public async send(): Promise<ResponseClass> {
127
+ this.dispatch<boolean>(RequestEvents.LOADING, true)
128
+
129
+ this.requestLoader?.setLoading(true)
130
+
131
+ const responseSkeleton = this.getResponse()
132
+
133
+ const requestBody = this.requestBody === undefined ? undefined : this.getRequestBodyFactory()?.make(this.requestBody)
134
+
135
+ return BaseRequest.requestDriver
136
+ .send(
137
+ this.buildUrl(),
138
+ this.method(),
139
+ {
140
+ Accept: responseSkeleton.getAcceptHeader(),
141
+ ...this.requestHeaders()
142
+ },
143
+ requestBody,
144
+ this.getConfig()
145
+ )
146
+ .then(async (responseHandler: ResponseHandlerContract) => {
147
+ await responseSkeleton.setResponse(responseHandler)
148
+
149
+ return responseSkeleton
150
+ })
151
+ .catch(async (error) => {
152
+ if (error instanceof ResponseException) {
153
+ const handler = new ErrorHandler<ResponseErrorBody>(error.getResponse())
154
+
155
+ await handler.handle()
156
+ }
157
+
158
+ console.error('HankIT-UI: Unknown error received.', error)
159
+
160
+ throw error
161
+ })
162
+ .finally(() => {
163
+ this.dispatch<boolean>(RequestEvents.LOADING, false)
164
+ this.requestLoader?.setLoading(false)
165
+ })
166
+ }
167
+
168
+ public isLoading(): RequestLoaderLoadingType {
169
+ if (!this.requestLoader) {
170
+ throw new Error('Request loader is not set.')
171
+ }
172
+
173
+ return this.requestLoader.isLoading()
174
+ }
175
+
176
+ public abstract getResponse(): ResponseClass
177
+
178
+ public getRequestBodyFactory(): BodyFactoryContract<RequestBodyInterface | undefined> | undefined {
179
+ return undefined
180
+ }
181
+
182
+ public setAbortSignal(signal: AbortSignal): this {
183
+ this.abortSignal = signal
184
+
185
+ return this
186
+ }
187
+
188
+ protected baseUrl(): undefined {
189
+ return undefined
190
+ }
191
+
192
+ protected getConfig(): DriverConfigContract | undefined {
193
+ return {
194
+ abortSignal: this.abortSignal
195
+ }
196
+ }
197
+ }
@@ -0,0 +1,64 @@
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
+
10
+ export type ErrorHandlerCallback = ((response: ResponseHandlerContract) => boolean | void) | undefined
11
+
12
+ export class ErrorHandler<ResponseErrorBody> {
13
+ protected body: ResponseErrorBody | undefined = undefined
14
+ protected static handler: ErrorHandlerCallback = undefined
15
+
16
+ public constructor(protected response: ResponseHandlerContract) {
17
+ // Check if there is a global error handler set
18
+ if (ErrorHandler.handler !== undefined) {
19
+ // If handler returns false, we don't process the error further
20
+ if (ErrorHandler.handler(response) === false) {
21
+ console.debug('Skipping further error handling due to global handler returning false.')
22
+ return
23
+ }
24
+ }
25
+ }
26
+
27
+ public async handle() {
28
+ this.body = await this.response.json<ResponseErrorBody>()
29
+
30
+ if (this.body === undefined) {
31
+ throw new NoResponseReceivedException(this.response)
32
+ }
33
+
34
+ this.handleResponseError(this.response, this.body)
35
+ }
36
+
37
+ public static registerHandler(callback: ErrorHandlerCallback) {
38
+ ErrorHandler.handler = callback
39
+ }
40
+
41
+ protected handleResponseError(response: ResponseHandlerContract, body: ResponseErrorBody) {
42
+ if (response.getStatusCode() === 401) {
43
+ throw new UnauthorizedException<ResponseErrorBody>(response, body)
44
+ }
45
+
46
+ if (response.getStatusCode() === 404) {
47
+ throw new NotFoundException<ResponseErrorBody>(response, body)
48
+ }
49
+
50
+ if (response.getStatusCode() === 419) {
51
+ throw new PageExpiredException<ResponseErrorBody>(response, body)
52
+ }
53
+
54
+ if (response.getStatusCode() === 422) {
55
+ throw new ValidationException<ResponseErrorBody>(response, body)
56
+ }
57
+
58
+ if (response.getStatusCode() === 500) {
59
+ throw new ServerErrorException<ResponseErrorBody>(response, body)
60
+ }
61
+
62
+ throw new ResponseException(response)
63
+ }
64
+ }
@@ -0,0 +1,3 @@
1
+ export enum RequestEvents {
2
+ LOADING = 'loading'
3
+ }
@@ -0,0 +1,8 @@
1
+ export enum RequestMethodEnum {
2
+ GET = 'GET',
3
+ HEAD = 'HEAD',
4
+ PATCH = 'PATCH',
5
+ POST = 'POST',
6
+ PUT = 'PUT',
7
+ DELETE = 'DELETE'
8
+ }
@@ -0,0 +1,41 @@
1
+ import { type BodyContract } from '../contracts/BodyContract'
2
+ import { type HeadersContract } from '../contracts/HeadersContract'
3
+ import { isObject } from '../../../helpers'
4
+
5
+ export class FormDataBody<RequestBody> implements BodyContract {
6
+ protected data: FormData
7
+
8
+ public constructor(data: RequestBody) {
9
+ this.data = this.toFormData(data)
10
+ }
11
+
12
+ public getContent(): FormData {
13
+ return this.data
14
+ }
15
+
16
+ public getHeaders(): HeadersContract {
17
+ return {}
18
+ }
19
+
20
+ protected toFormData(data: RequestBody, form: FormData = new FormData(), namespace: string | null = null): FormData {
21
+ for (const property in data) {
22
+ if (Object.prototype.hasOwnProperty.call(data, property)) {
23
+ const formKey = namespace ? namespace + '[' + property + ']' : property
24
+
25
+ // if the property is an object, but not a File, use recursivity.
26
+ if (data[property] instanceof Date) {
27
+ form.append(formKey, data[property].toISOString())
28
+ } else if (isObject(data[property]) && !(data[property] instanceof File)) {
29
+ // @ts-expect-error Problem with property of object
30
+ this.toFormData(data[property], form, formKey)
31
+ } else if (data[property] instanceof File || typeof data[property] === 'string') {
32
+ form.append(formKey, data[property])
33
+ } else {
34
+ throw new Error('Unexpected value')
35
+ }
36
+ }
37
+ }
38
+
39
+ return form
40
+ }
41
+ }
@@ -0,0 +1,16 @@
1
+ import { type BodyContract } from '../contracts/BodyContract'
2
+ import { type HeadersContract } from '../contracts/HeadersContract'
3
+
4
+ export class JsonBody<RequestBody> implements BodyContract {
5
+ public constructor(protected data: RequestBody) {}
6
+
7
+ public getHeaders(): HeadersContract {
8
+ return {
9
+ 'Content-Type': 'application/json'
10
+ }
11
+ }
12
+
13
+ public getContent(): string {
14
+ return JSON.stringify(this.data)
15
+ }
16
+ }
@@ -0,0 +1,3 @@
1
+ export interface AbortableRequestContract {
2
+ setSignal(): void
3
+ }
@@ -0,0 +1,36 @@
1
+ import { RequestMethodEnum } from '../RequestMethod.enum'
2
+ import { RequestEvents } from '../RequestEvents.enum'
3
+ import { type BodyFactoryContract } from './BodyFactoryContract'
4
+ import { type HeadersContract } from './HeadersContract'
5
+
6
+ export type EventHandlerCallback<T> = (value: T) => void
7
+
8
+ export interface BaseRequestContract<RequestLoaderLoadingType, RequestBodyInterface, ResponseClass, RequestParamsInterface extends object> {
9
+ method(): RequestMethodEnum
10
+
11
+ url(): URL | string
12
+
13
+ setParams(params?: RequestParamsInterface): this
14
+
15
+ withParams(params?: RequestParamsInterface): this
16
+
17
+ getParams(): RequestParamsInterface | undefined
18
+
19
+ setBody(requestBody: RequestBodyInterface | undefined): this
20
+
21
+ requestHeaders(): HeadersContract
22
+
23
+ buildUrl(): URL
24
+
25
+ on<T>(event: RequestEvents, handler: EventHandlerCallback<T>): this
26
+
27
+ send(): Promise<ResponseClass>
28
+
29
+ isLoading(): RequestLoaderLoadingType
30
+
31
+ getRequestBodyFactory(): BodyFactoryContract<RequestBodyInterface> | undefined
32
+
33
+ getResponse(): ResponseClass
34
+
35
+ setAbortSignal(signal: AbortSignal): this
36
+ }
@@ -0,0 +1,7 @@
1
+ import { type HeadersContract } from './HeadersContract'
2
+
3
+ export interface BodyContract {
4
+ getContent(): string | FormData
5
+
6
+ getHeaders(): HeadersContract
7
+ }
@@ -0,0 +1,5 @@
1
+ import { type BodyContract } from './BodyContract'
2
+
3
+ export interface BodyFactoryContract<RequestBodyInterface> {
4
+ make(body: RequestBodyInterface): BodyContract
5
+ }
@@ -0,0 +1,7 @@
1
+ import { type HeadersContract } from './HeadersContract'
2
+
3
+ export interface DriverConfigContract {
4
+ corsWithCredentials?: boolean | undefined
5
+ abortSignal?: AbortSignal | undefined
6
+ headers?: HeadersContract | undefined
7
+ }
@@ -0,0 +1,5 @@
1
+ export type HeaderValue = string | (() => string)
2
+
3
+ export interface HeadersContract {
4
+ [key: string]: HeaderValue
5
+ }
@@ -0,0 +1,15 @@
1
+ import { RequestMethodEnum } from '../RequestMethod.enum'
2
+ import { type HeadersContract } from './HeadersContract'
3
+ import { type BodyContract } from './BodyContract'
4
+ import { type ResponseHandlerContract } from '../drivers/contracts/ResponseHandlerContract'
5
+ import { type DriverConfigContract } from './DriverConfigContract'
6
+
7
+ export interface RequestDriverContract {
8
+ send(
9
+ url: URL | string,
10
+ method: RequestMethodEnum,
11
+ headers: HeadersContract,
12
+ body?: BodyContract,
13
+ requestConfig?: DriverConfigContract
14
+ ): Promise<ResponseHandlerContract>
15
+ }
@@ -0,0 +1,5 @@
1
+ export interface RequestLoaderContract<T> {
2
+ isLoading(): T
3
+
4
+ setLoading(value: boolean): void
5
+ }