@cerios/salesforce-sobjectly 0.0.0-stage → 1.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.
@@ -0,0 +1,3986 @@
1
+ import { createSign } from "node:crypto";
2
+ import { execFile } from "node:child_process";
3
+ //#region \0rolldown/runtime.js
4
+ var __esmMin = (fn, res, err) => () => {
5
+ if (err) throw err[0];
6
+ try {
7
+ return fn && (res = fn(fn = 0)), res;
8
+ } catch (e) {
9
+ throw err = [e], e;
10
+ }
11
+ };
12
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
13
+ //#endregion
14
+ //#region src/shared.ts
15
+ /** Starts `load` with a signal of its own and returns a handle callers can `join`. */
16
+ function share(load) {
17
+ const controller = new AbortController();
18
+ let waiters = 0;
19
+ const promise = load(controller.signal);
20
+ promise.catch(() => void 0);
21
+ return {
22
+ promise,
23
+ get abandoned() {
24
+ return controller.signal.aborted;
25
+ },
26
+ join(signal) {
27
+ waiters++;
28
+ if (!signal) return promise;
29
+ return new Promise((resolve, reject) => {
30
+ const leave = () => {
31
+ signal.removeEventListener("abort", onAbort);
32
+ };
33
+ const onAbort = () => {
34
+ leave();
35
+ waiters--;
36
+ if (waiters === 0) controller.abort(signal.reason);
37
+ reject(signal.reason);
38
+ };
39
+ if (signal.aborted) {
40
+ onAbort();
41
+ return;
42
+ }
43
+ signal.addEventListener("abort", onAbort, { once: true });
44
+ promise.then((value) => {
45
+ leave();
46
+ resolve(value);
47
+ }, (error) => {
48
+ leave();
49
+ reject(error);
50
+ });
51
+ });
52
+ }
53
+ };
54
+ }
55
+ var init_shared = __esmMin((() => {}));
56
+ //#endregion
57
+ //#region src/cache.ts
58
+ var MetadataCache;
59
+ var init_cache = __esmMin((() => {
60
+ init_shared();
61
+ MetadataCache = class {
62
+ _enabled;
63
+ _entries = /* @__PURE__ */ new Map();
64
+ constructor(_enabled = true) {
65
+ this._enabled = _enabled;
66
+ }
67
+ /**
68
+ * Returns the cached value for `key`, or loads (and caches) it. `load` receives a signal of
69
+ * its own: aborting `signal` only stops this caller from waiting.
70
+ */
71
+ getOrLoad(key, load, signal) {
72
+ if (!this._enabled) return load(signal);
73
+ let entry = this._entries.get(key);
74
+ if (!entry || entry.abandoned) {
75
+ const created = share((ownSignal) => load(ownSignal));
76
+ this._entries.set(key, created);
77
+ created.promise.catch(() => {
78
+ if (this._entries.get(key) === created) this._entries.delete(key);
79
+ });
80
+ entry = created;
81
+ }
82
+ return entry.join(signal);
83
+ }
84
+ /** Removes one entry, or every entry when `key` is omitted. */
85
+ clear(key) {
86
+ if (key === void 0) this._entries.clear();
87
+ else this._entries.delete(key);
88
+ }
89
+ };
90
+ }));
91
+ //#endregion
92
+ //#region src/api-version.ts
93
+ /** `true` for a version such as `"v66.0"`. */
94
+ function isApiVersion(value) {
95
+ return typeof value === "string" && API_VERSION_PATTERN.test(value);
96
+ }
97
+ /**
98
+ * Throws a `TypeError` that explains how to set the version when `value` is missing or not a
99
+ * version such as `"v66.0"`. `hint` is appended to the message.
100
+ */
101
+ function assertApiVersion(value, context, hint) {
102
+ if (value === void 0 || value === null || value === "") throw new TypeError(`${context}: apiVersion is required, e.g. "v66.0". ${hint}`);
103
+ if (!isApiVersion(value)) throw new TypeError(`${context}: invalid apiVersion ${JSON.stringify(value)}. Expected a value like "v66.0". ${hint}`);
104
+ }
105
+ /**
106
+ * Accepts the ways people write an API version (`"66"`, `"66.0"`, `"v66"`, `"v66.0"`) and
107
+ * returns the canonical form (`"v66.0"`), or `undefined` when the input is not a version.
108
+ */
109
+ function normalizeApiVersion(input) {
110
+ const match = /^v?(\d+)(?:\.(\d+))?$/i.exec(input.trim());
111
+ if (!match) return;
112
+ return `v${Number(match[1])}.${Number(match[2] ?? 0)}`;
113
+ }
114
+ var API_VERSION_PATTERN;
115
+ var init_api_version = __esmMin((() => {
116
+ API_VERSION_PATTERN = /^v\d+\.\d+$/;
117
+ }));
118
+ //#endregion
119
+ //#region src/errors.ts
120
+ function toRestErrors(body) {
121
+ return (Array.isArray(body) ? body : body !== null && typeof body === "object" ? [body] : []).filter((item) => typeof item === "object" && item !== null && "errorCode" in item && "message" in item);
122
+ }
123
+ function describeBody(body) {
124
+ if (body === void 0 || body === null) return "";
125
+ if (typeof body === "string") return body.slice(0, 500);
126
+ if (body instanceof Uint8Array) return `${body.byteLength} bytes`;
127
+ try {
128
+ return JSON.stringify(body).slice(0, 500);
129
+ } catch {
130
+ return "";
131
+ }
132
+ }
133
+ /**
134
+ * `true` when `error` is a `SalesforceError` (non-2xx response), optionally with `code` as one of
135
+ * its error codes.
136
+ */
137
+ function isSalesforceError(error, code) {
138
+ if (!(error instanceof SalesforceError)) return false;
139
+ return code === void 0 || error.errors.some((item) => item.errorCode === code);
140
+ }
141
+ /**
142
+ * `true` when `error` carries error code `code`: a `SalesforceError` from a failed request, or a
143
+ * `SalesforceSaveError` with a failed item (save results, collections, actions). Handy for
144
+ * `catch` blocks and retry decisions, e.g. `hasErrorCode(error, "UNABLE_TO_LOCK_ROW")`.
145
+ */
146
+ function hasErrorCode(error, code) {
147
+ if (error instanceof SalesforceError) return error.errors.some((item) => item.errorCode === code);
148
+ if (error instanceof SalesforceSaveError) return error.errors.some((item) => item.statusCode === code);
149
+ return false;
150
+ }
151
+ /** Runs chunked work; when a later chunk fails, the error keeps the results of the earlier ones. */
152
+ async function runChunks(chunks, run, message) {
153
+ const results = [];
154
+ for (const chunk of chunks) try {
155
+ results.push(...await run(chunk));
156
+ } catch (error) {
157
+ if (results.length === 0) throw error;
158
+ throw new SalesforcePartialFailureError(message, results, error);
159
+ }
160
+ return results;
161
+ }
162
+ var SalesforceError, SalesforceAuthError, SalesforceSaveError, SalesforceBulkJobError, SalesforcePartialFailureError;
163
+ var init_errors = __esmMin((() => {
164
+ SalesforceError = class extends Error {
165
+ name = "SalesforceError";
166
+ status;
167
+ method;
168
+ path;
169
+ /** The `errorCode` of the first error, e.g. `"MALFORMED_QUERY"` or `"INVALID_FIELD"`. */
170
+ errorCode;
171
+ /** All errors Salesforce returned, when the body was the standard error array. */
172
+ errors;
173
+ /** The parsed response body. */
174
+ body;
175
+ /** Response headers, without `set-cookie`. */
176
+ headers;
177
+ /** The value of the `Sforce-Limit-Info` header, e.g. `"api-usage=18/15000"`. */
178
+ limitInfo;
179
+ constructor(details) {
180
+ const errors = toRestErrors(details.body);
181
+ const first = errors[0];
182
+ const reason = first ? `${first.errorCode}: ${first.message}` : describeBody(details.body);
183
+ super(`Salesforce ${details.method} ${details.path} failed with status ${details.status}${reason ? ` - ${reason}` : ""}`);
184
+ this.status = details.status;
185
+ this.method = details.method;
186
+ this.path = details.path;
187
+ this.errors = errors;
188
+ this.errorCode = first?.errorCode;
189
+ this.body = details.body;
190
+ this.headers = details.headers;
191
+ this.limitInfo = details.headers["sforce-limit-info"];
192
+ }
193
+ };
194
+ SalesforceAuthError = class extends Error {
195
+ name = "SalesforceAuthError";
196
+ status;
197
+ /** The OAuth `error` code, e.g. `"invalid_client"` or `"invalid_grant"`. */
198
+ error;
199
+ errorDescription;
200
+ constructor(message, details = {}) {
201
+ super(message);
202
+ this.status = details.status;
203
+ this.error = details.error;
204
+ this.errorDescription = details.errorDescription;
205
+ }
206
+ };
207
+ SalesforceSaveError = class extends Error {
208
+ name = "SalesforceSaveError";
209
+ /** Every item result, including the successful ones. */
210
+ results;
211
+ /** The errors of all failed items, flattened. */
212
+ errors;
213
+ constructor(message, results, errors) {
214
+ const first = errors[0];
215
+ super(first ? `${message} - ${first.statusCode}: ${first.message}` : message);
216
+ this.results = results;
217
+ this.errors = errors;
218
+ }
219
+ };
220
+ SalesforceBulkJobError = class extends Error {
221
+ name = "SalesforceBulkJobError";
222
+ jobId;
223
+ state;
224
+ jobInfo;
225
+ constructor(message, jobId, state, jobInfo) {
226
+ super(message);
227
+ this.jobId = jobId;
228
+ this.state = state;
229
+ this.jobInfo = jobInfo;
230
+ }
231
+ };
232
+ SalesforcePartialFailureError = class extends Error {
233
+ name = "SalesforcePartialFailureError";
234
+ completedResults;
235
+ cause;
236
+ constructor(message, completedResults, cause) {
237
+ super(`${message} (${completedResults.length} item(s) were already processed): ${cause instanceof Error ? cause.message : String(cause)}`);
238
+ this.completedResults = completedResults;
239
+ this.cause = cause;
240
+ }
241
+ };
242
+ }));
243
+ //#endregion
244
+ //#region src/soql/escape.ts
245
+ /**
246
+ * Marks `sql` as a raw SOQL literal, e.g. a date literal such as `soqlLiteral("LAST_N_DAYS:30")`
247
+ * or `soqlLiteral("TODAY")`. Never pass untrusted input.
248
+ */
249
+ function soqlLiteral(sql) {
250
+ if (typeof sql !== "string" || sql.trim().length === 0) throw new Error("soqlLiteral() requires a non-blank SOQL fragment.");
251
+ const literal = Object.freeze({ sql });
252
+ literals.add(literal);
253
+ return literal;
254
+ }
255
+ /** `true` for values created with `soqlLiteral` (and the helpers built on it). */
256
+ function isSoqlLiteral(value) {
257
+ return typeof value === "object" && value !== null && literals.has(value);
258
+ }
259
+ /**
260
+ * Formats a value as a SOQL literal. Strings are quoted with backslashes, quotes and control
261
+ * characters escaped, which prevents SOQL injection for values from user or external input.
262
+ * In `LIKE` patterns `%` and `_` stay wildcards; use {@link soqlLike} to match them literally.
263
+ *
264
+ * A `Date` becomes a DateTime literal (`yyyy-MM-ddTHH:mm:ssZ`, UTC), which only works against
265
+ * DateTime fields. Use {@link soqlDate} for Date-only fields (e.g. `Birthdate`).
266
+ */
267
+ function soqlEscape(value) {
268
+ if (value === null) return "null";
269
+ if (value instanceof Date) return formatDateTime$1(value);
270
+ if (typeof value === "object") {
271
+ if (isSoqlLiteral(value)) return value.sql;
272
+ throw new TypeError("Objects can't be used as SOQL values; use soqlLiteral() for raw SOQL.");
273
+ }
274
+ if (typeof value === "number") return formatNumber(value);
275
+ if (typeof value === "boolean") return String(value);
276
+ if (typeof value !== "string") throw new TypeError(`Unsupported SOQL value of type ${typeof value}.`);
277
+ return `'${escapeText(value)}'`;
278
+ }
279
+ /** Escapes quotes, backslashes and control characters for use inside a quoted SOQL string. */
280
+ function escapeText(value) {
281
+ return value.replace(/[\\'"\n\r\t\b\f\u0000-\u001f]/g, (char) => {
282
+ switch (char) {
283
+ case "\\": return "\\\\";
284
+ case "'": return "\\'";
285
+ case "\"": return "\\\"";
286
+ case "\n": return "\\n";
287
+ case "\r": return "\\r";
288
+ case " ": return "\\t";
289
+ case "\b": return "\\b";
290
+ case "\f": return "\\f";
291
+ default: throw new Error(`SOQL strings can't contain the control character U+${char.charCodeAt(0).toString(16).padStart(4, "0")}.`);
292
+ }
293
+ });
294
+ }
295
+ /** Formats a number without exponent notation, which SOQL doesn't accept (`1e21`, `1e-7`). */
296
+ function formatNumber(value) {
297
+ if (!Number.isFinite(value)) throw new Error(`Cannot use ${value} in a SOQL query.`);
298
+ const plain = String(value);
299
+ if (!/e/i.test(plain)) return Object.is(value, -0) ? "0" : plain;
300
+ const [, sign = "", whole = "", fraction = "", exponent = "0"] = /^(-?)(\d+)(?:\.(\d+))?e([+-]\d+)$/i.exec(plain) ?? [];
301
+ const digits = whole + fraction;
302
+ const point = whole.length + Number(exponent);
303
+ if (point <= 0) return `${sign}0.${"0".repeat(-point)}${digits}`;
304
+ if (point >= digits.length) return sign + digits + "0".repeat(point - digits.length);
305
+ return `${sign}${digits.slice(0, point)}.${digits.slice(point)}`;
306
+ }
307
+ function assertYear(year, value) {
308
+ if (Number.isNaN(year)) throw new Error("Invalid Date can't be used in a SOQL query.");
309
+ if (year < MIN_YEAR || year > MAX_YEAR) throw new Error(`Salesforce only accepts dates from ${MIN_YEAR} to ${MAX_YEAR}, got ${value.toISOString()}.`);
310
+ }
311
+ /** `yyyy-MM-ddTHH:mm:ssZ` in UTC, one of the DateTime formats SOQL documents. */
312
+ function formatDateTime$1(value) {
313
+ assertYear(value.getUTCFullYear(), value);
314
+ return `${value.toISOString().slice(0, 19)}Z`;
315
+ }
316
+ /**
317
+ * Formats a `Date` as a Date-only literal (`yyyy-MM-dd`), for use against Date fields. Uses the
318
+ * UTC date unless `local` is set, in which case the date in the local time zone is used.
319
+ */
320
+ function soqlEscapeDateOnly(value, options = {}) {
321
+ if (!options.local) {
322
+ assertYear(value.getUTCFullYear(), value);
323
+ return value.toISOString().slice(0, 10);
324
+ }
325
+ assertYear(value.getFullYear(), value);
326
+ const pad = (n) => String(n).padStart(2, "0");
327
+ return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
328
+ }
329
+ /**
330
+ * A Date-only literal for the typed `where` helpers, e.g. `where("Birthdate", ">", soqlDate(d))`.
331
+ * Accepts a `Date` or a `yyyy-MM-dd` string. A `Date` uses its UTC date; pass `{ local: true }`
332
+ * to use the date in the local time zone (e.g. for `new Date(2026, 0, 31)`).
333
+ */
334
+ function soqlDate(value, options = {}) {
335
+ if (typeof value === "string") {
336
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) throw new Error(`soqlDate() expects a yyyy-MM-dd string, got "${value}".`);
337
+ return soqlLiteral(value);
338
+ }
339
+ return soqlLiteral(soqlEscapeDateOnly(value, options));
340
+ }
341
+ function soqlDateLiteral(name, n) {
342
+ if (FIXED_DATE_LITERALS.has(name)) return soqlLiteral(name);
343
+ if (!/^(LAST|NEXT)_N_|_AGO$/.test(name) || !/^[A-Z_]+$/.test(name)) throw new Error(`Unknown SOQL date literal "${name}".`);
344
+ if (n === void 0 || !Number.isSafeInteger(n) || n < 0) throw new Error(`soqlDateLiteral("${name}", n) requires a non-negative integer n.`);
345
+ return soqlLiteral(`${name}:${n}`);
346
+ }
347
+ /**
348
+ * Escapes a term for a SOSL `FIND {...}` clause by backslash-escaping the reserved characters
349
+ * `? & | ! { } [ ] ( ) ^ ~ * : \ " ' + -`.
350
+ */
351
+ function soslEscape(term) {
352
+ return term.replace(/[?&|!{}[\]()^~*:\\"'+-]/g, (match) => `\\${match}`);
353
+ }
354
+ var literals, MIN_YEAR, MAX_YEAR, FIXED_DATE_LITERALS;
355
+ var init_escape = __esmMin((() => {
356
+ literals = /* @__PURE__ */ new WeakSet();
357
+ MIN_YEAR = 1700;
358
+ MAX_YEAR = 4e3;
359
+ FIXED_DATE_LITERALS = /* @__PURE__ */ new Set([
360
+ "YESTERDAY",
361
+ "TODAY",
362
+ "TOMORROW",
363
+ "LAST_WEEK",
364
+ "THIS_WEEK",
365
+ "NEXT_WEEK",
366
+ "LAST_MONTH",
367
+ "THIS_MONTH",
368
+ "NEXT_MONTH",
369
+ "LAST_90_DAYS",
370
+ "NEXT_90_DAYS",
371
+ "THIS_QUARTER",
372
+ "LAST_QUARTER",
373
+ "NEXT_QUARTER",
374
+ "THIS_YEAR",
375
+ "LAST_YEAR",
376
+ "NEXT_YEAR",
377
+ "THIS_FISCAL_QUARTER",
378
+ "LAST_FISCAL_QUARTER",
379
+ "NEXT_FISCAL_QUARTER",
380
+ "THIS_FISCAL_YEAR",
381
+ "LAST_FISCAL_YEAR",
382
+ "NEXT_FISCAL_YEAR"
383
+ ]);
384
+ }));
385
+ //#endregion
386
+ //#region src/http/connection.ts
387
+ function hasErrorCodeIn(response, codes) {
388
+ const body = parseBody(response, "auto");
389
+ return Array.isArray(body) && body.some((error) => codes.includes(error.errorCode));
390
+ }
391
+ async function collect(chunks) {
392
+ const parts = [];
393
+ let length = 0;
394
+ for await (const chunk of chunks) {
395
+ parts.push(chunk);
396
+ length += chunk.byteLength;
397
+ }
398
+ const result = new Uint8Array(length);
399
+ let offset = 0;
400
+ for (const part of parts) {
401
+ result.set(part, offset);
402
+ offset += part.byteLength;
403
+ }
404
+ return result;
405
+ }
406
+ function resolveRetry(retry) {
407
+ if (!retry) return;
408
+ const options = retry === true ? {} : retry;
409
+ return {
410
+ retries: options.retries ?? 2,
411
+ statusCodes: options.statusCodes ?? [
412
+ 429,
413
+ 502,
414
+ 503,
415
+ 504
416
+ ],
417
+ errorCodes: options.errorCodes ?? [],
418
+ methods: options.methods ?? ["GET", "HEAD"],
419
+ baseDelayMs: options.baseDelayMs ?? 500,
420
+ maxDelayMs: options.maxDelayMs ?? 3e4
421
+ };
422
+ }
423
+ function appendQuery(url, query) {
424
+ if (!query) return;
425
+ for (const [key, value] of Object.entries(query)) {
426
+ if (value === void 0 || value === null) continue;
427
+ if (value instanceof Date) url.searchParams.append(key, value.toISOString());
428
+ else if (Array.isArray(value)) url.searchParams.append(key, value.join(","));
429
+ else url.searchParams.append(key, String(value));
430
+ }
431
+ }
432
+ function encodeRequest(request, defaultHeaders) {
433
+ const headers = new Headers(defaultHeaders);
434
+ for (const [key, value] of Object.entries(request.headers ?? {})) headers.set(key, value);
435
+ const responseType = request.responseType ?? "auto";
436
+ if (!headers.has("Accept")) headers.set("Accept", responseType === "auto" || responseType === "json" ? "application/json" : "*/*");
437
+ const { body } = request;
438
+ if (body === void 0 || body === null) return {
439
+ headers,
440
+ body: void 0
441
+ };
442
+ if (typeof body === "string") {
443
+ setDefault(headers, "Content-Type", "text/plain; charset=utf-8");
444
+ return {
445
+ headers,
446
+ body
447
+ };
448
+ }
449
+ const bytes = toBytes(body);
450
+ if (bytes) {
451
+ setDefault(headers, "Content-Type", "application/octet-stream");
452
+ return {
453
+ headers,
454
+ body: bytes
455
+ };
456
+ }
457
+ if (body instanceof URLSearchParams) {
458
+ setDefault(headers, "Content-Type", "application/x-www-form-urlencoded");
459
+ return {
460
+ headers,
461
+ body: body.toString()
462
+ };
463
+ }
464
+ assertSerializable(body);
465
+ setDefault(headers, "Content-Type", "application/json");
466
+ return {
467
+ headers,
468
+ body: JSON.stringify(body)
469
+ };
470
+ }
471
+ /** Binary bodies: `Uint8Array` (incl. `Buffer`), other typed arrays, `DataView` and `ArrayBuffer`. */
472
+ function toBytes(body) {
473
+ if (body instanceof Uint8Array) return body;
474
+ if (body instanceof ArrayBuffer) return new Uint8Array(body);
475
+ if (ArrayBuffer.isView(body)) return new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
476
+ }
477
+ /** Rejects bodies that `JSON.stringify` would silently turn into `{}`. */
478
+ function assertSerializable(body) {
479
+ if (typeof Blob !== "undefined" && body instanceof Blob || typeof FormData !== "undefined" && body instanceof FormData || typeof ReadableStream !== "undefined" && body instanceof ReadableStream) throw new TypeError("Unsupported request body: Blob, FormData and streams can't be sent. Pass a Uint8Array (see buildMultipart for multipart).");
480
+ }
481
+ /** Runs an observability hook; a throwing hook must never change the outcome of a request. */
482
+ function callHook(hook) {
483
+ try {
484
+ hook();
485
+ } catch {}
486
+ }
487
+ function setDefault(headers, name, value) {
488
+ if (!headers.has(name)) headers.set(name, value);
489
+ }
490
+ /**
491
+ * Parses a response body according to `responseType` and the `Content-Type` header. With
492
+ * `strict`, a body that should be JSON but isn't throws instead of being returned as text.
493
+ */
494
+ function parseBody(response, responseType, strict = false) {
495
+ if (responseType === "binary") return response.body;
496
+ if (response.body.byteLength === 0) return responseType === "text" ? "" : void 0;
497
+ const contentType = (response.headers.get("content-type") ?? "").toLowerCase();
498
+ if (responseType === "auto" && !isTextContentType(contentType)) return response.body;
499
+ const text = decoder$1.decode(response.body);
500
+ if (responseType === "text") return text;
501
+ if (responseType === "json" || contentType.includes("json")) try {
502
+ return JSON.parse(text);
503
+ } catch {
504
+ if (strict) throw new Error(`Salesforce returned invalid JSON (status ${response.status}, content-type "${contentType}"): ${text.slice(0, 200)}`);
505
+ return text;
506
+ }
507
+ return text;
508
+ }
509
+ function isTextContentType(contentType) {
510
+ return contentType === "" || contentType.includes("json") || contentType.startsWith("text/") || contentType.includes("xml") || contentType.includes("csv");
511
+ }
512
+ function combineSignals(signal, timeoutMs) {
513
+ if (timeoutMs <= 0) return signal;
514
+ const timeout = AbortSignal.timeout(timeoutMs);
515
+ return signal ? AbortSignal.any([signal, timeout]) : timeout;
516
+ }
517
+ function redactHeaders(headers) {
518
+ const result = headersToObject(headers);
519
+ if (result.authorization) result.authorization = "Bearer [REDACTED]";
520
+ return result;
521
+ }
522
+ /** Converts headers to a plain object with lowercase names, dropping `set-cookie`. */
523
+ function headersToObject(headers) {
524
+ const result = {};
525
+ headers.forEach((value, key) => {
526
+ if (key.toLowerCase() !== "set-cookie") result[key.toLowerCase()] = value;
527
+ });
528
+ return result;
529
+ }
530
+ function stripQuery(path) {
531
+ const index = path.indexOf("?");
532
+ return index === -1 ? path : path.slice(0, index);
533
+ }
534
+ function parseRetryAfter(value) {
535
+ if (!value) return;
536
+ const seconds = Number(value);
537
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
538
+ const date = Date.parse(value);
539
+ return Number.isNaN(date) ? void 0 : Math.max(0, date - Date.now());
540
+ }
541
+ function sleep(ms, signal) {
542
+ return new Promise((resolve, reject) => {
543
+ if (signal?.aborted) {
544
+ reject(signal.reason);
545
+ return;
546
+ }
547
+ const timer = setTimeout(() => {
548
+ signal?.removeEventListener("abort", onAbort);
549
+ resolve();
550
+ }, ms);
551
+ const onAbort = () => {
552
+ clearTimeout(timer);
553
+ reject(signal?.reason);
554
+ };
555
+ signal?.addEventListener("abort", onAbort, { once: true });
556
+ });
557
+ }
558
+ /** An external id value as a path segment; numbers are written without exponent notation. */
559
+ function externalIdText(value) {
560
+ return typeof value === "number" ? formatNumber(value) : value;
561
+ }
562
+ /**
563
+ * Encodes one URL path segment (an sObject name, id or external id value). `.` and `..` are
564
+ * rejected: URL parsing would resolve them and change which resource is called.
565
+ */
566
+ function segment(value) {
567
+ if (value === "." || value === "..") throw new Error(`"${value}" is not a valid id or name in a URL path.`);
568
+ return encodeURIComponent(value);
569
+ }
570
+ var DEFAULT_TIMEOUT_MS, decoder$1, SalesforceConnection;
571
+ var init_connection = __esmMin((() => {
572
+ init_api_version();
573
+ init_errors();
574
+ init_escape();
575
+ DEFAULT_TIMEOUT_MS = 12e4;
576
+ decoder$1 = new TextDecoder();
577
+ SalesforceConnection = class {
578
+ apiVersion;
579
+ _auth;
580
+ _transport;
581
+ _timeoutMs;
582
+ _retry;
583
+ _allowedOrigins;
584
+ _headers;
585
+ _hooks;
586
+ _apiUsage;
587
+ constructor(options) {
588
+ assertApiVersion(options.apiVersion, "SalesforceClient", "Pass it as { apiVersion }, e.g. the API_VERSION constant exported by your generated sObject types.");
589
+ this.apiVersion = options.apiVersion;
590
+ this._auth = options.auth;
591
+ this._transport = options.transport;
592
+ this._timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
593
+ this._retry = resolveRetry(options.retry);
594
+ this._allowedOrigins = new Set((options.allowedOrigins ?? []).map((origin) => new URL(origin).origin));
595
+ this._headers = options.headers ?? {};
596
+ this._hooks = options.hooks ?? {};
597
+ }
598
+ /** API usage as reported by the last response's `Sforce-Limit-Info` header. */
599
+ get apiUsage() {
600
+ return this._apiUsage;
601
+ }
602
+ get transport() {
603
+ return this._transport;
604
+ }
605
+ /** Returns the instance URL of the current token (authenticating if needed). */
606
+ async instanceUrl(signal) {
607
+ const token = await this._auth.getToken({
608
+ transport: this._transport,
609
+ signal
610
+ });
611
+ return new URL(token.instanceUrl).origin;
612
+ }
613
+ /** Sends a request and returns the parsed body. Throws `SalesforceError` for non-2xx responses. */
614
+ async request(request) {
615
+ return (await this.send(request)).data;
616
+ }
617
+ /** Sends a request and returns status, headers and the parsed body. */
618
+ async send(request) {
619
+ const method = request.method ?? "GET";
620
+ const timeoutMs = request.timeoutMs ?? this._timeoutMs;
621
+ let token = await this.getToken(request.signal, timeoutMs);
622
+ let reauthenticated = false;
623
+ let retries = 0;
624
+ for (let attempt = 1;; attempt++) {
625
+ let response;
626
+ try {
627
+ response = await this.sendOnce(request, method, token, attempt, timeoutMs);
628
+ } catch (error) {
629
+ if (this.canRetryFailure(method, request, retries)) {
630
+ retries++;
631
+ await sleep(this.retryDelay(void 0, retries), request.signal);
632
+ continue;
633
+ }
634
+ throw error;
635
+ }
636
+ if (response.status === 401 && !reauthenticated && this._auth.invalidate) {
637
+ reauthenticated = true;
638
+ const next = await this.reauthenticate(token, request.signal, timeoutMs);
639
+ if (next.accessToken !== token.accessToken) {
640
+ token = next;
641
+ continue;
642
+ }
643
+ }
644
+ if (this.shouldRetry(method, request, response, retries)) {
645
+ retries++;
646
+ await sleep(this.retryDelay(response, retries), request.signal);
647
+ continue;
648
+ }
649
+ return this.toResult(request, method, response);
650
+ }
651
+ }
652
+ /** Gets a token, bounded by the caller's signal and the request timeout. */
653
+ getToken(signal, timeoutMs) {
654
+ return this._auth.getToken({
655
+ transport: this._transport,
656
+ signal: combineSignals(signal, timeoutMs)
657
+ });
658
+ }
659
+ reauthenticate(token, signal, timeoutMs) {
660
+ this._auth.invalidate?.(token);
661
+ return this.getToken(signal, timeoutMs);
662
+ }
663
+ /** Resolves a request path against the instance URL, enforcing the origin allowlist. */
664
+ resolveUrl(path, instanceUrl, query) {
665
+ const base = new URL(instanceUrl);
666
+ let url;
667
+ if (/^https?:\/\//i.test(path)) url = new URL(path);
668
+ else if (path.startsWith("/services/")) url = new URL(path, base.origin);
669
+ else url = new URL(`/services/data/${this.apiVersion}${path.startsWith("/") ? path : `/${path}`}`, base.origin);
670
+ if (url.origin !== base.origin && !this._allowedOrigins.has(url.origin)) throw new Error(`Refusing to send a Salesforce request to ${url.origin}: only the instance origin ${base.origin} and allowedOrigins are permitted.`);
671
+ appendQuery(url, query);
672
+ return url;
673
+ }
674
+ async sendOnce(request, method, token, attempt, timeoutMs) {
675
+ const signal = combineSignals(request.signal, timeoutMs);
676
+ const prepared = this.prepare(request, method, token, attempt, signal, timeoutMs);
677
+ const response = await this._transport.send(prepared.transportRequest);
678
+ this.afterResponse(prepared.event, response.status, response.headers);
679
+ return response;
680
+ }
681
+ prepare(request, method, token, attempt, signal, timeoutMs) {
682
+ const url = this.resolveUrl(request.path, token.instanceUrl, request.query);
683
+ const { headers, body } = encodeRequest(request, this._headers);
684
+ headers.set("Authorization", `Bearer ${token.accessToken}`);
685
+ signal?.throwIfAborted();
686
+ const event = {
687
+ method,
688
+ url: url.toString(),
689
+ headers: redactHeaders(headers),
690
+ attempt
691
+ };
692
+ callHook(() => this._hooks.onRequest?.(event));
693
+ return {
694
+ transportRequest: {
695
+ method,
696
+ url,
697
+ headers,
698
+ body,
699
+ signal,
700
+ timeoutMs: timeoutMs > 0 ? timeoutMs : void 0
701
+ },
702
+ event: {
703
+ ...event,
704
+ started: Date.now()
705
+ }
706
+ };
707
+ }
708
+ afterResponse(event, status, headers) {
709
+ this.trackApiUsage(headers);
710
+ const { started, ...requestEvent } = event;
711
+ callHook(() => this._hooks.onResponse?.({
712
+ ...requestEvent,
713
+ status,
714
+ durationMs: Date.now() - started,
715
+ responseHeaders: headersToObject(headers)
716
+ }));
717
+ }
718
+ toResult(request, method, response) {
719
+ if (response.status < 200 || response.status >= 300) throw this.errorFor(request, method, response);
720
+ const data = parseBody(response, request.responseType ?? "auto", true);
721
+ return {
722
+ status: response.status,
723
+ headers: response.headers,
724
+ data
725
+ };
726
+ }
727
+ errorFor(request, method, response) {
728
+ return new SalesforceError({
729
+ status: response.status,
730
+ method,
731
+ path: stripQuery(request.path),
732
+ body: parseBody(response, "auto"),
733
+ headers: headersToObject(response.headers)
734
+ });
735
+ }
736
+ /**
737
+ * Sends a GET request and returns the body as a stream of chunks, when the transport supports
738
+ * streaming (`fetchTransport` does). Returns `undefined` otherwise, so callers can fall back to
739
+ * `send`. Non-2xx responses throw `SalesforceError`; the request is re-authenticated once on 401.
740
+ */
741
+ async stream(request) {
742
+ const stream = this._transport.stream?.bind(this._transport);
743
+ if (!stream) return;
744
+ const method = request.method ?? "GET";
745
+ const timeoutMs = request.timeoutMs ?? this._timeoutMs;
746
+ let token = await this.getToken(request.signal, timeoutMs);
747
+ let reauthenticated = false;
748
+ let retries = 0;
749
+ for (let attempt = 1;; attempt++) {
750
+ const response = await this.openStream(stream, request, method, token, attempt, timeoutMs);
751
+ if (response.status >= 200 && response.status < 300) return response;
752
+ const buffered = {
753
+ status: response.status,
754
+ headers: response.headers,
755
+ body: await collect(response.body)
756
+ };
757
+ if (response.status === 401 && !reauthenticated && this._auth.invalidate) {
758
+ reauthenticated = true;
759
+ const next = await this.reauthenticate(token, request.signal, timeoutMs);
760
+ if (next.accessToken !== token.accessToken) {
761
+ token = next;
762
+ continue;
763
+ }
764
+ }
765
+ if (this.shouldRetry(method, request, buffered, retries)) {
766
+ retries++;
767
+ await sleep(this.retryDelay(buffered, retries), request.signal);
768
+ continue;
769
+ }
770
+ throw this.errorFor(request, method, buffered);
771
+ }
772
+ }
773
+ /**
774
+ * Opens a streamed response. The timeout only covers waiting for the response headers, so a
775
+ * consumer that reads a large body slowly is not cut off.
776
+ */
777
+ async openStream(stream, request, method, token, attempt, timeoutMs) {
778
+ const headerTimeout = new AbortController();
779
+ const timer = timeoutMs > 0 ? setTimeout(() => headerTimeout.abort(new DOMException(`The request timed out after ${timeoutMs} ms.`, "TimeoutError")), timeoutMs) : void 0;
780
+ const signal = request.signal ? AbortSignal.any([request.signal, headerTimeout.signal]) : headerTimeout.signal;
781
+ try {
782
+ const prepared = this.prepare(request, method, token, attempt, signal, 0);
783
+ const response = await stream(prepared.transportRequest);
784
+ this.afterResponse(prepared.event, response.status, response.headers);
785
+ return response;
786
+ } finally {
787
+ clearTimeout(timer);
788
+ }
789
+ }
790
+ shouldRetry(method, request, response, retries) {
791
+ const retry = this._retry;
792
+ if (!retry || retries >= retry.retries || request.retry === false) return false;
793
+ if (retry.statusCodes.includes(response.status) && (request.retry === true || retry.methods.includes(method))) return true;
794
+ return retry.errorCodes.length > 0 && response.status >= 400 && hasErrorCodeIn(response, retry.errorCodes);
795
+ }
796
+ /**
797
+ * Network failures and per-attempt timeouts (the transport threw) are retried for idempotent
798
+ * requests when retries are enabled; a caller abort never is.
799
+ */
800
+ canRetryFailure(method, request, retries) {
801
+ const retry = this._retry;
802
+ if (!retry || retries >= retry.retries || request.retry === false || request.signal?.aborted) return false;
803
+ return request.retry === true || retry.methods.includes(method);
804
+ }
805
+ retryDelay(response, retries) {
806
+ const retry = this._retry;
807
+ const retryAfter = parseRetryAfter(response?.headers.get("retry-after") ?? null);
808
+ const backoff = retry.baseDelayMs * 2 ** (retries - 1);
809
+ return Math.min(retryAfter ?? backoff, retry.maxDelayMs);
810
+ }
811
+ trackApiUsage(headers) {
812
+ const match = /(?:^|[\s,;])api-usage=(\d+)\/(\d+)/.exec(headers.get("sforce-limit-info") ?? "");
813
+ if (match) this._apiUsage = {
814
+ used: Number(match[1]),
815
+ max: Number(match[2])
816
+ };
817
+ }
818
+ };
819
+ }));
820
+ //#endregion
821
+ //#region src/http/fetch-transport.ts
822
+ /** The default transport, built on the platform `fetch` (Node.js 20+). Supports streaming. */
823
+ function fetchTransport(options = {}) {
824
+ const fetchImpl = options.fetch ?? globalThis.fetch;
825
+ if (typeof fetchImpl !== "function") throw new TypeError("No global fetch is available. Use Node.js 20+ or pass a fetch implementation.");
826
+ const call = (request) => fetchImpl(request.url, {
827
+ method: request.method,
828
+ headers: request.headers,
829
+ body: request.body,
830
+ signal: request.signal,
831
+ redirect: "manual"
832
+ });
833
+ return {
834
+ async send(request) {
835
+ const response = await call(request);
836
+ const body = new Uint8Array(await response.arrayBuffer());
837
+ return {
838
+ status: response.status,
839
+ headers: response.headers,
840
+ body
841
+ };
842
+ },
843
+ async stream(request) {
844
+ const response = await call(request);
845
+ return {
846
+ status: response.status,
847
+ headers: response.headers,
848
+ body: chunks$1(response)
849
+ };
850
+ }
851
+ };
852
+ }
853
+ async function* chunks$1(response) {
854
+ if (!response.body) return;
855
+ const reader = response.body.getReader();
856
+ let finished = false;
857
+ try {
858
+ for (;;) {
859
+ const { done, value } = await reader.read();
860
+ if (done) {
861
+ finished = true;
862
+ return;
863
+ }
864
+ yield value;
865
+ }
866
+ } finally {
867
+ if (!finished) await reader.cancel().catch(() => void 0);
868
+ reader.releaseLock();
869
+ }
870
+ }
871
+ var init_fetch_transport = __esmMin((() => {}));
872
+ //#endregion
873
+ //#region src/resources/actions.ts
874
+ function actionResults(error) {
875
+ if (!(error instanceof SalesforceError) || error.status !== 400 || !Array.isArray(error.body) || error.body.length === 0) return;
876
+ const items = error.body;
877
+ const isResult = (item) => typeof item === "object" && item !== null && "isSuccess" in item;
878
+ return items.every(isResult) ? items : void 0;
879
+ }
880
+ var ActionsApi;
881
+ var init_actions = __esmMin((() => {
882
+ init_errors();
883
+ init_connection();
884
+ ActionsApi = class {
885
+ _connection;
886
+ constructor(_connection) {
887
+ this._connection = _connection;
888
+ }
889
+ async listStandard(options = {}) {
890
+ return (await this._connection.request({
891
+ path: "/actions/standard",
892
+ signal: options.signal
893
+ })).actions;
894
+ }
895
+ async listCustom(type, options = {}) {
896
+ return (await this._connection.request({
897
+ path: `/actions/custom/${segment(type)}`,
898
+ signal: options.signal
899
+ })).actions;
900
+ }
901
+ describeStandard(name, options = {}) {
902
+ return this._connection.request({
903
+ path: `/actions/standard/${segment(name)}`,
904
+ signal: options.signal
905
+ });
906
+ }
907
+ describeCustom(type, name, options = {}) {
908
+ return this._connection.request({
909
+ path: `/actions/custom/${segment(type)}/${segment(name)}`,
910
+ signal: options.signal
911
+ });
912
+ }
913
+ /** Invokes a standard action, e.g. `"emailSimple"` or `"chatterPost"`. One result per input. */
914
+ invokeStandard(name, inputs, options) {
915
+ return this.invoke(`/actions/standard/${segment(name)}`, name, inputs, options);
916
+ }
917
+ /** Invokes a custom action of `type`. One result per input. */
918
+ invokeCustom(type, name, inputs, options) {
919
+ return this.invoke(`/actions/custom/${segment(type)}/${segment(name)}`, name, inputs, options);
920
+ }
921
+ /**
922
+ * Runs an autolaunched Flow by API name. Input keys are the Flow's input variable names;
923
+ * `outputValues` holds its output variables plus `Flow__InterviewStatus`.
924
+ */
925
+ invokeFlow(apiName, inputs, options) {
926
+ return this.invokeCustom("flow", apiName, inputs, options);
927
+ }
928
+ /** Invokes an `@InvocableMethod` of an Apex class (`Namespace__ClassName` for managed packages). */
929
+ invokeApex(className, inputs, options) {
930
+ return this.invokeCustom("apex", className, inputs, options);
931
+ }
932
+ async invoke(path, name, inputs, options = {}) {
933
+ if (inputs.length === 0) throw new Error(`Invoking "${name}" requires at least one input (use [{}] for actions without inputs).`);
934
+ let results;
935
+ try {
936
+ results = await this._connection.request({
937
+ method: "POST",
938
+ path,
939
+ body: { inputs },
940
+ signal: options.signal
941
+ });
942
+ } catch (error) {
943
+ const recovered = actionResults(error);
944
+ if (!recovered) throw error;
945
+ results = recovered;
946
+ }
947
+ if (options.throwOnError !== false) {
948
+ const failed = results.filter((result) => !result.isSuccess);
949
+ if (failed.length > 0) throw new SalesforceSaveError(`Action "${name}" failed for ${failed.length} of ${results.length} input(s)`, results, failed.flatMap((result) => result.errors ?? []));
950
+ }
951
+ return results;
952
+ }
953
+ };
954
+ }));
955
+ //#endregion
956
+ //#region src/resources/approvals.ts
957
+ function requireId$1(id) {
958
+ if (typeof id !== "string" || id.trim().length === 0) throw new Error("A record or work item id is required.");
959
+ return id;
960
+ }
961
+ var WORKITEM_PREFIX, ApprovalsApi;
962
+ var init_approvals = __esmMin((() => {
963
+ init_errors();
964
+ init_escape();
965
+ WORKITEM_PREFIX = "04i";
966
+ ApprovalsApi = class {
967
+ _connection;
968
+ _queries;
969
+ constructor(_connection, _queries) {
970
+ this._connection = _connection;
971
+ this._queries = _queries;
972
+ }
973
+ /** Lists the approval processes per sObject. */
974
+ async list(options = {}) {
975
+ return (await this._connection.request({
976
+ path: "/process/approvals/",
977
+ signal: options.signal
978
+ })).approvals;
979
+ }
980
+ /** Submits a record for approval. */
981
+ submit(recordId, options = {}) {
982
+ return this.process({
983
+ actionType: "Submit",
984
+ contextId: requireId$1(recordId),
985
+ comments: options.comments,
986
+ nextApproverIds: options.nextApproverIds,
987
+ processDefinitionNameOrId: options.processDefinitionNameOrId,
988
+ skipEntryCriteria: options.skipEntryCriteria,
989
+ contextActorId: options.submitterId
990
+ }, options);
991
+ }
992
+ /**
993
+ * Approves a pending step. Pass the work item id (`04i...`), or the record id to approve its
994
+ * single pending work item.
995
+ */
996
+ async approve(recordOrWorkitemId, options = {}) {
997
+ const contextId = await this.resolveWorkitem(recordOrWorkitemId, options.signal);
998
+ return this.process({
999
+ actionType: "Approve",
1000
+ contextId,
1001
+ comments: options.comments,
1002
+ nextApproverIds: options.nextApproverIds
1003
+ }, options);
1004
+ }
1005
+ /** Rejects a pending step. Accepts a work item id or a record id, like `approve`. */
1006
+ async reject(recordOrWorkitemId, options = {}) {
1007
+ const contextId = await this.resolveWorkitem(recordOrWorkitemId, options.signal);
1008
+ return this.process({
1009
+ actionType: "Reject",
1010
+ contextId,
1011
+ comments: options.comments
1012
+ }, options);
1013
+ }
1014
+ /** The pending work items of a record (visible to the current user). */
1015
+ async pending(recordId, options = {}) {
1016
+ const soql = `SELECT Id, ActorId, ProcessInstanceId, ProcessInstance.TargetObjectId FROM ProcessInstanceWorkitem WHERE ProcessInstance.TargetObjectId = ${soqlEscape(requireId$1(recordId))} AND ProcessInstance.Status = 'Pending'`;
1017
+ return (await this._queries.collect(soql, { signal: options.signal })).map((row) => ({
1018
+ id: row.Id,
1019
+ actorId: row.ActorId,
1020
+ processInstanceId: row.ProcessInstanceId,
1021
+ targetObjectId: row.ProcessInstance?.TargetObjectId ?? recordId
1022
+ }));
1023
+ }
1024
+ async resolveWorkitem(id, signal) {
1025
+ if (requireId$1(id).startsWith(WORKITEM_PREFIX)) return id;
1026
+ const items = await this.pending(id, { signal });
1027
+ if (items.length === 0) throw new Error(`Record ${id} has no pending approval work item visible to the current user.`);
1028
+ if (items.length > 1) throw new Error(`Record ${id} has ${items.length} pending work items (${items.map((item) => item.id).join(", ")}); pass the work item id instead.`);
1029
+ return items[0].id;
1030
+ }
1031
+ async process(request, options) {
1032
+ const body = Object.fromEntries(Object.entries(request).filter(([, value]) => value !== void 0));
1033
+ const result = await this.send(request, body, options.signal);
1034
+ if (options.throwOnError !== false && !result.success) throw new SalesforceSaveError(`Approval ${request.actionType} failed`, [result], result.errors ?? []);
1035
+ return result;
1036
+ }
1037
+ /**
1038
+ * Sends one approval request. Failures such as `NO_APPLICABLE_PROCESS` or `ALREADY_IN_PROCESS`
1039
+ * come back as HTTP 400 with an error array; they become an unsuccessful result.
1040
+ */
1041
+ async send(request, body, signal) {
1042
+ let raw;
1043
+ try {
1044
+ [raw] = await this._connection.request({
1045
+ method: "POST",
1046
+ path: "/process/approvals/",
1047
+ body: { requests: [body] },
1048
+ signal
1049
+ });
1050
+ } catch (error) {
1051
+ if (error instanceof SalesforceError && error.status === 400 && error.errors.length > 0) return {
1052
+ actorIds: [],
1053
+ entityId: request.contextId,
1054
+ errors: error.errors.map((item) => ({
1055
+ statusCode: item.errorCode,
1056
+ message: item.message,
1057
+ fields: item.fields ?? []
1058
+ })),
1059
+ instanceId: "",
1060
+ instanceStatus: "",
1061
+ newWorkitemIds: [],
1062
+ success: false
1063
+ };
1064
+ throw error;
1065
+ }
1066
+ if (!raw) throw new Error(`Approval ${request.actionType} returned no result.`);
1067
+ const { newWorkItemIds, ...rest } = raw;
1068
+ return {
1069
+ ...rest,
1070
+ newWorkitemIds: rest.newWorkitemIds ?? newWorkItemIds ?? []
1071
+ };
1072
+ }
1073
+ };
1074
+ }));
1075
+ //#endregion
1076
+ //#region src/resources/csv.ts
1077
+ /**
1078
+ * Serializes records to Bulk API CSV. Columns are the union of all keys in first-seen order.
1079
+ * `null` becomes `#N/A` (clears the field), `undefined` stays empty (leaves it unchanged),
1080
+ * `Date` becomes an ISO string (a Date field takes its UTC date), the text `"#N/A"` is written so
1081
+ * that it is stored as text rather than as null, and one level of nesting becomes a relationship column, so
1082
+ * `{ Account: { External_Id__c: "A1" } }` is written as column `Account.External_Id__c`.
1083
+ */
1084
+ function toCsv(records, options = {}) {
1085
+ const delimiter = options.delimiter ?? ",";
1086
+ const newline = options.lineEnding === "CRLF" ? "\r\n" : "\n";
1087
+ const rows = records.map((record) => flattenRecord(record));
1088
+ const columns = [];
1089
+ const seen = /* @__PURE__ */ new Set();
1090
+ for (const row of rows) for (const key of Object.keys(row)) if (!seen.has(key)) {
1091
+ seen.add(key);
1092
+ columns.push(key);
1093
+ }
1094
+ if (columns.length === 0) throw new Error("Cannot build CSV: the records have no fields.");
1095
+ const lines = [columns.map((column) => quote(column, delimiter)).join(delimiter)];
1096
+ for (const row of rows) lines.push(columns.map((column) => quote(formatValue(row[column]), delimiter)).join(delimiter));
1097
+ return lines.join(newline) + newline;
1098
+ }
1099
+ /** Parses CSV (RFC 4180: quoted fields, escaped quotes, CRLF or LF) into rows of strings. */
1100
+ function parseCsvRows(text, delimiter = ",") {
1101
+ const parser = new CsvRowParser(delimiter);
1102
+ return [...parser.push(text), ...parser.end()];
1103
+ }
1104
+ /**
1105
+ * Parses streamed CSV (with a header row) into objects keyed by column name, yielding each row
1106
+ * as soon as it is complete.
1107
+ */
1108
+ async function* parseCsvStream(chunks, delimiter = ",") {
1109
+ const decoder = new TextDecoder();
1110
+ const parser = new CsvRowParser(delimiter);
1111
+ let header;
1112
+ const toRecords = function* (rows) {
1113
+ for (const row of rows) if (!header) header = row;
1114
+ else if (!isBlankLine(header, row)) yield toRecord(header, row);
1115
+ };
1116
+ for await (const chunk of chunks) yield* toRecords(parser.push(decoder.decode(chunk, { stream: true })));
1117
+ yield* toRecords([...parser.push(decoder.decode()), ...parser.end()]);
1118
+ }
1119
+ function toRecord(header, row) {
1120
+ const record = {};
1121
+ header.forEach((column, index) => {
1122
+ record[column] = row[index] ?? "";
1123
+ });
1124
+ return record;
1125
+ }
1126
+ /** Parses CSV with a header row into objects keyed by column name. */
1127
+ function parseCsv(text, delimiter = ",") {
1128
+ const [header, ...rows] = parseCsvRows(text, delimiter);
1129
+ if (!header || header.length === 0) return [];
1130
+ return rows.filter((row) => !isBlankLine(header, row)).map((row) => toRecord(header, row));
1131
+ }
1132
+ /**
1133
+ * A blank line separates nothing in multi-column CSV, but in a single-column file it is a row
1134
+ * with an empty (null) value and must be kept.
1135
+ */
1136
+ function isBlankLine(header, row) {
1137
+ return row.length === 0 && header.length !== 1;
1138
+ }
1139
+ function flattenRecord(record) {
1140
+ const flat = {};
1141
+ for (const [key, value] of Object.entries(record)) {
1142
+ if (key === "attributes") continue;
1143
+ if (isPlainObject(value)) {
1144
+ for (const [nestedKey, nestedValue] of Object.entries(value)) if (nestedKey !== "attributes") flat[`${key}.${nestedKey}`] = nestedValue;
1145
+ } else flat[key] = value;
1146
+ }
1147
+ return flat;
1148
+ }
1149
+ function isPlainObject(value) {
1150
+ return typeof value === "object" && value !== null && !(value instanceof Date) && !Array.isArray(value);
1151
+ }
1152
+ function formatValue(value) {
1153
+ if (value === void 0) return "";
1154
+ if (value === null) return NULL_VALUE;
1155
+ if (value instanceof Date) return value.toISOString();
1156
+ if (Array.isArray(value)) return value.join(";");
1157
+ if (typeof value === "string") return value === NULL_VALUE ? ` ${NULL_VALUE}` : value;
1158
+ if (typeof value === "number") return formatNumber(value);
1159
+ if (typeof value === "boolean" || typeof value === "bigint") return value.toString();
1160
+ return JSON.stringify(value) ?? "";
1161
+ }
1162
+ function quote(value, delimiter) {
1163
+ if (value.includes(delimiter) || value.includes("\"") || value.includes("\n") || value.includes("\r") || value !== value.trim()) return `"${value.replace(/"/g, "\"\"")}"`;
1164
+ return value;
1165
+ }
1166
+ var DELIMITERS, NULL_VALUE, CsvRowParser;
1167
+ var init_csv = __esmMin((() => {
1168
+ init_escape();
1169
+ DELIMITERS = {
1170
+ COMMA: ",",
1171
+ TAB: " ",
1172
+ PIPE: "|",
1173
+ SEMICOLON: ";",
1174
+ CARET: "^",
1175
+ BACKQUOTE: "`"
1176
+ };
1177
+ NULL_VALUE = "#N/A";
1178
+ CsvRowParser = class {
1179
+ _delimiter;
1180
+ _row = [];
1181
+ _field = "";
1182
+ _inQuotes = false;
1183
+ /** The current field was quoted, so it exists even when empty. */
1184
+ _fieldQuoted = false;
1185
+ /** A `"` was seen inside quotes; the next character decides whether it was an escaped quote. */
1186
+ _quotePending = false;
1187
+ /** The last row ended with `\r`; a following `\n` belongs to it. */
1188
+ _skipLineFeed = false;
1189
+ _started = false;
1190
+ constructor(_delimiter = ",") {
1191
+ this._delimiter = _delimiter;
1192
+ }
1193
+ /** Parses a chunk and returns the rows it completed. */
1194
+ push(chunk) {
1195
+ const rows = [];
1196
+ let text = chunk;
1197
+ if (!this._started && text.length > 0) {
1198
+ this._started = true;
1199
+ text = text.charCodeAt(0) === 65279 ? text.slice(1) : text;
1200
+ }
1201
+ for (const char of text) this.consume(char, rows);
1202
+ return rows;
1203
+ }
1204
+ /** Finishes parsing and returns the last row, if any. */
1205
+ end() {
1206
+ this._quotePending = false;
1207
+ this._inQuotes = false;
1208
+ if (this._field.length > 0 || this._row.length > 0 || this._fieldQuoted) {
1209
+ this._row.push(this._field);
1210
+ const row = this._row;
1211
+ this._row = [];
1212
+ this._field = "";
1213
+ this._fieldQuoted = false;
1214
+ return [row];
1215
+ }
1216
+ return [];
1217
+ }
1218
+ consume(char, rows) {
1219
+ if (this._skipLineFeed) {
1220
+ this._skipLineFeed = false;
1221
+ if (char === "\n") return;
1222
+ }
1223
+ if (this._quotePending) {
1224
+ this._quotePending = false;
1225
+ if (char === "\"") {
1226
+ this._field += "\"";
1227
+ return;
1228
+ }
1229
+ this._inQuotes = false;
1230
+ }
1231
+ if (this._inQuotes) {
1232
+ if (char === "\"") this._quotePending = true;
1233
+ else this._field += char;
1234
+ return;
1235
+ }
1236
+ this.consumeUnquoted(char, rows);
1237
+ }
1238
+ consumeUnquoted(char, rows) {
1239
+ if (char === "\"") {
1240
+ this._inQuotes = true;
1241
+ this._fieldQuoted = true;
1242
+ } else if (char === this._delimiter) {
1243
+ this._row.push(this._field);
1244
+ this._field = "";
1245
+ this._fieldQuoted = false;
1246
+ } else if (char === "\n" || char === "\r") {
1247
+ this._skipLineFeed = char === "\r";
1248
+ const blank = this._row.length === 0 && this._field === "" && !this._fieldQuoted;
1249
+ rows.push(blank ? [] : [...this._row, this._field]);
1250
+ this._row = [];
1251
+ this._field = "";
1252
+ this._fieldQuoted = false;
1253
+ } else this._field += char;
1254
+ }
1255
+ };
1256
+ }));
1257
+ //#endregion
1258
+ //#region src/resources/bulk.ts
1259
+ /** Wait options that abort on the call's signal as well as on the wait options' own signal. */
1260
+ function withSignal(wait, signal) {
1261
+ const signals = [signal, wait.signal].filter((item) => item !== void 0);
1262
+ return {
1263
+ ...wait,
1264
+ signal: signals.length > 1 ? AbortSignal.any(signals) : signals[0]
1265
+ };
1266
+ }
1267
+ async function waitForJob(job, options) {
1268
+ const interval = options.pollIntervalMs ?? 2e3;
1269
+ const deadline = Date.now() + (options.timeoutMs ?? 6e5);
1270
+ for (;;) {
1271
+ const info = await job.refresh({ signal: options.signal });
1272
+ if (info.state === "JobComplete") return info;
1273
+ if (info.state === "Failed" || info.state === "Aborted") throw new SalesforceBulkJobError(`Bulk job ${job.id} ended in state ${info.state}${info.errorMessage ? `: ${info.errorMessage}` : ""}`, job.id, info.state, info);
1274
+ if (Date.now() + interval > deadline) throw new SalesforceBulkJobError(`Bulk job ${job.id} did not complete in time (state ${info.state}).`, job.id, info.state, info);
1275
+ await sleep(interval, options.signal);
1276
+ }
1277
+ }
1278
+ var MAX_UPLOAD_BYTES$1, encoder$2, BulkIngestJob, BulkQueryJob, BulkApi;
1279
+ var init_bulk = __esmMin((() => {
1280
+ init_errors();
1281
+ init_connection();
1282
+ init_csv();
1283
+ MAX_UPLOAD_BYTES$1 = 104857600;
1284
+ encoder$2 = new TextEncoder();
1285
+ BulkIngestJob = class {
1286
+ _connection;
1287
+ info;
1288
+ constructor(_connection, info) {
1289
+ this._connection = _connection;
1290
+ this.info = info;
1291
+ }
1292
+ get id() {
1293
+ return this.info.id;
1294
+ }
1295
+ get path() {
1296
+ return `/jobs/ingest/${segment(this.id)}`;
1297
+ }
1298
+ get delimiter() {
1299
+ return DELIMITERS[this.info.columnDelimiter] ?? ",";
1300
+ }
1301
+ /** Uploads CSV data, or records that are converted to CSV. Only allowed while the job is `Open`. */
1302
+ async upload(data, options = {}) {
1303
+ const csv = typeof data === "string" ? data : toCsv(data, {
1304
+ delimiter: this.delimiter,
1305
+ lineEnding: this.info.lineEnding
1306
+ });
1307
+ const size = encoder$2.encode(csv).byteLength;
1308
+ if (size > MAX_UPLOAD_BYTES$1) throw new Error(`Bulk upload of ${size} bytes exceeds the ${MAX_UPLOAD_BYTES$1} byte limit; split the data.`);
1309
+ await this._connection.request({
1310
+ method: "PUT",
1311
+ path: `${this.path}/batches`,
1312
+ body: csv,
1313
+ headers: { "Content-Type": "text/csv" },
1314
+ signal: options.signal
1315
+ });
1316
+ }
1317
+ /** Marks the upload as complete so Salesforce starts processing. */
1318
+ close(options = {}) {
1319
+ return this.setState("UploadComplete", options.signal);
1320
+ }
1321
+ abort(options = {}) {
1322
+ return this.setState("Aborted", options.signal);
1323
+ }
1324
+ async delete(options = {}) {
1325
+ await this._connection.request({
1326
+ method: "DELETE",
1327
+ path: this.path,
1328
+ signal: options.signal
1329
+ });
1330
+ }
1331
+ /** Fetches the current job status. */
1332
+ async refresh(options = {}) {
1333
+ this.info = await this._connection.request({
1334
+ path: this.path,
1335
+ signal: options.signal
1336
+ });
1337
+ return this.info;
1338
+ }
1339
+ /** Polls until the job is `JobComplete`. Throws `SalesforceBulkJobError` on `Failed`, `Aborted` or timeout. */
1340
+ waitForCompletion(options = {}) {
1341
+ return waitForJob(this, options);
1342
+ }
1343
+ /** Successfully processed rows: `sf__Id`, `sf__Created` and the uploaded columns. */
1344
+ successfulResults(options = {}) {
1345
+ return this.results("successfulResults", options.signal);
1346
+ }
1347
+ /** Failed rows: `sf__Id`, `sf__Error` and the uploaded columns. */
1348
+ failedResults(options = {}) {
1349
+ return this.results("failedResults", options.signal);
1350
+ }
1351
+ /** Rows that were not processed (e.g. because the job was aborted). */
1352
+ unprocessedRecords(options = {}) {
1353
+ return this.results("unprocessedrecords", options.signal);
1354
+ }
1355
+ async results(resource, signal) {
1356
+ const csv = await this._connection.request({
1357
+ path: `${this.path}/${resource}/`,
1358
+ headers: { Accept: "text/csv" },
1359
+ responseType: "text",
1360
+ signal
1361
+ });
1362
+ return csv ? parseCsv(csv, this.delimiter) : [];
1363
+ }
1364
+ async setState(state, signal) {
1365
+ this.info = await this._connection.request({
1366
+ method: "PATCH",
1367
+ path: this.path,
1368
+ body: { state },
1369
+ signal
1370
+ });
1371
+ return this.info;
1372
+ }
1373
+ };
1374
+ BulkQueryJob = class {
1375
+ _connection;
1376
+ info;
1377
+ constructor(_connection, info) {
1378
+ this._connection = _connection;
1379
+ this.info = info;
1380
+ }
1381
+ get id() {
1382
+ return this.info.id;
1383
+ }
1384
+ get path() {
1385
+ return `/jobs/query/${segment(this.id)}`;
1386
+ }
1387
+ async refresh(options = {}) {
1388
+ this.info = await this._connection.request({
1389
+ path: this.path,
1390
+ signal: options.signal
1391
+ });
1392
+ return this.info;
1393
+ }
1394
+ /** Polls until the job is `JobComplete`. Throws `SalesforceBulkJobError` on `Failed`, `Aborted` or timeout. */
1395
+ waitForCompletion(options = {}) {
1396
+ return waitForJob(this, options);
1397
+ }
1398
+ async abort(options = {}) {
1399
+ this.info = await this._connection.request({
1400
+ method: "PATCH",
1401
+ path: this.path,
1402
+ body: { state: "Aborted" },
1403
+ signal: options.signal
1404
+ });
1405
+ return this.info;
1406
+ }
1407
+ async delete(options = {}) {
1408
+ await this._connection.request({
1409
+ method: "DELETE",
1410
+ path: this.path,
1411
+ signal: options.signal
1412
+ });
1413
+ }
1414
+ /** Yields result pages (following `Sforce-Locator`). The job must be complete. */
1415
+ async *pages(options = {}) {
1416
+ const delimiter = DELIMITERS[this.info.columnDelimiter] ?? ",";
1417
+ let locator;
1418
+ do {
1419
+ const response = await this._connection.send({
1420
+ path: `${this.path}/results`,
1421
+ query: {
1422
+ locator,
1423
+ maxRecords: options.maxRecords
1424
+ },
1425
+ headers: { Accept: "text/csv" },
1426
+ responseType: "text",
1427
+ signal: options.signal
1428
+ });
1429
+ yield response.data ? parseCsv(response.data, delimiter) : [];
1430
+ const next = response.headers.get("sforce-locator");
1431
+ locator = next && next !== "null" ? next : void 0;
1432
+ } while (locator);
1433
+ }
1434
+ /** Yields every result row. The job must be complete. */
1435
+ async *records(options = {}) {
1436
+ const delimiter = DELIMITERS[this.info.columnDelimiter] ?? ",";
1437
+ let locator;
1438
+ do {
1439
+ const request = {
1440
+ path: `${this.path}/results`,
1441
+ query: {
1442
+ locator,
1443
+ maxRecords: options.maxRecords
1444
+ },
1445
+ headers: { Accept: "text/csv" },
1446
+ signal: options.signal
1447
+ };
1448
+ const streamed = await this._connection.stream(request);
1449
+ if (!streamed) {
1450
+ for await (const page of this.pages(options)) yield* page;
1451
+ return;
1452
+ }
1453
+ yield* parseCsvStream(streamed.body, delimiter);
1454
+ const next = streamed.headers.get("sforce-locator");
1455
+ locator = next && next !== "null" ? next : void 0;
1456
+ } while (locator);
1457
+ }
1458
+ };
1459
+ BulkApi = class {
1460
+ _connection;
1461
+ constructor(_connection) {
1462
+ this._connection = _connection;
1463
+ }
1464
+ /** Creates an ingest job in state `Open`. Upload data with `job.upload()` and then call `job.close()`. */
1465
+ async createIngestJob(options) {
1466
+ if (options.operation === "upsert" && !options.externalIdFieldName) throw new Error("Bulk upsert requires externalIdFieldName.");
1467
+ const info = await this._connection.request({
1468
+ method: "POST",
1469
+ path: "/jobs/ingest",
1470
+ body: {
1471
+ object: options.object,
1472
+ operation: options.operation,
1473
+ externalIdFieldName: options.externalIdFieldName,
1474
+ assignmentRuleId: options.assignmentRuleId,
1475
+ contentType: "CSV",
1476
+ columnDelimiter: options.columnDelimiter ?? "COMMA",
1477
+ lineEnding: options.lineEnding ?? "LF"
1478
+ },
1479
+ signal: options.signal
1480
+ });
1481
+ return new BulkIngestJob(this._connection, info);
1482
+ }
1483
+ /** Attaches to an existing ingest job. */
1484
+ async ingestJob(id, options = {}) {
1485
+ const info = await this._connection.request({
1486
+ path: `/jobs/ingest/${segment(id)}`,
1487
+ signal: options.signal
1488
+ });
1489
+ return new BulkIngestJob(this._connection, info);
1490
+ }
1491
+ /**
1492
+ * Creates an ingest job, uploads the data, closes the job and (by default) waits for it.
1493
+ * Check `job.info.numberRecordsFailed` and `job.failedResults()` afterwards.
1494
+ */
1495
+ async ingest(options) {
1496
+ if (options.csv !== void 0 && options.records !== void 0) throw new Error("bulk.ingest() takes either records or csv, not both.");
1497
+ const data = options.csv ?? options.records;
1498
+ if (data === void 0 || data.length === 0 || typeof data === "string" && data.trim().length === 0) throw new Error("bulk.ingest() requires non-empty records or csv.");
1499
+ const job = await this.createIngestJob(options);
1500
+ try {
1501
+ await job.upload(data, { signal: options.signal });
1502
+ await job.close({ signal: options.signal });
1503
+ } catch (error) {
1504
+ await job.abort().catch(() => void 0);
1505
+ throw error;
1506
+ }
1507
+ if (options.wait !== false) await job.waitForCompletion(withSignal(typeof options.wait === "object" ? options.wait : {}, options.signal));
1508
+ return job;
1509
+ }
1510
+ createQueryJob(query, options = {}) {
1511
+ return this.startQueryJob(query, options);
1512
+ }
1513
+ async startQueryJob(query, options) {
1514
+ if (typeof query !== "string" && query.usesTypeOf) throw new Error("Bulk API 2.0 does not support TYPEOF; use sf.query()/sf.collect() instead.");
1515
+ const operation = options.includeDeleted ? "queryAll" : "query";
1516
+ const info = await this._connection.request({
1517
+ method: "POST",
1518
+ path: "/jobs/query",
1519
+ body: {
1520
+ operation,
1521
+ query: typeof query === "string" ? query : query.build(),
1522
+ contentType: "CSV",
1523
+ columnDelimiter: options.columnDelimiter ?? "COMMA",
1524
+ lineEnding: options.lineEnding ?? "LF"
1525
+ },
1526
+ signal: options.signal
1527
+ });
1528
+ return new BulkQueryJob(this._connection, info);
1529
+ }
1530
+ /** Attaches to an existing query job. */
1531
+ async queryJob(id, options = {}) {
1532
+ const info = await this._connection.request({
1533
+ path: `/jobs/query/${segment(id)}`,
1534
+ signal: options.signal
1535
+ });
1536
+ return new BulkQueryJob(this._connection, info);
1537
+ }
1538
+ async *query(query, options = {}) {
1539
+ const job = await this.startQueryJob(query, options);
1540
+ await job.waitForCompletion(withSignal(options.wait ?? {}, options.signal));
1541
+ yield* job.records({
1542
+ maxRecords: options.maxRecords,
1543
+ signal: options.signal
1544
+ });
1545
+ }
1546
+ };
1547
+ }));
1548
+ //#endregion
1549
+ //#region src/resources/collections.ts
1550
+ function chunks(items, size, options) {
1551
+ if (items.length === 0) throw new Error("At least one record or id is required.");
1552
+ if (items.length > size && !options.chunk) throw new Error(`sObject collections accept at most ${size} items per call, got ${items.length}. Pass { chunk: true } or use the Bulk API.`);
1553
+ if (items.length > size && options.allOrNone) throw new Error("allOrNone cannot be guaranteed across chunks. Send at most 200 records or drop allOrNone.");
1554
+ const result = [];
1555
+ for (let index = 0; index < items.length; index += size) result.push(items.slice(index, index + size));
1556
+ return result;
1557
+ }
1558
+ function assertSuccess(message, results, options) {
1559
+ if (options.throwOnError === false) return;
1560
+ const failed = results.filter((result) => !result.success);
1561
+ if (failed.length > 0) throw new SalesforceSaveError(`${message} failed for ${failed.length} of ${results.length} item(s)`, results, failed.flatMap((result) => result.errors));
1562
+ }
1563
+ var MAX_RECORDS, MAX_RETRIEVE_IDS, CollectionsApi;
1564
+ var init_collections = __esmMin((() => {
1565
+ init_errors();
1566
+ init_connection();
1567
+ MAX_RECORDS = 200;
1568
+ MAX_RETRIEVE_IDS = 2e3;
1569
+ CollectionsApi = class {
1570
+ _connection;
1571
+ constructor(_connection) {
1572
+ this._connection = _connection;
1573
+ }
1574
+ create(sobject, records, options = {}) {
1575
+ return this.save("POST", "/composite/sobjects", sobject, records, options, "Creating");
1576
+ }
1577
+ /** Updates records; every record needs its `Id`. */
1578
+ update(sobject, records, options = {}) {
1579
+ return this.save("PATCH", "/composite/sobjects", sobject, records, options, "Updating");
1580
+ }
1581
+ /** Upserts records of one sObject by external id. Every record needs the external id field. */
1582
+ upsert(sobject, externalIdField, records, options = {}) {
1583
+ return this.save("PATCH", `/composite/sobjects/${segment(sobject)}/${segment(externalIdField)}`, sobject, records, options, "Upserting");
1584
+ }
1585
+ /** Deletes records by id (they may be of different sObjects). */
1586
+ async delete(ids, options = {}) {
1587
+ const results = await runChunks(chunks(ids, MAX_RECORDS, options), (chunk) => this._connection.request({
1588
+ method: "DELETE",
1589
+ path: "/composite/sobjects",
1590
+ query: {
1591
+ ids: chunk,
1592
+ allOrNone: options.allOrNone
1593
+ },
1594
+ signal: options.signal
1595
+ }), "Deleting records failed");
1596
+ assertSuccess("Deleting records", results, options);
1597
+ return results;
1598
+ }
1599
+ /** Retrieves records by id with the given fields. Inaccessible or unknown ids yield `null`. */
1600
+ async retrieve(sobject, ids, fields, options = {}) {
1601
+ if (fields.length === 0) throw new Error("retrieve() requires at least one field.");
1602
+ const results = [];
1603
+ for (const chunk of chunks(ids, MAX_RETRIEVE_IDS, { chunk: true })) results.push(...await this._connection.request({
1604
+ method: "POST",
1605
+ path: `/composite/sobjects/${segment(sobject)}`,
1606
+ body: {
1607
+ ids: chunk,
1608
+ fields
1609
+ },
1610
+ signal: options.signal
1611
+ }));
1612
+ return results;
1613
+ }
1614
+ async save(method, path, sobject, records, options, verb) {
1615
+ const results = await runChunks(chunks(records, MAX_RECORDS, options), (chunk) => {
1616
+ const body = {
1617
+ allOrNone: options.allOrNone ?? false,
1618
+ records: chunk.map((record) => ({
1619
+ attributes: { type: sobject },
1620
+ ...record
1621
+ }))
1622
+ };
1623
+ return this._connection.request({
1624
+ method,
1625
+ path,
1626
+ body,
1627
+ signal: options.signal
1628
+ });
1629
+ }, `${verb} ${sobject} records failed`);
1630
+ assertSuccess(`${verb} ${sobject} records`, results, options);
1631
+ return results;
1632
+ }
1633
+ };
1634
+ }));
1635
+ //#endregion
1636
+ //#region src/resources/composite.ts
1637
+ /**
1638
+ * Encodes `value` with `encode` but leaves `@{...}` references as they are: Salesforce only
1639
+ * substitutes references it can read literally, so an encoded one would be sent as text.
1640
+ */
1641
+ function keepReferences(value, encode) {
1642
+ let result = "";
1643
+ let last = 0;
1644
+ for (const match of value.matchAll(REFERENCE)) {
1645
+ result += encode(value.slice(last, match.index)) + match[0];
1646
+ last = match.index + match[0].length;
1647
+ }
1648
+ return result + encode(value.slice(last));
1649
+ }
1650
+ /** A URL path segment that may be (or contain) a composite reference. */
1651
+ function refSegment(value) {
1652
+ return keepReferences(value, segment);
1653
+ }
1654
+ function assertCompositeLimits(builder) {
1655
+ const count = builder.subrequests.length;
1656
+ if (count === 0 || count > MAX_SUBREQUESTS) throw new Error(`A composite request needs 1 to ${MAX_SUBREQUESTS} subrequests, got ${count}.`);
1657
+ if (builder.queryCount > MAX_QUERY_SUBREQUESTS) throw new Error(`A composite request allows at most ${MAX_QUERY_SUBREQUESTS} query/collection subrequests, got ${builder.queryCount}.`);
1658
+ }
1659
+ function throwFirstError(response) {
1660
+ const failed = response.responses.find((item) => item.httpStatusCode >= 400 && !isProcessingHalted(item.body));
1661
+ if (failed) response.get({
1662
+ referenceId: failed.referenceId,
1663
+ ref: () => ""
1664
+ });
1665
+ }
1666
+ function isProcessingHalted(body) {
1667
+ return Array.isArray(body) && body.some((error) => error.errorCode === "PROCESSING_HALTED");
1668
+ }
1669
+ function countTreeRecords(records) {
1670
+ let total = 0;
1671
+ for (const record of records) {
1672
+ total++;
1673
+ for (const [key, value] of Object.entries(record)) if (key !== "attributes" && isRecordsWrapper(value)) total += countTreeRecords(value.records);
1674
+ }
1675
+ return total;
1676
+ }
1677
+ function isRecordsWrapper(value) {
1678
+ return typeof value === "object" && value !== null && Array.isArray(value.records);
1679
+ }
1680
+ var MAX_SUBREQUESTS, MAX_QUERY_SUBREQUESTS, MAX_GRAPHS, MAX_GRAPH_NODES, MAX_TREE_RECORDS, REFERENCE_ID_PATTERN, REFERENCE, CompositeRequestBuilder, CompositeResponse, CompositeApi;
1681
+ var init_composite = __esmMin((() => {
1682
+ init_errors();
1683
+ init_connection();
1684
+ MAX_SUBREQUESTS = 25;
1685
+ MAX_QUERY_SUBREQUESTS = 5;
1686
+ MAX_GRAPHS = 75;
1687
+ MAX_GRAPH_NODES = 500;
1688
+ MAX_TREE_RECORDS = 200;
1689
+ REFERENCE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_]*$/;
1690
+ REFERENCE = /@\{[^{}]+\}/g;
1691
+ CompositeRequestBuilder = class {
1692
+ _apiVersion;
1693
+ _referencePrefix;
1694
+ _subrequests = [];
1695
+ _queryCount = 0;
1696
+ constructor(_apiVersion, _referencePrefix = "ref") {
1697
+ this._apiVersion = _apiVersion;
1698
+ this._referencePrefix = _referencePrefix;
1699
+ }
1700
+ /** The subrequests collected so far. */
1701
+ get subrequests() {
1702
+ return this._subrequests;
1703
+ }
1704
+ /** Number of query subrequests collected so far (Salesforce allows at most 5 per composite request). */
1705
+ get queryCount() {
1706
+ return this._queryCount;
1707
+ }
1708
+ create(sobject, record, options) {
1709
+ return this.add("POST", `/sobjects/${segment(sobject)}`, record, options);
1710
+ }
1711
+ update(sobject, id, record, options) {
1712
+ return this.add("PATCH", `/sobjects/${segment(sobject)}/${refSegment(id)}`, record, options);
1713
+ }
1714
+ upsert(sobject, externalIdField, externalIdValue, record, options) {
1715
+ return this.add("PATCH", `/sobjects/${segment(sobject)}/${segment(externalIdField)}/${refSegment(externalIdText(externalIdValue))}`, record, options);
1716
+ }
1717
+ delete(sobject, id, options) {
1718
+ return this.add("DELETE", `/sobjects/${segment(sobject)}/${refSegment(id)}`, void 0, options);
1719
+ }
1720
+ get(sobject, id, fields, options) {
1721
+ const query = fields && fields.length > 0 ? `?fields=${fields.map(segment).join(",")}` : "";
1722
+ return this.add("GET", `/sobjects/${segment(sobject)}/${refSegment(id)}${query}`, void 0, options);
1723
+ }
1724
+ query(query, options) {
1725
+ const soql = typeof query === "string" ? query : query.build();
1726
+ this._queryCount++;
1727
+ return this.add("GET", `/query?q=${keepReferences(soql, encodeURIComponent)}`, void 0, options);
1728
+ }
1729
+ /** Adds any other supported subrequest (sObject collections, describe, ...). */
1730
+ request(subrequest, options) {
1731
+ const path = subrequest.path.split("?")[0].replace(/^\/?(services\/data\/)?v[\d.]+(?=\/)/, "");
1732
+ if (/^\/?(tooling\/)?(query|queryAll)(\/|$)|^\/?composite\/sobjects(\/|$)/.test(path)) this._queryCount++;
1733
+ return this.add(subrequest.method, subrequest.path, subrequest.body, options);
1734
+ }
1735
+ /** `ref1`, `ref2`, ...: skips ids already taken by a custom `referenceId`. */
1736
+ nextReferenceId() {
1737
+ const taken = new Set(this._subrequests.map((subrequest) => subrequest.referenceId));
1738
+ for (let index = this._subrequests.length + 1;; index++) {
1739
+ const candidate = `${this._referencePrefix}${index}`;
1740
+ if (!taken.has(candidate)) return candidate;
1741
+ }
1742
+ }
1743
+ add(method, path, body, options = {}) {
1744
+ const referenceId = options.referenceId ?? this.nextReferenceId();
1745
+ if (!REFERENCE_ID_PATTERN.test(referenceId)) throw new Error(`Invalid composite referenceId "${referenceId}": use letters, digits and underscores.`);
1746
+ if (this._subrequests.some((subrequest) => subrequest.referenceId === referenceId)) throw new Error(`Duplicate composite referenceId "${referenceId}".`);
1747
+ const subrequest = {
1748
+ method,
1749
+ url: path.startsWith("/services/") ? path : `/services/data/${this._apiVersion}${path.startsWith("/") ? path : `/${path}`}`,
1750
+ referenceId
1751
+ };
1752
+ if (body !== void 0) subrequest.body = body;
1753
+ if (options.httpHeaders) subrequest.httpHeaders = options.httpHeaders;
1754
+ this._subrequests.push(subrequest);
1755
+ return {
1756
+ referenceId,
1757
+ ref: (refPath) => `@{${referenceId}.${refPath}}`
1758
+ };
1759
+ }
1760
+ };
1761
+ CompositeResponse = class {
1762
+ responses;
1763
+ constructor(responses) {
1764
+ this.responses = responses;
1765
+ }
1766
+ /** `true` when any subrequest returned a status of 400 or higher. */
1767
+ get hasErrors() {
1768
+ return this.responses.some((response) => response.httpStatusCode >= 400);
1769
+ }
1770
+ /** The raw subrequest result for `ref`. */
1771
+ response(ref) {
1772
+ const response = this.responses.find((item) => item.referenceId === ref.referenceId);
1773
+ if (!response) throw new Error(`No composite response for referenceId "${ref.referenceId}".`);
1774
+ return response;
1775
+ }
1776
+ /** The body of the subrequest for `ref`. Throws `SalesforceError` when that subrequest failed. */
1777
+ get(ref) {
1778
+ const response = this.response(ref);
1779
+ if (response.httpStatusCode >= 400) throw new SalesforceError({
1780
+ status: response.httpStatusCode,
1781
+ method: "POST",
1782
+ path: `/composite (${ref.referenceId})`,
1783
+ body: response.body,
1784
+ headers: response.httpHeaders
1785
+ });
1786
+ return response.body;
1787
+ }
1788
+ };
1789
+ CompositeApi = class {
1790
+ _connection;
1791
+ constructor(_connection) {
1792
+ this._connection = _connection;
1793
+ }
1794
+ /**
1795
+ * Runs up to 25 subrequests in one call; later subrequests can reference earlier results.
1796
+ *
1797
+ * ```ts
1798
+ * const result = await sf.composite.execute(c => {
1799
+ * const account = c.create("Account", { Name: "Acme" });
1800
+ * c.create("Contact", { LastName: "Doe", AccountId: account.ref("id") });
1801
+ * return { account };
1802
+ * }, { allOrNone: true });
1803
+ * const accountId = result.get(result.refs.account).id;
1804
+ * ```
1805
+ */
1806
+ async execute(build, options = {}) {
1807
+ const builder = new CompositeRequestBuilder(this._connection.apiVersion);
1808
+ const refs = build(builder);
1809
+ assertCompositeLimits(builder);
1810
+ const body = { compositeRequest: builder.subrequests };
1811
+ if (options.allOrNone !== void 0) body.allOrNone = options.allOrNone;
1812
+ if (options.collateSubrequests !== void 0) body.collateSubrequests = options.collateSubrequests;
1813
+ const raw = await this._connection.request({
1814
+ method: "POST",
1815
+ path: "/composite",
1816
+ body,
1817
+ signal: options.signal
1818
+ });
1819
+ const response = Object.assign(new CompositeResponse(raw.compositeResponse), { refs });
1820
+ if (options.throwOnError) throwFirstError(response);
1821
+ return response;
1822
+ }
1823
+ /** `/composite/batch`: up to 25 independent subrequests (each counts against API limits). */
1824
+ async batch(requests, options = {}) {
1825
+ if (requests.length === 0 || requests.length > MAX_SUBREQUESTS) throw new Error(`A composite batch needs 1 to ${MAX_SUBREQUESTS} subrequests, got ${requests.length}.`);
1826
+ const version = this._connection.apiVersion;
1827
+ const batchRequests = requests.map((request) => ({
1828
+ method: request.method,
1829
+ url: `${version}${request.path.startsWith("/") ? request.path : `/${request.path}`}`,
1830
+ ...request.body === void 0 ? {} : { richInput: request.body }
1831
+ }));
1832
+ return this._connection.request({
1833
+ method: "POST",
1834
+ path: "/composite/batch",
1835
+ body: {
1836
+ haltOnError: options.haltOnError ?? false,
1837
+ batchRequests
1838
+ },
1839
+ signal: options.signal
1840
+ });
1841
+ }
1842
+ /**
1843
+ * `/composite/tree/{sobject}`: creates up to 200 records with nested children in one
1844
+ * all-or-nothing call. On failure a `SalesforceError` is thrown whose `body` holds the per-record errors.
1845
+ */
1846
+ tree(sobject, records, options = {}) {
1847
+ const total = countTreeRecords(records);
1848
+ if (total === 0 || total > MAX_TREE_RECORDS) throw new Error(`An sObject tree needs 1 to ${MAX_TREE_RECORDS} records in total, got ${total}.`);
1849
+ return this._connection.request({
1850
+ method: "POST",
1851
+ path: `/composite/tree/${segment(sobject)}`,
1852
+ body: { records },
1853
+ signal: options.signal
1854
+ });
1855
+ }
1856
+ /** `/composite/graph`: several independent all-or-nothing graphs of subrequests in one call. */
1857
+ async graph(graphs, options = {}) {
1858
+ if (graphs.length === 0 || graphs.length > MAX_GRAPHS) throw new Error(`A composite graph request needs 1 to ${MAX_GRAPHS} graphs, got ${graphs.length}.`);
1859
+ const payload = graphs.map((graph) => {
1860
+ const builder = new CompositeRequestBuilder(this._connection.apiVersion);
1861
+ graph.build(builder);
1862
+ if (builder.subrequests.length === 0) throw new Error(`Graph "${graph.graphId}" has no subrequests.`);
1863
+ return {
1864
+ graphId: graph.graphId,
1865
+ compositeRequest: builder.subrequests
1866
+ };
1867
+ });
1868
+ const nodes = payload.reduce((sum, graph) => sum + graph.compositeRequest.length, 0);
1869
+ if (nodes > MAX_GRAPH_NODES) throw new Error(`A composite graph request allows at most ${MAX_GRAPH_NODES} nodes, got ${nodes}.`);
1870
+ return (await this._connection.request({
1871
+ method: "POST",
1872
+ path: "/composite/graph",
1873
+ body: { graphs: payload },
1874
+ signal: options.signal
1875
+ })).graphs.map((graph) => ({
1876
+ graphId: graph.graphId,
1877
+ isSuccessful: graph.isSuccessful,
1878
+ response: new CompositeResponse(graph.graphResponse.compositeResponse)
1879
+ }));
1880
+ }
1881
+ };
1882
+ }));
1883
+ //#endregion
1884
+ //#region src/resources/events.ts
1885
+ /** Converts a save result, treating the `OPERATION_ENQUEUED` entry as the UUID instead of an error. */
1886
+ function toPublishResult(result) {
1887
+ const errors = result.errors ?? [];
1888
+ const enqueued = errors.find((error) => error.statusCode === "OPERATION_ENQUEUED");
1889
+ const realErrors = errors.filter((error) => error.statusCode !== "OPERATION_ENQUEUED");
1890
+ return {
1891
+ success: result.success && realErrors.length === 0,
1892
+ uuid: enqueued?.message,
1893
+ id: result.id,
1894
+ errors: realErrors
1895
+ };
1896
+ }
1897
+ function toSaveErrors(body) {
1898
+ if (!Array.isArray(body)) return [{
1899
+ statusCode: "UNKNOWN_EXCEPTION",
1900
+ message: JSON.stringify(body) ?? "",
1901
+ fields: []
1902
+ }];
1903
+ return body.map((error) => ({
1904
+ statusCode: error.errorCode,
1905
+ message: error.message,
1906
+ fields: error.fields ?? []
1907
+ }));
1908
+ }
1909
+ var MAX_PER_COMPOSITE, EventsApi;
1910
+ var init_events = __esmMin((() => {
1911
+ init_errors();
1912
+ init_connection();
1913
+ MAX_PER_COMPOSITE = 25;
1914
+ EventsApi = class {
1915
+ _connection;
1916
+ constructor(_connection) {
1917
+ this._connection = _connection;
1918
+ }
1919
+ async publish(eventName, events, options = {}) {
1920
+ if (typeof eventName !== "string" || !/^[A-Za-z][A-Za-z0-9_]*__e$/.test(eventName)) throw new Error(`"${String(eventName)}" is not a platform event name (it must end with __e).`);
1921
+ const list = Array.isArray(events) ? events : [events];
1922
+ if (list.length === 0) throw new Error("publish() requires at least one event.");
1923
+ const batches = [];
1924
+ for (let index = 0; list.length > 1 && index < list.length; index += MAX_PER_COMPOSITE) batches.push(list.slice(index, index + MAX_PER_COMPOSITE));
1925
+ const results = list.length === 1 ? [await this.publishOne(eventName, list[0], options)] : await runChunks(batches, (batch) => this.publishBatch(eventName, batch, options), `Publishing ${eventName} failed`);
1926
+ if (options.throwOnError !== false && results.some((result) => !result.success)) {
1927
+ const failed = results.filter((result) => !result.success);
1928
+ throw new SalesforceSaveError(`Publishing ${eventName} failed for ${failed.length} of ${results.length} event(s)`, results, failed.flatMap((result) => result.errors));
1929
+ }
1930
+ return results;
1931
+ }
1932
+ async publishOne(eventName, event, options) {
1933
+ try {
1934
+ return toPublishResult(await this._connection.request({
1935
+ method: "POST",
1936
+ path: `/sobjects/${segment(eventName)}`,
1937
+ body: event,
1938
+ signal: options.signal
1939
+ }));
1940
+ } catch (error) {
1941
+ if (error instanceof SalesforceError && error.status === 400 && error.errors.length > 0) return {
1942
+ success: false,
1943
+ uuid: void 0,
1944
+ id: void 0,
1945
+ errors: toSaveErrors(error.body)
1946
+ };
1947
+ throw error;
1948
+ }
1949
+ }
1950
+ async publishBatch(eventName, events, options) {
1951
+ const url = `/services/data/${this._connection.apiVersion}/sobjects/${segment(eventName)}`;
1952
+ return (await this._connection.request({
1953
+ method: "POST",
1954
+ path: "/composite",
1955
+ body: {
1956
+ allOrNone: false,
1957
+ compositeRequest: events.map((body, index) => ({
1958
+ method: "POST",
1959
+ url,
1960
+ referenceId: `event${index}`,
1961
+ body
1962
+ }))
1963
+ },
1964
+ signal: options.signal
1965
+ })).compositeResponse.map((item) => item.httpStatusCode < 400 ? toPublishResult(item.body) : {
1966
+ success: false,
1967
+ uuid: void 0,
1968
+ id: void 0,
1969
+ errors: toSaveErrors(item.body)
1970
+ });
1971
+ }
1972
+ };
1973
+ }));
1974
+ //#endregion
1975
+ //#region src/http/multipart.ts
1976
+ /**
1977
+ * Builds a `multipart/form-data` body as bytes, so it can go through any transport without
1978
+ * FormData support.
1979
+ */
1980
+ function buildMultipart(parts, boundary = randomBoundary()) {
1981
+ if (parts.length === 0) throw new Error("A multipart body needs at least one part.");
1982
+ const chunks = [];
1983
+ for (const part of parts) {
1984
+ const filename = part.filename === void 0 ? "" : `; filename="${escapeQuoted(part.filename)}"`;
1985
+ chunks.push(encoder$1.encode(`--${boundary}\r\nContent-Disposition: form-data; name="${escapeQuoted(part.name)}"${filename}\r\nContent-Type: ${part.contentType}\r\n\r\n`), typeof part.data === "string" ? encoder$1.encode(part.data) : part.data, encoder$1.encode("\r\n"));
1986
+ }
1987
+ chunks.push(encoder$1.encode(`--${boundary}--\r\n`));
1988
+ return {
1989
+ body: concat(chunks),
1990
+ contentType: `multipart/form-data; boundary=${boundary}`
1991
+ };
1992
+ }
1993
+ function randomBoundary() {
1994
+ return `----sobjectly${globalThis.crypto.randomUUID().replace(/-/g, "")}`;
1995
+ }
1996
+ function escapeQuoted(value) {
1997
+ return value.replace(/["\r\n]/g, "_");
1998
+ }
1999
+ function concat(chunks) {
2000
+ const result = new Uint8Array(chunks.reduce((total, chunk) => total + chunk.byteLength, 0));
2001
+ let offset = 0;
2002
+ for (const chunk of chunks) {
2003
+ result.set(chunk, offset);
2004
+ offset += chunk.byteLength;
2005
+ }
2006
+ return result;
2007
+ }
2008
+ var encoder$1;
2009
+ var init_multipart = __esmMin((() => {
2010
+ encoder$1 = new TextEncoder();
2011
+ }));
2012
+ //#endregion
2013
+ //#region src/resources/files.ts
2014
+ function requireSaved(result, action) {
2015
+ if (!result.success || !result.id) throw new SalesforceSaveError(`${action} failed`, [result], result.errors ?? []);
2016
+ return result.id;
2017
+ }
2018
+ function stripExtension(fileName) {
2019
+ const index = fileName.lastIndexOf(".");
2020
+ return index > 0 ? fileName.slice(0, index) : fileName;
2021
+ }
2022
+ var MAX_JSON_UPLOAD_BYTES, MAX_UPLOAD_BYTES, encoder, FilesApi;
2023
+ var init_files = __esmMin((() => {
2024
+ init_errors();
2025
+ init_connection();
2026
+ init_multipart();
2027
+ MAX_JSON_UPLOAD_BYTES = Math.floor(29491200);
2028
+ MAX_UPLOAD_BYTES = 2147483648;
2029
+ encoder = new TextEncoder();
2030
+ FilesApi = class {
2031
+ _connection;
2032
+ _jsonUploadLimit;
2033
+ constructor(_connection, _jsonUploadLimit = MAX_JSON_UPLOAD_BYTES) {
2034
+ this._connection = _connection;
2035
+ this._jsonUploadLimit = _jsonUploadLimit;
2036
+ }
2037
+ /** Uploads a file and optionally links it to a record. */
2038
+ async upload(options) {
2039
+ const fields = {
2040
+ Title: options.title ?? stripExtension(options.fileName),
2041
+ PathOnClient: options.fileName
2042
+ };
2043
+ if (options.description !== void 0) fields.Description = options.description;
2044
+ const explicitLink = options.linkTo !== void 0 && (options.shareType ?? options.visibility) !== void 0;
2045
+ if (options.linkTo !== void 0 && !explicitLink) fields.FirstPublishLocationId = options.linkTo;
2046
+ const uploaded = await this.createVersion(fields, options.data, options.signal);
2047
+ if (explicitLink && options.linkTo !== void 0) await this.link(uploaded.contentDocumentId, options.linkTo, {
2048
+ shareType: options.shareType,
2049
+ visibility: options.visibility,
2050
+ signal: options.signal
2051
+ });
2052
+ return uploaded;
2053
+ }
2054
+ /** Adds a new version to an existing file. */
2055
+ newVersion(contentDocumentId, options) {
2056
+ const fields = {
2057
+ ContentDocumentId: contentDocumentId,
2058
+ Title: options.title ?? stripExtension(options.fileName),
2059
+ PathOnClient: options.fileName
2060
+ };
2061
+ if (options.reasonForChange !== void 0) fields.ReasonForChange = options.reasonForChange;
2062
+ return this.createVersion(fields, options.data, options.signal);
2063
+ }
2064
+ /** Downloads file content by ContentVersion id (`068...`) or ContentDocument id (`069...`, latest version). */
2065
+ async download(id, options = {}) {
2066
+ let versionId = id;
2067
+ if (id.startsWith("069")) versionId = (await this._connection.request({
2068
+ path: `/sobjects/ContentDocument/${segment(id)}`,
2069
+ query: { fields: "LatestPublishedVersionId" },
2070
+ signal: options.signal
2071
+ })).LatestPublishedVersionId;
2072
+ return this._connection.request({
2073
+ path: `/sobjects/ContentVersion/${segment(versionId)}/VersionData`,
2074
+ responseType: "binary",
2075
+ signal: options.signal
2076
+ });
2077
+ }
2078
+ /** Shares a file with a record, user or library. Returns the ContentDocumentLink id. */
2079
+ async link(contentDocumentId, linkedEntityId, options = {}) {
2080
+ const body = {
2081
+ ContentDocumentId: contentDocumentId,
2082
+ LinkedEntityId: linkedEntityId,
2083
+ ShareType: options.shareType ?? "V"
2084
+ };
2085
+ if (options.visibility) body.Visibility = options.visibility;
2086
+ return requireSaved(await this._connection.request({
2087
+ method: "POST",
2088
+ path: "/sobjects/ContentDocumentLink",
2089
+ body,
2090
+ signal: options.signal
2091
+ }), "Linking the file");
2092
+ }
2093
+ async createVersion(fields, data, signal) {
2094
+ const bytes = typeof data === "string" ? encoder.encode(data) : data;
2095
+ if (bytes.byteLength > 2147483648) throw new Error(`File of ${bytes.byteLength} bytes exceeds the 2 GB ContentVersion limit.`);
2096
+ const contentVersionId = requireSaved(bytes.byteLength <= this._jsonUploadLimit ? await this._connection.request({
2097
+ method: "POST",
2098
+ path: "/sobjects/ContentVersion",
2099
+ body: {
2100
+ ...fields,
2101
+ VersionData: Buffer.from(bytes).toString("base64")
2102
+ },
2103
+ signal
2104
+ }) : await this.createVersionMultipart(fields, bytes, signal), "Uploading the file");
2105
+ return {
2106
+ contentVersionId,
2107
+ contentDocumentId: (await this._connection.request({
2108
+ path: `/sobjects/ContentVersion/${segment(contentVersionId)}`,
2109
+ query: { fields: "ContentDocumentId" },
2110
+ signal
2111
+ })).ContentDocumentId
2112
+ };
2113
+ }
2114
+ createVersionMultipart(fields, bytes, signal) {
2115
+ const { body, contentType } = buildMultipart([{
2116
+ name: "entity_content",
2117
+ contentType: "application/json",
2118
+ data: JSON.stringify(fields)
2119
+ }, {
2120
+ name: "VersionData",
2121
+ filename: String(fields.PathOnClient),
2122
+ contentType: "application/octet-stream",
2123
+ data: bytes
2124
+ }]);
2125
+ return this._connection.request({
2126
+ method: "POST",
2127
+ path: "/sobjects/ContentVersion",
2128
+ body,
2129
+ headers: { "Content-Type": contentType },
2130
+ signal,
2131
+ timeoutMs: 0
2132
+ });
2133
+ }
2134
+ };
2135
+ }));
2136
+ //#endregion
2137
+ //#region src/resources/query.ts
2138
+ function toQueryResult(response) {
2139
+ if (typeof response !== "object" || response === null || !Array.isArray(response.records)) throw new Error("Unexpected query response: missing records array.");
2140
+ return {
2141
+ records: response.records,
2142
+ totalSize: response.totalSize,
2143
+ done: response.done,
2144
+ nextRecordsUrl: response.nextRecordsUrl
2145
+ };
2146
+ }
2147
+ function batchSizeHeader(batchSize) {
2148
+ if (batchSize === void 0) return;
2149
+ if (!Number.isSafeInteger(batchSize) || batchSize < 200 || batchSize > 2e3) throw new Error("batchSize must be an integer between 200 and 2000.");
2150
+ return { "Sforce-Query-Options": `batchSize=${batchSize}` };
2151
+ }
2152
+ var MAX_QUERY_URL_LENGTH, QueryApi;
2153
+ var init_query = __esmMin((() => {
2154
+ init_errors();
2155
+ MAX_QUERY_URL_LENGTH = 12e3;
2156
+ QueryApi = class {
2157
+ _connection;
2158
+ _basePath;
2159
+ constructor(_connection, _basePath = "") {
2160
+ this._connection = _connection;
2161
+ this._basePath = _basePath;
2162
+ }
2163
+ /** Returns the first page of results. */
2164
+ async page(soql, options = {}) {
2165
+ if (typeof soql !== "string" || soql.trim().length === 0) throw new Error("query() requires a non-blank SOQL string.");
2166
+ const path = `${this._basePath}/${options.includeDeleted ? "queryAll" : "query"}`;
2167
+ const headers = batchSizeHeader(options.batchSize);
2168
+ const encoded = encodeURIComponent(soql);
2169
+ return toQueryResult(encoded.length > 12e3 ? await this.viaComposite(path, encoded, headers, options.signal) : await this._connection.request({
2170
+ path,
2171
+ query: { q: soql },
2172
+ headers,
2173
+ signal: options.signal
2174
+ }));
2175
+ }
2176
+ /** Sends a query that is too long for a URL as a composite subrequest (still one API call). */
2177
+ async viaComposite(path, encodedSoql, headers, signal) {
2178
+ const [response] = (await this._connection.request({
2179
+ method: "POST",
2180
+ path: `${this._basePath}/composite`,
2181
+ body: { compositeRequest: [{
2182
+ method: "GET",
2183
+ referenceId: "query",
2184
+ url: `/services/data/${this._connection.apiVersion}${path}?q=${encodedSoql}`,
2185
+ ...headers ? { httpHeaders: headers } : {}
2186
+ }] },
2187
+ retry: true,
2188
+ signal
2189
+ })).compositeResponse;
2190
+ if (!response || response.httpStatusCode >= 400) throw new SalesforceError({
2191
+ status: response?.httpStatusCode ?? 500,
2192
+ method: "GET",
2193
+ path,
2194
+ body: response?.body,
2195
+ headers: response?.httpHeaders
2196
+ });
2197
+ return response.body;
2198
+ }
2199
+ /** Fetches the page a cursor points to. */
2200
+ async more(cursor, options = {}) {
2201
+ if (typeof cursor !== "string" || !cursor.includes("/query")) throw new Error(`queryMore() expects a nextRecordsUrl, got "${String(cursor)}".`);
2202
+ return toQueryResult(await this._connection.request({
2203
+ path: cursor,
2204
+ headers: batchSizeHeader(options.batchSize),
2205
+ signal: options.signal
2206
+ }));
2207
+ }
2208
+ /** Yields every record, fetching further pages on demand. */
2209
+ async *iterate(soql, options = {}) {
2210
+ let result = await this.page(soql, options);
2211
+ yield* result.records;
2212
+ while (!result.done && result.nextRecordsUrl) {
2213
+ result = await this.more(result.nextRecordsUrl, options);
2214
+ yield* result.records;
2215
+ }
2216
+ }
2217
+ /** Fetches every page and returns all records. */
2218
+ async collect(soql, options = {}) {
2219
+ const records = [];
2220
+ for await (const record of this.iterate(soql, options)) records.push(record);
2221
+ return records;
2222
+ }
2223
+ };
2224
+ }));
2225
+ //#endregion
2226
+ //#region src/resources/quick-actions.ts
2227
+ var GLOBAL_BASE_PATH, QuickActionsApi;
2228
+ var init_quick_actions = __esmMin((() => {
2229
+ init_errors();
2230
+ init_connection();
2231
+ GLOBAL_BASE_PATH = "/quickActions";
2232
+ QuickActionsApi = class {
2233
+ _connection;
2234
+ _basePath;
2235
+ constructor(_connection, _basePath) {
2236
+ this._connection = _connection;
2237
+ this._basePath = _basePath;
2238
+ }
2239
+ list(options = {}) {
2240
+ return this._connection.request({
2241
+ path: `${this._basePath}/`,
2242
+ signal: options.signal
2243
+ });
2244
+ }
2245
+ describe(name, options = {}) {
2246
+ return this.send(name, "/describe/", { signal: options.signal });
2247
+ }
2248
+ /** The default field values the action would pre-fill, optionally for a context record. */
2249
+ defaultValues(name, contextId, options = {}) {
2250
+ const suffix = contextId ? `/${segment(contextId)}` : "";
2251
+ return this.send(name, `/defaultValues${suffix}`, { signal: options.signal });
2252
+ }
2253
+ /** Runs the action with the given record values. */
2254
+ async invoke(name, record, options = {}) {
2255
+ const body = { record };
2256
+ if (options.contextId) body.contextId = options.contextId;
2257
+ const result = await this.send(name, "", {
2258
+ method: "POST",
2259
+ body,
2260
+ signal: options.signal
2261
+ });
2262
+ if (options.throwOnError !== false && !result.success) throw new SalesforceSaveError(`Quick action "${name}" failed`, [result], result.errors ?? []);
2263
+ return result;
2264
+ }
2265
+ /** Calls the action under this sObject, falling back to the global action of that name. */
2266
+ async send(name, suffix, request) {
2267
+ if (typeof name !== "string" || !/^[A-Za-z][A-Za-z0-9_.]*$/.test(name)) throw new Error(`Invalid quick action name "${String(name)}".`);
2268
+ const path = (base) => `${base}/${segment(name)}${suffix}`;
2269
+ try {
2270
+ return await this._connection.request({
2271
+ ...request,
2272
+ path: path(this._basePath)
2273
+ });
2274
+ } catch (error) {
2275
+ if (this._basePath === GLOBAL_BASE_PATH || !hasErrorCode(error, "NOT_FOUND")) throw error;
2276
+ return this._connection.request({
2277
+ ...request,
2278
+ path: path(GLOBAL_BASE_PATH)
2279
+ });
2280
+ }
2281
+ }
2282
+ };
2283
+ }));
2284
+ //#endregion
2285
+ //#region src/resources/reports.ts
2286
+ /**
2287
+ * Flattens the detail rows of a report result into objects keyed by detail column API name
2288
+ * (e.g. `ACCOUNT.NAME`), each with the cell's `label` and `value`. Works for tabular, summary
2289
+ * and matrix reports; requires `includeDetails`.
2290
+ */
2291
+ function reportRows(result) {
2292
+ const columns = result.reportMetadata.detailColumns;
2293
+ const rows = [];
2294
+ const downDepth = result.reportMetadata.groupingsDown?.length ?? 0;
2295
+ const acrossDepth = result.reportMetadata.groupingsAcross?.length ?? 0;
2296
+ for (const [key, fact] of Object.entries(result.factMap)) {
2297
+ const [down = "T", across = "T"] = key.split("!");
2298
+ if (!fact.rows || groupingDepth(down) !== downDepth || groupingDepth(across) !== acrossDepth) continue;
2299
+ for (const row of fact.rows) {
2300
+ const record = {};
2301
+ columns.forEach((column, index) => {
2302
+ const cell = row.dataCells[index];
2303
+ if (cell) record[column] = cell;
2304
+ });
2305
+ rows.push(record);
2306
+ }
2307
+ }
2308
+ return rows;
2309
+ }
2310
+ /** `"T"` is the total (depth 0); `"0"` is depth 1, `"0_2"` depth 2, ... */
2311
+ function groupingDepth(key) {
2312
+ return key === "T" ? 0 : key.split("_").length;
2313
+ }
2314
+ function runMetadata(options) {
2315
+ if (!options.filters && options.booleanFilter === void 0) return;
2316
+ const metadata = {};
2317
+ if (options.filters) metadata.reportFilters = options.filters;
2318
+ if (options.booleanFilter !== void 0) metadata.reportBooleanFilter = options.booleanFilter;
2319
+ return metadata;
2320
+ }
2321
+ function reportPath(reportId) {
2322
+ if (typeof reportId !== "string" || !/^[A-Za-z0-9]{15,18}$/.test(reportId)) throw new Error(`Invalid report id "${String(reportId)}".`);
2323
+ return `/analytics/reports/${reportId}`;
2324
+ }
2325
+ var ReportsApi;
2326
+ var init_reports = __esmMin((() => {
2327
+ init_connection();
2328
+ ReportsApi = class {
2329
+ _connection;
2330
+ constructor(_connection) {
2331
+ this._connection = _connection;
2332
+ }
2333
+ /** Recently viewed reports. */
2334
+ list(options = {}) {
2335
+ return this._connection.request({
2336
+ path: "/analytics/reports",
2337
+ signal: options.signal
2338
+ });
2339
+ }
2340
+ describe(reportId, options = {}) {
2341
+ return this._connection.request({
2342
+ path: `${reportPath(reportId)}/describe`,
2343
+ signal: options.signal
2344
+ });
2345
+ }
2346
+ /** Runs a report synchronously. */
2347
+ run(reportId, options = {}) {
2348
+ const reportMetadata = runMetadata(options);
2349
+ return this._connection.request({
2350
+ method: reportMetadata ? "POST" : "GET",
2351
+ path: reportPath(reportId),
2352
+ query: { includeDetails: options.includeDetails ?? true },
2353
+ body: reportMetadata ? { reportMetadata } : void 0,
2354
+ signal: options.signal,
2355
+ timeoutMs: 0
2356
+ });
2357
+ }
2358
+ /** Starts an asynchronous run; results stay available for 24 hours. */
2359
+ runAsync(reportId, options = {}) {
2360
+ const reportMetadata = runMetadata(options);
2361
+ return this._connection.request({
2362
+ method: "POST",
2363
+ path: `${reportPath(reportId)}/instances`,
2364
+ query: { includeDetails: options.includeDetails ?? true },
2365
+ body: reportMetadata ? { reportMetadata } : {},
2366
+ signal: options.signal
2367
+ });
2368
+ }
2369
+ /** Status (and, when finished, results) of an asynchronous run. */
2370
+ instance(reportId, instanceId, options = {}) {
2371
+ return this._connection.request({
2372
+ path: `${reportPath(reportId)}/instances/${segment(instanceId)}`,
2373
+ signal: options.signal
2374
+ });
2375
+ }
2376
+ /** Polls an asynchronous run until it succeeds, then returns the results. */
2377
+ async waitForInstance(reportId, instanceId, options = {}) {
2378
+ const interval = options.pollIntervalMs ?? 2e3;
2379
+ const deadline = Date.now() + (options.timeoutMs ?? 3e5);
2380
+ for (;;) {
2381
+ const result = await this.instance(reportId, instanceId, { signal: options.signal });
2382
+ const status = result.attributes.status;
2383
+ if (status === "Success") return result;
2384
+ if (status === "Error") throw new Error(`Report run ${instanceId} of report ${reportId} failed.`);
2385
+ if (Date.now() + interval > deadline) throw new Error(`Report run ${instanceId} of report ${reportId} did not finish in time (status ${status}).`);
2386
+ await sleep(interval, options.signal);
2387
+ }
2388
+ }
2389
+ /** Flattens detail rows of a report result; see `reportRows`. */
2390
+ toRows(result) {
2391
+ return reportRows(result);
2392
+ }
2393
+ };
2394
+ }));
2395
+ //#endregion
2396
+ //#region src/resources/search.ts
2397
+ var SearchApi;
2398
+ var init_search = __esmMin((() => {
2399
+ SearchApi = class {
2400
+ _connection;
2401
+ constructor(_connection) {
2402
+ this._connection = _connection;
2403
+ }
2404
+ /**
2405
+ * Runs a SOSL query, e.g. `FIND {Acme*} IN NAME FIELDS RETURNING Account(Id, Name)`.
2406
+ * Escape user input in the `FIND {...}` term with `soslEscape`.
2407
+ */
2408
+ sosl(query, options = {}) {
2409
+ if (typeof query !== "string" || query.trim().length === 0) throw new Error("sosl() requires a non-blank SOSL string.");
2410
+ return this._connection.request({
2411
+ path: "/search",
2412
+ query: { q: query },
2413
+ signal: options.signal
2414
+ });
2415
+ }
2416
+ /** `POST /parameterizedSearch`: search without writing SOSL. */
2417
+ parameterized(request, options = {}) {
2418
+ return this._connection.request({
2419
+ method: "POST",
2420
+ path: "/parameterizedSearch",
2421
+ body: request,
2422
+ signal: options.signal
2423
+ });
2424
+ }
2425
+ /** `GET /search/suggestions`: auto-suggest records matching the start of a term. */
2426
+ suggestions(options) {
2427
+ const { signal, ...query } = options;
2428
+ return this._connection.request({
2429
+ path: "/search/suggestions",
2430
+ query,
2431
+ signal
2432
+ });
2433
+ }
2434
+ };
2435
+ }));
2436
+ //#endregion
2437
+ //#region src/soql/query-builder.ts
2438
+ /**
2439
+ * Narrows a record by its `attributes.type`, e.g. the union returned for a `selectTypeOf` field.
2440
+ * TypeScript doesn't narrow on nested properties, so `record.attributes.type === "Account"` alone
2441
+ * won't narrow; this guard does:
2442
+ *
2443
+ * ```ts
2444
+ * if (isSObjectType(task.What, "Account")) task.What.Phone;
2445
+ * ```
2446
+ */
2447
+ function isSObjectType(record, type) {
2448
+ return record?.attributes.type === type;
2449
+ }
2450
+ /** Returns a factory for builders bound to registry `R`: `const soql = soqlFor<SObjectRegistry>(); soql("Account")`. */
2451
+ function soqlFor() {
2452
+ return (sobjectName) => SoqlQueryBuilder.from(sobjectName);
2453
+ }
2454
+ function fieldList(fields) {
2455
+ fields.forEach((field) => assertIdentifier("TYPEOF field", field));
2456
+ return [...new Set(fields)].join(", ");
2457
+ }
2458
+ function assertParentPath(path) {
2459
+ const segments = typeof path === "string" ? path.split(".") : [];
2460
+ if (segments.length === 0 || segments.some((segment) => !IDENTIFIER.test(segment))) throw new Error(`Invalid relationship path "${String(path)}".`);
2461
+ if (segments.length > MAX_PARENT_DEPTH) throw new Error(`Relationship path "${path}" exceeds Salesforce's maximum of ${MAX_PARENT_DEPTH} levels.`);
2462
+ }
2463
+ function assertField(context, field) {
2464
+ if (typeof field !== "string" || !FIELD_PATH.test(field)) throw new Error(`${context}: invalid field name "${String(field)}".`);
2465
+ }
2466
+ function assertOperator(operator) {
2467
+ if (!OPERATORS.has(operator)) throw new Error(`Invalid SOQL operator "${String(operator)}". Use one of: ${[...OPERATORS].join(", ")}.`);
2468
+ }
2469
+ function orderSuffix(direction, nullOrder) {
2470
+ if (!DIRECTIONS.has(direction)) throw new Error(`Invalid sort direction "${String(direction)}". Use ASC or DESC.`);
2471
+ if (nullOrder !== void 0 && !NULL_ORDERS.has(nullOrder)) throw new Error(`Invalid null order "${String(nullOrder)}". Use NULLS FIRST or NULLS LAST.`);
2472
+ return `${direction}${nullOrder ? ` ${nullOrder}` : ""}`;
2473
+ }
2474
+ function assertIdentifier(context, value) {
2475
+ if (typeof value !== "string" || !IDENTIFIER.test(value)) throw new Error(`${context}: invalid name "${String(value)}".`);
2476
+ }
2477
+ function assertNonBlank(method, value) {
2478
+ if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${method}() requires a non-blank SOQL fragment.`);
2479
+ }
2480
+ function assertNonNegativeInteger(method, count) {
2481
+ if (!Number.isSafeInteger(count) || count < 0) throw new Error(`${method}() requires a non-negative safe integer.`);
2482
+ }
2483
+ var FIELDS_ALL_MAX_LIMIT, MAX_OFFSET, MAX_PARENT_DEPTH, ALIAS_PATTERN, IDENTIFIER, FIELD_PATH, OPERATORS, DIRECTIONS, NULL_ORDERS, RESERVED_WORDS, TypeOfBuilder, SoqlQueryBuilder;
2484
+ var init_query_builder = __esmMin((() => {
2485
+ init_escape();
2486
+ FIELDS_ALL_MAX_LIMIT = 200;
2487
+ MAX_OFFSET = 2e3;
2488
+ MAX_PARENT_DEPTH = 5;
2489
+ ALIAS_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/;
2490
+ IDENTIFIER = /^[A-Za-z][A-Za-z0-9_]*$/;
2491
+ FIELD_PATH = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)*$/;
2492
+ OPERATORS = /* @__PURE__ */ new Set([
2493
+ "=",
2494
+ "!=",
2495
+ ">",
2496
+ "<",
2497
+ ">=",
2498
+ "<=",
2499
+ "LIKE"
2500
+ ]);
2501
+ DIRECTIONS = /* @__PURE__ */ new Set(["ASC", "DESC"]);
2502
+ NULL_ORDERS = /* @__PURE__ */ new Set(["NULLS FIRST", "NULLS LAST"]);
2503
+ RESERVED_WORDS = new Set([
2504
+ "AND",
2505
+ "ASC",
2506
+ "AT",
2507
+ "BY",
2508
+ "CUBE",
2509
+ "DESC",
2510
+ "ELSE",
2511
+ "END",
2512
+ "EXCLUDES",
2513
+ "FALSE",
2514
+ "FIRST",
2515
+ "FOR",
2516
+ "FORMAT",
2517
+ "FROM",
2518
+ "GROUP",
2519
+ "HAVING",
2520
+ "IN",
2521
+ "INCLUDES",
2522
+ "LAST",
2523
+ "LIKE",
2524
+ "LIMIT",
2525
+ "NOT",
2526
+ "NULL",
2527
+ "NULLS",
2528
+ "OFFSET",
2529
+ "OR",
2530
+ "ORDER",
2531
+ "REFERENCE",
2532
+ "ROLLUP",
2533
+ "SELECT",
2534
+ "THEN",
2535
+ "TRACKING",
2536
+ "TRUE",
2537
+ "TYPEOF",
2538
+ "UPDATE",
2539
+ "USING",
2540
+ "VIEW",
2541
+ "WHEN",
2542
+ "WHERE",
2543
+ "WITH"
2544
+ ].map((word) => word.toLowerCase()));
2545
+ TypeOfBuilder = class {
2546
+ _whens = [];
2547
+ _types = /* @__PURE__ */ new Set();
2548
+ _else;
2549
+ /** `WHEN type THEN fields`. */
2550
+ when(sobject, firstField, ...additionalFields) {
2551
+ assertIdentifier("TYPEOF when()", sobject);
2552
+ if (this._types.has(sobject)) throw new Error(`TYPEOF already has a WHEN ${sobject} clause.`);
2553
+ if (this._else !== void 0) throw new Error("TYPEOF when() must come before else().");
2554
+ this._types.add(sobject);
2555
+ this._whens.push(`WHEN ${sobject} THEN ${fieldList([firstField, ...additionalFields])}`);
2556
+ return this;
2557
+ }
2558
+ /** `ELSE fields`: the Name fields for every type without a `when`. */
2559
+ else(firstField, ...additionalFields) {
2560
+ this._else = fieldList([firstField, ...additionalFields]);
2561
+ return this;
2562
+ }
2563
+ /** @internal */
2564
+ toSoql(relationship) {
2565
+ if (this._whens.length === 0) throw new Error(`selectTypeOf("${relationship}") requires at least one when().`);
2566
+ const elseClause = this._else === void 0 ? "" : ` ELSE ${this._else}`;
2567
+ return `TYPEOF ${relationship} ${this._whens.join(" ")}${elseClause} END`;
2568
+ }
2569
+ };
2570
+ SoqlQueryBuilder = class SoqlQueryBuilder {
2571
+ _fromClause;
2572
+ _fields = [];
2573
+ _conditions = [];
2574
+ _groupBy = [];
2575
+ _having = [];
2576
+ _orderBy = [];
2577
+ _limit;
2578
+ _offset;
2579
+ _withUserMode = false;
2580
+ _forClause;
2581
+ _usesTypeOf = false;
2582
+ _countAll = false;
2583
+ _usesAggregates = false;
2584
+ constructor(fromClause) {
2585
+ this._fromClause = fromClause;
2586
+ }
2587
+ /** Returns an independent copy, so the original can be reused as a base for other queries. */
2588
+ clone() {
2589
+ const copy = new SoqlQueryBuilder(this._fromClause);
2590
+ copy._fields = [...this._fields];
2591
+ copy._conditions = [...this._conditions];
2592
+ copy._groupBy = [...this._groupBy];
2593
+ copy._having = [...this._having];
2594
+ copy._orderBy = [...this._orderBy];
2595
+ copy._limit = this._limit;
2596
+ copy._offset = this._offset;
2597
+ copy._withUserMode = this._withUserMode;
2598
+ copy._forClause = this._forClause;
2599
+ copy._usesTypeOf = this._usesTypeOf;
2600
+ copy._countAll = this._countAll;
2601
+ copy._usesAggregates = this._usesAggregates;
2602
+ return copy;
2603
+ }
2604
+ /** Starts a query on `sobjectName`. Prefer `client.soql()` or `soqlFor<R>()`, which infer `R`. */
2605
+ static from(sobjectName) {
2606
+ if (typeof sobjectName !== "string" || !IDENTIFIER.test(sobjectName)) throw new Error(`Invalid sObject name "${String(sobjectName)}".`);
2607
+ return new SoqlQueryBuilder(sobjectName);
2608
+ }
2609
+ /** The sObject (or child relationship, in a subquery) this query selects from. */
2610
+ get sobjectName() {
2611
+ return this._fromClause;
2612
+ }
2613
+ /** `true` when the query contains `TYPEOF`, which Bulk API, semi-joins and GROUP BY don't support. */
2614
+ get usesTypeOf() {
2615
+ return this._usesTypeOf;
2616
+ }
2617
+ /** Adds fields to the select list. The result type is narrowed to the selected fields. */
2618
+ select(firstField, ...additionalFields) {
2619
+ if (typeof firstField !== "string" || firstField.length === 0) throw new Error("select() requires at least one field.");
2620
+ const fields = [firstField, ...additionalFields];
2621
+ fields.forEach((field) => assertField("select()", field));
2622
+ this.addFields(fields);
2623
+ return this.cast();
2624
+ }
2625
+ /** Escape hatch for select expressions the typed helpers can't express. The result gains an index signature. */
2626
+ selectRaw(firstExpression, ...additionalExpressions) {
2627
+ const expressions = [firstExpression, ...additionalExpressions];
2628
+ expressions.forEach((expression) => assertNonBlank("selectRaw", expression));
2629
+ this.addFields(expressions);
2630
+ return this.cast();
2631
+ }
2632
+ /**
2633
+ * Selects fields through a child-to-parent path, e.g. `selectRelated("Account", "Name")` or
2634
+ * `selectRelated("Account.Owner", "Email")` from `Contact`. Each level is `| null` in the result.
2635
+ */
2636
+ selectRelated(path, firstField, ...additionalFields) {
2637
+ assertParentPath(path);
2638
+ const fields = [firstField, ...additionalFields];
2639
+ fields.forEach((field) => assertIdentifier("selectRelated()", field));
2640
+ this.addFields(fields.map((field) => `${String(path)}.${field}`));
2641
+ return this.cast();
2642
+ }
2643
+ /**
2644
+ * Selects Name fields of a polymorphic lookup without TYPEOF, e.g. `selectPolymorphic("Owner", "Name")`
2645
+ * from `Case`. `attributes.type` tells which sObject it is.
2646
+ */
2647
+ selectPolymorphic(relationship, firstField, ...additionalFields) {
2648
+ assertIdentifier("selectPolymorphic()", relationship);
2649
+ [firstField, ...additionalFields].forEach((field) => assertIdentifier("selectPolymorphic()", field));
2650
+ this.addFields([firstField, ...additionalFields].map((field) => `${String(relationship)}.${field}`));
2651
+ return this.cast();
2652
+ }
2653
+ /**
2654
+ * Adds a `TYPEOF` expression for a polymorphic lookup. The result is a union discriminated on
2655
+ * `attributes.type`:
2656
+ *
2657
+ * ```ts
2658
+ * sf.soql("Task").select("Id").selectTypeOf("What", (t) =>
2659
+ * t.when("Account", "Phone").when("Opportunity", "Amount").else("Name"),
2660
+ * );
2661
+ * ```
2662
+ */
2663
+ selectTypeOf(relationship, build) {
2664
+ assertIdentifier("selectTypeOf()", relationship);
2665
+ const builder = build(new TypeOfBuilder());
2666
+ this._fields.push(builder.toSoql(relationship));
2667
+ this._usesTypeOf = true;
2668
+ return this.cast();
2669
+ }
2670
+ /**
2671
+ * Adds a parent-to-child subquery, e.g. `selectChild("Contacts", c => c.select("Id", "Email"))`
2672
+ * from `Account` builds `(SELECT Id, Email FROM Contacts)`.
2673
+ */
2674
+ selectChild(relationship, build) {
2675
+ assertIdentifier("selectChild()", relationship);
2676
+ const sub = new SoqlQueryBuilder(relationship);
2677
+ if (build(sub) !== sub) throw new Error(`selectChild("${String(relationship)}") callback must return the provided query builder.`);
2678
+ if (sub._fields.length === 0) throw new Error(`selectChild("${String(relationship)}") requires select() with at least one field.`);
2679
+ const invalid = [
2680
+ sub._usesTypeOf ? "TYPEOF" : "",
2681
+ sub._usesAggregates || sub._countAll ? "aggregate functions" : "",
2682
+ sub._groupBy.length > 0 || sub._having.length > 0 ? "GROUP BY/HAVING" : "",
2683
+ sub._withUserMode ? "WITH USER_MODE" : "",
2684
+ sub._forClause ? `FOR ${sub._forClause}` : ""
2685
+ ].filter((item) => item.length > 0);
2686
+ if (invalid.length > 0) throw new Error(`selectChild("${String(relationship)}"): ${invalid.join(", ")} can't be used in a subquery.`);
2687
+ this._fields.push(`(${sub.build()})`);
2688
+ return this.cast();
2689
+ }
2690
+ count(field, alias) {
2691
+ if (field === void 0) {
2692
+ this._countAll = true;
2693
+ this.addFields(["COUNT()"]);
2694
+ } else this.selectAggregate("COUNT", field, alias);
2695
+ return this.cast();
2696
+ }
2697
+ countDistinct(field, alias) {
2698
+ this.selectAggregate("COUNT_DISTINCT", field, alias);
2699
+ return this.cast();
2700
+ }
2701
+ sum(field, alias) {
2702
+ this.selectAggregate("SUM", field, alias);
2703
+ return this.cast();
2704
+ }
2705
+ avg(field, alias) {
2706
+ this.selectAggregate("AVG", field, alias);
2707
+ return this.cast();
2708
+ }
2709
+ min(field, alias) {
2710
+ this.selectAggregate("MIN", field, alias);
2711
+ return this.cast();
2712
+ }
2713
+ max(field, alias) {
2714
+ this.selectAggregate("MAX", field, alias);
2715
+ return this.cast();
2716
+ }
2717
+ /**
2718
+ * Adds `field operator value`. The value is escaped and type-checked against the field:
2719
+ * date fields take `soqlDate()`/`soqlDateLiteral()`, datetime fields a `Date` or literal,
2720
+ * `LIKE` only works on text, and `null` only on nillable fields with `=`/`!=`.
2721
+ */
2722
+ where(field, operator, value) {
2723
+ assertField("where()", field);
2724
+ assertOperator(operator);
2725
+ this._conditions.push(`${field} ${operator} ${soqlEscape(value)}`);
2726
+ return this;
2727
+ }
2728
+ /**
2729
+ * Adds a raw condition, wrapped in parentheses so an `OR` inside it can't change how it combines
2730
+ * with other conditions. Escape interpolated values with `soqlEscape`.
2731
+ */
2732
+ whereRaw(conditionSql) {
2733
+ assertNonBlank("whereRaw", conditionSql);
2734
+ this._conditions.push(`(${conditionSql})`);
2735
+ return this;
2736
+ }
2737
+ /** `field IN (...)` with a list of values or a single-field semi-join subquery. */
2738
+ whereIn(field, values) {
2739
+ return this.whereSet(field, "IN", values);
2740
+ }
2741
+ /** `field NOT IN (...)` with a list of values or a single-field anti-join subquery. */
2742
+ whereNotIn(field, values) {
2743
+ return this.whereSet(field, "NOT IN", values);
2744
+ }
2745
+ /**
2746
+ * `field INCLUDES (...)` for multi-select picklists. Each item matches when all its values are
2747
+ * selected: `whereIncludes("Interests__c", [["Golf", "Tennis"], "Chess"])` builds
2748
+ * `INCLUDES ('Golf;Tennis', 'Chess')`.
2749
+ */
2750
+ whereIncludes(field, values) {
2751
+ return this.whereMultiPicklist(String(field), "INCLUDES", values);
2752
+ }
2753
+ /** `field EXCLUDES (...)` for multi-select picklists; items combine like `whereIncludes`. */
2754
+ whereExcludes(field, values) {
2755
+ return this.whereMultiPicklist(String(field), "EXCLUDES", values);
2756
+ }
2757
+ /**
2758
+ * Wraps conditions in parentheses, joined by `join` (default `OR`). Groups nest, so
2759
+ * `(A OR B) AND (C OR (D AND E))` is expressible. Top-level conditions are always joined with AND.
2760
+ */
2761
+ whereGroup(build, join = "OR") {
2762
+ const conditions = this.groupConditions(build);
2763
+ if (conditions.length > 0) this._conditions.push(`(${conditions.join(` ${join} `)})`);
2764
+ return this;
2765
+ }
2766
+ /** Negates a group of conditions: `whereNot(g => g.where("Type", "=", "Prospect"))` builds `(NOT (Type = 'Prospect'))`. */
2767
+ whereNot(build, join = "AND") {
2768
+ const conditions = this.groupConditions(build);
2769
+ if (conditions.length === 0) throw new Error("whereNot() requires at least one condition.");
2770
+ this._conditions.push(`(NOT (${conditions.join(` ${join} `)}))`);
2771
+ return this;
2772
+ }
2773
+ /**
2774
+ * Condition on a parent field, e.g. `whereRelated("Account", "Name", "=", "Acme")` or
2775
+ * `whereRelated("Account.Owner", "IsActive", "=", true)` from `Contact`.
2776
+ */
2777
+ whereRelated(path, field, operator, value) {
2778
+ assertParentPath(path);
2779
+ assertIdentifier("whereRelated()", field);
2780
+ assertOperator(operator);
2781
+ this._conditions.push(`${String(path)}.${field} ${operator} ${soqlEscape(value)}`);
2782
+ return this;
2783
+ }
2784
+ groupBy(...fields) {
2785
+ fields.forEach((field) => assertField("groupBy()", field));
2786
+ this._groupBy.push(...fields);
2787
+ return this;
2788
+ }
2789
+ /** Raw `HAVING` condition, e.g. `"COUNT(Id) > 1"`, wrapped in parentheses. */
2790
+ havingRaw(conditionSql) {
2791
+ assertNonBlank("havingRaw", conditionSql);
2792
+ this._having.push(`(${conditionSql})`);
2793
+ return this;
2794
+ }
2795
+ havingGroup(build, join = "OR") {
2796
+ const group = new SoqlQueryBuilder(this._fromClause);
2797
+ build(group);
2798
+ group.assertOnly("havingGroup()", "having");
2799
+ if (group._having.length > 0) this._having.push(`(${group._having.join(` ${join} `)})`);
2800
+ return this;
2801
+ }
2802
+ orderBy(field, direction = "ASC", nullOrder) {
2803
+ assertField("orderBy()", field);
2804
+ this._orderBy.push(`${field} ${orderSuffix(direction, nullOrder)}`);
2805
+ return this;
2806
+ }
2807
+ /** Orders by a parent field, e.g. `orderByRelated("Account", "Name", "DESC")`. */
2808
+ orderByRelated(path, field, direction = "ASC", nullOrder) {
2809
+ assertParentPath(path);
2810
+ assertIdentifier("orderByRelated()", field);
2811
+ this._orderBy.push(`${String(path)}.${field} ${orderSuffix(direction, nullOrder)}`);
2812
+ return this;
2813
+ }
2814
+ /** Raw `ORDER BY` item, e.g. `"Account.Name DESC"`. */
2815
+ orderByRaw(expression) {
2816
+ assertNonBlank("orderByRaw", expression);
2817
+ this._orderBy.push(expression);
2818
+ return this;
2819
+ }
2820
+ /** Adds `WITH USER_MODE`, so sharing rules and field-level security of the running user apply. */
2821
+ withUserMode() {
2822
+ this._withUserMode = true;
2823
+ return this;
2824
+ }
2825
+ limit(count) {
2826
+ assertNonNegativeInteger("limit", count);
2827
+ this._limit = count;
2828
+ return this;
2829
+ }
2830
+ offset(count) {
2831
+ assertNonNegativeInteger("offset", count);
2832
+ if (count > MAX_OFFSET) throw new Error(`offset() cannot exceed Salesforce's maximum of ${MAX_OFFSET}.`);
2833
+ this._offset = count;
2834
+ return this;
2835
+ }
2836
+ /** Adds `FOR VIEW`, `FOR REFERENCE` or `FOR UPDATE`. */
2837
+ for(clause) {
2838
+ this._forClause = clause;
2839
+ return this;
2840
+ }
2841
+ /** Returns the SOQL string. Throws when the query is invalid. */
2842
+ build() {
2843
+ this.assertValidQuery();
2844
+ const clauses = [`SELECT ${this._fields.length > 0 ? this._fields.join(", ") : "FIELDS(ALL)"} FROM ${this._fromClause}`];
2845
+ if (this._conditions.length > 0) clauses.push(`WHERE ${this._conditions.join(" AND ")}`);
2846
+ if (this._withUserMode) clauses.push("WITH USER_MODE");
2847
+ if (this._groupBy.length > 0) clauses.push(`GROUP BY ${this._groupBy.join(", ")}`);
2848
+ if (this._having.length > 0) clauses.push(`HAVING ${this._having.join(" AND ")}`);
2849
+ if (this._orderBy.length > 0) clauses.push(`ORDER BY ${this._orderBy.join(", ")}`);
2850
+ if (this._limit !== void 0) clauses.push(`LIMIT ${this._limit}`);
2851
+ if (this._offset !== void 0) clauses.push(`OFFSET ${this._offset}`);
2852
+ if (this._forClause) clauses.push(`FOR ${this._forClause}`);
2853
+ return clauses.join(" ");
2854
+ }
2855
+ toString() {
2856
+ return this.build();
2857
+ }
2858
+ cast() {
2859
+ return this;
2860
+ }
2861
+ /** Adds fields, skipping duplicates. Field names are case-insensitive in SOQL. */
2862
+ addFields(fields) {
2863
+ const existing = new Set(this._fields.map((field) => field.toLowerCase()));
2864
+ for (const field of fields) if (!existing.has(field.toLowerCase())) {
2865
+ existing.add(field.toLowerCase());
2866
+ this._fields.push(field);
2867
+ }
2868
+ }
2869
+ groupConditions(build) {
2870
+ const group = new SoqlQueryBuilder(this._fromClause);
2871
+ build(group);
2872
+ group.assertOnly("whereGroup()/whereNot()", "conditions");
2873
+ return group._conditions;
2874
+ }
2875
+ /** A group callback may only add conditions (or HAVING conditions); anything else would be lost. */
2876
+ assertOnly(method, allowed) {
2877
+ const used = [
2878
+ this._fields.length > 0 ? "select" : "",
2879
+ allowed !== "conditions" && this._conditions.length > 0 ? "where" : "",
2880
+ allowed !== "having" && this._having.length > 0 ? "having" : "",
2881
+ this._groupBy.length > 0 ? "groupBy" : "",
2882
+ this._orderBy.length > 0 ? "orderBy" : "",
2883
+ this._limit !== void 0 || this._offset !== void 0 ? "limit/offset" : "",
2884
+ this._withUserMode || this._forClause ? "withUserMode/for" : ""
2885
+ ].filter((item) => item.length > 0);
2886
+ if (used.length > 0) throw new Error(`${method} callbacks can only add ${allowed === "conditions" ? "where" : "having"} conditions, not ${used.join(", ")}.`);
2887
+ }
2888
+ whereSet(field, operator, values) {
2889
+ const method = operator === "IN" ? "whereIn" : "whereNotIn";
2890
+ assertField(`${method}()`, field);
2891
+ if (values instanceof SoqlQueryBuilder) {
2892
+ if (values._fields.length !== 1) throw new Error(`${method}("${field}", subquery) requires the subquery to select exactly one field, got ${values._fields.length}.`);
2893
+ if (values._usesTypeOf) throw new Error(`${method}("${field}", subquery): TYPEOF is not allowed in a semi-join.`);
2894
+ this._conditions.push(`${field} ${operator} (${values.build()})`);
2895
+ return this;
2896
+ }
2897
+ if (values.length === 0) throw new Error(`${method}("${field}", []) would build an invalid "${operator} ()" SOQL clause.`);
2898
+ this._conditions.push(`${field} ${operator} (${values.map((value) => soqlEscape(value)).join(", ")})`);
2899
+ return this;
2900
+ }
2901
+ whereMultiPicklist(field, operator, values) {
2902
+ assertField(operator === "INCLUDES" ? "whereIncludes()" : "whereExcludes()", field);
2903
+ if (values.length === 0) throw new Error(`${operator.toLowerCase()}("${field}", []) requires at least one value.`);
2904
+ const items = values.map((value) => soqlEscape(typeof value === "string" ? value : value.join(";")));
2905
+ this._conditions.push(`${field} ${operator} (${items.join(", ")})`);
2906
+ return this;
2907
+ }
2908
+ selectAggregate(fn, field, alias) {
2909
+ assertField(`${fn}()`, field);
2910
+ if (alias === void 0 || !ALIAS_PATTERN.test(alias)) throw new Error(`Aggregate alias "${String(alias)}" must be a valid SOQL identifier.`);
2911
+ if (RESERVED_WORDS.has(alias.toLowerCase())) throw new Error(`Aggregate alias "${alias}" is a reserved SOQL keyword.`);
2912
+ this._usesAggregates = true;
2913
+ this._fields.push(`${fn}(${field}) ${alias}`);
2914
+ }
2915
+ assertValidQuery() {
2916
+ if (this._usesTypeOf && this._groupBy.length > 0) throw new Error("TYPEOF can't be combined with groupBy().");
2917
+ if (this._countAll && (this._fields.length > 1 || this._groupBy.length > 0 || this._orderBy.length > 0)) throw new Error("count() must be the only selected item and can't be combined with groupBy() or orderBy().");
2918
+ if (this._fields.length > 0) return;
2919
+ if (this._groupBy.length > 0) throw new Error("groupBy() requires select()/selectRaw(): FIELDS(ALL) can't be used in an aggregate query.");
2920
+ if (this._limit === void 0 || this._limit > FIELDS_ALL_MAX_LIMIT) throw new Error(`Queries using FIELDS(ALL) (select() was never called) require limit() of at most ${FIELDS_ALL_MAX_LIMIT}.`);
2921
+ }
2922
+ };
2923
+ }));
2924
+ //#endregion
2925
+ //#region src/resources/ui-api.ts
2926
+ var UiApi;
2927
+ var init_ui_api = __esmMin((() => {
2928
+ init_connection();
2929
+ UiApi = class {
2930
+ _connection;
2931
+ _cache;
2932
+ constructor(_connection, _cache) {
2933
+ this._connection = _connection;
2934
+ this._cache = _cache;
2935
+ }
2936
+ /** `GET /ui-api/object-info/{name}`: fields, record types and defaults as the UI sees them. Cached. */
2937
+ objectInfo(sobject, options = {}) {
2938
+ const load = (signal) => this._connection.request({
2939
+ path: `/ui-api/object-info/${segment(sobject)}`,
2940
+ signal
2941
+ });
2942
+ return this._cache ? this._cache.getOrLoad(`ui-object-info:${sobject}`, load, options.signal) : load(options.signal);
2943
+ }
2944
+ async picklistValues(sobject, recordTypeId, fieldOrOptions, maybeOptions = {}) {
2945
+ const base = `/ui-api/object-info/${segment(sobject)}/picklist-values/${segment(recordTypeId)}`;
2946
+ if (typeof fieldOrOptions === "string") return this._connection.request({
2947
+ path: `${base}/${segment(fieldOrOptions)}`,
2948
+ signal: maybeOptions.signal
2949
+ });
2950
+ return (await this._connection.request({
2951
+ path: base,
2952
+ signal: fieldOrOptions?.signal
2953
+ })).picklistFieldValues;
2954
+ }
2955
+ /**
2956
+ * `GET /ui-api/records/{id}` with typed field names. At least one of `fields`,
2957
+ * `optionalFields` or `layoutTypes` is required.
2958
+ *
2959
+ * ```ts
2960
+ * const record = await sf.uiApi.record("Account", id, { fields: ["Name", "Industry"] });
2961
+ * record.fields.Name.displayValue;
2962
+ * ```
2963
+ */
2964
+ record(sobject, id, options) {
2965
+ const qualify = (fields) => fields && fields.length > 0 ? fields.map((field) => `${sobject}.${field}`) : void 0;
2966
+ const fields = qualify(options.fields);
2967
+ const optionalFields = qualify(options.optionalFields);
2968
+ if (!fields && !optionalFields && !options.layoutTypes?.length) throw new Error("uiApi.record() requires fields, optionalFields or layoutTypes.");
2969
+ return this._connection.request({
2970
+ path: `/ui-api/records/${segment(id)}`,
2971
+ query: {
2972
+ fields,
2973
+ optionalFields,
2974
+ layoutTypes: options.layoutTypes,
2975
+ modes: options.modes
2976
+ },
2977
+ signal: options.signal
2978
+ });
2979
+ }
2980
+ /** `GET /ui-api/layout/{name}`: the page layout sections, rows and fields. */
2981
+ layout(sobject, options = {}) {
2982
+ const { signal, ...query } = options;
2983
+ return this._connection.request({
2984
+ path: `/ui-api/layout/${segment(sobject)}`,
2985
+ query: { ...query },
2986
+ signal
2987
+ });
2988
+ }
2989
+ };
2990
+ }));
2991
+ //#endregion
2992
+ //#region src/resources/sobject.ts
2993
+ function requireId(id) {
2994
+ if (typeof id !== "string" || id.trim().length === 0) throw new Error("A record id is required.");
2995
+ return id;
2996
+ }
2997
+ /** Formats a date as `yyyy-MM-ddTHH:mm:ss+00:00`, the format the deleted/updated resources expect. */
2998
+ function formatDateTime(date) {
2999
+ return `${date.toISOString().slice(0, 19)}+00:00`;
3000
+ }
3001
+ var SObjectResource;
3002
+ var init_sobject = __esmMin((() => {
3003
+ init_errors();
3004
+ init_connection();
3005
+ init_query_builder();
3006
+ init_quick_actions();
3007
+ init_ui_api();
3008
+ SObjectResource = class {
3009
+ _connection;
3010
+ _queries;
3011
+ name;
3012
+ _basePath;
3013
+ _cache;
3014
+ constructor(_connection, _queries, name, _basePath = "", _cache) {
3015
+ this._connection = _connection;
3016
+ this._queries = _queries;
3017
+ this.name = name;
3018
+ this._basePath = _basePath;
3019
+ this._cache = _cache;
3020
+ if (typeof name !== "string" || !/^[A-Za-z][A-Za-z0-9_]*$/.test(name)) throw new Error(`Invalid sObject name "${String(name)}".`);
3021
+ }
3022
+ get path() {
3023
+ return `${this._basePath}/sobjects/${this.name}`;
3024
+ }
3025
+ /** `GET /sobjects/{name}/describe`: fields, relationships, record types, ... Cached per client. */
3026
+ describe(options = {}) {
3027
+ const load = (signal) => this._connection.request({
3028
+ path: `${this.path}/describe`,
3029
+ signal
3030
+ });
3031
+ return this._cache ? this._cache.getOrLoad(`describe:${this._basePath}:${this.name}`, load, options.signal) : load(options.signal);
3032
+ }
3033
+ /**
3034
+ * Returns the id of a record type by DeveloperName, e.g. `recordTypeId("Complaint")`. Record type
3035
+ * ids differ between orgs, so resolve them at runtime instead of hard-coding them.
3036
+ */
3037
+ async recordTypeId(developerName, options = {}) {
3038
+ const describe = await this.describe(options);
3039
+ const match = describe.recordTypeInfos.find((info) => info.developerName === developerName);
3040
+ if (!match) {
3041
+ const available = describe.recordTypeInfos.map((info) => info.developerName).join(", ");
3042
+ throw new Error(`${this.name} has no record type "${developerName}". Available: ${available || "none"}.`);
3043
+ }
3044
+ return match.recordTypeId;
3045
+ }
3046
+ /**
3047
+ * Active picklist values of a field. With `recordType`, only the values available for that
3048
+ * record type are returned (via the UI API).
3049
+ */
3050
+ async picklistValues(field, options = {}) {
3051
+ if (options.recordType !== void 0) {
3052
+ const recordTypeId = await this.recordTypeId(options.recordType, options);
3053
+ const values = await new UiApi(this._connection).picklistValues(this.name, recordTypeId, field, options);
3054
+ return values.values.map((entry) => ({
3055
+ value: entry.value,
3056
+ label: entry.label,
3057
+ isDefault: values.defaultValue?.value === entry.value
3058
+ }));
3059
+ }
3060
+ const describeField = (await this.describe(options)).fields.find((item) => item.name === field);
3061
+ if (!describeField) throw new Error(`${this.name} has no field "${field}".`);
3062
+ return describeField.picklistValues.filter((entry) => entry.active).map((entry) => ({
3063
+ value: entry.value,
3064
+ label: entry.label ?? entry.value,
3065
+ isDefault: entry.defaultValue
3066
+ }));
3067
+ }
3068
+ /** Quick actions of this sObject. */
3069
+ get quickActions() {
3070
+ return new QuickActionsApi(this._connection, `${this.path}/quickActions`);
3071
+ }
3072
+ /** `GET /sobjects/{name}`: basic metadata and recently viewed records. */
3073
+ basicInfo(options = {}) {
3074
+ return this._connection.request({
3075
+ path: this.path,
3076
+ signal: options.signal
3077
+ });
3078
+ }
3079
+ /** Creates a record and returns its id. Throws `SalesforceSaveError` if Salesforce reports `success: false`. */
3080
+ async create(record, options = {}) {
3081
+ const result = await this._connection.request({
3082
+ method: "POST",
3083
+ path: this.path,
3084
+ body: record,
3085
+ signal: options.signal
3086
+ });
3087
+ if (!result.success || !result.id) throw new SalesforceSaveError(`Creating ${this.name} failed`, [result], result.errors);
3088
+ return result.id;
3089
+ }
3090
+ get(id, fieldsOrOptions, maybeOptions = {}) {
3091
+ const fields = Array.isArray(fieldsOrOptions) ? fieldsOrOptions : void 0;
3092
+ const options = Array.isArray(fieldsOrOptions) ? maybeOptions : fieldsOrOptions ?? {};
3093
+ return this._connection.request({
3094
+ path: `${this.path}/${segment(requireId(id))}`,
3095
+ query: { fields: fields && fields.length > 0 ? fields : void 0 },
3096
+ signal: options.signal
3097
+ });
3098
+ }
3099
+ /** Updates fields of a record. */
3100
+ async update(id, record, options = {}) {
3101
+ await this._connection.request({
3102
+ method: "PATCH",
3103
+ path: `${this.path}/${segment(requireId(id))}`,
3104
+ body: record,
3105
+ signal: options.signal
3106
+ });
3107
+ }
3108
+ /** Deletes a record. */
3109
+ async delete(id, options = {}) {
3110
+ await this._connection.request({
3111
+ method: "DELETE",
3112
+ path: `${this.path}/${segment(requireId(id))}`,
3113
+ signal: options.signal
3114
+ });
3115
+ }
3116
+ /**
3117
+ * Inserts or updates a record by external id (`PATCH /sobjects/{name}/{field}/{value}`).
3118
+ * Salesforce answers 300 (a `SalesforceError`) when the value matches more than one record.
3119
+ */
3120
+ async upsert(externalIdField, externalIdValue, record, options = {}) {
3121
+ const response = await this._connection.send({
3122
+ method: "PATCH",
3123
+ path: `${this.path}/${segment(externalIdField)}/${segment(externalIdText(externalIdValue))}`,
3124
+ query: { updateOnly: options.updateOnly ? true : void 0 },
3125
+ body: record,
3126
+ signal: options.signal
3127
+ });
3128
+ const result = response.data;
3129
+ if (result && (!result.success || !result.id)) throw new SalesforceSaveError(`Upserting ${this.name} failed`, [result], result.errors);
3130
+ return {
3131
+ id: result?.id ?? "",
3132
+ created: result?.created ?? response.status === 201
3133
+ };
3134
+ }
3135
+ /** Retrieves a record by external id. */
3136
+ getByExternalId(externalIdField, externalIdValue, fields, options = {}) {
3137
+ return this._connection.request({
3138
+ path: `${this.path}/${segment(externalIdField)}/${segment(externalIdText(externalIdValue))}`,
3139
+ query: { fields: fields && fields.length > 0 ? fields : void 0 },
3140
+ signal: options.signal
3141
+ });
3142
+ }
3143
+ /** Ids of records deleted between `start` and `end` (UTC, at most 15 days back). */
3144
+ getDeleted(start, end, options = {}) {
3145
+ return this._connection.request({
3146
+ path: `${this.path}/deleted/`,
3147
+ query: {
3148
+ start: formatDateTime(start),
3149
+ end: formatDateTime(end)
3150
+ },
3151
+ signal: options.signal
3152
+ });
3153
+ }
3154
+ /** Ids of records updated between `start` and `end` (UTC, at most 30 days back). */
3155
+ getUpdated(start, end, options = {}) {
3156
+ return this._connection.request({
3157
+ path: `${this.path}/updated/`,
3158
+ query: {
3159
+ start: formatDateTime(start),
3160
+ end: formatDateTime(end)
3161
+ },
3162
+ signal: options.signal
3163
+ });
3164
+ }
3165
+ /** Downloads a blob field, e.g. `sobject("ContentVersion").getBlob(id, "VersionData")`. */
3166
+ getBlob(id, field, options = {}) {
3167
+ return this._connection.request({
3168
+ path: `${this.path}/${segment(requireId(id))}/${segment(field)}`,
3169
+ responseType: "binary",
3170
+ signal: options.signal
3171
+ });
3172
+ }
3173
+ /** Starts a typed query on this sObject. */
3174
+ soql() {
3175
+ return SoqlQueryBuilder.from(this.name);
3176
+ }
3177
+ /** Runs a query built on this sObject and returns the first page. */
3178
+ query(build, options) {
3179
+ return this._queries.page(build(this.soql()).build(), options);
3180
+ }
3181
+ /** Runs a query built on this sObject and returns the records of all pages. */
3182
+ collect(build, options) {
3183
+ return this._queries.collect(build(this.soql()).build(), options);
3184
+ }
3185
+ /** Runs a query built on this sObject and yields records of all pages lazily. */
3186
+ iterate(build, options) {
3187
+ return this._queries.iterate(build(this.soql()).build(), options);
3188
+ }
3189
+ };
3190
+ }));
3191
+ //#endregion
3192
+ //#region src/resources/debug-logs.ts
3193
+ /** The trace flag was removed meanwhile (by a person or another tool): nothing left to restore. */
3194
+ async function ignoreMissing(operation) {
3195
+ try {
3196
+ await operation;
3197
+ } catch (error) {
3198
+ if (!hasErrorCode(error, "NOT_FOUND") && !hasErrorCode(error, "ENTITY_IS_DELETED")) throw error;
3199
+ }
3200
+ }
3201
+ function toEntry(row) {
3202
+ return {
3203
+ id: row.Id,
3204
+ operation: row.Operation,
3205
+ request: row.Request,
3206
+ status: row.Status,
3207
+ startTime: row.StartTime,
3208
+ durationMilliseconds: row.DurationMilliseconds,
3209
+ logLength: row.LogLength
3210
+ };
3211
+ }
3212
+ var DEFAULT_DEBUG_LEVELS, DEBUG_LEVEL_NAME, DebugLogCaptureError, LOG_FIELDS, DebugLogsApi;
3213
+ var init_debug_logs = __esmMin((() => {
3214
+ init_errors();
3215
+ init_connection();
3216
+ init_escape();
3217
+ DEFAULT_DEBUG_LEVELS = {
3218
+ ApexCode: "FINEST",
3219
+ ApexProfiling: "INFO",
3220
+ Callout: "INFO",
3221
+ Database: "INFO",
3222
+ System: "DEBUG",
3223
+ Validation: "INFO",
3224
+ Visualforce: "INFO",
3225
+ Workflow: "INFO"
3226
+ };
3227
+ DEBUG_LEVEL_NAME = "sobjectly";
3228
+ DebugLogCaptureError = class extends Error {
3229
+ logs;
3230
+ cause;
3231
+ cleanupError;
3232
+ name = "DebugLogCaptureError";
3233
+ constructor(logs, cause, cleanupError) {
3234
+ super(`${cause instanceof Error ? cause.message : String(cause)} (${logs.length} Apex log(s) captured)`);
3235
+ this.logs = logs;
3236
+ this.cause = cause;
3237
+ this.cleanupError = cleanupError;
3238
+ }
3239
+ };
3240
+ LOG_FIELDS = "Id, Operation, Request, Status, StartTime, DurationMilliseconds, LogLength";
3241
+ DebugLogsApi = class {
3242
+ _tooling;
3243
+ /** The latest capture per user: a user has one trace flag, so captures for a user take turns. */
3244
+ _captures = /* @__PURE__ */ new Map();
3245
+ constructor(_tooling) {
3246
+ this._tooling = _tooling;
3247
+ }
3248
+ /** The id of the authenticated user (from `/services/oauth2/userinfo`). */
3249
+ async currentUserId(options = {}) {
3250
+ return (await this._tooling.connection.request({
3251
+ path: "/services/oauth2/userinfo",
3252
+ signal: options.signal
3253
+ })).user_id;
3254
+ }
3255
+ /** The most recent Apex logs, newest first. */
3256
+ async list(options = {}) {
3257
+ const where = options.userId ? ` WHERE LogUserId = ${soqlEscape(options.userId)}` : "";
3258
+ return (await this._tooling.collect(`SELECT ${LOG_FIELDS} FROM ApexLog${where} ORDER BY StartTime DESC LIMIT ${options.limit ?? 50}`, { signal: options.signal })).map(toEntry);
3259
+ }
3260
+ /** The text of one Apex log. */
3261
+ body(logId, options = {}) {
3262
+ return this._tooling.connection.request({
3263
+ path: `/tooling/sobjects/ApexLog/${segment(logId)}/Body/`,
3264
+ responseType: "text",
3265
+ signal: options.signal
3266
+ });
3267
+ }
3268
+ /**
3269
+ * Runs `work` with debug logging enabled for the user, then returns its result together with
3270
+ * the Apex logs it produced (e.g. from triggers, flows or Apex REST calls):
3271
+ *
3272
+ * ```ts
3273
+ * const { result, logs } = await sf.tooling.debugLogs.capture(() => sf.sobject("Case").create({ Subject: "x" }));
3274
+ * console.log(logs.map((log) => log.body).join("\n"));
3275
+ * ```
3276
+ *
3277
+ * Salesforce allows one trace flag per user; an existing flag is reused and restored afterwards,
3278
+ * and captures for the same user through this client run one after another. Only work that runs
3279
+ * while the callback does is traced: async Apex it enqueues (queueable, batch, future) usually
3280
+ * runs later. At most the 200 newest logs of the user are collected.
3281
+ */
3282
+ async capture(work, options = {}) {
3283
+ const userId = options.userId ?? await this.currentUserId({ signal: options.signal });
3284
+ const current = (this._captures.get(userId) ?? Promise.resolve()).then(() => this.captureFor(userId, work, options));
3285
+ const settled = current.then(() => void 0, () => void 0);
3286
+ this._captures.set(userId, settled);
3287
+ try {
3288
+ return await current;
3289
+ } finally {
3290
+ if (this._captures.get(userId) === settled) this._captures.delete(userId);
3291
+ }
3292
+ }
3293
+ async captureFor(userId, work, options) {
3294
+ const { signal } = options;
3295
+ signal?.throwIfAborted();
3296
+ const before = new Set((await this.list({
3297
+ userId,
3298
+ limit: 200,
3299
+ signal
3300
+ })).map((log) => log.id));
3301
+ const debugLevelId = await this.ensureDebugLevel({
3302
+ ...DEFAULT_DEBUG_LEVELS,
3303
+ ...options.levels
3304
+ }, signal);
3305
+ const restore = await this.enableTraceFlag(userId, debugLevelId, options.expirationMinutes ?? 30, signal);
3306
+ let result;
3307
+ let failure;
3308
+ let cleanupError;
3309
+ try {
3310
+ result = await work();
3311
+ } catch (error) {
3312
+ failure = { error };
3313
+ }
3314
+ try {
3315
+ await restore();
3316
+ } catch (error) {
3317
+ cleanupError = error;
3318
+ }
3319
+ await sleep(options.settleMs ?? 1e3, signal);
3320
+ const logs = (await this.list({
3321
+ userId,
3322
+ limit: 200,
3323
+ signal
3324
+ })).filter((log) => !before.has(log.id)).reverse();
3325
+ if (options.includeBodies !== false) for (const log of logs) log.body = await this.body(log.id, { signal });
3326
+ if (failure) throw new DebugLogCaptureError(logs, failure.error, cleanupError);
3327
+ return cleanupError === void 0 ? {
3328
+ result,
3329
+ logs
3330
+ } : {
3331
+ result,
3332
+ logs,
3333
+ cleanupError
3334
+ };
3335
+ }
3336
+ async ensureDebugLevel(levels, signal) {
3337
+ const [existing] = await this._tooling.collect(`SELECT Id FROM DebugLevel WHERE DeveloperName = ${soqlEscape(DEBUG_LEVEL_NAME)}`, { signal });
3338
+ const levelSObject = this._tooling.sobject("DebugLevel");
3339
+ if (existing) {
3340
+ await levelSObject.update(existing.Id, { ...levels }, { signal });
3341
+ return existing.Id;
3342
+ }
3343
+ return levelSObject.create({
3344
+ DeveloperName: DEBUG_LEVEL_NAME,
3345
+ MasterLabel: DEBUG_LEVEL_NAME,
3346
+ ...levels
3347
+ }, { signal });
3348
+ }
3349
+ /** Enables tracing and returns a function that restores the previous state. */
3350
+ async enableTraceFlag(userId, debugLevelId, minutes, signal) {
3351
+ const expiration = new Date(Date.now() + Math.min(Math.max(minutes, 1), 1439) * 6e4).toISOString();
3352
+ const flags = this._tooling.sobject("TraceFlag");
3353
+ const [existing] = await this._tooling.collect(`SELECT Id, DebugLevelId, StartDate, ExpirationDate FROM TraceFlag WHERE TracedEntityId = ${soqlEscape(userId)} AND LogType = 'USER_DEBUG'`, { signal });
3354
+ if (!existing) {
3355
+ const id = await flags.create({
3356
+ TracedEntityId: userId,
3357
+ LogType: "USER_DEBUG",
3358
+ DebugLevelId: debugLevelId,
3359
+ StartDate: null,
3360
+ ExpirationDate: expiration
3361
+ }, { signal });
3362
+ return () => ignoreMissing(flags.delete(id));
3363
+ }
3364
+ await flags.update(existing.Id, {
3365
+ DebugLevelId: debugLevelId,
3366
+ StartDate: null,
3367
+ ExpirationDate: expiration
3368
+ }, { signal });
3369
+ return async () => {
3370
+ if (Date.parse(existing.ExpirationDate) > Date.now()) await ignoreMissing(flags.update(existing.Id, {
3371
+ DebugLevelId: existing.DebugLevelId,
3372
+ StartDate: existing.StartDate,
3373
+ ExpirationDate: existing.ExpirationDate
3374
+ }));
3375
+ else await ignoreMissing(flags.delete(existing.Id));
3376
+ };
3377
+ }
3378
+ };
3379
+ }));
3380
+ //#endregion
3381
+ //#region src/resources/tooling.ts
3382
+ var ApexExecutionError, ToolingApi;
3383
+ var init_tooling = __esmMin((() => {
3384
+ init_debug_logs();
3385
+ init_query();
3386
+ init_sobject();
3387
+ ApexExecutionError = class extends Error {
3388
+ result;
3389
+ logs;
3390
+ name = "ApexExecutionError";
3391
+ constructor(result, logs = []) {
3392
+ super(result.compiled ? `Anonymous Apex threw: ${result.exceptionMessage ?? "unknown error"}` : `Anonymous Apex failed to compile at line ${result.line}, column ${result.column}: ${result.compileProblem ?? "unknown problem"}`);
3393
+ this.result = result;
3394
+ this.logs = logs;
3395
+ }
3396
+ };
3397
+ ToolingApi = class {
3398
+ _connection;
3399
+ _queries;
3400
+ /** Apex debug logs: capture the logs of a block of work, list and read logs. */
3401
+ debugLogs;
3402
+ constructor(_connection) {
3403
+ this._connection = _connection;
3404
+ this._queries = new QueryApi(_connection, "/tooling");
3405
+ this.debugLogs = new DebugLogsApi(this);
3406
+ }
3407
+ /** The connection this API uses. */
3408
+ get connection() {
3409
+ return this._connection;
3410
+ }
3411
+ /** Runs a Tooling SOQL query and returns the first page. */
3412
+ query(soql, options) {
3413
+ return this._queries.page(soql, options);
3414
+ }
3415
+ queryMore(cursor, options) {
3416
+ return this._queries.more(cursor, options);
3417
+ }
3418
+ iterate(soql, options) {
3419
+ return this._queries.iterate(soql, options);
3420
+ }
3421
+ collect(soql, options) {
3422
+ return this._queries.collect(soql, options);
3423
+ }
3424
+ /** CRUD and describe for a Tooling sObject, e.g. `tooling.sobject("TraceFlag")`. */
3425
+ sobject(name) {
3426
+ return new SObjectResource(this._connection, this._queries, name, "/tooling");
3427
+ }
3428
+ describeGlobal(options = {}) {
3429
+ return this._connection.request({
3430
+ path: "/tooling/sobjects",
3431
+ signal: options.signal
3432
+ });
3433
+ }
3434
+ async executeAnonymous(apex, options = {}) {
3435
+ const encodedLength = encodeURIComponent(apex).length;
3436
+ if (encodedLength > 12e3) throw new Error(`Anonymous Apex is too long: ${encodedLength} characters once URL-encoded, the limit is about ${MAX_QUERY_URL_LENGTH}. Salesforce only accepts it in the URL; move the logic into an Apex class.`);
3437
+ if (options.captureLog) {
3438
+ const { result, logs } = await this.debugLogs.capture(() => this.executeAnonymous(apex, {
3439
+ signal: options.signal,
3440
+ throwOnError: false
3441
+ }), {
3442
+ ...options.logOptions,
3443
+ signal: options.signal
3444
+ });
3445
+ if (options.throwOnError !== false && (!result.compiled || !result.success)) throw new ApexExecutionError(result, logs);
3446
+ return {
3447
+ ...result,
3448
+ logs
3449
+ };
3450
+ }
3451
+ const result = await this._connection.request({
3452
+ path: "/tooling/executeAnonymous/",
3453
+ query: { anonymousBody: apex },
3454
+ signal: options.signal
3455
+ });
3456
+ if (options.throwOnError !== false && (!result.compiled || !result.success)) throw new ApexExecutionError(result);
3457
+ return result;
3458
+ }
3459
+ /** Runs Apex tests synchronously (a single class) and returns the results. */
3460
+ runTestsSynchronous(request, options = {}) {
3461
+ return this._connection.request({
3462
+ method: "POST",
3463
+ path: "/tooling/runTestsSynchronous/",
3464
+ body: request,
3465
+ signal: options.signal,
3466
+ timeoutMs: 0
3467
+ });
3468
+ }
3469
+ /** Enqueues Apex tests and returns the AsyncApexJob id. */
3470
+ runTestsAsynchronous(request, options = {}) {
3471
+ return this._connection.request({
3472
+ method: "POST",
3473
+ path: "/tooling/runTestsAsynchronous/",
3474
+ body: request,
3475
+ signal: options.signal
3476
+ });
3477
+ }
3478
+ /** Any other Tooling resource; `path` is relative to `/tooling`. */
3479
+ request(request) {
3480
+ const path = request.path.startsWith("/") ? request.path : `/${request.path}`;
3481
+ return this._connection.request({
3482
+ ...request,
3483
+ path: `/tooling${path}`
3484
+ });
3485
+ }
3486
+ };
3487
+ }));
3488
+ //#endregion
3489
+ //#region src/client.ts
3490
+ function toSoql(query) {
3491
+ return typeof query === "string" ? query : query.build();
3492
+ }
3493
+ var SalesforceClient;
3494
+ var init_client = __esmMin((() => {
3495
+ init_cache();
3496
+ init_connection();
3497
+ init_fetch_transport();
3498
+ init_actions();
3499
+ init_approvals();
3500
+ init_bulk();
3501
+ init_collections();
3502
+ init_composite();
3503
+ init_events();
3504
+ init_files();
3505
+ init_query();
3506
+ init_quick_actions();
3507
+ init_reports();
3508
+ init_search();
3509
+ init_sobject();
3510
+ init_tooling();
3511
+ init_ui_api();
3512
+ init_query_builder();
3513
+ SalesforceClient = class {
3514
+ /** The low-level executor, for advanced use. */
3515
+ connection;
3516
+ /** SOSL, parameterized search and search suggestions. */
3517
+ search;
3518
+ /** Composite, composite batch, sObject tree and composite graph. */
3519
+ composite;
3520
+ /** sObject collections: up to 200 records per call. */
3521
+ collections;
3522
+ /** Invocable actions, including Flows and Apex `@InvocableMethod`s. */
3523
+ actions;
3524
+ /** The Tooling API. */
3525
+ tooling;
3526
+ /** Bulk API 2.0 ingest and query jobs. */
3527
+ bulk;
3528
+ /** Publish platform events. */
3529
+ events;
3530
+ /** Approval processes: submit, approve, reject. */
3531
+ approvals;
3532
+ /** Global quick actions (per-sObject actions are on `sobject(name).quickActions`). */
3533
+ quickActions;
3534
+ /** User Interface API: object info, picklist values per record type, records, layouts. */
3535
+ uiApi;
3536
+ /** Salesforce Files: upload, download, new versions, sharing. */
3537
+ files;
3538
+ /** Reports: run synchronously or asynchronously and flatten the rows. */
3539
+ reports;
3540
+ _queries;
3541
+ _cache;
3542
+ constructor(options) {
3543
+ if (!options?.auth) throw new TypeError("SalesforceClient requires an auth provider.");
3544
+ this.connection = new SalesforceConnection({
3545
+ auth: options.auth,
3546
+ transport: options.transport ?? fetchTransport(),
3547
+ apiVersion: options.apiVersion,
3548
+ timeoutMs: options.timeoutMs,
3549
+ retry: options.retry,
3550
+ allowedOrigins: options.allowedOrigins,
3551
+ headers: options.headers,
3552
+ hooks: options.hooks
3553
+ });
3554
+ this._queries = new QueryApi(this.connection);
3555
+ this.search = new SearchApi(this.connection);
3556
+ this.composite = new CompositeApi(this.connection);
3557
+ this.collections = new CollectionsApi(this.connection);
3558
+ this.actions = new ActionsApi(this.connection);
3559
+ this.tooling = new ToolingApi(this.connection);
3560
+ this.bulk = new BulkApi(this.connection);
3561
+ this.events = new EventsApi(this.connection);
3562
+ this._cache = new MetadataCache(options.cache !== false);
3563
+ this.approvals = new ApprovalsApi(this.connection, this._queries);
3564
+ this.quickActions = new QuickActionsApi(this.connection, "/quickActions");
3565
+ this.uiApi = new UiApi(this.connection, this._cache);
3566
+ this.files = new FilesApi(this.connection);
3567
+ this.reports = new ReportsApi(this.connection);
3568
+ }
3569
+ /** Drops cached metadata (describe results, object info, record type ids). */
3570
+ clearCache() {
3571
+ this._cache.clear();
3572
+ }
3573
+ get apiVersion() {
3574
+ return this.connection.apiVersion;
3575
+ }
3576
+ /** API usage as reported by the last response (`Sforce-Limit-Info`). */
3577
+ get apiUsage() {
3578
+ return this.connection.apiUsage;
3579
+ }
3580
+ /** The instance URL of the current access token (authenticates if needed). */
3581
+ instanceUrl(options = {}) {
3582
+ return this.connection.instanceUrl(options.signal);
3583
+ }
3584
+ /**
3585
+ * Calls any REST resource. `path` is relative to `/services/data/{apiVersion}` unless it
3586
+ * starts with `/services/` or is an absolute URL on the instance.
3587
+ *
3588
+ * ```ts
3589
+ * const limits = await sf.request<OrgLimits>({ path: "/limits" });
3590
+ * ```
3591
+ */
3592
+ request(request) {
3593
+ return this.connection.request(request);
3594
+ }
3595
+ /** Like `request`, but also returns the status and response headers. */
3596
+ requestWithResponse(request) {
3597
+ return this.connection.send(request);
3598
+ }
3599
+ /** Calls an Apex REST endpoint (`/services/apexrest/...`). */
3600
+ apexRest(request) {
3601
+ const path = request.path.replace(/^\/+/, "").replace(/^services\/apexrest\//, "");
3602
+ return this.connection.request({
3603
+ ...request,
3604
+ path: `/services/apexrest/${path}`
3605
+ });
3606
+ }
3607
+ /** Typed operations on one sObject. */
3608
+ sobject(name) {
3609
+ return new SObjectResource(this.connection, this._queries, name, "", this._cache);
3610
+ }
3611
+ /** Starts a typed SOQL query. Run it with `query`, `queryAll`, `iterate` or `collect`. */
3612
+ soql(sobjectName) {
3613
+ return SoqlQueryBuilder.from(sobjectName);
3614
+ }
3615
+ query(query, options) {
3616
+ return this._queries.page(toSoql(query), options);
3617
+ }
3618
+ /** Fetches the next page of a query. */
3619
+ queryMore(cursor, options) {
3620
+ return this._queries.more(cursor, options);
3621
+ }
3622
+ iterate(query, options) {
3623
+ return this._queries.iterate(toSoql(query), options);
3624
+ }
3625
+ collect(query, options) {
3626
+ return this._queries.collect(toSoql(query), options);
3627
+ }
3628
+ /** `GET /services/data`: every API version the org supports. Does not count against limits. */
3629
+ versions(options = {}) {
3630
+ return this.connection.request({
3631
+ path: "/services/data/",
3632
+ signal: options.signal
3633
+ });
3634
+ }
3635
+ /** `GET /services/data/{version}`: the resources available in this version. */
3636
+ resources(options = {}) {
3637
+ return this.connection.request({
3638
+ path: "/",
3639
+ signal: options.signal
3640
+ });
3641
+ }
3642
+ /** `GET /limits`: org limits such as `DailyApiRequests`. */
3643
+ limits(options = {}) {
3644
+ return this.connection.request({
3645
+ path: "/limits",
3646
+ signal: options.signal
3647
+ });
3648
+ }
3649
+ /** `GET /limits/recordCount`: approximate record counts per sObject. */
3650
+ recordCount(sobjects, options = {}) {
3651
+ return this.connection.request({
3652
+ path: "/limits/recordCount",
3653
+ query: { sObjects: sobjects },
3654
+ signal: options.signal
3655
+ });
3656
+ }
3657
+ /** `GET /sobjects`: every sObject visible to the user. */
3658
+ describeGlobal(options = {}) {
3659
+ return this._cache.getOrLoad("describe-global", (signal) => this.connection.request({
3660
+ path: "/sobjects",
3661
+ signal
3662
+ }), options.signal);
3663
+ }
3664
+ /**
3665
+ * Asks Salesforce how it would run a query (`GET /query?explain=`, beta), e.g. to check a
3666
+ * filter is selective. Plans are sorted best first; `relativeCost` above 1 means not selective.
3667
+ */
3668
+ async explain(query, options = {}) {
3669
+ return (await this.connection.request({
3670
+ path: "/query",
3671
+ query: { explain: toSoql(query) },
3672
+ signal: options.signal
3673
+ })).plans;
3674
+ }
3675
+ };
3676
+ }));
3677
+ //#endregion
3678
+ //#region src/auth/caching-provider.ts
3679
+ function isExpired(token, fetchedAt) {
3680
+ if (token.expiresAt === void 0) return false;
3681
+ const margin = Math.min(EXPIRY_MARGIN_MS, Math.max(0, (token.expiresAt - fetchedAt) / 2));
3682
+ return token.expiresAt - margin <= Date.now();
3683
+ }
3684
+ var EXPIRY_MARGIN_MS, CachingAuthProvider;
3685
+ var init_caching_provider = __esmMin((() => {
3686
+ init_shared();
3687
+ EXPIRY_MARGIN_MS = 6e4;
3688
+ CachingAuthProvider = class {
3689
+ _fetchToken;
3690
+ _pending;
3691
+ _token;
3692
+ constructor(_fetchToken) {
3693
+ this._fetchToken = _fetchToken;
3694
+ }
3695
+ getToken(context) {
3696
+ if (this._token && !isExpired(this._token.value, this._token.fetchedAt)) return Promise.resolve(this._token.value);
3697
+ if (!this._pending || this._pending.abandoned) {
3698
+ const pending = share((signal) => this._fetchToken({
3699
+ transport: context.transport,
3700
+ signal
3701
+ }).then((token) => {
3702
+ this._token = {
3703
+ value: token,
3704
+ fetchedAt: Date.now()
3705
+ };
3706
+ return token;
3707
+ }));
3708
+ this._pending = pending;
3709
+ pending.promise.finally(() => {
3710
+ if (this._pending === pending) this._pending = void 0;
3711
+ }).catch(() => void 0);
3712
+ }
3713
+ return this._pending.join(context.signal);
3714
+ }
3715
+ invalidate(token) {
3716
+ if (this._token?.value.accessToken === token.accessToken) this._token = void 0;
3717
+ }
3718
+ };
3719
+ }));
3720
+ //#endregion
3721
+ //#region src/auth/oauth.ts
3722
+ /**
3723
+ * POSTs a form to `{loginUrl}/services/oauth2/token` and turns the response into an
3724
+ * `AccessToken`. Errors never include the submitted parameters.
3725
+ */
3726
+ async function requestOAuthToken(transport, loginUrl, params, signal) {
3727
+ const url = new URL("/services/oauth2/token", normalizeLoginUrl(loginUrl));
3728
+ const response = await transport.send({
3729
+ method: "POST",
3730
+ url,
3731
+ headers: new Headers({
3732
+ "Content-Type": "application/x-www-form-urlencoded",
3733
+ Accept: "application/json"
3734
+ }),
3735
+ body: new URLSearchParams(params).toString(),
3736
+ signal
3737
+ });
3738
+ const json = parseJson(decoder.decode(response.body));
3739
+ if (response.status < 200 || response.status >= 300) {
3740
+ const error = typeof json?.error === "string" ? json.error : void 0;
3741
+ const errorDescription = typeof json?.error_description === "string" ? json.error_description : void 0;
3742
+ throw new SalesforceAuthError(`Salesforce token request to ${url.origin} failed with status ${response.status}${error ? ` - ${error}${errorDescription ? `: ${errorDescription}` : ""}` : ""}`, {
3743
+ status: response.status,
3744
+ error,
3745
+ errorDescription
3746
+ });
3747
+ }
3748
+ if (typeof json?.access_token !== "string" || typeof json.instance_url !== "string") throw new SalesforceAuthError(`Salesforce token response from ${url.origin} has no access_token/instance_url.`, { status: response.status });
3749
+ const tokenResponse = json;
3750
+ return {
3751
+ token: {
3752
+ accessToken: tokenResponse.access_token,
3753
+ instanceUrl: tokenResponse.instance_url
3754
+ },
3755
+ response: tokenResponse
3756
+ };
3757
+ }
3758
+ /** Accepts `https://x.my.salesforce.com`, with or without a trailing slash or path. */
3759
+ function normalizeLoginUrl(loginUrl) {
3760
+ let url;
3761
+ try {
3762
+ url = new URL(loginUrl);
3763
+ } catch {
3764
+ throw new TypeError(`Invalid Salesforce login URL: "${loginUrl}".`);
3765
+ }
3766
+ if (url.protocol !== "https:" && url.hostname !== "localhost" && url.hostname !== "127.0.0.1") throw new TypeError(`Salesforce login URL must use https: "${url.origin}".`);
3767
+ return url.origin;
3768
+ }
3769
+ function parseJson(text) {
3770
+ try {
3771
+ const value = JSON.parse(text);
3772
+ return typeof value === "object" && value !== null ? value : void 0;
3773
+ } catch {
3774
+ return;
3775
+ }
3776
+ }
3777
+ var decoder;
3778
+ var init_oauth = __esmMin((() => {
3779
+ init_errors();
3780
+ decoder = new TextDecoder();
3781
+ }));
3782
+ //#endregion
3783
+ //#region src/auth/providers.ts
3784
+ /** Uses a fixed access token, e.g. one issued by the Salesforce CLI (`sf org display`). */
3785
+ function accessToken(token) {
3786
+ if (!token.accessToken || !token.instanceUrl) throw new TypeError("accessToken() requires both accessToken and instanceUrl.");
3787
+ return { getToken: () => Promise.resolve(token) };
3788
+ }
3789
+ /**
3790
+ * Wraps your own token source. The result is cached until Salesforce answers 401, after which
3791
+ * `fetchToken` is called again.
3792
+ */
3793
+ function tokenProvider(fetchToken) {
3794
+ return new CachingAuthProvider(fetchToken);
3795
+ }
3796
+ /**
3797
+ * OAuth 2.0 client credentials flow (server-to-server, runs as the app's "Run As" user).
3798
+ * The token is cached in memory and refreshed once when Salesforce answers 401.
3799
+ */
3800
+ function clientCredentials(options) {
3801
+ requireOptions("clientCredentials", options, [
3802
+ "loginUrl",
3803
+ "clientId",
3804
+ "clientSecret"
3805
+ ]);
3806
+ return new CachingAuthProvider(async (context) => {
3807
+ const params = {
3808
+ grant_type: "client_credentials",
3809
+ client_id: options.clientId,
3810
+ client_secret: options.clientSecret
3811
+ };
3812
+ if (options.scope) params.scope = options.scope;
3813
+ const { token } = await requestOAuthToken(context.transport, options.loginUrl, params, context.signal);
3814
+ return token;
3815
+ });
3816
+ }
3817
+ /** OAuth 2.0 JWT bearer flow. The assertion is signed locally with RS256; no extra dependencies. */
3818
+ function jwtBearer(options) {
3819
+ requireOptions("jwtBearer", options, [
3820
+ "loginUrl",
3821
+ "clientId",
3822
+ "username",
3823
+ "privateKey"
3824
+ ]);
3825
+ return new CachingAuthProvider(async (context) => {
3826
+ const assertion = createJwtAssertion(options);
3827
+ const { token } = await requestOAuthToken(context.transport, options.loginUrl, {
3828
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
3829
+ assertion
3830
+ }, context.signal);
3831
+ return token;
3832
+ });
3833
+ }
3834
+ /** Exchanges a refresh token for access tokens. */
3835
+ function refreshToken(options) {
3836
+ requireOptions("refreshToken", options, [
3837
+ "loginUrl",
3838
+ "clientId",
3839
+ "refreshToken"
3840
+ ]);
3841
+ let currentRefreshToken = options.refreshToken;
3842
+ return new CachingAuthProvider(async (context) => {
3843
+ const params = {
3844
+ grant_type: "refresh_token",
3845
+ client_id: options.clientId,
3846
+ refresh_token: currentRefreshToken
3847
+ };
3848
+ if (options.clientSecret) params.client_secret = options.clientSecret;
3849
+ const { token, response } = await requestOAuthToken(context.transport, options.loginUrl, params, context.signal);
3850
+ if (response.refresh_token && response.refresh_token !== currentRefreshToken) {
3851
+ currentRefreshToken = response.refresh_token;
3852
+ try {
3853
+ options.onRefreshTokenRotated?.(response.refresh_token);
3854
+ } catch {}
3855
+ }
3856
+ return token;
3857
+ });
3858
+ }
3859
+ /** Builds a signed RS256 JWT assertion for the JWT bearer flow. Exported for testing. */
3860
+ function createJwtAssertion(options, now = Date.now()) {
3861
+ const expiresIn = Math.min(Math.max(Math.floor(options.expiresInSeconds ?? 180), 1), 180);
3862
+ const header = {
3863
+ alg: "RS256",
3864
+ typ: "JWT"
3865
+ };
3866
+ const claims = {
3867
+ iss: options.clientId,
3868
+ sub: options.username,
3869
+ aud: options.audience ?? defaultAudience(options.loginUrl),
3870
+ exp: Math.floor(now / 1e3) + expiresIn
3871
+ };
3872
+ const unsigned = `${base64Url(JSON.stringify(header))}.${base64Url(JSON.stringify(claims))}`;
3873
+ return `${unsigned}.${base64Url(createSign("RSA-SHA256").update(unsigned).sign(options.privateKey))}`;
3874
+ }
3875
+ function defaultAudience(loginUrl) {
3876
+ const host = new URL(loginUrl).hostname;
3877
+ return host === "test.salesforce.com" || host.includes(".sandbox.") || host.includes(".scratch.") ? "https://test.salesforce.com" : "https://login.salesforce.com";
3878
+ }
3879
+ function base64Url(value) {
3880
+ return Buffer.from(value).toString("base64url");
3881
+ }
3882
+ function requireOptions(provider, options, keys) {
3883
+ const missing = keys.filter((key) => !options[key]);
3884
+ if (missing.length > 0) throw new TypeError(`${provider}() is missing required option(s): ${missing.join(", ")}.`);
3885
+ }
3886
+ var init_providers = __esmMin((() => {
3887
+ init_caching_provider();
3888
+ init_oauth();
3889
+ }));
3890
+ //#endregion
3891
+ //#region src/auth/sf-cli.ts
3892
+ /**
3893
+ * Uses an org you are logged into with the Salesforce CLI (`sf org login web --alias my-org`).
3894
+ * Handy for local development: no connected app or secrets needed.
3895
+ *
3896
+ * The token comes from `sf org auth show-access-token`, the instance URL from `sf org display`
3897
+ * (which no longer returns tokens). Both run without a shell. On a 401 the CLI is asked again;
3898
+ * it refreshes the token itself.
3899
+ */
3900
+ function sfCli(options = {}) {
3901
+ if (options.targetOrg !== void 0 && !ALIAS.test(options.targetOrg)) throw new TypeError(`sfCli(): invalid targetOrg "${options.targetOrg}".`);
3902
+ const command = options.command ?? "sf";
3903
+ const run = options.runCommand ?? runCommand;
3904
+ const target = options.targetOrg ? ["--target-org", options.targetOrg] : [];
3905
+ return new CachingAuthProvider(async () => {
3906
+ const display = await callSf(run, command, [
3907
+ "org",
3908
+ "display",
3909
+ "--json",
3910
+ ...target
3911
+ ]);
3912
+ const instanceUrl = display.instanceUrl;
3913
+ if (!instanceUrl) throw new SalesforceAuthError("`sf org display` returned no instanceUrl.");
3914
+ const token = await callSf(run, command, [
3915
+ "org",
3916
+ "auth",
3917
+ "show-access-token",
3918
+ "--json",
3919
+ ...target
3920
+ ]).catch((error) => {
3921
+ if (usableToken(display.accessToken)) return { accessToken: display.accessToken };
3922
+ throw error;
3923
+ });
3924
+ if (!usableToken(token.accessToken)) throw new SalesforceAuthError("The Salesforce CLI returned no usable access token. Log in again with `sf org login web`.");
3925
+ return {
3926
+ accessToken: token.accessToken,
3927
+ instanceUrl
3928
+ };
3929
+ });
3930
+ }
3931
+ async function callSf(run, command, args) {
3932
+ let stdout;
3933
+ try {
3934
+ stdout = await run(command, args);
3935
+ } catch (error) {
3936
+ if (error.code === "ENOENT") throw new SalesforceAuthError("The Salesforce CLI (sf) was not found. Install it from https://developer.salesforce.com/tools/salesforcecli and log in with `sf org login web`.");
3937
+ throw error;
3938
+ }
3939
+ let json;
3940
+ try {
3941
+ json = JSON.parse(stdout.slice(Math.max(0, stdout.indexOf("{"))));
3942
+ } catch {
3943
+ throw new SalesforceAuthError(`\`sf ${args.slice(0, 3).join(" ")}\` did not return JSON: ${stdout.trim().slice(0, 200)}`);
3944
+ }
3945
+ if (json.status !== 0 || !json.result) throw new SalesforceAuthError(`\`sf ${args.slice(0, 3).join(" ")}\` failed: ${json.message ?? json.name ?? "unknown error"}`);
3946
+ return json.result;
3947
+ }
3948
+ function usableToken(token) {
3949
+ return typeof token === "string" && token.length > 0 && !token.includes("REDACTED");
3950
+ }
3951
+ function quoteForCmd(value) {
3952
+ return /[\s"]/.test(value) ? `"${value.replace(/"/g, "\"\"")}"` : value;
3953
+ }
3954
+ var ALIAS, CMD_NOT_FOUND, runCommand;
3955
+ var init_sf_cli = __esmMin((() => {
3956
+ init_errors();
3957
+ init_caching_provider();
3958
+ ALIAS = /^[\w.@+-]+$/;
3959
+ CMD_NOT_FOUND = 9009;
3960
+ runCommand = (command, args) => new Promise((resolve, reject) => {
3961
+ const windows = process.platform === "win32";
3962
+ const [file, fileArgs] = windows ? ["cmd.exe", [
3963
+ "/d",
3964
+ "/s",
3965
+ "/c",
3966
+ `"${[command, ...args].map(quoteForCmd).join(" ")}"`
3967
+ ]] : [command, [...args]];
3968
+ execFile(file, fileArgs, {
3969
+ windowsHide: true,
3970
+ windowsVerbatimArguments: windows,
3971
+ maxBuffer: 10485760,
3972
+ env: {
3973
+ ...process.env,
3974
+ SF_JSON_TO_STDOUT: "true"
3975
+ }
3976
+ }, (error, stdout, stderr) => {
3977
+ if (error && (error.code === "ENOENT" || windows && (error.code === CMD_NOT_FOUND || /is not recognized/i.test(stderr)))) reject(Object.assign(/* @__PURE__ */ new Error(`${command} was not found`), { code: "ENOENT" }));
3978
+ else if (error && !stdout.includes("{")) reject(/* @__PURE__ */ new Error(`${error.message}${stderr ? `\n${stderr.trim()}` : ""}`));
3979
+ else resolve(stdout);
3980
+ });
3981
+ });
3982
+ }));
3983
+ //#endregion
3984
+ export { BulkApi as $, init_search as A, init_errors as At, MAX_UPLOAD_BYTES as B, init_ui_api as C, soslEscape as Ct, isSObjectType as D, SalesforcePartialFailureError as Dt, init_query_builder as E, SalesforceError as Et, init_quick_actions as F, MetadataCache as Ft, init_events as G, buildMultipart as H, QueryApi as I, init_cache as It, CompositeRequestBuilder as J, toPublishResult as K, init_query as L, __commonJSMin as Lt, init_reports as M, init_api_version as Mt, reportRows as N, isApiVersion as Nt, soqlFor as O, SalesforceSaveError as Ot, QuickActionsApi as P, normalizeApiVersion as Pt, init_collections as Q, FilesApi as R, __esmMin as Rt, UiApi as S, soqlLiteral as St, TypeOfBuilder as T, SalesforceBulkJobError as Tt, init_multipart as U, init_files as V, EventsApi as W, init_composite as X, CompositeResponse as Y, CollectionsApi as Z, DebugLogCaptureError as _, init_escape as _t, createJwtAssertion as a, parseCsv as at, SObjectResource as b, soqlEscape as bt, refreshToken as c, toCsv as ct, init_client as d, ActionsApi as dt, BulkIngestJob as et, ApexExecutionError as f, init_actions as ft, DEFAULT_DEBUG_LEVELS as g, init_connection as gt, DEBUG_LEVEL_NAME as h, SalesforceConnection as ht, clientCredentials as i, init_csv as it, ReportsApi as j, isSalesforceError as jt, SearchApi as k, hasErrorCode as kt, tokenProvider as l, ApprovalsApi as lt, init_tooling as m, init_fetch_transport as mt, sfCli as n, init_bulk as nt, init_providers as o, parseCsvRows as ot, ToolingApi as p, fetchTransport as pt, CompositeApi as q, accessToken as r, CsvRowParser as rt, jwtBearer as s, parseCsvStream as st, init_sf_cli as t, BulkQueryJob as tt, SalesforceClient as u, init_approvals as ut, DebugLogsApi as v, soqlDate as vt, SoqlQueryBuilder as w, SalesforceAuthError as wt, init_sobject as x, soqlEscapeDateOnly as xt, init_debug_logs as y, soqlDateLiteral as yt, MAX_JSON_UPLOAD_BYTES as z };
3985
+
3986
+ //# sourceMappingURL=sf-cli-CtWtdkJ0.mjs.map