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