@wordpress/abilities 0.2.0 → 0.2.1-next.738bb1424.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 (84) hide show
  1. package/README.md +32 -40
  2. package/build/{api.js → api.cjs} +22 -69
  3. package/build/api.cjs.map +7 -0
  4. package/build/{index.js → index.cjs} +4 -4
  5. package/build/{index.js.map → index.cjs.map} +2 -2
  6. package/build/store/{actions.js → actions.cjs} +43 -33
  7. package/build/store/actions.cjs.map +7 -0
  8. package/build/store/{constants.js → constants.cjs} +7 -16
  9. package/build/store/constants.cjs.map +7 -0
  10. package/build/store/{index.js → index.cjs} +6 -32
  11. package/build/store/index.cjs.map +7 -0
  12. package/build/store/{reducer.js → reducer.cjs} +4 -24
  13. package/build/store/reducer.cjs.map +7 -0
  14. package/build/store/{selectors.js → selectors.cjs} +2 -2
  15. package/build/store/selectors.cjs.map +7 -0
  16. package/build/store/types.cjs +19 -0
  17. package/build/store/types.cjs.map +7 -0
  18. package/build/{types.js → types.cjs} +1 -1
  19. package/build/types.cjs.map +7 -0
  20. package/build/{validation.js → validation.cjs} +3 -3
  21. package/build-module/api.js +22 -59
  22. package/build-module/api.js.map +2 -2
  23. package/build-module/index.js +3 -3
  24. package/build-module/index.js.map +2 -2
  25. package/build-module/store/actions.js +44 -33
  26. package/build-module/store/actions.js.map +2 -2
  27. package/build-module/store/constants.js +4 -10
  28. package/build-module/store/constants.js.map +2 -2
  29. package/build-module/store/index.js +6 -37
  30. package/build-module/store/index.js.map +2 -2
  31. package/build-module/store/reducer.js +3 -25
  32. package/build-module/store/reducer.js.map +2 -2
  33. package/build-module/store/selectors.js +1 -1
  34. package/build-module/store/selectors.js.map +2 -2
  35. package/build-module/store/types.js +1 -0
  36. package/build-module/store/types.js.map +7 -0
  37. package/build-types/api.d.ts +16 -23
  38. package/build-types/api.d.ts.map +1 -1
  39. package/build-types/index.d.ts +1 -1
  40. package/build-types/index.d.ts.map +1 -1
  41. package/build-types/store/actions.d.ts +5 -27
  42. package/build-types/store/actions.d.ts.map +1 -1
  43. package/build-types/store/constants.d.ts +2 -5
  44. package/build-types/store/constants.d.ts.map +1 -1
  45. package/build-types/store/index.d.ts +1 -1
  46. package/build-types/store/index.d.ts.map +1 -1
  47. package/build-types/store/reducer.d.ts +21 -11
  48. package/build-types/store/reducer.d.ts.map +1 -1
  49. package/build-types/store/selectors.d.ts +2 -1
  50. package/build-types/store/selectors.d.ts.map +1 -1
  51. package/build-types/store/types.d.ts +18 -0
  52. package/build-types/store/types.d.ts.map +1 -0
  53. package/build-types/types.d.ts +14 -24
  54. package/build-types/types.d.ts.map +1 -1
  55. package/package.json +8 -10
  56. package/src/api.ts +43 -123
  57. package/src/index.ts +0 -1
  58. package/src/store/actions.ts +61 -46
  59. package/src/store/constants.ts +4 -5
  60. package/src/store/index.ts +2 -34
  61. package/src/store/reducer.ts +27 -33
  62. package/src/store/selectors.ts +3 -7
  63. package/src/store/tests/actions.test.ts +100 -244
  64. package/src/store/tests/reducer.test.ts +0 -451
  65. package/src/store/tests/selectors.test.ts +1 -1
  66. package/src/store/types.ts +19 -0
  67. package/src/tests/api.test.ts +112 -158
  68. package/src/types.ts +14 -26
  69. package/build/api.js.map +0 -7
  70. package/build/store/actions.js.map +0 -7
  71. package/build/store/constants.js.map +0 -7
  72. package/build/store/index.js.map +0 -7
  73. package/build/store/reducer.js.map +0 -7
  74. package/build/store/resolvers.js +0 -107
  75. package/build/store/resolvers.js.map +0 -7
  76. package/build/store/selectors.js.map +0 -7
  77. package/build/types.js.map +0 -7
  78. package/build-module/store/resolvers.js +0 -84
  79. package/build-module/store/resolvers.js.map +0 -7
  80. package/build-types/store/resolvers.d.ts +0 -47
  81. package/build-types/store/resolvers.d.ts.map +0 -1
  82. package/src/store/resolvers.ts +0 -151
  83. package/src/store/tests/resolvers.test.ts +0 -642
  84. /package/build/{validation.js.map → validation.cjs.map} +0 -0
@@ -5,8 +5,7 @@
5
5
  /**
6
6
  * WordPress dependencies
7
7
  */
8
- import { dispatch, select, resolveSelect } from '@wordpress/data';
9
- import apiFetch from '@wordpress/api-fetch';
8
+ import { dispatch, select } from '@wordpress/data';
10
9
 
11
10
  /**
12
11
  * Internal dependencies
@@ -23,15 +22,11 @@ import {
23
22
  import { store } from '../store';
24
23
  import type { Ability, AbilityCategory } from '../types';
25
24
 
26
- // Mock WordPress dependencies
27
25
  jest.mock( '@wordpress/data', () => ( {
28
- dispatch: jest.fn(),
29
26
  select: jest.fn(),
30
- resolveSelect: jest.fn(),
27
+ dispatch: jest.fn(),
31
28
  } ) );
32
29
 
33
- jest.mock( '@wordpress/api-fetch' );
34
-
35
30
  jest.mock( '../store', () => ( {
36
31
  store: 'abilities-api/store',
37
32
  } ) );
@@ -42,7 +37,7 @@ describe( 'API functions', () => {
42
37
  } );
43
38
 
44
39
  describe( 'getAbilities', () => {
45
- it( 'should resolve and return all abilities from the store', async () => {
40
+ it( 'should return all abilities from the store', () => {
46
41
  const mockAbilities: Ability[] = [
47
42
  {
48
43
  name: 'test/ability1',
@@ -62,21 +57,19 @@ describe( 'API functions', () => {
62
57
  },
63
58
  ];
64
59
 
65
- const mockGetAbilities = jest
66
- .fn()
67
- .mockResolvedValue( mockAbilities );
68
- ( resolveSelect as jest.Mock ).mockReturnValue( {
60
+ const mockGetAbilities = jest.fn().mockReturnValue( mockAbilities );
61
+ ( select as jest.Mock ).mockReturnValue( {
69
62
  getAbilities: mockGetAbilities,
70
63
  } );
71
64
 
72
- const result = await getAbilities();
65
+ const result = getAbilities();
73
66
 
74
- expect( resolveSelect ).toHaveBeenCalledWith( store );
67
+ expect( select ).toHaveBeenCalledWith( store );
75
68
  expect( mockGetAbilities ).toHaveBeenCalled();
76
69
  expect( result ).toEqual( mockAbilities );
77
70
  } );
78
71
 
79
- it( 'should pass category parameter to store when filtering', async () => {
72
+ it( 'should pass category parameter to store when filtering', () => {
80
73
  const mockAbilities: Ability[] = [
81
74
  {
82
75
  name: 'test/ability1',
@@ -96,16 +89,14 @@ describe( 'API functions', () => {
96
89
  },
97
90
  ];
98
91
 
99
- const mockGetAbilities = jest
100
- .fn()
101
- .mockResolvedValue( mockAbilities );
102
- ( resolveSelect as jest.Mock ).mockReturnValue( {
92
+ const mockGetAbilities = jest.fn().mockReturnValue( mockAbilities );
93
+ ( select as jest.Mock ).mockReturnValue( {
103
94
  getAbilities: mockGetAbilities,
104
95
  } );
105
96
 
106
- const result = await getAbilities( { category: 'data-retrieval' } );
97
+ const result = getAbilities( { category: 'data-retrieval' } );
107
98
 
108
- expect( resolveSelect ).toHaveBeenCalledWith( store );
99
+ expect( select ).toHaveBeenCalledWith( store );
109
100
  expect( mockGetAbilities ).toHaveBeenCalledWith( {
110
101
  category: 'data-retrieval',
111
102
  } );
@@ -114,7 +105,7 @@ describe( 'API functions', () => {
114
105
  } );
115
106
 
116
107
  describe( 'getAbility', () => {
117
- it( 'should return a specific ability by name', async () => {
108
+ it( 'should return a specific ability by name', () => {
118
109
  const mockAbility: Ability = {
119
110
  name: 'test/ability',
120
111
  label: 'Test Ability',
@@ -124,29 +115,17 @@ describe( 'API functions', () => {
124
115
  output_schema: { type: 'object' },
125
116
  };
126
117
 
127
- const mockGetAbility = jest.fn().mockResolvedValue( mockAbility );
128
- ( resolveSelect as jest.Mock ).mockReturnValue( {
118
+ const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
119
+ ( select as jest.Mock ).mockReturnValue( {
129
120
  getAbility: mockGetAbility,
130
121
  } );
131
122
 
132
- const result = await getAbility( 'test/ability' );
123
+ const result = getAbility( 'test/ability' );
133
124
 
134
- expect( resolveSelect ).toHaveBeenCalledWith( store );
125
+ expect( select ).toHaveBeenCalledWith( store );
135
126
  expect( mockGetAbility ).toHaveBeenCalledWith( 'test/ability' );
136
127
  expect( result ).toEqual( mockAbility );
137
128
  } );
138
-
139
- it( 'should return null if ability not found', async () => {
140
- const mockGetAbility = jest.fn().mockResolvedValue( null );
141
- ( resolveSelect as jest.Mock ).mockReturnValue( {
142
- getAbility: mockGetAbility,
143
- } );
144
-
145
- const result = await getAbility( 'non-existent' );
146
-
147
- expect( mockGetAbility ).toHaveBeenCalledWith( 'non-existent' );
148
- expect( result ).toBeNull();
149
- } );
150
129
  } );
151
130
 
152
131
  describe( 'registerAbility', () => {
@@ -195,7 +174,10 @@ describe( 'API functions', () => {
195
174
  } );
196
175
 
197
176
  describe( 'executeAbility', () => {
198
- it( 'should execute a server-side ability via API', async () => {
177
+ it( 'should execute a server-side ability via callback', async () => {
178
+ const mockServerCallback = jest
179
+ .fn()
180
+ .mockResolvedValue( { success: true, result: 'test' } );
199
181
  const mockAbility: Ability = {
200
182
  name: 'test/server-ability',
201
183
  label: 'Server Ability',
@@ -209,30 +191,23 @@ describe( 'API functions', () => {
209
191
  required: [ 'message' ],
210
192
  },
211
193
  output_schema: { type: 'object' },
194
+ callback: mockServerCallback,
195
+ meta: { annotations: { serverRegistered: true } },
212
196
  };
213
197
 
214
- const mockGetAbility = jest.fn().mockResolvedValue( mockAbility );
215
- ( resolveSelect as jest.Mock ).mockReturnValue( {
198
+ const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
199
+ ( select as jest.Mock ).mockReturnValue( {
216
200
  getAbility: mockGetAbility,
217
201
  } );
218
202
 
219
- const mockResponse = { success: true, result: 'test' };
220
- ( apiFetch as unknown as jest.Mock ).mockResolvedValue(
221
- mockResponse
222
- );
223
-
224
203
  const input = { message: 'Hello' };
225
204
  const result = await executeAbility( 'test/server-ability', input );
226
205
 
227
206
  expect( mockGetAbility ).toHaveBeenCalledWith(
228
207
  'test/server-ability'
229
208
  );
230
- expect( apiFetch ).toHaveBeenCalledWith( {
231
- path: '/wp-abilities/v1/abilities/test/server-ability/run',
232
- method: 'POST',
233
- data: { input },
234
- } );
235
- expect( result ).toEqual( mockResponse );
209
+ expect( mockServerCallback ).toHaveBeenCalledWith( input );
210
+ expect( result ).toEqual( { success: true, result: 'test' } );
236
211
  } );
237
212
 
238
213
  it( 'should execute a client-side ability locally', async () => {
@@ -247,10 +222,11 @@ describe( 'API functions', () => {
247
222
  input_schema: { type: 'object' },
248
223
  output_schema: { type: 'object' },
249
224
  callback: mockCallback,
225
+ meta: { annotations: { clientRegistered: true } },
250
226
  };
251
227
 
252
- const mockGetAbility = jest.fn().mockResolvedValue( mockAbility );
253
- ( resolveSelect as jest.Mock ).mockReturnValue( {
228
+ const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
229
+ ( select as jest.Mock ).mockReturnValue( {
254
230
  getAbility: mockGetAbility,
255
231
  } );
256
232
 
@@ -261,13 +237,12 @@ describe( 'API functions', () => {
261
237
  'test/client-ability'
262
238
  );
263
239
  expect( mockCallback ).toHaveBeenCalledWith( input );
264
- expect( apiFetch ).not.toHaveBeenCalled();
265
240
  expect( result ).toEqual( { success: true } );
266
241
  } );
267
242
 
268
243
  it( 'should throw error if ability not found', async () => {
269
- const mockGetAbility = jest.fn().mockResolvedValue( null );
270
- ( resolveSelect as jest.Mock ).mockReturnValue( {
244
+ const mockGetAbility = jest.fn().mockReturnValue( null );
245
+ ( select as jest.Mock ).mockReturnValue( {
271
246
  getAbility: mockGetAbility,
272
247
  } );
273
248
 
@@ -292,10 +267,11 @@ describe( 'API functions', () => {
292
267
  },
293
268
  output_schema: { type: 'object' },
294
269
  callback: mockCallback,
270
+ meta: { annotations: { clientRegistered: true } },
295
271
  };
296
272
 
297
- const mockGetAbility = jest.fn().mockResolvedValue( mockAbility );
298
- ( resolveSelect as jest.Mock ).mockReturnValue( {
273
+ const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
274
+ ( select as jest.Mock ).mockReturnValue( {
299
275
  getAbility: mockGetAbility,
300
276
  } );
301
277
 
@@ -304,7 +280,10 @@ describe( 'API functions', () => {
304
280
  ).rejects.toThrow( 'invalid input' );
305
281
  } );
306
282
 
307
- it( 'should execute a read-only ability via GET', async () => {
283
+ it( 'should execute a read-only server ability', async () => {
284
+ const mockServerCallback = jest
285
+ .fn()
286
+ .mockResolvedValue( { data: 'read-only data' } );
308
287
  const mockAbility: Ability = {
309
288
  name: 'test/read-only',
310
289
  label: 'Read-only Ability',
@@ -319,31 +298,27 @@ describe( 'API functions', () => {
319
298
  },
320
299
  output_schema: { type: 'object' },
321
300
  meta: {
322
- annotations: { readonly: true },
301
+ annotations: { serverRegistered: true, readonly: true },
323
302
  },
303
+ callback: mockServerCallback,
324
304
  };
325
305
 
326
- const mockGetAbility = jest.fn().mockResolvedValue( mockAbility );
327
- ( resolveSelect as jest.Mock ).mockReturnValue( {
306
+ const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
307
+ ( select as jest.Mock ).mockReturnValue( {
328
308
  getAbility: mockGetAbility,
329
309
  } );
330
310
 
331
- const mockResponse = { data: 'read-only data' };
332
- ( apiFetch as unknown as jest.Mock ).mockResolvedValue(
333
- mockResponse
334
- );
335
-
336
311
  const input = { id: '123', format: 'json' };
337
312
  const result = await executeAbility( 'test/read-only', input );
338
313
 
339
- expect( apiFetch ).toHaveBeenCalledWith( {
340
- path: '/wp-abilities/v1/abilities/test/read-only/run?input%5Bid%5D=123&input%5Bformat%5D=json',
341
- method: 'GET',
342
- } );
343
- expect( result ).toEqual( mockResponse );
314
+ expect( mockServerCallback ).toHaveBeenCalledWith( input );
315
+ expect( result ).toEqual( { data: 'read-only data' } );
344
316
  } );
345
317
 
346
318
  it( 'should execute a read-only ability with empty input', async () => {
319
+ const mockServerCallback = jest
320
+ .fn()
321
+ .mockResolvedValue( { data: 'read-only data' } );
347
322
  const mockAbility: Ability = {
348
323
  name: 'test/read-only',
349
324
  label: 'Read-only Ability',
@@ -352,30 +327,26 @@ describe( 'API functions', () => {
352
327
  input_schema: { type: 'object' },
353
328
  output_schema: { type: 'object' },
354
329
  meta: {
355
- annotations: { readonly: true },
330
+ annotations: { serverRegistered: true, readonly: true },
356
331
  },
332
+ callback: mockServerCallback,
357
333
  };
358
334
 
359
- const mockGetAbility = jest.fn().mockResolvedValue( mockAbility );
360
- ( resolveSelect as jest.Mock ).mockReturnValue( {
335
+ const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
336
+ ( select as jest.Mock ).mockReturnValue( {
361
337
  getAbility: mockGetAbility,
362
338
  } );
363
339
 
364
- const mockResponse = { data: 'read-only data' };
365
- ( apiFetch as unknown as jest.Mock ).mockResolvedValue(
366
- mockResponse
367
- );
368
-
369
340
  const result = await executeAbility( 'test/read-only', {} );
370
341
 
371
- expect( apiFetch ).toHaveBeenCalledWith( {
372
- path: '/wp-abilities/v1/abilities/test/read-only/run?',
373
- method: 'GET',
374
- } );
375
- expect( result ).toEqual( mockResponse );
342
+ expect( mockServerCallback ).toHaveBeenCalledWith( {} );
343
+ expect( result ).toEqual( { data: 'read-only data' } );
376
344
  } );
377
345
 
378
- it( 'should execute a destructive idempotent ability via DELETE', async () => {
346
+ it( 'should execute a destructive idempotent server ability', async () => {
347
+ const mockServerCallback = jest
348
+ .fn()
349
+ .mockResolvedValue( 'Item deleted successfully.' );
379
350
  const mockAbility: Ability = {
380
351
  name: 'test/destructive',
381
352
  label: 'Destructive Ability',
@@ -391,30 +362,24 @@ describe( 'API functions', () => {
391
362
  output_schema: { type: 'string' },
392
363
  meta: {
393
364
  annotations: {
365
+ serverRegistered: true,
394
366
  destructive: true,
395
367
  idempotent: true,
396
368
  },
397
369
  },
370
+ callback: mockServerCallback,
398
371
  };
399
372
 
400
- const mockGetAbility = jest.fn().mockResolvedValue( mockAbility );
401
- ( resolveSelect as jest.Mock ).mockReturnValue( {
373
+ const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
374
+ ( select as jest.Mock ).mockReturnValue( {
402
375
  getAbility: mockGetAbility,
403
376
  } );
404
377
 
405
- const mockResponse = 'Item deleted successfully.';
406
- ( apiFetch as unknown as jest.Mock ).mockResolvedValue(
407
- mockResponse
408
- );
409
-
410
378
  const input = { id: '123', format: 'json' };
411
379
  const result = await executeAbility( 'test/destructive', input );
412
380
 
413
- expect( apiFetch ).toHaveBeenCalledWith( {
414
- path: '/wp-abilities/v1/abilities/test/destructive/run?input%5Bid%5D=123&input%5Bformat%5D=json',
415
- method: 'DELETE',
416
- } );
417
- expect( result ).toEqual( mockResponse );
381
+ expect( mockServerCallback ).toHaveBeenCalledWith( input );
382
+ expect( result ).toEqual( 'Item deleted successfully.' );
418
383
  } );
419
384
 
420
385
  it( 'should handle errors in client ability execution', async () => {
@@ -432,10 +397,11 @@ describe( 'API functions', () => {
432
397
  input_schema: { type: 'object' },
433
398
  output_schema: { type: 'object' },
434
399
  callback: mockCallback,
400
+ meta: { annotations: { clientRegistered: true } },
435
401
  };
436
402
 
437
- const mockGetAbility = jest.fn().mockResolvedValue( mockAbility );
438
- ( resolveSelect as jest.Mock ).mockReturnValue( {
403
+ const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
404
+ ( select as jest.Mock ).mockReturnValue( {
439
405
  getAbility: mockGetAbility,
440
406
  } );
441
407
 
@@ -444,7 +410,7 @@ describe( 'API functions', () => {
444
410
  ).rejects.toThrow( 'Execution failed' );
445
411
 
446
412
  expect( consoleErrorSpy ).toHaveBeenCalledWith(
447
- 'Error executing client ability test/client-ability:',
413
+ 'Error executing ability test/client-ability:',
448
414
  executionError
449
415
  );
450
416
 
@@ -455,7 +421,10 @@ describe( 'API functions', () => {
455
421
  const consoleErrorSpy = jest
456
422
  .spyOn( console, 'error' )
457
423
  .mockImplementation();
458
- const apiError = new Error( 'API request failed' );
424
+ const serverError = new Error( 'Server execution failed' );
425
+ const mockServerCallback = jest
426
+ .fn()
427
+ .mockRejectedValue( serverError );
459
428
 
460
429
  const mockAbility: Ability = {
461
430
  name: 'test/server-ability',
@@ -464,28 +433,31 @@ describe( 'API functions', () => {
464
433
  category: 'test-category',
465
434
  input_schema: { type: 'object' },
466
435
  output_schema: { type: 'object' },
436
+ callback: mockServerCallback,
437
+ meta: { annotations: { serverRegistered: true } },
467
438
  };
468
439
 
469
- const mockGetAbility = jest.fn().mockResolvedValue( mockAbility );
470
- ( resolveSelect as jest.Mock ).mockReturnValue( {
440
+ const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
441
+ ( select as jest.Mock ).mockReturnValue( {
471
442
  getAbility: mockGetAbility,
472
443
  } );
473
444
 
474
- ( apiFetch as unknown as jest.Mock ).mockRejectedValue( apiError );
475
-
476
445
  await expect(
477
446
  executeAbility( 'test/server-ability', {} )
478
- ).rejects.toThrow( 'API request failed' );
447
+ ).rejects.toThrow( 'Server execution failed' );
479
448
 
480
449
  expect( consoleErrorSpy ).toHaveBeenCalledWith(
481
450
  'Error executing ability test/server-ability:',
482
- apiError
451
+ serverError
483
452
  );
484
453
 
485
454
  consoleErrorSpy.mockRestore();
486
455
  } );
487
456
 
488
457
  it( 'should execute ability without callback as server ability', async () => {
458
+ const mockServerCallback = jest
459
+ .fn()
460
+ .mockResolvedValue( { success: true } );
489
461
  const mockAbility: Ability = {
490
462
  name: 'test/ability',
491
463
  label: 'Test Ability',
@@ -493,29 +465,24 @@ describe( 'API functions', () => {
493
465
  category: 'test-category',
494
466
  input_schema: { type: 'object' },
495
467
  output_schema: { type: 'object' },
496
- // No callback - should execute as server ability
468
+ // Server ability - should execute without client validation
469
+ callback: mockServerCallback,
470
+ meta: { annotations: { serverRegistered: true } },
497
471
  };
498
472
 
499
- const mockGetAbility = jest.fn().mockResolvedValue( mockAbility );
500
- ( resolveSelect as jest.Mock ).mockReturnValue( {
473
+ const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
474
+ ( select as jest.Mock ).mockReturnValue( {
501
475
  getAbility: mockGetAbility,
502
476
  } );
503
477
 
504
- const mockResponse = { success: true };
505
- ( apiFetch as unknown as jest.Mock ).mockResolvedValue(
506
- mockResponse
507
- );
508
-
509
478
  const result = await executeAbility( 'test/ability', {
510
479
  data: 'test',
511
480
  } );
512
481
 
513
- expect( apiFetch ).toHaveBeenCalledWith( {
514
- path: '/wp-abilities/v1/abilities/test/ability/run',
515
- method: 'POST',
516
- data: { input: { data: 'test' } },
482
+ expect( mockServerCallback ).toHaveBeenCalledWith( {
483
+ data: 'test',
517
484
  } );
518
- expect( result ).toEqual( mockResponse );
485
+ expect( result ).toEqual( { success: true } );
519
486
  } );
520
487
 
521
488
  it( 'should validate output for client abilities', async () => {
@@ -536,10 +503,11 @@ describe( 'API functions', () => {
536
503
  required: [ 'result' ],
537
504
  },
538
505
  callback: mockCallback,
506
+ meta: { annotations: { clientRegistered: true } },
539
507
  };
540
508
 
541
- const mockGetAbility = jest.fn().mockResolvedValue( mockAbility );
542
- ( resolveSelect as jest.Mock ).mockReturnValue( {
509
+ const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
510
+ ( select as jest.Mock ).mockReturnValue( {
543
511
  getAbility: mockGetAbility,
544
512
  } );
545
513
 
@@ -550,7 +518,7 @@ describe( 'API functions', () => {
550
518
  } );
551
519
 
552
520
  describe( 'getAbilityCategories', () => {
553
- it( 'should resolve and return all categories from the store', async () => {
521
+ it( 'should return all categories from the store', () => {
554
522
  const mockCategories: AbilityCategory[] = [
555
523
  {
556
524
  slug: 'data-retrieval',
@@ -566,32 +534,32 @@ describe( 'API functions', () => {
566
534
 
567
535
  const mockGetAbilityCategories = jest
568
536
  .fn()
569
- .mockResolvedValue( mockCategories );
570
- ( resolveSelect as jest.Mock ).mockReturnValue( {
537
+ .mockReturnValue( mockCategories );
538
+ ( select as jest.Mock ).mockReturnValue( {
571
539
  getAbilityCategories: mockGetAbilityCategories,
572
540
  } );
573
541
 
574
- const result = await getAbilityCategories();
542
+ const result = getAbilityCategories();
575
543
 
576
- expect( resolveSelect ).toHaveBeenCalledWith( store );
544
+ expect( select ).toHaveBeenCalledWith( store );
577
545
  expect( mockGetAbilityCategories ).toHaveBeenCalled();
578
546
  expect( result ).toEqual( mockCategories );
579
547
  } );
580
548
 
581
- it( 'should return empty array when no categories exist', async () => {
582
- const mockGetAbilityCategories = jest.fn().mockResolvedValue( [] );
583
- ( resolveSelect as jest.Mock ).mockReturnValue( {
549
+ it( 'should return empty array when no categories exist', () => {
550
+ const mockGetAbilityCategories = jest.fn().mockReturnValue( [] );
551
+ ( select as jest.Mock ).mockReturnValue( {
584
552
  getAbilityCategories: mockGetAbilityCategories,
585
553
  } );
586
554
 
587
- const result = await getAbilityCategories();
555
+ const result = getAbilityCategories();
588
556
 
589
557
  expect( result ).toEqual( [] );
590
558
  } );
591
559
  } );
592
560
 
593
561
  describe( 'getAbilityCategory', () => {
594
- it( 'should return a specific category by slug', async () => {
562
+ it( 'should return a specific category by slug', () => {
595
563
  const mockCategory: AbilityCategory = {
596
564
  slug: 'data-retrieval',
597
565
  label: 'Data Retrieval',
@@ -600,56 +568,42 @@ describe( 'API functions', () => {
600
568
 
601
569
  const mockGetAbilityCategory = jest
602
570
  .fn()
603
- .mockResolvedValue( mockCategory );
604
- ( resolveSelect as jest.Mock ).mockReturnValue( {
571
+ .mockReturnValue( mockCategory );
572
+ ( select as jest.Mock ).mockReturnValue( {
605
573
  getAbilityCategory: mockGetAbilityCategory,
606
574
  } );
607
575
 
608
- const result = await getAbilityCategory( 'data-retrieval' );
576
+ const result = getAbilityCategory( 'data-retrieval' );
609
577
 
610
- expect( resolveSelect ).toHaveBeenCalledWith( store );
578
+ expect( select ).toHaveBeenCalledWith( store );
611
579
  expect( mockGetAbilityCategory ).toHaveBeenCalledWith(
612
580
  'data-retrieval'
613
581
  );
614
582
  expect( result ).toEqual( mockCategory );
615
583
  } );
616
584
 
617
- it( 'should return null if category not found', async () => {
618
- const mockGetAbilityCategory = jest.fn().mockResolvedValue( null );
619
- ( resolveSelect as jest.Mock ).mockReturnValue( {
620
- getAbilityCategory: mockGetAbilityCategory,
621
- } );
622
-
623
- const result = await getAbilityCategory( 'non-existent' );
624
-
625
- expect( mockGetAbilityCategory ).toHaveBeenCalledWith(
626
- 'non-existent'
627
- );
628
- expect( result ).toBeNull();
629
- } );
630
-
631
- it( 'should handle categories with meta', async () => {
585
+ it( 'should handle categories with meta', () => {
632
586
  const mockCategory: AbilityCategory = {
633
587
  slug: 'user-management',
634
588
  label: 'User Management',
635
589
  description: 'Abilities for managing users',
636
590
  meta: {
637
- priority: 'high',
591
+ annotations: { clientRegistered: true },
638
592
  },
639
593
  };
640
594
 
641
595
  const mockGetAbilityCategory = jest
642
596
  .fn()
643
- .mockResolvedValue( mockCategory );
644
- ( resolveSelect as jest.Mock ).mockReturnValue( {
597
+ .mockReturnValue( mockCategory );
598
+ ( select as jest.Mock ).mockReturnValue( {
645
599
  getAbilityCategory: mockGetAbilityCategory,
646
600
  } );
647
601
 
648
- const result = await getAbilityCategory( 'user-management' );
602
+ const result = getAbilityCategory( 'user-management' );
649
603
 
650
604
  expect( result ).toEqual( mockCategory );
651
605
  expect( result?.meta ).toBeDefined();
652
- expect( result?.meta?.priority ).toBe( 'high' );
606
+ expect( result?.meta?.annotations?.clientRegistered ).toBe( true );
653
607
  } );
654
608
  } );
655
609
  } );
package/src/types.ts CHANGED
@@ -63,14 +63,13 @@ export interface Ability {
63
63
  output_schema?: Record< string, any >;
64
64
 
65
65
  /**
66
- * Callback function for client-side abilities.
67
- * If present, the ability will be executed locally in the browser.
68
- * If not present, the ability will be executed via REST API on the server.
66
+ * Callback function for ability execution.
67
+ * This property is required for all abilities.
69
68
  */
70
69
  callback?: AbilityCallback;
71
70
 
72
71
  /**
73
- * Client Permission callback for abilities.
72
+ * Permission callback for abilities.
74
73
  * Called before executing the ability to check if it's allowed.
75
74
  * If it returns false, the ability execution will be denied.
76
75
  */
@@ -78,15 +77,15 @@ export interface Ability {
78
77
 
79
78
  /**
80
79
  * Metadata about the ability.
81
- * @see WP_Ability::get_meta()
82
80
  */
83
81
  meta?: {
84
82
  annotations?: {
85
- readonly?: boolean | null;
86
- destructive?: boolean | null;
87
- idempotent?: boolean | null;
83
+ clientRegistered?: boolean;
84
+ serverRegistered?: boolean;
85
+ readonly?: boolean;
86
+ destructive?: boolean;
87
+ idempotent?: boolean;
88
88
  };
89
- [ key: string ]: any;
90
89
  };
91
90
  }
92
91
 
@@ -128,9 +127,13 @@ export interface AbilityCategory {
128
127
 
129
128
  /**
130
129
  * Metadata about the category.
131
- * @see WP_Ability_Category::get_meta()
132
130
  */
133
- meta?: Record< string, any >;
131
+ meta?: {
132
+ annotations?: {
133
+ clientRegistered?: boolean;
134
+ serverRegistered?: boolean;
135
+ };
136
+ };
134
137
  }
135
138
 
136
139
  /**
@@ -156,21 +159,6 @@ export interface AbilityCategoryArgs {
156
159
  meta?: Record< string, any >;
157
160
  }
158
161
 
159
- /**
160
- * The state shape for the abilities store.
161
- */
162
- export interface AbilitiesState {
163
- /**
164
- * Map of ability names to ability objects.
165
- */
166
- abilitiesByName: Record< string, Ability >;
167
-
168
- /**
169
- * Map of category slugs to category objects.
170
- */
171
- categoriesBySlug: Record< string, AbilityCategory >;
172
- }
173
-
174
162
  /**
175
163
  * Input parameters for ability execution.
176
164
  * Can be any JSON-serializable value: primitive, array, object, or null.