@piveau/sdk-vue 0.0.0-alpha.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/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # @piveau/sdk-vue
2
+
3
+ Asynchronous state management for interacting with piveau services in Vue.js.
4
+
5
+ Looking for a standalone API client? Try @piveau/sdk-core
6
+
7
+ ## Quick Features
8
+
9
+ - Provides intelligent client-side caching, background updates, and optimistic updates of piveau backend data state in a declarative way, powered by [@tanstack/vue-query](https://github.com/TanStack/query)
10
+ - Composable utility functions to help you model real-world use cases easier, e.g., facets, search, and pagination
11
+ - Runtime resource schema validation and model type inference powered by [Zod](https://zod.dev/)
12
+ - Shape data from API to your data model using getters
13
+ - Vue 3 compatible
14
+ - Supports Server-Side Rendering with Nuxt
15
+ - Written with TypeScript
16
+
17
+ ## Usage
18
+
19
+ <table>
20
+ <tbody valign=top align=left>
21
+ <tr><th>
22
+ Node 20+, Vue 3
23
+ </th><td>
24
+
25
+ Install with <code>npm/pnpm install @piveau/sdk-vue</code>, or <code>yarn add @piveau/sdk-vue</code>. For better devtooling, we recommend installing @piveau/sdk-core and vue-query with <code>npm/pnpm install @piveau/sdk-core @tanstack/vue-query@5</code>.
26
+
27
+ **Recommended**: In `main.ts`, setup the plugin:
28
+ ```js
29
+ import { createApp } from 'vue'
30
+ import { plugin } from '@piveau/sdk-vue'
31
+ import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query'
32
+ import App from './App.vue'
33
+
34
+ const qc = new QueryClient()
35
+
36
+ const app = createApp(App)
37
+
38
+ app.use(VueQueryPlugin, {
39
+ queryClient: qc,
40
+ })
41
+
42
+ app.use(plugin, {
43
+ queryClient: qc,
44
+ })
45
+ ```
46
+
47
+ </td></tr>
48
+ <tr><th>
49
+ Node 20+, Nuxt 3
50
+ </th><td>
51
+ *Work in Progress* See https://tanstack.com/query/latest/docs/framework/vue/guides/ssr and register the sdk-vue plugin as above
52
+ </td></tr>
53
+ </tbody>
54
+ </table>
55
+
56
+ ## piveau API Support
57
+
58
+ - `/search/filter={filter}` for searching resources
59
+ - `/search/{index}/{id}` for fetching a single resource
60
+
61
+ ## Work in Progress
62
+
63
+ - hub
64
+ - consus
65
+ - metrics
66
+ - statistics
@@ -0,0 +1,165 @@
1
+ import { ToRef, Ref, ComputedRef, MaybeRef, MaybeRefOrGetter, InjectionKey, App } from 'vue-demi';
2
+ import { UseQueryReturnType, QueryClient } from '@tanstack/vue-query';
3
+ import { GetResourceResult, SearchParamsBase, SearchResult, SearchResultFacetGroup, SearchParams } from '@piveau/sdk-core';
4
+ import * as z from 'zod';
5
+
6
+ /**
7
+ * Type of an object of Getters that infers the argument.
8
+ */
9
+ type GettersTree<TModelSchema = any, TContext = any> = Record<string, ((data: TModelSchema, ctx?: TContext) => any) | (() => any)>;
10
+
11
+ type MyUseQueryReturnType<TD, TE> = UseQueryReturnType<TD, TE>;
12
+ interface UseResourceResultNoData<TModelSchema = any, TContext = any, TGetterTree extends GettersTree<TModelSchema, TContext> = GettersTree<TModelSchema, TContext>> {
13
+ isSuccess: false;
14
+ result: ComputedRef<undefined>;
15
+ getters: {
16
+ [K in keyof TGetterTree]: ComputedRef<null>;
17
+ };
18
+ }
19
+ interface UseResourceResultWithData<TModelSchema = any, TContext = any, TGetterTree extends GettersTree<TModelSchema, TContext> = GettersTree<TModelSchema, TContext>> {
20
+ isSuccess: true;
21
+ result: ComputedRef<TModelSchema>;
22
+ getters: {
23
+ [K in keyof TGetterTree]: ComputedRef<ReturnType<TGetterTree[K]>>;
24
+ };
25
+ }
26
+ type UseResourceResultBase<TModelSchema = any, TContext = any, TGetterTree extends GettersTree<TModelSchema, TContext> = GettersTree<TModelSchema, TContext>> = UseResourceResultNoData<TModelSchema, TContext, TGetterTree> | UseResourceResultWithData<TModelSchema, TContext, TGetterTree>;
27
+ type UseResourceResult<TModelSchema = any, TContext = any, TGetterTree extends GettersTree<TModelSchema, TContext> = GettersTree<TModelSchema, TContext>, TResult = UseResourceResultBase<TModelSchema, TContext, TGetterTree>> = {
28
+ [K in keyof TResult]: K extends 'isSuccess' ? ToRef<TResult[K]> : TResult[K];
29
+ } & {
30
+ query: MyUseQueryReturnType<GetResourceResult<TModelSchema>, Error>;
31
+ isLoading: Ref<boolean> | Ref<true> | Ref<false>;
32
+ isFetching: Ref<boolean>;
33
+ };
34
+
35
+ type Reffify<T> = {
36
+ [key in keyof T]: MaybeRef<T[key]>;
37
+ };
38
+ type SearchParamsRef = Reffify<SearchParamsBase>;
39
+ interface SelectedFacetItemObject {
40
+ id: string;
41
+ title: Record<string, string> | string;
42
+ count: number;
43
+ }
44
+ type SelectedFacetItemList = (SelectedFacetItemObject | string)[];
45
+ type FacetRef<TFacetNames extends string = string> = {
46
+ readonly [key in TFacetNames]?: MaybeRef<SelectedFacetItemList>;
47
+ };
48
+
49
+ interface SearchResultFacetGroupLocalized {
50
+ id: string;
51
+ title: string;
52
+ items: {
53
+ id: string;
54
+ title: string | undefined;
55
+ count: number;
56
+ }[];
57
+ }
58
+ interface UseSearchFactoryReturn<TFacetNames extends string, TModelSchema, TGettersTree extends GettersTree<TModelSchema, any>> {
59
+ /**
60
+ * The search query object.
61
+ *
62
+ * @see https://tanstack.com/query/latest/docs/framework/vue/reference/useQuery
63
+ */
64
+ query: UseQueryReturnType<SearchResult<TModelSchema>, Error>;
65
+ /**
66
+ * The available facets for the current search results.
67
+ */
68
+ getAvailableFacets: ComputedRef<SearchResultFacetGroup[]>;
69
+ /**
70
+ * The available facets for the current search results with localized titles.
71
+ */
72
+ getAvailableFacetsLocalized: (locale?: MaybeRefOrGetter<string>) => ComputedRef<SearchResultFacetGroupLocalized[]>;
73
+ /**
74
+ * The search results.
75
+ */
76
+ getSearchResults: ComputedRef<TModelSchema[] | undefined>;
77
+ /**
78
+ * The total number of search results.
79
+ */
80
+ getSearchResultsCount: ComputedRef<number>;
81
+ /**
82
+ * The total number of pages of search results based on the current limit parameter.
83
+ * If the limit is not set or is less than or equal to 0, this will return `NaN`.
84
+ */
85
+ getSearchResultsPagesCount: ComputedRef<number>;
86
+ /**
87
+ * Whether the search query is loading.
88
+ */
89
+ isLoading: ComputedRef<boolean>;
90
+ /**
91
+ * Whether the search query is fetching.
92
+ */
93
+ isFetching: ComputedRef<boolean>;
94
+ /**
95
+ * Get a facet by its ID.
96
+ */
97
+ getFacetById: <TFacetName extends TFacetNames>(facetId: TFacetName) => ComputedRef<SearchResultFacetGroup | undefined>;
98
+ /**
99
+ * Go to the next page of search results.
100
+ */
101
+ nextPage: () => void;
102
+ /**
103
+ * Go to the previous page of search results.
104
+ */
105
+ previousPage: () => void;
106
+ /**
107
+ * Convert a model schema to a computed object of getters.
108
+ */
109
+ toGetters: (data: MaybeRefOrGetter<TModelSchema>) => {
110
+ [K in keyof TGettersTree]: ComputedRef<ReturnType<TGettersTree[K]>>;
111
+ };
112
+ queryParams: SearchParamsRef;
113
+ }
114
+
115
+ type _UseQueryReturnType<TData, TError> = UseQueryReturnType<TData, TError>;
116
+ interface ResourceSearchOptions<TFacetNames extends string, TIndex extends string = string, TModelSchema = any> {
117
+ baseUrl: string;
118
+ index: TIndex;
119
+ indexDetails?: string;
120
+ facets?: TFacetNames[];
121
+ schema: z.Schema<TModelSchema>;
122
+ enabled?: MaybeRefOrGetter<boolean>;
123
+ locale?: MaybeRefOrGetter<string>;
124
+ dateFormatter?: (datetime: string) => string;
125
+ }
126
+ interface GeneralOptions<TModelSchema = any> {
127
+ fetcherFn?: (_options: {
128
+ params: SearchParams;
129
+ additionalParams?: any;
130
+ headers?: Record<string, string>;
131
+ }) => Promise<SearchResult<TModelSchema>>;
132
+ qc?: QueryClient;
133
+ }
134
+ interface CreateUseResourceSearchParams<TFacetNames extends string, TGettersTree extends GettersTree<TModelSchema, ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>>, TIndex extends string = string, TModelSchema = any> {
135
+ resourceSearchOptions: ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>;
136
+ options?: GeneralOptions<TModelSchema>;
137
+ getters: TGettersTree;
138
+ }
139
+ declare function defineResourceSearchOptions<TFacetNames extends string, TIndex extends string = string, TModelSchema = any>(options: ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>): ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>;
140
+ declare function createUseResourceSearch<TFacetNames extends string, TGettersTree extends GettersTree<TModelSchema, ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>>, TIndex extends string = string, TModelSchema = any>({ resourceSearchOptions, options, getters, }: CreateUseResourceSearchParams<TFacetNames, TGettersTree, TIndex, TModelSchema>): {
141
+ useSearch: (params: {
142
+ queryParams?: Reffify<SearchParamsBase> | undefined;
143
+ selectedFacets?: FacetRef<string> | undefined;
144
+ }) => UseSearchFactoryReturn<string, TModelSchema, TGettersTree>;
145
+ useResources: (params: {
146
+ queryParams?: Reffify<SearchParamsBase> | undefined;
147
+ selectedFacets?: FacetRef<string> | undefined;
148
+ }) => UseSearchFactoryReturn<string, TModelSchema, TGettersTree>;
149
+ useResource: (resourceId: MaybeRefOrGetter<string>) => UseResourceResult<TModelSchema, ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>, TGettersTree, UseResourceResultBase<TModelSchema, ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>, TGettersTree>>;
150
+ refSyncedWithRouteQuery: <T extends string | number, K = T extends string ? string : T extends number ? number : never>(searchParam: keyof SearchParamsBase, defaultValue?: MaybeRefOrGetter<T> | undefined) => Ref<K>;
151
+ refSyncedWithRouteQueryFacet: <T_1 extends string[]>(searchParam: TFacetNames, defaultValue?: MaybeRefOrGetter<T_1> | undefined) => Ref<string[]>;
152
+ getters: TGettersTree;
153
+ resourceSearchOptions: ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>;
154
+ };
155
+
156
+ interface PluginOptions {
157
+ queryClient?: QueryClient;
158
+ }
159
+ declare const keyQueryClient: InjectionKey<QueryClient>;
160
+ declare const plugin: {
161
+ install(app: App, options?: PluginOptions): void;
162
+ };
163
+ declare function useQueryClient(): QueryClient | null;
164
+
165
+ export { type CreateUseResourceSearchParams, type GeneralOptions, type PluginOptions, type ResourceSearchOptions, type _UseQueryReturnType, createUseResourceSearch, defineResourceSearchOptions, keyQueryClient, plugin, useQueryClient };
@@ -0,0 +1,165 @@
1
+ import { ToRef, Ref, ComputedRef, MaybeRef, MaybeRefOrGetter, InjectionKey, App } from 'vue-demi';
2
+ import { UseQueryReturnType, QueryClient } from '@tanstack/vue-query';
3
+ import { GetResourceResult, SearchParamsBase, SearchResult, SearchResultFacetGroup, SearchParams } from '@piveau/sdk-core';
4
+ import * as z from 'zod';
5
+
6
+ /**
7
+ * Type of an object of Getters that infers the argument.
8
+ */
9
+ type GettersTree<TModelSchema = any, TContext = any> = Record<string, ((data: TModelSchema, ctx?: TContext) => any) | (() => any)>;
10
+
11
+ type MyUseQueryReturnType<TD, TE> = UseQueryReturnType<TD, TE>;
12
+ interface UseResourceResultNoData<TModelSchema = any, TContext = any, TGetterTree extends GettersTree<TModelSchema, TContext> = GettersTree<TModelSchema, TContext>> {
13
+ isSuccess: false;
14
+ result: ComputedRef<undefined>;
15
+ getters: {
16
+ [K in keyof TGetterTree]: ComputedRef<null>;
17
+ };
18
+ }
19
+ interface UseResourceResultWithData<TModelSchema = any, TContext = any, TGetterTree extends GettersTree<TModelSchema, TContext> = GettersTree<TModelSchema, TContext>> {
20
+ isSuccess: true;
21
+ result: ComputedRef<TModelSchema>;
22
+ getters: {
23
+ [K in keyof TGetterTree]: ComputedRef<ReturnType<TGetterTree[K]>>;
24
+ };
25
+ }
26
+ type UseResourceResultBase<TModelSchema = any, TContext = any, TGetterTree extends GettersTree<TModelSchema, TContext> = GettersTree<TModelSchema, TContext>> = UseResourceResultNoData<TModelSchema, TContext, TGetterTree> | UseResourceResultWithData<TModelSchema, TContext, TGetterTree>;
27
+ type UseResourceResult<TModelSchema = any, TContext = any, TGetterTree extends GettersTree<TModelSchema, TContext> = GettersTree<TModelSchema, TContext>, TResult = UseResourceResultBase<TModelSchema, TContext, TGetterTree>> = {
28
+ [K in keyof TResult]: K extends 'isSuccess' ? ToRef<TResult[K]> : TResult[K];
29
+ } & {
30
+ query: MyUseQueryReturnType<GetResourceResult<TModelSchema>, Error>;
31
+ isLoading: Ref<boolean> | Ref<true> | Ref<false>;
32
+ isFetching: Ref<boolean>;
33
+ };
34
+
35
+ type Reffify<T> = {
36
+ [key in keyof T]: MaybeRef<T[key]>;
37
+ };
38
+ type SearchParamsRef = Reffify<SearchParamsBase>;
39
+ interface SelectedFacetItemObject {
40
+ id: string;
41
+ title: Record<string, string> | string;
42
+ count: number;
43
+ }
44
+ type SelectedFacetItemList = (SelectedFacetItemObject | string)[];
45
+ type FacetRef<TFacetNames extends string = string> = {
46
+ readonly [key in TFacetNames]?: MaybeRef<SelectedFacetItemList>;
47
+ };
48
+
49
+ interface SearchResultFacetGroupLocalized {
50
+ id: string;
51
+ title: string;
52
+ items: {
53
+ id: string;
54
+ title: string | undefined;
55
+ count: number;
56
+ }[];
57
+ }
58
+ interface UseSearchFactoryReturn<TFacetNames extends string, TModelSchema, TGettersTree extends GettersTree<TModelSchema, any>> {
59
+ /**
60
+ * The search query object.
61
+ *
62
+ * @see https://tanstack.com/query/latest/docs/framework/vue/reference/useQuery
63
+ */
64
+ query: UseQueryReturnType<SearchResult<TModelSchema>, Error>;
65
+ /**
66
+ * The available facets for the current search results.
67
+ */
68
+ getAvailableFacets: ComputedRef<SearchResultFacetGroup[]>;
69
+ /**
70
+ * The available facets for the current search results with localized titles.
71
+ */
72
+ getAvailableFacetsLocalized: (locale?: MaybeRefOrGetter<string>) => ComputedRef<SearchResultFacetGroupLocalized[]>;
73
+ /**
74
+ * The search results.
75
+ */
76
+ getSearchResults: ComputedRef<TModelSchema[] | undefined>;
77
+ /**
78
+ * The total number of search results.
79
+ */
80
+ getSearchResultsCount: ComputedRef<number>;
81
+ /**
82
+ * The total number of pages of search results based on the current limit parameter.
83
+ * If the limit is not set or is less than or equal to 0, this will return `NaN`.
84
+ */
85
+ getSearchResultsPagesCount: ComputedRef<number>;
86
+ /**
87
+ * Whether the search query is loading.
88
+ */
89
+ isLoading: ComputedRef<boolean>;
90
+ /**
91
+ * Whether the search query is fetching.
92
+ */
93
+ isFetching: ComputedRef<boolean>;
94
+ /**
95
+ * Get a facet by its ID.
96
+ */
97
+ getFacetById: <TFacetName extends TFacetNames>(facetId: TFacetName) => ComputedRef<SearchResultFacetGroup | undefined>;
98
+ /**
99
+ * Go to the next page of search results.
100
+ */
101
+ nextPage: () => void;
102
+ /**
103
+ * Go to the previous page of search results.
104
+ */
105
+ previousPage: () => void;
106
+ /**
107
+ * Convert a model schema to a computed object of getters.
108
+ */
109
+ toGetters: (data: MaybeRefOrGetter<TModelSchema>) => {
110
+ [K in keyof TGettersTree]: ComputedRef<ReturnType<TGettersTree[K]>>;
111
+ };
112
+ queryParams: SearchParamsRef;
113
+ }
114
+
115
+ type _UseQueryReturnType<TData, TError> = UseQueryReturnType<TData, TError>;
116
+ interface ResourceSearchOptions<TFacetNames extends string, TIndex extends string = string, TModelSchema = any> {
117
+ baseUrl: string;
118
+ index: TIndex;
119
+ indexDetails?: string;
120
+ facets?: TFacetNames[];
121
+ schema: z.Schema<TModelSchema>;
122
+ enabled?: MaybeRefOrGetter<boolean>;
123
+ locale?: MaybeRefOrGetter<string>;
124
+ dateFormatter?: (datetime: string) => string;
125
+ }
126
+ interface GeneralOptions<TModelSchema = any> {
127
+ fetcherFn?: (_options: {
128
+ params: SearchParams;
129
+ additionalParams?: any;
130
+ headers?: Record<string, string>;
131
+ }) => Promise<SearchResult<TModelSchema>>;
132
+ qc?: QueryClient;
133
+ }
134
+ interface CreateUseResourceSearchParams<TFacetNames extends string, TGettersTree extends GettersTree<TModelSchema, ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>>, TIndex extends string = string, TModelSchema = any> {
135
+ resourceSearchOptions: ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>;
136
+ options?: GeneralOptions<TModelSchema>;
137
+ getters: TGettersTree;
138
+ }
139
+ declare function defineResourceSearchOptions<TFacetNames extends string, TIndex extends string = string, TModelSchema = any>(options: ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>): ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>;
140
+ declare function createUseResourceSearch<TFacetNames extends string, TGettersTree extends GettersTree<TModelSchema, ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>>, TIndex extends string = string, TModelSchema = any>({ resourceSearchOptions, options, getters, }: CreateUseResourceSearchParams<TFacetNames, TGettersTree, TIndex, TModelSchema>): {
141
+ useSearch: (params: {
142
+ queryParams?: Reffify<SearchParamsBase> | undefined;
143
+ selectedFacets?: FacetRef<string> | undefined;
144
+ }) => UseSearchFactoryReturn<string, TModelSchema, TGettersTree>;
145
+ useResources: (params: {
146
+ queryParams?: Reffify<SearchParamsBase> | undefined;
147
+ selectedFacets?: FacetRef<string> | undefined;
148
+ }) => UseSearchFactoryReturn<string, TModelSchema, TGettersTree>;
149
+ useResource: (resourceId: MaybeRefOrGetter<string>) => UseResourceResult<TModelSchema, ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>, TGettersTree, UseResourceResultBase<TModelSchema, ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>, TGettersTree>>;
150
+ refSyncedWithRouteQuery: <T extends string | number, K = T extends string ? string : T extends number ? number : never>(searchParam: keyof SearchParamsBase, defaultValue?: MaybeRefOrGetter<T> | undefined) => Ref<K>;
151
+ refSyncedWithRouteQueryFacet: <T_1 extends string[]>(searchParam: TFacetNames, defaultValue?: MaybeRefOrGetter<T_1> | undefined) => Ref<string[]>;
152
+ getters: TGettersTree;
153
+ resourceSearchOptions: ResourceSearchOptions<TFacetNames, TIndex, TModelSchema>;
154
+ };
155
+
156
+ interface PluginOptions {
157
+ queryClient?: QueryClient;
158
+ }
159
+ declare const keyQueryClient: InjectionKey<QueryClient>;
160
+ declare const plugin: {
161
+ install(app: App, options?: PluginOptions): void;
162
+ };
163
+ declare function useQueryClient(): QueryClient | null;
164
+
165
+ export { type CreateUseResourceSearchParams, type GeneralOptions, type PluginOptions, type ResourceSearchOptions, type _UseQueryReturnType, createUseResourceSearch, defineResourceSearchOptions, keyQueryClient, plugin, useQueryClient };
package/dist/index.mjs ADDED
@@ -0,0 +1,479 @@
1
+ import { computed, toValue, reactive, toRefs, unref, markRaw, isVue2, inject } from 'vue-demi';
2
+ import { useQuery, keepPreviousData, QueryClient } from '@tanstack/vue-query';
3
+ import { getResourceById, searchResource } from '@piveau/sdk-core';
4
+ import { useRoute, useRouter } from 'vue-router';
5
+ import { useRouteQuery } from '@vueuse/router';
6
+ import { z } from 'zod';
7
+
8
+ function assertQueryClient(qc) {
9
+ if (!qc)
10
+ throw new Error("QueryClient not found");
11
+ }
12
+ function extractFacetId(facet) {
13
+ return typeof facet === "string" ? facet : facet.id;
14
+ }
15
+ function mapToFacetIds(facets) {
16
+ if (typeof facets === "string")
17
+ return [facets];
18
+ return facets.map(extractFacetId);
19
+ }
20
+ function getRepresentativeLocaleOf({ candidates = [], preferredLocale = [] }) {
21
+ if (candidates.length === 0)
22
+ return "";
23
+ if (!preferredLocale || preferredLocale.length === 0)
24
+ return candidates[0];
25
+ const priorityListArray = Array.isArray(preferredLocale) ? preferredLocale : [preferredLocale];
26
+ for (const locale of priorityListArray) {
27
+ if (candidates.includes(locale))
28
+ return locale;
29
+ }
30
+ return candidates[0];
31
+ }
32
+ function getTranslationFor(obj, preferredLocale) {
33
+ if (!obj)
34
+ return "";
35
+ const locale = getRepresentativeLocaleOf({ candidates: Object.keys(obj), preferredLocale });
36
+ if (!locale)
37
+ return "";
38
+ return obj[locale];
39
+ }
40
+
41
+ function toComputedGetters({
42
+ data,
43
+ getters,
44
+ ctx
45
+ }) {
46
+ return Object.keys(getters || {}).reduce(
47
+ (computedGetters, name) => {
48
+ const getter = getters[name];
49
+ computedGetters[name] = computed(() => {
50
+ const dataValue = toValue(data);
51
+ if (!dataValue)
52
+ return null;
53
+ if (typeof getter === "function")
54
+ return getter(dataValue, ctx);
55
+ });
56
+ return computedGetters;
57
+ },
58
+ {}
59
+ );
60
+ }
61
+
62
+ function useSearchQueryParams(initialValue) {
63
+ const queryParams = reactive({
64
+ ...{
65
+ aggregation: null,
66
+ aggregationAllFields: null,
67
+ aggregationFields: null,
68
+ autocomplete: null,
69
+ bboxMaxLat: null,
70
+ bboxMaxLon: null,
71
+ bboxMinLat: null,
72
+ bboxMinLon: null,
73
+ boost: null,
74
+ countryData: null,
75
+ dataServices: null,
76
+ facetGroupOperator: null,
77
+ facetOperator: null,
78
+ fields: null,
79
+ filterDistributions: null,
80
+ globalAggregation: null,
81
+ includes: null,
82
+ limit: null,
83
+ maxDate: null,
84
+ minDate: null,
85
+ maxScoring: null,
86
+ minScoring: null,
87
+ page: null,
88
+ q: null,
89
+ scroll: null,
90
+ sort: null,
91
+ showScore: null,
92
+ vocabulary: null,
93
+ superCatalogue: null
94
+ },
95
+ ...initialValue
96
+ });
97
+ function setFacetOperator(operator) {
98
+ queryParams.facetOperator = operator;
99
+ }
100
+ function setFacetGroupOperator(operator) {
101
+ queryParams.facetGroupOperator = operator;
102
+ }
103
+ function setDataServices(dataServices) {
104
+ queryParams.dataServices = dataServices;
105
+ }
106
+ function setSuperCatalogue(val) {
107
+ queryParams.superCatalogue = val;
108
+ }
109
+ function setPage(val) {
110
+ queryParams.page = val;
111
+ }
112
+ function setQuery(val) {
113
+ queryParams.q = val;
114
+ }
115
+ function setSort(val) {
116
+ queryParams.sort = val;
117
+ }
118
+ function setLimit(val) {
119
+ queryParams.limit = val;
120
+ }
121
+ function setDatasetGeoBounds({ minLon, minLat, maxLon, maxLat }) {
122
+ queryParams.bboxMinLon = minLon;
123
+ queryParams.bboxMinLat = minLat;
124
+ queryParams.bboxMaxLon = maxLon;
125
+ queryParams.bboxMaxLat = maxLat;
126
+ }
127
+ function setMinScoring(val) {
128
+ queryParams.minScoring = val;
129
+ }
130
+ function setCountryData(val) {
131
+ queryParams.countryData = val;
132
+ }
133
+ function setDataScope(val) {
134
+ queryParams.countryData = val;
135
+ }
136
+ return {
137
+ queryParams: toRefs(queryParams),
138
+ setFacetOperator,
139
+ setFacetGroupOperator,
140
+ setDataServices,
141
+ setSuperCatalogue,
142
+ setPage,
143
+ setQuery,
144
+ setSort,
145
+ setLimit,
146
+ setDatasetGeoBounds,
147
+ setMinScoring,
148
+ setCountryData,
149
+ setDataScope
150
+ };
151
+ }
152
+
153
+ function useSearchFactory(options) {
154
+ const useSearch = function({
155
+ queryParams = {},
156
+ selectedFacets = {},
157
+ additionalParams = {},
158
+ headers = {}
159
+ }) {
160
+ const {
161
+ index,
162
+ enabled = true,
163
+ qc,
164
+ ctx = {},
165
+ getters,
166
+ fetcherFn
167
+ } = options;
168
+ assertQueryClient(qc);
169
+ const { queryParams: resolvedQueryParams } = useSearchQueryParams(queryParams);
170
+ const reactiveQueryParams = reactive(resolvedQueryParams);
171
+ reactiveQueryParams.limit = reactiveQueryParams.limit || 10;
172
+ const computedSelectedFacetIds = computed(() => {
173
+ const facets = toValue(selectedFacets);
174
+ const keys = Object.keys(facets);
175
+ const selected = {};
176
+ for (const key of keys) {
177
+ const value = facets[key];
178
+ selected[key] = mapToFacetIds(unref(value));
179
+ }
180
+ return selected;
181
+ });
182
+ const query = useQuery(
183
+ {
184
+ queryKey: computed(() => [
185
+ "pv-resources",
186
+ index,
187
+ reactiveQueryParams,
188
+ unref(computedSelectedFacetIds),
189
+ toValue(additionalParams),
190
+ toValue(headers)
191
+ ]),
192
+ queryFn: async () => markRaw(await fetcherFn({
193
+ params: {
194
+ ...reactiveQueryParams,
195
+ facets: computedSelectedFacetIds.value
196
+ },
197
+ additionalParams: toValue(additionalParams),
198
+ headers: toValue(headers)
199
+ })),
200
+ placeholderData: keepPreviousData,
201
+ staleTime: 1e3 * 60 * 2,
202
+ enabled
203
+ },
204
+ qc
205
+ );
206
+ const getAvailableFacets = computed(() => {
207
+ if (query.isError.value)
208
+ return [];
209
+ const facets = query.data?.value?.result.facets;
210
+ return facets || [];
211
+ });
212
+ const getAvailableFacetsLocalized = (locale) => {
213
+ return computed(() => {
214
+ const resolvedLocale = toValue(locale) || ctx.locale || "en";
215
+ const facets = getAvailableFacets.value;
216
+ if (!facets)
217
+ return [];
218
+ return facets.map((facet) => {
219
+ return {
220
+ ...facet,
221
+ items: facet.items.map((item) => {
222
+ return {
223
+ ...item,
224
+ title: typeof item.title === "string" ? item.title : getTranslationFor(item.title, resolvedLocale)
225
+ };
226
+ })
227
+ };
228
+ });
229
+ });
230
+ };
231
+ const getSearchResults = computed(() => {
232
+ if (query.isError.value)
233
+ return void 0;
234
+ const results = query.data?.value?.result.results;
235
+ return results;
236
+ });
237
+ const getSearchResultsCount = computed(() => {
238
+ const count = query.data?.value?.result.count;
239
+ return count ?? 0;
240
+ });
241
+ const getSearchResultsPagesCount = computed(() => {
242
+ const count = getSearchResultsCount.value;
243
+ const limit = reactiveQueryParams.limit;
244
+ if (!limit || limit <= 0)
245
+ return Number.NaN;
246
+ return Math.ceil((count ?? 0) / limit);
247
+ });
248
+ const isLoading = computed(() => {
249
+ return query.isLoading.value;
250
+ });
251
+ const isFetching = computed(() => {
252
+ return query.isFetching.value;
253
+ });
254
+ function getFacetById(facetId) {
255
+ return computed(() => {
256
+ const facets = query.data?.value?.result.facets;
257
+ if (!facets)
258
+ return void 0;
259
+ const facet = facets?.find((f) => f.id === facetId);
260
+ if (!facet)
261
+ return void 0;
262
+ return {
263
+ ...facet,
264
+ id: facetId
265
+ };
266
+ });
267
+ }
268
+ function nextPage() {
269
+ reactiveQueryParams.page = (reactiveQueryParams.page || 0) + 1;
270
+ }
271
+ function previousPage() {
272
+ reactiveQueryParams.page = Math.max(
273
+ (reactiveQueryParams.page || 0) - 1,
274
+ 0
275
+ );
276
+ }
277
+ function toGetters(data) {
278
+ return toComputedGetters({
279
+ data,
280
+ getters,
281
+ ctx
282
+ });
283
+ }
284
+ const returnObject = {
285
+ query,
286
+ getAvailableFacets,
287
+ getSearchResults,
288
+ getSearchResultsCount,
289
+ getSearchResultsPagesCount,
290
+ isLoading,
291
+ isFetching,
292
+ getAvailableFacetsLocalized,
293
+ getFacetById,
294
+ nextPage,
295
+ previousPage,
296
+ toGetters,
297
+ queryParams: toRefs(reactiveQueryParams)
298
+ };
299
+ return returnObject;
300
+ };
301
+ return useSearch;
302
+ }
303
+
304
+ function useResourceFactory(options) {
305
+ function useResource(resourceId) {
306
+ const {
307
+ index,
308
+ indexSearch = index,
309
+ baseUrl,
310
+ enabled = true,
311
+ qc,
312
+ ctx = {},
313
+ getters = {},
314
+ fetcherFn
315
+ } = options;
316
+ assertQueryClient(qc);
317
+ const resolvedIndex = computed(() => index ?? indexSearch);
318
+ const query = useQuery(
319
+ {
320
+ queryKey: computed(() => [
321
+ "pv-resource",
322
+ toValue(resolvedIndex),
323
+ toValue(resourceId)
324
+ ]),
325
+ queryFn: async () => {
326
+ const a = await fetcherFn({
327
+ baseUrl,
328
+ resource: toValue(resolvedIndex),
329
+ id: toValue(resourceId)
330
+ });
331
+ return a;
332
+ },
333
+ // placeholderData: keepPreviousData,
334
+ initialData() {
335
+ const cache = qc.getQueriesData({
336
+ queryKey: computed(() => ["pv-resources", resolvedIndex.value])
337
+ });
338
+ const values = cache.flatMap(([_, value]) => value?.result?.results ?? []).find((resource) => {
339
+ const schema = z.object({
340
+ id: z.string()
341
+ });
342
+ const result2 = schema.safeParse(resource);
343
+ if (result2.success)
344
+ return result2.data.id === toValue(resourceId);
345
+ return false;
346
+ });
347
+ if (!values)
348
+ return void 0;
349
+ const resultObj = {
350
+ result: values,
351
+ success: true
352
+ };
353
+ return resultObj;
354
+ },
355
+ enabled
356
+ },
357
+ qc
358
+ );
359
+ const result = computed(() => query.data.value?.result);
360
+ const computedGetters = toComputedGetters({
361
+ data: computed(() => query.data.value?.result),
362
+ getters,
363
+ ctx
364
+ });
365
+ const obj = {
366
+ query,
367
+ isSuccess: query.isSuccess,
368
+ isLoading: query.isLoading,
369
+ isFetching: query.isFetching,
370
+ result,
371
+ getters: { ...computedGetters }
372
+ };
373
+ return obj;
374
+ }
375
+ return useResource;
376
+ }
377
+
378
+ function defineResourceSearchOptions(options) {
379
+ return options;
380
+ }
381
+ function createUseResourceSearch({
382
+ resourceSearchOptions,
383
+ options,
384
+ getters
385
+ }) {
386
+ async function defaultFetcherFn(_opts) {
387
+ const res = await searchResource({
388
+ baseUrl: resourceSearchOptions.baseUrl,
389
+ params: {
390
+ ..._opts.params,
391
+ filter: resourceSearchOptions.index
392
+ },
393
+ additionalParams: _opts.additionalParams,
394
+ headers: _opts.headers
395
+ });
396
+ return res.data;
397
+ }
398
+ const {
399
+ fetcherFn = defaultFetcherFn,
400
+ qc = useQueryClient()
401
+ } = options || {};
402
+ const {
403
+ schema
404
+ } = resourceSearchOptions;
405
+ assertQueryClient(qc);
406
+ const useSearch = useSearchFactory({
407
+ schema,
408
+ ctx: resourceSearchOptions,
409
+ getters,
410
+ index: resourceSearchOptions.index,
411
+ fetcherFn,
412
+ enabled: resourceSearchOptions.enabled,
413
+ qc
414
+ });
415
+ const useResource = useResourceFactory({
416
+ schema,
417
+ ctx: resourceSearchOptions,
418
+ getters,
419
+ baseUrl: resourceSearchOptions.baseUrl,
420
+ fetcherFn: async (_options) => {
421
+ return getResourceById(_options);
422
+ },
423
+ index: resourceSearchOptions.indexDetails ?? resourceSearchOptions.index,
424
+ indexSearch: resourceSearchOptions.index,
425
+ qc
426
+ });
427
+ function refSyncedWithRouteQuery(searchParam, defaultValue) {
428
+ const route = useRoute();
429
+ const router = useRouter();
430
+ return useRouteQuery(searchParam, defaultValue, {
431
+ router,
432
+ route,
433
+ mode: "push"
434
+ });
435
+ }
436
+ function refSyncedWithRouteQueryFacet(searchParam, defaultValue) {
437
+ const route = useRoute();
438
+ const router = useRouter();
439
+ return useRouteQuery(searchParam, defaultValue, {
440
+ // always transform the value to an array
441
+ transform: (value) => {
442
+ if (Array.isArray(value))
443
+ return value;
444
+ if (typeof value === "string")
445
+ return [value];
446
+ return [];
447
+ },
448
+ router,
449
+ route,
450
+ mode: "push"
451
+ });
452
+ }
453
+ return {
454
+ useSearch,
455
+ useResources: useSearch,
456
+ useResource,
457
+ refSyncedWithRouteQuery,
458
+ refSyncedWithRouteQueryFacet,
459
+ getters,
460
+ resourceSearchOptions
461
+ };
462
+ }
463
+
464
+ const keyQueryClient = Symbol("");
465
+ const plugin = {
466
+ install(app, options = {}) {
467
+ const qc = options.queryClient || new QueryClient();
468
+ if (isVue2)
469
+ throw new Error("Vue 2 is not supported");
470
+ else
471
+ app.provide(keyQueryClient, qc);
472
+ }
473
+ };
474
+ function useQueryClient() {
475
+ const maybeQueryClient = inject(keyQueryClient, null);
476
+ return maybeQueryClient;
477
+ }
478
+
479
+ export { createUseResourceSearch, defineResourceSearchOptions, keyQueryClient, plugin, useQueryClient };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@piveau/sdk-vue",
3
+ "type": "module",
4
+ "version": "0.0.0-alpha.0",
5
+ "private": false,
6
+ "description": "Asynchronous state management for interacting with piveau services using Vue",
7
+ "author": "Fraunhofer FOKUS",
8
+ "license": "Apache-2.0",
9
+ "keywords": [],
10
+ "sideEffects": false,
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs"
15
+ }
16
+ },
17
+ "main": "./dist/index.mjs",
18
+ "module": "./dist/index.mjs",
19
+ "types": "./dist/index.d.ts",
20
+ "typesVersions": {
21
+ "*": {
22
+ "*": [
23
+ "./dist/*",
24
+ "./dist/index.d.ts"
25
+ ]
26
+ }
27
+ },
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "scripts": {
32
+ "build": "unbuild",
33
+ "test": "echo \"Error: no test specified\" && exit 1"
34
+ },
35
+ "peerDependencies": {
36
+ "vue": ">=3.0.0",
37
+ "vue-router": ">=4.0.0"
38
+ },
39
+ "dependencies": {
40
+ "@piveau/sdk-core": "workspace:^",
41
+ "@tanstack/vue-query": "^5.24.1",
42
+ "@vueuse/core": "^10.8.0",
43
+ "@vueuse/router": "^10.9.0",
44
+ "pinia": "^2.1.7",
45
+ "vue-demi": ">=0.14.7",
46
+ "zod": "^3.22.4"
47
+ },
48
+ "devDependencies": {
49
+ "msw": "^2.2.1",
50
+ "typescript": "^5.3.3",
51
+ "unbuild": "^2.0.0",
52
+ "vue": "^3.4.20",
53
+ "vue-router": "^4.2.5"
54
+ }
55
+ }