wp-native-client 0.0.1

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.
Files changed (59) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +41 -0
  3. package/dist/abilities/catalog.d.ts +49 -0
  4. package/dist/abilities/catalog.d.ts.map +1 -0
  5. package/dist/abilities/catalog.js +65 -0
  6. package/dist/abilities/catalog.js.map +1 -0
  7. package/dist/abilities/discovery.d.ts +43 -0
  8. package/dist/abilities/discovery.d.ts.map +1 -0
  9. package/dist/abilities/discovery.js +82 -0
  10. package/dist/abilities/discovery.js.map +1 -0
  11. package/dist/abilities/index.d.ts +7 -0
  12. package/dist/abilities/index.d.ts.map +1 -0
  13. package/dist/abilities/index.js +6 -0
  14. package/dist/abilities/index.js.map +1 -0
  15. package/dist/abilities/types.d.ts +88 -0
  16. package/dist/abilities/types.d.ts.map +1 -0
  17. package/dist/abilities/types.js +15 -0
  18. package/dist/abilities/types.js.map +1 -0
  19. package/dist/client.d.ts +110 -0
  20. package/dist/client.d.ts.map +1 -0
  21. package/dist/client.js +142 -0
  22. package/dist/client.js.map +1 -0
  23. package/dist/index.d.ts +16 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +13 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/transports/auth-fetch.d.ts +132 -0
  28. package/dist/transports/auth-fetch.d.ts.map +1 -0
  29. package/dist/transports/auth-fetch.js +217 -0
  30. package/dist/transports/auth-fetch.js.map +1 -0
  31. package/dist/transports/fetch.d.ts +42 -0
  32. package/dist/transports/fetch.d.ts.map +1 -0
  33. package/dist/transports/fetch.js +68 -0
  34. package/dist/transports/fetch.js.map +1 -0
  35. package/dist/transports/types.d.ts +22 -0
  36. package/dist/transports/types.d.ts.map +1 -0
  37. package/dist/transports/types.js +9 -0
  38. package/dist/transports/types.js.map +1 -0
  39. package/dist/transports/wp-api-fetch.d.ts +25 -0
  40. package/dist/transports/wp-api-fetch.d.ts.map +1 -0
  41. package/dist/transports/wp-api-fetch.js +40 -0
  42. package/dist/transports/wp-api-fetch.js.map +1 -0
  43. package/dist/wordpress.d.ts +8 -0
  44. package/dist/wordpress.d.ts.map +1 -0
  45. package/dist/wordpress.js +8 -0
  46. package/dist/wordpress.js.map +1 -0
  47. package/package.json +45 -0
  48. package/src/__smoke__/discovery.smoke.ts +66 -0
  49. package/src/abilities/catalog.ts +75 -0
  50. package/src/abilities/discovery.ts +111 -0
  51. package/src/abilities/index.ts +18 -0
  52. package/src/abilities/types.ts +98 -0
  53. package/src/client.ts +191 -0
  54. package/src/index.ts +32 -0
  55. package/src/transports/auth-fetch.ts +310 -0
  56. package/src/transports/fetch.ts +107 -0
  57. package/src/transports/types.ts +24 -0
  58. package/src/transports/wp-api-fetch.ts +58 -0
  59. package/src/wordpress.ts +8 -0
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Transport layer abstraction.
3
+ *
4
+ * A Transport is the only platform-specific piece in the client.
5
+ * It knows how to send HTTP requests and handle auth headers.
6
+ * Everything above it is pure TypeScript — platform agnostic.
7
+ */
8
+ export interface TransportRequest {
9
+ path: string;
10
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
11
+ body?: Record<string, unknown> | FormData;
12
+ headers?: Record<string, string>;
13
+ }
14
+ export interface TransportResponse<T = unknown> {
15
+ data: T;
16
+ status: number;
17
+ headers: Record<string, string>;
18
+ }
19
+ export interface Transport {
20
+ request<T>(req: TransportRequest): Promise<T>;
21
+ }
22
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/transports/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,OAAO;IAC5C,IAAI,EAAE,CAAC,CAAC;IACR,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC/C"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Transport layer abstraction.
3
+ *
4
+ * A Transport is the only platform-specific piece in the client.
5
+ * It knows how to send HTTP requests and handle auth headers.
6
+ * Everything above it is pure TypeScript — platform agnostic.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/transports/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * WordPress api-fetch transport.
3
+ *
4
+ * Wraps @wordpress/api-fetch for use inside WordPress blocks.
5
+ * Nonce handling, root URL, and middleware are all managed by WP core.
6
+ *
7
+ * This transport is in a separate entry point (wordpress.ts) so the main
8
+ * bundle has zero WordPress dependencies. Only import this in WP blocks.
9
+ */
10
+ import type { Transport, TransportRequest } from './types';
11
+ type ApiFetchOptions = {
12
+ path: string;
13
+ method: string;
14
+ data?: Record<string, unknown>;
15
+ body?: FormData;
16
+ headers?: Record<string, string>;
17
+ };
18
+ type ApiFetchFn = <T>(options: ApiFetchOptions) => Promise<T>;
19
+ export declare class WpApiFetchTransport implements Transport {
20
+ private apiFetch;
21
+ constructor(apiFetch: ApiFetchFn);
22
+ request<T>(req: TransportRequest): Promise<T>;
23
+ }
24
+ export {};
25
+ //# sourceMappingURL=wp-api-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wp-api-fetch.d.ts","sourceRoot":"","sources":["../../src/transports/wp-api-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAG3D,KAAK,eAAe,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,CAAC;AAEF,KAAK,UAAU,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;AAE9D,qBAAa,mBAAoB,YAAW,SAAS;IACnD,OAAO,CAAC,QAAQ,CAAa;gBAEjB,QAAQ,EAAE,UAAU;IAI1B,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,CAAC;CA2BpD"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * WordPress api-fetch transport.
3
+ *
4
+ * Wraps @wordpress/api-fetch for use inside WordPress blocks.
5
+ * Nonce handling, root URL, and middleware are all managed by WP core.
6
+ *
7
+ * This transport is in a separate entry point (wordpress.ts) so the main
8
+ * bundle has zero WordPress dependencies. Only import this in WP blocks.
9
+ */
10
+ import { ApiError } from './fetch';
11
+ export class WpApiFetchTransport {
12
+ apiFetch;
13
+ constructor(apiFetch) {
14
+ this.apiFetch = apiFetch;
15
+ }
16
+ async request(req) {
17
+ const isFormData = typeof FormData !== 'undefined' && req.body instanceof FormData;
18
+ try {
19
+ const result = await this.apiFetch({
20
+ path: req.path,
21
+ method: req.method,
22
+ ...(isFormData
23
+ ? { body: req.body }
24
+ : req.body
25
+ ? { data: req.body }
26
+ : {}),
27
+ ...(req.headers ? { headers: req.headers } : {}),
28
+ });
29
+ return result;
30
+ }
31
+ catch (error) {
32
+ if (error && typeof error === 'object' && 'code' in error) {
33
+ const wpError = error;
34
+ throw new ApiError(wpError.message, wpError.code, wpError.data?.status || 500);
35
+ }
36
+ throw error;
37
+ }
38
+ }
39
+ }
40
+ //# sourceMappingURL=wp-api-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wp-api-fetch.js","sourceRoot":"","sources":["../../src/transports/wp-api-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAYnC,MAAM,OAAO,mBAAmB;IACtB,QAAQ,CAAa;IAE7B,YAAY,QAAoB;QAC9B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,OAAO,CAAI,GAAqB;QACpC,MAAM,UAAU,GAAG,OAAO,QAAQ,KAAK,WAAW,IAAI,GAAG,CAAC,IAAI,YAAY,QAAQ,CAAC;QAEnF,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAI;gBACpC,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,GAAG,CAAC,UAAU;oBACZ,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAgB,EAAE;oBAChC,CAAC,CAAC,GAAG,CAAC,IAAI;wBACR,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAA+B,EAAE;wBAC/C,CAAC,CAAC,EAAE,CAAC;gBACT,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACjD,CAAC,CAAC;YACH,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,IAAI,KAAK,EAAE,CAAC;gBAC1D,MAAM,OAAO,GAAG,KAAqE,CAAC;gBACtF,MAAM,IAAI,QAAQ,CAChB,OAAO,CAAC,OAAO,EACf,OAAO,CAAC,IAAI,EACZ,OAAO,CAAC,IAAI,EAAE,MAAM,IAAI,GAAG,CAC5B,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * WordPress entry point — only import from inside Gutenberg blocks.
3
+ *
4
+ * Keeps @wordpress/api-fetch out of the main bundle so React Native /
5
+ * Node consumers don't pull in WP-specific deps.
6
+ */
7
+ export { WpApiFetchTransport } from './transports/wp-api-fetch';
8
+ //# sourceMappingURL=wordpress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wordpress.d.ts","sourceRoot":"","sources":["../src/wordpress.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * WordPress entry point — only import from inside Gutenberg blocks.
3
+ *
4
+ * Keeps @wordpress/api-fetch out of the main bundle so React Native /
5
+ * Node consumers don't pull in WP-specific deps.
6
+ */
7
+ export { WpApiFetchTransport } from './transports/wp-api-fetch';
8
+ //# sourceMappingURL=wordpress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wordpress.js","sourceRoot":"","sources":["../src/wordpress.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "wp-native-client",
3
+ "version": "0.0.1",
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
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "source": "src/index.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./wordpress": {
14
+ "types": "./dist/wordpress.d.ts",
15
+ "default": "./dist/wordpress.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "src",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "license": "GPL-2.0-or-later",
25
+ "author": "Chris Huber <chubes@extrachill.com>",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/chubes4/wp-native.git",
29
+ "directory": "packages/api-client"
30
+ },
31
+ "homepage": "https://github.com/chubes4/wp-native/tree/main/packages/api-client",
32
+ "bugs": "https://github.com/chubes4/wp-native/issues",
33
+ "keywords": [
34
+ "wordpress",
35
+ "abilities-api",
36
+ "headless",
37
+ "client",
38
+ "react-native",
39
+ "gutenberg"
40
+ ],
41
+ "scripts": {
42
+ "typecheck": "tsc -b",
43
+ "prepublishOnly": "tsc -b"
44
+ }
45
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Smoke test for ability discovery against a live WordPress site.
3
+ *
4
+ * Not a unit test — this hits the network. Run manually with:
5
+ *
6
+ * WP_BASE_URL=https://your-site.com/wp-json \
7
+ * WP_BEARER=eyJhbG... \
8
+ * npx tsx packages/api-client/src/__smoke__/discovery.smoke.ts
9
+ *
10
+ * Verifies:
11
+ * 1. Discovery walks pages and produces a non-empty catalog
12
+ * 2. The catalog can be queried by name, category, namespace
13
+ * 3. The shape returned by the server matches AbilityDescriptor
14
+ *
15
+ * If the server requires no auth for /abilities, omit WP_BEARER.
16
+ */
17
+
18
+ import { FetchTransport } from '../transports/fetch';
19
+ import { WPNativeClient } from '../client';
20
+
21
+ async function main(): Promise<void> {
22
+ const baseUrl = process.env['WP_BASE_URL'];
23
+ if (!baseUrl) {
24
+ throw new Error('WP_BASE_URL env var is required');
25
+ }
26
+
27
+ const bearer = process.env['WP_BEARER'];
28
+
29
+ const transport = new FetchTransport({
30
+ baseUrl,
31
+ ...(bearer
32
+ ? { getAuthHeaders: () => ({ Authorization: `Bearer ${bearer}` }) }
33
+ : {}),
34
+ });
35
+
36
+ const client = new WPNativeClient(transport);
37
+
38
+ console.log(`Discovering abilities at ${baseUrl}...`);
39
+ const catalog = await client.discover();
40
+
41
+ console.log(`Discovered ${catalog.size()} abilities.`);
42
+ console.log('First 10 names:');
43
+ for (const name of catalog.names().slice(0, 10)) {
44
+ console.log(` - ${name}`);
45
+ }
46
+
47
+ const namespaces = new Set<string>();
48
+ for (const ability of catalog.all()) {
49
+ const slash = ability.name.indexOf('/');
50
+ if (slash > 0) {
51
+ namespaces.add(ability.name.slice(0, slash));
52
+ }
53
+ }
54
+ console.log(`\nNamespaces found: ${Array.from(namespaces).sort().join(', ')}`);
55
+
56
+ const categories = new Set<string>();
57
+ for (const ability of catalog.all()) {
58
+ categories.add(ability.category);
59
+ }
60
+ console.log(`Categories found: ${Array.from(categories).sort().join(', ')}`);
61
+ }
62
+
63
+ main().catch((err: unknown) => {
64
+ console.error('Smoke test failed:', err);
65
+ process.exitCode = 1;
66
+ });
@@ -0,0 +1,75 @@
1
+ /**
2
+ * AbilityCatalog — in-memory index of a WordPress site's ability registry.
3
+ *
4
+ * Built once at app startup via WPNativeClient.discover(). Provides O(1)
5
+ * lookup by ability name plus convenience accessors for filtering by
6
+ * category / namespace.
7
+ *
8
+ * The catalog is the source of truth for "what can this site do?" —
9
+ * shell screens consult it to decide which ability to call for which
10
+ * UI slot, consumers query it to feature-detect, and the client uses
11
+ * it to fail fast when execute() is called with an unknown ability.
12
+ */
13
+
14
+ import type { AbilityDescriptor } from './types';
15
+
16
+ export class AbilityCatalog {
17
+ private readonly byName: Map<string, AbilityDescriptor>;
18
+
19
+ constructor(abilities: readonly AbilityDescriptor[]) {
20
+ this.byName = new Map(abilities.map((a) => [a.name, a]));
21
+ }
22
+
23
+ /**
24
+ * Get a single ability descriptor by name.
25
+ * Returns undefined if the ability is not registered on this site.
26
+ */
27
+ get(name: string): AbilityDescriptor | undefined {
28
+ return this.byName.get(name);
29
+ }
30
+
31
+ /**
32
+ * Whether the given ability is registered on this site.
33
+ */
34
+ has(name: string): boolean {
35
+ return this.byName.has(name);
36
+ }
37
+
38
+ /**
39
+ * All registered ability names, in catalog order.
40
+ */
41
+ names(): string[] {
42
+ return Array.from(this.byName.keys());
43
+ }
44
+
45
+ /**
46
+ * All registered abilities.
47
+ */
48
+ all(): AbilityDescriptor[] {
49
+ return Array.from(this.byName.values());
50
+ }
51
+
52
+ /**
53
+ * Abilities filtered by category slug.
54
+ */
55
+ byCategory(category: string): AbilityDescriptor[] {
56
+ return this.all().filter((a) => a.category === category);
57
+ }
58
+
59
+ /**
60
+ * Abilities whose name starts with the given namespace prefix.
61
+ *
62
+ * Example: catalog.byNamespace('wp-native') returns all wp-native/* abilities.
63
+ */
64
+ byNamespace(namespace: string): AbilityDescriptor[] {
65
+ const prefix = `${namespace}/`;
66
+ return this.all().filter((a) => a.name.startsWith(prefix));
67
+ }
68
+
69
+ /**
70
+ * Total number of abilities in the catalog.
71
+ */
72
+ size(): number {
73
+ return this.byName.size;
74
+ }
75
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Abilities discovery — walks /wp-abilities/v1/abilities and builds a catalog.
3
+ *
4
+ * The Abilities REST API is paginated. discoverAbilities() loops through
5
+ * all pages and returns a fully-populated AbilityCatalog. For sites with
6
+ * hundreds of abilities this is a few sequential requests at startup;
7
+ * the catalog is then cached in memory for the app session.
8
+ */
9
+
10
+ import type { Transport } from '../transports/types';
11
+ import { AbilityCatalog } from './catalog';
12
+ import type {
13
+ AbilityDescriptor,
14
+ AbilityListParams,
15
+ AbilityCategory,
16
+ } from './types';
17
+
18
+ const DEFAULT_PER_PAGE = 100;
19
+ const ABILITIES_PATH = 'wp-abilities/v1/abilities';
20
+ const CATEGORIES_PATH = 'wp-abilities/v1/categories';
21
+
22
+ /**
23
+ * Fetch a single page of abilities.
24
+ *
25
+ * Returns the items only — pagination is driven by the discovery loop.
26
+ */
27
+ export async function fetchAbilitiesPage(
28
+ transport: Transport,
29
+ params: AbilityListParams = {},
30
+ ): Promise<AbilityDescriptor[]> {
31
+ const query = new URLSearchParams();
32
+ query.set('per_page', String(params.perPage ?? DEFAULT_PER_PAGE));
33
+ query.set('page', String(params.page ?? 1));
34
+ if (params.category) {
35
+ query.set('category', params.category);
36
+ }
37
+
38
+ return transport.request<AbilityDescriptor[]>({
39
+ path: `${ABILITIES_PATH}?${query.toString()}`,
40
+ method: 'GET',
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Fetch one ability descriptor by name.
46
+ *
47
+ * Useful for refreshing a single ability's schema without re-discovering
48
+ * the whole catalog.
49
+ */
50
+ export async function fetchAbility(
51
+ transport: Transport,
52
+ name: string,
53
+ ): Promise<AbilityDescriptor> {
54
+ return transport.request<AbilityDescriptor>({
55
+ path: `${ABILITIES_PATH}/${encodeURIComponent(name)}`,
56
+ method: 'GET',
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Fetch all available ability categories.
62
+ */
63
+ export async function fetchAbilityCategories(
64
+ transport: Transport,
65
+ ): Promise<AbilityCategory[]> {
66
+ return transport.request<AbilityCategory[]>({
67
+ path: CATEGORIES_PATH,
68
+ method: 'GET',
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Discover all abilities on a WordPress site and return a populated catalog.
74
+ *
75
+ * Walks pages until an empty page is returned. The Abilities API caps
76
+ * `per_page` at 100, so a site with N abilities resolves in ⌈N / 100⌉ requests.
77
+ *
78
+ * Optionally filter by category — useful when an app only cares about a
79
+ * subset of the surface (e.g. only `wp-native/*` abilities for the auth
80
+ * bootstrap flow).
81
+ */
82
+ export async function discoverAbilities(
83
+ transport: Transport,
84
+ options: { category?: string; perPage?: number } = {},
85
+ ): Promise<AbilityCatalog> {
86
+ const perPage = options.perPage ?? DEFAULT_PER_PAGE;
87
+ const all: AbilityDescriptor[] = [];
88
+ let page = 1;
89
+
90
+ while (true) {
91
+ const params: AbilityListParams = { page, perPage };
92
+ if (options.category !== undefined) {
93
+ params.category = options.category;
94
+ }
95
+ const items = await fetchAbilitiesPage(transport, params);
96
+
97
+ if (items.length === 0) {
98
+ break;
99
+ }
100
+
101
+ all.push(...items);
102
+
103
+ if (items.length < perPage) {
104
+ break;
105
+ }
106
+
107
+ page += 1;
108
+ }
109
+
110
+ return new AbilityCatalog(all);
111
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Public surface for the abilities subsystem.
3
+ */
4
+
5
+ export { AbilityCatalog } from './catalog';
6
+ export {
7
+ discoverAbilities,
8
+ fetchAbilitiesPage,
9
+ fetchAbility,
10
+ fetchAbilityCategories,
11
+ } from './discovery';
12
+ export type {
13
+ AbilityDescriptor,
14
+ AbilityCategory,
15
+ AbilityExecutionResponse,
16
+ AbilityListPage,
17
+ AbilityListParams,
18
+ } from './types';
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Type definitions for the WordPress Abilities API REST surface.
3
+ *
4
+ * Mirrors the response shapes documented at:
5
+ * GET /wp-abilities/v1/abilities
6
+ * GET /wp-abilities/v1/abilities/{name}
7
+ * POST /wp-abilities/v1/abilities/{name}/run
8
+ *
9
+ * These types describe the wire format. They are intentionally permissive
10
+ * about ability-specific input/output (modeled as `unknown`) because the
11
+ * universal client can't know each ability's schema at compile time —
12
+ * codegen from `input_schema` / `output_schema` is a post-v0.1 concern.
13
+ */
14
+
15
+ /**
16
+ * A single ability as returned by the catalog endpoints.
17
+ */
18
+ export interface AbilityDescriptor {
19
+ /**
20
+ * Unique identifier, namespaced. Examples:
21
+ * wp/post.list
22
+ * wp-native/auth-login
23
+ * extrachill/artist.get
24
+ */
25
+ name: string;
26
+
27
+ /** Human-readable display label. */
28
+ label: string;
29
+
30
+ /** Longer description of what the ability does. */
31
+ description: string;
32
+
33
+ /**
34
+ * Category slug. Categories group related abilities for UI / discovery.
35
+ * Validated server-side against /^[a-z0-9]+(?:-[a-z0-9]+)*$/ — no slashes.
36
+ */
37
+ category: string;
38
+
39
+ /**
40
+ * JSON Schema describing the shape of the `input` argument expected by
41
+ * POST /abilities/{name}/run. May be an empty object for nullary abilities.
42
+ */
43
+ input_schema: Record<string, unknown>;
44
+
45
+ /**
46
+ * JSON Schema describing the shape of `result` returned by /run.
47
+ */
48
+ output_schema: Record<string, unknown>;
49
+
50
+ /**
51
+ * Meta information attached to the ability registration.
52
+ */
53
+ meta?: {
54
+ annotations?: Record<string, unknown> | boolean | null;
55
+ [key: string]: unknown;
56
+ };
57
+ }
58
+
59
+ /**
60
+ * An ability category as returned by GET /wp-abilities/v1/categories.
61
+ */
62
+ export interface AbilityCategory {
63
+ slug: string;
64
+ label: string;
65
+ description: string;
66
+ }
67
+
68
+ /**
69
+ * Successful response from POST /wp-abilities/v1/abilities/{name}/run.
70
+ *
71
+ * The actual `result` shape is ability-specific; consumers narrow with
72
+ * a type parameter at the `client.execute<TResult>()` call site.
73
+ */
74
+ export interface AbilityExecutionResponse<TResult = unknown> {
75
+ result: TResult;
76
+ }
77
+
78
+ /**
79
+ * Pagination wrapper. The Abilities REST API uses standard WP REST
80
+ * paginated lists — total count comes in the `X-WP-Total` header.
81
+ */
82
+ export interface AbilityListPage {
83
+ items: AbilityDescriptor[];
84
+ totalItems: number;
85
+ totalPages: number;
86
+ }
87
+
88
+ /**
89
+ * Optional filters for catalog listing.
90
+ */
91
+ export interface AbilityListParams {
92
+ /** Filter by category slug. */
93
+ category?: string;
94
+ /** Page number (1-indexed). Default: 1. */
95
+ page?: number;
96
+ /** Items per page. Server max is typically 100. Default: 50. */
97
+ perPage?: number;
98
+ }