@wp-typia/rest 0.3.6 → 0.3.8

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
@@ -15,26 +15,42 @@ 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
- The root `@wp-typia/rest` entry stays transport-oriented. If you want query and
19
- mutation hooks on top of those WordPress helpers, use the React-only subpath:
18
+ ## Export contract
19
+
20
+ - `@wp-typia/rest`
21
+ Canonical convenience surface. It intentionally combines the transport helper
22
+ layer and the HTTP decoder helpers.
23
+ - `@wp-typia/rest/client`
24
+ Focused transport surface for endpoint creation, validated fetch helpers,
25
+ WordPress REST route resolution, validation utilities, and named runtime
26
+ errors.
27
+ - `@wp-typia/rest/http`
28
+ Focused decoder surface for query/header/parameter decoders plus the shared
29
+ validation helper utilities they return.
30
+ - `@wp-typia/rest/react`
31
+ React-only cache and hook layer.
32
+
33
+ Prefer the root entry when you want the shortest import path. Reach for
34
+ `./client` or `./http` when a narrower surface makes the consumer boundary
35
+ clearer. Query and mutation hooks still live on the React-only subpath:
20
36
 
21
37
  ```ts
22
- import { useEndpointMutation, useEndpointQuery } from "@wp-typia/rest/react";
38
+ import { useEndpointMutation, useEndpointQuery } from '@wp-typia/rest/react';
23
39
  ```
24
40
 
25
41
  Typical usage:
26
42
 
27
43
  ```ts
28
- import { callEndpoint, createEndpoint } from "@wp-typia/rest";
44
+ import { callEndpoint, createEndpoint } from '@wp-typia/rest';
29
45
 
30
46
  const endpoint = createEndpoint<MyRequest, MyResponse>({
31
- method: "POST",
32
- path: "/my-namespace/v1/demo",
47
+ method: 'POST',
48
+ path: '/my-namespace/v1/demo',
33
49
  validateRequest: validators.request,
34
50
  validateResponse: validators.response,
35
51
  });
36
52
 
37
- const result = await callEndpoint(endpoint, { title: "Hello" });
53
+ const result = await callEndpoint(endpoint, { title: 'Hello' });
38
54
  ```
39
55
 
40
56
  `callEndpoint(...)` returns `EndpointValidationResult<Req, Res>`. If request
@@ -42,22 +58,32 @@ validation fails before transport execution, the result keeps
42
58
  `validationTarget: "request"`. Response validation runs after transport and uses
43
59
  `validationTarget: "response"`.
44
60
 
61
+ Invalid request/response payloads stay in that result union. Thrown exceptions
62
+ are reserved for public runtime misconfiguration or assertion APIs:
63
+
64
+ - `RestConfigurationError`
65
+ - `RestRootResolutionError`
66
+ - `RestQueryHookUsageError`
67
+ - `RestValidationAssertionError`
68
+ - shared base classes re-exported from `@wp-typia/api-client`:
69
+ `WpTypiaContractError` and `WpTypiaValidationAssertionError`
70
+
45
71
  If you need a canonical REST URL for a route path, use:
46
72
 
47
73
  ```ts
48
- import { resolveRestRouteUrl } from "@wp-typia/rest";
74
+ import { resolveRestRouteUrl } from '@wp-typia/rest/client';
49
75
 
50
- const url = resolveRestRouteUrl("/my-namespace/v1/demo");
76
+ const url = resolveRestRouteUrl('/my-namespace/v1/demo');
51
77
  ```
52
78
 
53
79
  If you want Typia-powered HTTP decoding, compile the decoder in the consumer project and pass it in:
54
80
 
55
81
  ```ts
56
- import typia from "typia";
57
- import { createQueryDecoder } from "@wp-typia/rest";
82
+ import typia from 'typia';
83
+ import { createQueryDecoder } from '@wp-typia/rest/http';
58
84
 
59
85
  const decodeQuery = createQueryDecoder(
60
- typia.http.createValidateQuery<MyQuery>()
86
+ typia.http.createValidateQuery<MyQuery>(),
61
87
  );
62
88
  ```
63
89
 
@@ -76,17 +102,14 @@ The `./react` subpath adds a small cache client and React hook layer on top of
76
102
  non-query calls go through `useEndpointMutation(...)`.
77
103
 
78
104
  ```tsx
79
- import {
80
- useEndpointMutation,
81
- useEndpointQuery,
82
- } from "@wp-typia/rest/react";
105
+ import { useEndpointMutation, useEndpointQuery } from '@wp-typia/rest/react';
83
106
 
84
107
  const query = useEndpointQuery(stateEndpoint, request, {
85
108
  staleTime: 30_000,
86
109
  resolveCallOptions: () => ({
87
110
  requestOptions: {
88
111
  headers: {
89
- "X-WP-Nonce": resolveRestNonce(),
112
+ 'X-WP-Nonce': resolveRestNonce(),
90
113
  },
91
114
  },
92
115
  }),
@@ -97,7 +120,7 @@ const mutation = useEndpointMutation(writeStateEndpoint, {
97
120
  resolveCallOptions: () => ({
98
121
  requestOptions: {
99
122
  headers: {
100
- "X-WP-Nonce": resolveRestNonce(),
123
+ 'X-WP-Nonce': resolveRestNonce(),
101
124
  },
102
125
  },
103
126
  }),
package/dist/client.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { EndpointValidationResult } from "@wp-typia/api-client";
3
3
  import { type ValidationLike, type ValidationResult } from "./internal/runtime-primitives.js";
4
4
  export type { ValidationError, ValidationLike, ValidationResult } from "./internal/runtime-primitives.js";
5
5
  export { isValidationResult, normalizeValidationError, toValidationResult } from "./internal/runtime-primitives.js";
6
+ export { ApiClientConfigurationError, RestConfigurationError, RestRootResolutionError, RestValidationAssertionError, WpTypiaContractError, WpTypiaValidationAssertionError, } from "./errors.js";
6
7
  export type { EndpointRequestValidationResult, EndpointResponseValidationResult, EndpointValidationResult, EndpointValidationTarget, } from "@wp-typia/api-client";
7
8
  export interface ValidatedFetch<T> {
8
9
  assertFetch(options: APIFetchOptions): Promise<T>;
package/dist/client.js CHANGED
@@ -1,6 +1,8 @@
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";
5
+ export { ApiClientConfigurationError, RestConfigurationError, RestRootResolutionError, RestValidationAssertionError, WpTypiaContractError, WpTypiaValidationAssertionError, } from "./errors.js";
4
6
  function getDefaultRestRoot() {
5
7
  if (typeof window !== "undefined") {
6
8
  const wpApiSettings = window.wpApiSettings;
@@ -15,7 +17,7 @@ function getDefaultRestRoot() {
15
17
  }
16
18
  }
17
19
  }
18
- throw new Error("Unable to resolve the WordPress REST root automatically. Provide wpApiSettings.root, an api.w.org discovery link, or an explicit url.");
20
+ 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
21
  }
20
22
  export function resolveRestRouteUrl(routePath, root = getDefaultRestRoot()) {
21
23
  const [pathWithQuery, hash = ""] = routePath.split("#", 2);
@@ -50,7 +52,7 @@ function resolveFetchUrl(options) {
50
52
  if (typeof options.path === "string" && options.path.length > 0) {
51
53
  return resolveRestRouteUrl(options.path);
52
54
  }
53
- throw new Error("API fetch options must include either a path or a url.");
55
+ throw new RestConfigurationError("API fetch options must include either a path or a url.");
54
56
  }
55
57
  async function defaultFetch(options) {
56
58
  const response = await fetch(resolveFetchUrl(options), {
@@ -154,7 +156,7 @@ export function createValidatedFetch(validator, fetchFn = defaultFetch) {
154
156
  async assertFetch(options) {
155
157
  const result = await this.fetch(options);
156
158
  if (!result.isValid) {
157
- throw new Error(result.errors[0]
159
+ throw new RestValidationAssertionError(result, result.errors[0]
158
160
  ? `${result.errors[0].path}: ${result.errors[0].expected}`
159
161
  : "REST response validation failed.");
160
162
  }
@@ -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/http.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { IValidation } from "@typia/interface";
2
- import { type ValidationLike, type ValidationResult } from "./client.js";
2
+ import { type ValidationLike, type ValidationResult } from "./internal/runtime-primitives.js";
3
+ export type { ValidationError, ValidationLike, ValidationResult, } from "./internal/runtime-primitives.js";
4
+ export { isValidationResult, normalizeValidationError, toValidationResult, } from "./internal/runtime-primitives.js";
3
5
  export declare function createQueryDecoder<T extends object>(validate?: (input: string | URLSearchParams) => ValidationLike<T> | IValidation<T>): (input: unknown) => ValidationResult<T>;
4
6
  export declare function createHeadersDecoder<T extends object>(validate?: (input: Record<string, string | string[] | undefined>) => ValidationLike<T> | IValidation<T>): (input: unknown) => ValidationResult<T>;
5
7
  export declare function createParameterDecoder<T extends string | number | boolean | bigint | null>(): (input: string) => T;
package/dist/http.js CHANGED
@@ -1,4 +1,5 @@
1
- import { toValidationResult, } from "./client.js";
1
+ import { toValidationResult, } from "./internal/runtime-primitives.js";
2
+ export { isValidationResult, normalizeValidationError, toValidationResult, } from "./internal/runtime-primitives.js";
2
3
  function toHeadersRecord(input) {
3
4
  if (input instanceof Headers) {
4
5
  const record = {};
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
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
@@ -56,7 +56,7 @@ export interface UseEndpointMutationResult<Req, Res> {
56
56
  validation: EndpointValidationResult<Req, Res> | null;
57
57
  }
58
58
  export interface EndpointDataProviderProps {
59
- children?: unknown;
59
+ children?: Parameters<typeof createElement>[2];
60
60
  client: EndpointDataClient;
61
61
  }
62
62
  export declare function createEndpointDataClient(): EndpointDataClient;
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,
@@ -126,6 +127,20 @@ function isEntryStale(entry, staleTime) {
126
127
  function asInternalClient(client) {
127
128
  return client;
128
129
  }
130
+ function castEndpointValidationResult(validation) {
131
+ return validation;
132
+ }
133
+ function castEndpointValidationPromise(promise) {
134
+ return promise;
135
+ }
136
+ function selectEndpointData(data, select) {
137
+ if (select) {
138
+ return select(data);
139
+ }
140
+ // The default selector is identity, but TypeScript cannot express that when
141
+ // Selected is an unconstrained generic chosen by the caller.
142
+ return data;
143
+ }
129
144
  function isInvalidValidationResult(validation) {
130
145
  return validation.isValid === false;
131
146
  }
@@ -248,11 +263,11 @@ export function createEndpointDataClient() {
248
263
  async __runQuery(cacheKey, execute, { force = false, staleTime }) {
249
264
  const entry = getOrCreateEntry(entries, cacheKey);
250
265
  if (entry.promise) {
251
- return entry.promise;
266
+ return castEndpointValidationPromise(entry.promise);
252
267
  }
253
268
  if (!force) {
254
269
  if (!isEntryStale(entry, staleTime) && entry.validation) {
255
- return entry.validation;
270
+ return castEndpointValidationResult(entry.validation);
256
271
  }
257
272
  }
258
273
  entry.error = null;
@@ -288,7 +303,7 @@ export function createEndpointDataClient() {
288
303
  notify(cacheKey);
289
304
  });
290
305
  entry.promise = promise;
291
- return promise;
306
+ return castEndpointValidationPromise(promise);
292
307
  },
293
308
  __seedData(cacheKey, data) {
294
309
  const entry = getOrCreateEntry(entries, cacheKey);
@@ -318,14 +333,16 @@ export function createEndpointDataClient() {
318
333
  }
319
334
  const defaultEndpointDataClient = createEndpointDataClient();
320
335
  export function EndpointDataProvider({ children, client, }) {
321
- return createElement(EndpointDataClientContext.Provider, { value: client }, children);
336
+ return children === undefined
337
+ ? createElement(EndpointDataClientContext.Provider, { value: client })
338
+ : createElement(EndpointDataClientContext.Provider, { value: client }, children);
322
339
  }
323
340
  export function useEndpointDataClient() {
324
341
  return useContext(EndpointDataClientContext) ?? defaultEndpointDataClient;
325
342
  }
326
343
  export function useEndpointQuery(endpoint, request, options = {}) {
327
344
  if (endpoint.method !== "GET") {
328
- throw new Error("useEndpointQuery only supports GET endpoints in v1.");
345
+ throw new RestQueryHookUsageError("useEndpointQuery only supports GET endpoints in v1.");
329
346
  }
330
347
  const defaultClient = useEndpointDataClient();
331
348
  const client = asInternalClient(options.client ?? defaultClient);
@@ -381,9 +398,7 @@ export function useEndpointQuery(endpoint, request, options = {}) {
381
398
  requestOptions: callOptions?.requestOptions,
382
399
  }), { force, staleTime: latest.staleTime });
383
400
  if (validation.isValid) {
384
- const selected = latest.select !== undefined
385
- ? latest.select(validation.data)
386
- : validation.data;
401
+ const selected = selectEndpointData(validation.data, latest.select);
387
402
  await latest.onSuccess?.(selected, validation);
388
403
  }
389
404
  return validation;
@@ -458,9 +473,7 @@ export function useEndpointQuery(endpoint, request, options = {}) {
458
473
  if (rawData === undefined) {
459
474
  return undefined;
460
475
  }
461
- return select !== undefined
462
- ? select(rawData)
463
- : rawData;
476
+ return selectEndpointData(rawData, select);
464
477
  }, [initialData, select, snapshot.data, snapshot.validation]);
465
478
  return {
466
479
  data,
@@ -469,7 +482,9 @@ export function useEndpointQuery(endpoint, request, options = {}) {
469
482
  isLoading: snapshot.isFetching &&
470
483
  data === undefined,
471
484
  refetch,
472
- validation: snapshot.validation,
485
+ validation: snapshot.validation === null
486
+ ? null
487
+ : castEndpointValidationResult(snapshot.validation),
473
488
  };
474
489
  }
475
490
  export function useEndpointMutation(endpoint, options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wp-typia/rest",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "Typed WordPress REST helpers powered by Typia validation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,14 +12,14 @@
12
12
  "default": "./dist/index.js"
13
13
  },
14
14
  "./client": {
15
- "types": "./dist/index.d.ts",
16
- "import": "./dist/index.js",
17
- "default": "./dist/index.js"
15
+ "types": "./dist/client.d.ts",
16
+ "import": "./dist/client.js",
17
+ "default": "./dist/client.js"
18
18
  },
19
19
  "./http": {
20
- "types": "./dist/index.d.ts",
21
- "import": "./dist/index.js",
22
- "default": "./dist/index.js"
20
+ "types": "./dist/http.d.ts",
21
+ "import": "./dist/http.js",
22
+ "default": "./dist/http.js"
23
23
  },
24
24
  "./react": {
25
25
  "types": "./dist/react.d.ts",
@@ -62,7 +62,7 @@
62
62
  "bun": ">=1.3.11"
63
63
  },
64
64
  "dependencies": {
65
- "@wp-typia/api-client": "^0.4.3",
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
  },