From af1ad61cf3e71d2bbcf51ace9e66195ccde08b6c Mon Sep 17 00:00:00 2001 From: Jonathan Bossenger Date: Fri, 26 Sep 2025 13:05:09 +0200 Subject: [PATCH 1/5] AI generated docs --- README.md | 1 + docs/7.javascript-client.md | 503 ++++++++++++++++++++++++++++++++++++ packages/client/README.md | 127 --------- 3 files changed, 504 insertions(+), 127 deletions(-) create mode 100644 docs/7.javascript-client.md delete mode 100644 packages/client/README.md diff --git a/README.md b/README.md index e257e7e..4762b71 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ - [Using Abilities](docs/4.using-abilities.md) - [REST API Reference](docs/5.rest-api.md) - [Hooks](docs/6.hooks.md) +- [JavaScript/TypeScript Client](docs/7.javascript-client.md) - [Contributing Guidelines](CONTRIBUTING.md) ## Inspiration diff --git a/docs/7.javascript-client.md b/docs/7.javascript-client.md new file mode 100644 index 0000000..7b68b95 --- /dev/null +++ b/docs/7.javascript-client.md @@ -0,0 +1,503 @@ +# 7. JavaScript/TypeScript Client + +The WordPress Abilities API includes a comprehensive JavaScript client that enables frontend applications, block editor plugins, and external systems to discover and execute abilities both on the server and within the browser. + +## Overview + +While the previous documentation covered registering and using abilities in PHP, this client extends that functionality to JavaScript environments. It provides: + +- **Server-side ability execution** via WordPress REST API +- **Client-side ability registration** for browser-based capabilities +- **Automatic discovery** of all registered abilities +- **Input/output validation** using JSON Schema +- **WordPress data store integration** for reactive UI development +- **TypeScript support** with full type definitions + +## Installation + +### As an NPM Package + +For modern JavaScript development workflows: + +```bash +npm install @wordpress/abilities +``` + +Then import the functions you need: + +```javascript +import { getAbilities, getAbility, executeAbility, registerAbility } from '@wordpress/abilities'; +``` + +### WordPress Global (Plugin Users) + +When the Abilities API plugin is active, the client is automatically available as a WordPress script: + +```javascript +// Available globally when the plugin is loaded +const { getAbilities, getAbility, executeAbility } = wp.abilities; +``` + +This approach is ideal for WordPress admin pages, block editor plugins, or theme scripts where you don't have a build process. + +## Basic Usage Patterns + +### Executing Server-Side Abilities + +Server-side abilities that you registered in PHP (as shown in [Registering Abilities](3.registering-abilities.md)) can be executed from JavaScript: + +```javascript +// Execute the site info ability we registered in earlier examples +const siteInfo = await executeAbility('my-plugin/get-site-info'); +console.log('Site Name:', siteInfo.name); + +// Execute an ability with input parameters +const result = await executeAbility('my-plugin/update-option', { + option_name: 'blogname', + option_value: 'My New Site Title' +}); + +if (result.success) { + console.log('Option updated successfully'); +} +``` + +### Discovering Available Abilities + +Get all registered abilities (both server-side and client-side): + +```javascript +// Get all abilities +const abilities = await getAbilities(); +console.log(`Found ${abilities.length} abilities`); + +// Filter by type (tools vs resources) +const tools = abilities.filter(ability => ability.meta?.type === 'tool'); +const resources = abilities.filter(ability => ability.meta?.type === 'resource'); + +// List abilities by namespace +abilities.forEach(ability => { + console.log(`${ability.name}: ${ability.description}`); +}); +``` + +### Getting a Specific Ability + +```javascript +const ability = await getAbility('my-plugin/get-site-info'); +if (ability) { + console.log('Label:', ability.label); + console.log('Description:', ability.description); + console.log('Input Schema:', ability.input_schema); + + // Check if it's client-side or server-side + if (ability.callback) { + console.log('This is a client-side ability'); + } else { + console.log('This is a server-side ability'); + } +} +``` + +## Client-Side Abilities + +One of the key features of the JavaScript client is the ability to register capabilities that run entirely in the browser. This is useful for UI interactions, data formatting, and browser-specific functionality. + +### Registering Client-Side Abilities + +```javascript +// Register a simple notification ability +registerAbility({ + name: 'my-plugin/show-notification', + label: 'Show Notification', + description: 'Display a notification message to the user', + input_schema: { + type: 'object', + properties: { + message: { type: 'string' }, + type: { type: 'string', enum: ['success', 'error', 'warning', 'info'] } + }, + required: ['message'] + }, + callback: async ({ message, type = 'info' }) => { + // Use WordPress notices API + wp.data.dispatch('core/notices').createNotice(type, message); + return { success: true, displayed: message }; + }, + permissionCallback: () => { + // Allow any logged-in user + return !!wp.data.select('core').getCurrentUser(); + } +}); +``` + +### Complex Client-Side Example + +Here's a more comprehensive example that demonstrates validation and error handling: + +```javascript +registerAbility({ + name: 'my-plugin/format-post-data', + label: 'Format Post Data', + description: 'Format and validate post data for display', + input_schema: { + type: 'object', + properties: { + title: { type: 'string', minLength: 1, maxLength: 100 }, + content: { type: 'string', minLength: 10 }, + tags: { + type: 'array', + items: { type: 'string' }, + maxItems: 10 + }, + publishDate: { type: 'string', format: 'date-time' } + }, + required: ['title', 'content'] + }, + output_schema: { + type: 'object', + properties: { + formattedTitle: { type: 'string' }, + wordCount: { type: 'number' }, + excerpt: { type: 'string' }, + tagList: { type: 'string' } + } + }, + callback: async ({ title, content, tags = [], publishDate }) => { + // Format title + const formattedTitle = title.trim().replace(/\s+/g, ' '); + + // Calculate word count + const wordCount = content.split(/\s+/).length; + + // Generate excerpt + const excerpt = content.substring(0, 150) + (content.length > 150 ? '...' : ''); + + // Format tags + const tagList = tags.length > 0 ? tags.join(', ') : 'No tags'; + + return { + formattedTitle, + wordCount, + excerpt, + tagList + }; + }, + permissionCallback: (input) => { + // Only allow users who can edit posts + return wp.data.select('core').getCurrentUser()?.capabilities?.edit_posts; + } +}); + +// Use the ability +const formatted = await executeAbility('my-plugin/format-post-data', { + title: ' My Blog Post Title ', + content: 'This is the content of my blog post. It contains multiple sentences and should be long enough to generate a proper excerpt when processed by the formatting ability.', + tags: ['WordPress', 'JavaScript', 'Abilities API'] +}); + +console.log('Formatted:', formatted.formattedTitle); +console.log('Word count:', formatted.wordCount); +``` + +## Error Handling + +The client provides structured error handling with specific error codes: + +```javascript +try { + const result = await executeAbility('my-plugin/risky-operation', input); + // Success - use result +} catch (error) { + switch (error.code) { + case 'ability_permission_denied': + console.error('You do not have permission:', error.message); + break; + case 'ability_invalid_input': + console.error('Invalid input data:', error.message); + break; + case 'ability_invalid_output': + console.error('Ability returned invalid output:', error.message); + break; + case 'rest_ability_not_found': + console.error('Ability not found:', error.message); + break; + default: + console.error('Execution failed:', error.message); + } +} +``` + +## WordPress Data Store Integration + +The client integrates with the WordPress data package for reactive state management in React applications: + +### Using Selectors in React Components + +```javascript +import { useSelect } from '@wordpress/data'; +import { store as abilitiesStore } from '@wordpress/abilities'; + +function AbilitiesList() { + const abilities = useSelect( + select => select(abilitiesStore).getAbilities(), + [] + ); + + return ( +
+

Available Abilities ({abilities.length})

+ +
+ ); +} +``` + +### Custom React Hook for Ability Execution + +```javascript +import { useState, useCallback } from 'react'; +import { executeAbility } from '@wordpress/abilities'; + +function useAbility(abilityName) { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [result, setResult] = useState(null); + + const execute = useCallback(async (input) => { + setLoading(true); + setError(null); + + try { + const output = await executeAbility(abilityName, input); + setResult(output); + return output; + } catch (err) { + setError(err.message); + throw err; + } finally { + setLoading(false); + } + }, [abilityName]); + + return { execute, loading, error, result }; +} + +// Usage in a component +function ContentGenerator() { + const { execute, loading, error } = useAbility('ai-plugin/generate-content'); + const [prompt, setPrompt] = useState(''); + + const handleGenerate = async () => { + try { + const content = await execute({ prompt, maxLength: 500 }); + console.log('Generated:', content); + } catch (error) { + console.error('Generation failed:', error); + } + }; + + return ( +
+