@wp-typia/rest 0.3.4 → 0.3.6

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 CHANGED
@@ -37,6 +37,11 @@ const endpoint = createEndpoint<MyRequest, MyResponse>({
37
37
  const result = await callEndpoint(endpoint, { title: "Hello" });
38
38
  ```
39
39
 
40
+ `callEndpoint(...)` returns `EndpointValidationResult<Req, Res>`. If request
41
+ validation fails before transport execution, the result keeps
42
+ `validationTarget: "request"`. Response validation runs after transport and uses
43
+ `validationTarget: "response"`.
44
+
40
45
  If you need a canonical REST URL for a route path, use:
41
46
 
42
47
  ```ts
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<ValidationResult<Res>>;
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,4 +1,5 @@
1
- import { isFormDataLike, isPlainObject, toValidationResult, } from "./internal/runtime-primitives.js";
1
+ import { encodeGetLikeRequest, joinPathWithQuery, joinUrlWithQuery, mergeHeaderInputs, parseResponsePayload, } from "@wp-typia/api-client/client-utils";
2
+ import { isFormDataLike, toValidationResult, } from "./internal/runtime-primitives.js";
2
3
  export { isValidationResult, normalizeValidationError, toValidationResult } from "./internal/runtime-primitives.js";
3
4
  function getDefaultRestRoot() {
4
5
  if (typeof window !== "undefined") {
@@ -75,73 +76,6 @@ async function defaultFetch(options) {
75
76
  return text;
76
77
  }
77
78
  }
78
- async function parseResponsePayload(response) {
79
- if (response.status === 204) {
80
- return undefined;
81
- }
82
- const text = await response.text();
83
- if (!text) {
84
- return undefined;
85
- }
86
- try {
87
- return JSON.parse(text);
88
- }
89
- catch {
90
- return text;
91
- }
92
- }
93
- function encodeGetLikeRequest(request) {
94
- if (request === undefined || request === null) {
95
- return "";
96
- }
97
- if (request instanceof URLSearchParams) {
98
- return request.toString();
99
- }
100
- if (!isPlainObject(request)) {
101
- throw new Error("GET/DELETE endpoint requests must be plain objects or URLSearchParams.");
102
- }
103
- const params = new URLSearchParams();
104
- for (const [key, value] of Object.entries(request)) {
105
- if (value === undefined || value === null) {
106
- continue;
107
- }
108
- if (Array.isArray(value)) {
109
- for (const item of value) {
110
- params.append(key, String(item));
111
- }
112
- continue;
113
- }
114
- params.set(key, String(value));
115
- }
116
- return params.toString();
117
- }
118
- function joinPathWithQuery(path, query) {
119
- if (!query) {
120
- return path;
121
- }
122
- return path.includes("?") ? `${path}&${query}` : `${path}?${query}`;
123
- }
124
- function joinUrlWithQuery(url, query) {
125
- if (!query) {
126
- return url;
127
- }
128
- const nextUrl = new URL(url, typeof window !== "undefined" ? window.location.origin : "http://localhost");
129
- for (const [key, value] of new URLSearchParams(query)) {
130
- nextUrl.searchParams.append(key, value);
131
- }
132
- return nextUrl.toString();
133
- }
134
- function mergeHeaderInputs(baseHeaders, requestHeaders) {
135
- if (!baseHeaders && !requestHeaders) {
136
- return undefined;
137
- }
138
- const mergedHeaders = new Headers(baseHeaders);
139
- const nextHeaders = new Headers(requestHeaders);
140
- for (const [key, value] of nextHeaders.entries()) {
141
- mergedHeaders.set(key, value);
142
- }
143
- return Object.fromEntries(mergedHeaders.entries());
144
- }
145
79
  function buildEndpointFetchOptions(endpoint, request) {
146
80
  const baseOptions = endpoint.buildRequestOptions?.(request) ?? {};
147
81
  if (endpoint.method === "GET" || endpoint.method === "DELETE") {
@@ -235,11 +169,26 @@ export function createValidatedFetch(validator, fetchFn = defaultFetch) {
235
169
  export function createEndpoint(config) {
236
170
  return config;
237
171
  }
172
+ function isInvalidValidationResult(validation) {
173
+ return validation.isValid === false;
174
+ }
175
+ function toEndpointRequestValidationResult(validation) {
176
+ return {
177
+ ...validation,
178
+ validationTarget: "request",
179
+ };
180
+ }
181
+ function toEndpointResponseValidationResult(validation) {
182
+ return {
183
+ ...validation,
184
+ validationTarget: "response",
185
+ };
186
+ }
238
187
  export async function callEndpoint(endpoint, request, { fetchFn = defaultFetch, requestOptions } = {}) {
239
188
  const requestValidation = endpoint.validateRequest(request);
240
- if (!requestValidation.isValid) {
241
- return requestValidation;
189
+ if (isInvalidValidationResult(requestValidation)) {
190
+ return toEndpointRequestValidationResult(requestValidation);
242
191
  }
243
192
  const payload = await fetchFn(mergeFetchOptions(buildEndpointFetchOptions(endpoint, request), requestOptions));
244
- return endpoint.validateResponse(payload);
193
+ return toEndpointResponseValidationResult(endpoint.validateResponse(payload));
245
194
  }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
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
2
  export { createHeadersDecoder, createParameterDecoder, createQueryDecoder, } from "./http.js";
@@ -1 +1 @@
1
- export { isFormDataLike, isPlainObject, isValidationResult, normalizeExpected, normalizePath, normalizeValidationError, toValidationResult, type RawValidationError, type ValidationError, type ValidationLike, type ValidationResult, } from "@wp-typia/api-client/internal/runtime-primitives";
1
+ export { isFormDataLike, isPlainObject, isValidationResult, normalizeExpected, normalizePath, normalizeValidationError, toValidationResult, type RawValidationError, type ValidationError, type ValidationLike, type ValidationResult, } from "@wp-typia/api-client/runtime-primitives";
@@ -1 +1 @@
1
- export { isFormDataLike, isPlainObject, isValidationResult, normalizeExpected, normalizePath, normalizeValidationError, toValidationResult, } from "@wp-typia/api-client/internal/runtime-primitives";
1
+ export { isFormDataLike, isPlainObject, isValidationResult, normalizeExpected, normalizePath, normalizeValidationError, toValidationResult, } from "@wp-typia/api-client/runtime-primitives";
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 ValidationResult } from "./client.js";
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: ValidationResult<Res>) => void | Promise<void>;
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<ValidationResult<Res>>;
33
- validation: ValidationResult<Res> | null;
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: ValidationResult<Res>) => EndpointInvalidateTargets);
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: ValidationResult<Res> | null;
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: ValidationResult<Res>, client: EndpointDataClient, context: Context | undefined) => void | Promise<void>;
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<ValidationResult<Res>>;
54
+ mutateAsync: (variables: Req) => Promise<EndpointValidationResult<Req, Res>>;
55
55
  reset: () => void;
56
- validation: ValidationResult<Res> | null;
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
@@ -10,6 +10,18 @@ const EMPTY_SNAPSHOT = {
10
10
  validation: null,
11
11
  };
12
12
  const EndpointDataClientContext = createContext(null);
13
+ const endpointIdentityIds = new WeakMap();
14
+ let nextEndpointIdentityId = 0;
15
+ function getStableEndpointIdentity(value) {
16
+ const existing = endpointIdentityIds.get(value);
17
+ if (existing !== undefined) {
18
+ return existing;
19
+ }
20
+ const created = nextEndpointIdentityId;
21
+ nextEndpointIdentityId += 1;
22
+ endpointIdentityIds.set(value, created);
23
+ return created;
24
+ }
13
25
  function normalizeCacheValue(value) {
14
26
  if (value === undefined) {
15
27
  return undefined;
@@ -45,7 +57,18 @@ function normalizeCacheValue(value) {
45
57
  return String(value);
46
58
  }
47
59
  function createEndpointPrefix(endpoint) {
48
- return `${endpoint.method} ${endpoint.path}`;
60
+ const requestValidatorId = getStableEndpointIdentity(endpoint.validateRequest);
61
+ const responseValidatorId = getStableEndpointIdentity(endpoint.validateResponse);
62
+ const requestBuilderId = endpoint.buildRequestOptions !== undefined
63
+ ? getStableEndpointIdentity(endpoint.buildRequestOptions)
64
+ : -1;
65
+ return [
66
+ endpoint.method,
67
+ endpoint.path,
68
+ `request:${requestValidatorId}`,
69
+ `response:${responseValidatorId}`,
70
+ `builder:${requestBuilderId}`,
71
+ ].join(" ");
49
72
  }
50
73
  function createCacheKey(endpoint, request) {
51
74
  const requestValidation = endpoint.validateRequest(request);
@@ -103,6 +126,21 @@ function isEntryStale(entry, staleTime) {
103
126
  function asInternalClient(client) {
104
127
  return client;
105
128
  }
129
+ function isInvalidValidationResult(validation) {
130
+ return validation.isValid === false;
131
+ }
132
+ function toEndpointRequestValidationResult(validation) {
133
+ return {
134
+ ...validation,
135
+ validationTarget: "request",
136
+ };
137
+ }
138
+ function toEndpointResponseValidationResult(validation) {
139
+ return {
140
+ ...validation,
141
+ validationTarget: "response",
142
+ };
143
+ }
106
144
  export function createEndpointDataClient() {
107
145
  const entries = new Map();
108
146
  function notify(cacheKey) {
@@ -171,11 +209,11 @@ export function createEndpointDataClient() {
171
209
  entry.data = resolvedNext;
172
210
  entry.error = null;
173
211
  entry.updatedAt = Date.now();
174
- entry.validation = {
212
+ entry.validation = toEndpointResponseValidationResult({
175
213
  data: resolvedNext,
176
214
  errors: [],
177
215
  isValid: true,
178
- };
216
+ });
179
217
  syncSnapshot(entry);
180
218
  notify(cacheKey);
181
219
  },
@@ -194,6 +232,9 @@ export function createEndpointDataClient() {
194
232
  if (validation.isValid) {
195
233
  entry.data = validation.data;
196
234
  }
235
+ else if (validation.validationTarget === "request") {
236
+ entry.data = undefined;
237
+ }
197
238
  syncSnapshot(entry);
198
239
  notify(cacheKey);
199
240
  },
@@ -257,11 +298,11 @@ export function createEndpointDataClient() {
257
298
  entry.data = data;
258
299
  entry.error = null;
259
300
  entry.updatedAt = Date.now();
260
- entry.validation = {
301
+ entry.validation = toEndpointResponseValidationResult({
261
302
  data,
262
303
  errors: [],
263
304
  isValid: true,
264
- };
305
+ });
265
306
  syncSnapshot(entry);
266
307
  notify(cacheKey);
267
308
  },
@@ -328,8 +369,8 @@ export function useEndpointQuery(endpoint, request, options = {}) {
328
369
  if (!executeQueryRef.current) {
329
370
  executeQueryRef.current = async (force) => {
330
371
  const latest = latestRef.current;
331
- if (!latest.requestValidation.isValid) {
332
- const invalidValidation = latest.requestValidation;
372
+ if (isInvalidValidationResult(latest.requestValidation)) {
373
+ const invalidValidation = toEndpointRequestValidationResult(latest.requestValidation);
333
374
  latest.client.__publishValidation(latest.cacheKey, invalidValidation);
334
375
  return invalidValidation;
335
376
  }
@@ -371,11 +412,11 @@ export function useEndpointQuery(endpoint, request, options = {}) {
371
412
  if (!enabled) {
372
413
  return;
373
414
  }
374
- if (!prepared.requestValidation.isValid) {
415
+ if (isInvalidValidationResult(prepared.requestValidation)) {
375
416
  if (snapshot.validation?.isValid === false) {
376
417
  return;
377
418
  }
378
- client.__publishValidation(prepared.cacheKey, prepared.requestValidation);
419
+ client.__publishValidation(prepared.cacheKey, toEndpointRequestValidationResult(prepared.requestValidation));
379
420
  return;
380
421
  }
381
422
  if (snapshot.isFetching) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wp-typia/rest",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Typed WordPress REST helpers powered by Typia validation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -59,10 +59,10 @@
59
59
  "engines": {
60
60
  "node": ">=20.0.0",
61
61
  "npm": ">=10.0.0",
62
- "bun": ">=1.3.10"
62
+ "bun": ">=1.3.11"
63
63
  },
64
64
  "dependencies": {
65
- "@wp-typia/api-client": "^0.4.0",
65
+ "@wp-typia/api-client": "^0.4.3",
66
66
  "@typia/interface": "^12.0.1",
67
67
  "@wordpress/api-fetch": "^7.42.0"
68
68
  },