@wordpress/abilities 0.2.1-next.8b30e05b0.0 → 0.2.1-next.8fd3f8831.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.
- package/README.md +32 -40
- package/build/{api.js → api.cjs} +22 -69
- package/build/api.cjs.map +7 -0
- package/build/{index.js → index.cjs} +4 -4
- package/build/{index.js.map → index.cjs.map} +2 -2
- package/build/store/{actions.js → actions.cjs} +43 -33
- package/build/store/actions.cjs.map +7 -0
- package/build/store/{constants.js → constants.cjs} +7 -16
- package/build/store/constants.cjs.map +7 -0
- package/build/store/{index.js → index.cjs} +6 -32
- package/build/store/index.cjs.map +7 -0
- package/build/store/{reducer.js → reducer.cjs} +4 -24
- package/build/store/reducer.cjs.map +7 -0
- package/build/store/{selectors.js → selectors.cjs} +2 -2
- package/build/store/selectors.cjs.map +7 -0
- package/build/store/types.cjs +19 -0
- package/build/store/types.cjs.map +7 -0
- package/build/{types.js → types.cjs} +1 -1
- package/build/types.cjs.map +7 -0
- package/build/{validation.js → validation.cjs} +3 -3
- package/build-module/api.js +22 -59
- package/build-module/api.js.map +2 -2
- package/build-module/index.js +3 -3
- package/build-module/index.js.map +2 -2
- package/build-module/store/actions.js +44 -33
- package/build-module/store/actions.js.map +2 -2
- package/build-module/store/constants.js +4 -10
- package/build-module/store/constants.js.map +2 -2
- package/build-module/store/index.js +6 -37
- package/build-module/store/index.js.map +2 -2
- package/build-module/store/reducer.js +3 -25
- package/build-module/store/reducer.js.map +2 -2
- package/build-module/store/selectors.js +1 -1
- package/build-module/store/selectors.js.map +2 -2
- package/build-module/store/types.js +1 -0
- package/build-module/store/types.js.map +7 -0
- package/build-types/api.d.ts +16 -23
- package/build-types/api.d.ts.map +1 -1
- package/build-types/index.d.ts +1 -1
- package/build-types/index.d.ts.map +1 -1
- package/build-types/store/actions.d.ts +5 -27
- package/build-types/store/actions.d.ts.map +1 -1
- package/build-types/store/constants.d.ts +2 -5
- package/build-types/store/constants.d.ts.map +1 -1
- package/build-types/store/index.d.ts.map +1 -1
- package/build-types/store/reducer.d.ts +21 -11
- package/build-types/store/reducer.d.ts.map +1 -1
- package/build-types/store/selectors.d.ts +2 -1
- package/build-types/store/selectors.d.ts.map +1 -1
- package/build-types/store/types.d.ts +18 -0
- package/build-types/store/types.d.ts.map +1 -0
- package/build-types/types.d.ts +14 -24
- package/build-types/types.d.ts.map +1 -1
- package/package.json +8 -10
- package/src/api.ts +43 -123
- package/src/index.ts +0 -1
- package/src/store/actions.ts +61 -46
- package/src/store/constants.ts +4 -5
- package/src/store/index.ts +2 -34
- package/src/store/reducer.ts +27 -33
- package/src/store/selectors.ts +3 -7
- package/src/store/tests/actions.test.ts +100 -244
- package/src/store/tests/reducer.test.ts +0 -451
- package/src/store/tests/selectors.test.ts +1 -1
- package/src/store/types.ts +19 -0
- package/src/tests/api.test.ts +112 -158
- package/src/types.ts +14 -26
- package/build/api.js.map +0 -7
- package/build/store/actions.js.map +0 -7
- package/build/store/constants.js.map +0 -7
- package/build/store/index.js.map +0 -7
- package/build/store/reducer.js.map +0 -7
- package/build/store/resolvers.js +0 -107
- package/build/store/resolvers.js.map +0 -7
- package/build/store/selectors.js.map +0 -7
- package/build/types.js.map +0 -7
- package/build-module/store/resolvers.js +0 -84
- package/build-module/store/resolvers.js.map +0 -7
- package/build-types/store/resolvers.d.ts +0 -47
- package/build-types/store/resolvers.d.ts.map +0 -1
- package/src/store/resolvers.ts +0 -151
- package/src/store/tests/resolvers.test.ts +0 -642
- /package/build/{validation.js.map → validation.cjs.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selectors.d.ts","sourceRoot":"","sources":["../../src/store/selectors.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"selectors.d.ts","sourceRoot":"","sources":["../../src/store/selectors.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,WAEhB,cAAc,iBACP,kBAAkB,KAC9B,OAAO,EAAE,qCAaZ,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,UAAU,CACzB,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,MAAM,GACV,OAAO,GAAG,SAAS,CAErB;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,WACvB,cAAc,KAAI,eAAe,EAAE,qCAI5C,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CACjC,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,MAAM,GACV,eAAe,GAAG,SAAS,CAE7B"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { Ability, AbilityCategory } from '../types';
|
|
5
|
+
/**
|
|
6
|
+
* The state shape for the abilities store.
|
|
7
|
+
*/
|
|
8
|
+
export interface AbilitiesState {
|
|
9
|
+
/**
|
|
10
|
+
* Map of ability names to ability objects.
|
|
11
|
+
*/
|
|
12
|
+
abilitiesByName: Record<string, Ability>;
|
|
13
|
+
/**
|
|
14
|
+
* Map of category slugs to category objects.
|
|
15
|
+
*/
|
|
16
|
+
categoriesBySlug: Record<string, AbilityCategory>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B;;OAEG;IACH,eAAe,EAAE,MAAM,CAAE,MAAM,EAAE,OAAO,CAAE,CAAC;IAE3C;;OAEG;IACH,gBAAgB,EAAE,MAAM,CAAE,MAAM,EAAE,eAAe,CAAE,CAAC;CACpD"}
|
package/build-types/types.d.ts
CHANGED
|
@@ -50,28 +50,27 @@ export interface Ability {
|
|
|
50
50
|
*/
|
|
51
51
|
output_schema?: Record<string, any>;
|
|
52
52
|
/**
|
|
53
|
-
* Callback function for
|
|
54
|
-
*
|
|
55
|
-
* If not present, the ability will be executed via REST API on the server.
|
|
53
|
+
* Callback function for ability execution.
|
|
54
|
+
* This property is required for all abilities.
|
|
56
55
|
*/
|
|
57
56
|
callback?: AbilityCallback;
|
|
58
57
|
/**
|
|
59
|
-
*
|
|
58
|
+
* Permission callback for abilities.
|
|
60
59
|
* Called before executing the ability to check if it's allowed.
|
|
61
60
|
* If it returns false, the ability execution will be denied.
|
|
62
61
|
*/
|
|
63
62
|
permissionCallback?: PermissionCallback;
|
|
64
63
|
/**
|
|
65
64
|
* Metadata about the ability.
|
|
66
|
-
* @see WP_Ability::get_meta()
|
|
67
65
|
*/
|
|
68
66
|
meta?: {
|
|
69
67
|
annotations?: {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
clientRegistered?: boolean;
|
|
69
|
+
serverRegistered?: boolean;
|
|
70
|
+
readonly?: boolean;
|
|
71
|
+
destructive?: boolean;
|
|
72
|
+
idempotent?: boolean;
|
|
73
73
|
};
|
|
74
|
-
[key: string]: any;
|
|
75
74
|
};
|
|
76
75
|
}
|
|
77
76
|
/**
|
|
@@ -108,9 +107,13 @@ export interface AbilityCategory {
|
|
|
108
107
|
description: string;
|
|
109
108
|
/**
|
|
110
109
|
* Metadata about the category.
|
|
111
|
-
* @see WP_Ability_Category::get_meta()
|
|
112
110
|
*/
|
|
113
|
-
meta?:
|
|
111
|
+
meta?: {
|
|
112
|
+
annotations?: {
|
|
113
|
+
clientRegistered?: boolean;
|
|
114
|
+
serverRegistered?: boolean;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
114
117
|
}
|
|
115
118
|
/**
|
|
116
119
|
* Arguments for registering an ability category.
|
|
@@ -132,19 +135,6 @@ export interface AbilityCategoryArgs {
|
|
|
132
135
|
*/
|
|
133
136
|
meta?: Record<string, any>;
|
|
134
137
|
}
|
|
135
|
-
/**
|
|
136
|
-
* The state shape for the abilities store.
|
|
137
|
-
*/
|
|
138
|
-
export interface AbilitiesState {
|
|
139
|
-
/**
|
|
140
|
-
* Map of ability names to ability objects.
|
|
141
|
-
*/
|
|
142
|
-
abilitiesByName: Record<string, Ability>;
|
|
143
|
-
/**
|
|
144
|
-
* Map of category slugs to category objects.
|
|
145
|
-
*/
|
|
146
|
-
categoriesBySlug: Record<string, AbilityCategory>;
|
|
147
|
-
}
|
|
148
138
|
/**
|
|
149
139
|
* Input parameters for ability execution.
|
|
150
140
|
* Can be any JSON-serializable value: primitive, array, object, or null.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAC7B,KAAK,EAAE,YAAY,KACf,aAAa,GAAG,OAAO,CAAE,aAAa,CAAE,CAAC;AAE9C;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAChC,KAAK,CAAC,EAAE,YAAY,KAChB,OAAO,GAAG,OAAO,CAAE,OAAO,CAAE,CAAC;AAElC;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACvB;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAE,MAAM,EAAE,GAAG,CAAE,CAAC;IAErC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAE,MAAM,EAAE,GAAG,CAAE,CAAC;IAEtC
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAC7B,KAAK,EAAE,YAAY,KACf,aAAa,GAAG,OAAO,CAAE,aAAa,CAAE,CAAC;AAE9C;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAChC,KAAK,CAAC,EAAE,YAAY,KAChB,OAAO,GAAG,OAAO,CAAE,OAAO,CAAE,CAAC;AAElC;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACvB;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAE,MAAM,EAAE,GAAG,CAAE,CAAC;IAErC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAE,MAAM,EAAE,GAAG,CAAE,CAAC;IAEtC;;;OAGG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAC;IAE3B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IAExC;;OAEG;IACH,IAAI,CAAC,EAAE;QACN,WAAW,CAAC,EAAE;YACb,gBAAgB,CAAC,EAAE,OAAO,CAAC;YAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;YAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;YACnB,WAAW,CAAC,EAAE,OAAO,CAAC;YACtB,UAAU,CAAC,EAAE,OAAO,CAAC;SACrB,CAAC;KACF,CAAC;CACF;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC/B;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,IAAI,CAAC,EAAE;QACN,WAAW,CAAC,EAAE;YACb,gBAAgB,CAAC,EAAE,OAAO,CAAC;YAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;SAC3B,CAAC;KACF,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IACnC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAE,MAAM,EAAE,GAAG,CAAE,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,GAAG,CAAC;AAE/B;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC;AAEhC;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/abilities",
|
|
3
|
-
"version": "0.2.1-next.
|
|
3
|
+
"version": "0.2.1-next.8fd3f8831.0",
|
|
4
4
|
"description": "JavaScript client for WordPress Abilities API.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -24,19 +24,20 @@
|
|
|
24
24
|
"npm": ">=8.19.2"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
|
+
"src",
|
|
27
28
|
"build",
|
|
28
29
|
"build-module",
|
|
29
30
|
"build-types",
|
|
30
|
-
"src",
|
|
31
31
|
"*.md"
|
|
32
32
|
],
|
|
33
|
-
"
|
|
33
|
+
"type": "module",
|
|
34
|
+
"main": "build/index.cjs",
|
|
34
35
|
"module": "build-module/index.js",
|
|
35
36
|
"exports": {
|
|
36
37
|
".": {
|
|
37
38
|
"types": "./build-types/index.d.ts",
|
|
38
39
|
"import": "./build-module/index.js",
|
|
39
|
-
"require": "./build/index.
|
|
40
|
+
"require": "./build/index.cjs"
|
|
40
41
|
},
|
|
41
42
|
"./package.json": "./package.json"
|
|
42
43
|
},
|
|
@@ -45,11 +46,8 @@
|
|
|
45
46
|
},
|
|
46
47
|
"types": "build-types",
|
|
47
48
|
"dependencies": {
|
|
48
|
-
"@wordpress/
|
|
49
|
-
"@wordpress/
|
|
50
|
-
"@wordpress/data": "^10.36.1-next.8b30e05b0.0",
|
|
51
|
-
"@wordpress/i18n": "^6.9.1-next.8b30e05b0.0",
|
|
52
|
-
"@wordpress/url": "^4.36.1-next.8b30e05b0.0",
|
|
49
|
+
"@wordpress/data": "^10.36.1-next.8fd3f8831.0",
|
|
50
|
+
"@wordpress/i18n": "^6.9.1-next.8fd3f8831.0",
|
|
53
51
|
"ajv": "^8.17.1",
|
|
54
52
|
"ajv-draft-04": "^1.0.0",
|
|
55
53
|
"ajv-formats": "^3.0.1"
|
|
@@ -57,5 +55,5 @@
|
|
|
57
55
|
"publishConfig": {
|
|
58
56
|
"access": "public"
|
|
59
57
|
},
|
|
60
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "e582b351bc4c4b8734bb087f63a3beec9875c3c7"
|
|
61
59
|
}
|
package/src/api.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { dispatch,
|
|
5
|
-
import apiFetch from '@wordpress/api-fetch';
|
|
6
|
-
import { addQueryArgs } from '@wordpress/url';
|
|
4
|
+
import { dispatch, select } from '@wordpress/data';
|
|
7
5
|
import { sprintf } from '@wordpress/i18n';
|
|
8
6
|
|
|
9
7
|
/**
|
|
@@ -24,43 +22,41 @@ import { validateValueFromSchema } from './validation';
|
|
|
24
22
|
* Get all available abilities with optional filtering.
|
|
25
23
|
*
|
|
26
24
|
* @param args Optional query arguments to filter. Defaults to empty object.
|
|
27
|
-
* @return
|
|
25
|
+
* @return Array of abilities.
|
|
28
26
|
*/
|
|
29
|
-
export
|
|
30
|
-
|
|
31
|
-
): Promise< Ability[] > {
|
|
32
|
-
return await resolveSelect( store ).getAbilities( args );
|
|
27
|
+
export function getAbilities( args: AbilitiesQueryArgs = {} ): Ability[] {
|
|
28
|
+
return select( store ).getAbilities( args );
|
|
33
29
|
}
|
|
34
30
|
|
|
35
31
|
/**
|
|
36
32
|
* Get a specific ability by name.
|
|
37
33
|
*
|
|
38
34
|
* @param name The ability name.
|
|
39
|
-
* @return
|
|
35
|
+
* @return The ability or undefined if not found.
|
|
40
36
|
*/
|
|
41
|
-
export
|
|
42
|
-
return
|
|
37
|
+
export function getAbility( name: string ): Ability | undefined {
|
|
38
|
+
return select( store ).getAbility( name );
|
|
43
39
|
}
|
|
44
40
|
|
|
45
41
|
/**
|
|
46
42
|
* Get all available ability categories.
|
|
47
43
|
*
|
|
48
|
-
* @return
|
|
44
|
+
* @return Array of categories.
|
|
49
45
|
*/
|
|
50
|
-
export
|
|
51
|
-
return
|
|
46
|
+
export function getAbilityCategories(): AbilityCategory[] {
|
|
47
|
+
return select( store ).getAbilityCategories();
|
|
52
48
|
}
|
|
53
49
|
|
|
54
50
|
/**
|
|
55
51
|
* Get a specific ability category by slug.
|
|
56
52
|
*
|
|
57
53
|
* @param slug The category slug.
|
|
58
|
-
* @return
|
|
54
|
+
* @return The category or undefined if not found.
|
|
59
55
|
*/
|
|
60
|
-
export
|
|
56
|
+
export function getAbilityCategory(
|
|
61
57
|
slug: string
|
|
62
|
-
):
|
|
63
|
-
return
|
|
58
|
+
): AbilityCategory | undefined {
|
|
59
|
+
return select( store ).getAbilityCategory( slug );
|
|
64
60
|
}
|
|
65
61
|
|
|
66
62
|
/**
|
|
@@ -70,17 +66,14 @@ export async function getAbilityCategory(
|
|
|
70
66
|
* a callback function. The ability will be validated by the store action,
|
|
71
67
|
* and an error will be thrown if validation fails.
|
|
72
68
|
*
|
|
73
|
-
*
|
|
74
|
-
* haven't been loaded yet, so you don't need to call getAbilityCategories()
|
|
75
|
-
* before registering abilities.
|
|
69
|
+
* The category must already be registered before registering abilities.
|
|
76
70
|
*
|
|
77
71
|
* @param ability The ability definition including callback.
|
|
78
|
-
* @return Promise that resolves when registration is complete.
|
|
79
72
|
* @throws {Error} If the ability fails validation.
|
|
80
73
|
*
|
|
81
74
|
* @example
|
|
82
75
|
* ```js
|
|
83
|
-
*
|
|
76
|
+
* registerAbility({
|
|
84
77
|
* name: 'my-plugin/navigate',
|
|
85
78
|
* label: 'Navigate to URL',
|
|
86
79
|
* description: 'Navigates to a URL within WordPress admin',
|
|
@@ -99,8 +92,8 @@ export async function getAbilityCategory(
|
|
|
99
92
|
* });
|
|
100
93
|
* ```
|
|
101
94
|
*/
|
|
102
|
-
export
|
|
103
|
-
|
|
95
|
+
export function registerAbility( ability: Ability ): void {
|
|
96
|
+
dispatch( store ).registerAbility( ability );
|
|
104
97
|
}
|
|
105
98
|
|
|
106
99
|
/**
|
|
@@ -123,24 +116,20 @@ export function unregisterAbility( name: string ): void {
|
|
|
123
116
|
* This is useful when registering client-side abilities that introduce new
|
|
124
117
|
* categories not defined by the server.
|
|
125
118
|
*
|
|
126
|
-
* Categories will be automatically fetched from the REST API if they haven't been
|
|
127
|
-
* loaded yet to check for duplicates against server-side categories.
|
|
128
|
-
*
|
|
129
119
|
* @param slug Category slug (lowercase alphanumeric with dashes only).
|
|
130
120
|
* @param args Category arguments (label, description, optional meta).
|
|
131
|
-
* @return Promise that resolves when registration is complete.
|
|
132
121
|
* @throws {Error} If the category fails validation.
|
|
133
122
|
*
|
|
134
123
|
* @example
|
|
135
124
|
* ```js
|
|
136
125
|
* // Register a new category for block editor abilities
|
|
137
|
-
*
|
|
126
|
+
* registerAbilityCategory('block-editor', {
|
|
138
127
|
* label: 'Block Editor',
|
|
139
128
|
* description: 'Abilities for interacting with the WordPress block editor'
|
|
140
129
|
* });
|
|
141
130
|
*
|
|
142
131
|
* // Then register abilities using this category
|
|
143
|
-
*
|
|
132
|
+
* registerAbility({
|
|
144
133
|
* name: 'my-plugin/insert-block',
|
|
145
134
|
* label: 'Insert Block',
|
|
146
135
|
* description: 'Inserts a block into the editor',
|
|
@@ -152,11 +141,11 @@ export function unregisterAbility( name: string ): void {
|
|
|
152
141
|
* });
|
|
153
142
|
* ```
|
|
154
143
|
*/
|
|
155
|
-
export
|
|
144
|
+
export function registerAbilityCategory(
|
|
156
145
|
slug: string,
|
|
157
146
|
args: AbilityCategoryArgs
|
|
158
|
-
):
|
|
159
|
-
|
|
147
|
+
): void {
|
|
148
|
+
dispatch( store ).registerAbilityCategory( slug, args );
|
|
160
149
|
}
|
|
161
150
|
|
|
162
151
|
/**
|
|
@@ -176,21 +165,29 @@ export function unregisterAbilityCategory( slug: string ): void {
|
|
|
176
165
|
}
|
|
177
166
|
|
|
178
167
|
/**
|
|
179
|
-
* Execute
|
|
168
|
+
* Execute an ability.
|
|
180
169
|
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
170
|
+
* Executes abilities with validation for client-side abilities only.
|
|
171
|
+
* Server abilities bypass validation as it's handled on the server.
|
|
172
|
+
*
|
|
173
|
+
* @param name The ability name.
|
|
174
|
+
* @param input Optional input parameters for the ability.
|
|
183
175
|
* @return Promise resolving to the ability execution result.
|
|
184
|
-
* @throws Error if
|
|
176
|
+
* @throws Error if the ability is not found or execution fails.
|
|
185
177
|
*/
|
|
186
|
-
async function
|
|
187
|
-
|
|
188
|
-
input
|
|
178
|
+
export async function executeAbility(
|
|
179
|
+
name: string,
|
|
180
|
+
input?: AbilityInput
|
|
189
181
|
): Promise< AbilityOutput > {
|
|
182
|
+
const ability = getAbility( name );
|
|
183
|
+
if ( ! ability ) {
|
|
184
|
+
throw new Error( sprintf( 'Ability not found: %s', name ) );
|
|
185
|
+
}
|
|
186
|
+
|
|
190
187
|
if ( ! ability.callback ) {
|
|
191
188
|
throw new Error(
|
|
192
189
|
sprintf(
|
|
193
|
-
'
|
|
190
|
+
'Ability "%s" is missing callback. Please ensure the ability is properly registered.',
|
|
194
191
|
ability.name
|
|
195
192
|
)
|
|
196
193
|
);
|
|
@@ -208,6 +205,7 @@ async function executeClientAbility(
|
|
|
208
205
|
}
|
|
209
206
|
}
|
|
210
207
|
|
|
208
|
+
// Validate input
|
|
211
209
|
if ( ability.input_schema ) {
|
|
212
210
|
const inputValidation = validateValueFromSchema(
|
|
213
211
|
input,
|
|
@@ -227,18 +225,17 @@ async function executeClientAbility(
|
|
|
227
225
|
}
|
|
228
226
|
}
|
|
229
227
|
|
|
228
|
+
// Execute the ability
|
|
230
229
|
let result: AbilityOutput;
|
|
231
230
|
try {
|
|
232
231
|
result = await ability.callback( input );
|
|
233
232
|
} catch ( error ) {
|
|
234
233
|
// eslint-disable-next-line no-console
|
|
235
|
-
console.error(
|
|
236
|
-
`Error executing client ability ${ ability.name }:`,
|
|
237
|
-
error
|
|
238
|
-
);
|
|
234
|
+
console.error( `Error executing ability ${ ability.name }:`, error );
|
|
239
235
|
throw error;
|
|
240
236
|
}
|
|
241
237
|
|
|
238
|
+
// Validate output
|
|
242
239
|
if ( ability.output_schema ) {
|
|
243
240
|
const outputValidation = validateValueFromSchema(
|
|
244
241
|
result,
|
|
@@ -260,80 +257,3 @@ async function executeClientAbility(
|
|
|
260
257
|
|
|
261
258
|
return result;
|
|
262
259
|
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Execute a server-side ability.
|
|
266
|
-
*
|
|
267
|
-
* @param ability The ability to execute.
|
|
268
|
-
* @param input Input parameters for the ability.
|
|
269
|
-
* @return Promise resolving to the ability execution result.
|
|
270
|
-
* @throws Error if the API call fails.
|
|
271
|
-
*/
|
|
272
|
-
async function executeServerAbility(
|
|
273
|
-
ability: Ability,
|
|
274
|
-
input: AbilityInput
|
|
275
|
-
): Promise< AbilityOutput > {
|
|
276
|
-
let method = 'POST';
|
|
277
|
-
if ( !! ability.meta?.annotations?.readonly ) {
|
|
278
|
-
method = 'GET';
|
|
279
|
-
} else if (
|
|
280
|
-
!! ability.meta?.annotations?.destructive &&
|
|
281
|
-
!! ability.meta?.annotations?.idempotent
|
|
282
|
-
) {
|
|
283
|
-
method = 'DELETE';
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
let path = `/wp-abilities/v1/abilities/${ ability.name }/run`;
|
|
287
|
-
const options: {
|
|
288
|
-
method: string;
|
|
289
|
-
data?: { input: AbilityInput };
|
|
290
|
-
} = {
|
|
291
|
-
method,
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
if ( [ 'GET', 'DELETE' ].includes( method ) && input !== null ) {
|
|
295
|
-
// For GET and DELETE requests, pass the input directly.
|
|
296
|
-
path = addQueryArgs( path, { input } );
|
|
297
|
-
} else if ( method === 'POST' && input !== null ) {
|
|
298
|
-
options.data = { input };
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Note: Input and output validation happens on the server side for these abilities.
|
|
302
|
-
try {
|
|
303
|
-
return await apiFetch< AbilityOutput >( {
|
|
304
|
-
path,
|
|
305
|
-
...options,
|
|
306
|
-
} );
|
|
307
|
-
} catch ( error ) {
|
|
308
|
-
// eslint-disable-next-line no-console
|
|
309
|
-
console.error( `Error executing ability ${ ability.name }:`, error );
|
|
310
|
-
throw error;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Execute an ability.
|
|
316
|
-
*
|
|
317
|
-
* Determines whether to execute locally (client abilities) or remotely (server abilities)
|
|
318
|
-
* based on whether the ability has a callback function.
|
|
319
|
-
*
|
|
320
|
-
* @param name The ability name.
|
|
321
|
-
* @param input Optional input parameters for the ability.
|
|
322
|
-
* @return Promise resolving to the ability execution result.
|
|
323
|
-
* @throws Error if the ability is not found or execution fails.
|
|
324
|
-
*/
|
|
325
|
-
export async function executeAbility(
|
|
326
|
-
name: string,
|
|
327
|
-
input?: AbilityInput
|
|
328
|
-
): Promise< AbilityOutput > {
|
|
329
|
-
const ability = await getAbility( name );
|
|
330
|
-
if ( ! ability ) {
|
|
331
|
-
throw new Error( sprintf( 'Ability not found: %s', name ) );
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if ( ability.callback ) {
|
|
335
|
-
return executeClientAbility( ability, input );
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
return executeServerAbility( ability, input );
|
|
339
|
-
}
|
package/src/index.ts
CHANGED
package/src/store/actions.ts
CHANGED
|
@@ -2,54 +2,50 @@
|
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
4
|
import { sprintf } from '@wordpress/i18n';
|
|
5
|
-
import { resolveSelect } from '@wordpress/data';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Internal dependencies
|
|
9
8
|
*/
|
|
10
9
|
import type { Ability, AbilityCategory, AbilityCategoryArgs } from '../types';
|
|
11
10
|
import {
|
|
12
|
-
RECEIVE_ABILITIES,
|
|
13
11
|
REGISTER_ABILITY,
|
|
14
12
|
UNREGISTER_ABILITY,
|
|
15
|
-
RECEIVE_CATEGORIES,
|
|
16
13
|
REGISTER_ABILITY_CATEGORY,
|
|
17
14
|
UNREGISTER_ABILITY_CATEGORY,
|
|
18
|
-
|
|
15
|
+
ABILITY_NAME_PATTERN,
|
|
16
|
+
CATEGORY_SLUG_PATTERN,
|
|
19
17
|
} from './constants';
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
* Returns an action object used to receive abilities into the store.
|
|
23
|
-
*
|
|
24
|
-
* @param abilities Array of abilities to store.
|
|
25
|
-
* @return Action object.
|
|
26
|
-
*/
|
|
27
|
-
export function receiveAbilities( abilities: Ability[] ) {
|
|
28
|
-
return {
|
|
29
|
-
type: RECEIVE_ABILITIES,
|
|
30
|
-
abilities,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
19
|
+
type AbilityAnnotations = NonNullable< Ability[ 'meta' ] >[ 'annotations' ];
|
|
33
20
|
|
|
34
21
|
/**
|
|
35
|
-
*
|
|
22
|
+
* Filters annotations to only include allowed keys with non-null values.
|
|
36
23
|
*
|
|
37
|
-
* @param
|
|
38
|
-
* @
|
|
24
|
+
* @param sourceAnnotations The source annotations object to filter.
|
|
25
|
+
* @param allowedKeys Array of annotation keys to include.
|
|
26
|
+
* @return Filtered annotations object.
|
|
39
27
|
*/
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
};
|
|
28
|
+
function filterAnnotations< K extends keyof NonNullable< AbilityAnnotations > >(
|
|
29
|
+
sourceAnnotations: Record< string, boolean > | undefined,
|
|
30
|
+
allowedKeys: readonly K[]
|
|
31
|
+
): NonNullable< AbilityAnnotations > {
|
|
32
|
+
const annotations: NonNullable< AbilityAnnotations > = {};
|
|
33
|
+
|
|
34
|
+
if ( sourceAnnotations ) {
|
|
35
|
+
for ( const key of allowedKeys ) {
|
|
36
|
+
if ( sourceAnnotations[ key ] !== undefined ) {
|
|
37
|
+
annotations[ key ] = sourceAnnotations[ key ];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return annotations;
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
/**
|
|
48
45
|
* Registers an ability in the store.
|
|
49
46
|
*
|
|
50
47
|
* This action validates the ability before registration. If validation fails,
|
|
51
|
-
* an error will be thrown.
|
|
52
|
-
* REST API if they haven't been loaded yet.
|
|
48
|
+
* an error will be thrown.
|
|
53
49
|
*
|
|
54
50
|
* @param ability The ability to register.
|
|
55
51
|
* @return Action object or function.
|
|
@@ -57,13 +53,13 @@ export function receiveCategories( categories: AbilityCategory[] ) {
|
|
|
57
53
|
*/
|
|
58
54
|
export function registerAbility( ability: Ability ) {
|
|
59
55
|
// @ts-expect-error - registry types are not yet available
|
|
60
|
-
return
|
|
56
|
+
return ( { select, dispatch } ) => {
|
|
61
57
|
if ( ! ability.name ) {
|
|
62
58
|
throw new Error( 'Ability name is required' );
|
|
63
59
|
}
|
|
64
60
|
|
|
65
61
|
// Validate name format matches server implementation
|
|
66
|
-
if ( !
|
|
62
|
+
if ( ! ABILITY_NAME_PATTERN.test( ability.name ) ) {
|
|
67
63
|
throw new Error(
|
|
68
64
|
'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.'
|
|
69
65
|
);
|
|
@@ -88,19 +84,18 @@ export function registerAbility( ability: Ability ) {
|
|
|
88
84
|
}
|
|
89
85
|
|
|
90
86
|
// Validate category format
|
|
91
|
-
if ( !
|
|
87
|
+
if ( ! CATEGORY_SLUG_PATTERN.test( ability.category ) ) {
|
|
92
88
|
throw new Error(
|
|
93
89
|
sprintf(
|
|
94
|
-
'Ability "%1$s" has an invalid category. Category must be lowercase alphanumeric with dashes only Got: "%2$s"',
|
|
90
|
+
'Ability "%1$s" has an invalid category. Category must be lowercase alphanumeric with dashes only. Got: "%2$s"',
|
|
95
91
|
ability.name,
|
|
96
92
|
ability.category
|
|
97
93
|
)
|
|
98
94
|
);
|
|
99
95
|
}
|
|
100
96
|
|
|
101
|
-
//
|
|
102
|
-
const categories =
|
|
103
|
-
await resolveSelect( STORE_NAME ).getAbilityCategories();
|
|
97
|
+
// Check that the category exists
|
|
98
|
+
const categories = select.getAbilityCategories();
|
|
104
99
|
const existingCategory = categories.find(
|
|
105
100
|
( cat: AbilityCategory ) => cat.slug === ability.category
|
|
106
101
|
);
|
|
@@ -132,10 +127,27 @@ export function registerAbility( ability: Ability ) {
|
|
|
132
127
|
);
|
|
133
128
|
}
|
|
134
129
|
|
|
130
|
+
const annotations = filterAnnotations( ability.meta?.annotations, [
|
|
131
|
+
'readonly',
|
|
132
|
+
'destructive',
|
|
133
|
+
'idempotent',
|
|
134
|
+
'serverRegistered',
|
|
135
|
+
'clientRegistered',
|
|
136
|
+
] );
|
|
137
|
+
|
|
138
|
+
if ( ! annotations.serverRegistered ) {
|
|
139
|
+
annotations.clientRegistered = true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const meta = { annotations };
|
|
143
|
+
|
|
135
144
|
// All validation passed, dispatch the registration action
|
|
136
145
|
dispatch( {
|
|
137
146
|
type: REGISTER_ABILITY,
|
|
138
|
-
ability
|
|
147
|
+
ability: {
|
|
148
|
+
...ability,
|
|
149
|
+
meta,
|
|
150
|
+
},
|
|
139
151
|
} );
|
|
140
152
|
};
|
|
141
153
|
}
|
|
@@ -157,8 +169,7 @@ export function unregisterAbility( name: string ) {
|
|
|
157
169
|
* Registers a client-side ability category in the store.
|
|
158
170
|
*
|
|
159
171
|
* This action validates the category before registration. If validation fails,
|
|
160
|
-
* an error will be thrown.
|
|
161
|
-
* REST API if they haven't been loaded yet to check for duplicates.
|
|
172
|
+
* an error will be thrown.
|
|
162
173
|
*
|
|
163
174
|
* @param slug The unique category slug identifier.
|
|
164
175
|
* @param args Category arguments (label, description, optional meta).
|
|
@@ -170,20 +181,19 @@ export function registerAbilityCategory(
|
|
|
170
181
|
args: AbilityCategoryArgs
|
|
171
182
|
) {
|
|
172
183
|
// @ts-expect-error - registry types are not yet available
|
|
173
|
-
return
|
|
184
|
+
return ( { select, dispatch } ) => {
|
|
174
185
|
if ( ! slug ) {
|
|
175
186
|
throw new Error( 'Category slug is required' );
|
|
176
187
|
}
|
|
177
188
|
|
|
178
189
|
// Validate slug format matches server implementation
|
|
179
|
-
if ( !
|
|
190
|
+
if ( ! CATEGORY_SLUG_PATTERN.test( slug ) ) {
|
|
180
191
|
throw new Error(
|
|
181
192
|
'Category slug must contain only lowercase alphanumeric characters and dashes.'
|
|
182
193
|
);
|
|
183
194
|
}
|
|
184
195
|
|
|
185
|
-
//
|
|
186
|
-
await resolveSelect( STORE_NAME ).getAbilityCategories();
|
|
196
|
+
// Check for duplicates
|
|
187
197
|
const existingCategory = select.getAbilityCategory( slug );
|
|
188
198
|
if ( existingCategory ) {
|
|
189
199
|
throw new Error(
|
|
@@ -214,16 +224,21 @@ export function registerAbilityCategory(
|
|
|
214
224
|
);
|
|
215
225
|
}
|
|
216
226
|
|
|
227
|
+
const annotations = filterAnnotations( args.meta?.annotations, [
|
|
228
|
+
'serverRegistered',
|
|
229
|
+
'clientRegistered',
|
|
230
|
+
] );
|
|
231
|
+
|
|
232
|
+
if ( ! annotations.serverRegistered ) {
|
|
233
|
+
annotations.clientRegistered = true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const meta = { annotations };
|
|
217
237
|
const category: AbilityCategory = {
|
|
218
238
|
slug,
|
|
219
239
|
label: args.label,
|
|
220
240
|
description: args.description,
|
|
221
|
-
meta
|
|
222
|
-
...( args.meta || {} ),
|
|
223
|
-
// Internal implementation note: Client-registered categories will have `meta._clientRegistered` set to `true` to differentiate them from server-fetched categories.
|
|
224
|
-
// This is used internally by the resolver to determine whether to fetch categories from the server.
|
|
225
|
-
_clientRegistered: true,
|
|
226
|
-
},
|
|
241
|
+
meta,
|
|
227
242
|
};
|
|
228
243
|
|
|
229
244
|
dispatch( {
|