@2bad/bitrix 2.5.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/README.md +94 -188
  2. package/build/bin.d.ts +1 -0
  3. package/build/bin.js +160 -0
  4. package/build/client-Bk76ySik.js +769 -0
  5. package/build/index.d.ts +2881 -0
  6. package/build/index.js +1 -0
  7. package/package.json +46 -73
  8. package/build/main/bitrix.d.ts +0 -114
  9. package/build/main/bitrix.integration.test.d.ts +0 -0
  10. package/build/main/bitrix.integration.test.js +0 -31
  11. package/build/main/bitrix.js +0 -61
  12. package/build/main/bitrix.unit.test.d.ts +0 -1
  13. package/build/main/bitrix.unit.test.js +0 -44
  14. package/build/main/client/client.unit.test.d.ts +0 -1
  15. package/build/main/client/client.unit.test.js +0 -70
  16. package/build/main/client/hooks/addAccessToken.d.ts +0 -6
  17. package/build/main/client/hooks/addAccessToken.js +0 -13
  18. package/build/main/client/hooks/addAccessToken.unit.test.d.ts +0 -1
  19. package/build/main/client/hooks/addAccessToken.unit.test.js +0 -31
  20. package/build/main/client/index.d.ts +0 -15
  21. package/build/main/client/index.js +0 -53
  22. package/build/main/client/methods/batch.d.ts +0 -40
  23. package/build/main/client/methods/batch.js +0 -85
  24. package/build/main/client/methods/batch.unit.test.d.ts +0 -1
  25. package/build/main/client/methods/batch.unit.test.js +0 -494
  26. package/build/main/client/methods/call.d.ts +0 -17
  27. package/build/main/client/methods/call.js +0 -36
  28. package/build/main/client/methods/call.unit.test.d.ts +0 -1
  29. package/build/main/client/methods/call.unit.test.js +0 -183
  30. package/build/main/client/methods/list.d.ts +0 -30
  31. package/build/main/client/methods/list.js +0 -66
  32. package/build/main/client/methods/list.unit.test.d.ts +0 -1
  33. package/build/main/client/methods/list.unit.test.js +0 -277
  34. package/build/main/commands.d.ts +0 -10
  35. package/build/main/commands.js +0 -2
  36. package/build/main/methods.d.ts +0 -92
  37. package/build/main/methods.js +0 -41
  38. package/build/main/payloads.d.ts +0 -40
  39. package/build/main/payloads.js +0 -2
  40. package/build/main/services/common.d.ts +0 -22
  41. package/build/main/services/common.js +0 -2
  42. package/build/main/services/companies/entities.d.ts +0 -54
  43. package/build/main/services/companies/entities.js +0 -2
  44. package/build/main/services/companies/index.d.ts +0 -26
  45. package/build/main/services/companies/index.integration.test.d.ts +0 -1
  46. package/build/main/services/companies/index.integration.test.js +0 -19
  47. package/build/main/services/companies/index.js +0 -10
  48. package/build/main/services/companies/index.unit.test.d.ts +0 -1
  49. package/build/main/services/companies/index.unit.test.js +0 -72
  50. package/build/main/services/companies/methods.d.ts +0 -44
  51. package/build/main/services/companies/methods.js +0 -3
  52. package/build/main/services/contacts/entities.d.ts +0 -45
  53. package/build/main/services/contacts/entities.js +0 -2
  54. package/build/main/services/contacts/index.d.ts +0 -26
  55. package/build/main/services/contacts/index.integration.test.d.ts +0 -1
  56. package/build/main/services/contacts/index.integration.test.js +0 -19
  57. package/build/main/services/contacts/index.js +0 -10
  58. package/build/main/services/contacts/index.unit.test.d.ts +0 -1
  59. package/build/main/services/contacts/index.unit.test.js +0 -72
  60. package/build/main/services/contacts/methods.d.ts +0 -44
  61. package/build/main/services/contacts/methods.js +0 -3
  62. package/build/main/services/currencies/entities.d.ts +0 -15
  63. package/build/main/services/currencies/entities.js +0 -2
  64. package/build/main/services/deals/entities.d.ts +0 -43
  65. package/build/main/services/deals/entities.js +0 -2
  66. package/build/main/services/deals/index.d.ts +0 -26
  67. package/build/main/services/deals/index.integration.test.d.ts +0 -1
  68. package/build/main/services/deals/index.integration.test.js +0 -19
  69. package/build/main/services/deals/index.js +0 -10
  70. package/build/main/services/deals/index.unit.test.d.ts +0 -1
  71. package/build/main/services/deals/index.unit.test.js +0 -72
  72. package/build/main/services/deals/methods.d.ts +0 -44
  73. package/build/main/services/deals/methods.js +0 -3
  74. package/build/main/services/leads/entities.d.ts +0 -49
  75. package/build/main/services/leads/entities.js +0 -2
  76. package/build/main/services/leads/index.d.ts +0 -26
  77. package/build/main/services/leads/index.integration.test.d.ts +0 -1
  78. package/build/main/services/leads/index.integration.test.js +0 -19
  79. package/build/main/services/leads/index.js +0 -10
  80. package/build/main/services/leads/index.unit.test.d.ts +0 -1
  81. package/build/main/services/leads/index.unit.test.js +0 -72
  82. package/build/main/services/leads/methods.d.ts +0 -44
  83. package/build/main/services/leads/methods.js +0 -3
  84. package/build/main/services/statuses/entities.d.ts +0 -16
  85. package/build/main/services/statuses/entities.js +0 -2
  86. package/build/main/services/statuses/index.d.ts +0 -14
  87. package/build/main/services/statuses/index.integration.test.d.ts +0 -1
  88. package/build/main/services/statuses/index.integration.test.js +0 -90
  89. package/build/main/services/statuses/index.js +0 -11
  90. package/build/main/services/statuses/index.unit.test.d.ts +0 -1
  91. package/build/main/services/statuses/index.unit.test.js +0 -60
  92. package/build/main/services/statuses/methods.d.ts +0 -51
  93. package/build/main/services/statuses/methods.js +0 -3
  94. package/build/main/services/users/entities.d.ts +0 -40
  95. package/build/main/services/users/entities.js +0 -2
  96. package/build/main/services/users/index.d.ts +0 -9
  97. package/build/main/services/users/index.integration.test.d.ts +0 -1
  98. package/build/main/services/users/index.integration.test.js +0 -33
  99. package/build/main/services/users/index.js +0 -7
  100. package/build/main/services/users/index.unit.test.d.ts +0 -1
  101. package/build/main/services/users/index.unit.test.js +0 -25
  102. package/build/main/services/users/methods.d.ts +0 -17
  103. package/build/main/services/users/methods.js +0 -3
  104. package/build/main/utils/Diff.d.ts +0 -1
  105. package/build/main/utils/Diff.js +0 -2
  106. package/build/main/utils/ExtractValue.d.ts +0 -6
  107. package/build/main/utils/ExtractValue.js +0 -2
  108. package/build/main/utils/isArray.d.ts +0 -2
  109. package/build/main/utils/isArray.js +0 -4
  110. package/build/module/bitrix.d.ts +0 -114
  111. package/build/module/bitrix.integration.test.d.ts +0 -0
  112. package/build/module/bitrix.integration.test.js +0 -31
  113. package/build/module/bitrix.js +0 -40
  114. package/build/module/bitrix.unit.test.d.ts +0 -1
  115. package/build/module/bitrix.unit.test.js +0 -16
  116. package/build/module/client/client.unit.test.d.ts +0 -1
  117. package/build/module/client/client.unit.test.js +0 -65
  118. package/build/module/client/hooks/addAccessToken.d.ts +0 -6
  119. package/build/module/client/hooks/addAccessToken.js +0 -11
  120. package/build/module/client/hooks/addAccessToken.unit.test.d.ts +0 -1
  121. package/build/module/client/hooks/addAccessToken.unit.test.js +0 -26
  122. package/build/module/client/index.d.ts +0 -15
  123. package/build/module/client/index.js +0 -48
  124. package/build/module/client/methods/batch.d.ts +0 -40
  125. package/build/module/client/methods/batch.js +0 -76
  126. package/build/module/client/methods/batch.unit.test.d.ts +0 -1
  127. package/build/module/client/methods/batch.unit.test.js +0 -466
  128. package/build/module/client/methods/call.d.ts +0 -17
  129. package/build/module/client/methods/call.js +0 -28
  130. package/build/module/client/methods/call.unit.test.d.ts +0 -1
  131. package/build/module/client/methods/call.unit.test.js +0 -155
  132. package/build/module/client/methods/list.d.ts +0 -30
  133. package/build/module/client/methods/list.js +0 -55
  134. package/build/module/client/methods/list.unit.test.d.ts +0 -1
  135. package/build/module/client/methods/list.unit.test.js +0 -252
  136. package/build/module/commands.d.ts +0 -10
  137. package/build/module/commands.js +0 -1
  138. package/build/module/methods.d.ts +0 -92
  139. package/build/module/methods.js +0 -38
  140. package/build/module/payloads.d.ts +0 -40
  141. package/build/module/payloads.js +0 -1
  142. package/build/module/services/common.d.ts +0 -22
  143. package/build/module/services/common.js +0 -1
  144. package/build/module/services/companies/entities.d.ts +0 -54
  145. package/build/module/services/companies/entities.js +0 -1
  146. package/build/module/services/companies/index.d.ts +0 -26
  147. package/build/module/services/companies/index.integration.test.d.ts +0 -1
  148. package/build/module/services/companies/index.integration.test.js +0 -14
  149. package/build/module/services/companies/index.js +0 -8
  150. package/build/module/services/companies/index.unit.test.d.ts +0 -1
  151. package/build/module/services/companies/index.unit.test.js +0 -67
  152. package/build/module/services/companies/methods.d.ts +0 -44
  153. package/build/module/services/companies/methods.js +0 -1
  154. package/build/module/services/contacts/entities.d.ts +0 -45
  155. package/build/module/services/contacts/entities.js +0 -1
  156. package/build/module/services/contacts/index.d.ts +0 -26
  157. package/build/module/services/contacts/index.integration.test.d.ts +0 -1
  158. package/build/module/services/contacts/index.integration.test.js +0 -14
  159. package/build/module/services/contacts/index.js +0 -8
  160. package/build/module/services/contacts/index.unit.test.d.ts +0 -1
  161. package/build/module/services/contacts/index.unit.test.js +0 -67
  162. package/build/module/services/contacts/methods.d.ts +0 -44
  163. package/build/module/services/contacts/methods.js +0 -1
  164. package/build/module/services/currencies/entities.d.ts +0 -15
  165. package/build/module/services/currencies/entities.js +0 -1
  166. package/build/module/services/deals/entities.d.ts +0 -43
  167. package/build/module/services/deals/entities.js +0 -1
  168. package/build/module/services/deals/index.d.ts +0 -26
  169. package/build/module/services/deals/index.integration.test.d.ts +0 -1
  170. package/build/module/services/deals/index.integration.test.js +0 -14
  171. package/build/module/services/deals/index.js +0 -8
  172. package/build/module/services/deals/index.unit.test.d.ts +0 -1
  173. package/build/module/services/deals/index.unit.test.js +0 -67
  174. package/build/module/services/deals/methods.d.ts +0 -44
  175. package/build/module/services/deals/methods.js +0 -1
  176. package/build/module/services/leads/entities.d.ts +0 -49
  177. package/build/module/services/leads/entities.js +0 -1
  178. package/build/module/services/leads/index.d.ts +0 -26
  179. package/build/module/services/leads/index.integration.test.d.ts +0 -1
  180. package/build/module/services/leads/index.integration.test.js +0 -14
  181. package/build/module/services/leads/index.js +0 -8
  182. package/build/module/services/leads/index.unit.test.d.ts +0 -1
  183. package/build/module/services/leads/index.unit.test.js +0 -67
  184. package/build/module/services/leads/methods.d.ts +0 -44
  185. package/build/module/services/leads/methods.js +0 -1
  186. package/build/module/services/statuses/entities.d.ts +0 -16
  187. package/build/module/services/statuses/entities.js +0 -1
  188. package/build/module/services/statuses/index.d.ts +0 -14
  189. package/build/module/services/statuses/index.integration.test.d.ts +0 -1
  190. package/build/module/services/statuses/index.integration.test.js +0 -85
  191. package/build/module/services/statuses/index.js +0 -9
  192. package/build/module/services/statuses/index.unit.test.d.ts +0 -1
  193. package/build/module/services/statuses/index.unit.test.js +0 -55
  194. package/build/module/services/statuses/methods.d.ts +0 -51
  195. package/build/module/services/statuses/methods.js +0 -1
  196. package/build/module/services/users/entities.d.ts +0 -40
  197. package/build/module/services/users/entities.js +0 -1
  198. package/build/module/services/users/index.d.ts +0 -9
  199. package/build/module/services/users/index.integration.test.d.ts +0 -1
  200. package/build/module/services/users/index.integration.test.js +0 -28
  201. package/build/module/services/users/index.js +0 -5
  202. package/build/module/services/users/index.unit.test.d.ts +0 -1
  203. package/build/module/services/users/index.unit.test.js +0 -20
  204. package/build/module/services/users/methods.d.ts +0 -17
  205. package/build/module/services/users/methods.js +0 -1
  206. package/build/module/utils/Diff.d.ts +0 -1
  207. package/build/module/utils/Diff.js +0 -1
  208. package/build/module/utils/ExtractValue.d.ts +0 -6
  209. package/build/module/utils/ExtractValue.js +0 -1
  210. package/build/module/utils/isArray.d.ts +0 -2
  211. package/build/module/utils/isArray.js +0 -2
@@ -0,0 +1,769 @@
1
+ // source/encode.ts
2
+ const encodeValue = (value) => {
3
+ if (value === null) return "";
4
+ if (value === true) return "1";
5
+ if (value === false) return "0";
6
+ if (value instanceof Date) return encodeURIComponent(value.toISOString());
7
+ return encodeURIComponent(String(value));
8
+ };
9
+ const pairs = (key, value) => {
10
+ if (value === void 0) return [];
11
+ if (Array.isArray(value)) return value.flatMap((item, index) => pairs(`${key}[${index}]`, item));
12
+ if (typeof value === "object" && value !== null && !(value instanceof Date)) return Object.entries(value).flatMap(([name, item]) => pairs(`${key}[${name}]`, item));
13
+ return [`${encodeURIComponent(key)}=${encodeValue(value)}`];
14
+ };
15
+ /**
16
+ * Serializes params into a PHP style query string (`filter[>ID]=5&select[0]=ID`), the format Bitrix expects
17
+ * for commands inside a `batch` call
18
+ * @param params params of a single command
19
+ */
20
+ const encode = (params) => Object.entries(params).flatMap(([key, value]) => pairs(key, value)).join("&");
21
+ // source/errors.ts
22
+ var BitrixError = class extends Error {
23
+ name = "BitrixError";
24
+ code;
25
+ description;
26
+ method;
27
+ status;
28
+ command;
29
+ constructor({ code, description, method, status, command, cause }) {
30
+ const target = command === void 0 ? method : `${method} (command ${command})`;
31
+ super(`${target}: ${[code, description].filter(Boolean).join(" ")}`, { cause });
32
+ this.code = code;
33
+ this.description = description;
34
+ this.method = method;
35
+ this.status = status;
36
+ this.command = command;
37
+ }
38
+ };
39
+ // source/transport.ts
40
+ const RETRYABLE_CODES = /* @__PURE__ */ new Set([
41
+ "QUERY_LIMIT_EXCEEDED",
42
+ "OPERATION_TIME_LIMIT",
43
+ "INTERNAL_SERVER_ERROR"
44
+ ]);
45
+ const sleep = (ms, signal) => new Promise((resolve, reject) => {
46
+ const onAbort = () => {
47
+ clearTimeout(timer);
48
+ reject(signal?.reason);
49
+ };
50
+ const timer = setTimeout(() => {
51
+ signal?.removeEventListener("abort", onAbort);
52
+ resolve();
53
+ }, ms);
54
+ signal?.addEventListener("abort", onAbort, { once: true });
55
+ });
56
+ /**
57
+ * Time to wait before retrying an operating time limit error: until the limit resets when Bitrix said when,
58
+ * exponential backoff otherwise
59
+ * @param resetAt when the limit resets, in milliseconds since the epoch
60
+ * @param delay base backoff delay in milliseconds
61
+ * @param attempt zero based retry attempt
62
+ */
63
+ const operatingDelay = (resetAt, delay, attempt) => resetAt !== void 0 && resetAt > Date.now() ? resetAt - Date.now() : delay * 2 ** attempt;
64
+ const isRetryable = (error) => error instanceof BitrixError ? RETRYABLE_CODES.has(error.code) || error.status !== void 0 && error.status >= 500 : error instanceof TypeError;
65
+ const parse = async (response, method) => {
66
+ const text = await response.text();
67
+ let body;
68
+ try {
69
+ body = JSON.parse(text);
70
+ } catch (cause) {
71
+ throw new BitrixError({
72
+ code: "INVALID_RESPONSE",
73
+ description: `expected JSON, received ${text.slice(0, 100)}`,
74
+ method,
75
+ status: response.status,
76
+ cause
77
+ });
78
+ }
79
+ if (!response.ok || "error" in body) throw new BitrixError({
80
+ code: typeof body["error"] === "string" && body["error"] !== "" ? body["error"] : `HTTP_${response.status}`,
81
+ description: typeof body["error_description"] === "string" ? body["error_description"] : response.statusText,
82
+ method,
83
+ status: response.status
84
+ });
85
+ return body;
86
+ };
87
+ /**
88
+ * Sends a single REST method call as a JSON POST, applying auth, rate limiting and retries
89
+ * @param options endpoint, auth, fetch and hook settings
90
+ * @param limiter rate limiter every request waits for
91
+ * @param retry how often and how long to wait before retrying
92
+ * @param retry.retries attempts after the first one
93
+ * @param retry.delay base backoff delay in milliseconds
94
+ */
95
+ const createTransport = (options, limiter, { retries, delay }) => {
96
+ const { auth, onAuthExpired } = options;
97
+ const fetcher = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
98
+ const base = new URL(options.url.endsWith("/") ? options.url : `${options.url}/`);
99
+ const operatingResetAt = /* @__PURE__ */ new Map();
100
+ let refreshing;
101
+ const refreshAuth = async () => {
102
+ refreshing ??= (async () => {
103
+ try {
104
+ await onAuthExpired?.();
105
+ } finally {
106
+ refreshing = void 0;
107
+ }
108
+ })();
109
+ await refreshing;
110
+ };
111
+ const request = async (method, params, signal) => {
112
+ const token = typeof auth === "function" ? await auth() : auth;
113
+ const response = await fetcher(new URL(method, base), {
114
+ method: "POST",
115
+ headers: {
116
+ "content-type": "application/json",
117
+ accept: "application/json"
118
+ },
119
+ body: JSON.stringify(token === void 0 ? params : {
120
+ ...params,
121
+ auth: token
122
+ }),
123
+ ...signal && { signal }
124
+ });
125
+ return parse(response, method);
126
+ };
127
+ const send = async (method, params, signal, attempt) => {
128
+ await limiter.acquire(signal);
129
+ options.onRequest?.({
130
+ method,
131
+ params,
132
+ attempt
133
+ });
134
+ const started = Date.now();
135
+ let envelope;
136
+ try {
137
+ envelope = await request(method, params, signal);
138
+ } catch (error) {
139
+ options.onResponse?.({
140
+ method,
141
+ attempt,
142
+ duration: Date.now() - started,
143
+ error
144
+ });
145
+ throw error;
146
+ }
147
+ options.onResponse?.({
148
+ method,
149
+ attempt,
150
+ duration: Date.now() - started,
151
+ ...envelope.time && { time: envelope.time }
152
+ });
153
+ if (envelope.time?.operating_reset_at !== void 0) operatingResetAt.set(method, envelope.time.operating_reset_at * 1e3);
154
+ return envelope;
155
+ };
156
+ const backoff = (error, method, attempt) => {
157
+ if (error instanceof BitrixError && error.code === "QUERY_LIMIT_EXCEEDED") {
158
+ limiter.exhaust();
159
+ return 0;
160
+ }
161
+ if (error instanceof BitrixError && error.code === "OPERATION_TIME_LIMIT") return operatingDelay(operatingResetAt.get(method), delay, attempt);
162
+ return delay * 2 ** attempt;
163
+ };
164
+ return async (method, params = {}, callSignal) => {
165
+ const signals = [options.signal, callSignal].filter((signal) => signal !== void 0);
166
+ const signal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
167
+ let refreshed = false;
168
+ let sent = 0;
169
+ for (let attempt = 0;; attempt++) try {
170
+ return await send(method, params, signal, sent++);
171
+ } catch (error) {
172
+ if (signal?.aborted) throw signal.reason;
173
+ if (error instanceof BitrixError && error.code === "expired_token" && onAuthExpired && !refreshed) {
174
+ refreshed = true;
175
+ await refreshAuth();
176
+ attempt--;
177
+ continue;
178
+ }
179
+ if (attempt >= retries || !isRetryable(error)) throw error;
180
+ await sleep(backoff(error, method, attempt), signal);
181
+ }
182
+ };
183
+ };
184
+ const OPERATION_TIME_LIMIT = "OPERATION_TIME_LIMIT";
185
+ const chunk = (items, size) => Array.from({ length: Math.ceil(items.length / size) }, (_, index) => items.slice(index * size, (index + 1) * size));
186
+ const toCommand = ({ method, params }) => {
187
+ const query = params ? encode(params) : "";
188
+ return query === "" ? method : `${method}?${query}`;
189
+ };
190
+ /**
191
+ * Runs any number of commands through `batch`, 50 per request. Keyed commands give keyed results,
192
+ * arrays give arrays. A failed command lands in `errors` and does not reject the whole call.
193
+ * Commands that hit the operating time limit are sent again once the limit resets, unless they
194
+ * reference results of other commands with `$result`
195
+ * @param transport sends the `batch` requests
196
+ * @param retry how often and how long to wait before sending limited commands again
197
+ * @param retry.retries attempts after the first one
198
+ * @param retry.delay base backoff delay in milliseconds
199
+ */
200
+ const createBatch = (transport, { retries, delay }) => {
201
+ const send = async (entries, { halt = false, signal }) => {
202
+ const cmd = Object.fromEntries(entries.map(([key, command]) => [key, toCommand(command)]));
203
+ const { result, time } = await transport("batch", {
204
+ halt: halt ? 1 : 0,
205
+ cmd
206
+ }, signal);
207
+ const methods = new Map(entries.map(([key, command]) => [key, command.method]));
208
+ const errors = Object.fromEntries(Object.entries(result.result_error).map(([key, raw]) => [key, new BitrixError({
209
+ code: raw.error || "COMMAND_FAILED",
210
+ description: raw.error_description ?? "",
211
+ method: methods.get(key) ?? "batch",
212
+ command: key
213
+ })]));
214
+ const resetAt = time?.operating_reset_at === void 0 ? void 0 : time.operating_reset_at * 1e3;
215
+ return {
216
+ results: { ...result.result },
217
+ errors,
218
+ resetAt
219
+ };
220
+ };
221
+ const retryable = (entries, { results, errors }, halt) => {
222
+ const limited = entries.filter(([key]) => errors[key]?.code === OPERATION_TIME_LIMIT);
223
+ if (limited.length === 0) return [];
224
+ const skipped = halt ? entries.filter(([key]) => !(key in results) && !(key in errors)) : [];
225
+ return [...limited, ...skipped].filter(([, command]) => !JSON.stringify(command.params ?? {}).includes("$result"));
226
+ };
227
+ const run = async (entries, options) => {
228
+ let response = await send(entries, options);
229
+ for (let attempt = 0; attempt < retries; attempt++) {
230
+ const again = retryable(entries, response, options.halt ?? false);
231
+ if (again.length === 0) break;
232
+ await sleep(operatingDelay(response.resetAt, delay, attempt), options.signal);
233
+ const retried = await send(again, options);
234
+ const keys = new Set(again.map(([key]) => key));
235
+ response = {
236
+ results: {
237
+ ...response.results,
238
+ ...retried.results
239
+ },
240
+ errors: {
241
+ ...Object.fromEntries(Object.entries(response.errors).filter(([key]) => !keys.has(key))),
242
+ ...retried.errors
243
+ },
244
+ resetAt: retried.resetAt
245
+ };
246
+ }
247
+ return response;
248
+ };
249
+ return (async (commands, options = {}) => {
250
+ const entries = Object.entries(commands);
251
+ const chunks = chunk(entries, 50);
252
+ const responses = [];
253
+ if (options.halt) for (const group of chunks) {
254
+ const response = await run(group, options);
255
+ responses.push(response);
256
+ if (Object.keys(response.errors).length > 0) break;
257
+ }
258
+ else responses.push(...await Promise.all(chunks.map((group) => run(group, options))));
259
+ const results = Object.assign({}, ...responses.map((response) => response.results));
260
+ const errors = Object.assign({}, ...responses.map((response) => response.errors));
261
+ return Array.isArray(commands) ? {
262
+ results: entries.map(([key]) => results[key]),
263
+ errors: entries.map(([key]) => errors[key])
264
+ } : {
265
+ results,
266
+ errors
267
+ };
268
+ });
269
+ };
270
+ // source/limiter.ts
271
+ /**
272
+ * Leaky bucket matching the Bitrix request limit: up to `burst` requests at once, then `refillPerSecond`.
273
+ * Requests over the limit reserve a token in advance and wait until it refills
274
+ * @param options bucket size and refill rate
275
+ * @param options.burst requests allowed at once, 50 by default
276
+ * @param options.refillPerSecond requests added back every second, 2 by default
277
+ */
278
+ const createLimiter = ({ burst = 50, refillPerSecond = 2 } = {}) => {
279
+ let tokens = burst;
280
+ let updatedAt = Date.now();
281
+ const refill = () => {
282
+ const now = Date.now();
283
+ tokens = Math.min(burst, tokens + (now - updatedAt) / 1e3 * refillPerSecond);
284
+ updatedAt = now;
285
+ };
286
+ const acquire = async (signal) => {
287
+ signal?.throwIfAborted();
288
+ refill();
289
+ tokens -= 1;
290
+ if (tokens >= 0) return;
291
+ try {
292
+ await sleep(Math.ceil(-tokens / refillPerSecond * 1e3), signal);
293
+ } catch (error) {
294
+ tokens += 1;
295
+ throw error;
296
+ }
297
+ };
298
+ const exhaust = () => {
299
+ refill();
300
+ tokens = Math.min(tokens, 0);
301
+ };
302
+ return {
303
+ acquire,
304
+ exhaust
305
+ };
306
+ };
307
+ /**
308
+ * Finds the rows in a list result: either the result itself or the single array a catalog style result wraps
309
+ * @param method method name used in the error message
310
+ * @param result result of a list method
311
+ */
312
+ const unwrap = (method, result) => {
313
+ if (Array.isArray(result)) return {
314
+ rows: result,
315
+ key: void 0
316
+ };
317
+ const entries = typeof result === "object" && result !== null ? Object.entries(result) : [];
318
+ const [entry] = entries;
319
+ if (entries.length === 1 && entry && Array.isArray(entry[1])) return {
320
+ rows: entry[1],
321
+ key: entry[0]
322
+ };
323
+ throw new TypeError(`${method} did not return a list`);
324
+ };
325
+ /**
326
+ * Batches start with one page and double up to 50, so a consumer that stops early wastes few requests
327
+ * @param size pages in the previous batch
328
+ */
329
+ const grow = (size) => Math.min(size * 2, 50);
330
+ /**
331
+ * Adds the fields pagination relies on to an explicit select. Wildcards and no select already include them
332
+ * @param select select of the caller
333
+ * @param fields fields to add
334
+ */
335
+ const withFields = (select, fields) => select && !select.includes("*") ? [.../* @__PURE__ */ new Set([...select, ...fields])] : select;
336
+ const createPaginate = (transport, batch) => {
337
+ const runBatch = async function* (method, pages, signal, resumable) {
338
+ const { results, errors } = await batch(pages.map((params) => ({
339
+ method,
340
+ params
341
+ })), signal && { signal });
342
+ for (const [index, error] of errors.entries()) {
343
+ if (error && resumable && index > 0 && error.code === "OPERATION_TIME_LIMIT") return;
344
+ if (error) throw error;
345
+ yield { rows: unwrap(method, results[index]).rows };
346
+ }
347
+ };
348
+ const cursor = async function* (method, params, idField, count, signal) {
349
+ if (Object.keys(params.order ?? {}).some((field) => field !== idField)) throw new TypeError(`cursor pagination orders by ${idField}, use { mode: 'offset' } for a custom order`);
350
+ const { start: _start, ...rest } = params;
351
+ const select = withFields(rest.select, [idField]);
352
+ const page = (filter) => ({
353
+ ...rest,
354
+ ...select && { select },
355
+ filter: {
356
+ ...rest.filter,
357
+ ...filter
358
+ },
359
+ order: { [idField]: "ASC" },
360
+ start: -1
361
+ });
362
+ const { start: _counted, ...counted } = page({});
363
+ const first = await transport(method, count ? counted : page({}), signal);
364
+ let { rows, key } = unwrap(method, first.result);
365
+ yield {
366
+ rows,
367
+ total: count ? first.total : void 0
368
+ };
369
+ for (let size = 1; rows.length === 50; size = grow(size)) {
370
+ const lastId = rows.at(-1)[idField];
371
+ const pages = Array.from({ length: size }, (_, index) => page({ [`>${idField}`]: index === 0 ? lastId : `$result[${index - 1}]${key ? `[${key}]` : ""}[49][${idField}]` }));
372
+ for await ({rows} of runBatch(method, pages, signal, true)) {
373
+ yield { rows };
374
+ if (rows.length < 50) return;
375
+ }
376
+ }
377
+ };
378
+ const modified = async function* (method, params, idField, modifiedField, since, count, signal) {
379
+ if (Object.keys(params.order ?? {}).some((field) => field !== modifiedField && field !== idField)) throw new TypeError(`modified pagination orders by ${modifiedField} and ${idField}`);
380
+ const { start: _start, order: _order, ...rest } = params;
381
+ const select = withFields(rest.select, [idField, modifiedField]);
382
+ let after = { [`>=${modifiedField}`]: since instanceof Date ? since.toISOString() : since };
383
+ for (let counting = count;; counting = false) {
384
+ const page = await transport(method, {
385
+ ...rest,
386
+ ...select && { select },
387
+ filter: {
388
+ ...rest.filter,
389
+ ...after
390
+ },
391
+ order: {
392
+ [modifiedField]: "ASC",
393
+ [idField]: "ASC"
394
+ },
395
+ ...!counting && { start: -1 }
396
+ }, signal);
397
+ const { rows } = unwrap(method, page.result);
398
+ yield {
399
+ rows,
400
+ total: counting ? page.total : void 0
401
+ };
402
+ if (rows.length < 50) return;
403
+ const last = rows.at(-1);
404
+ yield* cursor(method, {
405
+ ...rest,
406
+ ...select && { select },
407
+ filter: {
408
+ ...rest.filter,
409
+ [`=${modifiedField}`]: last[modifiedField],
410
+ [`>${idField}`]: last[idField]
411
+ }
412
+ }, idField, false, signal);
413
+ after = { [`>${modifiedField}`]: last[modifiedField] };
414
+ }
415
+ };
416
+ const offset = async function* (method, params, signal) {
417
+ const first = await transport(method, {
418
+ ...params,
419
+ start: params.start ?? 0
420
+ }, signal);
421
+ yield {
422
+ rows: unwrap(method, first.result).rows,
423
+ total: first.total
424
+ };
425
+ if (first.next === void 0) return;
426
+ const starts = [];
427
+ for (let start = first.next; start < (first.total ?? 0); start += 50) starts.push(start);
428
+ for (let index = 0, size = 1; index < starts.length; index += size, size = grow(size)) {
429
+ const pages = starts.slice(index, index + size).map((start) => ({
430
+ ...params,
431
+ start
432
+ }));
433
+ yield* runBatch(method, pages, signal, false);
434
+ }
435
+ };
436
+ const pages = (method, params, options) => {
437
+ const { idField = "ID", signal, onProgress } = options;
438
+ const count = onProgress !== void 0;
439
+ if (options.mode === "modified") return modified(method, params, idField, options.modifiedField ?? "DATE_MODIFY", options.since, count, signal);
440
+ return options.mode === "offset" ? offset(method, params, signal) : cursor(method, params, idField, count, signal);
441
+ };
442
+ return async function* (method, params = {}, options = {}) {
443
+ let read = 0;
444
+ let total;
445
+ for await (const page of pages(method, params, options)) {
446
+ read += page.rows.length;
447
+ total ??= page.total;
448
+ options.onProgress?.({
449
+ read,
450
+ ...total !== void 0 && { total }
451
+ });
452
+ yield* page.rows;
453
+ }
454
+ };
455
+ };
456
+ // source/services/crm.ts
457
+ /**
458
+ * CRM entities share one set of `crm.<entity>.*` methods, so their services only differ by name
459
+ * @param core client methods the service is built on
460
+ * @param core.call calls a single REST method
461
+ * @param core.batch runs many commands in as few requests as possible
462
+ * @param core.list reads every page of a list method
463
+ * @param core.paginate iterates over every page of a list method
464
+ * @param name entity name in the method names, like `deal` in `crm.deal.get`
465
+ */
466
+ const createCrmService = ({ call, batch, list, paginate }, name) => {
467
+ const send = call;
468
+ const sendMany = batch;
469
+ const method = `crm.${name}`;
470
+ const modifiedField = name === "activity" ? "LAST_UPDATED" : "DATE_MODIFY";
471
+ return {
472
+ fields: () => send(`${method}.fields`),
473
+ get: (id) => send(`${method}.get`, { id }),
474
+ list: (params, options) => list(`${method}.list`, params, options),
475
+ paginate: (params, options) => paginate(`${method}.list`, params, options),
476
+ changedSince: (since, params, options) => paginate(`${method}.list`, params, {
477
+ ...options,
478
+ mode: "modified",
479
+ since,
480
+ modifiedField
481
+ }),
482
+ create: (fields, params) => send(`${method}.add`, {
483
+ fields,
484
+ ...params && { params }
485
+ }),
486
+ update: (id, fields, params) => send(`${method}.update`, {
487
+ id,
488
+ fields,
489
+ ...params && { params }
490
+ }),
491
+ delete: (id) => send(`${method}.delete`, { id }),
492
+ createMany: (items, params) => sendMany(items.map((fields) => ({
493
+ method: `${method}.add`,
494
+ params: {
495
+ fields,
496
+ ...params && { params }
497
+ }
498
+ }))),
499
+ updateMany: (items, params) => sendMany(items.map(({ id, fields }) => ({
500
+ method: `${method}.update`,
501
+ params: {
502
+ id,
503
+ fields,
504
+ ...params && { params }
505
+ }
506
+ }))),
507
+ deleteMany: (ids) => sendMany(ids.map((id) => ({
508
+ method: `${method}.delete`,
509
+ params: { id }
510
+ })))
511
+ };
512
+ };
513
+ /**
514
+ * Product rows of a deal or lead
515
+ * @param core client methods the service is built on
516
+ * @param core.call calls a single REST method
517
+ * @param name entity name in the method names
518
+ */
519
+ const createProductRows = ({ call }, name) => ({
520
+ get: (id) => call(`crm.${name}.productrows.get`, { id }),
521
+ set: (id, rows) => call(`crm.${name}.productrows.set`, {
522
+ id,
523
+ rows
524
+ })
525
+ });
526
+ /**
527
+ * Links between CRM entities through `crm.<owner>.<linked>.*`, like the contacts of a deal
528
+ * @param core client methods the service is built on
529
+ * @param core.call calls a single REST method
530
+ * @param owner entity the links belong to
531
+ * @param linked entity that is linked
532
+ */
533
+ const createLinks = ({ call }, owner, linked) => {
534
+ const send = call;
535
+ const method = `crm.${owner}.${linked}`;
536
+ const key = `${linked.toUpperCase()}_ID`;
537
+ return {
538
+ get: (id) => send(`${method}.items.get`, { id }),
539
+ set: (id, links) => send(`${method}.items.set`, {
540
+ id,
541
+ items: links
542
+ }),
543
+ add: (id, link) => send(`${method}.add`, {
544
+ id,
545
+ fields: link
546
+ }),
547
+ delete: (id, linkedId) => send(`${method}.delete`, {
548
+ id,
549
+ fields: { [key]: linkedId }
550
+ }),
551
+ clear: (id) => send(`${method}.items.delete`, { id })
552
+ };
553
+ };
554
+ // source/services/currencies.ts
555
+ /**
556
+ * Currencies are identified by their code like `USD`, and `crm.currency.list` returns every currency at once
557
+ * @param core client methods the service is built on
558
+ * @param core.call calls a single REST method
559
+ */
560
+ const createCurrenciesService = ({ call }) => ({
561
+ fields: () => call("crm.currency.fields"),
562
+ get: (id) => call("crm.currency.get", { id }),
563
+ list: (params = {}) => call("crm.currency.list", params),
564
+ create: (fields) => call("crm.currency.add", { fields }),
565
+ update: (id, fields) => call("crm.currency.update", {
566
+ id,
567
+ fields
568
+ }),
569
+ delete: (id) => call("crm.currency.delete", { id }),
570
+ base: () => call("crm.currency.base.get"),
571
+ setBase: (id) => call("crm.currency.base.set", { id })
572
+ });
573
+ // source/services/items.ts
574
+ /**
575
+ * `crm.item.*` works for every CRM type by its `entityTypeId`, smart processes included, and wraps its results
576
+ * in `item`, `items` or `fields`
577
+ * @param core client methods the service is built on
578
+ * @param core.call calls a single REST method
579
+ * @param core.list reads every page of a list method
580
+ * @param core.paginate iterates over every page of a list method
581
+ * @param entityTypeId type id, like 2 for deals or the id of a smart process from `crm.type.list`
582
+ */
583
+ const createItemsService = ({ call, list, paginate }, entityTypeId) => ({
584
+ fields: async () => (await call("crm.item.fields", { entityTypeId })).fields,
585
+ get: async (id) => (await call("crm.item.get", {
586
+ entityTypeId,
587
+ id
588
+ })).item,
589
+ list: (params = {}, options = {}) => list("crm.item.list", {
590
+ ...params,
591
+ entityTypeId
592
+ }, {
593
+ ...options,
594
+ idField: "id"
595
+ }),
596
+ paginate: (params = {}, options = {}) => paginate("crm.item.list", {
597
+ ...params,
598
+ entityTypeId
599
+ }, {
600
+ ...options,
601
+ idField: "id"
602
+ }),
603
+ changedSince: (since, params = {}, options = {}) => paginate("crm.item.list", {
604
+ ...params,
605
+ entityTypeId
606
+ }, {
607
+ ...options,
608
+ mode: "modified",
609
+ since,
610
+ idField: "id",
611
+ modifiedField: "updatedTime"
612
+ }),
613
+ create: async (fields) => (await call("crm.item.add", {
614
+ entityTypeId,
615
+ fields
616
+ })).item,
617
+ update: async (id, fields) => (await call("crm.item.update", {
618
+ entityTypeId,
619
+ id,
620
+ fields
621
+ })).item,
622
+ delete: async (id) => {
623
+ await call("crm.item.delete", {
624
+ entityTypeId,
625
+ id
626
+ });
627
+ }
628
+ });
629
+ // source/services/products.ts
630
+ /**
631
+ * `catalog.product.list` rejects requests that do not select `id` and `iblockId`
632
+ * @param params list params from the caller
633
+ */
634
+ const withRequiredSelect = (params) => ({
635
+ ...params,
636
+ select: [.../* @__PURE__ */ new Set([
637
+ ...params.select ?? ["name"],
638
+ "id",
639
+ "iblockId"
640
+ ])]
641
+ });
642
+ const createProductsService = ({ call, list, paginate }) => ({
643
+ fields: async (iblockId) => (await call("catalog.product.getFieldsByFilter", { filter: { iblockId } })).product,
644
+ get: async (id) => (await call("catalog.product.get", { id })).product,
645
+ list: (params, options = {}) => list("catalog.product.list", withRequiredSelect(params), {
646
+ ...options,
647
+ idField: "id"
648
+ }),
649
+ paginate: (params, options = {}) => paginate("catalog.product.list", withRequiredSelect(params), {
650
+ ...options,
651
+ idField: "id"
652
+ }),
653
+ create: async (fields) => (await call("catalog.product.add", { fields })).element,
654
+ update: async (id, fields) => (await call("catalog.product.update", {
655
+ id,
656
+ fields
657
+ })).element,
658
+ delete: (id) => call("catalog.product.delete", { id })
659
+ });
660
+ // source/services/statuses.ts
661
+ /**
662
+ * `crm.status.list` returns every status at once, so there is no pagination here
663
+ * @param core client methods the service is built on
664
+ * @param core.call calls a single REST method
665
+ */
666
+ const createStatusesService = ({ call }) => ({
667
+ fields: () => call("crm.status.fields"),
668
+ get: (id) => call("crm.status.get", { id }),
669
+ list: (params = {}) => call("crm.status.list", params),
670
+ create: (fields) => call("crm.status.add", { fields }),
671
+ update: (id, fields) => call("crm.status.update", {
672
+ id,
673
+ fields
674
+ }),
675
+ delete: (id, { forced = false } = {}) => call("crm.status.delete", {
676
+ id,
677
+ params: { FORCED: forced ? "Y" : "N" }
678
+ })
679
+ });
680
+ // source/services/users.ts
681
+ /**
682
+ * `user.get` takes `sort` and `order` as plain strings instead of an order map, so users paginate by offset
683
+ * @param core client methods the service is built on
684
+ * @param core.call calls a single REST method
685
+ * @param core.list reads every page of a list method
686
+ * @param core.paginate iterates over every page of a list method
687
+ */
688
+ const createUsersService = ({ call, list, paginate }) => ({
689
+ fields: () => call("user.fields"),
690
+ current: () => call("user.current"),
691
+ get: async (id) => {
692
+ const [user] = await call("user.get", { ID: id });
693
+ if (!user) throw new BitrixError({
694
+ code: "NOT_FOUND",
695
+ description: `user ${id} not found`,
696
+ method: "user.get"
697
+ });
698
+ return user;
699
+ },
700
+ list: (params = {}, options = {}) => list("user.get", params, {
701
+ ...options,
702
+ mode: "offset"
703
+ }),
704
+ paginate: (params = {}, options = {}) => paginate("user.get", params, {
705
+ ...options,
706
+ mode: "offset"
707
+ })
708
+ });
709
+ // source/client.ts
710
+ /**
711
+ * Creates a Bitrix24 REST client
712
+ * @param options endpoint, auth, limiter and retry settings
713
+ * @example
714
+ * const bitrix = createClient({ url: 'https://portal.bitrix24.com/rest/1/WEBHOOK_TOKEN' })
715
+ * const deal = await bitrix.deals.get(77)
716
+ */
717
+ const createClient = (options) => {
718
+ const retry = {
719
+ retries: 3,
720
+ delay: 1e3,
721
+ ...options.retry
722
+ };
723
+ const transport = createTransport(options, createLimiter(options.limiter), retry);
724
+ const call = (async (method, params, { raw = false, signal } = {}) => {
725
+ const envelope = await transport(method, params, signal);
726
+ return raw ? envelope : envelope.result;
727
+ });
728
+ const batch = createBatch(transport, retry);
729
+ const paginate = createPaginate(transport, batch);
730
+ const list = (async (method, params, listOptions) => {
731
+ const rows = [];
732
+ for await (const row of paginate(method, params, listOptions)) rows.push(row);
733
+ return rows;
734
+ });
735
+ const core = {
736
+ call,
737
+ batch,
738
+ paginate,
739
+ list
740
+ };
741
+ return {
742
+ ...core,
743
+ activities: createCrmService(core, "activity"),
744
+ companies: {
745
+ ...createCrmService(core, "company"),
746
+ contacts: createLinks(core, "company", "contact")
747
+ },
748
+ contacts: {
749
+ ...createCrmService(core, "contact"),
750
+ companies: createLinks(core, "contact", "company")
751
+ },
752
+ currencies: createCurrenciesService(core),
753
+ deals: {
754
+ ...createCrmService(core, "deal"),
755
+ productRows: createProductRows(core, "deal"),
756
+ contacts: createLinks(core, "deal", "contact")
757
+ },
758
+ items: (entityTypeId) => createItemsService(core, entityTypeId),
759
+ leads: {
760
+ ...createCrmService(core, "lead"),
761
+ productRows: createProductRows(core, "lead"),
762
+ contacts: createLinks(core, "lead", "contact")
763
+ },
764
+ products: createProductsService(core),
765
+ statuses: createStatusesService(core),
766
+ users: createUsersService(core)
767
+ };
768
+ };
769
+ export { BitrixError as n, createClient as t };