@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.
- package/.editorconfig +508 -0
- package/.eslintrc.cjs +15 -0
- package/.prettierrc.json +8 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/docker-compose.yaml +8 -0
- package/docs/.vitepress/config.ts +68 -0
- package/docs/.vitepress/theme/Layout.vue +14 -0
- package/docs/.vitepress/theme/components/VersionSelector.vue +64 -0
- package/docs/.vitepress/theme/index.js +13 -0
- package/docs/index.md +70 -0
- package/docs/services/laravel/pagination.md +54 -0
- package/docs/services/laravel/requests.md +62 -0
- package/docs/services/requests/index.md +74 -0
- package/docs/vue/forms.md +326 -0
- package/docs/vue/requests/route-model-binding.md +66 -0
- package/docs/vue/state.md +293 -0
- package/env.d.ts +1 -0
- package/eslint.config.js +15 -0
- package/examples/files/7z2404-x64.exe +0 -0
- package/examples/index.html +14 -0
- package/examples/js/app.js +8 -0
- package/examples/js/router.js +22 -0
- package/examples/js/view/App.vue +49 -0
- package/examples/js/view/layout/DemoPage.vue +28 -0
- package/examples/js/view/pagination/Pagination.vue +28 -0
- package/examples/js/view/pagination/components/errorPagination/ErrorPagination.vue +71 -0
- package/examples/js/view/pagination/components/errorPagination/GetProductsRequest.ts +54 -0
- package/examples/js/view/pagination/components/infiniteScrolling/GetProductsRequest.ts +50 -0
- package/examples/js/view/pagination/components/infiniteScrolling/InfiniteScrolling.vue +57 -0
- package/examples/js/view/pagination/components/tablePagination/GetProductsRequest.ts +50 -0
- package/examples/js/view/pagination/components/tablePagination/TablePagination.vue +63 -0
- package/examples/js/view/requests/Requests.vue +34 -0
- package/examples/js/view/requests/components/abortableRequest/AbortableRequest.vue +36 -0
- package/examples/js/view/requests/components/abortableRequest/GetProductsRequest.ts +25 -0
- package/examples/js/view/requests/components/fileDownloadRequest/DownloadFileRequest.ts +15 -0
- package/examples/js/view/requests/components/fileDownloadRequest/FileDownloadRequest.vue +44 -0
- package/examples/js/view/requests/components/getRequestWithDynamicParams/GetProductsRequest.ts +34 -0
- package/examples/js/view/requests/components/getRequestWithDynamicParams/GetRequestWithDynamicParams.vue +59 -0
- package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.ts +21 -0
- package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.vue +53 -0
- package/package.json +81 -0
- package/release-tool.json +7 -0
- package/src/helpers.ts +78 -0
- package/src/service/bulkRequests/BulkRequestEvent.enum.ts +4 -0
- package/src/service/bulkRequests/BulkRequestSender.ts +184 -0
- package/src/service/bulkRequests/BulkRequestWrapper.ts +49 -0
- package/src/service/bulkRequests/index.ts +6 -0
- package/src/service/laravel/pagination/contracts/PaginationParamsContract.ts +4 -0
- package/src/service/laravel/pagination/contracts/PaginationResponseBodyContract.ts +6 -0
- package/src/service/laravel/pagination/dataDrivers/RequestDriver.ts +32 -0
- package/src/service/laravel/pagination/index.ts +7 -0
- package/src/service/laravel/requests/JsonBaseRequest.ts +35 -0
- package/src/service/laravel/requests/PaginationJsonBaseRequest.ts +29 -0
- package/src/service/laravel/requests/index.ts +9 -0
- package/src/service/laravel/requests/responses/JsonResponse.ts +8 -0
- package/src/service/laravel/requests/responses/PaginationResponse.ts +16 -0
- package/src/service/pagination/InfiniteScroller.ts +21 -0
- package/src/service/pagination/Paginator.ts +149 -0
- package/src/service/pagination/contracts/PaginateableRequestContract.ts +13 -0
- package/src/service/pagination/contracts/PaginationDataDriverContract.ts +5 -0
- package/src/service/pagination/contracts/PaginationResponseContract.ts +7 -0
- package/src/service/pagination/contracts/PaginatorLoadDataOptions.ts +4 -0
- package/src/service/pagination/contracts/ViewDriverContract.ts +12 -0
- package/src/service/pagination/contracts/ViewDriverFactoryContract.ts +5 -0
- package/src/service/pagination/dataDrivers/ArrayDriver.ts +28 -0
- package/src/service/pagination/dtos/PaginationDataDto.ts +14 -0
- package/src/service/pagination/factories/VuePaginationDriverFactory.ts +9 -0
- package/src/service/pagination/frontendDrivers/VuePaginationDriver.ts +61 -0
- package/src/service/pagination/index.ts +16 -0
- package/src/service/persistenceDrivers/LocalStorageDriver.ts +22 -0
- package/src/service/persistenceDrivers/NonPersistentDriver.ts +12 -0
- package/src/service/persistenceDrivers/SessionStorageDriver.ts +22 -0
- package/src/service/persistenceDrivers/index.ts +8 -0
- package/src/service/persistenceDrivers/types/PersistenceDriver.ts +5 -0
- package/src/service/requests/BaseRequest.ts +197 -0
- package/src/service/requests/ErrorHandler.ts +64 -0
- package/src/service/requests/RequestEvents.enum.ts +3 -0
- package/src/service/requests/RequestMethod.enum.ts +8 -0
- package/src/service/requests/bodies/FormDataBody.ts +41 -0
- package/src/service/requests/bodies/JsonBody.ts +16 -0
- package/src/service/requests/contracts/AbortableRequestContract.ts +3 -0
- package/src/service/requests/contracts/BaseRequestContract.ts +36 -0
- package/src/service/requests/contracts/BodyContract.ts +7 -0
- package/src/service/requests/contracts/BodyFactoryContract.ts +5 -0
- package/src/service/requests/contracts/DriverConfigContract.ts +7 -0
- package/src/service/requests/contracts/HeadersContract.ts +5 -0
- package/src/service/requests/contracts/RequestDriverContract.ts +15 -0
- package/src/service/requests/contracts/RequestLoaderContract.ts +5 -0
- package/src/service/requests/contracts/RequestLoaderFactoryContract.ts +5 -0
- package/src/service/requests/contracts/ResponseContract.ts +7 -0
- package/src/service/requests/drivers/contracts/ResponseHandlerContract.ts +10 -0
- package/src/service/requests/drivers/fetch/FetchDriver.ts +115 -0
- package/src/service/requests/drivers/fetch/FetchResponse.ts +30 -0
- package/src/service/requests/exceptions/NoResponseReceivedException.ts +3 -0
- package/src/service/requests/exceptions/NotFoundException.ts +3 -0
- package/src/service/requests/exceptions/PageExpiredException.ts +3 -0
- package/src/service/requests/exceptions/ResponseBodyException.ts +15 -0
- package/src/service/requests/exceptions/ResponseException.ts +11 -0
- package/src/service/requests/exceptions/ServerErrorException.ts +3 -0
- package/src/service/requests/exceptions/UnauthorizedException.ts +3 -0
- package/src/service/requests/exceptions/ValidationException.ts +3 -0
- package/src/service/requests/exceptions/index.ts +19 -0
- package/src/service/requests/factories/FormDataFactory.ts +9 -0
- package/src/service/requests/factories/JsonBodyFactory.ts +9 -0
- package/src/service/requests/index.ts +50 -0
- package/src/service/requests/responses/BaseResponse.ts +41 -0
- package/src/service/requests/responses/BlobResponse.ts +19 -0
- package/src/service/requests/responses/JsonResponse.ts +15 -0
- package/src/service/requests/responses/PlainTextResponse.ts +15 -0
- package/src/service/support/DeferredPromise.ts +67 -0
- package/src/service/support/index.ts +3 -0
- package/src/vue/composables/useConfirmDialog.ts +59 -0
- package/src/vue/composables/useGlobalCheckbox.ts +145 -0
- package/src/vue/composables/useIsEmpty.ts +34 -0
- package/src/vue/composables/useIsOpen.ts +37 -0
- package/src/vue/composables/useIsOpenFromVar.ts +61 -0
- package/src/vue/composables/useModelWrapper.ts +24 -0
- package/src/vue/composables/useOnOpen.ts +34 -0
- package/src/vue/contracts/ModelValueOptions.ts +3 -0
- package/src/vue/contracts/ModelValueProps.ts +3 -0
- package/src/vue/forms/BaseForm.ts +1074 -0
- package/src/vue/forms/PropertyAwareArray.ts +78 -0
- package/src/vue/forms/index.ts +11 -0
- package/src/vue/forms/types/PersistedForm.ts +6 -0
- package/src/vue/forms/validation/ValidationMode.enum.ts +14 -0
- package/src/vue/forms/validation/index.ts +12 -0
- package/src/vue/forms/validation/rules/BaseRule.ts +7 -0
- package/src/vue/forms/validation/rules/ConfirmedRule.ts +39 -0
- package/src/vue/forms/validation/rules/MinRule.ts +61 -0
- package/src/vue/forms/validation/rules/RequiredRule.ts +19 -0
- package/src/vue/forms/validation/rules/UrlRule.ts +24 -0
- package/src/vue/forms/validation/types/BidirectionalRule.ts +11 -0
- package/src/vue/index.ts +14 -0
- package/src/vue/requests/factories/VueRequestLoaderFactory.ts +9 -0
- package/src/vue/requests/index.ts +5 -0
- package/src/vue/requests/loaders/VueRequestBatchLoader.ts +30 -0
- package/src/vue/requests/loaders/VueRequestLoader.ts +18 -0
- package/src/vue/router/routeModelBinding/RouteModelRequestResolver.ts +11 -0
- package/src/vue/router/routeModelBinding/defineRoute.ts +31 -0
- package/src/vue/router/routeModelBinding/index.ts +8 -0
- package/src/vue/router/routeModelBinding/installRouteInjection.ts +73 -0
- package/src/vue/router/routeModelBinding/types.ts +46 -0
- package/src/vue/state/State.ts +391 -0
- package/src/vue/state/index.ts +3 -0
- package/tests/service/helpers/mergeDeep.test.ts +53 -0
- package/tests/service/laravel/pagination/dataDrivers/RequestDriver.test.ts +84 -0
- package/tests/service/laravel/requests/JsonBaseRequest.test.ts +43 -0
- package/tests/service/laravel/requests/PaginationJsonBaseRequest.test.ts +58 -0
- package/tests/service/laravel/requests/responses/JsonResponse.test.ts +59 -0
- package/tests/service/laravel/requests/responses/PaginationResponse.test.ts +127 -0
- package/tests/service/pagination/dtos/PaginationDataDto.test.ts +35 -0
- package/tests/service/pagination/factories/VuePaginationDriverFactory.test.ts +32 -0
- package/tests/service/pagination/frontendDrivers/VuePaginationDriver.test.ts +66 -0
- package/tests/service/requests/ErrorHandler.test.ts +141 -0
- package/tsconfig.json +114 -0
- package/vite.config.ts +34 -0
- package/vitest.config.ts +14 -0
|
@@ -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,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,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,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,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,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
|
+
}
|