wp-native-client 0.0.1 → 0.0.2
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 +77 -24
- package/dist/abilities/discovery.d.ts +4 -0
- package/dist/abilities/discovery.d.ts.map +1 -1
- package/dist/abilities/discovery.js +7 -1
- package/dist/abilities/discovery.js.map +1 -1
- package/dist/abilities/types.d.ts +5 -5
- package/dist/abilities/types.js +1 -1
- package/dist/client.d.ts +4 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +53 -17
- package/dist/client.js.map +1 -1
- package/package.json +1 -1
- package/src/abilities/discovery.ts +8 -1
- package/src/abilities/types.ts +5 -5
- package/src/client.ts +67 -24
package/README.md
CHANGED
|
@@ -1,41 +1,94 @@
|
|
|
1
1
|
# wp-native-client
|
|
2
2
|
|
|
3
|
-
Universal WordPress client built on the Abilities API.
|
|
3
|
+
Universal WordPress client built on the [Abilities API](https://make.wordpress.org/core/). Discovery + execution. Works in React Native apps, Gutenberg blocks, and Node scripts — one client, three transports, one ability surface.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
# React Native or Node
|
|
9
|
+
npm install wp-native-client
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- `AuthTransport` — token lifecycle (load / save / refresh / clear)
|
|
13
|
-
- `WpApiFetchTransport` — adapter for `@wordpress/api-fetch` (Gutenberg blocks)
|
|
14
|
-
- *(post-v0.1)* `generateTypes()` — CLI tool to codegen TypeScript types from ability JSON schemas
|
|
11
|
+
# Gutenberg blocks — also import the /wordpress subpath (see below)
|
|
12
|
+
npm install wp-native-client
|
|
13
|
+
```
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
No peer dependencies. The main entry point uses plain `fetch`. The `/wordpress` subpath entry wraps `@wordpress/api-fetch` for block contexts.
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
## Quick start
|
|
19
18
|
|
|
20
19
|
```ts
|
|
21
|
-
import { WPNativeClient,
|
|
20
|
+
import { WPNativeClient, AuthFetchTransport } from 'wp-native-client';
|
|
22
21
|
|
|
23
|
-
const
|
|
24
|
-
baseUrl: 'https://
|
|
25
|
-
|
|
22
|
+
const transport = new AuthFetchTransport({
|
|
23
|
+
baseUrl: 'https://example.com/wp-json',
|
|
24
|
+
getDeviceId: () => 'your-uuid-v4',
|
|
25
|
+
loadTokens: async () => null,
|
|
26
|
+
saveTokens: async () => {},
|
|
27
|
+
clearTokens: async () => {},
|
|
28
|
+
onAuthFailure: () => console.log('session expired'),
|
|
26
29
|
});
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
await transport.initialize();
|
|
32
|
+
|
|
33
|
+
const client = new WPNativeClient(transport);
|
|
34
|
+
|
|
35
|
+
// Discovery — fetches the site's ability catalog
|
|
36
|
+
await client.discover();
|
|
37
|
+
|
|
38
|
+
// Execution — call any ability by name
|
|
39
|
+
const me = await client.execute('wp-native/auth-me');
|
|
40
|
+
const posts = await client.execute('wp/post.list', { per_page: 20 });
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Three transports
|
|
44
|
+
|
|
45
|
+
| Transport | Use when | Import |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| **`AuthFetchTransport`** | React Native apps, Node scripts — manages token lifecycle (load, save, refresh, clear) | `wp-native-client` |
|
|
48
|
+
| **`FetchTransport`** | Unauthenticated or externally-managed auth | `wp-native-client` |
|
|
49
|
+
| **`WpApiFetchTransport`** | Gutenberg blocks — wraps `@wordpress/api-fetch`, nonce handling by WP core | `wp-native-client/wordpress` |
|
|
30
50
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
```ts
|
|
52
|
+
// Gutenberg block example
|
|
53
|
+
import { WPNativeClient } from 'wp-native-client';
|
|
54
|
+
import { WpApiFetchTransport } from 'wp-native-client/wordpress';
|
|
55
|
+
import apiFetch from '@wordpress/api-fetch';
|
|
56
|
+
|
|
57
|
+
const client = new WPNativeClient(new WpApiFetchTransport(apiFetch));
|
|
58
|
+
await client.discover();
|
|
59
|
+
const posts = await client.execute('wp/post.list', { per_page: 10 });
|
|
35
60
|
```
|
|
36
61
|
|
|
37
|
-
|
|
62
|
+
## Public API summary
|
|
63
|
+
|
|
64
|
+
### Client
|
|
65
|
+
|
|
66
|
+
- **`WPNativeClient`** — the universal client. Wraps a transport, exposes `discover()`, `execute()`, `executeUnchecked()`, `describe()`, `catalog`.
|
|
67
|
+
- **`WPNativeClientConfig`** — optional config (`validateAbilityNames`).
|
|
68
|
+
|
|
69
|
+
### Transports
|
|
70
|
+
|
|
71
|
+
- **`AuthFetchTransport`** — authenticated fetch with automatic token refresh and 401 retry.
|
|
72
|
+
- **`FetchTransport`** — plain fetch, no auth.
|
|
73
|
+
- **`WpApiFetchTransport`** — wraps `@wordpress/api-fetch` for block contexts. Separate entry point: `wp-native-client/wordpress`.
|
|
74
|
+
- **`ApiError`** — structured error with `code`, `message`, `status`.
|
|
75
|
+
|
|
76
|
+
### Abilities
|
|
77
|
+
|
|
78
|
+
- **`discoverAbilities(transport, filter?)`** — walk the Abilities API, return an `AbilityCatalog`.
|
|
79
|
+
- **`AbilityCatalog`** — in-memory lookup of ability descriptors by name.
|
|
80
|
+
- **`AbilityDescriptor`** — metadata for a single ability (name, category, input/output schemas).
|
|
81
|
+
|
|
82
|
+
### Types
|
|
83
|
+
|
|
84
|
+
- **`StoredTokens`** — `{ accessToken, refreshToken, accessExpiresAt }` (expiry is a Unix timestamp in seconds).
|
|
85
|
+
- **`Transport`**, **`TransportRequest`**, **`TransportResponse`** — transport interface contracts.
|
|
86
|
+
- **`AuthFetchTransportConfig`**, **`FetchTransportConfig`** — transport configuration shapes.
|
|
87
|
+
|
|
88
|
+
## Broader project
|
|
89
|
+
|
|
90
|
+
wp-native-client is the abilities client layer of [wp-native](https://github.com/chubes4/wp-native) — an open-source framework for turning WordPress sites into real native apps. See the [main repo](https://github.com/chubes4/wp-native) for the shell, the auth plugin, and the full documentation.
|
|
38
91
|
|
|
39
|
-
##
|
|
92
|
+
## License
|
|
40
93
|
|
|
41
|
-
|
|
94
|
+
GPL-2.0-or-later — [github.com/chubes4/wp-native](https://github.com/chubes4/wp-native)
|
|
@@ -22,6 +22,10 @@ export declare function fetchAbilitiesPage(transport: Transport, params?: Abilit
|
|
|
22
22
|
* the whole catalog.
|
|
23
23
|
*/
|
|
24
24
|
export declare function fetchAbility(transport: Transport, name: string): Promise<AbilityDescriptor>;
|
|
25
|
+
/**
|
|
26
|
+
* Encode an ability name without escaping its namespace separator.
|
|
27
|
+
*/
|
|
28
|
+
export declare function encodeAbilityName(name: string): string;
|
|
25
29
|
/**
|
|
26
30
|
* Fetch all available ability categories.
|
|
27
31
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/abilities/discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EAChB,MAAM,SAAS,CAAC;AAMjB;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,SAAS,EACpB,MAAM,GAAE,iBAAsB,GAC7B,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAY9B;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,CAAC,CAK5B;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,eAAe,EAAE,CAAC,CAK5B;AAED;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,SAAS,EACpB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACpD,OAAO,CAAC,cAAc,CAAC,CA0BzB"}
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/abilities/discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EAChB,MAAM,SAAS,CAAC;AAMjB;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,SAAS,EACpB,MAAM,GAAE,iBAAsB,GAC7B,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAY9B;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,CAAC,CAK5B;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,eAAe,EAAE,CAAC,CAK5B;AAED;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,SAAS,EACpB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACpD,OAAO,CAAC,cAAc,CAAC,CA0BzB"}
|
|
@@ -35,10 +35,16 @@ export async function fetchAbilitiesPage(transport, params = {}) {
|
|
|
35
35
|
*/
|
|
36
36
|
export async function fetchAbility(transport, name) {
|
|
37
37
|
return transport.request({
|
|
38
|
-
path: `${ABILITIES_PATH}/${
|
|
38
|
+
path: `${ABILITIES_PATH}/${encodeAbilityName(name)}`,
|
|
39
39
|
method: 'GET',
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Encode an ability name without escaping its namespace separator.
|
|
44
|
+
*/
|
|
45
|
+
export function encodeAbilityName(name) {
|
|
46
|
+
return name.split('/').map(encodeURIComponent).join('/');
|
|
47
|
+
}
|
|
42
48
|
/**
|
|
43
49
|
* Fetch all available ability categories.
|
|
44
50
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/abilities/discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAO3C,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,cAAc,GAAG,2BAA2B,CAAC;AACnD,MAAM,eAAe,GAAG,4BAA4B,CAAC;AAErD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,SAAoB,EACpB,SAA4B,EAAE;IAE9B,MAAM,KAAK,GAAG,IAAI,eAAe,EAAE,CAAC;IACpC,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,IAAI,gBAAgB,CAAC,CAAC,CAAC;IAClE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5C,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAED,OAAO,SAAS,CAAC,OAAO,CAAsB;QAC5C,IAAI,EAAE,GAAG,cAAc,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE;QAC7C,MAAM,EAAE,KAAK;KACd,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,SAAoB,EACpB,IAAY;IAEZ,OAAO,SAAS,CAAC,OAAO,CAAoB;QAC1C,IAAI,EAAE,GAAG,cAAc,IAAI,
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/abilities/discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAO3C,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,cAAc,GAAG,2BAA2B,CAAC;AACnD,MAAM,eAAe,GAAG,4BAA4B,CAAC;AAErD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,SAAoB,EACpB,SAA4B,EAAE;IAE9B,MAAM,KAAK,GAAG,IAAI,eAAe,EAAE,CAAC;IACpC,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,IAAI,gBAAgB,CAAC,CAAC,CAAC;IAClE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5C,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAED,OAAO,SAAS,CAAC,OAAO,CAAsB;QAC5C,IAAI,EAAE,GAAG,cAAc,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE;QAC7C,MAAM,EAAE,KAAK;KACd,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,SAAoB,EACpB,IAAY;IAEZ,OAAO,SAAS,CAAC,OAAO,CAAoB;QAC1C,IAAI,EAAE,GAAG,cAAc,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE;QACpD,MAAM,EAAE,KAAK;KACd,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,SAAoB;IAEpB,OAAO,SAAS,CAAC,OAAO,CAAoB;QAC1C,IAAI,EAAE,eAAe;QACrB,MAAM,EAAE,KAAK;KACd,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAoB,EACpB,UAAmD,EAAE;IAErD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,gBAAgB,CAAC;IACpD,MAAM,GAAG,GAAwB,EAAE,CAAC;IACpC,IAAI,IAAI,GAAG,CAAC,CAAC;IAEb,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,MAAM,GAAsB,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QACpD,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YACnC,MAAM,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACrC,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,kBAAkB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAE1D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM;QACR,CAAC;QAED,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;QAEnB,IAAI,KAAK,CAAC,MAAM,GAAG,OAAO,EAAE,CAAC;YAC3B,MAAM;QACR,CAAC;QAED,IAAI,IAAI,CAAC,CAAC;IACZ,CAAC;IAED,OAAO,IAAI,cAAc,CAAC,GAAG,CAAC,CAAC;AACjC,CAAC"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Mirrors the response shapes documented at:
|
|
5
5
|
* GET /wp-abilities/v1/abilities
|
|
6
6
|
* GET /wp-abilities/v1/abilities/{name}
|
|
7
|
-
* POST /wp-abilities/v1/abilities/{name}/run
|
|
7
|
+
* GET|POST|DELETE /wp-abilities/v1/abilities/{name}/run
|
|
8
8
|
*
|
|
9
9
|
* These types describe the wire format. They are intentionally permissive
|
|
10
10
|
* about ability-specific input/output (modeled as `unknown`) because the
|
|
@@ -33,7 +33,7 @@ export interface AbilityDescriptor {
|
|
|
33
33
|
category: string;
|
|
34
34
|
/**
|
|
35
35
|
* JSON Schema describing the shape of the `input` argument expected by
|
|
36
|
-
*
|
|
36
|
+
* /abilities/{name}/run. May be an empty object for nullary abilities.
|
|
37
37
|
*/
|
|
38
38
|
input_schema: Record<string, unknown>;
|
|
39
39
|
/**
|
|
@@ -57,10 +57,10 @@ export interface AbilityCategory {
|
|
|
57
57
|
description: string;
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
|
-
*
|
|
60
|
+
* Legacy wrapped execution response type retained for source compatibility.
|
|
61
|
+
* WordPress currently returns the ability result directly from `/run`.
|
|
61
62
|
*
|
|
62
|
-
*
|
|
63
|
-
* a type parameter at the `client.execute<TResult>()` call site.
|
|
63
|
+
* @deprecated Use the result type passed to `client.execute<TResult>()`.
|
|
64
64
|
*/
|
|
65
65
|
export interface AbilityExecutionResponse<TResult = unknown> {
|
|
66
66
|
result: TResult;
|
package/dist/abilities/types.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Mirrors the response shapes documented at:
|
|
5
5
|
* GET /wp-abilities/v1/abilities
|
|
6
6
|
* GET /wp-abilities/v1/abilities/{name}
|
|
7
|
-
* POST /wp-abilities/v1/abilities/{name}/run
|
|
7
|
+
* GET|POST|DELETE /wp-abilities/v1/abilities/{name}/run
|
|
8
8
|
*
|
|
9
9
|
* These types describe the wire format. They are intentionally permissive
|
|
10
10
|
* about ability-specific input/output (modeled as `unknown`) because the
|
package/dist/client.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export declare class WPNativeClient {
|
|
|
48
48
|
private readonly transport;
|
|
49
49
|
private readonly config;
|
|
50
50
|
private _catalog;
|
|
51
|
+
private readonly descriptors;
|
|
51
52
|
constructor(transport: Transport, config?: WPNativeClientConfig);
|
|
52
53
|
/**
|
|
53
54
|
* Walk the Abilities API and populate the in-memory catalog.
|
|
@@ -81,11 +82,8 @@ export declare class WPNativeClient {
|
|
|
81
82
|
* Execute an ability by name.
|
|
82
83
|
*
|
|
83
84
|
* The ability is looked up in the catalog (when validateAbilityNames is
|
|
84
|
-
* enabled), then
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* The response shape `{ result: TResult }` is unwrapped — callers receive
|
|
88
|
-
* the result value directly.
|
|
85
|
+
* enabled), then sent to /wp-abilities/v1/abilities/{name}/run using the
|
|
86
|
+
* HTTP method required by the ability annotations.
|
|
89
87
|
*
|
|
90
88
|
* Throws:
|
|
91
89
|
* - Error if the ability is not in the catalog (and validation enabled)
|
|
@@ -101,6 +99,7 @@ export declare class WPNativeClient {
|
|
|
101
99
|
* the call site.
|
|
102
100
|
*/
|
|
103
101
|
executeUnchecked<TResult = unknown, TInput = unknown>(name: string, input?: TInput): Promise<TResult>;
|
|
102
|
+
private executeDescriptor;
|
|
104
103
|
/**
|
|
105
104
|
* Get a single ability descriptor from the catalog.
|
|
106
105
|
* Returns undefined if not registered or catalog not loaded.
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EACV,iBAAiB,EAClB,MAAM,mBAAmB,CAAC;AAI3B,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;;;OAUG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IACxD,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAwC;gBAExD,SAAS,EAAE,SAAS,EAAE,MAAM,GAAE,oBAAyB;IAOnE;;;;;;;;;OASG;IACG,QAAQ,CAAC,OAAO,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,cAAc,CAAC;IAa5E;;;;OAIG;IACH,IAAI,OAAO,IAAI,cAAc,CAO5B;IAED;;;OAGG;IACH,aAAa,IAAI,cAAc,GAAG,IAAI;IAItC;;OAEG;IACH,UAAU,IAAI,OAAO;IAIrB;;;;;;;;;;OAUG;IACG,OAAO,CAAC,OAAO,GAAG,OAAO,EAAE,MAAM,GAAG,OAAO,EAC/C,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC;IAoBnB;;;;;;;OAOG;IACG,gBAAgB,CAAC,OAAO,GAAG,OAAO,EAAE,MAAM,GAAG,OAAO,EACxD,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC;YAML,iBAAiB;IAgC/B;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;CAGtD"}
|
package/dist/client.js
CHANGED
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
* const posts = await client.execute<Post[]>('wp/post.list', { per_page: 20 });
|
|
28
28
|
* const me = await client.execute<User>('wp-native/user.me');
|
|
29
29
|
*/
|
|
30
|
-
import {
|
|
31
|
-
import { discoverAbilities } from './abilities/discovery';
|
|
30
|
+
import { discoverAbilities, encodeAbilityName, fetchAbility } from './abilities/discovery';
|
|
32
31
|
const RUN_PATH_PREFIX = 'wp-abilities/v1/abilities';
|
|
33
32
|
export class WPNativeClient {
|
|
34
33
|
transport;
|
|
35
34
|
config;
|
|
36
35
|
_catalog = null;
|
|
36
|
+
descriptors = new Map();
|
|
37
37
|
constructor(transport, config = {}) {
|
|
38
38
|
this.transport = transport;
|
|
39
39
|
this.config = {
|
|
@@ -57,6 +57,9 @@ export class WPNativeClient {
|
|
|
57
57
|
}
|
|
58
58
|
const catalog = await discoverAbilities(this.transport, filter);
|
|
59
59
|
this._catalog = catalog;
|
|
60
|
+
for (const ability of catalog.all()) {
|
|
61
|
+
this.descriptors.set(ability.name, ability);
|
|
62
|
+
}
|
|
60
63
|
return catalog;
|
|
61
64
|
}
|
|
62
65
|
/**
|
|
@@ -87,11 +90,8 @@ export class WPNativeClient {
|
|
|
87
90
|
* Execute an ability by name.
|
|
88
91
|
*
|
|
89
92
|
* The ability is looked up in the catalog (when validateAbilityNames is
|
|
90
|
-
* enabled), then
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* The response shape `{ result: TResult }` is unwrapped — callers receive
|
|
94
|
-
* the result value directly.
|
|
93
|
+
* enabled), then sent to /wp-abilities/v1/abilities/{name}/run using the
|
|
94
|
+
* HTTP method required by the ability annotations.
|
|
95
95
|
*
|
|
96
96
|
* Throws:
|
|
97
97
|
* - Error if the ability is not in the catalog (and validation enabled)
|
|
@@ -105,7 +105,12 @@ export class WPNativeClient {
|
|
|
105
105
|
`to bypass validation.`);
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
|
|
108
|
+
let descriptor = this._catalog?.get(name) ?? this.descriptors.get(name);
|
|
109
|
+
if (!descriptor) {
|
|
110
|
+
descriptor = await fetchAbility(this.transport, name);
|
|
111
|
+
this.descriptors.set(name, descriptor);
|
|
112
|
+
}
|
|
113
|
+
return this.executeDescriptor(descriptor, input);
|
|
109
114
|
}
|
|
110
115
|
/**
|
|
111
116
|
* Execute an ability without checking the catalog.
|
|
@@ -116,20 +121,35 @@ export class WPNativeClient {
|
|
|
116
121
|
* the call site.
|
|
117
122
|
*/
|
|
118
123
|
async executeUnchecked(name, input) {
|
|
119
|
-
const
|
|
124
|
+
const descriptor = await fetchAbility(this.transport, name);
|
|
125
|
+
this.descriptors.set(name, descriptor);
|
|
126
|
+
return this.executeDescriptor(descriptor, input);
|
|
127
|
+
}
|
|
128
|
+
async executeDescriptor(descriptor, input) {
|
|
129
|
+
const annotations = descriptor.meta?.annotations;
|
|
130
|
+
const annotationMap = annotations && typeof annotations === 'object' ? annotations : {};
|
|
131
|
+
const method = annotationMap.readonly
|
|
132
|
+
? 'GET'
|
|
133
|
+
: annotationMap.destructive && annotationMap.idempotent
|
|
134
|
+
? 'DELETE'
|
|
135
|
+
: 'POST';
|
|
136
|
+
let path = `${RUN_PATH_PREFIX}/${encodeAbilityName(descriptor.name)}/run`;
|
|
120
137
|
const body = {
|
|
121
138
|
input: input === undefined ? null : input,
|
|
122
139
|
};
|
|
123
|
-
|
|
140
|
+
if (method !== 'POST') {
|
|
141
|
+
const query = new URLSearchParams();
|
|
142
|
+
appendQueryValue(query, 'input', body.input);
|
|
143
|
+
const queryString = query.toString();
|
|
144
|
+
if (queryString) {
|
|
145
|
+
path += `?${queryString}`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return this.transport.request({
|
|
124
149
|
path,
|
|
125
|
-
method
|
|
126
|
-
body: body,
|
|
150
|
+
method,
|
|
151
|
+
...(method === 'POST' ? { body: body } : {}),
|
|
127
152
|
});
|
|
128
|
-
if (!response || typeof response !== 'object' || !('result' in response)) {
|
|
129
|
-
throw new ApiError(`WPNativeClient: malformed ability response for "${name}". ` +
|
|
130
|
-
`Expected { result: ... }.`, 'malformed_ability_response', 500);
|
|
131
|
-
}
|
|
132
|
-
return response.result;
|
|
133
153
|
}
|
|
134
154
|
/**
|
|
135
155
|
* Get a single ability descriptor from the catalog.
|
|
@@ -139,4 +159,20 @@ export class WPNativeClient {
|
|
|
139
159
|
return this._catalog?.get(name);
|
|
140
160
|
}
|
|
141
161
|
}
|
|
162
|
+
function appendQueryValue(query, key, value) {
|
|
163
|
+
if (value === null || value === undefined) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (Array.isArray(value)) {
|
|
167
|
+
value.forEach((item, index) => appendQueryValue(query, `${key}[${index}]`, item));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (typeof value === 'object') {
|
|
171
|
+
Object.entries(value).forEach(([childKey, childValue]) => {
|
|
172
|
+
appendQueryValue(query, `${key}[${childKey}]`, childValue);
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
query.append(key, String(value));
|
|
177
|
+
}
|
|
142
178
|
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wp-native-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Universal WordPress client built on the Abilities API. Discovery + execution. Works in WordPress blocks, React Native, and Node — one client, three transports, one ability surface.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -52,11 +52,18 @@ export async function fetchAbility(
|
|
|
52
52
|
name: string,
|
|
53
53
|
): Promise<AbilityDescriptor> {
|
|
54
54
|
return transport.request<AbilityDescriptor>({
|
|
55
|
-
path: `${ABILITIES_PATH}/${
|
|
55
|
+
path: `${ABILITIES_PATH}/${encodeAbilityName(name)}`,
|
|
56
56
|
method: 'GET',
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Encode an ability name without escaping its namespace separator.
|
|
62
|
+
*/
|
|
63
|
+
export function encodeAbilityName(name: string): string {
|
|
64
|
+
return name.split('/').map(encodeURIComponent).join('/');
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
/**
|
|
61
68
|
* Fetch all available ability categories.
|
|
62
69
|
*/
|
package/src/abilities/types.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Mirrors the response shapes documented at:
|
|
5
5
|
* GET /wp-abilities/v1/abilities
|
|
6
6
|
* GET /wp-abilities/v1/abilities/{name}
|
|
7
|
-
* POST /wp-abilities/v1/abilities/{name}/run
|
|
7
|
+
* GET|POST|DELETE /wp-abilities/v1/abilities/{name}/run
|
|
8
8
|
*
|
|
9
9
|
* These types describe the wire format. They are intentionally permissive
|
|
10
10
|
* about ability-specific input/output (modeled as `unknown`) because the
|
|
@@ -38,7 +38,7 @@ export interface AbilityDescriptor {
|
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* JSON Schema describing the shape of the `input` argument expected by
|
|
41
|
-
*
|
|
41
|
+
* /abilities/{name}/run. May be an empty object for nullary abilities.
|
|
42
42
|
*/
|
|
43
43
|
input_schema: Record<string, unknown>;
|
|
44
44
|
|
|
@@ -66,10 +66,10 @@ export interface AbilityCategory {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* Legacy wrapped execution response type retained for source compatibility.
|
|
70
|
+
* WordPress currently returns the ability result directly from `/run`.
|
|
70
71
|
*
|
|
71
|
-
*
|
|
72
|
-
* a type parameter at the `client.execute<TResult>()` call site.
|
|
72
|
+
* @deprecated Use the result type passed to `client.execute<TResult>()`.
|
|
73
73
|
*/
|
|
74
74
|
export interface AbilityExecutionResponse<TResult = unknown> {
|
|
75
75
|
result: TResult;
|
package/src/client.ts
CHANGED
|
@@ -29,12 +29,10 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import type { Transport } from './transports/types';
|
|
32
|
-
import { ApiError } from './transports/fetch';
|
|
33
32
|
import { AbilityCatalog } from './abilities/catalog';
|
|
34
|
-
import { discoverAbilities } from './abilities/discovery';
|
|
33
|
+
import { discoverAbilities, encodeAbilityName, fetchAbility } from './abilities/discovery';
|
|
35
34
|
import type {
|
|
36
35
|
AbilityDescriptor,
|
|
37
|
-
AbilityExecutionResponse,
|
|
38
36
|
} from './abilities/types';
|
|
39
37
|
|
|
40
38
|
const RUN_PATH_PREFIX = 'wp-abilities/v1/abilities';
|
|
@@ -58,6 +56,7 @@ export class WPNativeClient {
|
|
|
58
56
|
private readonly transport: Transport;
|
|
59
57
|
private readonly config: Required<WPNativeClientConfig>;
|
|
60
58
|
private _catalog: AbilityCatalog | null = null;
|
|
59
|
+
private readonly descriptors = new Map<string, AbilityDescriptor>();
|
|
61
60
|
|
|
62
61
|
constructor(transport: Transport, config: WPNativeClientConfig = {}) {
|
|
63
62
|
this.transport = transport;
|
|
@@ -83,6 +82,9 @@ export class WPNativeClient {
|
|
|
83
82
|
}
|
|
84
83
|
const catalog = await discoverAbilities(this.transport, filter);
|
|
85
84
|
this._catalog = catalog;
|
|
85
|
+
for (const ability of catalog.all()) {
|
|
86
|
+
this.descriptors.set(ability.name, ability);
|
|
87
|
+
}
|
|
86
88
|
return catalog;
|
|
87
89
|
}
|
|
88
90
|
|
|
@@ -119,11 +121,8 @@ export class WPNativeClient {
|
|
|
119
121
|
* Execute an ability by name.
|
|
120
122
|
*
|
|
121
123
|
* The ability is looked up in the catalog (when validateAbilityNames is
|
|
122
|
-
* enabled), then
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* The response shape `{ result: TResult }` is unwrapped — callers receive
|
|
126
|
-
* the result value directly.
|
|
124
|
+
* enabled), then sent to /wp-abilities/v1/abilities/{name}/run using the
|
|
125
|
+
* HTTP method required by the ability annotations.
|
|
127
126
|
*
|
|
128
127
|
* Throws:
|
|
129
128
|
* - Error if the ability is not in the catalog (and validation enabled)
|
|
@@ -143,7 +142,13 @@ export class WPNativeClient {
|
|
|
143
142
|
}
|
|
144
143
|
}
|
|
145
144
|
|
|
146
|
-
|
|
145
|
+
let descriptor = this._catalog?.get(name) ?? this.descriptors.get(name);
|
|
146
|
+
if (!descriptor) {
|
|
147
|
+
descriptor = await fetchAbility(this.transport, name);
|
|
148
|
+
this.descriptors.set(name, descriptor);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return this.executeDescriptor<TResult, TInput>(descriptor, input);
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
/**
|
|
@@ -158,27 +163,41 @@ export class WPNativeClient {
|
|
|
158
163
|
name: string,
|
|
159
164
|
input?: TInput,
|
|
160
165
|
): Promise<TResult> {
|
|
161
|
-
const
|
|
166
|
+
const descriptor = await fetchAbility(this.transport, name);
|
|
167
|
+
this.descriptors.set(name, descriptor);
|
|
168
|
+
return this.executeDescriptor<TResult, TInput>(descriptor, input);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private async executeDescriptor<TResult, TInput>(
|
|
172
|
+
descriptor: AbilityDescriptor,
|
|
173
|
+
input?: TInput,
|
|
174
|
+
): Promise<TResult> {
|
|
175
|
+
const annotations = descriptor.meta?.annotations;
|
|
176
|
+
const annotationMap = annotations && typeof annotations === 'object' ? annotations : {};
|
|
177
|
+
const method = annotationMap.readonly
|
|
178
|
+
? 'GET'
|
|
179
|
+
: annotationMap.destructive && annotationMap.idempotent
|
|
180
|
+
? 'DELETE'
|
|
181
|
+
: 'POST';
|
|
182
|
+
let path = `${RUN_PATH_PREFIX}/${encodeAbilityName(descriptor.name)}/run`;
|
|
162
183
|
const body: { input: TInput | null } = {
|
|
163
184
|
input: input === undefined ? null : input,
|
|
164
185
|
};
|
|
165
186
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
throw new ApiError(
|
|
174
|
-
`WPNativeClient: malformed ability response for "${name}". ` +
|
|
175
|
-
`Expected { result: ... }.`,
|
|
176
|
-
'malformed_ability_response',
|
|
177
|
-
500,
|
|
178
|
-
);
|
|
187
|
+
if (method !== 'POST') {
|
|
188
|
+
const query = new URLSearchParams();
|
|
189
|
+
appendQueryValue(query, 'input', body.input);
|
|
190
|
+
const queryString = query.toString();
|
|
191
|
+
if (queryString) {
|
|
192
|
+
path += `?${queryString}`;
|
|
193
|
+
}
|
|
179
194
|
}
|
|
180
195
|
|
|
181
|
-
return
|
|
196
|
+
return this.transport.request<TResult>({
|
|
197
|
+
path,
|
|
198
|
+
method,
|
|
199
|
+
...(method === 'POST' ? { body: body as Record<string, unknown> } : {}),
|
|
200
|
+
});
|
|
182
201
|
}
|
|
183
202
|
|
|
184
203
|
/**
|
|
@@ -189,3 +208,27 @@ export class WPNativeClient {
|
|
|
189
208
|
return this._catalog?.get(name);
|
|
190
209
|
}
|
|
191
210
|
}
|
|
211
|
+
|
|
212
|
+
function appendQueryValue(
|
|
213
|
+
query: URLSearchParams,
|
|
214
|
+
key: string,
|
|
215
|
+
value: unknown,
|
|
216
|
+
): void {
|
|
217
|
+
if (value === null || value === undefined) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (Array.isArray(value)) {
|
|
222
|
+
value.forEach((item, index) => appendQueryValue(query, `${key}[${index}]`, item));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (typeof value === 'object') {
|
|
227
|
+
Object.entries(value).forEach(([childKey, childValue]) => {
|
|
228
|
+
appendQueryValue(query, `${key}[${childKey}]`, childValue);
|
|
229
|
+
});
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
query.append(key, String(value));
|
|
234
|
+
}
|