@wordpress/abilities 0.2.1-next.8b30e05b0.0 → 0.3.0

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 (72) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +32 -40
  3. package/build/api.js +19 -66
  4. package/build/api.js.map +3 -3
  5. package/build/index.js.map +2 -2
  6. package/build/store/actions.js +41 -31
  7. package/build/store/actions.js.map +2 -2
  8. package/build/store/constants.js +6 -15
  9. package/build/store/constants.js.map +2 -2
  10. package/build/store/index.js +1 -27
  11. package/build/store/index.js.map +3 -3
  12. package/build/store/reducer.js +2 -22
  13. package/build/store/reducer.js.map +2 -2
  14. package/build/store/selectors.js +1 -1
  15. package/build/store/selectors.js.map +2 -2
  16. package/build/store/types.js +19 -0
  17. package/build/store/types.js.map +7 -0
  18. package/build/types.js.map +1 -1
  19. package/build-module/api.js +20 -57
  20. package/build-module/api.js.map +2 -2
  21. package/build-module/index.js.map +2 -2
  22. package/build-module/store/actions.js +43 -32
  23. package/build-module/store/actions.js.map +2 -2
  24. package/build-module/store/constants.js +4 -10
  25. package/build-module/store/constants.js.map +2 -2
  26. package/build-module/store/index.js +3 -34
  27. package/build-module/store/index.js.map +2 -2
  28. package/build-module/store/reducer.js +2 -24
  29. package/build-module/store/reducer.js.map +2 -2
  30. package/build-module/store/selectors.js +1 -1
  31. package/build-module/store/selectors.js.map +2 -2
  32. package/build-module/store/types.js +1 -0
  33. package/build-module/store/types.js.map +7 -0
  34. package/build-types/api.d.ts +16 -23
  35. package/build-types/api.d.ts.map +1 -1
  36. package/build-types/index.d.ts +1 -1
  37. package/build-types/index.d.ts.map +1 -1
  38. package/build-types/store/actions.d.ts +5 -27
  39. package/build-types/store/actions.d.ts.map +1 -1
  40. package/build-types/store/constants.d.ts +2 -5
  41. package/build-types/store/constants.d.ts.map +1 -1
  42. package/build-types/store/index.d.ts.map +1 -1
  43. package/build-types/store/reducer.d.ts +21 -11
  44. package/build-types/store/reducer.d.ts.map +1 -1
  45. package/build-types/store/selectors.d.ts +2 -1
  46. package/build-types/store/selectors.d.ts.map +1 -1
  47. package/build-types/store/types.d.ts +18 -0
  48. package/build-types/store/types.d.ts.map +1 -0
  49. package/build-types/types.d.ts +14 -24
  50. package/build-types/types.d.ts.map +1 -1
  51. package/package.json +4 -7
  52. package/src/api.ts +43 -123
  53. package/src/index.ts +0 -1
  54. package/src/store/actions.ts +61 -46
  55. package/src/store/constants.ts +4 -5
  56. package/src/store/index.ts +2 -34
  57. package/src/store/reducer.ts +27 -33
  58. package/src/store/selectors.ts +3 -7
  59. package/src/store/tests/actions.test.ts +100 -244
  60. package/src/store/tests/reducer.test.ts +0 -451
  61. package/src/store/tests/selectors.test.ts +1 -1
  62. package/src/store/types.ts +19 -0
  63. package/src/tests/api.test.ts +112 -158
  64. package/src/types.ts +14 -26
  65. package/build/store/resolvers.js +0 -107
  66. package/build/store/resolvers.js.map +0 -7
  67. package/build-module/store/resolvers.js +0 -84
  68. package/build-module/store/resolvers.js.map +0 -7
  69. package/build-types/store/resolvers.d.ts +0 -47
  70. package/build-types/store/resolvers.d.ts.map +0 -1
  71. package/src/store/resolvers.ts +0 -151
  72. package/src/store/tests/resolvers.test.ts +0 -642
package/CHANGELOG.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.0 (2025-12-23)
6
+
5
7
  ## 0.2.0 (2025-11-26)
6
8
 
7
9
  ## 0.1.0 (Unreleased)
package/README.md CHANGED
@@ -25,10 +25,10 @@ const { getAbilities, getAbility, executeAbility } = wp.abilities;
25
25
  // or import { getAbilities, getAbility, executeAbility } from '@wordpress/abilities'; depending on your setup
26
26
 
27
27
  // Get all abilities
28
- const abilities = await getAbilities();
28
+ const abilities = getAbilities();
29
29
 
30
30
  // Get a specific ability
31
- const ability = await getAbility( 'my-plugin/my-ability' );
31
+ const ability = getAbility( 'my-plugin/my-ability' );
32
32
 
33
33
  // Execute an ability
34
34
  const result = await executeAbility( 'my-plugin/my-ability', {
@@ -77,37 +77,37 @@ function MyComponent() {
77
77
 
78
78
  ### Functions
79
79
 
80
- #### `getAbilities( args: AbilitiesQueryArgs = {} ): Promise<Ability[]>`
80
+ #### `getAbilities( args: AbilitiesQueryArgs = {} ): Ability[]`
81
81
 
82
- Returns all registered abilities. Optionally filter by category slug. Automatically handles pagination to fetch all abilities across multiple pages if needed.
82
+ Returns all registered abilities. Optionally filter by category slug.
83
83
 
84
84
  ```javascript
85
85
  // Get all abilities
86
- const abilities = await getAbilities();
86
+ const abilities = getAbilities();
87
87
  console.log( `Found ${ abilities.length } abilities` );
88
88
 
89
89
  // Get abilities in a specific category
90
- const dataAbilities = await getAbilities( { category: 'data-retrieval' } );
90
+ const dataAbilities = getAbilities( { category: 'data-retrieval' } );
91
91
  console.log( `Found ${ dataAbilities.length } data retrieval abilities` );
92
92
  ```
93
93
 
94
- #### `getAbility( name: string ): Promise<Ability | null>`
94
+ #### `getAbility( name: string ): Ability | undefined`
95
95
 
96
- Returns a specific ability by name, or null if not found.
96
+ Returns a specific ability by name, or undefined if not found.
97
97
 
98
98
  ```javascript
99
- const ability = await getAbility( 'my-plugin/create-post' );
99
+ const ability = getAbility( 'my-plugin/create-post' );
100
100
  if ( ability ) {
101
101
  console.log( `Found ability: ${ ability.label }` );
102
102
  }
103
103
  ```
104
104
 
105
- #### `getAbilityCategories(): Promise<AbilityCategory[]>`
105
+ #### `getAbilityCategories(): AbilityCategory[]`
106
106
 
107
107
  Returns all registered ability categories. Categories are used to organize abilities into logical groups.
108
108
 
109
109
  ```javascript
110
- const categories = await getAbilityCategories();
110
+ const categories = getAbilityCategories();
111
111
  console.log( `Found ${ categories.length } categories` );
112
112
 
113
113
  categories.forEach( ( category ) => {
@@ -115,26 +115,26 @@ categories.forEach( ( category ) => {
115
115
  } );
116
116
  ```
117
117
 
118
- #### `getAbilityCategory( slug: string ): Promise<AbilityCategory | null>`
118
+ #### `getAbilityCategory( slug: string ): AbilityCategory | undefined`
119
119
 
120
- Returns a specific ability category by slug, or null if not found.
120
+ Returns a specific ability category by slug, or undefined if not found.
121
121
 
122
122
  ```javascript
123
- const category = await getAbilityCategory( 'data-retrieval' );
123
+ const category = getAbilityCategory( 'data-retrieval' );
124
124
  if ( category ) {
125
125
  console.log( `Found category: ${ category.label }` );
126
126
  console.log( `Description: ${ category.description }` );
127
127
  }
128
128
  ```
129
129
 
130
- #### `registerAbility( ability: Ability ): Promise<void>`
130
+ #### `registerAbility( ability: Ability ): void`
131
131
 
132
132
  Registers a client-side ability. Client abilities are executed locally in the browser and must include a callback function and a valid category.
133
133
 
134
134
  ```javascript
135
135
  import { registerAbility } from '@wordpress/abilities';
136
136
 
137
- await registerAbility( {
137
+ registerAbility( {
138
138
  name: 'my-plugin/navigate',
139
139
  label: 'Navigate to URL',
140
140
  description: 'Navigates to a URL within WordPress admin',
@@ -153,17 +153,7 @@ await registerAbility( {
153
153
  } );
154
154
  ```
155
155
 
156
- #### `unregisterAbility( name: string ): void`
157
-
158
- Unregisters a client-side ability from the store.
159
-
160
- ```javascript
161
- import { unregisterAbility } from '@wordpress/abilities';
162
-
163
- unregisterAbility( 'my-plugin/navigate' );
164
- ```
165
-
166
- #### `registerAbilityCategory( slug: string, args: AbilityCategoryArgs ): Promise<void>`
156
+ #### `registerAbilityCategory( slug: string, args: AbilityCategoryArgs ): void`
167
157
 
168
158
  Registers a client-side ability category. This is useful when registering client-side abilities that introduce new categories not defined by the server.
169
159
 
@@ -171,13 +161,13 @@ Registers a client-side ability category. This is useful when registering client
171
161
  import { registerAbilityCategory } from '@wordpress/abilities';
172
162
 
173
163
  // Register a new category
174
- await registerAbilityCategory( 'block-editor', {
164
+ registerAbilityCategory( 'block-editor', {
175
165
  label: 'Block Editor',
176
166
  description: 'Abilities for interacting with the WordPress block editor',
177
167
  } );
178
168
 
179
169
  // Register a category with optional metadata
180
- await registerAbilityCategory( 'custom-category', {
170
+ registerAbilityCategory( 'custom-category', {
181
171
  label: 'Custom Category',
182
172
  description: 'A category for custom abilities',
183
173
  meta: {
@@ -186,7 +176,7 @@ await registerAbilityCategory( 'custom-category', {
186
176
  } );
187
177
 
188
178
  // Then register abilities using the new category
189
- await registerAbility( {
179
+ registerAbility( {
190
180
  name: 'my-plugin/insert-block',
191
181
  label: 'Insert Block',
192
182
  description: 'Inserts a block into the editor',
@@ -208,20 +198,22 @@ import { unregisterAbilityCategory } from '@wordpress/abilities';
208
198
  unregisterAbilityCategory( 'block-editor' );
209
199
  ```
210
200
 
211
- #### `executeAbility( name: string, input?: Record<string, any> ): Promise<any>`
212
-
213
- Executes an ability with optional input parameters. The HTTP method is automatically determined based on the ability's annotations:
201
+ #### `unregisterAbility( name: string ): void`
214
202
 
215
- - `readonly` abilities use GET (read-only operations)
216
- - regular abilities use POST (write operations)
203
+ Unregisters a client-side ability from the store.
217
204
 
218
205
  ```javascript
219
- // Execute a read-only ability (GET)
220
- const data = await executeAbility( 'my-plugin/get-data', {
221
- id: 123,
222
- } );
206
+ import { unregisterAbility } from '@wordpress/abilities';
207
+
208
+ unregisterAbility( 'my-plugin/navigate' );
209
+ ```
210
+
211
+ #### `executeAbility( name: string, input?: Record<string, any> ): Promise<any>`
223
212
 
224
- // Execute a regular ability (POST)
213
+ Executes an ability with optional input parameters.
214
+
215
+ ```javascript
216
+ // Execute an ability
225
217
  const result = await executeAbility( 'my-plugin/create-item', {
226
218
  title: 'New Item',
227
219
  content: 'Item content',
package/build/api.js CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // packages/abilities/src/api.ts
@@ -42,40 +32,42 @@ __export(api_exports, {
42
32
  });
43
33
  module.exports = __toCommonJS(api_exports);
44
34
  var import_data = require("@wordpress/data");
45
- var import_api_fetch = __toESM(require("@wordpress/api-fetch"));
46
- var import_url = require("@wordpress/url");
47
35
  var import_i18n = require("@wordpress/i18n");
48
36
  var import_store = require("./store");
49
37
  var import_validation = require("./validation");
50
- async function getAbilities(args = {}) {
51
- return await (0, import_data.resolveSelect)(import_store.store).getAbilities(args);
38
+ function getAbilities(args = {}) {
39
+ return (0, import_data.select)(import_store.store).getAbilities(args);
52
40
  }
53
- async function getAbility(name) {
54
- return await (0, import_data.resolveSelect)(import_store.store).getAbility(name);
41
+ function getAbility(name) {
42
+ return (0, import_data.select)(import_store.store).getAbility(name);
55
43
  }
56
- async function getAbilityCategories() {
57
- return await (0, import_data.resolveSelect)(import_store.store).getAbilityCategories();
44
+ function getAbilityCategories() {
45
+ return (0, import_data.select)(import_store.store).getAbilityCategories();
58
46
  }
59
- async function getAbilityCategory(slug) {
60
- return await (0, import_data.resolveSelect)(import_store.store).getAbilityCategory(slug);
47
+ function getAbilityCategory(slug) {
48
+ return (0, import_data.select)(import_store.store).getAbilityCategory(slug);
61
49
  }
62
- async function registerAbility(ability) {
63
- await (0, import_data.dispatch)(import_store.store).registerAbility(ability);
50
+ function registerAbility(ability) {
51
+ (0, import_data.dispatch)(import_store.store).registerAbility(ability);
64
52
  }
65
53
  function unregisterAbility(name) {
66
54
  (0, import_data.dispatch)(import_store.store).unregisterAbility(name);
67
55
  }
68
- async function registerAbilityCategory(slug, args) {
69
- await (0, import_data.dispatch)(import_store.store).registerAbilityCategory(slug, args);
56
+ function registerAbilityCategory(slug, args) {
57
+ (0, import_data.dispatch)(import_store.store).registerAbilityCategory(slug, args);
70
58
  }
71
59
  function unregisterAbilityCategory(slug) {
72
60
  (0, import_data.dispatch)(import_store.store).unregisterAbilityCategory(slug);
73
61
  }
74
- async function executeClientAbility(ability, input) {
62
+ async function executeAbility(name, input) {
63
+ const ability = getAbility(name);
64
+ if (!ability) {
65
+ throw new Error((0, import_i18n.sprintf)("Ability not found: %s", name));
66
+ }
75
67
  if (!ability.callback) {
76
68
  throw new Error(
77
69
  (0, import_i18n.sprintf)(
78
- "Client ability %s is missing callback function",
70
+ 'Ability "%s" is missing callback. Please ensure the ability is properly registered.',
79
71
  ability.name
80
72
  )
81
73
  );
@@ -112,10 +104,7 @@ async function executeClientAbility(ability, input) {
112
104
  try {
113
105
  result = await ability.callback(input);
114
106
  } catch (error) {
115
- console.error(
116
- `Error executing client ability ${ability.name}:`,
117
- error
118
- );
107
+ console.error(`Error executing ability ${ability.name}:`, error);
119
108
  throw error;
120
109
  }
121
110
  if (ability.output_schema) {
@@ -138,42 +127,6 @@ async function executeClientAbility(ability, input) {
138
127
  }
139
128
  return result;
140
129
  }
141
- async function executeServerAbility(ability, input) {
142
- let method = "POST";
143
- if (!!ability.meta?.annotations?.readonly) {
144
- method = "GET";
145
- } else if (!!ability.meta?.annotations?.destructive && !!ability.meta?.annotations?.idempotent) {
146
- method = "DELETE";
147
- }
148
- let path = `/wp-abilities/v1/abilities/${ability.name}/run`;
149
- const options = {
150
- method
151
- };
152
- if (["GET", "DELETE"].includes(method) && input !== null) {
153
- path = (0, import_url.addQueryArgs)(path, { input });
154
- } else if (method === "POST" && input !== null) {
155
- options.data = { input };
156
- }
157
- try {
158
- return await (0, import_api_fetch.default)({
159
- path,
160
- ...options
161
- });
162
- } catch (error) {
163
- console.error(`Error executing ability ${ability.name}:`, error);
164
- throw error;
165
- }
166
- }
167
- async function executeAbility(name, input) {
168
- const ability = await getAbility(name);
169
- if (!ability) {
170
- throw new Error((0, import_i18n.sprintf)("Ability not found: %s", name));
171
- }
172
- if (ability.callback) {
173
- return executeClientAbility(ability, input);
174
- }
175
- return executeServerAbility(ability, input);
176
- }
177
130
  // Annotate the CommonJS export names for ESM import in node:
178
131
  0 && (module.exports = {
179
132
  executeAbility,
package/build/api.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/api.ts"],
4
- "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { dispatch, resolveSelect } from '@wordpress/data';\nimport apiFetch from '@wordpress/api-fetch';\nimport { addQueryArgs } from '@wordpress/url';\nimport { sprintf } from '@wordpress/i18n';\n\n/**\n * Internal dependencies\n */\nimport { store } from './store';\nimport type {\n\tAbility,\n\tAbilityCategory,\n\tAbilityCategoryArgs,\n\tAbilitiesQueryArgs,\n\tAbilityInput,\n\tAbilityOutput,\n} from './types';\nimport { validateValueFromSchema } from './validation';\n\n/**\n * Get all available abilities with optional filtering.\n *\n * @param args Optional query arguments to filter. Defaults to empty object.\n * @return Promise resolving to array of abilities.\n */\nexport async function getAbilities(\n\targs: AbilitiesQueryArgs = {}\n): Promise< Ability[] > {\n\treturn await resolveSelect( store ).getAbilities( args );\n}\n\n/**\n * Get a specific ability by name.\n *\n * @param name The ability name.\n * @return Promise resolving to the ability or null if not found.\n */\nexport async function getAbility( name: string ): Promise< Ability | null > {\n\treturn await resolveSelect( store ).getAbility( name );\n}\n\n/**\n * Get all available ability categories.\n *\n * @return Promise resolving to array of categories.\n */\nexport async function getAbilityCategories(): Promise< AbilityCategory[] > {\n\treturn await resolveSelect( store ).getAbilityCategories();\n}\n\n/**\n * Get a specific ability category by slug.\n *\n * @param slug The category slug.\n * @return Promise resolving to the category or null if not found.\n */\nexport async function getAbilityCategory(\n\tslug: string\n): Promise< AbilityCategory | null > {\n\treturn await resolveSelect( store ).getAbilityCategory( slug );\n}\n\n/**\n * Register a client-side ability.\n *\n * Client abilities are executed locally in the browser and must include\n * a callback function. The ability will be validated by the store action,\n * and an error will be thrown if validation fails.\n *\n * Categories will be automatically fetched from the REST API if they\n * haven't been loaded yet, so you don't need to call getAbilityCategories()\n * before registering abilities.\n *\n * @param ability The ability definition including callback.\n * @return Promise that resolves when registration is complete.\n * @throws {Error} If the ability fails validation.\n *\n * @example\n * ```js\n * await registerAbility({\n * name: 'my-plugin/navigate',\n * label: 'Navigate to URL',\n * description: 'Navigates to a URL within WordPress admin',\n * category: 'navigation',\n * input_schema: {\n * type: 'object',\n * properties: {\n * url: { type: 'string' }\n * },\n * required: ['url']\n * },\n * callback: async ({ url }) => {\n * window.location.href = url;\n * return { success: true };\n * }\n * });\n * ```\n */\nexport async function registerAbility( ability: Ability ): Promise< void > {\n\tawait dispatch( store ).registerAbility( ability );\n}\n\n/**\n * Unregister an ability from the store.\n *\n * Remove a client-side ability from the store.\n * Note: This will return an error for server-side abilities.\n *\n * @param name The ability name to unregister.\n */\nexport function unregisterAbility( name: string ): void {\n\tdispatch( store ).unregisterAbility( name );\n}\n\n/**\n * Register a client-side ability category.\n *\n * Categories registered on the client are stored alongside server-side categories\n * in the same store and can be used when registering client side abilities.\n * This is useful when registering client-side abilities that introduce new\n * categories not defined by the server.\n *\n * Categories will be automatically fetched from the REST API if they haven't been\n * loaded yet to check for duplicates against server-side categories.\n *\n * @param slug Category slug (lowercase alphanumeric with dashes only).\n * @param args Category arguments (label, description, optional meta).\n * @return Promise that resolves when registration is complete.\n * @throws {Error} If the category fails validation.\n *\n * @example\n * ```js\n * // Register a new category for block editor abilities\n * await registerAbilityCategory('block-editor', {\n * label: 'Block Editor',\n * description: 'Abilities for interacting with the WordPress block editor'\n * });\n *\n * // Then register abilities using this category\n * await registerAbility({\n * name: 'my-plugin/insert-block',\n * label: 'Insert Block',\n * description: 'Inserts a block into the editor',\n * category: 'block-editor',\n * callback: async ({ blockType }) => {\n * // Implementation\n * return { success: true };\n * }\n * });\n * ```\n */\nexport async function registerAbilityCategory(\n\tslug: string,\n\targs: AbilityCategoryArgs\n): Promise< void > {\n\tawait dispatch( store ).registerAbilityCategory( slug, args );\n}\n\n/**\n * Unregister an ability category.\n *\n * Removes a category from the store.\n *\n * @param slug The category slug to unregister.\n *\n * @example\n * ```js\n * unregisterAbilityCategory('block-editor');\n * ```\n */\nexport function unregisterAbilityCategory( slug: string ): void {\n\tdispatch( store ).unregisterAbilityCategory( slug );\n}\n\n/**\n * Execute a client-side ability.\n *\n * @param ability The ability to execute.\n * @param input Input parameters for the ability.\n * @return Promise resolving to the ability execution result.\n * @throws Error if validation fails or execution errors.\n */\nasync function executeClientAbility(\n\tability: Ability,\n\tinput: AbilityInput\n): Promise< AbilityOutput > {\n\tif ( ! ability.callback ) {\n\t\tthrow new Error(\n\t\t\tsprintf(\n\t\t\t\t'Client ability %s is missing callback function',\n\t\t\t\tability.name\n\t\t\t)\n\t\t);\n\t}\n\n\t// Check permission callback if defined\n\tif ( ability.permissionCallback ) {\n\t\tconst hasPermission = await ability.permissionCallback( input );\n\t\tif ( ! hasPermission ) {\n\t\t\tconst error = new Error(\n\t\t\t\tsprintf( 'Permission denied for ability: %s', ability.name )\n\t\t\t);\n\t\t\t( error as any ).code = 'ability_permission_denied';\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tif ( ability.input_schema ) {\n\t\tconst inputValidation = validateValueFromSchema(\n\t\t\tinput,\n\t\t\tability.input_schema,\n\t\t\t'input'\n\t\t);\n\t\tif ( inputValidation !== true ) {\n\t\t\tconst error = new Error(\n\t\t\t\tsprintf(\n\t\t\t\t\t'Ability \"%1$s\" has invalid input. Reason: %2$s',\n\t\t\t\t\tability.name,\n\t\t\t\t\tinputValidation\n\t\t\t\t)\n\t\t\t);\n\t\t\t( error as any ).code = 'ability_invalid_input';\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tlet result: AbilityOutput;\n\ttry {\n\t\tresult = await ability.callback( input );\n\t} catch ( error ) {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.error(\n\t\t\t`Error executing client ability ${ ability.name }:`,\n\t\t\terror\n\t\t);\n\t\tthrow error;\n\t}\n\n\tif ( ability.output_schema ) {\n\t\tconst outputValidation = validateValueFromSchema(\n\t\t\tresult,\n\t\t\tability.output_schema,\n\t\t\t'output'\n\t\t);\n\t\tif ( outputValidation !== true ) {\n\t\t\tconst error = new Error(\n\t\t\t\tsprintf(\n\t\t\t\t\t'Ability \"%1$s\" has invalid output. Reason: %2$s',\n\t\t\t\t\tability.name,\n\t\t\t\t\toutputValidation\n\t\t\t\t)\n\t\t\t);\n\t\t\t( error as any ).code = 'ability_invalid_output';\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Execute a server-side ability.\n *\n * @param ability The ability to execute.\n * @param input Input parameters for the ability.\n * @return Promise resolving to the ability execution result.\n * @throws Error if the API call fails.\n */\nasync function executeServerAbility(\n\tability: Ability,\n\tinput: AbilityInput\n): Promise< AbilityOutput > {\n\tlet method = 'POST';\n\tif ( !! ability.meta?.annotations?.readonly ) {\n\t\tmethod = 'GET';\n\t} else if (\n\t\t!! ability.meta?.annotations?.destructive &&\n\t\t!! ability.meta?.annotations?.idempotent\n\t) {\n\t\tmethod = 'DELETE';\n\t}\n\n\tlet path = `/wp-abilities/v1/abilities/${ ability.name }/run`;\n\tconst options: {\n\t\tmethod: string;\n\t\tdata?: { input: AbilityInput };\n\t} = {\n\t\tmethod,\n\t};\n\n\tif ( [ 'GET', 'DELETE' ].includes( method ) && input !== null ) {\n\t\t// For GET and DELETE requests, pass the input directly.\n\t\tpath = addQueryArgs( path, { input } );\n\t} else if ( method === 'POST' && input !== null ) {\n\t\toptions.data = { input };\n\t}\n\n\t// Note: Input and output validation happens on the server side for these abilities.\n\ttry {\n\t\treturn await apiFetch< AbilityOutput >( {\n\t\t\tpath,\n\t\t\t...options,\n\t\t} );\n\t} catch ( error ) {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.error( `Error executing ability ${ ability.name }:`, error );\n\t\tthrow error;\n\t}\n}\n\n/**\n * Execute an ability.\n *\n * Determines whether to execute locally (client abilities) or remotely (server abilities)\n * based on whether the ability has a callback function.\n *\n * @param name The ability name.\n * @param input Optional input parameters for the ability.\n * @return Promise resolving to the ability execution result.\n * @throws Error if the ability is not found or execution fails.\n */\nexport async function executeAbility(\n\tname: string,\n\tinput?: AbilityInput\n): Promise< AbilityOutput > {\n\tconst ability = await getAbility( name );\n\tif ( ! ability ) {\n\t\tthrow new Error( sprintf( 'Ability not found: %s', name ) );\n\t}\n\n\tif ( ability.callback ) {\n\t\treturn executeClientAbility( ability, input );\n\t}\n\n\treturn executeServerAbility( ability, input );\n}\n"],
5
- "mappings": "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",
6
- "names": ["apiFetch"]
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { dispatch, select } from '@wordpress/data';\nimport { sprintf } from '@wordpress/i18n';\n\n/**\n * Internal dependencies\n */\nimport { store } from './store';\nimport type {\n\tAbility,\n\tAbilityCategory,\n\tAbilityCategoryArgs,\n\tAbilitiesQueryArgs,\n\tAbilityInput,\n\tAbilityOutput,\n} from './types';\nimport { validateValueFromSchema } from './validation';\n\n/**\n * Get all available abilities with optional filtering.\n *\n * @param args Optional query arguments to filter. Defaults to empty object.\n * @return Array of abilities.\n */\nexport function getAbilities( args: AbilitiesQueryArgs = {} ): Ability[] {\n\treturn select( store ).getAbilities( args );\n}\n\n/**\n * Get a specific ability by name.\n *\n * @param name The ability name.\n * @return The ability or undefined if not found.\n */\nexport function getAbility( name: string ): Ability | undefined {\n\treturn select( store ).getAbility( name );\n}\n\n/**\n * Get all available ability categories.\n *\n * @return Array of categories.\n */\nexport function getAbilityCategories(): AbilityCategory[] {\n\treturn select( store ).getAbilityCategories();\n}\n\n/**\n * Get a specific ability category by slug.\n *\n * @param slug The category slug.\n * @return The category or undefined if not found.\n */\nexport function getAbilityCategory(\n\tslug: string\n): AbilityCategory | undefined {\n\treturn select( store ).getAbilityCategory( slug );\n}\n\n/**\n * Register a client-side ability.\n *\n * Client abilities are executed locally in the browser and must include\n * a callback function. The ability will be validated by the store action,\n * and an error will be thrown if validation fails.\n *\n * The category must already be registered before registering abilities.\n *\n * @param ability The ability definition including callback.\n * @throws {Error} If the ability fails validation.\n *\n * @example\n * ```js\n * registerAbility({\n * name: 'my-plugin/navigate',\n * label: 'Navigate to URL',\n * description: 'Navigates to a URL within WordPress admin',\n * category: 'navigation',\n * input_schema: {\n * type: 'object',\n * properties: {\n * url: { type: 'string' }\n * },\n * required: ['url']\n * },\n * callback: async ({ url }) => {\n * window.location.href = url;\n * return { success: true };\n * }\n * });\n * ```\n */\nexport function registerAbility( ability: Ability ): void {\n\tdispatch( store ).registerAbility( ability );\n}\n\n/**\n * Unregister an ability from the store.\n *\n * Remove a client-side ability from the store.\n * Note: This will return an error for server-side abilities.\n *\n * @param name The ability name to unregister.\n */\nexport function unregisterAbility( name: string ): void {\n\tdispatch( store ).unregisterAbility( name );\n}\n\n/**\n * Register a client-side ability category.\n *\n * Categories registered on the client are stored alongside server-side categories\n * in the same store and can be used when registering client side abilities.\n * This is useful when registering client-side abilities that introduce new\n * categories not defined by the server.\n *\n * @param slug Category slug (lowercase alphanumeric with dashes only).\n * @param args Category arguments (label, description, optional meta).\n * @throws {Error} If the category fails validation.\n *\n * @example\n * ```js\n * // Register a new category for block editor abilities\n * registerAbilityCategory('block-editor', {\n * label: 'Block Editor',\n * description: 'Abilities for interacting with the WordPress block editor'\n * });\n *\n * // Then register abilities using this category\n * registerAbility({\n * name: 'my-plugin/insert-block',\n * label: 'Insert Block',\n * description: 'Inserts a block into the editor',\n * category: 'block-editor',\n * callback: async ({ blockType }) => {\n * // Implementation\n * return { success: true };\n * }\n * });\n * ```\n */\nexport function registerAbilityCategory(\n\tslug: string,\n\targs: AbilityCategoryArgs\n): void {\n\tdispatch( store ).registerAbilityCategory( slug, args );\n}\n\n/**\n * Unregister an ability category.\n *\n * Removes a category from the store.\n *\n * @param slug The category slug to unregister.\n *\n * @example\n * ```js\n * unregisterAbilityCategory('block-editor');\n * ```\n */\nexport function unregisterAbilityCategory( slug: string ): void {\n\tdispatch( store ).unregisterAbilityCategory( slug );\n}\n\n/**\n * Execute an ability.\n *\n * Executes abilities with validation for client-side abilities only.\n * Server abilities bypass validation as it's handled on the server.\n *\n * @param name The ability name.\n * @param input Optional input parameters for the ability.\n * @return Promise resolving to the ability execution result.\n * @throws Error if the ability is not found or execution fails.\n */\nexport async function executeAbility(\n\tname: string,\n\tinput?: AbilityInput\n): Promise< AbilityOutput > {\n\tconst ability = getAbility( name );\n\tif ( ! ability ) {\n\t\tthrow new Error( sprintf( 'Ability not found: %s', name ) );\n\t}\n\n\tif ( ! ability.callback ) {\n\t\tthrow new Error(\n\t\t\tsprintf(\n\t\t\t\t'Ability \"%s\" is missing callback. Please ensure the ability is properly registered.',\n\t\t\t\tability.name\n\t\t\t)\n\t\t);\n\t}\n\n\t// Check permission callback if defined\n\tif ( ability.permissionCallback ) {\n\t\tconst hasPermission = await ability.permissionCallback( input );\n\t\tif ( ! hasPermission ) {\n\t\t\tconst error = new Error(\n\t\t\t\tsprintf( 'Permission denied for ability: %s', ability.name )\n\t\t\t);\n\t\t\t( error as any ).code = 'ability_permission_denied';\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// Validate input\n\tif ( ability.input_schema ) {\n\t\tconst inputValidation = validateValueFromSchema(\n\t\t\tinput,\n\t\t\tability.input_schema,\n\t\t\t'input'\n\t\t);\n\t\tif ( inputValidation !== true ) {\n\t\t\tconst error = new Error(\n\t\t\t\tsprintf(\n\t\t\t\t\t'Ability \"%1$s\" has invalid input. Reason: %2$s',\n\t\t\t\t\tability.name,\n\t\t\t\t\tinputValidation\n\t\t\t\t)\n\t\t\t);\n\t\t\t( error as any ).code = 'ability_invalid_input';\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// Execute the ability\n\tlet result: AbilityOutput;\n\ttry {\n\t\tresult = await ability.callback( input );\n\t} catch ( error ) {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.error( `Error executing ability ${ ability.name }:`, error );\n\t\tthrow error;\n\t}\n\n\t// Validate output\n\tif ( ability.output_schema ) {\n\t\tconst outputValidation = validateValueFromSchema(\n\t\t\tresult,\n\t\t\tability.output_schema,\n\t\t\t'output'\n\t\t);\n\t\tif ( outputValidation !== true ) {\n\t\t\tconst error = new Error(\n\t\t\t\tsprintf(\n\t\t\t\t\t'Ability \"%1$s\" has invalid output. Reason: %2$s',\n\t\t\t\t\tability.name,\n\t\t\t\t\toutputValidation\n\t\t\t\t)\n\t\t\t);\n\t\t\t( error as any ).code = 'ability_invalid_output';\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\treturn result;\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,kBAAiC;AACjC,kBAAwB;AAKxB,mBAAsB;AAStB,wBAAwC;AAQjC,SAAS,aAAc,OAA2B,CAAC,GAAe;AACxE,aAAO,oBAAQ,kBAAM,EAAE,aAAc,IAAK;AAC3C;AAQO,SAAS,WAAY,MAAoC;AAC/D,aAAO,oBAAQ,kBAAM,EAAE,WAAY,IAAK;AACzC;AAOO,SAAS,uBAA0C;AACzD,aAAO,oBAAQ,kBAAM,EAAE,qBAAqB;AAC7C;AAQO,SAAS,mBACf,MAC8B;AAC9B,aAAO,oBAAQ,kBAAM,EAAE,mBAAoB,IAAK;AACjD;AAmCO,SAAS,gBAAiB,SAAyB;AACzD,4BAAU,kBAAM,EAAE,gBAAiB,OAAQ;AAC5C;AAUO,SAAS,kBAAmB,MAAqB;AACvD,4BAAU,kBAAM,EAAE,kBAAmB,IAAK;AAC3C;AAmCO,SAAS,wBACf,MACA,MACO;AACP,4BAAU,kBAAM,EAAE,wBAAyB,MAAM,IAAK;AACvD;AAcO,SAAS,0BAA2B,MAAqB;AAC/D,4BAAU,kBAAM,EAAE,0BAA2B,IAAK;AACnD;AAaA,eAAsB,eACrB,MACA,OAC2B;AAC3B,QAAM,UAAU,WAAY,IAAK;AACjC,MAAK,CAAE,SAAU;AAChB,UAAM,IAAI,UAAO,qBAAS,yBAAyB,IAAK,CAAE;AAAA,EAC3D;AAEA,MAAK,CAAE,QAAQ,UAAW;AACzB,UAAM,IAAI;AAAA,UACT;AAAA,QACC;AAAA,QACA,QAAQ;AAAA,MACT;AAAA,IACD;AAAA,EACD;AAGA,MAAK,QAAQ,oBAAqB;AACjC,UAAM,gBAAgB,MAAM,QAAQ,mBAAoB,KAAM;AAC9D,QAAK,CAAE,eAAgB;AACtB,YAAM,QAAQ,IAAI;AAAA,YACjB,qBAAS,qCAAqC,QAAQ,IAAK;AAAA,MAC5D;AACA,MAAE,MAAe,OAAO;AACxB,YAAM;AAAA,IACP;AAAA,EACD;AAGA,MAAK,QAAQ,cAAe;AAC3B,UAAM,sBAAkB;AAAA,MACvB;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,IACD;AACA,QAAK,oBAAoB,MAAO;AAC/B,YAAM,QAAQ,IAAI;AAAA,YACjB;AAAA,UACC;AAAA,UACA,QAAQ;AAAA,UACR;AAAA,QACD;AAAA,MACD;AACA,MAAE,MAAe,OAAO;AACxB,YAAM;AAAA,IACP;AAAA,EACD;AAGA,MAAI;AACJ,MAAI;AACH,aAAS,MAAM,QAAQ,SAAU,KAAM;AAAA,EACxC,SAAU,OAAQ;AAEjB,YAAQ,MAAO,2BAA4B,QAAQ,IAAK,KAAK,KAAM;AACnE,UAAM;AAAA,EACP;AAGA,MAAK,QAAQ,eAAgB;AAC5B,UAAM,uBAAmB;AAAA,MACxB;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,IACD;AACA,QAAK,qBAAqB,MAAO;AAChC,YAAM,QAAQ,IAAI;AAAA,YACjB;AAAA,UACC;AAAA,UACA,QAAQ;AAAA,UACR;AAAA,QACD;AAAA,MACD;AACA,MAAE,MAAe,OAAO;AACxB,YAAM;AAAA,IACP;AAAA,EACD;AAEA,SAAO;AACR;",
6
+ "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["/**\n * WordPress Abilities API Client\n *\n * This package provides a client for interacting with the\n * WordPress Abilities API, allowing you to list, retrieve, and execute\n * abilities from client-side code.\n *\n * @package\n */\n\n/**\n * Public API functions\n */\nexport {\n\tgetAbilities,\n\tgetAbility,\n\tgetAbilityCategories,\n\tgetAbilityCategory,\n\texecuteAbility,\n\tregisterAbility,\n\tunregisterAbility,\n\tregisterAbilityCategory,\n\tunregisterAbilityCategory,\n} from './api';\n\n/**\n * The store can be used directly with @wordpress/data via selectors\n * in React components with useSelect.\n *\n * @example\n * ```js\n * import { useSelect } from '@wordpress/data';\n * import { store as abilitiesStore } from '@wordpress/abilities';\n *\n * function MyComponent() {\n * const abilities = useSelect(\n * (select) => select(abilitiesStore).getAbilities(),\n * []\n * );\n * // Use abilities...\n * }\n * ```\n */\nexport { store } from './store';\n\n/**\n * Type definitions\n */\nexport type {\n\tAbility,\n\tAbilityCategory,\n\tAbilityCategoryArgs,\n\tAbilitiesState,\n\tAbilitiesQueryArgs,\n\tAbilityCallback,\n\tPermissionCallback,\n\tAbilityInput,\n\tAbilityOutput,\n\tValidationError,\n} from './types';\n\n/**\n * Validation utilities\n */\nexport { validateValueFromSchema } from './validation';\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,iBAUO;AAoBP,mBAAsB;AAqBtB,wBAAwC;",
4
+ "sourcesContent": ["/**\n * WordPress Abilities API Client\n *\n * This package provides a client for interacting with the\n * WordPress Abilities API, allowing you to list, retrieve, and execute\n * abilities from client-side code.\n *\n * @package\n */\n\n/**\n * Public API functions\n */\nexport {\n\tgetAbilities,\n\tgetAbility,\n\tgetAbilityCategories,\n\tgetAbilityCategory,\n\texecuteAbility,\n\tregisterAbility,\n\tunregisterAbility,\n\tregisterAbilityCategory,\n\tunregisterAbilityCategory,\n} from './api';\n\n/**\n * The store can be used directly with @wordpress/data via selectors\n * in React components with useSelect.\n *\n * @example\n * ```js\n * import { useSelect } from '@wordpress/data';\n * import { store as abilitiesStore } from '@wordpress/abilities';\n *\n * function MyComponent() {\n * const abilities = useSelect(\n * (select) => select(abilitiesStore).getAbilities(),\n * []\n * );\n * // Use abilities...\n * }\n * ```\n */\nexport { store } from './store';\n\n/**\n * Type definitions\n */\nexport type {\n\tAbility,\n\tAbilityCategory,\n\tAbilityCategoryArgs,\n\tAbilitiesQueryArgs,\n\tAbilityCallback,\n\tPermissionCallback,\n\tAbilityInput,\n\tAbilityOutput,\n\tValidationError,\n} from './types';\n\n/**\n * Validation utilities\n */\nexport { validateValueFromSchema } from './validation';\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,iBAUO;AAoBP,mBAAsB;AAoBtB,wBAAwC;",
6
6
  "names": []
7
7
  }
@@ -20,8 +20,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // packages/abilities/src/store/actions.ts
21
21
  var actions_exports = {};
22
22
  __export(actions_exports, {
23
- receiveAbilities: () => receiveAbilities,
24
- receiveCategories: () => receiveCategories,
25
23
  registerAbility: () => registerAbility,
26
24
  registerAbilityCategory: () => registerAbilityCategory,
27
25
  unregisterAbility: () => unregisterAbility,
@@ -29,26 +27,24 @@ __export(actions_exports, {
29
27
  });
30
28
  module.exports = __toCommonJS(actions_exports);
31
29
  var import_i18n = require("@wordpress/i18n");
32
- var import_data = require("@wordpress/data");
33
30
  var import_constants = require("./constants");
34
- function receiveAbilities(abilities) {
35
- return {
36
- type: import_constants.RECEIVE_ABILITIES,
37
- abilities
38
- };
39
- }
40
- function receiveCategories(categories) {
41
- return {
42
- type: import_constants.RECEIVE_CATEGORIES,
43
- categories
44
- };
31
+ function filterAnnotations(sourceAnnotations, allowedKeys) {
32
+ const annotations = {};
33
+ if (sourceAnnotations) {
34
+ for (const key of allowedKeys) {
35
+ if (sourceAnnotations[key] !== void 0) {
36
+ annotations[key] = sourceAnnotations[key];
37
+ }
38
+ }
39
+ }
40
+ return annotations;
45
41
  }
46
42
  function registerAbility(ability) {
47
- return async ({ select, dispatch }) => {
43
+ return ({ select, dispatch }) => {
48
44
  if (!ability.name) {
49
45
  throw new Error("Ability name is required");
50
46
  }
51
- if (!/^[a-z0-9-]+\/[a-z0-9-]+$/.test(ability.name)) {
47
+ if (!import_constants.ABILITY_NAME_PATTERN.test(ability.name)) {
52
48
  throw new Error(
53
49
  'Ability name must be a string containing a namespace prefix, i.e. "my-plugin/my-ability". It can only contain lowercase alphanumeric characters, dashes and the forward slash.'
54
50
  );
@@ -68,16 +64,16 @@ function registerAbility(ability) {
68
64
  (0, import_i18n.sprintf)('Ability "%s" must have a category', ability.name)
69
65
  );
70
66
  }
71
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(ability.category)) {
67
+ if (!import_constants.CATEGORY_SLUG_PATTERN.test(ability.category)) {
72
68
  throw new Error(
73
69
  (0, import_i18n.sprintf)(
74
- 'Ability "%1$s" has an invalid category. Category must be lowercase alphanumeric with dashes only Got: "%2$s"',
70
+ 'Ability "%1$s" has an invalid category. Category must be lowercase alphanumeric with dashes only. Got: "%2$s"',
75
71
  ability.name,
76
72
  ability.category
77
73
  )
78
74
  );
79
75
  }
80
- const categories = await (0, import_data.resolveSelect)(import_constants.STORE_NAME).getAbilityCategories();
76
+ const categories = select.getAbilityCategories();
81
77
  const existingCategory = categories.find(
82
78
  (cat) => cat.slug === ability.category
83
79
  );
@@ -104,9 +100,23 @@ function registerAbility(ability) {
104
100
  (0, import_i18n.sprintf)('Ability "%s" is already registered', ability.name)
105
101
  );
106
102
  }
103
+ const annotations = filterAnnotations(ability.meta?.annotations, [
104
+ "readonly",
105
+ "destructive",
106
+ "idempotent",
107
+ "serverRegistered",
108
+ "clientRegistered"
109
+ ]);
110
+ if (!annotations.serverRegistered) {
111
+ annotations.clientRegistered = true;
112
+ }
113
+ const meta = { annotations };
107
114
  dispatch({
108
115
  type: import_constants.REGISTER_ABILITY,
109
- ability
116
+ ability: {
117
+ ...ability,
118
+ meta
119
+ }
110
120
  });
111
121
  };
112
122
  }
@@ -117,16 +127,15 @@ function unregisterAbility(name) {
117
127
  };
118
128
  }
119
129
  function registerAbilityCategory(slug, args) {
120
- return async ({ select, dispatch }) => {
130
+ return ({ select, dispatch }) => {
121
131
  if (!slug) {
122
132
  throw new Error("Category slug is required");
123
133
  }
124
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) {
134
+ if (!import_constants.CATEGORY_SLUG_PATTERN.test(slug)) {
125
135
  throw new Error(
126
136
  "Category slug must contain only lowercase alphanumeric characters and dashes."
127
137
  );
128
138
  }
129
- await (0, import_data.resolveSelect)(import_constants.STORE_NAME).getAbilityCategories();
130
139
  const existingCategory = select.getAbilityCategory(slug);
131
140
  if (existingCategory) {
132
141
  throw new Error(
@@ -148,16 +157,19 @@ function registerAbilityCategory(slug, args) {
148
157
  "The category properties should provide a valid `meta` object."
149
158
  );
150
159
  }
160
+ const annotations = filterAnnotations(args.meta?.annotations, [
161
+ "serverRegistered",
162
+ "clientRegistered"
163
+ ]);
164
+ if (!annotations.serverRegistered) {
165
+ annotations.clientRegistered = true;
166
+ }
167
+ const meta = { annotations };
151
168
  const category = {
152
169
  slug,
153
170
  label: args.label,
154
171
  description: args.description,
155
- meta: {
156
- ...args.meta || {},
157
- // Internal implementation note: Client-registered categories will have `meta._clientRegistered` set to `true` to differentiate them from server-fetched categories.
158
- // This is used internally by the resolver to determine whether to fetch categories from the server.
159
- _clientRegistered: true
160
- }
172
+ meta
161
173
  };
162
174
  dispatch({
163
175
  type: import_constants.REGISTER_ABILITY_CATEGORY,
@@ -173,8 +185,6 @@ function unregisterAbilityCategory(slug) {
173
185
  }
174
186
  // Annotate the CommonJS export names for ESM import in node:
175
187
  0 && (module.exports = {
176
- receiveAbilities,
177
- receiveCategories,
178
188
  registerAbility,
179
189
  registerAbilityCategory,
180
190
  unregisterAbility,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/store/actions.ts"],
4
- "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { sprintf } from '@wordpress/i18n';\nimport { resolveSelect } from '@wordpress/data';\n\n/**\n * Internal dependencies\n */\nimport type { Ability, AbilityCategory, AbilityCategoryArgs } from '../types';\nimport {\n\tRECEIVE_ABILITIES,\n\tREGISTER_ABILITY,\n\tUNREGISTER_ABILITY,\n\tRECEIVE_CATEGORIES,\n\tREGISTER_ABILITY_CATEGORY,\n\tUNREGISTER_ABILITY_CATEGORY,\n\tSTORE_NAME,\n} from './constants';\n\n/**\n * Returns an action object used to receive abilities into the store.\n *\n * @param abilities Array of abilities to store.\n * @return Action object.\n */\nexport function receiveAbilities( abilities: Ability[] ) {\n\treturn {\n\t\ttype: RECEIVE_ABILITIES,\n\t\tabilities,\n\t};\n}\n\n/**\n * Returns an action object used to receive categories into the store.\n *\n * @param categories Array of categories to store.\n * @return Action object.\n */\nexport function receiveCategories( categories: AbilityCategory[] ) {\n\treturn {\n\t\ttype: RECEIVE_CATEGORIES,\n\t\tcategories,\n\t};\n}\n\n/**\n * Registers an ability in the store.\n *\n * This action validates the ability before registration. If validation fails,\n * an error will be thrown. Categories will be automatically fetched from the\n * REST API if they haven't been loaded yet.\n *\n * @param ability The ability to register.\n * @return Action object or function.\n * @throws {Error} If validation fails.\n */\nexport function registerAbility( ability: Ability ) {\n\t// @ts-expect-error - registry types are not yet available\n\treturn async ( { select, dispatch } ) => {\n\t\tif ( ! ability.name ) {\n\t\t\tthrow new Error( 'Ability name is required' );\n\t\t}\n\n\t\t// Validate name format matches server implementation\n\t\tif ( ! /^[a-z0-9-]+\\/[a-z0-9-]+$/.test( ability.name ) ) {\n\t\t\tthrow new Error(\n\t\t\t\t'Ability name must be a string containing a namespace prefix, i.e. \"my-plugin/my-ability\". It can only contain lowercase alphanumeric characters, dashes and the forward slash.'\n\t\t\t);\n\t\t}\n\n\t\tif ( ! ability.label ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf( 'Ability \"%s\" must have a label', ability.name )\n\t\t\t);\n\t\t}\n\n\t\tif ( ! ability.description ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf( 'Ability \"%s\" must have a description', ability.name )\n\t\t\t);\n\t\t}\n\n\t\tif ( ! ability.category ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf( 'Ability \"%s\" must have a category', ability.name )\n\t\t\t);\n\t\t}\n\n\t\t// Validate category format\n\t\tif ( ! /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test( ability.category ) ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf(\n\t\t\t\t\t'Ability \"%1$s\" has an invalid category. Category must be lowercase alphanumeric with dashes only Got: \"%2$s\"',\n\t\t\t\t\tability.name,\n\t\t\t\t\tability.category\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\t// Ensure categories are loaded before validating\n\t\tconst categories =\n\t\t\tawait resolveSelect( STORE_NAME ).getAbilityCategories();\n\t\tconst existingCategory = categories.find(\n\t\t\t( cat: AbilityCategory ) => cat.slug === ability.category\n\t\t);\n\t\tif ( ! existingCategory ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf(\n\t\t\t\t\t'Ability \"%1$s\" references non-existent category \"%2$s\". Please register the category first.',\n\t\t\t\t\tability.name,\n\t\t\t\t\tability.category\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\t// Client-side abilities must have a callback\n\t\tif ( ability.callback && typeof ability.callback !== 'function' ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf(\n\t\t\t\t\t'Ability \"%s\" has an invalid callback. Callback must be a function',\n\t\t\t\t\tability.name\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\t// Check if ability is already registered\n\t\tconst existingAbility = select.getAbility( ability.name );\n\t\tif ( existingAbility ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf( 'Ability \"%s\" is already registered', ability.name )\n\t\t\t);\n\t\t}\n\n\t\t// All validation passed, dispatch the registration action\n\t\tdispatch( {\n\t\t\ttype: REGISTER_ABILITY,\n\t\t\tability,\n\t\t} );\n\t};\n}\n\n/**\n * Returns an action object used to unregister a client-side ability.\n *\n * @param name The name of the ability to unregister.\n * @return Action object.\n */\nexport function unregisterAbility( name: string ) {\n\treturn {\n\t\ttype: UNREGISTER_ABILITY,\n\t\tname,\n\t};\n}\n\n/**\n * Registers a client-side ability category in the store.\n *\n * This action validates the category before registration. If validation fails,\n * an error will be thrown. Categories will be automatically fetched from the\n * REST API if they haven't been loaded yet to check for duplicates.\n *\n * @param slug The unique category slug identifier.\n * @param args Category arguments (label, description, optional meta).\n * @return Action object or function.\n * @throws {Error} If validation fails.\n */\nexport function registerAbilityCategory(\n\tslug: string,\n\targs: AbilityCategoryArgs\n) {\n\t// @ts-expect-error - registry types are not yet available\n\treturn async ( { select, dispatch } ) => {\n\t\tif ( ! slug ) {\n\t\t\tthrow new Error( 'Category slug is required' );\n\t\t}\n\n\t\t// Validate slug format matches server implementation\n\t\tif ( ! /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test( slug ) ) {\n\t\t\tthrow new Error(\n\t\t\t\t'Category slug must contain only lowercase alphanumeric characters and dashes.'\n\t\t\t);\n\t\t}\n\n\t\t// Ensure categories are loaded before checking for duplicates\n\t\tawait resolveSelect( STORE_NAME ).getAbilityCategories();\n\t\tconst existingCategory = select.getAbilityCategory( slug );\n\t\tif ( existingCategory ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf( 'Category \"%s\" is already registered.', slug )\n\t\t\t);\n\t\t}\n\n\t\t// Validate label presence and type (matches PHP empty() + is_string())\n\t\tif ( ! args.label || typeof args.label !== 'string' ) {\n\t\t\tthrow new Error(\n\t\t\t\t'The category properties must contain a `label` string.'\n\t\t\t);\n\t\t}\n\n\t\t// Validate description presence and type (matches PHP empty() + is_string())\n\t\tif ( ! args.description || typeof args.description !== 'string' ) {\n\t\t\tthrow new Error(\n\t\t\t\t'The category properties must contain a `description` string.'\n\t\t\t);\n\t\t}\n\n\t\tif (\n\t\t\targs.meta !== undefined &&\n\t\t\t( typeof args.meta !== 'object' || Array.isArray( args.meta ) )\n\t\t) {\n\t\t\tthrow new Error(\n\t\t\t\t'The category properties should provide a valid `meta` object.'\n\t\t\t);\n\t\t}\n\n\t\tconst category: AbilityCategory = {\n\t\t\tslug,\n\t\t\tlabel: args.label,\n\t\t\tdescription: args.description,\n\t\t\tmeta: {\n\t\t\t\t...( args.meta || {} ),\n\t\t\t\t// Internal implementation note: Client-registered categories will have `meta._clientRegistered` set to `true` to differentiate them from server-fetched categories.\n\t\t\t\t// This is used internally by the resolver to determine whether to fetch categories from the server.\n\t\t\t\t_clientRegistered: true,\n\t\t\t},\n\t\t};\n\n\t\tdispatch( {\n\t\t\ttype: REGISTER_ABILITY_CATEGORY,\n\t\t\tcategory,\n\t\t} );\n\t};\n}\n\n/**\n * Returns an action object used to unregister a client-side ability category.\n *\n * @param slug The slug of the category to unregister.\n * @return Action object.\n */\nexport function unregisterAbilityCategory( slug: string ) {\n\treturn {\n\t\ttype: UNREGISTER_ABILITY_CATEGORY,\n\t\tslug,\n\t};\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { sprintf } from '@wordpress/i18n';\n\n/**\n * Internal dependencies\n */\nimport type { Ability, AbilityCategory, AbilityCategoryArgs } from '../types';\nimport {\n\tREGISTER_ABILITY,\n\tUNREGISTER_ABILITY,\n\tREGISTER_ABILITY_CATEGORY,\n\tUNREGISTER_ABILITY_CATEGORY,\n\tABILITY_NAME_PATTERN,\n\tCATEGORY_SLUG_PATTERN,\n} from './constants';\n\ntype AbilityAnnotations = NonNullable< Ability[ 'meta' ] >[ 'annotations' ];\n\n/**\n * Filters annotations to only include allowed keys with non-null values.\n *\n * @param sourceAnnotations The source annotations object to filter.\n * @param allowedKeys Array of annotation keys to include.\n * @return Filtered annotations object.\n */\nfunction filterAnnotations< K extends keyof NonNullable< AbilityAnnotations > >(\n\tsourceAnnotations: Record< string, boolean > | undefined,\n\tallowedKeys: readonly K[]\n): NonNullable< AbilityAnnotations > {\n\tconst annotations: NonNullable< AbilityAnnotations > = {};\n\n\tif ( sourceAnnotations ) {\n\t\tfor ( const key of allowedKeys ) {\n\t\t\tif ( sourceAnnotations[ key ] !== undefined ) {\n\t\t\t\tannotations[ key ] = sourceAnnotations[ key ];\n\t\t\t}\n\t\t}\n\t}\n\treturn annotations;\n}\n\n/**\n * Registers an ability in the store.\n *\n * This action validates the ability before registration. If validation fails,\n * an error will be thrown.\n *\n * @param ability The ability to register.\n * @return Action object or function.\n * @throws {Error} If validation fails.\n */\nexport function registerAbility( ability: Ability ) {\n\t// @ts-expect-error - registry types are not yet available\n\treturn ( { select, dispatch } ) => {\n\t\tif ( ! ability.name ) {\n\t\t\tthrow new Error( 'Ability name is required' );\n\t\t}\n\n\t\t// Validate name format matches server implementation\n\t\tif ( ! ABILITY_NAME_PATTERN.test( ability.name ) ) {\n\t\t\tthrow new Error(\n\t\t\t\t'Ability name must be a string containing a namespace prefix, i.e. \"my-plugin/my-ability\". It can only contain lowercase alphanumeric characters, dashes and the forward slash.'\n\t\t\t);\n\t\t}\n\n\t\tif ( ! ability.label ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf( 'Ability \"%s\" must have a label', ability.name )\n\t\t\t);\n\t\t}\n\n\t\tif ( ! ability.description ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf( 'Ability \"%s\" must have a description', ability.name )\n\t\t\t);\n\t\t}\n\n\t\tif ( ! ability.category ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf( 'Ability \"%s\" must have a category', ability.name )\n\t\t\t);\n\t\t}\n\n\t\t// Validate category format\n\t\tif ( ! CATEGORY_SLUG_PATTERN.test( ability.category ) ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf(\n\t\t\t\t\t'Ability \"%1$s\" has an invalid category. Category must be lowercase alphanumeric with dashes only. Got: \"%2$s\"',\n\t\t\t\t\tability.name,\n\t\t\t\t\tability.category\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\t// Check that the category exists\n\t\tconst categories = select.getAbilityCategories();\n\t\tconst existingCategory = categories.find(\n\t\t\t( cat: AbilityCategory ) => cat.slug === ability.category\n\t\t);\n\t\tif ( ! existingCategory ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf(\n\t\t\t\t\t'Ability \"%1$s\" references non-existent category \"%2$s\". Please register the category first.',\n\t\t\t\t\tability.name,\n\t\t\t\t\tability.category\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\t// Client-side abilities must have a callback\n\t\tif ( ability.callback && typeof ability.callback !== 'function' ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf(\n\t\t\t\t\t'Ability \"%s\" has an invalid callback. Callback must be a function',\n\t\t\t\t\tability.name\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\t// Check if ability is already registered\n\t\tconst existingAbility = select.getAbility( ability.name );\n\t\tif ( existingAbility ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf( 'Ability \"%s\" is already registered', ability.name )\n\t\t\t);\n\t\t}\n\n\t\tconst annotations = filterAnnotations( ability.meta?.annotations, [\n\t\t\t'readonly',\n\t\t\t'destructive',\n\t\t\t'idempotent',\n\t\t\t'serverRegistered',\n\t\t\t'clientRegistered',\n\t\t] );\n\n\t\tif ( ! annotations.serverRegistered ) {\n\t\t\tannotations.clientRegistered = true;\n\t\t}\n\n\t\tconst meta = { annotations };\n\n\t\t// All validation passed, dispatch the registration action\n\t\tdispatch( {\n\t\t\ttype: REGISTER_ABILITY,\n\t\t\tability: {\n\t\t\t\t...ability,\n\t\t\t\tmeta,\n\t\t\t},\n\t\t} );\n\t};\n}\n\n/**\n * Returns an action object used to unregister a client-side ability.\n *\n * @param name The name of the ability to unregister.\n * @return Action object.\n */\nexport function unregisterAbility( name: string ) {\n\treturn {\n\t\ttype: UNREGISTER_ABILITY,\n\t\tname,\n\t};\n}\n\n/**\n * Registers a client-side ability category in the store.\n *\n * This action validates the category before registration. If validation fails,\n * an error will be thrown.\n *\n * @param slug The unique category slug identifier.\n * @param args Category arguments (label, description, optional meta).\n * @return Action object or function.\n * @throws {Error} If validation fails.\n */\nexport function registerAbilityCategory(\n\tslug: string,\n\targs: AbilityCategoryArgs\n) {\n\t// @ts-expect-error - registry types are not yet available\n\treturn ( { select, dispatch } ) => {\n\t\tif ( ! slug ) {\n\t\t\tthrow new Error( 'Category slug is required' );\n\t\t}\n\n\t\t// Validate slug format matches server implementation\n\t\tif ( ! CATEGORY_SLUG_PATTERN.test( slug ) ) {\n\t\t\tthrow new Error(\n\t\t\t\t'Category slug must contain only lowercase alphanumeric characters and dashes.'\n\t\t\t);\n\t\t}\n\n\t\t// Check for duplicates\n\t\tconst existingCategory = select.getAbilityCategory( slug );\n\t\tif ( existingCategory ) {\n\t\t\tthrow new Error(\n\t\t\t\tsprintf( 'Category \"%s\" is already registered.', slug )\n\t\t\t);\n\t\t}\n\n\t\t// Validate label presence and type (matches PHP empty() + is_string())\n\t\tif ( ! args.label || typeof args.label !== 'string' ) {\n\t\t\tthrow new Error(\n\t\t\t\t'The category properties must contain a `label` string.'\n\t\t\t);\n\t\t}\n\n\t\t// Validate description presence and type (matches PHP empty() + is_string())\n\t\tif ( ! args.description || typeof args.description !== 'string' ) {\n\t\t\tthrow new Error(\n\t\t\t\t'The category properties must contain a `description` string.'\n\t\t\t);\n\t\t}\n\n\t\tif (\n\t\t\targs.meta !== undefined &&\n\t\t\t( typeof args.meta !== 'object' || Array.isArray( args.meta ) )\n\t\t) {\n\t\t\tthrow new Error(\n\t\t\t\t'The category properties should provide a valid `meta` object.'\n\t\t\t);\n\t\t}\n\n\t\tconst annotations = filterAnnotations( args.meta?.annotations, [\n\t\t\t'serverRegistered',\n\t\t\t'clientRegistered',\n\t\t] );\n\n\t\tif ( ! annotations.serverRegistered ) {\n\t\t\tannotations.clientRegistered = true;\n\t\t}\n\n\t\tconst meta = { annotations };\n\t\tconst category: AbilityCategory = {\n\t\t\tslug,\n\t\t\tlabel: args.label,\n\t\t\tdescription: args.description,\n\t\t\tmeta,\n\t\t};\n\n\t\tdispatch( {\n\t\t\ttype: REGISTER_ABILITY_CATEGORY,\n\t\t\tcategory,\n\t\t} );\n\t};\n}\n\n/**\n * Returns an action object used to unregister a client-side ability category.\n *\n * @param slug The slug of the category to unregister.\n * @return Action object.\n */\nexport function unregisterAbilityCategory( slug: string ) {\n\treturn {\n\t\ttype: UNREGISTER_ABILITY_CATEGORY,\n\t\tslug,\n\t};\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -20,11 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // packages/abilities/src/store/constants.ts
21
21
  var constants_exports = {};
22
22
  __export(constants_exports, {
23
- ENTITY_KIND: () => ENTITY_KIND,
24
- ENTITY_NAME: () => ENTITY_NAME,
25
- ENTITY_NAME_CATEGORIES: () => ENTITY_NAME_CATEGORIES,
26
- RECEIVE_ABILITIES: () => RECEIVE_ABILITIES,
27
- RECEIVE_CATEGORIES: () => RECEIVE_CATEGORIES,
23
+ ABILITY_NAME_PATTERN: () => ABILITY_NAME_PATTERN,
24
+ CATEGORY_SLUG_PATTERN: () => CATEGORY_SLUG_PATTERN,
28
25
  REGISTER_ABILITY: () => REGISTER_ABILITY,
29
26
  REGISTER_ABILITY_CATEGORY: () => REGISTER_ABILITY_CATEGORY,
30
27
  STORE_NAME: () => STORE_NAME,
@@ -33,22 +30,16 @@ __export(constants_exports, {
33
30
  });
34
31
  module.exports = __toCommonJS(constants_exports);
35
32
  var STORE_NAME = "core/abilities";
36
- var ENTITY_KIND = "root";
37
- var ENTITY_NAME = "abilities";
38
- var ENTITY_NAME_CATEGORIES = "ability-categories";
39
- var RECEIVE_ABILITIES = "RECEIVE_ABILITIES";
33
+ var ABILITY_NAME_PATTERN = /^[a-z0-9-]+\/[a-z0-9-]+$/;
34
+ var CATEGORY_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
40
35
  var REGISTER_ABILITY = "REGISTER_ABILITY";
41
36
  var UNREGISTER_ABILITY = "UNREGISTER_ABILITY";
42
- var RECEIVE_CATEGORIES = "RECEIVE_CATEGORIES";
43
37
  var REGISTER_ABILITY_CATEGORY = "REGISTER_ABILITY_CATEGORY";
44
38
  var UNREGISTER_ABILITY_CATEGORY = "UNREGISTER_ABILITY_CATEGORY";
45
39
  // Annotate the CommonJS export names for ESM import in node:
46
40
  0 && (module.exports = {
47
- ENTITY_KIND,
48
- ENTITY_NAME,
49
- ENTITY_NAME_CATEGORIES,
50
- RECEIVE_ABILITIES,
51
- RECEIVE_CATEGORIES,
41
+ ABILITY_NAME_PATTERN,
42
+ CATEGORY_SLUG_PATTERN,
52
43
  REGISTER_ABILITY,
53
44
  REGISTER_ABILITY_CATEGORY,
54
45
  STORE_NAME,