@dereekb/zoho 13.0.6 → 13.0.7

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 (56) hide show
  1. package/index.cjs.js +2164 -231
  2. package/index.esm.js +2143 -232
  3. package/nestjs/LICENSE +21 -0
  4. package/nestjs/README.md +11 -0
  5. package/nestjs/docs/configuration.md +165 -0
  6. package/nestjs/docs/crm-getting-started.md +296 -0
  7. package/nestjs/index.cjs.js +653 -5
  8. package/nestjs/index.esm.js +647 -7
  9. package/nestjs/package.json +3 -3
  10. package/nestjs/src/lib/crm/crm.api.d.ts +46 -0
  11. package/nestjs/src/lib/crm/crm.config.d.ts +16 -1
  12. package/nestjs/src/lib/crm/crm.module.d.ts +77 -8
  13. package/nestjs/src/lib/index.d.ts +2 -0
  14. package/nestjs/src/lib/recruit/recruit.api.d.ts +50 -0
  15. package/nestjs/src/lib/recruit/recruit.config.d.ts +16 -1
  16. package/nestjs/src/lib/recruit/recruit.module.d.ts +77 -8
  17. package/nestjs/src/lib/sign/index.d.ts +3 -0
  18. package/nestjs/src/lib/sign/sign.api.d.ts +54 -0
  19. package/nestjs/src/lib/sign/sign.config.d.ts +10 -0
  20. package/nestjs/src/lib/sign/sign.module.d.ts +94 -0
  21. package/package.json +2 -2
  22. package/src/lib/accounts/accounts.api.d.ts +149 -3
  23. package/src/lib/accounts/accounts.factory.d.ts +73 -6
  24. package/src/lib/crm/crm.api.d.ts +599 -62
  25. package/src/lib/crm/crm.api.notes.d.ts +46 -3
  26. package/src/lib/crm/crm.api.tags.d.ts +65 -2
  27. package/src/lib/crm/crm.config.d.ts +30 -0
  28. package/src/lib/crm/crm.criteria.d.ts +60 -3
  29. package/src/lib/crm/crm.error.api.d.ts +42 -0
  30. package/src/lib/crm/crm.factory.d.ts +39 -3
  31. package/src/lib/crm/crm.notes.d.ts +36 -0
  32. package/src/lib/crm/crm.tags.d.ts +3 -0
  33. package/src/lib/index.d.ts +1 -0
  34. package/src/lib/recruit/recruit.api.candidates.d.ts +44 -3
  35. package/src/lib/recruit/recruit.api.d.ts +719 -57
  36. package/src/lib/recruit/recruit.api.notes.d.ts +140 -0
  37. package/src/lib/recruit/recruit.api.tags.d.ts +122 -14
  38. package/src/lib/recruit/recruit.config.d.ts +30 -0
  39. package/src/lib/recruit/recruit.criteria.d.ts +55 -3
  40. package/src/lib/recruit/recruit.error.api.d.ts +39 -0
  41. package/src/lib/recruit/recruit.factory.d.ts +39 -3
  42. package/src/lib/recruit/recruit.notes.d.ts +21 -0
  43. package/src/lib/recruit/recruit.tags.d.ts +3 -0
  44. package/src/lib/shared/criteria.d.ts +95 -11
  45. package/src/lib/shared/criteria.util.d.ts +19 -4
  46. package/src/lib/sign/index.d.ts +6 -0
  47. package/src/lib/sign/sign.api.d.ts +397 -0
  48. package/src/lib/sign/sign.api.page.d.ts +109 -0
  49. package/src/lib/sign/sign.config.d.ts +24 -0
  50. package/src/lib/sign/sign.d.ts +225 -0
  51. package/src/lib/sign/sign.error.api.d.ts +7 -0
  52. package/src/lib/sign/sign.factory.d.ts +58 -0
  53. package/src/lib/zoho.api.page.d.ts +41 -10
  54. package/src/lib/zoho.config.d.ts +24 -9
  55. package/src/lib/zoho.limit.d.ts +41 -9
  56. package/src/lib/zoho.type.d.ts +24 -8
package/index.esm.js CHANGED
@@ -3,7 +3,10 @@ import { fetchPageFactory, FetchResponseError, makeUrlSearchParams, parseFetchFi
3
3
  import { BaseError } from 'make-error';
4
4
 
5
5
  /**
6
- * Returns an empty ZohoPageResult that is typed to R and has no more records/results.
6
+ * Creates an empty {@link ZohoPageResult} with no data and `more_records: false`.
7
+ * Useful as a fallback when the API returns `null` instead of an empty result.
8
+ *
9
+ * @returns An empty page result with default pagination info
7
10
  */
8
11
  function emptyZohoPageResult() {
9
12
  return {
@@ -17,11 +20,26 @@ function emptyZohoPageResult() {
17
20
  };
18
21
  }
19
22
  /**
20
- * Creates a FetchPageFactory using the input ZohoFetchPageFetchFunction.
23
+ * Creates a page factory that wraps a Zoho fetch function with automatic pagination.
21
24
  *
22
- * @param fetch
23
- * @param defaults
24
- * @returns
25
+ * The factory reads `info.more_records` from each response to determine if additional
26
+ * pages exist, and automatically increments the `page` parameter for subsequent requests.
27
+ *
28
+ * @param fetch - The Zoho fetch function to paginate over
29
+ * @param defaults - Optional default configuration for the page factory
30
+ * @returns A page factory that produces iterable page fetchers
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const pageFactory = zohoFetchPageFactory(zohoCrmSearchRecords(context));
35
+ *
36
+ * const fetchPage = pageFactory({ module: 'Contacts', word: 'Smith', per_page: 10 });
37
+ * const firstPage = await fetchPage.fetchNext();
38
+ *
39
+ * if (firstPage.result.info.more_records) {
40
+ * const secondPage = await firstPage.fetchNext();
41
+ * }
42
+ * ```
25
43
  */
26
44
  function zohoFetchPageFactory(fetch, defaults) {
27
45
  return fetchPageFactory({
@@ -38,7 +56,13 @@ function zohoFetchPageFactory(fetch, defaults) {
38
56
  });
39
57
  }
40
58
 
59
+ /**
60
+ * Service identifier used to distinguish Zoho Recruit from other Zoho services when managing access tokens and API routing.
61
+ */
41
62
  const ZOHO_RECRUIT_SERVICE_NAME = 'recruit';
63
+ /**
64
+ * Resolves an environment key or passthrough URL to the full Zoho Recruit API URL.
65
+ */
42
66
  function zohoRecruitConfigApiUrl(input) {
43
67
  switch (input) {
44
68
  case 'sandbox':
@@ -86,9 +110,33 @@ const ZOHO_RECRUIT_RECORD_ATTACHMENT_METADATA_ATTACH_TYPE_RESUME = 'Resume';
86
110
  */
87
111
  const MAX_ZOHO_SEARCH_MODULE_RECORDS_CRITERIA = 10;
88
112
  /**
89
- * Creates a ZohoSearchRecordsCriteriaString from a ZohoSearchRecordsCriteriaTree.
113
+ * Compiles a {@link ZohoSearchRecordsCriteriaTreeElement} into a URL-ready criteria string.
114
+ *
115
+ * Accepts any supported input shape: a raw criteria string (passed through),
116
+ * an array of {@link ZohoSearchRecordsCriteriaEntry} (AND-joined), or a full
117
+ * {@link ZohoSearchRecordsCriteriaTree} with nested AND/OR groups. Returns
118
+ * `undefined` when the input is nullish or empty.
90
119
  *
91
- * If the input tree is empty, returns undefined.
120
+ * @param input - Criteria tree element, entry array, or raw criteria string
121
+ * @returns Compiled criteria string, or `undefined` if input is empty
122
+ *
123
+ * @example
124
+ * ```typescript
125
+ * // From an entry array (AND-joined):
126
+ * const criteria = zohoSearchRecordsCriteriaString([
127
+ * { field: 'Last_Name', filter: 'equals', value: 'Smith' },
128
+ * { field: 'Email', filter: 'contains', value: 'example.com' }
129
+ * ]);
130
+ * // => '((Last_Name:equals:Smith)and(Email:contains:example.com))'
131
+ *
132
+ * // From a tree with OR:
133
+ * const orCriteria = zohoSearchRecordsCriteriaString({
134
+ * or: [
135
+ * [{ field: 'Status', filter: 'equals', value: 'Active' }],
136
+ * [{ field: 'Status', filter: 'equals', value: 'Pending' }]
137
+ * ]
138
+ * });
139
+ * ```
92
140
  */
93
141
  function zohoSearchRecordsCriteriaString(input) {
94
142
  let result;
@@ -111,6 +159,14 @@ function zohoSearchRecordsCriteriaString(input) {
111
159
  }
112
160
  return result;
113
161
  }
162
+ /**
163
+ * Compiles a {@link ZohoSearchRecordsCriteriaTree} into a criteria string by
164
+ * recursively resolving nested AND/OR groups. When both `and` and `or` are
165
+ * present at the same level, the OR group is merged into the AND group.
166
+ *
167
+ * @param tree - Criteria tree containing `and` and/or `or` branches
168
+ * @returns Compiled criteria string, or `undefined` if the tree is empty
169
+ */
114
170
  function zohoSearchRecordsCriteriaStringForTree(tree) {
115
171
  function convertToString(value) {
116
172
  let result;
@@ -144,7 +200,9 @@ function zohoSearchRecordsCriteriaStringForTree(tree) {
144
200
  return result;
145
201
  }
146
202
  /**
147
- * Escape used for ZohoSearchRecordsCriteriaString
203
+ * Escapes parentheses and commas in a field value for use in a {@link ZohoSearchRecordsCriteriaString}.
204
+ *
205
+ * Characters `(`, `)`, and `,` are escaped with a double-backslash prefix as required by the Zoho API.
148
206
  */
149
207
  const escapeZohoFieldValueForCriteriaString = escapeStringCharactersFunction({
150
208
  /**
@@ -154,10 +212,22 @@ const escapeZohoFieldValueForCriteriaString = escapeStringCharactersFunction({
154
212
  escapeCharacter: (char) => `\\\\${char}`
155
213
  });
156
214
  /**
157
- * Converts the input entry to a ZohoSearchRecordsCriteriaString. Properly escapes any parenthesis or commas.
215
+ * Converts a single {@link ZohoSearchRecordsCriteriaEntry} into a parenthesized criteria string.
158
216
  *
159
- * @param entry
160
- * @returns
217
+ * Automatically escapes parentheses and commas in the value via {@link escapeZohoFieldValueForCriteriaString}.
218
+ *
219
+ * @param entry - The criteria entry to convert
220
+ * @returns Criteria string in the format `(field:filter:escapedValue)`
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * const str = zohoSearchRecordsCriteriaEntryToCriteriaString({
225
+ * field: 'Last_Name',
226
+ * filter: 'equals',
227
+ * value: 'Smith'
228
+ * });
229
+ * // => '(Last_Name:equals:Smith)'
230
+ * ```
161
231
  */
162
232
  function zohoSearchRecordsCriteriaEntryToCriteriaString(entry) {
163
233
  const escapedValue = escapeZohoFieldValueForCriteriaString(entry.value);
@@ -165,15 +235,35 @@ function zohoSearchRecordsCriteriaEntryToCriteriaString(entry) {
165
235
  }
166
236
 
167
237
  /**
168
- * Can search up to 10 criteria at a time.
169
- *
170
- * https://www.zoho.com/recruit/developer-guide/apiv2/search-records.html
238
+ * Maximum number of criteria allowed per Recruit search query (10).
171
239
  *
172
240
  * "You can search for a maximum of 10 criteria (with the same or different column) with equals and starts_with conditions."
241
+ *
242
+ * Re-exports {@link MAX_ZOHO_SEARCH_MODULE_RECORDS_CRITERIA}.
243
+ *
244
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/search-records.html
173
245
  */
174
246
  const MAX_ZOHO_RECRUIT_SEARCH_MODULE_RECORDS_CRITERIA = MAX_ZOHO_SEARCH_MODULE_RECORDS_CRITERIA;
247
+ /**
248
+ * Recruit-specific re-export of {@link zohoSearchRecordsCriteriaString}.
249
+ *
250
+ * Compiles a criteria tree element (entry array, nested tree, or raw string) into a
251
+ * URL-ready criteria string. Returns `undefined` when the input is nullish or empty.
252
+ */
175
253
  const zohoRecruitSearchRecordsCriteriaString = zohoSearchRecordsCriteriaString;
254
+ /**
255
+ * Recruit-specific re-export of {@link zohoSearchRecordsCriteriaStringForTree}.
256
+ *
257
+ * Compiles a {@link ZohoRecruitSearchRecordsCriteriaTree} into a criteria string by
258
+ * recursively resolving nested AND/OR groups.
259
+ */
176
260
  const zohoRecruitSearchRecordsCriteriaStringForTree = zohoSearchRecordsCriteriaStringForTree;
261
+ /**
262
+ * Recruit-specific re-export of {@link zohoSearchRecordsCriteriaEntryToCriteriaString}.
263
+ *
264
+ * Converts a single criteria entry into a parenthesized criteria string
265
+ * in the format `(field:filter:escapedValue)`.
266
+ */
177
267
  const zohoRecruitSearchRecordsCriteriaEntryToCriteriaString = zohoSearchRecordsCriteriaEntryToCriteriaString;
178
268
 
179
269
  /**
@@ -480,19 +570,37 @@ class ZohoRecruitRecordNoContentError extends BaseError {
480
570
  this.recordId = recordId;
481
571
  }
482
572
  }
573
+ /**
574
+ * Base error for Zoho Recruit record CRUD operations, carrying structured error details from the API response.
575
+ */
483
576
  class ZohoRecruitRecordCrudError extends ZohoServerError {
484
577
  }
578
+ /**
579
+ * Thrown when a required field is missing from the record data submitted to Zoho Recruit.
580
+ */
485
581
  class ZohoRecruitRecordCrudMandatoryFieldNotFoundError extends ZohoRecruitRecordCrudError {
486
582
  }
583
+ /**
584
+ * Thrown when a record cannot be created or updated because it would produce duplicate data in Zoho Recruit.
585
+ */
487
586
  class ZohoRecruitRecordCrudDuplicateDataError extends ZohoRecruitRecordCrudError {
488
587
  }
588
+ /**
589
+ * Thrown when the Zoho Recruit API rejects record data as invalid. Provides per-field error details via {@link invalidFieldDetails}.
590
+ */
489
591
  class ZohoRecruitRecordCrudInvalidDataError extends ZohoRecruitRecordCrudError {
490
592
  get invalidFieldDetails() {
491
593
  return this.error.details;
492
594
  }
493
595
  }
596
+ /**
597
+ * Thrown when the 'id' field in the submitted data does not match any existing Zoho Recruit record.
598
+ */
494
599
  class ZohoRecruitRecordCrudNoMatchingRecordError extends ZohoRecruitRecordCrudInvalidDataError {
495
600
  }
601
+ /**
602
+ * Creates a typed CRUD error subclass based on the error code returned by the Zoho Recruit API, enabling callers to catch specific failure modes.
603
+ */
496
604
  function zohoRecruitRecordCrudError(error) {
497
605
  let result;
498
606
  switch (error.code) {
@@ -517,6 +625,9 @@ function zohoRecruitRecordCrudError(error) {
517
625
  }
518
626
  return result;
519
627
  }
628
+ /**
629
+ * Returns an assertion function that throws {@link ZohoRecruitRecordNoContentError} when the data array result is empty or null, indicating the requested record does not exist.
630
+ */
520
631
  function assertZohoRecruitRecordDataArrayResultHasContent(moduleName, recordId) {
521
632
  return (x) => {
522
633
  if (x == null || !x.data?.length) {
@@ -527,7 +638,13 @@ function assertZohoRecruitRecordDataArrayResultHasContent(moduleName, recordId)
527
638
  }
528
639
  };
529
640
  }
641
+ /**
642
+ * Logs Zoho Recruit server errors to the console, prefixed with the 'ZohoRecruit' service label.
643
+ */
530
644
  const logZohoRecruitErrorToConsole = logZohoServerErrorFunction('ZohoRecruit');
645
+ /**
646
+ * Parses the JSON body of a failed Zoho Recruit fetch response into a structured error, returning undefined if the body cannot be parsed.
647
+ */
531
648
  async function parseZohoRecruitError(responseError) {
532
649
  const data = await responseError.response.json().catch((x) => undefined);
533
650
  let result;
@@ -536,6 +653,9 @@ async function parseZohoRecruitError(responseError) {
536
653
  }
537
654
  return result;
538
655
  }
656
+ /**
657
+ * Converts raw Zoho Recruit error response data into a parsed error, delegating to Recruit-specific handlers for known error codes and falling back to the shared Zoho parser.
658
+ */
539
659
  function parseZohoRecruitServerErrorResponseData(errorResponseData, responseError) {
540
660
  let result;
541
661
  const error = tryFindZohoServerErrorData(errorResponseData, responseError);
@@ -550,7 +670,13 @@ function parseZohoRecruitServerErrorResponseData(errorResponseData, responseErro
550
670
  }
551
671
  return result;
552
672
  }
673
+ /**
674
+ * Intercepts Zoho Recruit API responses that return HTTP 200 but contain an error payload, re-throwing them as proper errors so callers are not silently misled by a success status.
675
+ */
553
676
  const interceptZohoRecruit200StatusWithErrorResponse = interceptZohoErrorResponseFactory(parseZohoRecruitServerErrorResponseData);
677
+ /**
678
+ * Wraps a fetch client to automatically parse Zoho Recruit error responses, log them, and invoke a custom handler (e.g., for token refresh on authentication failures).
679
+ */
554
680
  const handleZohoRecruitErrorFetch = handleZohoErrorFetchFactory(parseZohoRecruitError, logZohoRecruitErrorToConsole);
555
681
  // MARK: Compat
556
682
  /**
@@ -566,9 +692,10 @@ const assertRecordDataArrayResultHasContent = assertZohoRecruitRecordDataArrayRe
566
692
  */
567
693
  const ZOHO_RECRUIT_CRUD_FUNCTION_MAX_RECORDS_LIMIT = 100;
568
694
  /**
569
- * The APIs for Insert, Upsert, and Update have the same structure.
695
+ * Shared implementation for the Insert, Upsert, and Update endpoints, which all share the same request/response structure.
570
696
  *
571
- * @returns
697
+ * When a single record is provided, the function returns the change details directly or throws on error.
698
+ * When multiple records are provided, it returns a paired success/error result.
572
699
  */
573
700
  function updateRecordLikeFunction$1(context, fetchUrlPrefix, fetchMethod) {
574
701
  return (({ data, module }) => context.fetchJson(`/v2/${module}${fetchUrlPrefix}`, zohoRecruitApiFetchJsonInput(fetchMethod, { data: asArray(data) })).then((x) => {
@@ -589,45 +716,145 @@ function updateRecordLikeFunction$1(context, fetchUrlPrefix, fetchMethod) {
589
716
  }));
590
717
  }
591
718
  /**
592
- * Inserts one or more records into Recruit.
719
+ * Creates a {@link ZohoRecruitInsertRecordFunction} bound to the given context.
593
720
  *
594
- * https://www.zoho.com/recruit/developer-guide/apiv2/insert-records.html
721
+ * Inserts one or more records into a Recruit module. When a single record is
722
+ * provided, returns the {@link ZohoRecruitChangeObjectDetails} directly or
723
+ * throws on error. When multiple records are provided, returns a
724
+ * {@link ZohoRecruitUpdateRecordResult} with paired success/error arrays.
595
725
  *
596
- * @param context
597
- * @returns
726
+ * Maximum of {@link ZOHO_RECRUIT_CRUD_FUNCTION_MAX_RECORDS_LIMIT} records per call.
727
+ *
728
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
729
+ * @returns Function that inserts records into the specified module
730
+ *
731
+ * @example
732
+ * ```typescript
733
+ * const insertRecord = zohoRecruitInsertRecord(context);
734
+ *
735
+ * // Single record — returns details directly or throws on error:
736
+ * const details = await insertRecord({
737
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
738
+ * data: { First_Name: 'Jane', Last_Name: 'Doe', Email: 'jane@example.com' }
739
+ * });
740
+ *
741
+ * // Multiple records — returns paired success/error arrays:
742
+ * const result = await insertRecord({
743
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
744
+ * data: [
745
+ * { First_Name: 'Jane', Last_Name: 'Doe', Email: 'jane@example.com' },
746
+ * { First_Name: 'John', Last_Name: 'Doe', Email: 'john@example.com' }
747
+ * ]
748
+ * });
749
+ * ```
750
+ *
751
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/insert-records.html
598
752
  */
599
753
  function zohoRecruitInsertRecord(context) {
600
754
  return updateRecordLikeFunction$1(context, '', 'POST');
601
755
  }
602
756
  /**
603
- * Updates or inserts one or more records in Recruit.
757
+ * Creates a {@link ZohoRecruitUpsertRecordFunction} bound to the given context.
604
758
  *
605
- * https://www.zoho.com/recruit/developer-guide/apiv2/upsert-records.html
759
+ * Inserts or updates one or more records in a Recruit module based on whether
760
+ * each record includes an `id`. Uses the `/upsert` endpoint. Single-record
761
+ * calls return details directly or throw; multi-record calls return paired
762
+ * success/error arrays.
606
763
  *
607
- * @param context
608
- * @returns
764
+ * Maximum of {@link ZOHO_RECRUIT_CRUD_FUNCTION_MAX_RECORDS_LIMIT} records per call.
765
+ *
766
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
767
+ * @returns Function that upserts records in the specified module
768
+ *
769
+ * @example
770
+ * ```typescript
771
+ * const upsertRecord = zohoRecruitUpsertRecord(context);
772
+ *
773
+ * // Create (no id) — returns details directly:
774
+ * const created = await upsertRecord({
775
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
776
+ * data: { Email: 'new@example.com', Last_Name: 'New' }
777
+ * });
778
+ *
779
+ * // Update (with id) — returns details directly:
780
+ * const updated = await upsertRecord({
781
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
782
+ * data: { id: existingId, First_Name: 'Updated' }
783
+ * });
784
+ *
785
+ * // Mixed create and update — returns paired arrays:
786
+ * const result = await upsertRecord({
787
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
788
+ * data: [
789
+ * { Email: 'create@example.com', Last_Name: 'Create' },
790
+ * { id: existingId, First_Name: 'Update' }
791
+ * ]
792
+ * });
793
+ * ```
794
+ *
795
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/upsert-records.html
609
796
  */
610
797
  function zohoRecruitUpsertRecord(context) {
611
798
  return updateRecordLikeFunction$1(context, '/upsert', 'POST');
612
799
  }
613
800
  /**
614
- * Updates one or more records in Recruit.
801
+ * Creates a {@link ZohoRecruitUpdateRecordFunction} bound to the given context.
615
802
  *
616
- * https://www.zoho.com/recruit/developer-guide/apiv2/update-records.html
803
+ * Updates one or more existing records in a Recruit module. Each record must
804
+ * include an `id` field. Single-record calls return details directly or throw;
805
+ * multi-record calls return paired success/error arrays.
617
806
  *
618
- * @param context
619
- * @returns
807
+ * Maximum of {@link ZOHO_RECRUIT_CRUD_FUNCTION_MAX_RECORDS_LIMIT} records per call.
808
+ *
809
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
810
+ * @returns Function that updates records in the specified module
811
+ *
812
+ * @example
813
+ * ```typescript
814
+ * const updateRecord = zohoRecruitUpdateRecord(context);
815
+ *
816
+ * // Single record — returns details directly:
817
+ * const details = await updateRecord({
818
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
819
+ * data: { id: recordId, First_Name: 'Updated Name' }
820
+ * });
821
+ *
822
+ * // Multiple records — returns paired arrays:
823
+ * const result = await updateRecord({
824
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
825
+ * data: [
826
+ * { id: recordId1, First_Name: 'Updated 1' },
827
+ * { id: recordId2, First_Name: 'Updated 2' }
828
+ * ]
829
+ * });
830
+ * ```
831
+ *
832
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/update-records.html
620
833
  */
621
834
  function zohoRecruitUpdateRecord(context) {
622
835
  return updateRecordLikeFunction$1(context, '', 'PUT');
623
836
  }
624
837
  /**
625
- * Deletes one or more records from the given module.
838
+ * Creates a {@link ZohoRecruitDeleteRecordFunction} bound to the given context.
626
839
  *
627
- * https://www.zoho.com/recruit/developer-guide/apiv2/delete-records.html
840
+ * Deletes one or more records from a Recruit module by their IDs. Supports
841
+ * an optional `wf_trigger` flag to execute workflow rules on deletion. Returns
842
+ * a response with separated success and error entries.
628
843
  *
629
- * @param context
630
- * @returns ZohoRecruitDeleteRecordFunction
844
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
845
+ * @returns Function that deletes records from the specified module
846
+ *
847
+ * @example
848
+ * ```typescript
849
+ * const deleteRecord = zohoRecruitDeleteRecord(context);
850
+ *
851
+ * const result = await deleteRecord({
852
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
853
+ * ids: candidateId
854
+ * });
855
+ * ```
856
+ *
857
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/delete-records.html
631
858
  */
632
859
  function zohoRecruitDeleteRecord(context) {
633
860
  return ({ ids, module, wf_trigger }) => {
@@ -635,12 +862,26 @@ function zohoRecruitDeleteRecord(context) {
635
862
  };
636
863
  }
637
864
  /**
638
- * Retrieves a specific record from the given module.
865
+ * Creates a {@link ZohoRecruitGetRecordByIdFunction} bound to the given context.
639
866
  *
640
- * https://www.zoho.com/recruit/developer-guide/apiv2/get-records.html
867
+ * Retrieves a single record from a Recruit module by its ID. The response is
868
+ * unwrapped from the standard data array, returning the record directly.
869
+ * Throws if the record is not found.
641
870
  *
642
- * @param context
643
- * @returns
871
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
872
+ * @returns Function that retrieves a record by module name and ID
873
+ *
874
+ * @example
875
+ * ```typescript
876
+ * const getRecordById = zohoRecruitGetRecordById(context);
877
+ *
878
+ * const record = await getRecordById({
879
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
880
+ * id: candidateId
881
+ * });
882
+ * ```
883
+ *
884
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/get-records.html
644
885
  */
645
886
  function zohoRecruitGetRecordById(context) {
646
887
  return (input) => context
@@ -649,23 +890,61 @@ function zohoRecruitGetRecordById(context) {
649
890
  .then((x) => x.data[0]);
650
891
  }
651
892
  /**
652
- * Retrieves records from the given module. Used for paginating across all records.
893
+ * Creates a {@link ZohoRecruitGetRecordsFunction} bound to the given context.
653
894
  *
654
- * https://www.zoho.com/recruit/developer-guide/apiv2/get-records.html
895
+ * Retrieves a paginated list of records from a Recruit module. Supports field
896
+ * selection, sorting, custom view filtering, territory filtering, and
897
+ * conversion/approval status filters via {@link ZohoRecruitGetRecordsInput}.
655
898
  *
656
- * @param context
657
- * @returns
899
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
900
+ * @returns Function that retrieves paginated records from a module
901
+ *
902
+ * @example
903
+ * ```typescript
904
+ * const getRecords = zohoRecruitGetRecords(context);
905
+ *
906
+ * const page = await getRecords({
907
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
908
+ * per_page: 10
909
+ * });
910
+ * ```
911
+ *
912
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/get-records.html
658
913
  */
659
914
  function zohoRecruitGetRecords(context) {
660
915
  return ((input) => context.fetchJson(`/v2/${input.module}?${zohoRecruitUrlSearchParamsMinusModule(input).toString()}`, zohoRecruitApiFetchJsonInput('GET')));
661
916
  }
662
917
  /**
663
- * Searches records from the given module.
918
+ * Creates a {@link ZohoRecruitSearchRecordsFunction} bound to the given context.
664
919
  *
665
- * https://www.zoho.com/recruit/developer-guide/apiv2/search-records.html
920
+ * Searches records in a Recruit module using one of: criteria tree (compiled
921
+ * via {@link zohoRecruitSearchRecordsCriteriaString}), email, phone, or keyword.
922
+ * At least one search parameter must be provided. Returns a paginated result,
923
+ * defaulting to an empty data array when no matches are found.
666
924
  *
667
- * @param context
668
- * @returns
925
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
926
+ * @returns Function that searches records in the specified module
927
+ * @throws {Error} If none of `criteria`, `email`, `phone`, or `word` are provided
928
+ *
929
+ * @example
930
+ * ```typescript
931
+ * const searchRecords = zohoRecruitSearchRecords(context);
932
+ *
933
+ * // Search by criteria:
934
+ * const result = await searchRecords({
935
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
936
+ * criteria: [{ field: 'Last_Name', filter: 'starts_with', value: 'Smith' }],
937
+ * per_page: 10
938
+ * });
939
+ *
940
+ * // Search by keyword:
941
+ * const wordResult = await searchRecords({
942
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
943
+ * word: 'engineer'
944
+ * });
945
+ * ```
946
+ *
947
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/search-records.html
669
948
  */
670
949
  function zohoRecruitSearchRecords(context) {
671
950
  function searchRecordsUrlSearchParams(input) {
@@ -683,16 +962,60 @@ function zohoRecruitSearchRecords(context) {
683
962
  }
684
963
  return ((input) => context.fetchJson(`/v2/${input.module}/search?${searchRecordsUrlSearchParams(input).toString()}`, zohoRecruitApiFetchJsonInput('GET')).then((x) => x ?? { data: [], info: { more_records: false } }));
685
964
  }
965
+ /**
966
+ * Creates a {@link ZohoRecruitSearchRecordsPageFactory} bound to the given context.
967
+ *
968
+ * Returns a page factory that automatically handles Zoho Recruit's pagination,
969
+ * making it easy to iterate through all search results across multiple pages.
970
+ *
971
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
972
+ * @returns Page factory for iterating over search results
973
+ *
974
+ * @example
975
+ * ```typescript
976
+ * const pageFactory = zohoRecruitSearchRecordsPageFactory(context);
977
+ *
978
+ * const fetchPage = pageFactory({
979
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
980
+ * criteria: [{ field: 'Last_Name', filter: 'starts_with', value: 'Smith' }],
981
+ * per_page: 5
982
+ * });
983
+ *
984
+ * const firstPage = await fetchPage.fetchNext();
985
+ * const secondPage = await firstPage.fetchNext();
986
+ * ```
987
+ */
686
988
  function zohoRecruitSearchRecordsPageFactory(context) {
687
989
  return zohoFetchPageFactory(zohoRecruitSearchRecords(context));
688
990
  }
689
991
  /**
690
- * Creates a ZohoRecruitGetRelatedRecordsFunctionFactory, which can be used to create ZohoRecruitGetRelatedRecordsFunction<T> that targets retrieving related records of a given type.
992
+ * Creates a {@link ZohoRecruitGetRelatedRecordsFunctionFactory} bound to the given context.
993
+ *
994
+ * Returns a factory that produces typed functions for fetching related records
995
+ * (e.g. Notes, Emails, Attachments) of a specific target module. The factory
996
+ * accepts a {@link ZohoRecruitGetRelatedRecordsFunctionConfig} to specify the
997
+ * target module and empty-result behavior. By default, returns an empty page
998
+ * result instead of null when no records are found.
999
+ *
1000
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1001
+ * @returns Factory that creates typed related-records retrieval functions
691
1002
  *
692
- * https://www.zoho.com/recruit/developer-guide/apiv2/get-related-records.html
1003
+ * @example
1004
+ * ```typescript
1005
+ * const factory = zohoRecruitGetRelatedRecordsFunctionFactory(context);
693
1006
  *
694
- * @param context the ZohoRecruitContext to use
695
- * @returns a ZohoRecruitGetRelatedRecordsFunctionFactory
1007
+ * // Create a typed function for fetching related Notes:
1008
+ * const getNotesForRecord = factory<ZohoRecruitRecordNote>({
1009
+ * targetModule: ZOHO_RECRUIT_NOTES_MODULE
1010
+ * });
1011
+ *
1012
+ * const notes = await getNotesForRecord({
1013
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
1014
+ * id: candidateId
1015
+ * });
1016
+ * ```
1017
+ *
1018
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/get-related-records.html
696
1019
  */
697
1020
  function zohoRecruitGetRelatedRecordsFunctionFactory(context) {
698
1021
  return (config) => {
@@ -700,15 +1023,126 @@ function zohoRecruitGetRelatedRecordsFunctionFactory(context) {
700
1023
  return (input) => context.fetchJson(`/v2/${input.module}/${input.id}/${targetModule}?${zohoRecruitUrlSearchParamsMinusIdAndModule(input, input.filter).toString()}`, zohoRecruitApiFetchJsonInput('GET')).then((x) => x ?? (returnEmptyRecordsInsteadOfNull !== false ? emptyZohoPageResult() : x));
701
1024
  };
702
1025
  }
1026
+ /**
1027
+ * Creates a {@link ZohoRecruitGetEmailsForRecordFunction} bound to the given context.
1028
+ *
1029
+ * Retrieves email metadata related to a specific record by targeting the
1030
+ * Emails module via the related records API. Returns a paginated result of
1031
+ * {@link ZohoRecruitRecordEmailMetadata} entries. When no emails exist for the
1032
+ * record, the result contains an empty data array rather than null.
1033
+ *
1034
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1035
+ * @returns Function that retrieves emails for a record
1036
+ *
1037
+ * @example
1038
+ * ```typescript
1039
+ * const getEmailsForRecord = zohoRecruitGetEmailsForRecord(context);
1040
+ *
1041
+ * const result = await getEmailsForRecord({
1042
+ * id: candidateId,
1043
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE
1044
+ * });
1045
+ * ```
1046
+ *
1047
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/get-related-records.html
1048
+ */
703
1049
  function zohoRecruitGetEmailsForRecord(context) {
704
1050
  return zohoRecruitGetRelatedRecordsFunctionFactory(context)({ targetModule: ZOHO_RECRUIT_EMAILS_MODULE });
705
1051
  }
1052
+ /**
1053
+ * Creates a {@link ZohoRecruitGetEmailsForRecordPageFactory} bound to the given context.
1054
+ *
1055
+ * Returns a page factory for iterating over emails related to a record across
1056
+ * multiple pages. Wraps {@link zohoRecruitGetEmailsForRecord} with automatic
1057
+ * pagination handling via {@link zohoFetchPageFactory}.
1058
+ *
1059
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1060
+ * @returns Page factory for iterating over record emails
1061
+ *
1062
+ * @example
1063
+ * ```typescript
1064
+ * const pageFactory = zohoRecruitGetEmailsForRecordPageFactory(context);
1065
+ *
1066
+ * const fetchPage = pageFactory({
1067
+ * id: candidateId,
1068
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
1069
+ * per_page: 5
1070
+ * });
1071
+ *
1072
+ * const firstPage = await fetchPage.fetchNext();
1073
+ *
1074
+ * if (firstPage.result.info.more_records) {
1075
+ * const secondPage = await firstPage.fetchNext();
1076
+ * }
1077
+ * ```
1078
+ *
1079
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/get-related-records.html
1080
+ */
706
1081
  function zohoRecruitGetEmailsForRecordPageFactory(context) {
707
1082
  return zohoFetchPageFactory(zohoRecruitGetEmailsForRecord(context));
708
1083
  }
1084
+ /**
1085
+ * Creates a {@link ZohoRecruitGetAttachmentsForRecordFunction} bound to the given context.
1086
+ *
1087
+ * Retrieves attachment metadata related to a specific record by targeting the
1088
+ * Attachments module via the related records API. Returns a paginated result of
1089
+ * {@link ZohoRecruitRecordAttachmentMetadata} entries including file names, sizes,
1090
+ * and category information. When no attachments exist for the record, the result
1091
+ * contains an empty data array rather than null.
1092
+ *
1093
+ * Each attachment entry includes a `$type` field that distinguishes between
1094
+ * directly uploaded attachments (`'Attachment'`) and linked attachments.
1095
+ *
1096
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1097
+ * @returns Function that retrieves attachments for a record
1098
+ *
1099
+ * @example
1100
+ * ```typescript
1101
+ * const getAttachmentsForRecord = zohoRecruitGetAttachmentsForRecord(context);
1102
+ *
1103
+ * const result = await getAttachmentsForRecord({
1104
+ * id: candidateId,
1105
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE
1106
+ * });
1107
+ *
1108
+ * // Filter to only directly uploaded attachments (downloadable):
1109
+ * const downloadable = result.data.filter((x) => x.$type === 'Attachment');
1110
+ * ```
1111
+ *
1112
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/get-related-records.html
1113
+ */
709
1114
  function zohoRecruitGetAttachmentsForRecord(context) {
710
1115
  return zohoRecruitGetRelatedRecordsFunctionFactory(context)({ targetModule: ZOHO_RECRUIT_ATTACHMENTS_MODULE });
711
1116
  }
1117
+ /**
1118
+ * Creates a {@link ZohoRecruitGetAttachmentsForRecordPageFactory} bound to the given context.
1119
+ *
1120
+ * Returns a page factory for iterating over attachments related to a record
1121
+ * across multiple pages. Wraps {@link zohoRecruitGetAttachmentsForRecord} with
1122
+ * automatic pagination handling via {@link zohoFetchPageFactory}.
1123
+ *
1124
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1125
+ * @returns Page factory for iterating over record attachments
1126
+ *
1127
+ * @example
1128
+ * ```typescript
1129
+ * const pageFactory = zohoRecruitGetAttachmentsForRecordPageFactory(context);
1130
+ *
1131
+ * const fetchPage = pageFactory({
1132
+ * id: candidateId,
1133
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
1134
+ * per_page: 10
1135
+ * });
1136
+ *
1137
+ * const firstPage = await fetchPage.fetchNext();
1138
+ *
1139
+ * if (firstPage.result.info.more_records) {
1140
+ * const secondPage = await firstPage.fetchNext();
1141
+ * }
1142
+ * ```
1143
+ *
1144
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/get-related-records.html
1145
+ */
712
1146
  function zohoRecruitGetAttachmentsForRecordPageFactory(context) {
713
1147
  return zohoFetchPageFactory(zohoRecruitGetAttachmentsForRecord(context));
714
1148
  }
@@ -719,88 +1153,121 @@ function zohoRecruitGetAttachmentsForRecordPageFactory(context) {
719
1153
  */
720
1154
  const ZOHO_RECRUIT_ATTACHMENT_MAX_SIZE = 20 * 1024 * 1024;
721
1155
  /**
722
- * Uploads an attachment to a record.
1156
+ * Creates a {@link ZohoRecruitUploadAttachmentForRecordFunction} bound to the given context.
723
1157
  *
724
- * https://www.zoho.com/recruit/developer-guide/apiv2/upload-attachment.html
1158
+ * Uploads an attachment to a specific record. Supports either a direct
1159
+ * {@link File} upload (sent as multipart/form-data) or a URL from which Zoho
1160
+ * will fetch the file. An attachment category must be specified by ID or name.
1161
+ * Maximum file size is {@link ZOHO_RECRUIT_ATTACHMENT_MAX_SIZE} (20MB).
725
1162
  *
726
- * @param context
727
- * @returns
1163
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1164
+ * @returns Function that uploads an attachment to a record
1165
+ * @throws {Error} If neither `file` nor `attachmentUrl` is provided
1166
+ * @throws {Error} If neither `attachmentCategoryId` nor `attachmentCategoryName` is provided
1167
+ *
1168
+ * @example
1169
+ * ```typescript
1170
+ * const uploadAttachment = zohoRecruitUploadAttachmentForRecord(context);
1171
+ *
1172
+ * // Upload a file directly:
1173
+ * await uploadAttachment({
1174
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
1175
+ * id: candidateId,
1176
+ * file: new File(['content'], 'resume.pdf', { type: 'application/pdf' }),
1177
+ * attachmentCategoryName: 'Resume'
1178
+ * });
1179
+ *
1180
+ * // Upload from a URL:
1181
+ * await uploadAttachment({
1182
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
1183
+ * id: candidateId,
1184
+ * attachmentUrl: 'https://example.com/document.pdf',
1185
+ * attachmentCategoryName: 'Others'
1186
+ * });
1187
+ * ```
1188
+ *
1189
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/upload-attachment.html
728
1190
  */
729
1191
  function zohoRecruitUploadAttachmentForRecord(context) {
730
1192
  return (input) => {
731
- const { attachmentCategoryId, attachmentCategoryName, formData } = input;
1193
+ const { attachmentCategoryId, attachmentCategoryName, file, attachmentUrl } = input;
732
1194
  const urlParams = {
733
1195
  attachments_category_id: joinStringsWithCommas(attachmentCategoryId),
734
- attachments_category: joinStringsWithCommas(attachmentCategoryName),
735
- attachment_url: input.attachmentUrl
1196
+ attachments_category: joinStringsWithCommas(attachmentCategoryName)
736
1197
  };
737
1198
  if (!urlParams.attachments_category_id?.length && !urlParams.attachments_category?.length) {
738
1199
  throw new Error('attachmentCategoryId or attachmentCategoryName must be provided and not empty.');
739
1200
  }
740
- if (formData != null) {
741
- delete urlParams.attachment_url;
1201
+ const url = `/v2/${input.module}/${input.id}/${ZOHO_RECRUIT_ATTACHMENTS_MODULE}?${makeUrlSearchParams(urlParams).toString()}`;
1202
+ if (file != null) {
1203
+ const body = new FormData();
1204
+ body.append('file', file);
1205
+ // Clear the base Content-Type header (empty string removes it via mergeRequestHeaders) so fetch auto-detects multipart/form-data with the correct boundary from the FormData body.
1206
+ return context.fetch(url, { method: 'POST', headers: { 'Content-Type': '' }, body });
742
1207
  }
743
- const url = `https://recruitsandbox.zoho.com/recruit/v2/${input.module}/${input.id}/${ZOHO_RECRUIT_ATTACHMENTS_MODULE}?${makeUrlSearchParams(urlParams).toString()}`;
744
- let response;
745
- if (urlParams.attachment_url) {
746
- response = context.fetch(url, { method: 'POST' });
747
- }
748
- else if (formData != null) {
749
- throw new Error('unsupported currently. Use the attachmentUrl parameter instead.');
750
- // There is something weird going on with sending requests this way and zoho's server is rejecting it.
751
- /*
752
- response = context.fetch(url, {
753
- method: 'POST',
754
- headers: {
755
- 'Content-Type': 'multipart/form-data',
756
- 'content-length': '210'
757
- },
758
- body: formData
759
- });
760
- */
761
- /*
762
- const fullUrl = (context.config.apiUrl as string) + url;
763
- const accessToken = await context.accessTokenStringFactory();
764
-
765
- response = fetch(fullUrl, {
766
- headers: {
767
- Authorization: `Bearer ${accessToken}`
768
- },
769
- body: formData,
770
- method: 'POST'
771
- });
772
-
773
- console.log({ response });
774
- */
1208
+ else if (attachmentUrl) {
1209
+ const urlWithAttachment = `${url}&${makeUrlSearchParams({ attachment_url: attachmentUrl }).toString()}`;
1210
+ return context.fetch(urlWithAttachment, { method: 'POST' });
775
1211
  }
776
1212
  else {
777
- throw new Error('body or attachmentUrl must be provided.');
1213
+ throw new Error('file or attachmentUrl must be provided.');
778
1214
  }
779
- return response;
780
1215
  };
781
1216
  }
782
1217
  /**
783
- * Downloads an attachment from a record.
1218
+ * Creates a {@link ZohoRecruitDownloadAttachmentForRecordFunction} bound to the given context.
784
1219
  *
785
- * https://www.zoho.com/recruit/developer-guide/apiv2/download-attachments.html
1220
+ * Downloads a specific attachment from a record. Returns a parsed
1221
+ * {@link FetchFileResponse} containing the file data and metadata extracted
1222
+ * from the response headers.
786
1223
  *
787
- * @param context
788
- * @returns
1224
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1225
+ * @returns Function that downloads an attachment by record and attachment ID
1226
+ *
1227
+ * @example
1228
+ * ```typescript
1229
+ * const downloadAttachment = zohoRecruitDownloadAttachmentForRecord(context);
1230
+ *
1231
+ * const fileResponse = await downloadAttachment({
1232
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
1233
+ * id: candidateId,
1234
+ * attachment_id: attachmentId
1235
+ * });
1236
+ * ```
1237
+ *
1238
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/download-attachments.html
789
1239
  */
790
1240
  function zohoRecruitDownloadAttachmentForRecord(context) {
791
1241
  return (input) => context.fetch(`/v2/${input.module}/${input.id}/${ZOHO_RECRUIT_ATTACHMENTS_MODULE}/${input.attachment_id}`, { method: 'GET' }).then(parseFetchFileResponse);
792
1242
  }
793
1243
  /**
794
- * Deletes an attachment from a record.
1244
+ * Creates a {@link ZohoRecruitDeleteAttachmentFromRecordFunction} bound to the given context.
795
1245
  *
796
- * https://www.zoho.com/recruit/developer-guide/apiv2/delete-attachments.html
1246
+ * Deletes a specific attachment from a record by its attachment ID.
1247
+ * Returns the raw {@link Response}.
797
1248
  *
798
- * @param context
799
- * @returns
1249
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1250
+ * @returns Function that deletes an attachment by record and attachment ID
1251
+ *
1252
+ * @example
1253
+ * ```typescript
1254
+ * const deleteAttachment = zohoRecruitDeleteAttachmentFromRecord(context);
1255
+ *
1256
+ * const response = await deleteAttachment({
1257
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
1258
+ * id: candidateId,
1259
+ * attachment_id: attachmentId
1260
+ * });
1261
+ * ```
1262
+ *
1263
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/delete-attachments.html
800
1264
  */
801
1265
  function zohoRecruitDeleteAttachmentFromRecord(context) {
802
1266
  return (input) => context.fetch(`/v2/${input.module}/${input.id}/${ZOHO_RECRUIT_ATTACHMENTS_MODULE}/${input.attachment_id}`, { method: 'DELETE' });
803
1267
  }
1268
+ /**
1269
+ * Thrown when a Zoho Recruit serverless function execution fails, wrapping the error response with the API error code and message.
1270
+ */
804
1271
  class ZohoRecruitExecuteRestApiFunctionError extends BaseError {
805
1272
  error;
806
1273
  constructor(error) {
@@ -809,15 +1276,39 @@ class ZohoRecruitExecuteRestApiFunctionError extends BaseError {
809
1276
  }
810
1277
  }
811
1278
  /**
812
- * Creates a ZohoRecruitExecuteRestApiFunctionFunction
1279
+ * Creates a function that executes Zoho Recruit serverless functions via the REST API.
1280
+ *
1281
+ * Supports both OAuth-based and API-key-based authentication. When using an API key, a custom target URL can be specified for cross-environment calls.
813
1282
  *
814
1283
  * OAuth Details:
815
1284
  * - https://www.zoho.com/crm/developer/docs/functions/serverless-fn-oauth.html#OAuth2
816
1285
  * - There is no documentation for ZohoRecruit specifically, but it seems to behave the same way
817
1286
  * - You will need the following scopes: ZohoRecruit.functions.execute.READ,ZohoRecruit.functions.execute.CREATE
818
1287
  *
819
- * @param context
820
- * @returns
1288
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1289
+ * @returns Function that executes serverless functions via the REST API
1290
+ * @throws {ZohoRecruitExecuteRestApiFunctionError} If the function execution fails
1291
+ *
1292
+ * @example
1293
+ * ```typescript
1294
+ * const executeFunction = zohoRecruitExecuteRestApiFunction(context);
1295
+ *
1296
+ * // Execute using OAuth credentials:
1297
+ * const result = await executeFunction({ functionName: 'my_function' });
1298
+ *
1299
+ * // Execute with parameters:
1300
+ * const paramResult = await executeFunction({
1301
+ * functionName: 'process_candidate',
1302
+ * params: { candidate_id: '12345', action: 'approve' }
1303
+ * });
1304
+ *
1305
+ * // Execute using an API key (cross-environment):
1306
+ * const apiResult = await executeFunction({
1307
+ * functionName: 'my_function',
1308
+ * apiKey: 'your-api-key',
1309
+ * apiUrl: 'production'
1310
+ * });
1311
+ * ```
821
1312
  */
822
1313
  function zohoRecruitExecuteRestApiFunction(context) {
823
1314
  return (input) => {
@@ -839,9 +1330,15 @@ function zohoRecruitExecuteRestApiFunction(context) {
839
1330
  };
840
1331
  }
841
1332
  // MARK: Util
1333
+ /**
1334
+ * Builds URL search params from input objects, omitting the `module` key since it is used in the URL path rather than query string.
1335
+ */
842
1336
  function zohoRecruitUrlSearchParamsMinusModule(...input) {
843
1337
  return makeUrlSearchParams(input, { omitKeys: 'module' });
844
1338
  }
1339
+ /**
1340
+ * Builds URL search params from input objects, omitting both `id` and `module` keys since they are used in the URL path.
1341
+ */
845
1342
  function zohoRecruitUrlSearchParamsMinusIdAndModule(...input) {
846
1343
  return makeUrlSearchParams(input, { omitKeys: ['id', 'module'] });
847
1344
  }
@@ -849,6 +1346,9 @@ function zohoRecruitUrlSearchParamsMinusIdAndModule(...input) {
849
1346
  * @deprecated use makeUrlSearchParams instead.
850
1347
  */
851
1348
  const zohoRecruitUrlSearchParams = makeUrlSearchParams;
1349
+ /**
1350
+ * Constructs a standard {@link FetchJsonInput} for Zoho Recruit API calls with the given HTTP method and optional body.
1351
+ */
852
1352
  function zohoRecruitApiFetchJsonInput(method, body) {
853
1353
  const result = {
854
1354
  method,
@@ -856,6 +1356,28 @@ function zohoRecruitApiFetchJsonInput(method, body) {
856
1356
  };
857
1357
  return result;
858
1358
  }
1359
+ /**
1360
+ * Separates a change response's entries into success and error arrays based on their status.
1361
+ *
1362
+ * Iterates over the `data` array from a Zoho Recruit change operation response and
1363
+ * partitions entries into `successItems` and `errorItems` based on their `status` field.
1364
+ * The original response is spread into the result, so all original fields remain accessible.
1365
+ *
1366
+ * Used internally by {@link zohoRecruitDeleteRecord} and similar functions to provide
1367
+ * convenient access to separated success/error results.
1368
+ *
1369
+ * @param response - Raw change operation response containing mixed success/error entries
1370
+ * @returns The response augmented with pre-separated `successItems` and `errorItems` arrays
1371
+ *
1372
+ * @example
1373
+ * ```typescript
1374
+ * const rawResponse = await context.fetchJson<ZohoRecruitChangeObjectLikeResponse>(...);
1375
+ * const result = zohoRecruitChangeObjectLikeResponseSuccessAndErrorPairs(rawResponse);
1376
+ *
1377
+ * result.successItems; // entries with status === 'success'
1378
+ * result.errorItems; // entries with non-success status
1379
+ * ```
1380
+ */
859
1381
  function zohoRecruitChangeObjectLikeResponseSuccessAndErrorPairs(response) {
860
1382
  const { data } = response;
861
1383
  const successItems = [];
@@ -875,6 +1397,20 @@ function zohoRecruitChangeObjectLikeResponseSuccessAndErrorPairs(response) {
875
1397
  };
876
1398
  return result;
877
1399
  }
1400
+ /**
1401
+ * Pairs each input record with its corresponding API result and separates them into success and error arrays by status.
1402
+ *
1403
+ * Iterates over the `input` and `results` arrays in parallel, matching each input record
1404
+ * to its positional result. Entries are classified as success or error based on
1405
+ * the result's `status` field matching {@link ZOHO_SUCCESS_STATUS}.
1406
+ *
1407
+ * Used internally by {@link updateRecordLikeFunction} to pair input data with API outcomes
1408
+ * for insert, update, and upsert operations.
1409
+ *
1410
+ * @param input - Array of input records that were submitted to the API
1411
+ * @param results - Array of per-record results returned by the API, positionally aligned with `input`
1412
+ * @returns Object with `successItems` and `errorItems`, each containing paired `{ input, result }` entries
1413
+ */
878
1414
  function zohoRecruitMultiRecordResult(input, results) {
879
1415
  const successItems = [];
880
1416
  const errorItems = [];
@@ -971,10 +1507,9 @@ const executeRestApiFunction = zohoRecruitExecuteRestApiFunction;
971
1507
  /**
972
1508
  * Associates one or more candidates with one or more job openings.
973
1509
  *
974
- * https://www.zoho.com/recruit/developer-guide/apiv2/associate-candidate.html
1510
+ * The result separates "already associated" errors from other errors, allowing callers to treat duplicate associations as non-fatal.
975
1511
  *
976
- * @param context
977
- * @returns
1512
+ * https://www.zoho.com/recruit/developer-guide/apiv2/associate-candidate.html
978
1513
  */
979
1514
  function zohoRecruitAssociateCandidateRecordsWithJobOpenings(context) {
980
1515
  return (input) => context.fetchJson(`/v2/${ZOHO_RECRUIT_CANDIDATES_MODULE}/actions/associate`, zohoRecruitApiFetchJsonInput('PUT', { data: asArray(input) })).then((x) => {
@@ -991,6 +1526,9 @@ function zohoRecruitAssociateCandidateRecordsWithJobOpenings(context) {
991
1526
  };
992
1527
  });
993
1528
  }
1529
+ /**
1530
+ * Searches for records associated with a given candidate or job opening. Returns an empty page result when no records are found.
1531
+ */
994
1532
  function zohoRecruitSearchAssociatedRecords(context) {
995
1533
  return (input) => {
996
1534
  return context.fetchJson(`/v2/${input.module}/${input.id}/associate?${zohoRecruitUrlSearchParamsMinusIdAndModule(input).toString()}`, zohoRecruitApiFetchJsonInput('GET')).then((x) => {
@@ -999,6 +1537,9 @@ function zohoRecruitSearchAssociatedRecords(context) {
999
1537
  });
1000
1538
  };
1001
1539
  }
1540
+ /**
1541
+ * Searches for job openings associated with a specific candidate.
1542
+ */
1002
1543
  function zohoRecruitSearchCandidateAssociatedJobOpeningRecords(context) {
1003
1544
  const searchAssociatedRecordsFactory = zohoRecruitSearchAssociatedRecords(context);
1004
1545
  return (input) => {
@@ -1008,9 +1549,15 @@ function zohoRecruitSearchCandidateAssociatedJobOpeningRecords(context) {
1008
1549
  });
1009
1550
  };
1010
1551
  }
1552
+ /**
1553
+ * Creates a page factory for paginating over job openings associated with a candidate.
1554
+ */
1011
1555
  function zohoRecruitSearchCandidateAssociatedJobOpeningRecordsPageFactory(context) {
1012
1556
  return zohoFetchPageFactory(zohoRecruitSearchCandidateAssociatedJobOpeningRecords(context));
1013
1557
  }
1558
+ /**
1559
+ * Searches for candidates associated with a specific job opening.
1560
+ */
1014
1561
  function zohoRecruitSearchJobOpeningAssociatedCandidateRecords(context, jobOpeningModuleName = ZOHO_RECRUIT_JOB_OPENINGS_MODULE) {
1015
1562
  const searchAssociatedRecordsFactory = zohoRecruitSearchAssociatedRecords(context);
1016
1563
  return (input) => {
@@ -1020,26 +1567,124 @@ function zohoRecruitSearchJobOpeningAssociatedCandidateRecords(context, jobOpeni
1020
1567
  });
1021
1568
  };
1022
1569
  }
1570
+ /**
1571
+ * Creates a page factory for paginating over candidates associated with a job opening.
1572
+ */
1023
1573
  function zohoRecruitSearchJobOpeningAssociatedCandidateRecordsPageFactory(context) {
1024
1574
  return zohoFetchPageFactory(zohoRecruitSearchJobOpeningAssociatedCandidateRecords(context));
1025
1575
  }
1026
1576
 
1577
+ /**
1578
+ * Creates a {@link ZohoRecruitCreateNotesFunction} bound to the given context.
1579
+ *
1580
+ * Creates one or more notes directly in the Notes module. Each note entry must include
1581
+ * `se_module` and `Parent_Id` to link the note to a record.
1582
+ *
1583
+ * Prefer {@link zohoRecruitCreateNotesForRecord} when creating notes linked to a specific
1584
+ * record, as it handles the module/parent linking automatically.
1585
+ *
1586
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1587
+ * @returns Function that creates notes in the Notes module
1588
+ *
1589
+ * @example
1590
+ * ```typescript
1591
+ * const createNotes = zohoRecruitCreateNotes(context);
1592
+ *
1593
+ * const result = await createNotes({
1594
+ * data: [{
1595
+ * Note_Title: 'Interview Notes',
1596
+ * Note_Content: 'Strong candidate',
1597
+ * se_module: ZOHO_RECRUIT_CANDIDATES_MODULE,
1598
+ * Parent_Id: candidateId
1599
+ * }]
1600
+ * });
1601
+ * ```
1602
+ */
1027
1603
  function zohoRecruitCreateNotes(context) {
1028
1604
  return (input) => context.fetchJson(`/v2/${ZOHO_RECRUIT_NOTES_MODULE}`, zohoRecruitApiFetchJsonInput('POST', { data: input.data })).then((x) => {
1029
1605
  return zohoRecruitMultiRecordResult(asArray(input.data), x.data);
1030
1606
  });
1031
1607
  }
1608
+ /**
1609
+ * Creates a {@link ZohoRecruitDeleteNotesFunction} bound to the given context.
1610
+ *
1611
+ * Deletes one or more notes by their IDs from the Notes module. Returns a paired
1612
+ * success/error result for each note ID.
1613
+ *
1614
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1615
+ * @returns Function that deletes notes by ID
1616
+ *
1617
+ * @example
1618
+ * ```typescript
1619
+ * const deleteNotes = zohoRecruitDeleteNotes(context);
1620
+ *
1621
+ * const result = await deleteNotes({ ids: [noteId1, noteId2] });
1622
+ * ```
1623
+ */
1032
1624
  function zohoRecruitDeleteNotes(context) {
1033
1625
  return (input) => context.fetchJson(`/v2/${ZOHO_RECRUIT_NOTES_MODULE}?${makeUrlSearchParams({ ids: input.ids })}`, zohoRecruitApiFetchJsonInput('DELETE')).then((x) => {
1034
1626
  return zohoRecruitMultiRecordResult(asArray(input.ids), x.data);
1035
1627
  });
1036
1628
  }
1629
+ /**
1630
+ * Creates a {@link ZohoRecruitGetNotesForRecordFunction} bound to the given context.
1631
+ *
1632
+ * Retrieves notes related to a specific record by targeting the Notes module
1633
+ * via the related records API. Returns an empty page result when no notes exist.
1634
+ *
1635
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1636
+ * @returns Function that retrieves notes for a record
1637
+ *
1638
+ * @example
1639
+ * ```typescript
1640
+ * const getNotesForRecord = zohoRecruitGetNotesForRecord(context);
1641
+ *
1642
+ * const result = await getNotesForRecord({
1643
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
1644
+ * id: candidateId
1645
+ * });
1646
+ * ```
1647
+ */
1037
1648
  function zohoRecruitGetNotesForRecord(context) {
1038
1649
  return zohoRecruitGetRelatedRecordsFunctionFactory(context)({ targetModule: ZOHO_RECRUIT_NOTES_MODULE });
1039
1650
  }
1651
+ /**
1652
+ * Creates a {@link ZohoRecruitGetNotesForRecordPageFactory} bound to the given context.
1653
+ *
1654
+ * Returns a page factory for iterating over notes related to a record across
1655
+ * multiple pages. Wraps {@link zohoRecruitGetNotesForRecord} with automatic
1656
+ * pagination handling via {@link zohoFetchPageFactory}.
1657
+ *
1658
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1659
+ * @returns Page factory for iterating over record notes
1660
+ */
1040
1661
  function zohoRecruitGetNotesForRecordPageFactory(context) {
1041
1662
  return zohoFetchPageFactory(zohoRecruitGetNotesForRecord(context));
1042
1663
  }
1664
+ /**
1665
+ * Creates a {@link ZohoRecruitCreateNotesForRecordFunction} bound to the given context.
1666
+ *
1667
+ * Creates one or more notes linked to a specific record. Automatically sets
1668
+ * `se_module` and `Parent_Id` on each note entry from the request's `module` and `id`,
1669
+ * then delegates to {@link zohoRecruitCreateNotes}.
1670
+ *
1671
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1672
+ * @returns Function that creates notes linked to a specific record
1673
+ *
1674
+ * @example
1675
+ * ```typescript
1676
+ * const createNotesForRecord = zohoRecruitCreateNotesForRecord(context);
1677
+ *
1678
+ * const result = await createNotesForRecord({
1679
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
1680
+ * id: candidateId,
1681
+ * notes: {
1682
+ * Note_Title: 'Interview Notes',
1683
+ * Note_Content: 'Strong candidate for the role.'
1684
+ * }
1685
+ * });
1686
+ * ```
1687
+ */
1043
1688
  function zohoRecruitCreateNotesForRecord(context) {
1044
1689
  const createNotesInstance = zohoRecruitCreateNotes(context);
1045
1690
  return (input) => {
@@ -1076,6 +1721,30 @@ const getNotesForRecordPageFactory = zohoRecruitGetNotesForRecordPageFactory;
1076
1721
  */
1077
1722
  const createNotesForRecord = zohoRecruitCreateNotesForRecord;
1078
1723
 
1724
+ /**
1725
+ * Creates a {@link ZohoRecruitCreateTagsFunction} bound to the given context.
1726
+ *
1727
+ * Creates one or more tags for a module. The result separates duplicate tag errors
1728
+ * (which are often non-fatal) from other errors, making it easy to handle the common
1729
+ * case where a tag already exists.
1730
+ *
1731
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1732
+ * @returns Function that creates tags in the specified module
1733
+ *
1734
+ * @example
1735
+ * ```typescript
1736
+ * const createTags = zohoRecruitCreateTagsForModule(context);
1737
+ *
1738
+ * const result = await createTags({
1739
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
1740
+ * tags: [{ name: 'Interviewed' }, { name: 'Priority', color_code: '#FF0000' }]
1741
+ * });
1742
+ *
1743
+ * // Duplicate tags are separated for convenience:
1744
+ * result.duplicateErrorItems; // tags that already existed
1745
+ * result.errorItems; // other (real) errors
1746
+ * ```
1747
+ */
1079
1748
  function zohoRecruitCreateTagsForModule(context) {
1080
1749
  return (input) => context.fetchJson(`/v2/settings/tags?${makeUrlSearchParams({ module: input.module })}`, zohoRecruitApiFetchJsonInput('POST', { tags: asArray(input.tags) })).then((x) => {
1081
1750
  const result = zohoRecruitMultiRecordResult(asArray(input.tags), x.tags);
@@ -1091,12 +1760,24 @@ function zohoRecruitCreateTagsForModule(context) {
1091
1760
  });
1092
1761
  }
1093
1762
  /**
1094
- * Returns the list of tags within a module.
1763
+ * Creates a {@link ZohoRecruitGetTagsFunction} bound to the given context.
1095
1764
  *
1096
- * https://www.zoho.com/recruit/developer-guide/apiv2/get-tag-list.html
1765
+ * Returns the list of tags within a module. Normalizes the non-standard API response
1766
+ * that returns data under a `tags` key instead of the standard `data` key.
1097
1767
  *
1098
- * @param context
1099
- * @returns
1768
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1769
+ * @returns Function that retrieves tags for a module
1770
+ *
1771
+ * @example
1772
+ * ```typescript
1773
+ * const getTags = zohoRecruitGetTagsForModule(context);
1774
+ *
1775
+ * const result = await getTags({
1776
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE
1777
+ * });
1778
+ * ```
1779
+ *
1780
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/get-tag-list.html
1100
1781
  */
1101
1782
  function zohoRecruitGetTagsForModule(context) {
1102
1783
  return (input) => context.fetchJson(`/v2/settings/tags?${makeUrlSearchParams({ module: input.module, my_tags: input.my_tags })}`, zohoRecruitApiFetchJsonInput('GET')).then((x) => {
@@ -1107,21 +1788,45 @@ function zohoRecruitGetTagsForModule(context) {
1107
1788
  };
1108
1789
  });
1109
1790
  }
1791
+ /**
1792
+ * Creates a {@link ZohoRecruitGetTagsForModulePageFactory} bound to the given context.
1793
+ *
1794
+ * Returns a page factory for iterating over tags in a module across multiple pages.
1795
+ * Wraps {@link zohoRecruitGetTagsForModule} with automatic pagination handling.
1796
+ *
1797
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1798
+ * @returns Page factory for iterating over module tags
1799
+ */
1110
1800
  function zohoRecruitGetTagsForModulePageFactory(context) {
1111
1801
  return zohoFetchPageFactory(zohoRecruitGetTagsForModule(context));
1112
1802
  }
1113
1803
  // MARK: Add Tag To Record
1114
1804
  /**
1115
- * Limit enforced by Zoho Recruit
1805
+ * Maximum number of record ids allowed when adding tags, enforced by the Zoho Recruit API.
1116
1806
  */
1117
1807
  const ZOHO_RECRUIT_ADD_TAGS_TO_RECORDS_MAX_IDS_ALLOWED = 100;
1118
1808
  /**
1119
- * Adds one or more tags to one or more records.
1809
+ * Creates a {@link ZohoRecruitAddTagsToRecordsFunction} bound to the given context.
1120
1810
  *
1121
- * https://www.zoho.com/recruit/developer-guide/apiv2/add-tags.html
1811
+ * Adds one or more tags to one or more records. Returns a paired success/error result
1812
+ * for each record. Maximum of {@link ZOHO_RECRUIT_ADD_TAGS_TO_RECORDS_MAX_IDS_ALLOWED} (100) record IDs per call.
1122
1813
  *
1123
- * @param context
1124
- * @returns
1814
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1815
+ * @returns Function that adds tags to records
1816
+ * @throws {Error} If more than 100 record IDs are provided
1817
+ *
1818
+ * @example
1819
+ * ```typescript
1820
+ * const addTags = zohoRecruitAddTagsToRecords(context);
1821
+ *
1822
+ * const result = await addTags({
1823
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
1824
+ * tag_names: ['Interviewed', 'Priority'],
1825
+ * ids: [candidateId1, candidateId2]
1826
+ * });
1827
+ * ```
1828
+ *
1829
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/add-tags.html
1125
1830
  */
1126
1831
  function zohoRecruitAddTagsToRecords(context) {
1127
1832
  return (input) => {
@@ -1136,16 +1841,31 @@ function zohoRecruitAddTagsToRecords(context) {
1136
1841
  }
1137
1842
  // MARK: Remove Tag From Record
1138
1843
  /**
1139
- * Limit enforced by Zoho Recruit
1844
+ * Maximum number of record ids allowed when removing tags, enforced by the Zoho Recruit API.
1140
1845
  */
1141
1846
  const ZOHO_RECRUIT_REMOVE_TAGS_FROM_RECORDS_MAX_IDS_ALLOWED = 100;
1142
1847
  /**
1143
- * Removes one or more tags from one or more records.
1848
+ * Creates a {@link ZohoRecruitRemoveTagsFromRecordsFunction} bound to the given context.
1144
1849
  *
1145
- * https://www.zoho.com/recruit/developer-guide/apiv2/remove-tags.html
1850
+ * Removes one or more tags from one or more records. Returns a paired success/error result
1851
+ * for each record. Maximum of {@link ZOHO_RECRUIT_REMOVE_TAGS_FROM_RECORDS_MAX_IDS_ALLOWED} (100) record IDs per call.
1146
1852
  *
1147
- * @param context
1148
- * @returns
1853
+ * @param context - Authenticated Zoho Recruit context providing fetch and rate limiting
1854
+ * @returns Function that removes tags from records
1855
+ * @throws {Error} If more than 100 record IDs are provided
1856
+ *
1857
+ * @example
1858
+ * ```typescript
1859
+ * const removeTags = zohoRecruitRemoveTagsFromRecords(context);
1860
+ *
1861
+ * const result = await removeTags({
1862
+ * module: ZOHO_RECRUIT_CANDIDATES_MODULE,
1863
+ * tag_names: 'Interviewed',
1864
+ * ids: candidateId
1865
+ * });
1866
+ * ```
1867
+ *
1868
+ * @see https://www.zoho.com/recruit/developer-guide/apiv2/remove-tags.html
1149
1869
  */
1150
1870
  function zohoRecruitRemoveTagsFromRecords(context) {
1151
1871
  return (input) => {
@@ -1248,16 +1968,39 @@ function zohoAccessTokenStringFactory(zohoAccessTokenFactory) {
1248
1968
  };
1249
1969
  }
1250
1970
 
1971
+ /**
1972
+ * Default handler that logs a warning to the console when the Zoho API rate limit is exceeded.
1973
+ */
1251
1974
  const DEFAULT_ZOHO_RATE_LIMITED_TOO_MANY_REQUETS_LOG_FUNCTION = (headers) => {
1252
1975
  console.warn(`zohoRateLimitedFetchHandler(): Too many requests made. The limit is ${headers.limit} requests per reset period. Will be reset at ${headers.resetAt}.`);
1253
1976
  };
1977
+ /**
1978
+ * Creates a {@link ZohoRateLimitedFetchHandler} that throttles outgoing requests based on
1979
+ * Zoho's rate limit headers (`X-RATELIMIT-LIMIT`, `X-RATELIMIT-REMAINING`, `X-RATELIMIT-RESET`).
1980
+ *
1981
+ * The handler uses an exponential backoff strategy with the following behavior:
1982
+ * - Rate limiting begins after 10% of the allowed requests have been made (`startLimitAt`)
1983
+ * - Wait times increase exponentially (rate 1.08) as more requests are made, capped at 10 seconds
1984
+ * - On each response, the limiter updates its remaining count and reset time from headers
1985
+ * - When the API returns a different limit than configured, the limiter dynamically adjusts
1986
+ * - On 429 responses, the request is automatically retried after the rate limiter delay
1987
+ * - The limiter is disabled when responses lack rate limit headers (e.g., error responses)
1988
+ *
1989
+ * @param config - Optional configuration for rate limit, reset period, and 429 handling
1990
+ * @returns A rate-limited fetch handler with the underlying rate limiter accessible via `_rateLimiter`
1991
+ */
1254
1992
  function zohoRateLimitedFetchHandler(config) {
1255
1993
  const onTooManyRequests = config?.onTooManyRequests !== false ? (config?.onTooManyRequests ?? DEFAULT_ZOHO_RATE_LIMITED_TOO_MANY_REQUETS_LOG_FUNCTION) : undefined;
1256
1994
  const defaultLimit = config?.maxRateLimit ?? DEFAULT_ZOHO_API_RATE_LIMIT;
1257
1995
  const defaultResetPeriod = config?.resetPeriod ?? DEFAULT_ZOHO_API_RATE_LIMIT_RESET_PERIOD;
1996
+ /**
1997
+ * Builds a rate limiter config derived from the given limit.
1998
+ * Called once at initialization with `defaultLimit`, and again dynamically
1999
+ * when the API's `X-RATELIMIT-LIMIT` header reports a different value.
2000
+ */
1258
2001
  function configForLimit(limit, resetAt) {
1259
2002
  return {
1260
- limit: defaultLimit,
2003
+ limit,
1261
2004
  startLimitAt: Math.ceil(limit / 10), // can do 10% of the requests of the limit before rate limiting begins
1262
2005
  cooldownRate: 1.2 * (limit / (defaultResetPeriod / MS_IN_SECOND)),
1263
2006
  exponentRate: 1.08,
@@ -1270,6 +2013,10 @@ function zohoRateLimitedFetchHandler(config) {
1270
2013
  const rateLimiter = resetPeriodPromiseRateLimiter(defaultConfig);
1271
2014
  return rateLimitedFetchHandler({
1272
2015
  rateLimiter,
2016
+ /**
2017
+ * Inspects each response for Zoho rate limit headers and updates the limiter accordingly.
2018
+ * Returns `true` to signal a retry when a 429 status is received.
2019
+ */
1273
2020
  updateWithResponse: function (response, fetchResponseError) {
1274
2021
  const hasLimitHeader = response.headers.has(ZOHO_RATE_LIMIT_REMAINING_HEADER);
1275
2022
  let shouldRetry = false;
@@ -1278,7 +2025,7 @@ function zohoRateLimitedFetchHandler(config) {
1278
2025
  const headerDetails = zohoRateLimitHeaderDetails(response.headers);
1279
2026
  if (headerDetails) {
1280
2027
  const { limit, resetAt, remaining } = headerDetails;
1281
- if (limit !== defaultLimit) {
2028
+ if (limit && limit !== defaultLimit) {
1282
2029
  const newConfig = configForLimit(limit, resetAt);
1283
2030
  rateLimiter.setConfig(newConfig, false);
1284
2031
  }
@@ -1301,6 +2048,31 @@ function zohoRateLimitedFetchHandler(config) {
1301
2048
  });
1302
2049
  }
1303
2050
 
2051
+ /**
2052
+ * Creates a {@link ZohoRecruitFactory} from the given configuration.
2053
+ *
2054
+ * The factory pre-initializes shared resources (access token provider, rate limiter)
2055
+ * once, then produces {@link ZohoRecruit} client instances for each {@link ZohoRecruitConfig}.
2056
+ * Each client handles OAuth token refresh on {@link ZohoInvalidTokenError}, rate limiting,
2057
+ * and Zoho Recruit's non-standard error responses (200 status with error body).
2058
+ *
2059
+ * @param factoryConfig - Configuration providing account credentials and optional overrides
2060
+ * @returns A factory function that creates authenticated Zoho Recruit clients
2061
+ *
2062
+ * @example
2063
+ * ```typescript
2064
+ * const factory = zohoRecruitFactory({
2065
+ * accountsContext: zohoAccountsApi.accountsContext
2066
+ * });
2067
+ *
2068
+ * const zohoRecruit = factory({
2069
+ * apiUrl: 'https://recruit.zoho.com'
2070
+ * });
2071
+ *
2072
+ * // Use the recruit context for API calls:
2073
+ * const { recruitContext } = zohoRecruit;
2074
+ * ```
2075
+ */
1304
2076
  function zohoRecruitFactory(factoryConfig) {
1305
2077
  const { accountsContext } = factoryConfig;
1306
2078
  const accessTokenStringFactory = zohoAccessTokenStringFactory(accountsContext.loadAccessToken);
@@ -1355,7 +2127,13 @@ function zohoRecruitFactory(factoryConfig) {
1355
2127
  */
1356
2128
  const ZOHO_RECRUIT_TAG_NAME_MAX_LENGTH = 25;
1357
2129
 
2130
+ /**
2131
+ * Service identifier used for Zoho CRM API access token resolution and service routing.
2132
+ */
1358
2133
  const ZOHO_CRM_SERVICE_NAME = 'crm';
2134
+ /**
2135
+ * Resolves a CRM API URL input to its full base URL. Well-known keys ('sandbox', 'production') map to their respective Zoho CRM endpoints; custom URLs pass through unchanged.
2136
+ */
1359
2137
  function zohoCrmConfigApiUrl(input) {
1360
2138
  switch (input) {
1361
2139
  case 'sandbox':
@@ -1398,16 +2176,41 @@ const isZohoCrmValidUrl = isStandardInternetAccessibleWebsiteUrl;
1398
2176
  const ZOHO_CRM_RECORD_ATTACHMENT_METADATA_ATTACH_TYPE_RESUME = 'Resume';
1399
2177
 
1400
2178
  /**
1401
- * Can search up to 10 criteria at a time.
1402
- *
1403
- * https://www.zoho.com/crm/developer/docs/api/v8/search-records.html
2179
+ * Maximum number of criteria allowed per CRM search query (10).
1404
2180
  *
1405
2181
  * "You can search for a maximum of 10 criteria (with the same or different column) with equals and starts_with conditions."
2182
+ *
2183
+ * Re-exports {@link MAX_ZOHO_SEARCH_MODULE_RECORDS_CRITERIA}.
2184
+ *
2185
+ * @see https://www.zoho.com/crm/developer/docs/api/v8/search-records.html
1406
2186
  */
1407
2187
  const MAX_ZOHO_CRM_SEARCH_MODULE_RECORDS_CRITERIA = MAX_ZOHO_SEARCH_MODULE_RECORDS_CRITERIA;
2188
+ /**
2189
+ * CRM-specific re-export of {@link zohoSearchRecordsCriteriaString}.
2190
+ *
2191
+ * Compiles a criteria tree element (entry array, nested tree, or raw string) into a
2192
+ * URL-ready criteria string. Returns `undefined` when the input is nullish or empty.
2193
+ */
1408
2194
  const zohoCrmSearchRecordsCriteriaString = zohoSearchRecordsCriteriaString;
2195
+ /**
2196
+ * CRM-specific re-export of {@link zohoSearchRecordsCriteriaStringForTree}.
2197
+ *
2198
+ * Compiles a {@link ZohoCrmSearchRecordsCriteriaTree} into a criteria string by
2199
+ * recursively resolving nested AND/OR groups.
2200
+ */
1409
2201
  const zohoCrmSearchRecordsCriteriaStringForTree = zohoSearchRecordsCriteriaStringForTree;
2202
+ /**
2203
+ * CRM-specific re-export of {@link escapeZohoFieldValueForCriteriaString}.
2204
+ *
2205
+ * Escapes parentheses and commas in a field value for use in a criteria string.
2206
+ */
1410
2207
  const escapeZohoCrmFieldValueForCriteriaString = escapeZohoFieldValueForCriteriaString;
2208
+ /**
2209
+ * CRM-specific re-export of {@link zohoSearchRecordsCriteriaEntryToCriteriaString}.
2210
+ *
2211
+ * Converts a single criteria entry into a parenthesized criteria string
2212
+ * in the format `(field:filter:escapedValue)`.
2213
+ */
1411
2214
  const zohoCrmSearchRecordsCriteriaEntryToCriteriaString = zohoSearchRecordsCriteriaEntryToCriteriaString;
1412
2215
 
1413
2216
  /**
@@ -1428,19 +2231,37 @@ class ZohoCrmRecordNoContentError extends BaseError {
1428
2231
  this.recordId = recordId;
1429
2232
  }
1430
2233
  }
2234
+ /**
2235
+ * Base error for Zoho CRM record CRUD operations. Wraps the server-provided error data including field-level details.
2236
+ */
1431
2237
  class ZohoCrmRecordCrudError extends ZohoServerError {
1432
2238
  }
2239
+ /**
2240
+ * Thrown when a CRM create/update request is missing a required field defined in the module layout.
2241
+ */
1433
2242
  class ZohoCrmRecordCrudMandatoryFieldNotFoundError extends ZohoCrmRecordCrudError {
1434
2243
  }
2244
+ /**
2245
+ * Thrown when a CRM create/update request would produce a duplicate record based on unique field constraints.
2246
+ */
1435
2247
  class ZohoCrmRecordCrudDuplicateDataError extends ZohoCrmRecordCrudError {
1436
2248
  }
2249
+ /**
2250
+ * Thrown when a CRM request contains invalid field data. Provides access to the offending field details via {@link invalidFieldDetails}.
2251
+ */
1437
2252
  class ZohoCrmRecordCrudInvalidDataError extends ZohoCrmRecordCrudError {
1438
2253
  get invalidFieldDetails() {
1439
2254
  return this.error.details;
1440
2255
  }
1441
2256
  }
2257
+ /**
2258
+ * Thrown when an invalid data error targets the 'id' field, indicating the referenced record does not exist.
2259
+ */
1442
2260
  class ZohoCrmRecordCrudNoMatchingRecordError extends ZohoCrmRecordCrudInvalidDataError {
1443
2261
  }
2262
+ /**
2263
+ * Maps a Zoho CRM server error to the appropriate typed {@link ZohoCrmRecordCrudError} subclass based on the error code and affected field.
2264
+ */
1444
2265
  function zohoCrmRecordCrudError(error) {
1445
2266
  let result;
1446
2267
  switch (error.code) {
@@ -1465,6 +2286,9 @@ function zohoCrmRecordCrudError(error) {
1465
2286
  }
1466
2287
  return result;
1467
2288
  }
2289
+ /**
2290
+ * Returns an assertion function that throws {@link ZohoCrmRecordNoContentError} when a data array result is empty or null, typically indicating a missing record.
2291
+ */
1468
2292
  function assertZohoCrmRecordDataArrayResultHasContent(moduleName, recordId) {
1469
2293
  return (x) => {
1470
2294
  if (x == null || !x.data?.length) {
@@ -1475,7 +2299,13 @@ function assertZohoCrmRecordDataArrayResultHasContent(moduleName, recordId) {
1475
2299
  }
1476
2300
  };
1477
2301
  }
2302
+ /**
2303
+ * Pre-configured console logger for Zoho CRM server errors. Data array errors are suppressed since they are handled separately by CRUD error classes.
2304
+ */
1478
2305
  const logZohoCrmErrorToConsole = logZohoServerErrorFunction('ZohoCrm', { logDataArrayErrors: false });
2306
+ /**
2307
+ * Parses a fetch response error into a typed Zoho CRM server error by extracting and interpreting the JSON error body.
2308
+ */
1479
2309
  async function parseZohoCrmError(responseError) {
1480
2310
  const data = await responseError.response.json().catch((x) => undefined);
1481
2311
  let result;
@@ -1484,6 +2314,9 @@ async function parseZohoCrmError(responseError) {
1484
2314
  }
1485
2315
  return result;
1486
2316
  }
2317
+ /**
2318
+ * Parses a Zoho CRM error response body into a typed error. Delegates to CRM-specific error code handling before falling back to the generic Zoho error parser.
2319
+ */
1487
2320
  function parseZohoCrmServerErrorResponseData(errorResponseData, responseError) {
1488
2321
  let result;
1489
2322
  const error = tryFindZohoServerErrorData(errorResponseData, responseError);
@@ -1498,7 +2331,13 @@ function parseZohoCrmServerErrorResponseData(errorResponseData, responseError) {
1498
2331
  }
1499
2332
  return result;
1500
2333
  }
2334
+ /**
2335
+ * Fetch response interceptor that detects Zoho CRM error payloads hidden within HTTP 200 responses and converts them into proper errors.
2336
+ */
1501
2337
  const interceptZohoCrm200StatusWithErrorResponse = interceptZohoErrorResponseFactory(parseZohoCrmServerErrorResponseData);
2338
+ /**
2339
+ * Wraps a fetch function with Zoho CRM error parsing and console logging, ensuring all CRM API errors are surfaced as typed exceptions.
2340
+ */
1502
2341
  const handleZohoCrmErrorFetch = handleZohoErrorFetchFactory(parseZohoCrmError, logZohoCrmErrorToConsole);
1503
2342
 
1504
2343
  // MARK: Insert/Update/Upsert Response
@@ -1509,9 +2348,10 @@ const handleZohoCrmErrorFetch = handleZohoErrorFetchFactory(parseZohoCrmError, l
1509
2348
  */
1510
2349
  const ZOHO_CRM_CRUD_FUNCTION_MAX_RECORDS_LIMIT = 100;
1511
2350
  /**
1512
- * The APIs for Insert, Upsert, and Update have the same structure.
2351
+ * Shared implementation for the Insert, Upsert, and Update endpoints, which all share the same request/response structure.
1513
2352
  *
1514
- * @returns
2353
+ * When a single record is provided, the function returns the change details directly or throws on error.
2354
+ * When multiple records are provided, it returns a paired success/error result.
1515
2355
  */
1516
2356
  function updateRecordLikeFunction(context, fetchUrlPrefix, fetchMethod) {
1517
2357
  return (({ data, module }) => context
@@ -1535,45 +2375,145 @@ function updateRecordLikeFunction(context, fetchUrlPrefix, fetchMethod) {
1535
2375
  }));
1536
2376
  }
1537
2377
  /**
1538
- * Inserts one or more records into Crm.
2378
+ * Creates a {@link ZohoCrmInsertRecordFunction} bound to the given context.
1539
2379
  *
1540
- * https://www.zoho.com/crm/developer-guide/apiv2/insert-records.html
2380
+ * Inserts one or more records into a CRM module. When a single record is
2381
+ * provided, returns the {@link ZohoCrmChangeObjectDetails} directly or
2382
+ * throws on error. When multiple records are provided, returns a
2383
+ * {@link ZohoCrmUpdateRecordResult} with paired success/error arrays.
1541
2384
  *
1542
- * @param context
1543
- * @returns
2385
+ * Maximum of {@link ZOHO_CRM_CRUD_FUNCTION_MAX_RECORDS_LIMIT} records per call.
2386
+ *
2387
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2388
+ * @returns Function that inserts records into the specified module
2389
+ *
2390
+ * @example
2391
+ * ```typescript
2392
+ * const insertRecord = zohoCrmInsertRecord(context);
2393
+ *
2394
+ * // Single record — returns details directly or throws on error:
2395
+ * const details = await insertRecord({
2396
+ * module: 'Contacts',
2397
+ * data: { First_Name: 'Jane', Last_Name: 'Doe', Email: 'jane@example.com' }
2398
+ * });
2399
+ *
2400
+ * // Multiple records — returns paired success/error arrays:
2401
+ * const result = await insertRecord({
2402
+ * module: 'Contacts',
2403
+ * data: [
2404
+ * { First_Name: 'Jane', Last_Name: 'Doe', Email: 'jane@example.com' },
2405
+ * { First_Name: 'John', Last_Name: 'Doe', Email: 'john@example.com' }
2406
+ * ]
2407
+ * });
2408
+ * ```
2409
+ *
2410
+ * @see https://www.zoho.com/crm/developer-guide/apiv2/insert-records.html
1544
2411
  */
1545
2412
  function zohoCrmInsertRecord(context) {
1546
2413
  return updateRecordLikeFunction(context, '', 'POST');
1547
2414
  }
1548
2415
  /**
1549
- * Updates or inserts one or more records in Crm.
2416
+ * Creates a {@link ZohoCrmUpsertRecordFunction} bound to the given context.
1550
2417
  *
1551
- * https://www.zoho.com/crm/developer-guide/apiv2/upsert-records.html
2418
+ * Inserts or updates one or more records in a CRM module based on whether
2419
+ * each record includes an `id`. Uses the `/upsert` endpoint. Single-record
2420
+ * calls return details directly or throw; multi-record calls return paired
2421
+ * success/error arrays.
1552
2422
  *
1553
- * @param context
1554
- * @returns
2423
+ * Maximum of {@link ZOHO_CRM_CRUD_FUNCTION_MAX_RECORDS_LIMIT} records per call.
2424
+ *
2425
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2426
+ * @returns Function that upserts records in the specified module
2427
+ *
2428
+ * @example
2429
+ * ```typescript
2430
+ * const upsertRecord = zohoCrmUpsertRecord(context);
2431
+ *
2432
+ * // Create (no id) — returns details directly:
2433
+ * const created = await upsertRecord({
2434
+ * module: 'Contacts',
2435
+ * data: { Email: 'new@example.com', Last_Name: 'New' }
2436
+ * });
2437
+ *
2438
+ * // Update (with id) — returns details directly:
2439
+ * const updated = await upsertRecord({
2440
+ * module: 'Contacts',
2441
+ * data: { id: existingId, First_Name: 'Updated' }
2442
+ * });
2443
+ *
2444
+ * // Mixed create and update — returns paired arrays:
2445
+ * const result = await upsertRecord({
2446
+ * module: 'Contacts',
2447
+ * data: [
2448
+ * { Email: 'create@example.com', Last_Name: 'Create' },
2449
+ * { id: existingId, First_Name: 'Update' }
2450
+ * ]
2451
+ * });
2452
+ * ```
2453
+ *
2454
+ * @see https://www.zoho.com/crm/developer-guide/apiv2/upsert-records.html
1555
2455
  */
1556
2456
  function zohoCrmUpsertRecord(context) {
1557
2457
  return updateRecordLikeFunction(context, '/upsert', 'POST');
1558
2458
  }
1559
2459
  /**
1560
- * Updates one or more records in Crm.
2460
+ * Creates a {@link ZohoCrmUpdateRecordFunction} bound to the given context.
1561
2461
  *
1562
- * https://www.zoho.com/crm/developer-guide/apiv2/update-records.html
2462
+ * Updates one or more existing records in a CRM module. Each record must
2463
+ * include an `id` field. Single-record calls return details directly or throw;
2464
+ * multi-record calls return paired success/error arrays.
1563
2465
  *
1564
- * @param context
1565
- * @returns
2466
+ * Maximum of {@link ZOHO_CRM_CRUD_FUNCTION_MAX_RECORDS_LIMIT} records per call.
2467
+ *
2468
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2469
+ * @returns Function that updates records in the specified module
2470
+ *
2471
+ * @example
2472
+ * ```typescript
2473
+ * const updateRecord = zohoCrmUpdateRecord(context);
2474
+ *
2475
+ * // Single record — returns details directly:
2476
+ * const details = await updateRecord({
2477
+ * module: 'Contacts',
2478
+ * data: { id: recordId, First_Name: 'Updated Name' }
2479
+ * });
2480
+ *
2481
+ * // Multiple records — returns paired arrays:
2482
+ * const result = await updateRecord({
2483
+ * module: 'Contacts',
2484
+ * data: [
2485
+ * { id: recordId1, First_Name: 'Updated 1' },
2486
+ * { id: recordId2, First_Name: 'Updated 2' }
2487
+ * ]
2488
+ * });
2489
+ * ```
2490
+ *
2491
+ * @see https://www.zoho.com/crm/developer-guide/apiv2/update-records.html
1566
2492
  */
1567
2493
  function zohoCrmUpdateRecord(context) {
1568
2494
  return updateRecordLikeFunction(context, '', 'PUT');
1569
2495
  }
1570
2496
  /**
1571
- * Deletes one or more records from the given module.
2497
+ * Creates a {@link ZohoCrmDeleteRecordFunction} bound to the given context.
1572
2498
  *
1573
- * https://www.zoho.com/crm/developer-guide/apiv2/delete-records.html
2499
+ * Deletes one or more records from a CRM module by their IDs. Supports
2500
+ * an optional `wf_trigger` flag to execute workflow rules on deletion. Returns
2501
+ * a response with separated success and error entries.
1574
2502
  *
1575
- * @param context
1576
- * @returns ZohoCrmDeleteRecordFunction
2503
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2504
+ * @returns Function that deletes records from the specified module
2505
+ *
2506
+ * @example
2507
+ * ```typescript
2508
+ * const deleteRecord = zohoCrmDeleteRecord(context);
2509
+ *
2510
+ * const result = await deleteRecord({
2511
+ * module: 'Contacts',
2512
+ * ids: contactId
2513
+ * });
2514
+ * ```
2515
+ *
2516
+ * @see https://www.zoho.com/crm/developer-guide/apiv2/delete-records.html
1577
2517
  */
1578
2518
  function zohoCrmDeleteRecord(context) {
1579
2519
  return ({ ids, module, wf_trigger }) => {
@@ -1584,12 +2524,26 @@ function zohoCrmDeleteRecord(context) {
1584
2524
  };
1585
2525
  }
1586
2526
  /**
1587
- * Retrieves a specific record from the given module.
2527
+ * Creates a {@link ZohoCrmGetRecordByIdFunction} bound to the given context.
1588
2528
  *
1589
- * https://www.zoho.com/crm/developer-guide/apiv2/get-records.html
2529
+ * Retrieves a single record from a CRM module by its ID. The response is
2530
+ * unwrapped from the standard data array, returning the record directly.
2531
+ * Throws if the record is not found.
1590
2532
  *
1591
- * @param context
1592
- * @returns
2533
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2534
+ * @returns Function that retrieves a record by module name and ID
2535
+ *
2536
+ * @example
2537
+ * ```typescript
2538
+ * const getRecordById = zohoCrmGetRecordById(context);
2539
+ *
2540
+ * const record = await getRecordById({
2541
+ * module: 'Contacts',
2542
+ * id: contactId
2543
+ * });
2544
+ * ```
2545
+ *
2546
+ * @see https://www.zoho.com/crm/developer-guide/apiv2/get-records.html
1593
2547
  */
1594
2548
  function zohoCrmGetRecordById(context) {
1595
2549
  return (input) => context
@@ -1598,23 +2552,62 @@ function zohoCrmGetRecordById(context) {
1598
2552
  .then((x) => x.data[0]);
1599
2553
  }
1600
2554
  /**
1601
- * Retrieves records from the given module. Used for paginating across all records.
2555
+ * Creates a {@link ZohoCrmGetRecordsFunction} bound to the given context.
1602
2556
  *
1603
- * https://www.zoho.com/crm/developer-guide/apiv2/get-records.html
2557
+ * Retrieves a paginated list of records from a CRM module. Supports field
2558
+ * selection, sorting, custom view filtering, territory filtering, and
2559
+ * conversion/approval status filters via {@link ZohoCrmGetRecordsInput}.
1604
2560
  *
1605
- * @param context
1606
- * @returns
2561
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2562
+ * @returns Function that retrieves paginated records from a module
2563
+ *
2564
+ * @example
2565
+ * ```typescript
2566
+ * const getRecords = zohoCrmGetRecords(context);
2567
+ *
2568
+ * const page = await getRecords({
2569
+ * module: 'Contacts',
2570
+ * fields: 'First_Name,Last_Name,Email',
2571
+ * per_page: 10
2572
+ * });
2573
+ * ```
2574
+ *
2575
+ * @see https://www.zoho.com/crm/developer-guide/apiv2/get-records.html
1607
2576
  */
1608
2577
  function zohoCrmGetRecords(context) {
1609
2578
  return ((input) => context.fetchJson(`/v8/${input.module}?${zohoCrmUrlSearchParamsMinusModule(input).toString()}`, zohoCrmApiFetchJsonInput('GET')));
1610
2579
  }
1611
2580
  /**
1612
- * Searches records from the given module.
2581
+ * Creates a {@link ZohoCrmSearchRecordsFunction} bound to the given context.
1613
2582
  *
1614
- * https://www.zoho.com/crm/developer-guide/apiv2/search-records.html
2583
+ * Searches records in a CRM module using one of: criteria tree (compiled
2584
+ * via {@link zohoCrmSearchRecordsCriteriaString}), email, phone, cvid, or keyword.
2585
+ * At least one search parameter must be provided. Returns a paginated result,
2586
+ * defaulting to an empty data array when no matches are found.
1615
2587
  *
1616
- * @param context
1617
- * @returns
2588
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2589
+ * @returns Function that searches records in the specified module
2590
+ * @throws {Error} If none of `criteria`, `email`, `phone`, `cvid`, or `word` are provided
2591
+ *
2592
+ * @example
2593
+ * ```typescript
2594
+ * const searchRecords = zohoCrmSearchRecords(context);
2595
+ *
2596
+ * // Search by criteria:
2597
+ * const result = await searchRecords({
2598
+ * module: 'Contacts',
2599
+ * criteria: [{ field: 'Last_Name', filter: 'starts_with', value: 'Smith' }],
2600
+ * per_page: 10
2601
+ * });
2602
+ *
2603
+ * // Search by keyword:
2604
+ * const wordResult = await searchRecords({
2605
+ * module: 'Contacts',
2606
+ * word: 'engineer'
2607
+ * });
2608
+ * ```
2609
+ *
2610
+ * @see https://www.zoho.com/crm/developer-guide/apiv2/search-records.html
1618
2611
  */
1619
2612
  function zohoCrmSearchRecords(context) {
1620
2613
  function searchRecordsUrlSearchParams(input) {
@@ -1632,16 +2625,60 @@ function zohoCrmSearchRecords(context) {
1632
2625
  }
1633
2626
  return ((input) => context.fetchJson(`/v8/${input.module}/search?${searchRecordsUrlSearchParams(input).toString()}`, zohoCrmApiFetchJsonInput('GET')).then((x) => x ?? { data: [], info: { more_records: false } }));
1634
2627
  }
2628
+ /**
2629
+ * Creates a {@link ZohoCrmSearchRecordsPageFactory} bound to the given context.
2630
+ *
2631
+ * Returns a page factory that automatically handles Zoho CRM's pagination,
2632
+ * making it easy to iterate through all search results across multiple pages.
2633
+ *
2634
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2635
+ * @returns Page factory for iterating over search results
2636
+ *
2637
+ * @example
2638
+ * ```typescript
2639
+ * const pageFactory = zohoCrmSearchRecordsPageFactory(context);
2640
+ *
2641
+ * const fetchPage = pageFactory({
2642
+ * module: 'Contacts',
2643
+ * criteria: [{ field: 'Last_Name', filter: 'starts_with', value: 'Smith' }],
2644
+ * per_page: 5
2645
+ * });
2646
+ *
2647
+ * const firstPage = await fetchPage.fetchNext();
2648
+ * const secondPage = await firstPage.fetchNext();
2649
+ * ```
2650
+ */
1635
2651
  function zohoCrmSearchRecordsPageFactory(context) {
1636
2652
  return zohoFetchPageFactory(zohoCrmSearchRecords(context));
1637
2653
  }
1638
2654
  /**
1639
- * Creates a ZohoCrmGetRelatedRecordsFunctionFactory, which can be used to create ZohoCrmGetRelatedRecordsFunction<T> that targets retrieving related records of a given type.
2655
+ * Creates a {@link ZohoCrmGetRelatedRecordsFunctionFactory} bound to the given context.
2656
+ *
2657
+ * Returns a factory that produces typed functions for fetching related records
2658
+ * (e.g. Notes, Emails, Attachments) of a specific target module. The factory
2659
+ * accepts a {@link ZohoCrmGetRelatedRecordsFunctionConfig} to specify the
2660
+ * target module and empty-result behavior. By default, returns an empty page
2661
+ * result instead of null when no records are found.
2662
+ *
2663
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2664
+ * @returns Factory that creates typed related-records retrieval functions
2665
+ *
2666
+ * @example
2667
+ * ```typescript
2668
+ * const factory = zohoCrmGetRelatedRecordsFunctionFactory(context);
1640
2669
  *
1641
- * https://www.zoho.com/crm/developer-guide/apiv2/get-related-records.html
2670
+ * // Create a typed function for fetching related Notes:
2671
+ * const getNotesForRecord = factory<ZohoCrmRecordNote>({
2672
+ * targetModule: ZOHO_CRM_NOTES_MODULE
2673
+ * });
1642
2674
  *
1643
- * @param context the ZohoCrmContext to use
1644
- * @returns a ZohoCrmGetRelatedRecordsFunctionFactory
2675
+ * const notes = await getNotesForRecord({
2676
+ * module: 'Contacts',
2677
+ * id: contactId
2678
+ * });
2679
+ * ```
2680
+ *
2681
+ * @see https://www.zoho.com/crm/developer-guide/apiv2/get-related-records.html
1645
2682
  */
1646
2683
  function zohoCrmGetRelatedRecordsFunctionFactory(context) {
1647
2684
  return (config) => {
@@ -1649,6 +2686,29 @@ function zohoCrmGetRelatedRecordsFunctionFactory(context) {
1649
2686
  return (input) => context.fetchJson(`/v8/${input.module}/${input.id}/${targetModule}?${zohoCrmUrlSearchParamsMinusIdAndModule(input, input.filter).toString()}`, zohoCrmApiFetchJsonInput('GET')).then((x) => x ?? (returnEmptyRecordsInsteadOfNull !== false ? emptyZohoPageResult() : x));
1650
2687
  };
1651
2688
  }
2689
+ /**
2690
+ * Creates a {@link ZohoCrmGetEmailsForRecordFunction} bound to the given context.
2691
+ *
2692
+ * Retrieves email metadata related to a specific record by targeting the
2693
+ * Emails module via the related records API. Normalizes the Zoho API response
2694
+ * which returns email data under an `Emails` key instead of the standard `data` key.
2695
+ * Returns a paginated result of {@link ZohoCrmRecordEmailMetadata} entries.
2696
+ *
2697
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2698
+ * @returns Function that retrieves emails for a record
2699
+ *
2700
+ * @example
2701
+ * ```typescript
2702
+ * const getEmailsForRecord = zohoCrmGetEmailsForRecord(context);
2703
+ *
2704
+ * const result = await getEmailsForRecord({
2705
+ * id: contactId,
2706
+ * module: 'Contacts'
2707
+ * });
2708
+ * ```
2709
+ *
2710
+ * @see https://www.zoho.com/crm/developer/docs/api/v8/get-email-rel-list.html
2711
+ */
1652
2712
  function zohoCrmGetEmailsForRecord(context) {
1653
2713
  const getEmailsFactory = zohoCrmGetRelatedRecordsFunctionFactory(context)({ targetModule: ZOHO_CRM_EMAILS_MODULE });
1654
2714
  return (input) => getEmailsFactory(input).then((x) => {
@@ -1656,12 +2716,96 @@ function zohoCrmGetEmailsForRecord(context) {
1656
2716
  return { ...x, data };
1657
2717
  });
1658
2718
  }
2719
+ /**
2720
+ * Creates a {@link ZohoCrmGetEmailsForRecordPageFactory} bound to the given context.
2721
+ *
2722
+ * Returns a page factory for iterating over emails related to a record across
2723
+ * multiple pages. Wraps {@link zohoCrmGetEmailsForRecord} with automatic
2724
+ * pagination handling via {@link zohoFetchPageFactory}.
2725
+ *
2726
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2727
+ * @returns Page factory for iterating over record emails
2728
+ *
2729
+ * @example
2730
+ * ```typescript
2731
+ * const pageFactory = zohoCrmGetEmailsForRecordPageFactory(context);
2732
+ *
2733
+ * const fetchPage = pageFactory({
2734
+ * id: contactId,
2735
+ * module: 'Contacts',
2736
+ * per_page: 5
2737
+ * });
2738
+ *
2739
+ * const firstPage = await fetchPage.fetchNext();
2740
+ *
2741
+ * if (firstPage.result.info.more_records) {
2742
+ * const secondPage = await firstPage.fetchNext();
2743
+ * }
2744
+ * ```
2745
+ *
2746
+ * @see https://www.zoho.com/crm/developer/docs/api/v8/get-email-rel-list.html
2747
+ */
1659
2748
  function zohoCrmGetEmailsForRecordPageFactory(context) {
1660
2749
  return zohoFetchPageFactory(zohoCrmGetEmailsForRecord(context));
1661
2750
  }
2751
+ /**
2752
+ * Creates a {@link ZohoCrmGetAttachmentsForRecordFunction} bound to the given context.
2753
+ *
2754
+ * Retrieves attachment metadata related to a specific record by targeting the
2755
+ * Attachments module via the related records API. Returns a paginated result of
2756
+ * {@link ZohoCrmRecordAttachmentMetadata} entries including file names, sizes,
2757
+ * and category information. When no attachments exist for the record, the result
2758
+ * contains an empty data array rather than null.
2759
+ *
2760
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2761
+ * @returns Function that retrieves attachments for a record
2762
+ *
2763
+ * @example
2764
+ * ```typescript
2765
+ * const getAttachmentsForRecord = zohoCrmGetAttachmentsForRecord(context);
2766
+ *
2767
+ * const result = await getAttachmentsForRecord({
2768
+ * id: contactId,
2769
+ * module: 'Contacts',
2770
+ * fields: 'File_Name,Size,Created_Time'
2771
+ * });
2772
+ * ```
2773
+ *
2774
+ * @see https://www.zoho.com/crm/developer-guide/apiv2/get-related-records.html
2775
+ */
1662
2776
  function zohoCrmGetAttachmentsForRecord(context) {
1663
2777
  return zohoCrmGetRelatedRecordsFunctionFactory(context)({ targetModule: ZOHO_CRM_ATTACHMENTS_MODULE });
1664
2778
  }
2779
+ /**
2780
+ * Creates a {@link ZohoCrmGetAttachmentsForRecordPageFactory} bound to the given context.
2781
+ *
2782
+ * Returns a page factory for iterating over attachments related to a record
2783
+ * across multiple pages. Wraps {@link zohoCrmGetAttachmentsForRecord} with
2784
+ * automatic pagination handling via {@link zohoFetchPageFactory}.
2785
+ *
2786
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2787
+ * @returns Page factory for iterating over record attachments
2788
+ *
2789
+ * @example
2790
+ * ```typescript
2791
+ * const pageFactory = zohoCrmGetAttachmentsForRecordPageFactory(context);
2792
+ *
2793
+ * const fetchPage = pageFactory({
2794
+ * id: contactId,
2795
+ * module: 'Contacts',
2796
+ * fields: 'File_Name,Size',
2797
+ * per_page: 10
2798
+ * });
2799
+ *
2800
+ * const firstPage = await fetchPage.fetchNext();
2801
+ *
2802
+ * if (firstPage.result.info.more_records) {
2803
+ * const secondPage = await firstPage.fetchNext();
2804
+ * }
2805
+ * ```
2806
+ *
2807
+ * @see https://www.zoho.com/crm/developer-guide/apiv2/get-related-records.html
2808
+ */
1665
2809
  function zohoCrmGetAttachmentsForRecordPageFactory(context) {
1666
2810
  return zohoFetchPageFactory(zohoCrmGetAttachmentsForRecord(context));
1667
2811
  }
@@ -1672,88 +2816,101 @@ function zohoCrmGetAttachmentsForRecordPageFactory(context) {
1672
2816
  */
1673
2817
  const ZOHO_CRM_ATTACHMENT_MAX_SIZE = 20 * 1024 * 1024;
1674
2818
  /**
1675
- * Uploads an attachment to a record.
2819
+ * Creates a {@link ZohoCrmUploadAttachmentForRecordFunction} bound to the given context.
1676
2820
  *
1677
- * https://www.zoho.com/crm/developer-guide/apiv2/upload-attachment.html
2821
+ * Uploads a file attachment to a specific record. The file is sent as
2822
+ * multipart/form-data. An attachment category must be specified by ID or name.
2823
+ * Maximum file size is {@link ZOHO_CRM_ATTACHMENT_MAX_SIZE} (20MB).
1678
2824
  *
1679
- * @param context
1680
- * @returns
2825
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2826
+ * @returns Function that uploads an attachment to a record
2827
+ * @throws {Error} If neither `attachmentCategoryId` nor `attachmentCategoryName` is provided
2828
+ *
2829
+ * @example
2830
+ * ```typescript
2831
+ * const uploadAttachment = zohoCrmUploadAttachmentForRecord(context);
2832
+ *
2833
+ * await uploadAttachment({
2834
+ * module: 'Contacts',
2835
+ * id: contactId,
2836
+ * file: new File(['content'], 'resume.pdf', { type: 'application/pdf' }),
2837
+ * attachmentCategoryName: 'Resume'
2838
+ * });
2839
+ * ```
2840
+ *
2841
+ * @see https://www.zoho.com/crm/developer/docs/api/v2.1/upload-attachment.html
1681
2842
  */
1682
2843
  function zohoCrmUploadAttachmentForRecord(context) {
1683
2844
  return (input) => {
1684
- const { attachmentCategoryId, attachmentCategoryName, formData } = input;
2845
+ const { attachmentCategoryId, attachmentCategoryName, file } = input;
1685
2846
  const urlParams = {
1686
2847
  attachments_category_id: joinStringsWithCommas(attachmentCategoryId),
1687
- attachments_category: joinStringsWithCommas(attachmentCategoryName),
1688
- attachment_url: input.attachmentUrl
2848
+ attachments_category: joinStringsWithCommas(attachmentCategoryName)
1689
2849
  };
1690
2850
  if (!urlParams.attachments_category_id?.length && !urlParams.attachments_category?.length) {
1691
2851
  throw new Error('attachmentCategoryId or attachmentCategoryName must be provided and not empty.');
1692
2852
  }
1693
- if (formData != null) {
1694
- delete urlParams.attachment_url;
1695
- }
1696
- const url = `https://crmsandbox.zoho.com/crm/v8/${input.module}/${input.id}/${ZOHO_CRM_ATTACHMENTS_MODULE}?${makeUrlSearchParams(urlParams).toString()}`;
1697
- let response;
1698
- if (urlParams.attachment_url) {
1699
- response = context.fetch(url, { method: 'POST' });
1700
- }
1701
- else if (formData != null) {
1702
- throw new Error('unsupported currently. Use the attachmentUrl parameter instead.');
1703
- // There is something weird going on with sending requests this way and zoho's server is rejecting it.
1704
- /*
1705
- response = context.fetch(url, {
1706
- method: 'POST',
1707
- headers: {
1708
- 'Content-Type': 'multipart/form-data',
1709
- 'content-length': '210'
1710
- },
1711
- body: formData
1712
- });
1713
- */
1714
- /*
1715
- const fullUrl = (context.config.apiUrl as string) + url;
1716
- const accessToken = await context.accessTokenStringFactory();
1717
-
1718
- response = fetch(fullUrl, {
1719
- headers: {
1720
- Authorization: `Bearer ${accessToken}`
1721
- },
1722
- body: formData,
1723
- method: 'POST'
1724
- });
1725
-
1726
- console.log({ response });
1727
- */
1728
- }
1729
- else {
1730
- throw new Error('body or attachmentUrl must be provided.');
1731
- }
1732
- return response;
2853
+ const url = `/v8/${input.module}/${input.id}/${ZOHO_CRM_ATTACHMENTS_MODULE}?${makeUrlSearchParams(urlParams).toString()}`;
2854
+ const body = new FormData();
2855
+ body.append('file', file);
2856
+ // Clear the base Content-Type header (empty string removes it via mergeRequestHeaders) so fetch auto-detects multipart/form-data with the correct boundary from the FormData body.
2857
+ return context.fetch(url, { method: 'POST', headers: { 'Content-Type': '' }, body });
1733
2858
  };
1734
2859
  }
1735
2860
  /**
1736
- * Downloads an attachment from a record.
2861
+ * Creates a {@link ZohoCrmDownloadAttachmentForRecordFunction} bound to the given context.
1737
2862
  *
1738
- * https://www.zoho.com/crm/developer-guide/apiv2/download-attachments.html
2863
+ * Downloads a specific attachment from a record. Returns a parsed
2864
+ * {@link FetchFileResponse} containing the file data and metadata extracted
2865
+ * from the response headers.
1739
2866
  *
1740
- * @param context
1741
- * @returns
2867
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2868
+ * @returns Function that downloads an attachment by record and attachment ID
2869
+ *
2870
+ * @example
2871
+ * ```typescript
2872
+ * const downloadAttachment = zohoCrmDownloadAttachmentForRecord(context);
2873
+ *
2874
+ * const fileResponse = await downloadAttachment({
2875
+ * module: 'Contacts',
2876
+ * id: contactId,
2877
+ * attachment_id: attachmentId
2878
+ * });
2879
+ * ```
2880
+ *
2881
+ * @see https://www.zoho.com/crm/developer-guide/apiv2/download-attachments.html
1742
2882
  */
1743
2883
  function zohoCrmDownloadAttachmentForRecord(context) {
1744
2884
  return (input) => context.fetch(`/v8/${input.module}/${input.id}/${ZOHO_CRM_ATTACHMENTS_MODULE}/${input.attachment_id}`, { method: 'GET' }).then(parseFetchFileResponse);
1745
2885
  }
1746
2886
  /**
1747
- * Deletes an attachment from a record.
2887
+ * Creates a {@link ZohoCrmDeleteAttachmentFromRecordFunction} bound to the given context.
1748
2888
  *
1749
- * https://www.zoho.com/crm/developer-guide/apiv2/delete-attachments.html
2889
+ * Deletes a specific attachment from a record by its attachment ID.
2890
+ * Returns the raw {@link Response}.
1750
2891
  *
1751
- * @param context
1752
- * @returns
2892
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2893
+ * @returns Function that deletes an attachment by record and attachment ID
2894
+ *
2895
+ * @example
2896
+ * ```typescript
2897
+ * const deleteAttachment = zohoCrmDeleteAttachmentFromRecord(context);
2898
+ *
2899
+ * const response = await deleteAttachment({
2900
+ * module: 'Contacts',
2901
+ * id: contactId,
2902
+ * attachment_id: attachmentId
2903
+ * });
2904
+ * ```
2905
+ *
2906
+ * @see https://www.zoho.com/crm/developer-guide/apiv2/delete-attachments.html
1753
2907
  */
1754
2908
  function zohoCrmDeleteAttachmentFromRecord(context) {
1755
2909
  return (input) => context.fetch(`/v8/${input.module}/${input.id}/${ZOHO_CRM_ATTACHMENTS_MODULE}/${input.attachment_id}`, { method: 'DELETE' });
1756
2910
  }
2911
+ /**
2912
+ * Thrown when a Zoho CRM serverless function returns a non-success response code.
2913
+ */
1757
2914
  class ZohoCrmExecuteRestApiFunctionError extends BaseError {
1758
2915
  error;
1759
2916
  constructor(error) {
@@ -1762,15 +2919,41 @@ class ZohoCrmExecuteRestApiFunctionError extends BaseError {
1762
2919
  }
1763
2920
  }
1764
2921
  /**
1765
- * Creates a ZohoCrmExecuteRestApiFunctionFunction
2922
+ * Creates a {@link ZohoCrmExecuteRestApiFunctionFunction} bound to the given context.
2923
+ *
2924
+ * Executes Zoho CRM serverless functions via the REST API. Supports both
2925
+ * OAuth-based and API-key-based authentication. When using an API key, a custom
2926
+ * target URL can be specified for cross-environment calls.
1766
2927
  *
1767
2928
  * OAuth Details:
1768
2929
  * - https://www.zoho.com/crm/developer/docs/functions/serverless-fn-oauth.html#OAuth2
1769
2930
  * - There is no documentation for ZohoCrm specifically, but it seems to behave the same way
1770
2931
  * - You will need the following scopes: ZohoCrm.functions.execute.READ,ZohoCrm.functions.execute.CREATE
1771
2932
  *
1772
- * @param context
1773
- * @returns
2933
+ * @param context - Authenticated Zoho CRM context providing fetch and rate limiting
2934
+ * @returns Function that executes serverless functions via the REST API
2935
+ * @throws {ZohoCrmExecuteRestApiFunctionError} If the function execution fails
2936
+ *
2937
+ * @example
2938
+ * ```typescript
2939
+ * const executeFunction = zohoCrmExecuteRestApiFunction(context);
2940
+ *
2941
+ * // Execute using OAuth credentials:
2942
+ * const result = await executeFunction({ functionName: 'my_function' });
2943
+ *
2944
+ * // Execute with parameters:
2945
+ * const paramResult = await executeFunction({
2946
+ * functionName: 'process_contact',
2947
+ * params: { contact_id: '12345', action: 'approve' }
2948
+ * });
2949
+ *
2950
+ * // Execute using an API key (cross-environment):
2951
+ * const apiResult = await executeFunction({
2952
+ * functionName: 'my_function',
2953
+ * apiKey: 'your-api-key',
2954
+ * apiUrl: 'production'
2955
+ * });
2956
+ * ```
1774
2957
  */
1775
2958
  function zohoCrmExecuteRestApiFunction(context) {
1776
2959
  return (input) => {
@@ -1792,9 +2975,15 @@ function zohoCrmExecuteRestApiFunction(context) {
1792
2975
  };
1793
2976
  }
1794
2977
  // MARK: Util
2978
+ /**
2979
+ * Builds URL search params from the input objects, omitting the `module` key since it is used in the URL path rather than as a query parameter.
2980
+ */
1795
2981
  function zohoCrmUrlSearchParamsMinusModule(...input) {
1796
2982
  return makeUrlSearchParams(input, { omitKeys: 'module' });
1797
2983
  }
2984
+ /**
2985
+ * Builds URL search params from the input objects, omitting both `id` and `module` keys since they are used in the URL path.
2986
+ */
1798
2987
  function zohoCrmUrlSearchParamsMinusIdAndModule(...input) {
1799
2988
  return makeUrlSearchParams(input, { omitKeys: ['id', 'module'] });
1800
2989
  }
@@ -1802,6 +2991,9 @@ function zohoCrmUrlSearchParamsMinusIdAndModule(...input) {
1802
2991
  * @deprecated use makeUrlSearchParams instead.
1803
2992
  */
1804
2993
  const zohoCrmUrlSearchParams = makeUrlSearchParams;
2994
+ /**
2995
+ * Constructs the standard FetchJsonInput used by CRM API calls, pairing the HTTP method with an optional body.
2996
+ */
1805
2997
  function zohoCrmApiFetchJsonInput(method, body) {
1806
2998
  const result = {
1807
2999
  method,
@@ -1827,6 +3019,28 @@ function zohoCrmCatchZohoCrmChangeObjectLikeResponseError(e) {
1827
3019
  }
1828
3020
  return result;
1829
3021
  }
3022
+ /**
3023
+ * Separates a change response's entries into success and error arrays based on their status.
3024
+ *
3025
+ * Iterates over the `data` array from a Zoho CRM change operation response and
3026
+ * partitions entries into `successItems` and `errorItems` based on their `status` field.
3027
+ * The original response is spread into the result, so all original fields remain accessible.
3028
+ *
3029
+ * Used internally by {@link zohoCrmDeleteRecord} and similar functions to provide
3030
+ * convenient access to separated success/error results.
3031
+ *
3032
+ * @param response - Raw change operation response containing mixed success/error entries
3033
+ * @returns The response augmented with pre-separated `successItems` and `errorItems` arrays
3034
+ *
3035
+ * @example
3036
+ * ```typescript
3037
+ * const rawResponse = await context.fetchJson<ZohoCrmChangeObjectLikeResponse>(...);
3038
+ * const result = zohoCrmChangeObjectLikeResponseSuccessAndErrorPairs(rawResponse);
3039
+ *
3040
+ * result.successItems; // entries with status === 'success'
3041
+ * result.errorItems; // entries with non-success status
3042
+ * ```
3043
+ */
1830
3044
  function zohoCrmChangeObjectLikeResponseSuccessAndErrorPairs(response) {
1831
3045
  const { data } = response;
1832
3046
  const successItems = [];
@@ -1846,6 +3060,20 @@ function zohoCrmChangeObjectLikeResponseSuccessAndErrorPairs(response) {
1846
3060
  };
1847
3061
  return result;
1848
3062
  }
3063
+ /**
3064
+ * Pairs each input record with its corresponding API result and separates them into success and error arrays by status.
3065
+ *
3066
+ * Iterates over the `input` and `results` arrays in parallel, matching each input record
3067
+ * to its positional result. Entries are classified as success or error based on
3068
+ * the result's `status` field matching {@link ZOHO_SUCCESS_STATUS}.
3069
+ *
3070
+ * Used internally by {@link updateRecordLikeFunction} to pair input data with API outcomes
3071
+ * for insert, update, and upsert operations.
3072
+ *
3073
+ * @param input - Array of input records that were submitted to the API
3074
+ * @param results - Array of per-record results returned by the API, positionally aligned with `input`
3075
+ * @returns Object with `successItems` and `errorItems`, each containing paired `{ input, result }` entries
3076
+ */
1849
3077
  function zohoCrmMultiRecordResult(input, results) {
1850
3078
  const successItems = [];
1851
3079
  const errorItems = [];
@@ -1871,27 +3099,40 @@ function zohoCrmMultiRecordResult(input, results) {
1871
3099
  return result;
1872
3100
  }
1873
3101
 
3102
+ /**
3103
+ * Creates notes directly in the CRM Notes module. Each note must include the parent record reference and module name.
3104
+ *
3105
+ * For creating notes associated with a specific record, prefer {@link zohoCrmCreateNotesForRecord} which handles parent binding automatically.
3106
+ */
1874
3107
  function zohoCrmCreateNotes(context) {
1875
3108
  return (input) => context.fetchJson(`/v2/${ZOHO_CRM_NOTES_MODULE}`, zohoCrmApiFetchJsonInput('POST', { data: input.data })).then((x) => {
1876
3109
  return zohoCrmMultiRecordResult(asArray(input.data), x.data);
1877
3110
  });
1878
3111
  }
3112
+ /**
3113
+ * Deletes one or more notes from the CRM Notes module by their IDs.
3114
+ */
1879
3115
  function zohoCrmDeleteNotes(context) {
1880
3116
  return (input) => context.fetchJson(`/v2/${ZOHO_CRM_NOTES_MODULE}?${makeUrlSearchParams({ ids: input.ids })}`, zohoCrmApiFetchJsonInput('DELETE')).then((x) => {
1881
3117
  return zohoCrmMultiRecordResult(asArray(input.ids), x.data);
1882
3118
  });
1883
3119
  }
3120
+ /**
3121
+ * Retrieves paginated notes associated with a specific CRM record using the related records API.
3122
+ */
1884
3123
  function zohoCrmGetNotesForRecord(context) {
1885
3124
  return zohoCrmGetRelatedRecordsFunctionFactory(context)({ targetModule: ZOHO_CRM_NOTES_MODULE });
1886
3125
  }
3126
+ /**
3127
+ * Creates a page factory for iterating through all notes for a record across multiple pages.
3128
+ */
1887
3129
  function zohoCrmGetNotesForRecordPageFactory(context) {
1888
3130
  return zohoFetchPageFactory(zohoCrmGetNotesForRecord(context));
1889
3131
  }
1890
3132
  /**
1891
- * https://www.zoho.com/crm/developer/docs/api/v8/create-notes.html
3133
+ * Creates notes for a specific record, automatically binding the parent module and record ID to each note entry.
1892
3134
  *
1893
- * @param context
1894
- * @returns
3135
+ * https://www.zoho.com/crm/developer/docs/api/v8/create-notes.html
1895
3136
  */
1896
3137
  function zohoCrmCreateNotesForRecord(context) {
1897
3138
  const createNotesInstance = zohoCrmCreateNotes(context);
@@ -1908,6 +3149,9 @@ function zohoCrmCreateNotesForRecord(context) {
1908
3149
  };
1909
3150
  }
1910
3151
 
3152
+ /**
3153
+ * Creates one or more tags for a CRM module. Duplicate tag errors are separated from other errors in the result to simplify idempotent workflows.
3154
+ */
1911
3155
  function zohoCrmCreateTagsForModule(context) {
1912
3156
  return (input) => context
1913
3157
  .fetchJson(`/v8/settings/tags?${makeUrlSearchParams({ module: input.module })}`, zohoCrmApiFetchJsonInput('POST', { tags: asArray(input.tags) }))
@@ -1969,12 +3213,15 @@ function zohoCrmGetTagsForModule(context) {
1969
3213
  };
1970
3214
  });
1971
3215
  }
3216
+ /**
3217
+ * Creates a page factory for iterating through all tags in a module across multiple pages.
3218
+ */
1972
3219
  function zohoCrmGetTagsForModulePageFactory(context) {
1973
3220
  return zohoFetchPageFactory(zohoCrmGetTagsForModule(context));
1974
3221
  }
1975
3222
  // MARK: Add Tag To Record
1976
3223
  /**
1977
- * Limit enforced by Zoho Crm
3224
+ * Maximum number of record IDs allowed per add-tags request, enforced by Zoho CRM.
1978
3225
  */
1979
3226
  const ZOHO_CRM_ADD_TAGS_TO_RECORDS_MAX_IDS_ALLOWED = 100;
1980
3227
  /**
@@ -1993,6 +3240,9 @@ function zohoCrmAddTagsToRecords(context) {
1993
3240
  });
1994
3241
  };
1995
3242
  }
3243
+ /**
3244
+ * Builds the request body for the add/remove tags endpoints, merging `tag_names` into `tags` and enforcing the max ID limit.
3245
+ */
1996
3246
  function zohoCrmAddTagsToRecordsRequestBody(input) {
1997
3247
  if (Array.isArray(input.ids) && input.ids.length > ZOHO_CRM_ADD_TAGS_TO_RECORDS_MAX_IDS_ALLOWED) {
1998
3248
  throw new Error(`Cannot add/remove tags from more than ${ZOHO_CRM_ADD_TAGS_TO_RECORDS_MAX_IDS_ALLOWED} records at once.`);
@@ -2014,7 +3264,7 @@ function zohoCrmAddTagsToRecordsRequestBody(input) {
2014
3264
  }
2015
3265
  // MARK: Remove Tag From Record
2016
3266
  /**
2017
- * Limit enforced by Zoho Crm
3267
+ * Maximum number of record IDs allowed per remove-tags request, enforced by Zoho CRM.
2018
3268
  */
2019
3269
  const ZOHO_CRM_REMOVE_TAGS_FROM_RECORDS_MAX_IDS_ALLOWED = 100;
2020
3270
  /**
@@ -2034,6 +3284,31 @@ function zohoCrmRemoveTagsFromRecords(context) {
2034
3284
  };
2035
3285
  }
2036
3286
 
3287
+ /**
3288
+ * Creates a {@link ZohoCrmFactory} from the given configuration.
3289
+ *
3290
+ * The factory pre-initializes shared resources (access token provider, rate limiter)
3291
+ * once, then produces {@link ZohoCrm} client instances for each {@link ZohoCrmConfig}.
3292
+ * Each client handles OAuth token refresh on {@link ZohoInvalidTokenError}, rate limiting,
3293
+ * and Zoho CRM's non-standard error responses (200 status with error body).
3294
+ *
3295
+ * @param factoryConfig - Configuration providing account credentials and optional overrides
3296
+ * @returns A factory function that creates authenticated Zoho CRM clients
3297
+ *
3298
+ * @example
3299
+ * ```typescript
3300
+ * const factory = zohoCrmFactory({
3301
+ * accountsContext: zohoAccountsApi.accountsContext
3302
+ * });
3303
+ *
3304
+ * const zohoCrm = factory({
3305
+ * apiUrl: 'https://www.zohoapis.com/crm'
3306
+ * });
3307
+ *
3308
+ * // Use the CRM context for API calls:
3309
+ * const { crmContext } = zohoCrm;
3310
+ * ```
3311
+ */
2037
3312
  function zohoCrmFactory(factoryConfig) {
2038
3313
  const { accountsContext } = factoryConfig;
2039
3314
  const accessTokenStringFactory = zohoAccessTokenStringFactory(accountsContext.loadAccessToken);
@@ -2089,9 +3364,543 @@ function zohoCrmFactory(factoryConfig) {
2089
3364
  const ZOHO_CRM_TAG_NAME_MAX_LENGTH = 25;
2090
3365
 
2091
3366
  /**
2092
- * Trades a refresh token for a new AccessToken
2093
- * @param context
2094
- * @returns
3367
+ * Creates a page factory that wraps a Zoho Sign fetch function with automatic offset-based pagination.
3368
+ *
3369
+ * The factory reads `page_context.has_more_rows` from each response to determine if additional
3370
+ * pages exist, and automatically advances `start_index` by `row_count` for subsequent requests.
3371
+ *
3372
+ * @param fetch - The Zoho Sign fetch function to paginate over
3373
+ * @param defaults - Optional default configuration for the page factory
3374
+ * @returns A page factory that produces iterable page fetchers
3375
+ *
3376
+ * @example
3377
+ * ```typescript
3378
+ * const pageFactory = zohoSignFetchPageFactory(zohoSignGetDocumentsList(context));
3379
+ *
3380
+ * const fetchPage = pageFactory({ row_count: 10 });
3381
+ * const firstPage = await fetchPage.fetchNext();
3382
+ *
3383
+ * if (firstPage.result.page_context.has_more_rows) {
3384
+ * const secondPage = await firstPage.fetchNext();
3385
+ * }
3386
+ * ```
3387
+ */
3388
+ function zohoSignFetchPageFactory(fetch, defaults) {
3389
+ return fetchPageFactory({
3390
+ ...defaults,
3391
+ fetch,
3392
+ readFetchPageResultInfo: function (result) {
3393
+ return {
3394
+ hasNext: result.page_context?.has_more_rows ?? false
3395
+ };
3396
+ },
3397
+ buildInputForNextPage: function (pageResult, input, options) {
3398
+ const previousResult = pageResult.result;
3399
+ const previousPageContext = previousResult?.page_context;
3400
+ const rowCount = options.maxItemsPerPage ?? input.row_count ?? previousPageContext?.row_count ?? 20;
3401
+ const nextStartIndex = (previousPageContext?.start_index ?? input.start_index ?? 1) + rowCount;
3402
+ return { ...input, start_index: nextStartIndex, row_count: rowCount };
3403
+ }
3404
+ });
3405
+ }
3406
+
3407
+ // MARK: Utility
3408
+ /**
3409
+ * Builds a {@link FetchJsonInput} for Zoho Sign API calls.
3410
+ */
3411
+ function zohoSignApiFetchJsonInput(method, body) {
3412
+ return {
3413
+ method,
3414
+ body: body ?? undefined
3415
+ };
3416
+ }
3417
+ /**
3418
+ * Creates a {@link ZohoSignGetDocumentFunction} bound to the given context.
3419
+ *
3420
+ * Fetches the full details of a single Zoho Sign request (envelope) by its ID,
3421
+ * including actions, documents, and field data.
3422
+ *
3423
+ * @param context - Authenticated Zoho Sign context providing fetch and rate limiting
3424
+ * @returns Function that retrieves a document by request ID
3425
+ *
3426
+ * @example
3427
+ * ```typescript
3428
+ * const getDocument = zohoSignGetDocument(context);
3429
+ * const response = await getDocument({ requestId: '12345' });
3430
+ * ```
3431
+ *
3432
+ * @see https://www.zoho.com/sign/api/document-managment/get-details-of-a-particular-document.html
3433
+ */
3434
+ function zohoSignGetDocument(context) {
3435
+ return ({ requestId }) => context.fetchJson(`/requests/${requestId}`, zohoSignApiFetchJsonInput('GET'));
3436
+ }
3437
+ /**
3438
+ * Creates a {@link ZohoSignGetDocumentsFunction} bound to the given context.
3439
+ *
3440
+ * Fetches a paginated list of Zoho Sign requests. Supports sorting via
3441
+ * {@link ZohoSignPageFilter} and filtering via {@link ZohoSignSearchColumns}.
3442
+ * Pagination parameters (`start_index`, `row_count`) are serialized as a
3443
+ * JSON `data` query parameter per the Zoho Sign API convention.
3444
+ *
3445
+ * @param context - Authenticated Zoho Sign context providing fetch and rate limiting
3446
+ * @returns Function that retrieves a paginated list of documents
3447
+ *
3448
+ * @example
3449
+ * ```typescript
3450
+ * const getDocuments = zohoSignGetDocuments(context);
3451
+ * const response = await getDocuments({ start_index: 1, row_count: 10 });
3452
+ * ```
3453
+ *
3454
+ * @see https://www.zoho.com/sign/api/document-managment/get-document-list.html
3455
+ */
3456
+ function zohoSignGetDocuments(context) {
3457
+ return (input) => {
3458
+ const { search_columns, ...pageFilter } = input;
3459
+ const data = {
3460
+ page_context: {
3461
+ ...pageFilter,
3462
+ ...(search_columns ? { search_columns } : {})
3463
+ }
3464
+ };
3465
+ return context.fetchJson({
3466
+ url: `/requests`,
3467
+ queryParams: { data: JSON.stringify(data) }
3468
+ }, zohoSignApiFetchJsonInput('GET'));
3469
+ };
3470
+ }
3471
+ /**
3472
+ * Creates a {@link zohoSignFetchPageFactory} bound to the given context.
3473
+ *
3474
+ * Returns a page factory that automatically handles Zoho Sign's
3475
+ * `start_index`/`row_count` pagination, making it easy to iterate
3476
+ * through all documents across multiple pages.
3477
+ *
3478
+ * @param context - Authenticated Zoho Sign context providing fetch and rate limiting
3479
+ * @returns Page factory for iterating through documents
3480
+ *
3481
+ * @example
3482
+ * ```typescript
3483
+ * const pageFactory = zohoSignGetDocumentsPageFactory(context);
3484
+ * const fetchPage = pageFactory({ row_count: 5 });
3485
+ *
3486
+ * const firstPage = await fetchPage.fetchNext();
3487
+ * const secondPage = await firstPage.fetchNext();
3488
+ * ```
3489
+ */
3490
+ function zohoSignGetDocumentsPageFactory(context) {
3491
+ const getDocuments = zohoSignGetDocuments(context);
3492
+ return zohoSignFetchPageFactory(getDocuments);
3493
+ }
3494
+ /**
3495
+ * Creates a {@link ZohoSignGetDocumentFormDataFunction} bound to the given context.
3496
+ *
3497
+ * Retrieves the filled form field values for a completed document. Only
3498
+ * returns data after all recipients have signed. The response includes
3499
+ * field labels and values grouped by recipient action.
3500
+ *
3501
+ * @param context - Authenticated Zoho Sign context providing fetch and rate limiting
3502
+ * @returns Function that retrieves form data for a completed document
3503
+ *
3504
+ * @example
3505
+ * ```typescript
3506
+ * const getFormData = zohoSignGetDocumentFormData(context);
3507
+ * const response = await getFormData({ requestId: '12345' });
3508
+ * ```
3509
+ *
3510
+ * @see https://www.zoho.com/sign/api/document-managment/get-document-form-data.html
3511
+ */
3512
+ function zohoSignGetDocumentFormData(context) {
3513
+ return ({ requestId }) => context.fetchJson(`/requests/${requestId}/fielddata`, zohoSignApiFetchJsonInput('GET'));
3514
+ }
3515
+ /**
3516
+ * Creates a {@link ZohoSignRetrieveFieldTypesFunction} bound to the given context.
3517
+ *
3518
+ * Retrieves all available field types (e.g. Signature, Textfield, Checkbox)
3519
+ * that can be placed on documents. Useful for dynamically building document
3520
+ * templates or validating field configurations.
3521
+ *
3522
+ * @param context - Authenticated Zoho Sign context providing fetch and rate limiting
3523
+ * @returns Function that retrieves all available field types
3524
+ *
3525
+ * @example
3526
+ * ```typescript
3527
+ * const retrieveFieldTypes = zohoSignRetrieveFieldTypes(context);
3528
+ * const response = await retrieveFieldTypes();
3529
+ * ```
3530
+ *
3531
+ * @see https://www.zoho.com/sign/api/document-managment/retrieve-field-type.html
3532
+ */
3533
+ function zohoSignRetrieveFieldTypes(context) {
3534
+ return () => context.fetchJson(`/fieldtypes`, zohoSignApiFetchJsonInput('GET'));
3535
+ }
3536
+ /**
3537
+ * Creates a {@link ZohoSignDownloadPdfFunction} bound to the given context.
3538
+ *
3539
+ * Downloads the signed PDF document(s) for a completed request. Returns a
3540
+ * raw {@link Response} containing either a PDF (single document) or a ZIP
3541
+ * archive (multiple documents). Supports optional parameters to include the
3542
+ * completion certificate, merge all documents, or provide a password for
3543
+ * protected files.
3544
+ *
3545
+ * @param context - Authenticated Zoho Sign context providing fetch and rate limiting
3546
+ * @returns Function that downloads signed PDFs by request ID
3547
+ *
3548
+ * @example
3549
+ * ```typescript
3550
+ * const downloadPdf = zohoSignDownloadPdf(context);
3551
+ *
3552
+ * // Download signed PDF with completion certificate merged in:
3553
+ * const response = await downloadPdf({
3554
+ * requestId: '12345',
3555
+ * with_coc: true,
3556
+ * merge: true
3557
+ * });
3558
+ *
3559
+ * const blob = await response.blob();
3560
+ * ```
3561
+ *
3562
+ * @see https://www.zoho.com/sign/api/document-managment/download-pdf.html
3563
+ */
3564
+ function zohoSignDownloadPdf(context) {
3565
+ return ({ requestId, ...params }) => {
3566
+ const searchParams = makeUrlSearchParams(params);
3567
+ const queryString = searchParams.toString();
3568
+ const url = `/requests/${requestId}/pdf${queryString ? `?${queryString}` : ''}`;
3569
+ return context.fetch(url, { method: 'GET' });
3570
+ };
3571
+ }
3572
+ /**
3573
+ * Creates a {@link ZohoSignDownloadCompletionCertificateFunction} bound to the given context.
3574
+ *
3575
+ * Downloads the completion certificate PDF for a finished request. The
3576
+ * certificate contains an audit trail of the signing process including
3577
+ * timestamps and recipient details. Returns a raw {@link Response}.
3578
+ *
3579
+ * @param context - Authenticated Zoho Sign context providing fetch and rate limiting
3580
+ * @returns Function that downloads the completion certificate by request ID
3581
+ *
3582
+ * @example
3583
+ * ```typescript
3584
+ * const downloadCert = zohoSignDownloadCompletionCertificate(context);
3585
+ * const response = await downloadCert({ requestId: '12345' });
3586
+ * const blob = await response.blob();
3587
+ * ```
3588
+ *
3589
+ * @see https://www.zoho.com/sign/api/document-managment/download-completion-certificate.html
3590
+ */
3591
+ function zohoSignDownloadCompletionCertificate(context) {
3592
+ return ({ requestId }) => context.fetch(`/requests/${requestId}/completioncertificate`, { method: 'GET' });
3593
+ }
3594
+ /**
3595
+ * Creates a {@link ZohoSignCreateDocumentFunction} bound to the given context.
3596
+ *
3597
+ * Creates a new document draft in Zoho Sign. The request data and file are
3598
+ * sent as multipart/form-data. The `Content-Type` header is intentionally
3599
+ * cleared so that `fetch` auto-detects the correct multipart boundary from
3600
+ * the {@link FormData} body.
3601
+ *
3602
+ * The created document starts in draft status and must be submitted
3603
+ * separately via {@link zohoSignSendDocumentForSignature}.
3604
+ *
3605
+ * @param context - Authenticated Zoho Sign context providing fetch and rate limiting
3606
+ * @returns Function that creates a new document draft
3607
+ *
3608
+ * @example
3609
+ * ```typescript
3610
+ * const createDocument = zohoSignCreateDocument(context);
3611
+ *
3612
+ * const response = await createDocument({
3613
+ * requestData: {
3614
+ * request_name: 'NDA Agreement',
3615
+ * is_sequential: true,
3616
+ * actions: [
3617
+ * {
3618
+ * action_type: 'SIGN',
3619
+ * recipient_name: 'Jane Doe',
3620
+ * recipient_email: 'jane@example.com'
3621
+ * }
3622
+ * ]
3623
+ * },
3624
+ * file: new File([pdfBuffer], 'nda.pdf', { type: 'application/pdf' })
3625
+ * });
3626
+ *
3627
+ * const draftId = response.requests.request_id;
3628
+ * ```
3629
+ *
3630
+ * @see https://www.zoho.com/sign/api/document-managment/create-document.html
3631
+ */
3632
+ function zohoSignCreateDocument(context) {
3633
+ return ({ requestData, file }) => {
3634
+ const body = new FormData();
3635
+ body.append('data', JSON.stringify({ requests: requestData }));
3636
+ body.append('file', file);
3637
+ // Clear the base Content-Type header (empty string removes it via mergeRequestHeaders) so fetch auto-detects multipart/form-data with the correct boundary from the FormData body.
3638
+ return context.fetch(`/requests`, { method: 'POST', headers: { 'Content-Type': '' }, body }).then((response) => response.json());
3639
+ };
3640
+ }
3641
+ /**
3642
+ * Creates a {@link ZohoSignUpdateDocumentFunction} bound to the given context.
3643
+ *
3644
+ * Updates an existing document that is still in draft status. Accepts partial
3645
+ * {@link ZohoSignRequestData} to modify fields such as actions, notes, or
3646
+ * expiration settings. Cannot be used on documents that have already been
3647
+ * submitted for signature.
3648
+ *
3649
+ * @param context - Authenticated Zoho Sign context providing fetch and rate limiting
3650
+ * @returns Function that updates a draft document by request ID
3651
+ *
3652
+ * @example
3653
+ * ```typescript
3654
+ * const updateDocument = zohoSignUpdateDocument(context);
3655
+ *
3656
+ * await updateDocument({
3657
+ * requestId: '12345',
3658
+ * data: { notes: 'Updated notes for this document' }
3659
+ * });
3660
+ * ```
3661
+ *
3662
+ * @see https://www.zoho.com/sign/api/document-managment/update-document.html
3663
+ */
3664
+ function zohoSignUpdateDocument(context) {
3665
+ return ({ requestId, data }) => context.fetchJson(`/requests/${requestId}`, zohoSignApiFetchJsonInput('PUT', { requests: data }));
3666
+ }
3667
+ /**
3668
+ * Creates a {@link ZohoSignSendDocumentForSignatureFunction} bound to the given context.
3669
+ *
3670
+ * Submits a draft document to its recipients for signing. Optionally accepts
3671
+ * partial {@link ZohoSignRequestData} to apply last-minute updates before
3672
+ * submission. Once submitted, the document transitions from draft to
3673
+ * "inprogress" status and recipients receive signing notifications.
3674
+ *
3675
+ * @param context - Authenticated Zoho Sign context providing fetch and rate limiting
3676
+ * @returns Function that sends a document for signature by request ID
3677
+ *
3678
+ * @example
3679
+ * ```typescript
3680
+ * const sendForSignature = zohoSignSendDocumentForSignature(context);
3681
+ *
3682
+ * // Submit a draft for signing:
3683
+ * await sendForSignature({ requestId: '12345' });
3684
+ *
3685
+ * // Submit with last-minute updates:
3686
+ * await sendForSignature({
3687
+ * requestId: '12345',
3688
+ * data: { expiration_days: 30 }
3689
+ * });
3690
+ * ```
3691
+ *
3692
+ * @see https://www.zoho.com/sign/api/document-managment/send-document-for-signature.html
3693
+ */
3694
+ function zohoSignSendDocumentForSignature(context) {
3695
+ return ({ requestId, data }) => {
3696
+ const body = data ? { requests: data } : undefined;
3697
+ return context.fetchJson(`/requests/${requestId}/submit`, zohoSignApiFetchJsonInput('POST', body));
3698
+ };
3699
+ }
3700
+ /**
3701
+ * Creates a {@link ZohoSignExtendDocumentFunction} bound to the given context.
3702
+ *
3703
+ * Extends the expiration date of an in-progress document. The new date is
3704
+ * provided as a human-readable string (e.g. "30 November 2024"). Useful for
3705
+ * giving recipients additional time to complete signing.
3706
+ *
3707
+ * @param context - Authenticated Zoho Sign context providing fetch and rate limiting
3708
+ * @returns Function that extends a document's expiration by request ID
3709
+ *
3710
+ * @example
3711
+ * ```typescript
3712
+ * const extendDocument = zohoSignExtendDocument(context);
3713
+ * await extendDocument({ requestId: '12345', expire_by: '30 November 2024' });
3714
+ * ```
3715
+ *
3716
+ * @see https://www.zoho.com/sign/api/document-managment/extend-document.html
3717
+ */
3718
+ function zohoSignExtendDocument(context) {
3719
+ return ({ requestId, expire_by }) => context.fetchJson(`/requests/${requestId}/extend`, zohoSignApiFetchJsonInput('PUT', { expire_by }));
3720
+ }
3721
+ /**
3722
+ * Creates a {@link ZohoSignDeleteDocumentFunction} bound to the given context.
3723
+ *
3724
+ * Deletes a document from Zoho Sign. For documents that are currently
3725
+ * in-progress, set `recall_inprogress` to `true` to recall the document
3726
+ * from recipients before deletion. An optional `reason` can be provided
3727
+ * to explain the recall. Sends the parameters as URL-encoded form data.
3728
+ *
3729
+ * @param context - Authenticated Zoho Sign context providing fetch and rate limiting
3730
+ * @returns Function that deletes a document by request ID
3731
+ *
3732
+ * @example
3733
+ * ```typescript
3734
+ * const deleteDocument = zohoSignDeleteDocument(context);
3735
+ *
3736
+ * // Delete a draft:
3737
+ * await deleteDocument({ requestId: '12345' });
3738
+ *
3739
+ * // Recall an in-progress document before deleting:
3740
+ * await deleteDocument({
3741
+ * requestId: '12345',
3742
+ * recall_inprogress: true,
3743
+ * reason: 'Contract terms changed'
3744
+ * });
3745
+ * ```
3746
+ *
3747
+ * @see https://www.zoho.com/sign/api/document-managment/delete-document.html
3748
+ */
3749
+ function zohoSignDeleteDocument(context) {
3750
+ return ({ requestId, recall_inprogress, reason }) => {
3751
+ const params = {};
3752
+ if (recall_inprogress != null) {
3753
+ params['recall_inprogress'] = String(recall_inprogress);
3754
+ }
3755
+ if (reason != null) {
3756
+ params['reason'] = reason;
3757
+ }
3758
+ const form = makeUrlSearchParams(params);
3759
+ const hasForm = form.toString().length > 0;
3760
+ return context.fetchJson(`/requests/${requestId}/delete`, {
3761
+ method: 'PUT',
3762
+ ...(hasForm ? { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: form.toString() } : {})
3763
+ });
3764
+ };
3765
+ }
3766
+
3767
+ const ZOHO_SIGN_SERVICE_NAME = 'sign';
3768
+ function zohoSignConfigApiUrl(input) {
3769
+ switch (input) {
3770
+ case 'sandbox':
3771
+ return 'https://signsandbox.zoho.com/api/v1';
3772
+ case 'production':
3773
+ return 'https://sign.zoho.com/api/v1';
3774
+ default:
3775
+ return input;
3776
+ }
3777
+ }
3778
+
3779
+ const logZohoSignErrorToConsole = logZohoServerErrorFunction('ZohoSign', { logDataArrayErrors: false });
3780
+ async function parseZohoSignError(responseError) {
3781
+ const data = await responseError.response.json().catch(() => undefined);
3782
+ let result;
3783
+ if (data) {
3784
+ result = parseZohoSignServerErrorResponseData(data, responseError);
3785
+ }
3786
+ return result;
3787
+ }
3788
+ function parseZohoSignServerErrorResponseData(errorResponseData, responseError) {
3789
+ let result;
3790
+ const error = tryFindZohoServerErrorData(errorResponseData, responseError);
3791
+ if (error) {
3792
+ const errorData = zohoServerErrorData(error);
3793
+ switch (errorData.code) {
3794
+ // TODO: Add sign-specific error codes here.
3795
+ default:
3796
+ result = parseZohoServerErrorResponseData(errorResponseData, responseError);
3797
+ break;
3798
+ }
3799
+ }
3800
+ return result;
3801
+ }
3802
+ const interceptZohoSign200StatusWithErrorResponse = interceptZohoErrorResponseFactory(parseZohoSignServerErrorResponseData);
3803
+ const handleZohoSignErrorFetch = handleZohoErrorFetchFactory(parseZohoSignError, logZohoSignErrorToConsole);
3804
+
3805
+ /**
3806
+ * Creates a {@link ZohoSignFactory} from the given configuration.
3807
+ *
3808
+ * The factory pre-initializes shared resources (access token provider, rate limiter)
3809
+ * once, then produces {@link ZohoSign} client instances for each {@link ZohoSignConfig}.
3810
+ * Each client handles OAuth token refresh on {@link ZohoInvalidTokenError}, rate limiting,
3811
+ * and Zoho Sign's non-standard error responses (200 status with error body).
3812
+ *
3813
+ * @param factoryConfig - Configuration providing account credentials and optional overrides
3814
+ * @returns A factory function that creates authenticated Zoho Sign clients
3815
+ *
3816
+ * @example
3817
+ * ```typescript
3818
+ * const factory = zohoSignFactory({
3819
+ * accountsContext: zohoAccountsApi.accountsContext
3820
+ * });
3821
+ *
3822
+ * const zohoSign = factory({
3823
+ * apiUrl: 'https://sign.zoho.com'
3824
+ * });
3825
+ *
3826
+ * // Use the sign context for API calls:
3827
+ * const { signContext } = zohoSign;
3828
+ * ```
3829
+ */
3830
+ function zohoSignFactory(factoryConfig) {
3831
+ const { accountsContext } = factoryConfig;
3832
+ const accessTokenStringFactory = zohoAccessTokenStringFactory(accountsContext.loadAccessToken);
3833
+ const fetchHandler = zohoRateLimitedFetchHandler(factoryConfig.rateLimiterConfig);
3834
+ const { logZohoServerErrorFunction, fetchFactory = (input) => fetchApiFetchService.makeFetch({
3835
+ baseUrl: input.apiUrl,
3836
+ baseRequest: async () => ({
3837
+ headers: {
3838
+ 'Content-Type': 'application/json',
3839
+ Authorization: `Bearer ${await accessTokenStringFactory()}`
3840
+ }
3841
+ }),
3842
+ fetchHandler,
3843
+ timeout: 20 * 1000, // 20 second timeout
3844
+ requireOkResponse: true, // enforce ok response
3845
+ useTimeout: true // use timeout
3846
+ }) } = factoryConfig;
3847
+ return (config) => {
3848
+ if (!config.apiUrl) {
3849
+ throw new Error('ZohoConfig missing api url.');
3850
+ }
3851
+ const apiUrl = zohoSignConfigApiUrl(config.apiUrl);
3852
+ const baseFetch = fetchFactory({ apiUrl });
3853
+ const fetch = handleZohoSignErrorFetch(baseFetch, logZohoServerErrorFunction, (x) => {
3854
+ if (x instanceof ZohoInvalidTokenError) {
3855
+ accountsContext.loadAccessToken.resetAccessToken();
3856
+ }
3857
+ });
3858
+ const fetchJson = fetchJsonFunction(fetch, {
3859
+ interceptJsonResponse: interceptZohoSign200StatusWithErrorResponse, // intercept errors that return status 200
3860
+ handleFetchJsonParseErrorFunction: returnNullHandleFetchJsonParseErrorFunction
3861
+ });
3862
+ const signContext = {
3863
+ fetch,
3864
+ fetchJson,
3865
+ accessTokenStringFactory,
3866
+ config: {
3867
+ ...config,
3868
+ apiUrl
3869
+ },
3870
+ zohoRateLimiter: fetchHandler._rateLimiter
3871
+ };
3872
+ const zohoSign = {
3873
+ signContext
3874
+ };
3875
+ return zohoSign;
3876
+ };
3877
+ }
3878
+
3879
+ /**
3880
+ * Creates a function that exchanges a refresh token for a new short-lived access token
3881
+ * via the Zoho OAuth `/oauth/v2/token` endpoint.
3882
+ *
3883
+ * The returned function accepts optional overrides for client credentials and refresh token.
3884
+ * When omitted, values are read from the {@link ZohoAccountsContext}'s config.
3885
+ *
3886
+ * @param context - Authenticated Zoho Accounts context providing fetch and config
3887
+ * @returns Function that exchanges a refresh token for an access token
3888
+ *
3889
+ * @example
3890
+ * ```typescript
3891
+ * const getAccessToken = zohoAccountsAccessToken(accountsContext);
3892
+ *
3893
+ * // Using config defaults:
3894
+ * const { access_token, expires_in } = await getAccessToken();
3895
+ *
3896
+ * // With overrides:
3897
+ * const response = await getAccessToken({
3898
+ * client: { clientId: 'other-id', clientSecret: 'other-secret' },
3899
+ * refreshToken: 'other-refresh-token'
3900
+ * });
3901
+ * ```
3902
+ *
3903
+ * @see https://www.zoho.com/accounts/protocol/oauth/web-apps/access-token-expiry.html
2095
3904
  */
2096
3905
  function zohoAccountsAccessToken(context) {
2097
3906
  return (input) => {
@@ -2100,9 +3909,62 @@ function zohoAccountsAccessToken(context) {
2100
3909
  const clientId = client?.clientId ?? configClientId;
2101
3910
  const clientSecret = client?.clientSecret ?? configClientSecret;
2102
3911
  const refreshToken = inputRefreshToken ?? configRefreshToken;
2103
- return context.fetchJson(`/oauth/v2/token?grant_type=refresh_token&client_id=${clientId}&client_secret=${clientSecret}&refresh_token=${refreshToken}`, zohoAccountsApiFetchJsonInput('POST'));
3912
+ const params = makeUrlSearchParams({
3913
+ grant_type: 'refresh_token',
3914
+ client_id: clientId,
3915
+ client_secret: clientSecret,
3916
+ refresh_token: refreshToken
3917
+ });
3918
+ return context.fetchJson(`/oauth/v2/token?${params}`, zohoAccountsApiFetchJsonInput('POST'));
2104
3919
  };
2105
3920
  }
3921
+ /**
3922
+ * Creates a function that exchanges a single-use OAuth authorization code for
3923
+ * an access token and (optionally) a long-lived refresh token via the Zoho
3924
+ * `/oauth/v2/token` endpoint with `grant_type=authorization_code`.
3925
+ *
3926
+ * This is the second step of the Zoho OAuth 2.0 web server flow:
3927
+ * 1. Redirect the user to Zoho's consent page to obtain an authorization code
3928
+ * 2. Exchange the authorization code for tokens using this function
3929
+ * 3. Use the refresh token with {@link zohoAccountsAccessToken} to obtain new access tokens
3930
+ *
3931
+ * The refresh token is only returned if `access_type=offline` was passed during
3932
+ * the initial authorization request.
3933
+ *
3934
+ * @param context - Zoho Accounts context providing fetch and config (clientId/clientSecret)
3935
+ * @returns Function that exchanges an authorization code for tokens
3936
+ *
3937
+ * @example
3938
+ * ```typescript
3939
+ * const exchangeCode = zohoAccountsRefreshTokenFromAuthorizationCode(accountsContext);
3940
+ *
3941
+ * const { access_token, refresh_token } = await exchangeCode({
3942
+ * code: '1000.abc123.def456',
3943
+ * redirectUri: 'https://myapp.example.com/oauth/callback'
3944
+ * });
3945
+ * ```
3946
+ *
3947
+ * @see https://www.zoho.com/accounts/protocol/oauth/web-apps/access-token.html
3948
+ */
3949
+ function zohoAccountsRefreshTokenFromAuthorizationCode(context) {
3950
+ return (input) => {
3951
+ const { clientId: configClientId, clientSecret: configClientSecret } = context.config;
3952
+ const { client, code, redirectUri } = input;
3953
+ const clientId = client?.clientId ?? configClientId;
3954
+ const clientSecret = client?.clientSecret ?? configClientSecret;
3955
+ const params = makeUrlSearchParams({
3956
+ grant_type: 'authorization_code',
3957
+ client_id: clientId,
3958
+ client_secret: clientSecret,
3959
+ code,
3960
+ redirect_uri: redirectUri
3961
+ });
3962
+ return context.fetchJson(`/oauth/v2/token?${params}`, zohoAccountsApiFetchJsonInput('POST'));
3963
+ };
3964
+ }
3965
+ /**
3966
+ * Constructs a standard {@link FetchJsonInput} for Zoho Accounts API calls with the given HTTP method and optional body.
3967
+ */
2106
3968
  function zohoAccountsApiFetchJsonInput(method, body) {
2107
3969
  const result = {
2108
3970
  method,
@@ -2124,6 +3986,38 @@ function zohoAccountsConfigApiUrl(input) {
2124
3986
  }
2125
3987
  }
2126
3988
 
3989
+ /**
3990
+ * Creates a {@link ZohoAccountsFactory} from the given configuration.
3991
+ *
3992
+ * The factory pre-initializes shared resources (rate limiter) once, then produces
3993
+ * {@link ZohoAccounts} client instances for each {@link ZohoAccountsConfig}. Each client
3994
+ * handles OAuth token refresh via the configured `refreshToken`, `clientId`, and `clientSecret`,
3995
+ * with in-memory caching and optional external {@link ZohoAccessTokenCache} support.
3996
+ *
3997
+ * The Accounts client is the foundation for CRM, Recruit, and Sign clients, as it provides
3998
+ * the {@link ZohoAccountsContext} needed for OAuth token management.
3999
+ *
4000
+ * @param factoryConfig - Configuration providing optional fetch and logging overrides
4001
+ * @returns A factory function that creates authenticated Zoho Accounts clients
4002
+ * @throws {Error} If `refreshToken`, `clientId`, or `clientSecret` are missing from the config
4003
+ *
4004
+ * @example
4005
+ * ```typescript
4006
+ * const factory = zohoAccountsFactory({});
4007
+ *
4008
+ * const zohoAccounts = factory({
4009
+ * refreshToken: 'your-refresh-token',
4010
+ * clientId: 'your-client-id',
4011
+ * clientSecret: 'your-client-secret',
4012
+ * apiUrl: 'us'
4013
+ * });
4014
+ *
4015
+ * // Pass the accounts context to CRM/Recruit/Sign factories:
4016
+ * const crmFactory = zohoCrmFactory({
4017
+ * accountsContext: zohoAccounts.accountsContext
4018
+ * });
4019
+ * ```
4020
+ */
2127
4021
  function zohoAccountsFactory(factoryConfig) {
2128
4022
  const fetchHandler = zohoRateLimitedFetchHandler();
2129
4023
  const { logZohoServerErrorFunction, fetchFactory = (input) => fetchApiFetchService.makeFetch({
@@ -2190,10 +4084,20 @@ function zohoAccountsFactory(factoryConfig) {
2190
4084
  };
2191
4085
  }
2192
4086
  /**
2193
- * Creates a ZohoAccountsZohoAccessTokenFactoryConfig
4087
+ * Creates a {@link ZohoAccessTokenFactory} that manages access token lifecycle with
4088
+ * in-memory caching, optional external cache support, and automatic refresh.
2194
4089
  *
2195
- * @param config
2196
- * @returns
4090
+ * Token resolution order:
4091
+ * 1. Return the in-memory cached token if valid (not expired within the buffer)
4092
+ * 2. Load from the external {@link ZohoAccessTokenCache} if available
4093
+ * 3. Fetch a fresh token via the {@link ZohoAccessTokenRefresher}
4094
+ *
4095
+ * The returned function also exposes a `resetAccessToken()` method to invalidate
4096
+ * the current cached token, typically called on {@link ZohoInvalidTokenError}.
4097
+ *
4098
+ * @param config - Token refresh, caching, and expiration buffer configuration
4099
+ * @returns A token factory function with `resetAccessToken` for cache invalidation
4100
+ * @throws {ZohoAccountsAuthFailureError} If the token refresher fails
2197
4101
  */
2198
4102
  function zohoAccountsZohoAccessTokenFactory(config) {
2199
4103
  const { tokenRefresher, accessTokenCache, tokenExpirationBuffer: inputTokenExpirationBuffer } = config;
@@ -2248,14 +4152,21 @@ function safeZohoDateTimeString(date) {
2248
4152
  return date != null ? zohoDateTimeString(date) : date;
2249
4153
  }
2250
4154
  /**
2251
- * Converts the input date to a Zoho date.
4155
+ * Converts a {@link Date} to a {@link ZohoDateTimeString} by stripping milliseconds
4156
+ * from the ISO 8601 representation.
2252
4157
  *
2253
- * @param date
2254
- * @returns
4158
+ * @param date - Date to convert
4159
+ * @returns Zoho-formatted date-time string (e.g. `'2019-05-02T11:17:33Z'`)
4160
+ *
4161
+ * @example
4162
+ * ```typescript
4163
+ * zohoDateTimeString(new Date('2019-05-02T11:17:33.000Z'));
4164
+ * // => '2019-05-02T11:17:33Z'
4165
+ * ```
2255
4166
  */
2256
4167
  function zohoDateTimeString(date) {
2257
4168
  const isoDate = date.toISOString();
2258
4169
  return isoDate.substring(0, isoDate.length - 5) + 'Z';
2259
4170
  }
2260
4171
 
2261
- export { DEFAULT_ZOHO_API_RATE_LIMIT, DEFAULT_ZOHO_API_RATE_LIMIT_RESET_PERIOD, DEFAULT_ZOHO_RATE_LIMITED_TOO_MANY_REQUETS_LOG_FUNCTION, MAX_ZOHO_CRM_SEARCH_MODULE_RECORDS_CRITERIA, MAX_ZOHO_RECRUIT_SEARCH_MODULE_RECORDS_CRITERIA, ZOHO_ACCOUNTS_INVALID_CLIENT_ERROR_CODE, ZOHO_ACCOUNTS_INVALID_CODE_ERROR_CODE, ZOHO_ACCOUNTS_US_API_URL, ZOHO_CRM_ADD_TAGS_TO_RECORDS_MAX_IDS_ALLOWED, ZOHO_CRM_ALREADY_ASSOCIATED_ERROR_CODE, ZOHO_CRM_ATTACHMENTS_MODULE, ZOHO_CRM_ATTACHMENT_MAX_SIZE, ZOHO_CRM_CONTACTS_MODULE, ZOHO_CRM_CRUD_FUNCTION_MAX_RECORDS_LIMIT, ZOHO_CRM_EMAILS_MODULE, ZOHO_CRM_LEADS_MODULE, ZOHO_CRM_NOTES_MODULE, ZOHO_CRM_RECORD_ATTACHMENT_METADATA_ATTACH_TYPE_RESUME, ZOHO_CRM_REMOVE_TAGS_FROM_RECORDS_MAX_IDS_ALLOWED, ZOHO_CRM_SERVICE_NAME, ZOHO_CRM_TAG_NAME_MAX_LENGTH, ZOHO_CRM_TASKS_MODULE, ZOHO_DATA_ARRAY_BLANK_ERROR_CODE, ZOHO_DUPLICATE_DATA_ERROR_CODE, ZOHO_ERROR_STATUS, ZOHO_FAILURE_ERROR_CODE, ZOHO_INTERNAL_ERROR_CODE, ZOHO_INVALID_AUTHORIZATION_ERROR_CODE, ZOHO_INVALID_DATA_ERROR_CODE, ZOHO_INVALID_QUERY_ERROR_CODE, ZOHO_INVALID_TOKEN_ERROR_CODE, ZOHO_MANDATORY_NOT_FOUND_ERROR_CODE, ZOHO_RATE_LIMIT_LIMIT_HEADER, ZOHO_RATE_LIMIT_REMAINING_HEADER, ZOHO_RATE_LIMIT_RESET_HEADER, ZOHO_RECRUIT_ADD_TAGS_TO_RECORDS_MAX_IDS_ALLOWED, ZOHO_RECRUIT_ALREADY_ASSOCIATED_ERROR_CODE, ZOHO_RECRUIT_ATTACHMENTS_MODULE, ZOHO_RECRUIT_ATTACHMENT_MAX_SIZE, ZOHO_RECRUIT_CANDIDATES_MODULE, ZOHO_RECRUIT_CRUD_FUNCTION_MAX_RECORDS_LIMIT, ZOHO_RECRUIT_EMAILS_MODULE, ZOHO_RECRUIT_JOB_OPENINGS_MODULE, ZOHO_RECRUIT_NOTES_MODULE, ZOHO_RECRUIT_RECORD_ATTACHMENT_METADATA_ATTACH_TYPE_RESUME, ZOHO_RECRUIT_REMOVE_TAGS_FROM_RECORDS_MAX_IDS_ALLOWED, ZOHO_RECRUIT_SERVICE_NAME, ZOHO_RECRUIT_TAG_NAME_MAX_LENGTH, ZOHO_SUCCESS_CODE, ZOHO_SUCCESS_STATUS, ZOHO_TOO_MANY_REQUESTS_ERROR_CODE, ZOHO_TOO_MANY_REQUESTS_HTTP_STATUS_CODE, ZohoAccountsAccessTokenError, ZohoAccountsAuthFailureError, ZohoCrmExecuteRestApiFunctionError, ZohoCrmRecordCrudDuplicateDataError, ZohoCrmRecordCrudError, ZohoCrmRecordCrudInvalidDataError, ZohoCrmRecordCrudMandatoryFieldNotFoundError, ZohoCrmRecordCrudNoMatchingRecordError, ZohoCrmRecordNoContentError, ZohoInternalError, ZohoInvalidAuthorizationError, ZohoInvalidQueryError, ZohoInvalidTokenError, ZohoRecruitExecuteRestApiFunctionError, ZohoRecruitRecordCrudDuplicateDataError, ZohoRecruitRecordCrudError, ZohoRecruitRecordCrudInvalidDataError, ZohoRecruitRecordCrudMandatoryFieldNotFoundError, ZohoRecruitRecordCrudNoMatchingRecordError, ZohoRecruitRecordNoContentError, ZohoServerError, ZohoServerFetchResponseDataArrayError, ZohoServerFetchResponseError, ZohoTooManyRequestsError, addTagsToRecords, assertRecordDataArrayResultHasContent, assertZohoCrmRecordDataArrayResultHasContent, assertZohoRecruitRecordDataArrayResultHasContent, createNotes, createNotesForRecord, createTagsForModule, deleteAttachmentFromRecord, deleteNotes, deleteRecord, downloadAttachmentForRecord, emptyZohoPageResult, escapeZohoCrmFieldValueForCriteriaString, executeRestApiFunction, getAttachmentsForRecord, getAttachmentsForRecordPageFactory, getEmailsForRecord, getEmailsForRecordPageFactory, getNotesForRecord, getNotesForRecordPageFactory, getRecordById, getRecords, getRelatedRecordsFunctionFactory, getTagsForModule, getTagsForModulePageFactory, handleZohoAccountsErrorFetch, handleZohoCrmErrorFetch, handleZohoErrorFetchFactory, handleZohoRecruitErrorFetch, insertRecord, interceptZohoAccounts200StatusWithErrorResponse, interceptZohoCrm200StatusWithErrorResponse, interceptZohoErrorResponseFactory, interceptZohoRecruit200StatusWithErrorResponse, isZohoCrmValidUrl, isZohoRecruitValidUrl, isZohoServerErrorResponseDataArrayRef, logZohoAccountsErrorToConsole, logZohoCrmErrorToConsole, logZohoRecruitErrorToConsole, logZohoServerErrorFunction, parseZohoAccountsError, parseZohoAccountsServerErrorResponseData, parseZohoCrmError, parseZohoCrmServerErrorResponseData, parseZohoRecruitError, parseZohoRecruitServerErrorResponseData, parseZohoServerErrorResponseData, removeTagsFromRecords, safeZohoDateTimeString, searchRecords, searchRecordsPageFactory, tryFindZohoServerErrorData, updateRecord, uploadAttachmentForRecord, upsertRecord, zohoAccessTokenStringFactory, zohoAccountsAccessToken, zohoAccountsApiFetchJsonInput, zohoAccountsConfigApiUrl, zohoAccountsFactory, zohoAccountsZohoAccessTokenFactory, zohoCrmAddTagsToRecords, zohoCrmAddTagsToRecordsRequestBody, zohoCrmApiFetchJsonInput, zohoCrmCatchZohoCrmChangeObjectLikeResponseError, zohoCrmChangeObjectLikeResponseSuccessAndErrorPairs, zohoCrmConfigApiUrl, zohoCrmCreateNotes, zohoCrmCreateNotesForRecord, zohoCrmCreateTagsForModule, zohoCrmDeleteAttachmentFromRecord, zohoCrmDeleteNotes, zohoCrmDeleteRecord, zohoCrmDeleteTag, zohoCrmDownloadAttachmentForRecord, zohoCrmExecuteRestApiFunction, zohoCrmFactory, zohoCrmGetAttachmentsForRecord, zohoCrmGetAttachmentsForRecordPageFactory, zohoCrmGetEmailsForRecord, zohoCrmGetEmailsForRecordPageFactory, zohoCrmGetNotesForRecord, zohoCrmGetNotesForRecordPageFactory, zohoCrmGetRecordById, zohoCrmGetRecords, zohoCrmGetRelatedRecordsFunctionFactory, zohoCrmGetTagsForModule, zohoCrmGetTagsForModulePageFactory, zohoCrmInsertRecord, zohoCrmMultiRecordResult, zohoCrmRecordCrudError, zohoCrmRemoveTagsFromRecords, zohoCrmSearchRecords, zohoCrmSearchRecordsCriteriaEntryToCriteriaString, zohoCrmSearchRecordsCriteriaString, zohoCrmSearchRecordsCriteriaStringForTree, zohoCrmSearchRecordsPageFactory, zohoCrmUpdateRecord, zohoCrmUploadAttachmentForRecord, zohoCrmUpsertRecord, zohoCrmUrlSearchParams, zohoCrmUrlSearchParamsMinusIdAndModule, zohoCrmUrlSearchParamsMinusModule, zohoDateTimeString, zohoFetchPageFactory, zohoRateLimitHeaderDetails, zohoRateLimitedFetchHandler, zohoRecruitAddTagsToRecords, zohoRecruitApiFetchJsonInput, zohoRecruitAssociateCandidateRecordsWithJobOpenings, zohoRecruitChangeObjectLikeResponseSuccessAndErrorPairs, zohoRecruitConfigApiUrl, zohoRecruitCreateNotes, zohoRecruitCreateNotesForRecord, zohoRecruitCreateTagsForModule, zohoRecruitDeleteAttachmentFromRecord, zohoRecruitDeleteNotes, zohoRecruitDeleteRecord, zohoRecruitDownloadAttachmentForRecord, zohoRecruitExecuteRestApiFunction, zohoRecruitFactory, zohoRecruitGetAttachmentsForRecord, zohoRecruitGetAttachmentsForRecordPageFactory, zohoRecruitGetEmailsForRecord, zohoRecruitGetEmailsForRecordPageFactory, zohoRecruitGetNotesForRecord, zohoRecruitGetNotesForRecordPageFactory, zohoRecruitGetRecordById, zohoRecruitGetRecords, zohoRecruitGetRelatedRecordsFunctionFactory, zohoRecruitGetTagsForModule, zohoRecruitGetTagsForModulePageFactory, zohoRecruitInsertRecord, zohoRecruitMultiRecordResult, zohoRecruitRecordCrudError, zohoRecruitRemoveTagsFromRecords, zohoRecruitSearchAssociatedRecords, zohoRecruitSearchCandidateAssociatedJobOpeningRecords, zohoRecruitSearchCandidateAssociatedJobOpeningRecordsPageFactory, zohoRecruitSearchJobOpeningAssociatedCandidateRecords, zohoRecruitSearchJobOpeningAssociatedCandidateRecordsPageFactory, zohoRecruitSearchRecords, zohoRecruitSearchRecordsCriteriaEntryToCriteriaString, zohoRecruitSearchRecordsCriteriaString, zohoRecruitSearchRecordsCriteriaStringForTree, zohoRecruitSearchRecordsPageFactory, zohoRecruitUpdateRecord, zohoRecruitUploadAttachmentForRecord, zohoRecruitUpsertRecord, zohoRecruitUrlSearchParams, zohoRecruitUrlSearchParamsMinusIdAndModule, zohoRecruitUrlSearchParamsMinusModule, zohoServerErrorData };
4172
+ export { DEFAULT_ZOHO_API_RATE_LIMIT, DEFAULT_ZOHO_API_RATE_LIMIT_RESET_PERIOD, DEFAULT_ZOHO_RATE_LIMITED_TOO_MANY_REQUETS_LOG_FUNCTION, MAX_ZOHO_CRM_SEARCH_MODULE_RECORDS_CRITERIA, MAX_ZOHO_RECRUIT_SEARCH_MODULE_RECORDS_CRITERIA, ZOHO_ACCOUNTS_INVALID_CLIENT_ERROR_CODE, ZOHO_ACCOUNTS_INVALID_CODE_ERROR_CODE, ZOHO_ACCOUNTS_US_API_URL, ZOHO_CRM_ADD_TAGS_TO_RECORDS_MAX_IDS_ALLOWED, ZOHO_CRM_ALREADY_ASSOCIATED_ERROR_CODE, ZOHO_CRM_ATTACHMENTS_MODULE, ZOHO_CRM_ATTACHMENT_MAX_SIZE, ZOHO_CRM_CONTACTS_MODULE, ZOHO_CRM_CRUD_FUNCTION_MAX_RECORDS_LIMIT, ZOHO_CRM_EMAILS_MODULE, ZOHO_CRM_LEADS_MODULE, ZOHO_CRM_NOTES_MODULE, ZOHO_CRM_RECORD_ATTACHMENT_METADATA_ATTACH_TYPE_RESUME, ZOHO_CRM_REMOVE_TAGS_FROM_RECORDS_MAX_IDS_ALLOWED, ZOHO_CRM_SERVICE_NAME, ZOHO_CRM_TAG_NAME_MAX_LENGTH, ZOHO_CRM_TASKS_MODULE, ZOHO_DATA_ARRAY_BLANK_ERROR_CODE, ZOHO_DUPLICATE_DATA_ERROR_CODE, ZOHO_ERROR_STATUS, ZOHO_FAILURE_ERROR_CODE, ZOHO_INTERNAL_ERROR_CODE, ZOHO_INVALID_AUTHORIZATION_ERROR_CODE, ZOHO_INVALID_DATA_ERROR_CODE, ZOHO_INVALID_QUERY_ERROR_CODE, ZOHO_INVALID_TOKEN_ERROR_CODE, ZOHO_MANDATORY_NOT_FOUND_ERROR_CODE, ZOHO_RATE_LIMIT_LIMIT_HEADER, ZOHO_RATE_LIMIT_REMAINING_HEADER, ZOHO_RATE_LIMIT_RESET_HEADER, ZOHO_RECRUIT_ADD_TAGS_TO_RECORDS_MAX_IDS_ALLOWED, ZOHO_RECRUIT_ALREADY_ASSOCIATED_ERROR_CODE, ZOHO_RECRUIT_ATTACHMENTS_MODULE, ZOHO_RECRUIT_ATTACHMENT_MAX_SIZE, ZOHO_RECRUIT_CANDIDATES_MODULE, ZOHO_RECRUIT_CRUD_FUNCTION_MAX_RECORDS_LIMIT, ZOHO_RECRUIT_EMAILS_MODULE, ZOHO_RECRUIT_JOB_OPENINGS_MODULE, ZOHO_RECRUIT_NOTES_MODULE, ZOHO_RECRUIT_RECORD_ATTACHMENT_METADATA_ATTACH_TYPE_RESUME, ZOHO_RECRUIT_REMOVE_TAGS_FROM_RECORDS_MAX_IDS_ALLOWED, ZOHO_RECRUIT_SERVICE_NAME, ZOHO_RECRUIT_TAG_NAME_MAX_LENGTH, ZOHO_SIGN_SERVICE_NAME, ZOHO_SUCCESS_CODE, ZOHO_SUCCESS_STATUS, ZOHO_TOO_MANY_REQUESTS_ERROR_CODE, ZOHO_TOO_MANY_REQUESTS_HTTP_STATUS_CODE, ZohoAccountsAccessTokenError, ZohoAccountsAuthFailureError, ZohoCrmExecuteRestApiFunctionError, ZohoCrmRecordCrudDuplicateDataError, ZohoCrmRecordCrudError, ZohoCrmRecordCrudInvalidDataError, ZohoCrmRecordCrudMandatoryFieldNotFoundError, ZohoCrmRecordCrudNoMatchingRecordError, ZohoCrmRecordNoContentError, ZohoInternalError, ZohoInvalidAuthorizationError, ZohoInvalidQueryError, ZohoInvalidTokenError, ZohoRecruitExecuteRestApiFunctionError, ZohoRecruitRecordCrudDuplicateDataError, ZohoRecruitRecordCrudError, ZohoRecruitRecordCrudInvalidDataError, ZohoRecruitRecordCrudMandatoryFieldNotFoundError, ZohoRecruitRecordCrudNoMatchingRecordError, ZohoRecruitRecordNoContentError, ZohoServerError, ZohoServerFetchResponseDataArrayError, ZohoServerFetchResponseError, ZohoTooManyRequestsError, addTagsToRecords, assertRecordDataArrayResultHasContent, assertZohoCrmRecordDataArrayResultHasContent, assertZohoRecruitRecordDataArrayResultHasContent, createNotes, createNotesForRecord, createTagsForModule, deleteAttachmentFromRecord, deleteNotes, deleteRecord, downloadAttachmentForRecord, emptyZohoPageResult, escapeZohoCrmFieldValueForCriteriaString, executeRestApiFunction, getAttachmentsForRecord, getAttachmentsForRecordPageFactory, getEmailsForRecord, getEmailsForRecordPageFactory, getNotesForRecord, getNotesForRecordPageFactory, getRecordById, getRecords, getRelatedRecordsFunctionFactory, getTagsForModule, getTagsForModulePageFactory, handleZohoAccountsErrorFetch, handleZohoCrmErrorFetch, handleZohoErrorFetchFactory, handleZohoRecruitErrorFetch, handleZohoSignErrorFetch, insertRecord, interceptZohoAccounts200StatusWithErrorResponse, interceptZohoCrm200StatusWithErrorResponse, interceptZohoErrorResponseFactory, interceptZohoRecruit200StatusWithErrorResponse, interceptZohoSign200StatusWithErrorResponse, isZohoCrmValidUrl, isZohoRecruitValidUrl, isZohoServerErrorResponseDataArrayRef, logZohoAccountsErrorToConsole, logZohoCrmErrorToConsole, logZohoRecruitErrorToConsole, logZohoServerErrorFunction, logZohoSignErrorToConsole, parseZohoAccountsError, parseZohoAccountsServerErrorResponseData, parseZohoCrmError, parseZohoCrmServerErrorResponseData, parseZohoRecruitError, parseZohoRecruitServerErrorResponseData, parseZohoServerErrorResponseData, parseZohoSignError, parseZohoSignServerErrorResponseData, removeTagsFromRecords, safeZohoDateTimeString, searchRecords, searchRecordsPageFactory, tryFindZohoServerErrorData, updateRecord, uploadAttachmentForRecord, upsertRecord, zohoAccessTokenStringFactory, zohoAccountsAccessToken, zohoAccountsApiFetchJsonInput, zohoAccountsConfigApiUrl, zohoAccountsFactory, zohoAccountsRefreshTokenFromAuthorizationCode, zohoAccountsZohoAccessTokenFactory, zohoCrmAddTagsToRecords, zohoCrmAddTagsToRecordsRequestBody, zohoCrmApiFetchJsonInput, zohoCrmCatchZohoCrmChangeObjectLikeResponseError, zohoCrmChangeObjectLikeResponseSuccessAndErrorPairs, zohoCrmConfigApiUrl, zohoCrmCreateNotes, zohoCrmCreateNotesForRecord, zohoCrmCreateTagsForModule, zohoCrmDeleteAttachmentFromRecord, zohoCrmDeleteNotes, zohoCrmDeleteRecord, zohoCrmDeleteTag, zohoCrmDownloadAttachmentForRecord, zohoCrmExecuteRestApiFunction, zohoCrmFactory, zohoCrmGetAttachmentsForRecord, zohoCrmGetAttachmentsForRecordPageFactory, zohoCrmGetEmailsForRecord, zohoCrmGetEmailsForRecordPageFactory, zohoCrmGetNotesForRecord, zohoCrmGetNotesForRecordPageFactory, zohoCrmGetRecordById, zohoCrmGetRecords, zohoCrmGetRelatedRecordsFunctionFactory, zohoCrmGetTagsForModule, zohoCrmGetTagsForModulePageFactory, zohoCrmInsertRecord, zohoCrmMultiRecordResult, zohoCrmRecordCrudError, zohoCrmRemoveTagsFromRecords, zohoCrmSearchRecords, zohoCrmSearchRecordsCriteriaEntryToCriteriaString, zohoCrmSearchRecordsCriteriaString, zohoCrmSearchRecordsCriteriaStringForTree, zohoCrmSearchRecordsPageFactory, zohoCrmUpdateRecord, zohoCrmUploadAttachmentForRecord, zohoCrmUpsertRecord, zohoCrmUrlSearchParams, zohoCrmUrlSearchParamsMinusIdAndModule, zohoCrmUrlSearchParamsMinusModule, zohoDateTimeString, zohoFetchPageFactory, zohoRateLimitHeaderDetails, zohoRateLimitedFetchHandler, zohoRecruitAddTagsToRecords, zohoRecruitApiFetchJsonInput, zohoRecruitAssociateCandidateRecordsWithJobOpenings, zohoRecruitChangeObjectLikeResponseSuccessAndErrorPairs, zohoRecruitConfigApiUrl, zohoRecruitCreateNotes, zohoRecruitCreateNotesForRecord, zohoRecruitCreateTagsForModule, zohoRecruitDeleteAttachmentFromRecord, zohoRecruitDeleteNotes, zohoRecruitDeleteRecord, zohoRecruitDownloadAttachmentForRecord, zohoRecruitExecuteRestApiFunction, zohoRecruitFactory, zohoRecruitGetAttachmentsForRecord, zohoRecruitGetAttachmentsForRecordPageFactory, zohoRecruitGetEmailsForRecord, zohoRecruitGetEmailsForRecordPageFactory, zohoRecruitGetNotesForRecord, zohoRecruitGetNotesForRecordPageFactory, zohoRecruitGetRecordById, zohoRecruitGetRecords, zohoRecruitGetRelatedRecordsFunctionFactory, zohoRecruitGetTagsForModule, zohoRecruitGetTagsForModulePageFactory, zohoRecruitInsertRecord, zohoRecruitMultiRecordResult, zohoRecruitRecordCrudError, zohoRecruitRemoveTagsFromRecords, zohoRecruitSearchAssociatedRecords, zohoRecruitSearchCandidateAssociatedJobOpeningRecords, zohoRecruitSearchCandidateAssociatedJobOpeningRecordsPageFactory, zohoRecruitSearchJobOpeningAssociatedCandidateRecords, zohoRecruitSearchJobOpeningAssociatedCandidateRecordsPageFactory, zohoRecruitSearchRecords, zohoRecruitSearchRecordsCriteriaEntryToCriteriaString, zohoRecruitSearchRecordsCriteriaString, zohoRecruitSearchRecordsCriteriaStringForTree, zohoRecruitSearchRecordsPageFactory, zohoRecruitUpdateRecord, zohoRecruitUploadAttachmentForRecord, zohoRecruitUpsertRecord, zohoRecruitUrlSearchParams, zohoRecruitUrlSearchParamsMinusIdAndModule, zohoRecruitUrlSearchParamsMinusModule, zohoServerErrorData, zohoSignConfigApiUrl, zohoSignCreateDocument, zohoSignDeleteDocument, zohoSignDownloadCompletionCertificate, zohoSignDownloadPdf, zohoSignExtendDocument, zohoSignFactory, zohoSignFetchPageFactory, zohoSignGetDocument, zohoSignGetDocumentFormData, zohoSignGetDocuments, zohoSignGetDocumentsPageFactory, zohoSignRetrieveFieldTypes, zohoSignSendDocumentForSignature, zohoSignUpdateDocument };