@cap-kit/people 8.0.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 (42) hide show
  1. package/CapKitPeople.podspec +20 -0
  2. package/LICENSE +21 -0
  3. package/Package.swift +28 -0
  4. package/README.md +1177 -0
  5. package/android/build.gradle +101 -0
  6. package/android/src/main/AndroidManifest.xml +4 -0
  7. package/android/src/main/java/io/capkit/people/PeopleImpl.kt +1003 -0
  8. package/android/src/main/java/io/capkit/people/PeopleObserver.kt +80 -0
  9. package/android/src/main/java/io/capkit/people/PeoplePlugin.kt +766 -0
  10. package/android/src/main/java/io/capkit/people/config/PeopleConfig.kt +44 -0
  11. package/android/src/main/java/io/capkit/people/error/PeopleError.kt +90 -0
  12. package/android/src/main/java/io/capkit/people/error/PeopleErrorMessages.kt +39 -0
  13. package/android/src/main/java/io/capkit/people/logger/PeopleLogger.kt +85 -0
  14. package/android/src/main/java/io/capkit/people/models/ContactModels.kt +64 -0
  15. package/android/src/main/java/io/capkit/people/utils/PeopleUtils.kt +133 -0
  16. package/android/src/main/res/.gitkeep +0 -0
  17. package/dist/docs.json +1449 -0
  18. package/dist/esm/definitions.d.ts +775 -0
  19. package/dist/esm/definitions.js +31 -0
  20. package/dist/esm/definitions.js.map +1 -0
  21. package/dist/esm/index.d.ts +15 -0
  22. package/dist/esm/index.js +18 -0
  23. package/dist/esm/index.js.map +1 -0
  24. package/dist/esm/web.d.ts +120 -0
  25. package/dist/esm/web.js +252 -0
  26. package/dist/esm/web.js.map +1 -0
  27. package/dist/plugin.cjs +300 -0
  28. package/dist/plugin.cjs.map +1 -0
  29. package/dist/plugin.js +303 -0
  30. package/dist/plugin.js.map +1 -0
  31. package/ios/Sources/PeoplePlugin/PeopleImpl.swift +463 -0
  32. package/ios/Sources/PeoplePlugin/PeoplePlugin.swift +627 -0
  33. package/ios/Sources/PeoplePlugin/PrivacyInfo.xcprivacy +13 -0
  34. package/ios/Sources/PeoplePlugin/Utils/PeopleUtils.swift +120 -0
  35. package/ios/Sources/PeoplePlugin/Version.swift +16 -0
  36. package/ios/Sources/PeoplePlugin/config/PeopleConfig.swift +56 -0
  37. package/ios/Sources/PeoplePlugin/error/PeopleError.swift +89 -0
  38. package/ios/Sources/PeoplePlugin/error/PeopleErrorMessages.swift +25 -0
  39. package/ios/Sources/PeoplePlugin/logger/PeopleLogging.swift +69 -0
  40. package/ios/Sources/PeoplePlugin/models/ContactModels.swift +68 -0
  41. package/ios/Tests/PeoplePluginTests/PeoplePluginTests.swift +10 -0
  42. package/package.json +119 -0
package/README.md ADDED
@@ -0,0 +1,1177 @@
1
+ <p align="center">
2
+ <img
3
+ src="https://raw.githubusercontent.com/cap-kit/capacitor-plugins/main/assets/logo.png"
4
+ alt="CapKit Logo"
5
+ width="128"
6
+ />
7
+ </p>
8
+
9
+ <h3 align="center">People</h3>
10
+ <p align="center">
11
+ <strong>
12
+ <code>@cap-kit/people</code>
13
+ </strong>
14
+ </p>
15
+
16
+ <p align="center">
17
+ Enterprise-grade People Directory for Capacitor. Provides a unified, capability-based abstraction over native contact
18
+ systems, featuring native projection queries, zero-permission contact picking, systemic access with fine-grained
19
+ capabilities, live change observation, native semantic search, and first-class vCard import/export. Designed for
20
+ performance, privacy, and large datasets without memory bloat.
21
+ </p>
22
+
23
+ <p align="center">
24
+ <a href="https://www.npmjs.com/package/@cap-kit/people">
25
+ <img src="https://img.shields.io/npm/v/@cap-kit/people?color=blue&label=npm&logo=npm&style=flat-square" alt="npm version">
26
+ </a>
27
+ <a href="https://github.com/cap-kit/capacitor-plugins/actions">
28
+ <img src="https://img.shields.io/github/actions/workflow/status/cap-kit/capacitor-plugins/ci.yml?branch=main&label=CI&logo=github&style=flat-square" alt="CI Status" />
29
+ </a>
30
+ <a href="https://capacitorjs.com/">
31
+ <img src="https://img.shields.io/badge/Capacitor-Plugin-blue?logo=capacitor&style=flat-square" alt="Capacitor Plugin">
32
+ </a>
33
+ <a href="https://www.npmjs.com/package/@cap-kit/people">
34
+ <img src="https://img.shields.io/npm/dm/@cap-kit/people?style=flat-square" alt="Downloads" />
35
+ </a>
36
+ <a href="./LICENSE">
37
+ <img src="https://img.shields.io/npm/l/@cap-kit/people?style=flat-square&logo=open-source-initiative&logoColor=white&color=green" alt="License" />
38
+ </a>
39
+ <img src="https://img.shields.io/maintenance/yes/2026?style=flat-square" alt="Maintained" />
40
+ </p>
41
+ <br>
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pnpm add @cap-kit/people
47
+ # or
48
+ npm install @cap-kit/people
49
+ # or
50
+ yarn add @cap-kit/people
51
+ # then run:
52
+ npx cap sync
53
+ ```
54
+
55
+ ## Apple Privacy Manifest
56
+
57
+ Apple mandates that app developers specify approved reasons for API usage to enhance user privacy.
58
+
59
+ This plugin includes a skeleton `PrivacyInfo.xcprivacy` file located in `ios/Sources/PeoplePlugin/PrivacyInfo.xcprivacy`.
60
+
61
+ **You must populate this file if your plugin uses any [Required Reason APIs](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api).**
62
+
63
+ ### Example: User Defaults
64
+
65
+ If your plugin uses `UserDefaults`, you must declare it in the manifest:
66
+
67
+ ```xml
68
+ <dict>
69
+ <key>NSPrivacyAccessedAPIType</key>
70
+ <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
71
+ <key>NSPrivacyAccessedAPITypeReasons</key>
72
+ <array>
73
+ <string>CA92.1</string>
74
+ </array>
75
+ </dict>
76
+
77
+ ```
78
+
79
+ For detailed steps, please see the [Capacitor Docs](https://capacitorjs.com/docs/ios/privacy-manifest).
80
+
81
+ ## Configuration
82
+
83
+ <docgen-config>
84
+ <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
85
+
86
+ Configuration options for the People plugin.
87
+
88
+ | Prop | Type | Description | Default | Since |
89
+ | -------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- |
90
+ | **`verboseLogging`** | <code>boolean</code> | Enables verbose native logging. When enabled, additional debug information is printed to the native console (Logcat on Android, Xcode on iOS). This option affects native logging behavior only and has no impact on the JavaScript API. | <code>false</code> | 8.0.0 |
91
+
92
+ ### Examples
93
+
94
+ In `capacitor.config.json`:
95
+
96
+ ```json
97
+ {
98
+ "plugins": {
99
+ "People": {
100
+ "verboseLogging": true
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ In `capacitor.config.ts`:
107
+
108
+ ```ts
109
+ /// <reference types="@cap-kit/people" />
110
+
111
+ import { CapacitorConfig } from '@capacitor/cli';
112
+
113
+ const config: CapacitorConfig = {
114
+ plugins: {
115
+ People: {
116
+ verboseLogging: true,
117
+ },
118
+ },
119
+ };
120
+
121
+ export default config;
122
+ ```
123
+
124
+ </docgen-config>
125
+
126
+ ## Permissions
127
+
128
+ ### Android
129
+
130
+ This plugin requires the following permissions be added to your `AndroidManifest.xml`:
131
+
132
+ ```xml
133
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
134
+ <uses-permission android:name="android.permission.WRITE_CONTACTS" />
135
+
136
+ ```
137
+
138
+ Read about [Setting Permissions](https://capacitorjs.com/docs/android/configuration#setting-permissions) in the [Android Guide](https://capacitorjs.com/docs/android) for more information on setting Android permissions.
139
+
140
+ ### iOS
141
+
142
+ To use the plugin on iOS, you need to add the following keys to your `Info.plist` file:
143
+
144
+ #### Contacts
145
+
146
+ - `NSContactsUsageDescription`
147
+ - _Privacy - Contacts Usage Description_
148
+
149
+ Read about [Configuring `Info.plist`](https://capacitorjs.com/docs/ios/configuration#configuring-infoplist) in the [iOS Guide](https://capacitorjs.com/docs/ios) for more information on setting iOS permissions in Xcode.
150
+
151
+ ### Web
152
+
153
+ On the Web platform, only the zero-permission contact picker is supported via the Contact Picker API when available.
154
+ All systemic access operations (`getContacts`, `getContact`, `searchPeople`, CRUD operations, group management, and `peopleChange` listeners) are not implemented on Web and will reject as unimplemented.
155
+
156
+ ---
157
+
158
+ ### ✅ Correct Usage
159
+
160
+ All People plugin APIs are based on Promise and follow the standard Capacitor v8 reject paradigm. Use `try / catch` to handle native cancellations and errors.
161
+
162
+ ```ts
163
+ import { People, PeopleErrorCode } from '@cap-kit/people';
164
+
165
+ try {
166
+ const { contact } = await People.pickContact({
167
+ projection: ['name', 'phones', 'emails'],
168
+ });
169
+ console.log('Picked contact:', contact);
170
+ } catch (err: any) {
171
+ if (err.code === PeopleErrorCode.CANCELLED) {
172
+ // User canceled selection
173
+ console.log('Picker cancelled');
174
+ } else {
175
+ console.error('Error:', err.message);
176
+ }
177
+ }
178
+ ```
179
+
180
+ ---
181
+
182
+ ### ❌ Incorrect Usage
183
+
184
+ Do not use checks based on the `success` property in the result, as the methods reject the Promise on error.
185
+
186
+ ```ts
187
+ // ❌ DO NOT DO THIS
188
+ const result = await People.pickContact();
189
+ if (result.success) { ... }
190
+ ```
191
+
192
+ ---
193
+
194
+ ## Error Handling
195
+
196
+ All People plugin methods can reject the Promise if they fail. It is recommended to handle standardized error codes using `PeopleErrorCode`.
197
+
198
+ ### Error Codes
199
+
200
+ All error codes are standardized and exposed via `PeopleErrorCode`:
201
+
202
+ - `UNAVAILABLE` – Feature not available or OS limitation
203
+ - `CANCELLED` – User cancelled an interactive flow (e.g., contact picker)
204
+ - `PERMISSION_DENIED` – Permission denied or restricted
205
+ - `INIT_FAILED` – Internal initialization or processing failure
206
+ - `INVALID_INPUT` – Invalid, missing, or malformed input
207
+ - `UNKNOWN_TYPE` – Invalid or unsupported projection/type
208
+
209
+ These codes are consistent across **iOS**, **Android**, and **Web**.
210
+
211
+ ### Example
212
+
213
+ ```ts
214
+ import { People, PeopleErrorCode } from '@cap-kit/people';
215
+
216
+ try {
217
+ const { contact } = await People.pickContact();
218
+ console.log('Contact selected:', contact);
219
+ } catch (err: any) {
220
+ switch (err.code) {
221
+ case PeopleErrorCode.CANCELLED:
222
+ // The user canceled the selection
223
+ console.log('Picker cancelled by user');
224
+ break;
225
+
226
+ case PeopleErrorCode.UNAVAILABLE:
227
+ // The user canceled the selection or the picker is not available
228
+ console.log('Picker unavailable or cancelled by user');
229
+ break;
230
+
231
+ case PeopleErrorCode.PERMISSION_DENIED:
232
+ // User has denied access to contacts (for methods that require permissions)
233
+ console.error('Permission to access contacts was denied');
234
+ break;
235
+
236
+ case PeopleErrorCode.INIT_FAILED:
237
+ // Internal error while processing native data
238
+ console.error('Native initialization or processing failure');
239
+ break;
240
+
241
+ default:
242
+ // Generic or unexpected error
243
+ console.error('An unexpected error occurred:', err.message);
244
+ break;
245
+ }
246
+ }
247
+ ```
248
+
249
+ ---
250
+
251
+ ## API
252
+
253
+ <docgen-index>
254
+
255
+ * [`createContact(...)`](#createcontact)
256
+ * [`updateContact(...)`](#updatecontact)
257
+ * [`deleteContact(...)`](#deletecontact)
258
+ * [`mergeContacts(...)`](#mergecontacts)
259
+ * [`listGroups()`](#listgroups)
260
+ * [`createGroup(...)`](#creategroup)
261
+ * [`deleteGroup(...)`](#deletegroup)
262
+ * [`addPeopleToGroup(...)`](#addpeopletogroup)
263
+ * [`removePeopleFromGroup(...)`](#removepeoplefromgroup)
264
+ * [`checkPermissions()`](#checkpermissions)
265
+ * [`requestPermissions(...)`](#requestpermissions)
266
+ * [`pickContact(...)`](#pickcontact)
267
+ * [`getContacts(...)`](#getcontacts)
268
+ * [`getContact(...)`](#getcontact)
269
+ * [`getCapabilities()`](#getcapabilities)
270
+ * [`getPluginVersion()`](#getpluginversion)
271
+ * [`searchPeople(...)`](#searchpeople)
272
+ * [`addListener('peopleChange', ...)`](#addlistenerpeoplechange-)
273
+ * [`addListener(string, ...)`](#addlistenerstring-)
274
+ * [`removeAllListeners()`](#removealllisteners)
275
+ * [Interfaces](#interfaces)
276
+ * [Type Aliases](#type-aliases)
277
+
278
+ </docgen-index>
279
+
280
+ <docgen-api>
281
+ <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
282
+
283
+ Capacitor People plugin interface.
284
+ * This interface defines the contract between the JavaScript layer and the
285
+ native implementations (Android and iOS).
286
+
287
+ ### createContact(...)
288
+
289
+ ```typescript
290
+ createContact(options: CreateContactOptions) => Promise<CreateContactResult>
291
+ ```
292
+
293
+ [CRUD]
294
+ Creates a new contact in the device's address book.
295
+ * @throws {PeopleError} PERMISSION_DENIED if contacts permission is missing.
296
+
297
+ | Param | Type |
298
+ | ------------- | --------------------------------------------------------------------- |
299
+ | **`options`** | <code><a href="#createcontactoptions">CreateContactOptions</a></code> |
300
+
301
+ **Returns:** <code>Promise&lt;<a href="#createcontactresult">CreateContactResult</a>&gt;</code>
302
+
303
+ **Since:** 8.0.0
304
+
305
+ #### Example
306
+
307
+ ```typescript
308
+ import { People } from '@cap-kit/people';
309
+
310
+ try {
311
+ const { contact } = await People.createContact({
312
+ contact: {
313
+ name: { given: 'John', family: 'Appleseed' },
314
+ emails: [{ address: 'john.appleseed@example.com', label: 'work' }],
315
+ },
316
+ });
317
+ console.log('Created contact ID:', contact.id);
318
+ } catch (error) {
319
+ console.error('Failed to create contact:', error.code);
320
+ }
321
+ ```
322
+
323
+ --------------------
324
+
325
+
326
+ ### updateContact(...)
327
+
328
+ ```typescript
329
+ updateContact(options: UpdateContactOptions) => Promise<UpdateContactResult>
330
+ ```
331
+
332
+ [CRUD]
333
+ Updates an existing contact using a patch-based approach.
334
+ * @throws {PeopleError} PERMISSION_DENIED if permission is missing.
335
+
336
+ | Param | Type |
337
+ | ------------- | --------------------------------------------------------------------- |
338
+ | **`options`** | <code><a href="#updatecontactoptions">UpdateContactOptions</a></code> |
339
+
340
+ **Returns:** <code>Promise&lt;<a href="#updatecontactresult">UpdateContactResult</a>&gt;</code>
341
+
342
+ **Since:** 8.0.0
343
+
344
+ #### Example
345
+
346
+ ```typescript
347
+ import { People } from '@cap-kit/people';
348
+
349
+ try {
350
+ const { contact } = await People.updateContact({
351
+ contactId: 'some-contact-id',
352
+ contact: {
353
+ organization: { company: 'New Company Inc.' },
354
+ },
355
+ });
356
+ console.log('Updated contact company:', contact.organization?.company);
357
+ } catch (error) {
358
+ console.error('Update failed:', error.message);
359
+ }
360
+ ```
361
+
362
+ --------------------
363
+
364
+
365
+ ### deleteContact(...)
366
+
367
+ ```typescript
368
+ deleteContact(options: DeleteContactOptions) => Promise<void>
369
+ ```
370
+
371
+ [CRUD]
372
+ Deletes a contact from the device's address book.
373
+ Only contacts owned by the app can be deleted.
374
+ * @throws {PeopleError} UNAVAILABLE if deletion fails or contact is not app-owned.
375
+
376
+ | Param | Type |
377
+ | ------------- | --------------------------------------------------------------------- |
378
+ | **`options`** | <code><a href="#deletecontactoptions">DeleteContactOptions</a></code> |
379
+
380
+ **Since:** 8.0.0
381
+
382
+ #### Example
383
+
384
+ ```typescript
385
+ import { People } from '@cap-kit/people';
386
+
387
+ await People.deleteContact({ contactId: 'contact-id-to-delete' });
388
+ ```
389
+
390
+ --------------------
391
+
392
+
393
+ ### mergeContacts(...)
394
+
395
+ ```typescript
396
+ mergeContacts(options: MergeContactsOptions) => Promise<MergeContactsResult>
397
+ ```
398
+
399
+ [CRUD]
400
+ Merges a source contact into a destination contact.
401
+ The source contact is deleted after the merge.
402
+ * @throws {PeopleError} PERMISSION_DENIED if permission is missing.
403
+
404
+ | Param | Type |
405
+ | ------------- | --------------------------------------------------------------------- |
406
+ | **`options`** | <code><a href="#mergecontactsoptions">MergeContactsOptions</a></code> |
407
+
408
+ **Returns:** <code>Promise&lt;<a href="#mergecontactsresult">MergeContactsResult</a>&gt;</code>
409
+
410
+ **Since:** 8.0.0
411
+
412
+ #### Example
413
+
414
+ ```typescript
415
+ import { People } from '@cap-kit/people';
416
+
417
+ const { contact } = await People.mergeContacts({
418
+ sourceContactId: 'duplicate-contact-id',
419
+ destinationContactId: 'main-contact-id',
420
+ });
421
+ console.log('Final contact state:', contact);
422
+ ```
423
+
424
+ --------------------
425
+
426
+
427
+ ### listGroups()
428
+
429
+ ```typescript
430
+ listGroups() => Promise<ListGroupsResult>
431
+ ```
432
+
433
+ [GROUPS]
434
+ Lists all available contact groups.
435
+
436
+ **Returns:** <code>Promise&lt;<a href="#listgroupsresult">ListGroupsResult</a>&gt;</code>
437
+
438
+ **Since:** 8.0.0
439
+
440
+ #### Example
441
+
442
+ ```typescript
443
+ const { groups } = await People.listGroups();
444
+ ```
445
+
446
+ --------------------
447
+
448
+
449
+ ### createGroup(...)
450
+
451
+ ```typescript
452
+ createGroup(options: CreateGroupOptions) => Promise<CreateGroupResult>
453
+ ```
454
+
455
+ [GROUPS]
456
+ Creates a new contact group.
457
+ * @example
458
+ ```typescript
459
+ const { group } = await People.createGroup({ name: 'Family' });
460
+ ```
461
+
462
+ | Param | Type |
463
+ | ------------- | ----------------------------------------------------------------- |
464
+ | **`options`** | <code><a href="#creategroupoptions">CreateGroupOptions</a></code> |
465
+
466
+ **Returns:** <code>Promise&lt;<a href="#creategroupresult">CreateGroupResult</a>&gt;</code>
467
+
468
+ **Since:** 8.0.0
469
+
470
+ --------------------
471
+
472
+
473
+ ### deleteGroup(...)
474
+
475
+ ```typescript
476
+ deleteGroup(options: DeleteGroupOptions) => Promise<void>
477
+ ```
478
+
479
+ [GROUPS]
480
+ Deletes a contact group.
481
+
482
+ | Param | Type |
483
+ | ------------- | ----------------------------------------------------------------- |
484
+ | **`options`** | <code><a href="#deletegroupoptions">DeleteGroupOptions</a></code> |
485
+
486
+ **Since:** 8.0.0
487
+
488
+ #### Example
489
+
490
+ ```typescript
491
+ import { People } from '@cap-kit/people';
492
+
493
+ await People.deleteGroup({ groupId: 'group-id-to-delete' });
494
+ ```
495
+
496
+ --------------------
497
+
498
+
499
+ ### addPeopleToGroup(...)
500
+
501
+ ```typescript
502
+ addPeopleToGroup(options: AddPeopleToGroupOptions) => Promise<void>
503
+ ```
504
+
505
+ [GROUPS]
506
+ Adds contacts to a group.
507
+
508
+ | Param | Type |
509
+ | ------------- | --------------------------------------------------------------------------- |
510
+ | **`options`** | <code><a href="#addpeopletogroupoptions">AddPeopleToGroupOptions</a></code> |
511
+
512
+ **Since:** 8.0.0
513
+
514
+ #### Example
515
+
516
+ ```typescript
517
+ import { People } from '@cap-kit/people';
518
+
519
+ await People.addPeopleToGroup({
520
+ groupId: 'group-id',
521
+ contactIds: ['contact-id-1', 'contact-id-2'],
522
+ });
523
+ ```
524
+
525
+ --------------------
526
+
527
+
528
+ ### removePeopleFromGroup(...)
529
+
530
+ ```typescript
531
+ removePeopleFromGroup(options: RemovePeopleFromGroupOptions) => Promise<void>
532
+ ```
533
+
534
+ [GROUPS]
535
+ Removes contacts from a group.
536
+
537
+ | Param | Type |
538
+ | ------------- | ------------------------------------------------------------------------------------- |
539
+ | **`options`** | <code><a href="#removepeoplefromgroupoptions">RemovePeopleFromGroupOptions</a></code> |
540
+
541
+ **Since:** 8.0.0
542
+
543
+ #### Example
544
+
545
+ ```typescript
546
+ import { People } from '@cap-kit/people';
547
+
548
+ await People.removePeopleFromGroup({
549
+ groupId: 'group-id',
550
+ contactIds: ['contact-id-1'],
551
+ });
552
+ ```
553
+
554
+ --------------------
555
+
556
+
557
+ ### checkPermissions()
558
+
559
+ ```typescript
560
+ checkPermissions() => Promise<PeoplePluginPermissions>
561
+ ```
562
+
563
+ Check the status of permissions.
564
+
565
+ **Returns:** <code>Promise&lt;<a href="#peoplepluginpermissions">PeoplePluginPermissions</a>&gt;</code>
566
+
567
+ **Since:** 8.0.0
568
+
569
+ #### Example
570
+
571
+ ```typescript
572
+ import { People } from '@cap-kit/people';
573
+ const permissions = await People.checkPermissions();
574
+ console.log(permissions.contacts); // Output: 'granted' | 'denied' | 'prompt'
575
+ ```
576
+
577
+ --------------------
578
+
579
+
580
+ ### requestPermissions(...)
581
+
582
+ ```typescript
583
+ requestPermissions(permissions?: { permissions: 'contacts'[]; } | undefined) => Promise<PeoplePluginPermissions>
584
+ ```
585
+
586
+ Request permissions.
587
+
588
+ | Param | Type | Description |
589
+ | ----------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
590
+ | **`permissions`** | <code>{ permissions: 'contacts'[]; }</code> | - An optional object specifying which permissions to request. If not provided, all permissions defined in the plugin will be requested. |
591
+
592
+ **Returns:** <code>Promise&lt;<a href="#peoplepluginpermissions">PeoplePluginPermissions</a>&gt;</code>
593
+
594
+ **Since:** 8.0.0
595
+
596
+ #### Example
597
+
598
+ ```typescript
599
+ import { People } from '@cap-kit/people';
600
+ const permissions = await People.requestPermissions();
601
+ // OR
602
+ // const permissions = await People.requestPermissions({ permissions: ['contacts'] });
603
+ console.log(permissions.contacts); // Output: 'granted' | 'denied'
604
+ ```
605
+
606
+ --------------------
607
+
608
+
609
+ ### pickContact(...)
610
+
611
+ ```typescript
612
+ pickContact(options?: { projection?: PeopleProjection[] | undefined; } | undefined) => Promise<PickContactResult>
613
+ ```
614
+
615
+ [ZERO-PERMISSION]
616
+ Launches the native OS contact picker UI.
617
+ This method does NOT require any entries in AndroidManifest.xml or Info.plist
618
+ as the user explicitly selects the data via the system UI.
619
+
620
+ | Param | Type |
621
+ | ------------- | ------------------------------------------------- |
622
+ | **`options`** | <code>{ projection?: PeopleProjection[]; }</code> |
623
+
624
+ **Returns:** <code>Promise&lt;<a href="#pickcontactresult">PickContactResult</a>&gt;</code>
625
+
626
+ **Since:** 8.0.0
627
+
628
+ #### Example
629
+
630
+ ```typescript
631
+ try {
632
+ const { contact } = await People.pickContact({
633
+ projection: ['name', 'phones', 'emails']
634
+ });
635
+ console.log('User selected:', contact);
636
+ } catch (error) {
637
+ if (error.code === 'CANCELLED') {
638
+ console.log('User cancelled the picker.');
639
+ }
640
+ }
641
+ ```
642
+
643
+ --------------------
644
+
645
+
646
+ ### getContacts(...)
647
+
648
+ ```typescript
649
+ getContacts(options?: GetContactsOptions | undefined) => Promise<GetContactsResult>
650
+ ```
651
+
652
+ [SYSTEMIC-ACCESS]
653
+ Queries the entire contact database with specific projection and pagination.
654
+ REQUIRES 'contacts' permission.
655
+ Use `includeTotal` only when needed: computing `totalCount` may require scanning/counting across the full contacts set and can be expensive on large address books. Default is `false`.
656
+
657
+ | Param | Type |
658
+ | ------------- | ----------------------------------------------------------------- |
659
+ | **`options`** | <code><a href="#getcontactsoptions">GetContactsOptions</a></code> |
660
+
661
+ **Returns:** <code>Promise&lt;<a href="#getcontactsresult">GetContactsResult</a>&gt;</code>
662
+
663
+ **Since:** 8.0.0
664
+
665
+ #### Example
666
+
667
+ ```typescript
668
+ const result = await People.getContacts({
669
+ projection: ['name', 'phones'],
670
+ limit: 20,
671
+ offset: 0
672
+ });
673
+ ```
674
+
675
+ --------------------
676
+
677
+
678
+ ### getContact(...)
679
+
680
+ ```typescript
681
+ getContact(options: { id: string; projection?: PeopleProjection[]; }) => Promise<{ contact: UnifiedContact; }>
682
+ ```
683
+
684
+ Retrieves a single contact by ID.
685
+ * @throws {PeopleError} UNAVAILABLE if contact is not found.
686
+
687
+ | Param | Type |
688
+ | ------------- | ------------------------------------------------------------- |
689
+ | **`options`** | <code>{ id: string; projection?: PeopleProjection[]; }</code> |
690
+
691
+ **Returns:** <code>Promise&lt;{ contact: <a href="#unifiedcontact">UnifiedContact</a>; }&gt;</code>
692
+
693
+ **Since:** 8.0.0
694
+
695
+ #### Example
696
+
697
+ ```typescript
698
+ import { People } from '@cap-kit/people';
699
+
700
+ const { contact } = await People.getContact({ id: 'contact-id', projection: ['name', 'emails'] });
701
+ console.log('Contact details:', contact);
702
+ ```
703
+
704
+ --------------------
705
+
706
+
707
+ ### getCapabilities()
708
+
709
+ ```typescript
710
+ getCapabilities() => Promise<PeopleCapabilities>
711
+ ```
712
+
713
+ Returns what this device/implementation is capable of.
714
+ Useful for UI adaptation (e.g. hiding "Edit" buttons).
715
+
716
+ **Returns:** <code>Promise&lt;<a href="#peoplecapabilities">PeopleCapabilities</a>&gt;</code>
717
+
718
+ **Since:** 8.0.0
719
+
720
+ #### Example
721
+
722
+ ```typescript
723
+ import { People } from '@cap-kit/people';
724
+
725
+ const capabilities = await People.getCapabilities();
726
+ console.log('Can Read Contacts:', capabilities.canRead);
727
+ console.log('Can Write Contacts:', capabilities.canWrite);
728
+ ```
729
+
730
+ --------------------
731
+
732
+
733
+ ### getPluginVersion()
734
+
735
+ ```typescript
736
+ getPluginVersion() => Promise<PluginVersionResult>
737
+ ```
738
+
739
+ Returns the native plugin version.
740
+
741
+ The returned version corresponds to the native implementation
742
+ bundled with the application.
743
+
744
+ **Returns:** <code>Promise&lt;<a href="#pluginversionresult">PluginVersionResult</a>&gt;</code>
745
+
746
+ **Since:** 8.0.0
747
+
748
+ #### Example
749
+
750
+ ```ts
751
+ const { version } = await People.getPluginVersion();
752
+ ```
753
+
754
+ --------------------
755
+
756
+
757
+ ### searchPeople(...)
758
+
759
+ ```typescript
760
+ searchPeople(options: { query: string; projection?: PeopleProjection[]; limit?: number; }) => Promise<GetContactsResult>
761
+ ```
762
+
763
+ [SYSTEMIC-ACCESS]
764
+ Searches the database with projection.
765
+ REQUIRES 'contacts' permission.
766
+
767
+ | Param | Type |
768
+ | ------------- | -------------------------------------------------------------------------------- |
769
+ | **`options`** | <code>{ query: string; projection?: PeopleProjection[]; limit?: number; }</code> |
770
+
771
+ **Returns:** <code>Promise&lt;<a href="#getcontactsresult">GetContactsResult</a>&gt;</code>
772
+
773
+ **Since:** 8.0.0
774
+
775
+ #### Example
776
+
777
+ ```typescript
778
+ import { People } from '@cap-kit/people';
779
+
780
+ const result = await People.searchPeople({ query: 'John', projection: ['name', 'phones'], limit: 10 });
781
+ console.log('Fetched contacts:', result.contacts);
782
+ ```
783
+
784
+ --------------------
785
+
786
+
787
+ ### addListener('peopleChange', ...)
788
+
789
+ ```typescript
790
+ addListener(eventName: 'peopleChange', listenerFunc: (payload: PeopleChangeEvent) => void) => Promise<PluginListenerHandle>
791
+ ```
792
+
793
+ Listen for changes in the system address book.
794
+ REQUIRES 'contacts' permission.
795
+ * @returns A promise that resolves to a handle to remove the listener.
796
+
797
+ | Param | Type |
798
+ | ------------------ | ------------------------------------------------------------------------------------- |
799
+ | **`eventName`** | <code>'peopleChange'</code> |
800
+ | **`listenerFunc`** | <code>(payload: <a href="#peoplechangeevent">PeopleChangeEvent</a>) =&gt; void</code> |
801
+
802
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
803
+
804
+ **Since:** 8.0.0
805
+
806
+ #### Example
807
+
808
+ ```typescript
809
+ import { People } from '@cap-kit/people';
810
+
811
+ const handle = await People.addListener('peopleChange', (event) => {
812
+ console.log('People change detected:', event.type);
813
+ });
814
+
815
+ // To remove the listener later:
816
+ // await handle.remove();
817
+ ```
818
+
819
+ --------------------
820
+
821
+
822
+ ### addListener(string, ...)
823
+
824
+ ```typescript
825
+ addListener(eventName: string, listenerFunc: (...args: unknown[]) => void) => Promise<PluginListenerHandle>
826
+ ```
827
+
828
+ Registers a listener for plugin events using a generic event name.
829
+
830
+ Prefer the typed `peopleChange` overload for full payload type safety.
831
+
832
+ | Param | Type |
833
+ | ------------------ | -------------------------------------------- |
834
+ | **`eventName`** | <code>string</code> |
835
+ | **`listenerFunc`** | <code>(...args: unknown[]) =&gt; void</code> |
836
+
837
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
838
+
839
+ **Since:** 8.0.0
840
+
841
+ --------------------
842
+
843
+
844
+ ### removeAllListeners()
845
+
846
+ ```typescript
847
+ removeAllListeners() => Promise<void>
848
+ ```
849
+
850
+ Removes all registered listeners for this plugin.
851
+
852
+ **Since:** 8.0.0
853
+
854
+ #### Example
855
+
856
+ ```typescript
857
+ import { People } from '@cap-kit/people';
858
+
859
+ await People.removeAllListeners();
860
+ ```
861
+
862
+ --------------------
863
+
864
+
865
+ ### Interfaces
866
+
867
+
868
+ #### CreateContactResult
869
+
870
+ Result returned by `createContact`.
871
+
872
+ | Prop | Type | Description |
873
+ | ------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |
874
+ | **`contact`** | <code><a href="#unifiedcontact">UnifiedContact</a></code> | The full contact object as it was saved, including its new unique ID. |
875
+
876
+
877
+ #### UnifiedContact
878
+
879
+ Represents a Unified Person in the directory.
880
+ Maps to CNContact (iOS) and Aggregated Contact (Android).
881
+
882
+ | Prop | Type | Description |
883
+ | ------------------ | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
884
+ | **`id`** | <code>string</code> | The platform-specific unique identifier (UUID or Long) |
885
+ | **`name`** | <code>{ display: string; given?: string; middle?: string; family?: string; prefix?: string; suffix?: string; }</code> | The unified display name |
886
+ | **`organization`** | <code>{ company?: string; title?: string; department?: string; }</code> | |
887
+ | **`birthday`** | <code>{ year?: number; month: number; day: number; }</code> | |
888
+ | **`phones`** | <code>PhoneNumber[]</code> | |
889
+ | **`emails`** | <code>EmailAddress[]</code> | |
890
+ | **`addresses`** | <code>PostalAddress[]</code> | |
891
+ | **`urls`** | <code>string[]</code> | |
892
+ | **`note`** | <code>string</code> | |
893
+ | **`image`** | <code>string</code> | Base64 thumbnail string (iOS only, only if projected). |
894
+
895
+
896
+ #### PhoneNumber
897
+
898
+ Phone number representation.
899
+
900
+ | Prop | Type | Description |
901
+ | ---------------- | ------------------- | ------------------------------------------------- |
902
+ | **`label`** | <code>string</code> | Normalized label (e.g., 'mobile', 'home', 'work') |
903
+ | **`number`** | <code>string</code> | The raw input string |
904
+ | **`normalized`** | <code>string</code> | E.164 formatted number (if parsing succeeded) |
905
+
906
+
907
+ #### EmailAddress
908
+
909
+ Email address representation.
910
+
911
+ | Prop | Type |
912
+ | ------------- | ------------------- |
913
+ | **`label`** | <code>string</code> |
914
+ | **`address`** | <code>string</code> |
915
+
916
+
917
+ #### PostalAddress
918
+
919
+ Postal address representation.
920
+
921
+ | Prop | Type |
922
+ | --------------- | ------------------- |
923
+ | **`label`** | <code>string</code> |
924
+ | **`formatted`** | <code>string</code> |
925
+ | **`street`** | <code>string</code> |
926
+ | **`city`** | <code>string</code> |
927
+ | **`region`** | <code>string</code> |
928
+ | **`postcode`** | <code>string</code> |
929
+ | **`country`** | <code>string</code> |
930
+
931
+
932
+ #### CreateContactOptions
933
+
934
+ Options for creating a new contact.
935
+ The contact data is provided as a partial <a href="#unifiedcontact">UnifiedContact</a>.
936
+
937
+ | Prop | Type | Description |
938
+ | ------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
939
+ | **`contact`** | <code>Partial&lt;Omit&lt;<a href="#unifiedcontact">UnifiedContact</a>, 'id'&gt;&gt;</code> | The contact data to be saved. At least one writable field (e.g., name, email) must be provided. |
940
+
941
+
942
+ #### UpdateContactResult
943
+
944
+ Result returned by `updateContact`.
945
+
946
+ | Prop | Type | Description |
947
+ | ------------- | --------------------------------------------------------- | ---------------------------------------------------------- |
948
+ | **`contact`** | <code><a href="#unifiedcontact">UnifiedContact</a></code> | The full contact object after the update has been applied. |
949
+
950
+
951
+ #### UpdateContactOptions
952
+
953
+ Options for updating an existing contact.
954
+ This operation performs a patch, not a full replacement.
955
+
956
+ | Prop | Type | Description |
957
+ | --------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
958
+ | **`contactId`** | <code>string</code> | The unique identifier of the contact to update. |
959
+ | **`contact`** | <code>Partial&lt;Omit&lt;<a href="#unifiedcontact">UnifiedContact</a>, 'id'&gt;&gt;</code> | An object containing the fields to be updated. Only the provided fields will be modified. |
960
+
961
+
962
+ #### DeleteContactOptions
963
+
964
+ Options for deleting a contact.
965
+
966
+ | Prop | Type | Description |
967
+ | --------------- | ------------------- | ----------------------------------------------- |
968
+ | **`contactId`** | <code>string</code> | The unique identifier of the contact to delete. |
969
+
970
+
971
+ #### MergeContactsResult
972
+
973
+ Result returned by `mergeContacts`.
974
+
975
+ | Prop | Type | Description |
976
+ | ------------- | --------------------------------------------------------- | ----------------------------------------------------- |
977
+ | **`contact`** | <code><a href="#unifiedcontact">UnifiedContact</a></code> | The state of the destination contact after the merge. |
978
+
979
+
980
+ #### MergeContactsOptions
981
+
982
+ Options for merging two contacts.
983
+
984
+ | Prop | Type | Description |
985
+ | -------------------------- | ------------------- | ---------------------------------------------------------------- |
986
+ | **`sourceContactId`** | <code>string</code> | The identifier of the contact that will be subsumed and deleted. |
987
+ | **`destinationContactId`** | <code>string</code> | The identifier of the contact that will be kept and updated. |
988
+
989
+
990
+ #### ListGroupsResult
991
+
992
+ Result returned by `listGroups`.
993
+
994
+ | Prop | Type | Description |
995
+ | ------------ | -------------------- | --------------------------------------------- |
996
+ | **`groups`** | <code>Group[]</code> | An array of groups found in the address book. |
997
+
998
+
999
+ #### Group
1000
+
1001
+ Represents a group in the address book.
1002
+ A group can be system-generated (e.g., "All Contacts") or user-created.
1003
+
1004
+ | Prop | Type | Description |
1005
+ | -------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1006
+ | **`id`** | <code>string</code> | A unique identifier for the group. This ID is stable and can be used for subsequent operations. |
1007
+ | **`name`** | <code>string</code> | The display name of the group. e.g., "Family", "Work", "Book Club" |
1008
+ | **`source`** | <code>string</code> | The source or account where the group originates. On iOS, this could be "iCloud" or "Local". On Android, this corresponds to the account name (e.g., "user@gmail.com"). For logical groups, this will be 'local'. |
1009
+ | **`readOnly`** | <code>boolean</code> | Indicates if the group is read-only. System groups are typically read-only and cannot be deleted or renamed. |
1010
+
1011
+
1012
+ #### CreateGroupResult
1013
+
1014
+ Result returned by `createGroup`.
1015
+
1016
+ | Prop | Type | Description |
1017
+ | ----------- | --------------------------------------- | ------------------------ |
1018
+ | **`group`** | <code><a href="#group">Group</a></code> | The newly created group. |
1019
+
1020
+
1021
+ #### CreateGroupOptions
1022
+
1023
+ Options for creating a new group.
1024
+
1025
+ | Prop | Type | Description |
1026
+ | ---------- | ------------------- | --------------------------- |
1027
+ | **`name`** | <code>string</code> | The name for the new group. |
1028
+
1029
+
1030
+ #### DeleteGroupOptions
1031
+
1032
+ Options for deleting a group.
1033
+
1034
+ | Prop | Type | Description |
1035
+ | ------------- | ------------------- | --------------------------------------------- |
1036
+ | **`groupId`** | <code>string</code> | The unique identifier of the group to delete. |
1037
+
1038
+
1039
+ #### AddPeopleToGroupOptions
1040
+
1041
+ Options for adding people to a group.
1042
+
1043
+ | Prop | Type | Description |
1044
+ | ---------------- | --------------------- | ---------------------------------------------------- |
1045
+ | **`groupId`** | <code>string</code> | The unique identifier of the group. |
1046
+ | **`contactIds`** | <code>string[]</code> | An array of contact identifiers to add to the group. |
1047
+
1048
+
1049
+ #### RemovePeopleFromGroupOptions
1050
+
1051
+ Options for removing people from a group.
1052
+
1053
+ | Prop | Type | Description |
1054
+ | ---------------- | --------------------- | --------------------------------------------------------- |
1055
+ | **`groupId`** | <code>string</code> | The unique identifier of the group. |
1056
+ | **`contactIds`** | <code>string[]</code> | An array of contact identifiers to remove from the group. |
1057
+
1058
+
1059
+ #### PeoplePluginPermissions
1060
+
1061
+ Permissions status interface.
1062
+
1063
+ | Prop | Type |
1064
+ | -------------- | ----------------------------------------------------------- |
1065
+ | **`contacts`** | <code><a href="#permissionstate">PermissionState</a></code> |
1066
+
1067
+
1068
+ #### PickContactResult
1069
+
1070
+ Result returned by pickContact().
1071
+ Now strictly returns the contact object on success.
1072
+
1073
+ | Prop | Type |
1074
+ | ------------- | --------------------------------------------------------- |
1075
+ | **`contact`** | <code><a href="#unifiedcontact">UnifiedContact</a></code> |
1076
+
1077
+
1078
+ #### GetContactsResult
1079
+
1080
+ Result returned by getContacts().
1081
+
1082
+ | Prop | Type | Description |
1083
+ | ---------------- | ----------------------------- | ---------------------------------------------- |
1084
+ | **`contacts`** | <code>UnifiedContact[]</code> | |
1085
+ | **`totalCount`** | <code>number</code> | Total count in the DB (permissions permitting) |
1086
+
1087
+
1088
+ #### GetContactsOptions
1089
+
1090
+ Options for querying contacts.
1091
+
1092
+ | Prop | Type | Description | Default |
1093
+ | ------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------- |
1094
+ | **`projection`** | <code>PeopleProjection[]</code> | Array of fields to fetch. MISSING fields will not be read from DB (Performance). | <code>['name']</code> |
1095
+ | **`limit`** | <code>number</code> | Max number of records to return | |
1096
+ | **`offset`** | <code>number</code> | Skip count (implement pagination via cursor usually better, but offset for now) | |
1097
+ | **`includeTotal`** | <code>boolean</code> | Whether to compute totalCount across the full contacts set. This may require scanning/counting the full address book and can be expensive on large datasets. | <code>false</code> |
1098
+
1099
+
1100
+ #### PeopleCapabilities
1101
+
1102
+ Capabilities of the People plugin on this device/implementation.
1103
+
1104
+ | Prop | Type |
1105
+ | --------------------- | -------------------- |
1106
+ | **`canRead`** | <code>boolean</code> |
1107
+ | **`canWrite`** | <code>boolean</code> |
1108
+ | **`canObserve`** | <code>boolean</code> |
1109
+ | **`canManageGroups`** | <code>boolean</code> |
1110
+ | **`canPickContact`** | <code>boolean</code> |
1111
+
1112
+
1113
+ #### PluginVersionResult
1114
+
1115
+ Result object returned by the `getPluginVersion()` method.
1116
+
1117
+ | Prop | Type | Description |
1118
+ | ------------- | ------------------- | --------------------------------- |
1119
+ | **`version`** | <code>string</code> | The native plugin version string. |
1120
+
1121
+
1122
+ #### PluginListenerHandle
1123
+
1124
+ | Prop | Type |
1125
+ | ------------ | ----------------------------------------- |
1126
+ | **`remove`** | <code>() =&gt; Promise&lt;void&gt;</code> |
1127
+
1128
+
1129
+ #### PeopleChangeEvent
1130
+
1131
+ Event emitted when changes are detected in the device's address book.
1132
+
1133
+ | Prop | Type | Description |
1134
+ | ---------- | ------------------------------------------------------------- | ------------------------------------------------------------ |
1135
+ | **`ids`** | <code>string[]</code> | Array of affected contact IDs (always present, may be empty) |
1136
+ | **`type`** | <code><a href="#peoplechangetype">PeopleChangeType</a></code> | The type of change detected |
1137
+
1138
+
1139
+ ### Type Aliases
1140
+
1141
+
1142
+ #### PermissionState
1143
+
1144
+ <code>'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'</code>
1145
+
1146
+
1147
+ #### PeopleProjection
1148
+
1149
+ Supported fields for the Projection Engine.
1150
+ Requesting only what you need reduces memory usage by O(N).
1151
+ `image` projection support is iOS-only. On Android and Web, requesting `image` is rejected with `UNKNOWN_TYPE` and message `Unsupported projection field: image`.
1152
+
1153
+ <code>'name' | 'organization' | 'birthday' | 'phones' | 'emails' | 'addresses' | 'urls' | 'image' | 'note'</code>
1154
+
1155
+
1156
+ #### PeopleChangeType
1157
+
1158
+ Payload delivered to listeners registered for "peopleChange".
1159
+ - `ids`: always present, contains changed contact IDs (may be empty).
1160
+ - `type`: one of 'insert' | 'update' | 'delete' (default 'update').
1161
+ Current native implementations emit `update`.
1162
+
1163
+ <code>'insert' | 'update' | 'delete'</code>
1164
+
1165
+ </docgen-api>
1166
+
1167
+ ---
1168
+
1169
+ ## Contributing
1170
+
1171
+ Contributions are welcome! Please read the [contributing guide](CONTRIBUTING.md) before submitting a pull request.
1172
+
1173
+ ---
1174
+
1175
+ ## License
1176
+
1177
+ MIT