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