rastack 0.0.16

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 (140) hide show
  1. package/CHANGELOG.md +43013 -0
  2. package/dist/.prettierrc +7 -0
  3. package/dist/compile/analyze.d.ts +12 -0
  4. package/dist/compile/analyze.js +233 -0
  5. package/dist/compile/index.d.ts +19 -0
  6. package/dist/compile/index.js +107 -0
  7. package/dist/compile/manifest.d.ts +4 -0
  8. package/dist/compile/manifest.js +12 -0
  9. package/dist/compile/model.d.ts +54 -0
  10. package/dist/compile/model.js +7 -0
  11. package/dist/compile/openapi.d.ts +11 -0
  12. package/dist/compile/openapi.js +233 -0
  13. package/dist/compile/program.d.ts +10 -0
  14. package/dist/compile/program.js +61 -0
  15. package/dist/csv-schema.d.ts +28 -0
  16. package/dist/csv-schema.js +162 -0
  17. package/dist/define/index.d.ts +117 -0
  18. package/dist/define/index.js +74 -0
  19. package/dist/entity-generation/delete-method.d.ts +1 -0
  20. package/dist/entity-generation/delete-method.js +31 -0
  21. package/dist/entity-generation/form.d.ts +13 -0
  22. package/dist/entity-generation/form.js +136 -0
  23. package/dist/entity-generation/get-method.d.ts +1 -0
  24. package/dist/entity-generation/get-method.js +67 -0
  25. package/dist/entity-generation/imports.d.ts +5 -0
  26. package/dist/entity-generation/imports.js +91 -0
  27. package/dist/entity-generation/sync-method.d.ts +30 -0
  28. package/dist/entity-generation/sync-method.js +158 -0
  29. package/dist/entity-generation/update-method.d.ts +1 -0
  30. package/dist/entity-generation/update-method.js +29 -0
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +53 -0
  33. package/dist/rad-compile.d.ts +11 -0
  34. package/dist/rad-compile.js +82 -0
  35. package/dist/rad-wasm-build.d.ts +14 -0
  36. package/dist/rad-wasm-build.js +74 -0
  37. package/dist/rad.d.ts +17 -0
  38. package/dist/rad.js +88 -0
  39. package/dist/scan.d.ts +12 -0
  40. package/dist/scan.js +79 -0
  41. package/dist/schema/camel-to-pastel.d.ts +1 -0
  42. package/dist/schema/camel-to-pastel.js +7 -0
  43. package/dist/schema/capitalise-first-letter.d.ts +1 -0
  44. package/dist/schema/capitalise-first-letter.js +7 -0
  45. package/dist/schema/extract-response.d.ts +2 -0
  46. package/dist/schema/extract-response.js +30 -0
  47. package/dist/schema/fetch-schema.d.ts +7 -0
  48. package/dist/schema/fetch-schema.js +23 -0
  49. package/dist/schema/remove-non-model-paths.d.ts +2 -0
  50. package/dist/schema/remove-non-model-paths.js +15 -0
  51. package/dist/schema/to-camel-case.d.ts +1 -0
  52. package/dist/schema/to-camel-case.js +11 -0
  53. package/dist/schema/to-pastel-case.d.ts +1 -0
  54. package/dist/schema/to-pastel-case.js +10 -0
  55. package/dist/schema-convert.d.ts +1 -0
  56. package/dist/schema-convert.js +63 -0
  57. package/dist/schema-entities.d.ts +14 -0
  58. package/dist/schema-entities.js +293 -0
  59. package/dist/schema-fetch.d.ts +1 -0
  60. package/dist/schema-fetch.js +81 -0
  61. package/dist/schema-full.d.ts +1 -0
  62. package/dist/schema-full.js +45 -0
  63. package/dist/schema-index.d.ts +1 -0
  64. package/dist/schema-index.js +34 -0
  65. package/dist/schema-params.d.ts +1 -0
  66. package/dist/schema-params.js +100 -0
  67. package/dist/seed.d.ts +17 -0
  68. package/dist/seed.js +252 -0
  69. package/hooks/form/form.ts +207 -0
  70. package/hooks/form/index.ts +8 -0
  71. package/hooks/form/interfaces.ts +217 -0
  72. package/hooks/form/structure.ts +39 -0
  73. package/hooks/form/validate-schema.ts +49 -0
  74. package/hooks/index.ts +3 -0
  75. package/hooks/query/api.ts +42 -0
  76. package/hooks/query/delete.ts +45 -0
  77. package/hooks/query/fetch.ts +48 -0
  78. package/hooks/query/index.ts +21 -0
  79. package/hooks/query/interfaces.ts +111 -0
  80. package/hooks/query/list.ts +286 -0
  81. package/hooks/query/update.ts +88 -0
  82. package/hooks/query/url.ts +31 -0
  83. package/hooks/real-time/index.ts +1 -0
  84. package/hooks/real-time/pusher.ts +43 -0
  85. package/jest.config.cjs +6 -0
  86. package/package.json +57 -0
  87. package/provider/index.ts +13 -0
  88. package/provider/provider.tsx +187 -0
  89. package/provider/types.ts +114 -0
  90. package/provider/warehouse.ts +167 -0
  91. package/provider/wasm.ts +124 -0
  92. package/runtime.ts +3 -0
  93. package/src/.prettierrc +7 -0
  94. package/src/compile/analyze.ts +224 -0
  95. package/src/compile/index.ts +86 -0
  96. package/src/compile/manifest.ts +10 -0
  97. package/src/compile/model.ts +69 -0
  98. package/src/compile/openapi.ts +266 -0
  99. package/src/compile/program.ts +40 -0
  100. package/src/csv-schema.ts +187 -0
  101. package/src/define/index.ts +162 -0
  102. package/src/entity-generation/delete-method.ts +37 -0
  103. package/src/entity-generation/form.ts +169 -0
  104. package/src/entity-generation/get-method.ts +80 -0
  105. package/src/entity-generation/imports.ts +110 -0
  106. package/src/entity-generation/sync-method.ts +222 -0
  107. package/src/entity-generation/update-method.ts +38 -0
  108. package/src/index.ts +77 -0
  109. package/src/rad-compile.ts +88 -0
  110. package/src/rad-wasm-build.ts +102 -0
  111. package/src/rad.ts +101 -0
  112. package/src/scan.ts +89 -0
  113. package/src/schema/camel-to-pastel.ts +3 -0
  114. package/src/schema/capitalise-first-letter.ts +3 -0
  115. package/src/schema/extract-response.ts +38 -0
  116. package/src/schema/fetch-schema.ts +12 -0
  117. package/src/schema/remove-non-model-paths.ts +13 -0
  118. package/src/schema/to-camel-case.ts +7 -0
  119. package/src/schema/to-pastel-case.ts +6 -0
  120. package/src/schema-convert.ts +61 -0
  121. package/src/schema-entities.ts +401 -0
  122. package/src/schema-fetch.ts +82 -0
  123. package/src/schema-full.ts +38 -0
  124. package/src/schema-index.ts +29 -0
  125. package/src/schema-params.ts +116 -0
  126. package/src/seed.ts +297 -0
  127. package/sync/engine.ts +392 -0
  128. package/sync/hooks.ts +450 -0
  129. package/sync/index.ts +8 -0
  130. package/sync/persistence.ts +237 -0
  131. package/sync/provider.tsx +91 -0
  132. package/sync/registry.ts +36 -0
  133. package/sync/store.ts +126 -0
  134. package/sync/transactions.ts +300 -0
  135. package/sync/types.ts +94 -0
  136. package/test/compile.spec.ts +192 -0
  137. package/test/csv-schema.spec.ts +143 -0
  138. package/test/schema-entities.spec.ts +688 -0
  139. package/tsconfig.json +24 -0
  140. package/types.ts +19 -0
@@ -0,0 +1,39 @@
1
+ import { SchemaObject } from "ajv";
2
+ import { FormType } from "./interfaces";
3
+
4
+ interface GetFieldStructureResponse {
5
+ isRequired: boolean;
6
+ type: FormType;
7
+ }
8
+
9
+ export function getFieldStructure<TFieldName>(
10
+ validationSchema: SchemaObject | undefined,
11
+ fieldName: TFieldName,
12
+ ): GetFieldStructureResponse {
13
+ let type: FormType = "text";
14
+ if (validationSchema?.properties) {
15
+ const schemaJson = validationSchema.properties[fieldName];
16
+ if (!schemaJson) {
17
+ return { isRequired: false, type };
18
+ }
19
+ let schemaJsonType = schemaJson.type;
20
+
21
+ if (typeof schemaJsonType === "string") {
22
+ schemaJsonType = [schemaJsonType];
23
+ }
24
+ if (schemaJsonType) {
25
+ if (schemaJsonType.includes("integer")) {
26
+ type = "number";
27
+ }
28
+ if (schemaJsonType.includes("array")) {
29
+ type = "array";
30
+ }
31
+ }
32
+ }
33
+
34
+ let isRequired = false;
35
+ if (validationSchema?.required) {
36
+ isRequired = validationSchema.required?.includes(fieldName);
37
+ }
38
+ return { isRequired, type };
39
+ }
@@ -0,0 +1,49 @@
1
+ import { FormikErrors } from "formik";
2
+ import addFormats from "ajv-formats";
3
+ import Ajv, { ErrorObject } from "ajv";
4
+
5
+ export function createAJV() {
6
+ const ajvInstance = new Ajv({ allErrors: true });
7
+ addFormats(ajvInstance);
8
+
9
+ ajvInstance.addFormat("decimal", {
10
+ type: "number",
11
+ validate: (num: number) =>
12
+ // @ts-ignore
13
+ !Number.isNaN(num) && num.toString().includes("."),
14
+ });
15
+
16
+ return ajvInstance;
17
+ }
18
+
19
+ export function formikJSONSchemaValidation<TData>(
20
+ data: TData,
21
+ schema?: any | undefined,
22
+ ): FormikErrors<TData> {
23
+ const AJV = createAJV();
24
+ const validate = AJV.compile(schema);
25
+ const valid = validate(data);
26
+ if (valid) return {};
27
+
28
+ const errors: FormikErrors<TData> = {};
29
+ validate.errors?.forEach((error) => {
30
+ const ajvError = error as ErrorObject & {
31
+ dataPath: string;
32
+ params: { missingProperty?: string };
33
+ };
34
+ let path = ajvError.instancePath.replace(/^\//, "").replace(/\//g, ".");
35
+
36
+ if (
37
+ // @ts-ignore
38
+ ajvError.schemaPath.includes("#/required") &&
39
+ ajvError.params.missingProperty
40
+ ) {
41
+ path = `${path}.${ajvError.params.missingProperty}`.replace(/^\./, "");
42
+ (errors as Record<string, string>)[path] =
43
+ `${ajvError.params.missingProperty} is required`;
44
+ } else {
45
+ (errors as Record<string, string>)[path] = ajvError.message as string;
46
+ }
47
+ });
48
+ return errors;
49
+ }
package/hooks/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./form";
2
+ export * from "./query";
3
+ export * from "./real-time";
@@ -0,0 +1,42 @@
1
+ import axios, { AxiosAdapter, AxiosInstance } from "axios";
2
+
3
+ // @ts-ignore
4
+ const defaultBaseURL = process.env.NEXT_PUBLIC_BASE_URL || "http://127.0.0.1:8000";
5
+
6
+ /**
7
+ * The shared axios client every generated hook talks through. It is a stable
8
+ * singleton — imported directly all over the codegen — so `<RAStackProvider>`
9
+ * reconfigures it *in place* (base URL for `remote` mode, or a WASM adapter for
10
+ * `local`/`s3` mode) rather than swapping the instance. That is what lets the
11
+ * same hooks hit a Lambda over HTTP or an in-browser WASM engine with no
12
+ * codegen changes.
13
+ */
14
+ export const API: AxiosInstance = axios.create({
15
+ baseURL: defaultBaseURL,
16
+ });
17
+
18
+ export interface ApiConfig {
19
+ /** Base URL for HTTP (`remote`) mode. */
20
+ baseURL?: string;
21
+ /**
22
+ * Axios adapter override. Pass the WASM adapter to route requests into the
23
+ * in-browser engine; pass `null` to restore axios's default HTTP transport.
24
+ */
25
+ adapter?: AxiosAdapter | null;
26
+ }
27
+
28
+ /** Reconfigure the shared client. Called by `<RAStackProvider>` on mode changes. */
29
+ export function configureApi(config: ApiConfig): void {
30
+ if (config.baseURL !== undefined) {
31
+ API.defaults.baseURL = config.baseURL;
32
+ }
33
+ if ("adapter" in config) {
34
+ API.defaults.adapter = config.adapter ?? undefined;
35
+ }
36
+ }
37
+
38
+ /** Restore the client to its default HTTP transport and base URL. */
39
+ export function resetApi(): void {
40
+ API.defaults.baseURL = defaultBaseURL;
41
+ API.defaults.adapter = undefined;
42
+ }
@@ -0,0 +1,45 @@
1
+ import { AxiosResponse } from "axios";
2
+ import { UseMutationResult } from "react-query/types/react/types";
3
+ import { useMutation, useQueryClient } from "react-query";
4
+ import { IDeleteQueryResult } from "./interfaces";
5
+ import { API } from "./api";
6
+
7
+ interface DeleteQueryProps {
8
+ deleteUrl?: ((id: string) => string) | string;
9
+ listQueryCacheKey?: string | undefined;
10
+ }
11
+
12
+ export function useDeleteQuery<TResult>({
13
+ deleteUrl,
14
+ listQueryCacheKey,
15
+ }: DeleteQueryProps): IDeleteQueryResult<TResult> {
16
+ const queryClient = useQueryClient();
17
+
18
+ return useMutation(
19
+ async (params: { id?: string }) => {
20
+ if (params.id === undefined) {
21
+ throw new Error("ID is required for delete operation");
22
+ }
23
+ let url: string = "";
24
+ if (typeof deleteUrl === "function") {
25
+ if (params.id) {
26
+ url = deleteUrl(params.id);
27
+ }
28
+ } else {
29
+ if (deleteUrl) {
30
+ url = deleteUrl;
31
+ }
32
+ }
33
+
34
+ const response: AxiosResponse = await API.delete(url);
35
+ return response.data.results;
36
+ },
37
+ {
38
+ onSuccess: () => {
39
+ if (listQueryCacheKey) {
40
+ queryClient.invalidateQueries(listQueryCacheKey);
41
+ }
42
+ },
43
+ },
44
+ ) as UseMutationResult<TResult>;
45
+ }
@@ -0,0 +1,48 @@
1
+ import { AxiosResponse } from "axios";
2
+ import { useQuery } from "react-query";
3
+ import { API } from "./api";
4
+ import { stringifyQueryParams } from "./url";
5
+ import { GetQueryOptions, IGetQueryResult } from "./interfaces";
6
+
7
+ export interface GetQueryProps<TParam> {
8
+ getUrl: string;
9
+ queryKey: string;
10
+ pathParameters: (keyof TParam)[];
11
+ requiredParams: (keyof TParam)[];
12
+ isAuthenticated: boolean;
13
+ options?: GetQueryOptions<TParam>;
14
+ }
15
+
16
+ export function useGetQuery<TParam, TResult>({
17
+ getUrl,
18
+ queryKey,
19
+ pathParameters,
20
+ requiredParams,
21
+ isAuthenticated,
22
+ options,
23
+ }: GetQueryProps<TParam>): IGetQueryResult<TResult> {
24
+ const isRequiredParamsPresent = requiredParams?.every((param) => {
25
+ if (!options?.params) {
26
+ return false;
27
+ } else {
28
+ return options?.params[param as keyof TParam] !== undefined;
29
+ }
30
+ });
31
+
32
+ const params = options?.params;
33
+
34
+ return useQuery(
35
+ [queryKey, getUrl, params],
36
+ async () => {
37
+ const urlParams = stringifyQueryParams(pathParameters, params);
38
+ const serverURL = `${getUrl}${urlParams ? `?${urlParams}` : ""}`;
39
+
40
+ const response: AxiosResponse<TResult> = await API.get(serverURL);
41
+
42
+ return response.data;
43
+ },
44
+ {
45
+ enabled: isAuthenticated && isRequiredParamsPresent,
46
+ },
47
+ );
48
+ }
@@ -0,0 +1,21 @@
1
+ export { useGetQuery } from "./fetch";
2
+ export type {
3
+ IGetQueryResult,
4
+ IUpdateQueryResult,
5
+ IDeleteQueryResult,
6
+ IListQuery,
7
+ ListQueryOptions,
8
+ GetQueryOptions,
9
+ } from "./interfaces";
10
+ export { useGetPaginatedQueryList } from "./list";
11
+ export { useUpdateQuery } from "./update";
12
+ export { useDeleteQuery } from "./delete";
13
+ export type {
14
+ IPaginationActions,
15
+ ISearchingActions,
16
+ IFilterActions,
17
+ ISortingActions,
18
+ ReturnedHeader,
19
+ } from "./interfaces";
20
+ export { API, configureApi, resetApi } from "./api";
21
+ export type { ApiConfig } from "./api";
@@ -0,0 +1,111 @@
1
+ import {
2
+ UseMutationResult,
3
+ UseQueryResult,
4
+ } from "react-query/types/react/types";
5
+ import { ExtractKey } from "../../types";
6
+
7
+ export interface IPaginationActions {
8
+ currentPage: number;
9
+ pageSize: number;
10
+ itemCount: number;
11
+ pageCount: number;
12
+ nextPage: () => void;
13
+ previousPage: () => void;
14
+ setPageSize: (pageSize: number) => void;
15
+ }
16
+
17
+ export interface ISearchingActions<TParam> {
18
+ searchText: string | undefined;
19
+ setSearchText: (value: string | undefined) => void;
20
+ }
21
+
22
+ export interface IPagination {
23
+ next: number | null;
24
+ previous: number | null;
25
+ current: number;
26
+ last: number;
27
+ page_size: number;
28
+ count: number;
29
+ }
30
+
31
+ export interface ISortingActions<TParam> {
32
+ sortField: (key: NormalizedField<ExtractKey<TParam, "order_by">>) => void;
33
+ getSortingDirection: (
34
+ key: NormalizedField<ExtractKey<TParam, "order_by">>,
35
+ ) => "asc" | "desc" | undefined;
36
+ }
37
+
38
+ export interface IFilterActions<TParam> {
39
+ getFilterFieldValue: (key: keyof TParam) => TParam[keyof TParam];
40
+ setFilterFieldValue: (
41
+ key: keyof TParam,
42
+ ) => (value: TParam[keyof TParam]) => void;
43
+ }
44
+
45
+ export interface IFilter {
46
+ [key: string]: unknown;
47
+ }
48
+
49
+ export type UseQueryResultWithoutData<TResult> = Omit<
50
+ UseQueryResult<IPaginatedResponse<TResult>>,
51
+ "data"
52
+ >;
53
+
54
+ export type NormalizedField<T> = T extends `-${infer U}` ? U : T;
55
+
56
+ export interface ReturnedHeader {
57
+ key: string;
58
+ name: string;
59
+ }
60
+
61
+ export type IPaginatedResponse<TResult> = {
62
+ [key in keyof TResult]: TResult[key];
63
+ } & {
64
+ headers?: ReturnedHeader[];
65
+ pagination: IPagination;
66
+ };
67
+
68
+ export interface IListQuery<TParam, TQuery>
69
+ extends UseQueryResultWithoutData<TQuery> {
70
+ pagination: IPaginationActions;
71
+ searching: ISearchingActions<TParam>;
72
+ sorting: ISortingActions<TParam>;
73
+ filtering: IFilterActions<TParam>;
74
+ header: ReturnedHeader[] | undefined;
75
+ data: TQuery[] | undefined;
76
+ raw: IPaginatedResponse<TQuery> | undefined;
77
+ }
78
+
79
+ export type IGetQueryResult<TResult> = UseQueryResult<TResult>;
80
+
81
+ export interface WriteQueryVariables<TParams, TPayload> {
82
+ params: TParams;
83
+ payload: TPayload;
84
+ }
85
+
86
+ export type IUpdateQueryResult<TParams, TPayload, TResult> = UseMutationResult<
87
+ TResult,
88
+ unknown,
89
+ WriteQueryVariables<TParams, TPayload>,
90
+ unknown
91
+ >;
92
+
93
+ export type IDeleteQueryResult<TResult> = UseMutationResult<TResult>;
94
+
95
+ export interface ListQueryOptions<TParam, TResult> {
96
+ params?: Partial<TParam>;
97
+ requiredParams?: (keyof TParam)[];
98
+ searchKey?: keyof TParam;
99
+ defaultSortFields?: (string & ExtractKey<TParam, "order_by">)[];
100
+ onSuccess?: (result: IPaginatedResponse<TResult>) => void;
101
+ localCache?: boolean;
102
+ pageSize?: number;
103
+ localSearchFunction?: (
104
+ searchValue: string | undefined,
105
+ searchData: TResult,
106
+ ) => boolean;
107
+ }
108
+
109
+ export interface GetQueryOptions<TParam> {
110
+ params?: Partial<TParam>;
111
+ }
@@ -0,0 +1,286 @@
1
+ import { AxiosResponse } from "axios";
2
+ import { useQuery } from "react-query";
3
+ import { useEffect, useState } from "react";
4
+ import {
5
+ IFilterActions,
6
+ IListQuery,
7
+ IPaginatedResponse,
8
+ IPaginationActions,
9
+ ISearchingActions,
10
+ ISortingActions,
11
+ ListQueryOptions,
12
+ NormalizedField,
13
+ } from "./interfaces";
14
+ import { stringifyQueryParams } from "./url";
15
+ import { useDebounceValue } from "usehooks-ts";
16
+ import { API } from "./api";
17
+ import { ExtractKey } from "../../types";
18
+
19
+ export interface GetPaginatedQueryListProps<TParam, TResult> {
20
+ postUrl?: string;
21
+ getUrl?: string;
22
+ queryKey: string;
23
+ pathParameters: (keyof TParam)[];
24
+ isAuthenticated: boolean;
25
+ options?: ListQueryOptions<TParam, TResult>;
26
+ }
27
+
28
+ export function useGetPaginatedQueryList<TParam, TResult>({
29
+ postUrl,
30
+ getUrl,
31
+ queryKey,
32
+ pathParameters,
33
+ isAuthenticated,
34
+ options,
35
+ }: GetPaginatedQueryListProps<TParam, TResult>): IListQuery<TParam, TResult> {
36
+ const [searchText, setSearchText] = useState<string | undefined>(undefined);
37
+ const [serverSearchText, setServerSearchText] = useDebounceValue<
38
+ string | undefined
39
+ >(undefined, 500);
40
+
41
+ const [queryParamState, setQueryParamState] = useState({
42
+ page: 1,
43
+ page_size: options?.pageSize ? options?.pageSize : 20,
44
+ order_by: options?.defaultSortFields ?? [],
45
+ });
46
+
47
+ const pageParams = ["page", "page_size"] as unknown as (keyof TParam)[];
48
+ const requiredParams = options?.requiredParams
49
+ ? [...options.requiredParams, ...pageParams]
50
+ : pageParams;
51
+
52
+ useEffect(() => {
53
+ if (!options?.localCache) {
54
+ setServerSearchText(searchText);
55
+ }
56
+ }, [searchText]);
57
+
58
+ useEffect(() => {
59
+ if (options?.searchKey) {
60
+ setQueryParamState((current) => ({
61
+ ...current,
62
+ page: 1,
63
+ [options?.searchKey as string]: serverSearchText,
64
+ }));
65
+ }
66
+ }, [serverSearchText]);
67
+
68
+ const serverQueryParamState = {
69
+ ...queryParamState,
70
+ ...options?.params,
71
+ };
72
+
73
+ const isRequiredQueryParamsPresent = requiredParams?.every(
74
+ // @ts-ignore Pre Validation of page, page_size and order_by will happen when the two hooks are combined
75
+ (param) => serverQueryParamState[param] !== undefined,
76
+ );
77
+
78
+ const fetchDataForAllPages = async (
79
+ currentPage: number = 1,
80
+ accumulatedData: TResult[] = [],
81
+ ): Promise<IPaginatedResponse<TResult>> => {
82
+ const serverURL = `${getUrl}?${stringifyQueryParams(pathParameters, {
83
+ ...serverQueryParamState,
84
+ // @ts-ignore Pre Validation of page, page_size and order_by will happen when the two hooks are combined
85
+ page: currentPage,
86
+ })}`;
87
+
88
+ const response: AxiosResponse<IPaginatedResponse<TResult>> = postUrl
89
+ ? await API.post(postUrl, serverQueryParamState)
90
+ : await API.get(serverURL);
91
+
92
+ // @ts-ignore This will be resolved when the List and Get endpoints will be merged
93
+ const newData = response.data.results;
94
+
95
+ const mergedData = [...accumulatedData, ...newData];
96
+
97
+ if (currentPage < response.data.pagination.last) {
98
+ return fetchDataForAllPages(currentPage + 1, mergedData);
99
+ } else {
100
+ return {
101
+ ...response.data,
102
+ pagination: {
103
+ current: 1,
104
+ last: 1,
105
+ next: 1,
106
+ previous: 1,
107
+ page_size: mergedData.length,
108
+ count: mergedData.length,
109
+ },
110
+ results: mergedData,
111
+ };
112
+ }
113
+ };
114
+
115
+ const fetchData = async () => {
116
+ if (options?.localCache) {
117
+ return fetchDataForAllPages();
118
+ } else {
119
+ // @ts-ignore Pre Validation of page, page_size and order_by will happen when the two hooks are combined
120
+ const serverURL = `${getUrl}?${stringifyQueryParams(pathParameters, serverQueryParamState)}`;
121
+ const response: AxiosResponse<IPaginatedResponse<TResult>> = postUrl
122
+ ? await API.post(postUrl, serverQueryParamState)
123
+ : await API.get(serverURL);
124
+ return response.data;
125
+ }
126
+ };
127
+
128
+ const query = useQuery<IPaginatedResponse<TResult>>(
129
+ [queryKey, getUrl, options?.params, serverQueryParamState],
130
+ fetchData,
131
+ {
132
+ enabled: isRequiredQueryParamsPresent && isAuthenticated,
133
+ onSuccess: options?.onSuccess,
134
+ },
135
+ );
136
+
137
+ const pageCount = query.data?.pagination?.last || 0;
138
+ const itemCount = query.data?.pagination?.count || 0;
139
+
140
+ function previousPage() {
141
+ if (queryParamState.page) {
142
+ const previousPage = (queryParamState.page as unknown as number) - 1;
143
+ setQueryParamState((current) => ({
144
+ ...current,
145
+ page: previousPage < 1 ? 1 : previousPage,
146
+ }));
147
+ }
148
+ }
149
+
150
+ function nextPage() {
151
+ if (queryParamState.page) {
152
+ const nextPage = (queryParamState.page as unknown as number) + 1;
153
+ setQueryParamState((current) => ({
154
+ ...current,
155
+ page: nextPage > pageCount ? pageCount : nextPage,
156
+ }));
157
+ }
158
+ }
159
+
160
+ function setPageSize(pageSize: number) {
161
+ setQueryParamState((current) => ({
162
+ ...current,
163
+ page_size: pageSize,
164
+ }));
165
+ }
166
+
167
+ const pagination: IPaginationActions = {
168
+ previousPage,
169
+ nextPage,
170
+ currentPage: serverQueryParamState.page,
171
+ pageSize: serverQueryParamState.page_size,
172
+ pageCount,
173
+ itemCount,
174
+ setPageSize,
175
+ };
176
+
177
+ const searching: ISearchingActions<TParam> = {
178
+ searchText,
179
+ setSearchText,
180
+ };
181
+
182
+ function sortField(field: NormalizedField<ExtractKey<TParam, "order_by">>) {
183
+ setQueryParamState((current) => {
184
+ const ascendingField = current.order_by?.find((item) => item === field);
185
+ const descendingField = current.order_by?.find(
186
+ (item) => item === `-${field}`,
187
+ );
188
+
189
+ let result: (string & ExtractKey<TParam, "order_by">)[] | undefined = [];
190
+ if (descendingField) {
191
+ result = current.order_by?.filter((item) => item !== `-${field}`);
192
+ } else if (ascendingField) {
193
+ result = current.order_by?.filter((item) => item !== field);
194
+ result?.push(`-${field}` as string & ExtractKey<TParam, "order_by">);
195
+ } else {
196
+ result = current.order_by
197
+ ? [
198
+ ...current.order_by,
199
+ field as string & ExtractKey<TParam, "order_by">,
200
+ ]
201
+ : [field as string & ExtractKey<TParam, "order_by">];
202
+ }
203
+
204
+ return {
205
+ ...current,
206
+ order_by: result,
207
+ page: 1,
208
+ };
209
+ });
210
+ }
211
+
212
+ function getSortingDirection(
213
+ field: NormalizedField<ExtractKey<TParam, "order_by">>,
214
+ ) {
215
+ const invertedField = `-${field}`;
216
+ if (serverQueryParamState.order_by?.find((item) => item === field)) {
217
+ return "asc";
218
+ } else if (
219
+ serverQueryParamState.order_by?.find((item) => item === invertedField)
220
+ ) {
221
+ return "desc";
222
+ } else {
223
+ return undefined;
224
+ }
225
+ }
226
+
227
+ const sorting: ISortingActions<TParam> = {
228
+ sortField,
229
+ getSortingDirection,
230
+ };
231
+
232
+ function getFilterFieldValue(key: keyof TParam) {
233
+ // @ts-ignore correctly handle the keyof in the future
234
+ return queryParamState[key] as TParam[keyof TParam];
235
+ }
236
+
237
+ function setFilterFieldValue(key: keyof TParam) {
238
+ return (value: unknown) => {
239
+ setQueryParamState((current) => ({
240
+ ...current,
241
+ [key]: value,
242
+ page: 1,
243
+ }));
244
+ };
245
+ }
246
+
247
+ const filtering: IFilterActions<TParam> = {
248
+ getFilterFieldValue,
249
+ setFilterFieldValue,
250
+ };
251
+
252
+ const data =
253
+ options?.localCache && searchText
254
+ ? // @ts-ignore the results key is based on the interface returned. This will get sorted out in a future version
255
+ query.data?.results.filter((value: any) => {
256
+ if (options.localSearchFunction && searchText) {
257
+ if (searchText.includes(" ")) {
258
+ const searchWords = searchText.split(" ");
259
+ return searchWords.every((word) => {
260
+ if (options.localSearchFunction) {
261
+ return options.localSearchFunction(word, value);
262
+ } else {
263
+ return false;
264
+ }
265
+ });
266
+ } else {
267
+ return options.localSearchFunction(searchText, value);
268
+ }
269
+ } else {
270
+ return true;
271
+ }
272
+ })
273
+ : // @ts-ignore
274
+ query.data?.results;
275
+
276
+ return {
277
+ ...query,
278
+ raw: query.data,
279
+ data,
280
+ header: query.data?.headers,
281
+ pagination,
282
+ searching,
283
+ sorting,
284
+ filtering,
285
+ };
286
+ }