@qualisero/openapi-endpoint 0.12.3 → 0.14.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.
@@ -1,118 +1,55 @@
1
- import { type ComputedRef, type Ref, type MaybeRefOrGetter } from 'vue';
2
- import { type GetPathParameters, type QMutationVars, type GetResponseData, type QMutationOptions, Operations, GetRequestBody } from './types';
3
- import { type OpenApiHelpers } from './openapi-helpers';
1
+ import { type ComputedRef, type Ref } from 'vue';
2
+ import type { MaybeRefOrGetter } from '@vue/reactivity';
4
3
  import { type AxiosResponse } from 'axios';
5
- export type EndpointMutationReturn<Ops extends Operations<Ops>, Op extends keyof Ops> = ReturnType<typeof useEndpointMutation<Ops, Op>>;
4
+ import { type EndpointConfig, type MutationOptions, type MutateFn, type MutateAsyncFn } from './types';
6
5
  /**
7
- * Composable for performing a strictly typed OpenAPI mutation operation using Vue Query.
8
- * Ensures the operation is a mutation (POST/PUT/PATCH/DELETE) at runtime.
9
- * Returns a reactive mutation object, including helpers for query key and enabled state.
6
+ * Return type of `useEndpointMutation` (the `useMutation` composable on a generated namespace).
10
7
  *
11
- * NOTE: By default, the mutation will automatically update cache with returned data and reload
12
- * any matching GET queries for the same path.
8
+ * @template TResponse Response data type
9
+ * @template TPathParams Path parameters type (concrete, required values)
10
+ * @template TRequest Request body type (`never` if none)
11
+ * @template TQueryParams Query parameters type
13
12
  *
14
- * @template T OperationId type representing the OpenAPI operation.
15
- * @param operationId The OpenAPI operation ID to mutate.
16
- * @param pathParams Optional path parameters for the endpoint, can be reactive.
17
- * @param options Optional mutation options, including Vue Query options and custom axios options:
18
- * - 'dontUpdateCache': If true, will not update cache with returned data (default: false)
19
- * - 'dontInvalidate': If true, will not invalidate matching GET queries (default: false)
20
- * - 'invalidateOperations': List of additional OperationIds to invalidate after mutation (can also be a map of OperationId to path parameters)
21
- * - 'refetchEndpoints': List of additional EndpointQueryReturn objects to refetch after mutation
22
- * - `axiosOptions`: Custom axios request options (e.g., headers, params)
23
- * - All properties from {@link UseMutationOptions} (from @tanstack/vue-query)
24
- * @throws Error if the operation is not a mutation operation.
25
- * @returns Mutation object with
26
- * - `data`: ComputedRef of response data.
27
- * - `isEnabled`: ComputedRef indicating if mutation can be executed (path resolved).
28
- * - `extraPathParams`: Ref to set of additional path parameters when calling mutate.
29
- * - `mutate` and `mutateAsync`: Functions to trigger the mutation, taking an object with:
30
- * - `data`: The request body data for the mutation.
31
- * - `pathParams`: Optional additional path parameters for the mutation.
32
- * - `axiosOptions`: Optional axios configuration overrides for this specific mutation call.
33
- * - `dontUpdateCache`, `dontInvalidate`, `invalidateOperations`, `refetchEndpoints`: Same as options, but can be set per-mutation.
34
- * - All other properties and methods from the underlying Vue Query mutation object.
13
+ * @group Types
35
14
  */
36
- export declare function useEndpointMutation<Ops extends Operations<Ops>, Op extends keyof Ops>(operationId: Op, h: OpenApiHelpers<Ops, Op>, // helpers
37
- pathParamsOrOptions?: MaybeRefOrGetter<GetPathParameters<Ops, Op> | null | undefined> | QMutationOptions<Ops, Op>, optionsOrNull?: QMutationOptions<Ops, Op>): {
38
- data: ComputedRef<AxiosResponse<GetResponseData<Ops, Op>> | undefined>;
15
+ export interface MutationReturn<TResponse, TPathParams extends Record<string, unknown> = Record<string, never>, TRequest = never, TQueryParams extends Record<string, unknown> = Record<string, never>> {
16
+ /** The Axios response (undefined until mutation completes). */
17
+ data: ComputedRef<AxiosResponse<TResponse> | undefined>;
18
+ /** The error if the mutation failed. */
19
+ error: Ref<Error | null>;
20
+ /** True while the mutation is in progress. */
21
+ isPending: Ref<boolean>;
22
+ /** True when the mutation succeeded. */
23
+ isSuccess: Ref<boolean>;
24
+ /** True when the mutation failed. */
25
+ isError: Ref<boolean>;
26
+ /** Execute the mutation (non-blocking). */
27
+ mutate: MutateFn<TPathParams, TRequest, TQueryParams>;
28
+ /** Execute the mutation and await the response. */
29
+ mutateAsync: MutateAsyncFn<TResponse, TPathParams, TRequest, TQueryParams>;
30
+ /** Reset the mutation state. */
31
+ reset: () => void;
32
+ /** Whether the mutation can execute (all required path params are resolved). */
39
33
  isEnabled: ComputedRef<boolean>;
40
- extraPathParams: Ref<GetPathParameters<Ops, Op>, GetPathParameters<Ops, Op>>;
41
- pathParams: ComputedRef<GetPathParameters<Ops, Op>>;
42
- context: Ref<unknown, unknown>;
43
- error: Ref<null, null>;
44
- isError: Ref<false, false>;
45
- isPending: Ref<false, false>;
46
- isSuccess: Ref<false, false>;
47
- status: Ref<"idle", "idle">;
48
- failureCount: Ref<number, number>;
49
- failureReason: Ref<Error | null, Error | null>;
50
- isPaused: Ref<boolean, boolean>;
51
- variables: Ref<undefined, undefined>;
52
- isIdle: Ref<true, true>;
53
- submittedAt: Ref<number, number>;
54
- mutate: (variables: GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, options?: import("@tanstack/query-core").MutateOptions<AxiosResponse<any, any, {}>, Error, GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, unknown> | undefined) => void;
55
- mutateAsync: import("@tanstack/query-core").MutateFunction<AxiosResponse<any, any, {}>, Error, GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, unknown>;
56
- reset: import("@tanstack/query-core").MutationObserverResult<TData, TError, TVariables, TOnMutateResult>["reset"];
57
- } | {
58
- data: ComputedRef<AxiosResponse<GetResponseData<Ops, Op>> | undefined>;
59
- isEnabled: ComputedRef<boolean>;
60
- extraPathParams: Ref<GetPathParameters<Ops, Op>, GetPathParameters<Ops, Op>>;
61
- pathParams: ComputedRef<GetPathParameters<Ops, Op>>;
62
- context: Ref<unknown, unknown>;
63
- error: Ref<null, null>;
64
- isError: Ref<false, false>;
65
- isPending: Ref<true, true>;
66
- isSuccess: Ref<false, false>;
67
- status: Ref<"pending", "pending">;
68
- failureCount: Ref<number, number>;
69
- failureReason: Ref<Error | null, Error | null>;
70
- isPaused: Ref<boolean, boolean>;
71
- variables: import("@vue/shared").IfAny<GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, Ref<GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>>, [GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>] extends [Ref<any, any>] ? Ref<any, any> & (GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>) : Ref<GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>>>;
72
- isIdle: Ref<false, false>;
73
- submittedAt: Ref<number, number>;
74
- mutate: (variables: GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, options?: import("@tanstack/query-core").MutateOptions<AxiosResponse<any, any, {}>, Error, GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, unknown> | undefined) => void;
75
- mutateAsync: import("@tanstack/query-core").MutateFunction<AxiosResponse<any, any, {}>, Error, GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, unknown>;
76
- reset: import("@tanstack/query-core").MutationObserverResult<TData, TError, TVariables, TOnMutateResult>["reset"];
77
- } | {
78
- data: ComputedRef<AxiosResponse<GetResponseData<Ops, Op>> | undefined>;
79
- isEnabled: ComputedRef<boolean>;
80
- extraPathParams: Ref<GetPathParameters<Ops, Op>, GetPathParameters<Ops, Op>>;
81
- pathParams: ComputedRef<GetPathParameters<Ops, Op>>;
82
- context: Ref<unknown, unknown>;
83
- error: Ref<Error, Error>;
84
- isError: Ref<true, true>;
85
- isPending: Ref<false, false>;
86
- isSuccess: Ref<false, false>;
87
- status: Ref<"error", "error">;
88
- failureCount: Ref<number, number>;
89
- failureReason: Ref<Error | null, Error | null>;
90
- isPaused: Ref<boolean, boolean>;
91
- variables: import("@vue/shared").IfAny<GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, Ref<GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>>, [GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>] extends [Ref<any, any>] ? Ref<any, any> & (GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>) : Ref<GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>>>;
92
- isIdle: Ref<false, false>;
93
- submittedAt: Ref<number, number>;
94
- mutate: (variables: GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, options?: import("@tanstack/query-core").MutateOptions<AxiosResponse<any, any, {}>, Error, GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, unknown> | undefined) => void;
95
- mutateAsync: import("@tanstack/query-core").MutateFunction<AxiosResponse<any, any, {}>, Error, GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, unknown>;
96
- reset: import("@tanstack/query-core").MutationObserverResult<TData, TError, TVariables, TOnMutateResult>["reset"];
97
- } | {
98
- data: ComputedRef<AxiosResponse<GetResponseData<Ops, Op>> | undefined>;
99
- isEnabled: ComputedRef<boolean>;
100
- extraPathParams: Ref<GetPathParameters<Ops, Op>, GetPathParameters<Ops, Op>>;
101
- pathParams: ComputedRef<GetPathParameters<Ops, Op>>;
102
- context: Ref<unknown, unknown>;
103
- error: Ref<null, null>;
104
- isError: Ref<false, false>;
105
- isPending: Ref<false, false>;
106
- isSuccess: Ref<true, true>;
107
- status: Ref<"success", "success">;
108
- failureCount: Ref<number, number>;
109
- failureReason: Ref<Error | null, Error | null>;
110
- isPaused: Ref<boolean, boolean>;
111
- variables: import("@vue/shared").IfAny<GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, Ref<GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>>, [GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>] extends [Ref<any, any>] ? Ref<any, any> & (GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>) : Ref<GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>>>;
112
- isIdle: Ref<false, false>;
113
- submittedAt: Ref<number, number>;
114
- mutate: (variables: GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, options?: import("@tanstack/query-core").MutateOptions<AxiosResponse<any, any, {}>, Error, GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, unknown> | undefined) => void;
115
- mutateAsync: import("@tanstack/query-core").MutateFunction<AxiosResponse<any, any, {}>, Error, GetRequestBody<Ops, Op> extends never ? void | QMutationVars<Ops, Op> : QMutationVars<Ops, Op>, unknown>;
116
- reset: import("@tanstack/query-core").MutationObserverResult<TData, TError, TVariables, TOnMutateResult>["reset"];
117
- };
34
+ /** The resolved path parameters. */
35
+ pathParams: ComputedRef<TPathParams>;
36
+ /** Additional path params that can be supplied at mutation time. */
37
+ extraPathParams: Ref<TPathParams>;
38
+ }
39
+ /**
40
+ * Execute a type-safe mutation (POST/PUT/PATCH/DELETE) with automatic cache management.
41
+ *
42
+ * This is a low-level primitive — in normal usage it is called by the generated
43
+ * per-operation `useMutation` wrappers in `api-client.ts`, not directly.
44
+ *
45
+ * @template TResponse The response data type
46
+ * @template TPathParams The path parameters type (concrete, required values)
47
+ * @template TRequest The request body type (`never` if no body)
48
+ * @template TQueryParams The query parameters type
49
+ *
50
+ * @param config Endpoint config: axios instance, queryClient, path, method, listPath, operationsRegistry
51
+ * @param pathParams Path parameters (reactive). Pass `undefined` for operations without path params.
52
+ * @param options Mutation options (dontInvalidate, refetchEndpoints, etc.)
53
+ */
54
+ export declare function useEndpointMutation<TResponse, TPathParams extends Record<string, unknown> = Record<string, never>, TRequest = never, TQueryParams extends Record<string, unknown> = Record<string, never>>(config: EndpointConfig, pathParams?: MaybeRefOrGetter<Record<string, string | number | undefined> | null | undefined>, options?: MutationOptions<TResponse, TPathParams, TRequest, TQueryParams>): MutationReturn<TResponse, TPathParams, TRequest, TQueryParams>;
118
55
  //# sourceMappingURL=openapi-mutation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"openapi-mutation.d.ts","sourceRoot":"","sources":["../src/openapi-mutation.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,WAAW,EAAE,KAAK,GAAG,EAAE,KAAK,gBAAgB,EAAE,MAAM,KAAK,CAAA;AAG/F,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EAErB,UAAU,EACV,cAAc,EACf,MAAM,SAAS,CAAA;AAEhB,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,OAAO,CAAA;AAE1C,MAAM,MAAM,sBAAsB,CAAC,GAAG,SAAS,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,MAAM,GAAG,IAAI,UAAU,CAChG,OAAO,mBAAmB,CAAC,GAAG,EAAE,EAAE,CAAC,CACpC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,SAAS,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,MAAM,GAAG,EACnF,WAAW,EAAE,EAAE,EACf,CAAC,EAAE,cAAc,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,UAAU;AACtC,mBAAmB,CAAC,EAAE,gBAAgB,CAAC,iBAAiB,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,gBAAgB,CAAC,GAAG,EAAE,EAAE,CAAC,EACjH,aAAa,CAAC,EAAE,gBAAgB,CAAC,GAAG,EAAE,EAAE,CAAC;UA2LhB,WAAW,CAAC,aAAa,CAAC,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;UAAhE,WAAW,CAAC,aAAa,CAAC,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;UAAhE,WAAW,CAAC,aAAa,CAAC,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;UAAhE,WAAW,CAAC,aAAa,CAAC,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;EAK1F"}
1
+ {"version":3,"file":"openapi-mutation.d.ts","sourceRoot":"","sources":["../src/openapi-mutation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,WAAW,EAAE,KAAK,GAAG,EAAE,MAAM,KAAK,CAAA;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAEvD,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,OAAO,CAAA;AAE1C,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,eAAe,EAEpB,KAAK,QAAQ,EACb,KAAK,aAAa,EAGnB,MAAM,SAAS,CAAA;AAShB;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc,CAC7B,SAAS,EACT,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EACnE,QAAQ,GAAG,KAAK,EAChB,YAAY,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAEpE,+DAA+D;IAC/D,IAAI,EAAE,WAAW,CAAC,aAAa,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC,CAAA;IACvD,wCAAwC;IACxC,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAA;IACxB,8CAA8C;IAC9C,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IACvB,wCAAwC;IACxC,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IACvB,qCAAqC;IACrC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IACrB,2CAA2C;IAC3C,MAAM,EAAE,QAAQ,CAAC,WAAW,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAA;IACrD,mDAAmD;IACnD,WAAW,EAAE,aAAa,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAA;IAC1E,gCAAgC;IAChC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,gFAAgF;IAChF,SAAS,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IAC/B,oCAAoC;IACpC,UAAU,EAAE,WAAW,CAAC,WAAW,CAAC,CAAA;IACpC,oEAAoE;IACpE,eAAe,EAAE,GAAG,CAAC,WAAW,CAAC,CAAA;CAClC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EACT,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EACnE,QAAQ,GAAG,KAAK,EAChB,YAAY,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAEpE,MAAM,EAAE,cAAc,EACtB,UAAU,CAAC,EAAE,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,EAC7F,OAAO,CAAC,EAAE,eAAe,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,CAAC,GACxE,cAAc,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,CAAC,CAuLhE"}
@@ -1,125 +1,82 @@
1
- import { computed, ref, toValue } from 'vue';
1
+ import { computed, ref } from 'vue';
2
2
  import { useMutation } from '@tanstack/vue-query';
3
- import { HttpMethod, } from './types.js';
4
- import { resolvePath, generateQueryKey, isPathResolved, getParamsOptionsFrom } from './openapi-utils.js';
3
+ import { HttpMethod, isMutationMethod, } from './types.js';
4
+ import { isPathResolved, normalizeParamsOptions, useResolvedOperation, resolvePath, generateQueryKey, } from './openapi-utils.js';
5
5
  /**
6
- * Composable for performing a strictly typed OpenAPI mutation operation using Vue Query.
7
- * Ensures the operation is a mutation (POST/PUT/PATCH/DELETE) at runtime.
8
- * Returns a reactive mutation object, including helpers for query key and enabled state.
6
+ * Execute a type-safe mutation (POST/PUT/PATCH/DELETE) with automatic cache management.
9
7
  *
10
- * NOTE: By default, the mutation will automatically update cache with returned data and reload
11
- * any matching GET queries for the same path.
8
+ * This is a low-level primitive in normal usage it is called by the generated
9
+ * per-operation `useMutation` wrappers in `api-client.ts`, not directly.
12
10
  *
13
- * @template T OperationId type representing the OpenAPI operation.
14
- * @param operationId The OpenAPI operation ID to mutate.
15
- * @param pathParams Optional path parameters for the endpoint, can be reactive.
16
- * @param options Optional mutation options, including Vue Query options and custom axios options:
17
- * - 'dontUpdateCache': If true, will not update cache with returned data (default: false)
18
- * - 'dontInvalidate': If true, will not invalidate matching GET queries (default: false)
19
- * - 'invalidateOperations': List of additional OperationIds to invalidate after mutation (can also be a map of OperationId to path parameters)
20
- * - 'refetchEndpoints': List of additional EndpointQueryReturn objects to refetch after mutation
21
- * - `axiosOptions`: Custom axios request options (e.g., headers, params)
22
- * - All properties from {@link UseMutationOptions} (from @tanstack/vue-query)
23
- * @throws Error if the operation is not a mutation operation.
24
- * @returns Mutation object with
25
- * - `data`: ComputedRef of response data.
26
- * - `isEnabled`: ComputedRef indicating if mutation can be executed (path resolved).
27
- * - `extraPathParams`: Ref to set of additional path parameters when calling mutate.
28
- * - `mutate` and `mutateAsync`: Functions to trigger the mutation, taking an object with:
29
- * - `data`: The request body data for the mutation.
30
- * - `pathParams`: Optional additional path parameters for the mutation.
31
- * - `axiosOptions`: Optional axios configuration overrides for this specific mutation call.
32
- * - `dontUpdateCache`, `dontInvalidate`, `invalidateOperations`, `refetchEndpoints`: Same as options, but can be set per-mutation.
33
- * - All other properties and methods from the underlying Vue Query mutation object.
11
+ * @template TResponse The response data type
12
+ * @template TPathParams The path parameters type (concrete, required values)
13
+ * @template TRequest The request body type (`never` if no body)
14
+ * @template TQueryParams The query parameters type
15
+ *
16
+ * @param config Endpoint config: axios instance, queryClient, path, method, listPath, operationsRegistry
17
+ * @param pathParams Path parameters (reactive). Pass `undefined` for operations without path params.
18
+ * @param options Mutation options (dontInvalidate, refetchEndpoints, etc.)
34
19
  */
35
- export function useEndpointMutation(operationId, h, // helpers
36
- pathParamsOrOptions, optionsOrNull) {
37
- // Runtime check to ensure this is actually a mutation operation
38
- if (!h.isMutationOperation(operationId)) {
39
- throw new Error(`Operation ${String(operationId)} is not a mutation operation (POST/PUT/PATCH/DELETE)`);
20
+ export function useEndpointMutation(config, pathParams, options) {
21
+ if (!isMutationMethod(config.method)) {
22
+ throw new Error(`Operation at '${config.path}' uses method ${config.method} and cannot be used with useMutation(). ` +
23
+ `Use useQuery() for GET/HEAD/OPTIONS operations.`);
40
24
  }
41
- const { path, method } = h.getOperationInfo(operationId);
42
- const { pathParams, options } = getParamsOptionsFrom(path, pathParamsOrOptions, optionsOrNull);
43
- const { axiosOptions, dontInvalidate, dontUpdateCache, invalidateOperations, refetchEndpoints, queryParams, ...useMutationOptions } = options;
25
+ const { pathParams: resolvedPathParamsInput, options: resolvedOptions } = normalizeParamsOptions(pathParams, options);
26
+ const { axiosOptions, dontInvalidate, dontUpdateCache, invalidateOperations, refetchEndpoints, queryParams, ...useMutationOptions } = resolvedOptions;
44
27
  const extraPathParams = ref({});
45
- // Compute the resolved path - same pattern as query
46
- // This ensures that when pathParams is a function, it gets called within the computed
47
- // so Vue can track dependencies of variables referenced inside the function
48
- const basePathParams = computed(() => {
49
- const result = toValue(pathParams);
50
- return result;
51
- });
52
- const allPathParams = computed(() => ({
53
- ...basePathParams.value,
54
- ...extraPathParams.value,
55
- }));
56
- const resolvedPath = computed(() => resolvePath(path, allPathParams.value));
57
- const queryKey = computed(() => generateQueryKey(resolvedPath.value));
58
- // Make query parameters reactive
59
- const allQueryParams = computed(() => {
60
- const result = toValue(queryParams);
61
- return result;
62
- });
28
+ const { resolvedPath, queryKey, queryParams: resolvedQueryParams, pathParams: allPathParams, } = useResolvedOperation(config.path, resolvedPathParamsInput, queryParams, extraPathParams);
63
29
  const mutation = useMutation({
64
30
  mutationFn: async (vars) => {
65
- const { data, pathParams: pathParamsFromMutate, axiosOptions: axiosOptionsFromMutate, queryParams: queryParamsFromMutate, } = vars;
66
- extraPathParams.value = pathParamsFromMutate || {};
67
- // TODO: use typing to ensure all required path params are provided
31
+ const { pathParams: pathParamsFromMutate, axiosOptions: axiosOptionsFromMutate, queryParams: queryParamsFromMutate, } = (vars || {});
32
+ const data = vars?.data;
33
+ extraPathParams.value = (pathParamsFromMutate || {});
68
34
  if (!isPathResolved(resolvedPath.value)) {
69
- return Promise.reject(new Error(`Mutation for '${String(operationId)}' cannot be used, as path is not resolved: ${resolvedPath.value} (params: ${JSON.stringify(allPathParams.value)})`));
35
+ return Promise.reject(new Error(`Cannot execute mutation at '${config.path}': path parameters not resolved. ` +
36
+ `Path: '${resolvedPath.value}', provided params: ${JSON.stringify(allPathParams.value)}`));
70
37
  }
71
- // Cancel any ongoing queries for this path (prevent race conditions with refresh)
72
- await h.queryClient.cancelQueries({ queryKey: queryKey.value, exact: false });
73
- return h.axios({
74
- method: method.toLowerCase(),
38
+ await config.queryClient.cancelQueries({ queryKey: queryKey.value, exact: false });
39
+ return config.axios({
40
+ method: config.method.toLowerCase(),
75
41
  url: resolvedPath.value,
76
42
  ...(data !== undefined && { data }),
77
43
  ...axiosOptions,
78
44
  ...axiosOptionsFromMutate,
79
45
  params: {
80
46
  ...(axiosOptions?.params || {}),
81
- ...(allQueryParams.value || {}),
47
+ ...(resolvedQueryParams.value || {}),
82
48
  ...(queryParamsFromMutate || {}),
83
49
  },
84
50
  });
85
51
  },
86
- onSuccess: async (response, vars, _context) => {
52
+ onSuccess: async (response, vars) => {
87
53
  const data = response.data;
88
- const { dontInvalidate: dontInvalidateMutate, dontUpdateCache: dontUpdateCacheMutate, invalidateOperations: invalidateOperationsMutate, refetchEndpoints: refetchEndpointsMutate, } = vars || {};
89
- // update cache with returned data for PUT/PATCH requests
90
- if (
91
- // dontUpdateCacheMutate supersedes dontUpdateCache from options
92
- (dontUpdateCacheMutate !== undefined ? !dontUpdateCacheMutate : !dontUpdateCache) &&
54
+ const { dontInvalidate: dontInvalidateMutate, dontUpdateCache: dontUpdateCacheMutate, invalidateOperations: invalidateOperationsMutate, refetchEndpoints: refetchEndpointsMutate, } = (vars || {});
55
+ // Update cache for PUT/PATCH
56
+ if ((dontUpdateCacheMutate !== undefined ? !dontUpdateCacheMutate : !dontUpdateCache) &&
93
57
  data &&
94
- [HttpMethod.PUT, HttpMethod.PATCH].includes(method)) {
95
- await h.queryClient.setQueryData(queryKey.value, data);
58
+ [HttpMethod.PUT, HttpMethod.PATCH].includes(config.method)) {
59
+ await config.queryClient.setQueryData(queryKey.value, data);
96
60
  }
97
- // Invalidate queries for this path, and any additional specified operations
61
+ // Invalidate queries for this path
98
62
  if (dontInvalidateMutate !== undefined ? !dontInvalidateMutate : !dontInvalidate) {
99
- // Invalidate all queries for this path (exact for POST, prefix for others):
100
- await h.queryClient.invalidateQueries({ queryKey: queryKey.value, exact: method !== HttpMethod.POST });
101
- const listPath = h.getListOperationPath(operationId);
102
- if (listPath) {
103
- const listResolvedPath = resolvePath(listPath, pathParams);
63
+ await config.queryClient.invalidateQueries({
64
+ queryKey: queryKey.value,
65
+ exact: config.method !== HttpMethod.POST,
66
+ });
67
+ // Invalidate associated list path
68
+ if (config.listPath) {
69
+ const listResolvedPath = resolvePath(config.listPath, resolvedPathParamsInput);
104
70
  if (isPathResolved(listResolvedPath)) {
105
71
  const listQueryKey = generateQueryKey(listResolvedPath);
106
- // Invalidate list queries by comparing normalized query keys.
107
- // For queries with query parameters (objects), strip the last element before comparing.
108
- // This matches:
109
- // - List queries without params: ["api", "user"]
110
- // - List queries with params: ["api", "user", {filter}] → normalized to ["api", "user"]
111
- // But NOT single-item queries where last element is a primitive:
112
- // - Single-item: ["api", "user", "uuid"] → kept as ["api", "user", "uuid"]
113
- await h.queryClient.invalidateQueries({
72
+ await config.queryClient.invalidateQueries({
114
73
  predicate: (query) => {
115
74
  const qKey = query.queryKey;
116
75
  if (!qKey || qKey.length === 0)
117
76
  return false;
118
- // Normalize query key: strip last element if it's an object (query params)
119
77
  const normalizedKey = typeof qKey[qKey.length - 1] === 'object' && qKey[qKey.length - 1] !== null
120
78
  ? qKey.slice(0, -1)
121
79
  : qKey;
122
- // Compare with listQueryKey
123
80
  if (normalizedKey.length !== listQueryKey.length)
124
81
  return false;
125
82
  for (let i = 0; i < listQueryKey.length; i++) {
@@ -132,47 +89,61 @@ pathParamsOrOptions, optionsOrNull) {
132
89
  }
133
90
  }
134
91
  }
92
+ // Resolve invalidateOperations entries using the registry
93
+ const registry = config.operationsRegistry || {};
94
+ const allInvalidateOps = [invalidateOperations, invalidateOperationsMutate].filter(Boolean);
135
95
  const operationsWithPathParams = [];
136
- Array.from([invalidateOperations, invalidateOperationsMutate]).forEach((ops) => {
137
- operationsWithPathParams.push(...(typeof ops === 'object' && !Array.isArray(ops)
138
- ? Object.entries(ops)
139
- : ops?.map((opId) => [opId, {}]) || []));
140
- });
96
+ for (const ops of allInvalidateOps) {
97
+ if (!ops)
98
+ continue;
99
+ if (Array.isArray(ops)) {
100
+ operationsWithPathParams.push(...ops.map((id) => [id, {}]));
101
+ }
102
+ else {
103
+ operationsWithPathParams.push(...Object.entries(ops).map(([id, params]) => [id, params]));
104
+ }
105
+ }
141
106
  if (operationsWithPathParams.length > 0) {
142
107
  const promises = operationsWithPathParams.map(([opId, opParams]) => {
143
- const opInfo = h.getOperationInfo(opId);
108
+ const opInfo = registry[opId];
109
+ if (!opInfo) {
110
+ console.warn(`Cannot invalidate operation '${opId}': not found in operations registry`);
111
+ return Promise.resolve();
112
+ }
144
113
  const opPath = resolvePath(opInfo.path, {
145
114
  ...allPathParams.value,
146
115
  ...opParams,
147
116
  });
148
117
  if (isPathResolved(opPath)) {
149
118
  const opQueryKey = generateQueryKey(opPath);
150
- return h.queryClient.invalidateQueries({ queryKey: opQueryKey, exact: true });
119
+ return config.queryClient.invalidateQueries({ queryKey: opQueryKey, exact: true });
151
120
  }
152
121
  else {
153
- console.warn(`Cannot invalidate operation '${String(opId)}', path not resolved: ${opPath} (params: ${JSON.stringify({ ...allPathParams.value, ...opParams })})`);
154
- return Promise.reject();
122
+ console.warn(`Cannot invalidate operation '${opId}', path not resolved: ${opPath}`);
123
+ return Promise.resolve();
155
124
  }
156
125
  });
157
126
  await Promise.all(promises);
158
127
  }
159
- if (refetchEndpoints && refetchEndpoints.length > 0) {
160
- await Promise.all(refetchEndpoints.map((endpoint) => endpoint.refetch()));
161
- }
162
- if (refetchEndpointsMutate && refetchEndpointsMutate.length > 0) {
163
- await Promise.all(refetchEndpointsMutate.map((endpoint) => endpoint.refetch()));
128
+ const allRefetch = [
129
+ ...(refetchEndpoints || []),
130
+ ...(vars?.refetchEndpoints || []),
131
+ ...(refetchEndpointsMutate || []),
132
+ ];
133
+ if (allRefetch.length > 0) {
134
+ await Promise.all(allRefetch.map((ep) => ep.refetch()));
164
135
  }
165
136
  },
166
137
  onSettled: () => {
167
138
  extraPathParams.value = {};
168
139
  },
169
140
  ...useMutationOptions,
170
- }, h.queryClient);
141
+ }, config.queryClient);
171
142
  return {
172
143
  ...mutation,
173
144
  data: mutation.data,
174
145
  isEnabled: computed(() => isPathResolved(resolvedPath.value)),
175
- extraPathParams,
146
+ extraPathParams: extraPathParams,
176
147
  pathParams: allPathParams,
177
148
  };
178
149
  }