@wp-typia/rest 0.3.5 → 0.3.7
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 +42 -16
- package/dist/client.d.ts +3 -1
- package/dist/client.js +22 -6
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +25 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/react.d.ts +11 -11
- package/dist/react.js +52 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -15,44 +15,73 @@ It does not include any WordPress PHP bridge logic. Generated PHP route code sta
|
|
|
15
15
|
If you need a backend-neutral consumer instead of WordPress-specific route
|
|
16
16
|
resolution, use `@wp-typia/api-client`.
|
|
17
17
|
|
|
18
|
+
## Export contract
|
|
19
|
+
|
|
20
|
+
- `@wp-typia/rest`
|
|
21
|
+
Canonical convenience surface for transport helpers plus HTTP decoder helpers.
|
|
22
|
+
- `@wp-typia/rest/client`
|
|
23
|
+
Backward-compatible alias of the root surface. It is not a distinct semantic
|
|
24
|
+
contract and may be removed in a future major once downstream imports settle.
|
|
25
|
+
- `@wp-typia/rest/http`
|
|
26
|
+
Backward-compatible alias of the root surface. It remains publishable for
|
|
27
|
+
existing imports, but it is not a distinct decoder-only contract in the
|
|
28
|
+
current major line.
|
|
29
|
+
- `@wp-typia/rest/react`
|
|
30
|
+
React-only cache and hook layer.
|
|
31
|
+
|
|
18
32
|
The root `@wp-typia/rest` entry stays transport-oriented. If you want query and
|
|
19
33
|
mutation hooks on top of those WordPress helpers, use the React-only subpath:
|
|
20
34
|
|
|
21
35
|
```ts
|
|
22
|
-
import { useEndpointMutation, useEndpointQuery } from
|
|
36
|
+
import { useEndpointMutation, useEndpointQuery } from '@wp-typia/rest/react';
|
|
23
37
|
```
|
|
24
38
|
|
|
25
39
|
Typical usage:
|
|
26
40
|
|
|
27
41
|
```ts
|
|
28
|
-
import { callEndpoint, createEndpoint } from
|
|
42
|
+
import { callEndpoint, createEndpoint } from '@wp-typia/rest';
|
|
29
43
|
|
|
30
44
|
const endpoint = createEndpoint<MyRequest, MyResponse>({
|
|
31
|
-
method:
|
|
32
|
-
path:
|
|
45
|
+
method: 'POST',
|
|
46
|
+
path: '/my-namespace/v1/demo',
|
|
33
47
|
validateRequest: validators.request,
|
|
34
48
|
validateResponse: validators.response,
|
|
35
49
|
});
|
|
36
50
|
|
|
37
|
-
const result = await callEndpoint(endpoint, { title:
|
|
51
|
+
const result = await callEndpoint(endpoint, { title: 'Hello' });
|
|
38
52
|
```
|
|
39
53
|
|
|
54
|
+
`callEndpoint(...)` returns `EndpointValidationResult<Req, Res>`. If request
|
|
55
|
+
validation fails before transport execution, the result keeps
|
|
56
|
+
`validationTarget: "request"`. Response validation runs after transport and uses
|
|
57
|
+
`validationTarget: "response"`.
|
|
58
|
+
|
|
59
|
+
Invalid request/response payloads stay in that result union. Thrown exceptions
|
|
60
|
+
are reserved for public runtime misconfiguration or assertion APIs:
|
|
61
|
+
|
|
62
|
+
- `RestConfigurationError`
|
|
63
|
+
- `RestRootResolutionError`
|
|
64
|
+
- `RestQueryHookUsageError`
|
|
65
|
+
- `RestValidationAssertionError`
|
|
66
|
+
- shared base classes re-exported from `@wp-typia/api-client`:
|
|
67
|
+
`WpTypiaContractError` and `WpTypiaValidationAssertionError`
|
|
68
|
+
|
|
40
69
|
If you need a canonical REST URL for a route path, use:
|
|
41
70
|
|
|
42
71
|
```ts
|
|
43
|
-
import { resolveRestRouteUrl } from
|
|
72
|
+
import { resolveRestRouteUrl } from '@wp-typia/rest';
|
|
44
73
|
|
|
45
|
-
const url = resolveRestRouteUrl(
|
|
74
|
+
const url = resolveRestRouteUrl('/my-namespace/v1/demo');
|
|
46
75
|
```
|
|
47
76
|
|
|
48
77
|
If you want Typia-powered HTTP decoding, compile the decoder in the consumer project and pass it in:
|
|
49
78
|
|
|
50
79
|
```ts
|
|
51
|
-
import typia from
|
|
52
|
-
import { createQueryDecoder } from
|
|
80
|
+
import typia from 'typia';
|
|
81
|
+
import { createQueryDecoder } from '@wp-typia/rest';
|
|
53
82
|
|
|
54
83
|
const decodeQuery = createQueryDecoder(
|
|
55
|
-
typia.http.createValidateQuery<MyQuery>()
|
|
84
|
+
typia.http.createValidateQuery<MyQuery>(),
|
|
56
85
|
);
|
|
57
86
|
```
|
|
58
87
|
|
|
@@ -71,17 +100,14 @@ The `./react` subpath adds a small cache client and React hook layer on top of
|
|
|
71
100
|
non-query calls go through `useEndpointMutation(...)`.
|
|
72
101
|
|
|
73
102
|
```tsx
|
|
74
|
-
import {
|
|
75
|
-
useEndpointMutation,
|
|
76
|
-
useEndpointQuery,
|
|
77
|
-
} from "@wp-typia/rest/react";
|
|
103
|
+
import { useEndpointMutation, useEndpointQuery } from '@wp-typia/rest/react';
|
|
78
104
|
|
|
79
105
|
const query = useEndpointQuery(stateEndpoint, request, {
|
|
80
106
|
staleTime: 30_000,
|
|
81
107
|
resolveCallOptions: () => ({
|
|
82
108
|
requestOptions: {
|
|
83
109
|
headers: {
|
|
84
|
-
|
|
110
|
+
'X-WP-Nonce': resolveRestNonce(),
|
|
85
111
|
},
|
|
86
112
|
},
|
|
87
113
|
}),
|
|
@@ -92,7 +118,7 @@ const mutation = useEndpointMutation(writeStateEndpoint, {
|
|
|
92
118
|
resolveCallOptions: () => ({
|
|
93
119
|
requestOptions: {
|
|
94
120
|
headers: {
|
|
95
|
-
|
|
121
|
+
'X-WP-Nonce': resolveRestNonce(),
|
|
96
122
|
},
|
|
97
123
|
},
|
|
98
124
|
}),
|
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { APIFetchOptions, ApiFetch } from "@wordpress/api-fetch";
|
|
2
|
+
import type { EndpointValidationResult } from "@wp-typia/api-client";
|
|
2
3
|
import { type ValidationLike, type ValidationResult } from "./internal/runtime-primitives.js";
|
|
3
4
|
export type { ValidationError, ValidationLike, ValidationResult } from "./internal/runtime-primitives.js";
|
|
4
5
|
export { isValidationResult, normalizeValidationError, toValidationResult } from "./internal/runtime-primitives.js";
|
|
6
|
+
export type { EndpointRequestValidationResult, EndpointResponseValidationResult, EndpointValidationResult, EndpointValidationTarget, } from "@wp-typia/api-client";
|
|
5
7
|
export interface ValidatedFetch<T> {
|
|
6
8
|
assertFetch(options: APIFetchOptions): Promise<T>;
|
|
7
9
|
fetch(options: APIFetchOptions): Promise<ValidationResult<T>>;
|
|
@@ -25,4 +27,4 @@ export interface EndpointCallOptions {
|
|
|
25
27
|
export declare function resolveRestRouteUrl(routePath: string, root?: string): string;
|
|
26
28
|
export declare function createValidatedFetch<T>(validator: (input: unknown) => ValidationLike<T>, fetchFn?: ApiFetch): ValidatedFetch<T>;
|
|
27
29
|
export declare function createEndpoint<Req, Res>(config: ApiEndpoint<Req, Res>): ApiEndpoint<Req, Res>;
|
|
28
|
-
export declare function callEndpoint<Req, Res>(endpoint: ApiEndpoint<Req, Res>, request: Req, { fetchFn, requestOptions }?: EndpointCallOptions): Promise<
|
|
30
|
+
export declare function callEndpoint<Req, Res>(endpoint: ApiEndpoint<Req, Res>, request: Req, { fetchFn, requestOptions }?: EndpointCallOptions): Promise<EndpointValidationResult<Req, Res>>;
|
package/dist/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { encodeGetLikeRequest, joinPathWithQuery, joinUrlWithQuery, mergeHeaderInputs, parseResponsePayload, } from "@wp-typia/api-client/client-utils";
|
|
2
2
|
import { isFormDataLike, toValidationResult, } from "./internal/runtime-primitives.js";
|
|
3
|
+
import { RestConfigurationError, RestRootResolutionError, RestValidationAssertionError, } from "./errors.js";
|
|
3
4
|
export { isValidationResult, normalizeValidationError, toValidationResult } from "./internal/runtime-primitives.js";
|
|
4
5
|
function getDefaultRestRoot() {
|
|
5
6
|
if (typeof window !== "undefined") {
|
|
@@ -15,7 +16,7 @@ function getDefaultRestRoot() {
|
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
|
-
throw new
|
|
19
|
+
throw new RestRootResolutionError("Unable to resolve the WordPress REST root automatically. Provide wpApiSettings.root, an api.w.org discovery link, or an explicit url.");
|
|
19
20
|
}
|
|
20
21
|
export function resolveRestRouteUrl(routePath, root = getDefaultRestRoot()) {
|
|
21
22
|
const [pathWithQuery, hash = ""] = routePath.split("#", 2);
|
|
@@ -50,7 +51,7 @@ function resolveFetchUrl(options) {
|
|
|
50
51
|
if (typeof options.path === "string" && options.path.length > 0) {
|
|
51
52
|
return resolveRestRouteUrl(options.path);
|
|
52
53
|
}
|
|
53
|
-
throw new
|
|
54
|
+
throw new RestConfigurationError("API fetch options must include either a path or a url.");
|
|
54
55
|
}
|
|
55
56
|
async function defaultFetch(options) {
|
|
56
57
|
const response = await fetch(resolveFetchUrl(options), {
|
|
@@ -154,7 +155,7 @@ export function createValidatedFetch(validator, fetchFn = defaultFetch) {
|
|
|
154
155
|
async assertFetch(options) {
|
|
155
156
|
const result = await this.fetch(options);
|
|
156
157
|
if (!result.isValid) {
|
|
157
|
-
throw new
|
|
158
|
+
throw new RestValidationAssertionError(result, result.errors[0]
|
|
158
159
|
? `${result.errors[0].path}: ${result.errors[0].expected}`
|
|
159
160
|
: "REST response validation failed.");
|
|
160
161
|
}
|
|
@@ -169,11 +170,26 @@ export function createValidatedFetch(validator, fetchFn = defaultFetch) {
|
|
|
169
170
|
export function createEndpoint(config) {
|
|
170
171
|
return config;
|
|
171
172
|
}
|
|
173
|
+
function isInvalidValidationResult(validation) {
|
|
174
|
+
return validation.isValid === false;
|
|
175
|
+
}
|
|
176
|
+
function toEndpointRequestValidationResult(validation) {
|
|
177
|
+
return {
|
|
178
|
+
...validation,
|
|
179
|
+
validationTarget: "request",
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function toEndpointResponseValidationResult(validation) {
|
|
183
|
+
return {
|
|
184
|
+
...validation,
|
|
185
|
+
validationTarget: "response",
|
|
186
|
+
};
|
|
187
|
+
}
|
|
172
188
|
export async function callEndpoint(endpoint, request, { fetchFn = defaultFetch, requestOptions } = {}) {
|
|
173
189
|
const requestValidation = endpoint.validateRequest(request);
|
|
174
|
-
if (
|
|
175
|
-
return requestValidation;
|
|
190
|
+
if (isInvalidValidationResult(requestValidation)) {
|
|
191
|
+
return toEndpointRequestValidationResult(requestValidation);
|
|
176
192
|
}
|
|
177
193
|
const payload = await fetchFn(mergeFetchOptions(buildEndpointFetchOptions(endpoint, request), requestOptions));
|
|
178
|
-
return endpoint.validateResponse(payload);
|
|
194
|
+
return toEndpointResponseValidationResult(endpoint.validateResponse(payload));
|
|
179
195
|
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { WpTypiaContractError, WpTypiaValidationAssertionError, type ValidationResult } from "@wp-typia/api-client";
|
|
2
|
+
export { ApiClientConfigurationError, WpTypiaContractError, WpTypiaValidationAssertionError, } from "@wp-typia/api-client";
|
|
3
|
+
export declare class RestConfigurationError extends WpTypiaContractError {
|
|
4
|
+
constructor(message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class RestRootResolutionError extends RestConfigurationError {
|
|
7
|
+
constructor(message: string);
|
|
8
|
+
}
|
|
9
|
+
export declare class RestQueryHookUsageError extends RestConfigurationError {
|
|
10
|
+
constructor(message: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class RestValidationAssertionError<T = unknown> extends WpTypiaValidationAssertionError<T> {
|
|
13
|
+
constructor(validation: ValidationResult<T>, message: string);
|
|
14
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { WpTypiaContractError, WpTypiaValidationAssertionError, } from "@wp-typia/api-client";
|
|
2
|
+
export { ApiClientConfigurationError, WpTypiaContractError, WpTypiaValidationAssertionError, } from "@wp-typia/api-client";
|
|
3
|
+
export class RestConfigurationError extends WpTypiaContractError {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message, "REST_CONFIGURATION_ERROR");
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class RestRootResolutionError extends RestConfigurationError {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = new.target.name;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class RestQueryHookUsageError extends RestConfigurationError {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = new.target.name;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class RestValidationAssertionError extends WpTypiaValidationAssertionError {
|
|
21
|
+
constructor(validation, message) {
|
|
22
|
+
super(validation, message);
|
|
23
|
+
this.name = new.target.name;
|
|
24
|
+
}
|
|
25
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export { createEndpoint, createValidatedFetch, callEndpoint, isValidationResult, normalizeValidationError, resolveRestRouteUrl, toValidationResult, type ApiEndpoint, type EndpointCallOptions, type ValidatedFetch, type ValidationResult, type ValidationError, type ValidationLike, } from "./client.js";
|
|
1
|
+
export { createEndpoint, createValidatedFetch, callEndpoint, isValidationResult, normalizeValidationError, resolveRestRouteUrl, toValidationResult, type ApiEndpoint, type EndpointCallOptions, type EndpointRequestValidationResult, type EndpointResponseValidationResult, type EndpointValidationResult, type EndpointValidationTarget, type ValidatedFetch, type ValidationResult, type ValidationError, type ValidationLike, } from "./client.js";
|
|
2
|
+
export { ApiClientConfigurationError, RestConfigurationError, RestQueryHookUsageError, RestRootResolutionError, RestValidationAssertionError, WpTypiaContractError, WpTypiaValidationAssertionError, } from "./errors.js";
|
|
2
3
|
export { createHeadersDecoder, createParameterDecoder, createQueryDecoder, } from "./http.js";
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { createEndpoint, createValidatedFetch, callEndpoint, isValidationResult, normalizeValidationError, resolveRestRouteUrl, toValidationResult, } from "./client.js";
|
|
2
|
+
export { ApiClientConfigurationError, RestConfigurationError, RestQueryHookUsageError, RestRootResolutionError, RestValidationAssertionError, WpTypiaContractError, WpTypiaValidationAssertionError, } from "./errors.js";
|
|
2
3
|
export { createHeadersDecoder, createParameterDecoder, createQueryDecoder, } from "./http.js";
|
package/dist/react.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createElement } from "@wordpress/element";
|
|
2
2
|
import type { ApiFetch } from "@wordpress/api-fetch";
|
|
3
|
-
import { type ApiEndpoint, type EndpointCallOptions, type
|
|
3
|
+
import { type ApiEndpoint, type EndpointCallOptions, type EndpointResponseValidationResult, type EndpointValidationResult } from "./client.js";
|
|
4
4
|
type EndpointDataUpdater<T> = T | ((current: T | undefined) => T | undefined);
|
|
5
5
|
export interface EndpointDataClient {
|
|
6
6
|
invalidate<Req, Res>(endpoint: ApiEndpoint<Req, Res>, request?: Req): void;
|
|
@@ -19,31 +19,31 @@ export interface UseEndpointQueryOptions<_Req, Res, Selected = Res> {
|
|
|
19
19
|
fetchFn?: ApiFetch;
|
|
20
20
|
initialData?: Res;
|
|
21
21
|
onError?: (error: unknown) => void | Promise<void>;
|
|
22
|
-
onSuccess?: (data: Selected, validation:
|
|
22
|
+
onSuccess?: (data: Selected, validation: EndpointResponseValidationResult<Res>) => void | Promise<void>;
|
|
23
23
|
resolveCallOptions?: () => EndpointCallOptions | undefined;
|
|
24
24
|
select?: (data: Res) => Selected;
|
|
25
25
|
staleTime?: number;
|
|
26
26
|
}
|
|
27
|
-
export interface UseEndpointQueryResult<Res, Selected = Res> {
|
|
27
|
+
export interface UseEndpointQueryResult<Res, Selected = Res, Req = unknown> {
|
|
28
28
|
data: Selected | undefined;
|
|
29
29
|
error: unknown;
|
|
30
30
|
isFetching: boolean;
|
|
31
31
|
isLoading: boolean;
|
|
32
|
-
refetch: () => Promise<
|
|
33
|
-
validation:
|
|
32
|
+
refetch: () => Promise<EndpointValidationResult<Req, Res>>;
|
|
33
|
+
validation: EndpointValidationResult<Req, Res> | null;
|
|
34
34
|
}
|
|
35
35
|
export interface UseEndpointMutationOptions<Req, Res, Context = unknown> {
|
|
36
36
|
client?: EndpointDataClient;
|
|
37
37
|
fetchFn?: ApiFetch;
|
|
38
|
-
invalidate?: EndpointInvalidateTargets | ((data: Res | undefined, variables: Req, validation:
|
|
38
|
+
invalidate?: EndpointInvalidateTargets | ((data: Res | undefined, variables: Req, validation: EndpointValidationResult<Req, Res>) => EndpointInvalidateTargets);
|
|
39
39
|
onError?: (error: unknown, variables: Req, client: EndpointDataClient, context: Context | undefined) => void | Promise<void>;
|
|
40
40
|
onMutate?: (variables: Req, client: EndpointDataClient) => Context | Promise<Context>;
|
|
41
41
|
onSettled?: (result: {
|
|
42
42
|
data: Res | undefined;
|
|
43
43
|
error: unknown;
|
|
44
|
-
validation:
|
|
44
|
+
validation: EndpointValidationResult<Req, Res> | null;
|
|
45
45
|
}, variables: Req, client: EndpointDataClient, context: Context | undefined) => void | Promise<void>;
|
|
46
|
-
onSuccess?: (data: Res | undefined, variables: Req, validation:
|
|
46
|
+
onSuccess?: (data: Res | undefined, variables: Req, validation: EndpointResponseValidationResult<Res>, client: EndpointDataClient, context: Context | undefined) => void | Promise<void>;
|
|
47
47
|
resolveCallOptions?: (variables: Req) => EndpointCallOptions | undefined;
|
|
48
48
|
}
|
|
49
49
|
export interface UseEndpointMutationResult<Req, Res> {
|
|
@@ -51,9 +51,9 @@ export interface UseEndpointMutationResult<Req, Res> {
|
|
|
51
51
|
error: unknown;
|
|
52
52
|
isPending: boolean;
|
|
53
53
|
mutate: (variables: Req) => void;
|
|
54
|
-
mutateAsync: (variables: Req) => Promise<
|
|
54
|
+
mutateAsync: (variables: Req) => Promise<EndpointValidationResult<Req, Res>>;
|
|
55
55
|
reset: () => void;
|
|
56
|
-
validation:
|
|
56
|
+
validation: EndpointValidationResult<Req, Res> | null;
|
|
57
57
|
}
|
|
58
58
|
export interface EndpointDataProviderProps {
|
|
59
59
|
children?: unknown;
|
|
@@ -62,6 +62,6 @@ export interface EndpointDataProviderProps {
|
|
|
62
62
|
export declare function createEndpointDataClient(): EndpointDataClient;
|
|
63
63
|
export declare function EndpointDataProvider({ children, client, }: EndpointDataProviderProps): ReturnType<typeof createElement>;
|
|
64
64
|
export declare function useEndpointDataClient(): EndpointDataClient;
|
|
65
|
-
export declare function useEndpointQuery<Req, Res, Selected = Res>(endpoint: ApiEndpoint<Req, Res>, request: Req, options?: UseEndpointQueryOptions<Req, Res, Selected>): UseEndpointQueryResult<Res, Selected>;
|
|
65
|
+
export declare function useEndpointQuery<Req, Res, Selected = Res>(endpoint: ApiEndpoint<Req, Res>, request: Req, options?: UseEndpointQueryOptions<Req, Res, Selected>): UseEndpointQueryResult<Res, Selected, Req>;
|
|
66
66
|
export declare function useEndpointMutation<Req, Res, Context = unknown>(endpoint: ApiEndpoint<Req, Res>, options?: UseEndpointMutationOptions<Req, Res, Context>): UseEndpointMutationResult<Req, Res>;
|
|
67
67
|
export {};
|
package/dist/react.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createContext, createElement, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from "@wordpress/element";
|
|
2
2
|
import { callEndpoint, } from "./client.js";
|
|
3
|
+
import { RestQueryHookUsageError } from "./errors.js";
|
|
3
4
|
import { isPlainObject } from "./internal/runtime-primitives.js";
|
|
4
5
|
const EMPTY_SNAPSHOT = {
|
|
5
6
|
data: undefined,
|
|
@@ -10,6 +11,18 @@ const EMPTY_SNAPSHOT = {
|
|
|
10
11
|
validation: null,
|
|
11
12
|
};
|
|
12
13
|
const EndpointDataClientContext = createContext(null);
|
|
14
|
+
const endpointIdentityIds = new WeakMap();
|
|
15
|
+
let nextEndpointIdentityId = 0;
|
|
16
|
+
function getStableEndpointIdentity(value) {
|
|
17
|
+
const existing = endpointIdentityIds.get(value);
|
|
18
|
+
if (existing !== undefined) {
|
|
19
|
+
return existing;
|
|
20
|
+
}
|
|
21
|
+
const created = nextEndpointIdentityId;
|
|
22
|
+
nextEndpointIdentityId += 1;
|
|
23
|
+
endpointIdentityIds.set(value, created);
|
|
24
|
+
return created;
|
|
25
|
+
}
|
|
13
26
|
function normalizeCacheValue(value) {
|
|
14
27
|
if (value === undefined) {
|
|
15
28
|
return undefined;
|
|
@@ -45,7 +58,18 @@ function normalizeCacheValue(value) {
|
|
|
45
58
|
return String(value);
|
|
46
59
|
}
|
|
47
60
|
function createEndpointPrefix(endpoint) {
|
|
48
|
-
|
|
61
|
+
const requestValidatorId = getStableEndpointIdentity(endpoint.validateRequest);
|
|
62
|
+
const responseValidatorId = getStableEndpointIdentity(endpoint.validateResponse);
|
|
63
|
+
const requestBuilderId = endpoint.buildRequestOptions !== undefined
|
|
64
|
+
? getStableEndpointIdentity(endpoint.buildRequestOptions)
|
|
65
|
+
: -1;
|
|
66
|
+
return [
|
|
67
|
+
endpoint.method,
|
|
68
|
+
endpoint.path,
|
|
69
|
+
`request:${requestValidatorId}`,
|
|
70
|
+
`response:${responseValidatorId}`,
|
|
71
|
+
`builder:${requestBuilderId}`,
|
|
72
|
+
].join(" ");
|
|
49
73
|
}
|
|
50
74
|
function createCacheKey(endpoint, request) {
|
|
51
75
|
const requestValidation = endpoint.validateRequest(request);
|
|
@@ -103,6 +127,21 @@ function isEntryStale(entry, staleTime) {
|
|
|
103
127
|
function asInternalClient(client) {
|
|
104
128
|
return client;
|
|
105
129
|
}
|
|
130
|
+
function isInvalidValidationResult(validation) {
|
|
131
|
+
return validation.isValid === false;
|
|
132
|
+
}
|
|
133
|
+
function toEndpointRequestValidationResult(validation) {
|
|
134
|
+
return {
|
|
135
|
+
...validation,
|
|
136
|
+
validationTarget: "request",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function toEndpointResponseValidationResult(validation) {
|
|
140
|
+
return {
|
|
141
|
+
...validation,
|
|
142
|
+
validationTarget: "response",
|
|
143
|
+
};
|
|
144
|
+
}
|
|
106
145
|
export function createEndpointDataClient() {
|
|
107
146
|
const entries = new Map();
|
|
108
147
|
function notify(cacheKey) {
|
|
@@ -171,11 +210,11 @@ export function createEndpointDataClient() {
|
|
|
171
210
|
entry.data = resolvedNext;
|
|
172
211
|
entry.error = null;
|
|
173
212
|
entry.updatedAt = Date.now();
|
|
174
|
-
entry.validation = {
|
|
213
|
+
entry.validation = toEndpointResponseValidationResult({
|
|
175
214
|
data: resolvedNext,
|
|
176
215
|
errors: [],
|
|
177
216
|
isValid: true,
|
|
178
|
-
};
|
|
217
|
+
});
|
|
179
218
|
syncSnapshot(entry);
|
|
180
219
|
notify(cacheKey);
|
|
181
220
|
},
|
|
@@ -194,6 +233,9 @@ export function createEndpointDataClient() {
|
|
|
194
233
|
if (validation.isValid) {
|
|
195
234
|
entry.data = validation.data;
|
|
196
235
|
}
|
|
236
|
+
else if (validation.validationTarget === "request") {
|
|
237
|
+
entry.data = undefined;
|
|
238
|
+
}
|
|
197
239
|
syncSnapshot(entry);
|
|
198
240
|
notify(cacheKey);
|
|
199
241
|
},
|
|
@@ -257,11 +299,11 @@ export function createEndpointDataClient() {
|
|
|
257
299
|
entry.data = data;
|
|
258
300
|
entry.error = null;
|
|
259
301
|
entry.updatedAt = Date.now();
|
|
260
|
-
entry.validation = {
|
|
302
|
+
entry.validation = toEndpointResponseValidationResult({
|
|
261
303
|
data,
|
|
262
304
|
errors: [],
|
|
263
305
|
isValid: true,
|
|
264
|
-
};
|
|
306
|
+
});
|
|
265
307
|
syncSnapshot(entry);
|
|
266
308
|
notify(cacheKey);
|
|
267
309
|
},
|
|
@@ -284,7 +326,7 @@ export function useEndpointDataClient() {
|
|
|
284
326
|
}
|
|
285
327
|
export function useEndpointQuery(endpoint, request, options = {}) {
|
|
286
328
|
if (endpoint.method !== "GET") {
|
|
287
|
-
throw new
|
|
329
|
+
throw new RestQueryHookUsageError("useEndpointQuery only supports GET endpoints in v1.");
|
|
288
330
|
}
|
|
289
331
|
const defaultClient = useEndpointDataClient();
|
|
290
332
|
const client = asInternalClient(options.client ?? defaultClient);
|
|
@@ -328,8 +370,8 @@ export function useEndpointQuery(endpoint, request, options = {}) {
|
|
|
328
370
|
if (!executeQueryRef.current) {
|
|
329
371
|
executeQueryRef.current = async (force) => {
|
|
330
372
|
const latest = latestRef.current;
|
|
331
|
-
if (
|
|
332
|
-
const invalidValidation = latest.requestValidation;
|
|
373
|
+
if (isInvalidValidationResult(latest.requestValidation)) {
|
|
374
|
+
const invalidValidation = toEndpointRequestValidationResult(latest.requestValidation);
|
|
333
375
|
latest.client.__publishValidation(latest.cacheKey, invalidValidation);
|
|
334
376
|
return invalidValidation;
|
|
335
377
|
}
|
|
@@ -371,11 +413,11 @@ export function useEndpointQuery(endpoint, request, options = {}) {
|
|
|
371
413
|
if (!enabled) {
|
|
372
414
|
return;
|
|
373
415
|
}
|
|
374
|
-
if (
|
|
416
|
+
if (isInvalidValidationResult(prepared.requestValidation)) {
|
|
375
417
|
if (snapshot.validation?.isValid === false) {
|
|
376
418
|
return;
|
|
377
419
|
}
|
|
378
|
-
client.__publishValidation(prepared.cacheKey, prepared.requestValidation);
|
|
420
|
+
client.__publishValidation(prepared.cacheKey, toEndpointRequestValidationResult(prepared.requestValidation));
|
|
379
421
|
return;
|
|
380
422
|
}
|
|
381
423
|
if (snapshot.isFetching) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wp-typia/rest",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Typed WordPress REST helpers powered by Typia validation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"bun": ">=1.3.11"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@wp-typia/api-client": "^0.4.
|
|
65
|
+
"@wp-typia/api-client": "^0.4.4",
|
|
66
66
|
"@typia/interface": "^12.0.1",
|
|
67
67
|
"@wordpress/api-fetch": "^7.42.0"
|
|
68
68
|
},
|