@salesforce/lds-runtime-mobile 1.428.0-dev14 → 1.428.0-dev16

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.
package/sfdc/main.js CHANGED
@@ -19,8 +19,9 @@ import { withRegistration, register } from 'force/ldsEngine';
19
19
  import { setupInstrumentation, instrumentAdapter as instrumentAdapter$1, instrumentLuvio, setLdsAdaptersUiapiInstrumentation, setLdsNetworkAdapterInstrumentation } from 'force/ldsInstrumentation';
20
20
  import { HttpStatusCode as HttpStatusCode$1, setBypassDeepFreeze, StoreKeySet, StringKeyInMemoryStore, Reader, serializeStructuredKey, deepFreeze as deepFreeze$1, emitAdapterEvent, ingestShape, coerceConfig as coerceConfig$1, typeCheckConfig as typeCheckConfig$h, createResourceParams as createResourceParams$h, StoreKeyMap, buildNetworkSnapshotCachePolicy as buildNetworkSnapshotCachePolicy$f, resolveLink, createCustomAdapterEventEmitter, isFileReference, Environment, Luvio, InMemoryStore } from 'force/luvioEngine';
21
21
  import { isSupportedEntity, configuration, getObjectInfoAdapterFactory, RECORD_ID_PREFIX, RECORD_FIELDS_KEY_JUNCTION, isStoreKeyRecordViewEntity, extractRecordIdFromStoreKey, buildRecordRepKeyFromId, keyBuilderRecord, RecordRepresentationTTL, keyBuilderQuickActionExecutionRepresentation, ingestQuickActionExecutionRepresentation, getRecordId18 as getRecordId18$1, getRecordsAdapterFactory as getRecordsAdapterFactory$1, RecordRepresentationRepresentationType, ObjectInfoRepresentationType, getObjectInfosAdapterFactory, getObjectInfoDirectoryAdapterFactory, UiApiNamespace, RecordRepresentationType, RecordRepresentationVersion } from 'force/ldsAdaptersUiapi';
22
- import allowUpdatesForNonCachedRecords from '@salesforce/gate/lmr.allowUpdatesForNonCachedRecords';
22
+ import draftQueueMaxRetryAttemptsGate from '@salesforce/gate/lmr.draft-queue-max-retry-attempts';
23
23
  import { getInstrumentation, idleDetector } from 'o11y/client';
24
+ import allowUpdatesForNonCachedRecords from '@salesforce/gate/lmr.allowUpdatesForNonCachedRecords';
24
25
  import caseSensitiveUserId from '@salesforce/user/Id';
25
26
  import { Kind as Kind$1, visit as visit$1, isObjectType, defaultFieldResolver, buildSchema, parse as parse$8, extendSchema, isScalarType, execute, print } from 'force/ldsGraphqlParser';
26
27
  import graphqlRelationshipFieldsPerf from '@salesforce/gate/lds.graphqlRelationshipFieldsPerf';
@@ -2338,6 +2339,13 @@ function customActionHandler(executor, id, draftQueue) {
2338
2339
  }
2339
2340
 
2340
2341
  const DRAFT_SEGMENT = 'DRAFT';
2342
+ /**
2343
+ * Metadata key under which an action's upload retry-attempt count is persisted.
2344
+ * Lives in the action's durable `metadata` bag so the count survives app restarts
2345
+ * (the in-memory backoff interval resets on every startQueue()). Written by the
2346
+ * action handler on each retry and cleared when an action is manually retried.
2347
+ */
2348
+ const DRAFT_ACTION_RETRY_COUNT_METADATA_KEY = 'retryCount';
2341
2349
  class DurableDraftQueue {
2342
2350
  getHandler(id) {
2343
2351
  const handler = this.handlers[id];
@@ -2696,10 +2704,14 @@ class DurableDraftQueue {
2696
2704
  if (!isDraftError(target)) {
2697
2705
  throw Error(`Action ${actionId} is not in Error state`);
2698
2706
  }
2707
+ // A manual retry is a fresh start: clear the persisted upload retry-attempt
2708
+ // count so the action doesn't immediately re-cap after a single failure.
2709
+ const { [DRAFT_ACTION_RETRY_COUNT_METADATA_KEY]: _retryCount, ...metadataWithoutRetryCount } = target.metadata || {};
2699
2710
  let pendingAction = {
2700
2711
  ...target,
2701
2712
  status: DraftActionStatus.Pending,
2702
2713
  error: undefined,
2714
+ metadata: metadataWithoutRetryCount,
2703
2715
  };
2704
2716
  await this.draftStore.writeAction(pendingAction);
2705
2717
  await this.notifyChangedListeners({
@@ -42127,6 +42139,324 @@ function makeEnvironmentUiApiRecordDraftAware(luvio, options, env) {
42127
42139
  return create$3(adapterSpecificEnvironments, {});
42128
42140
  }
42129
42141
 
42142
+ /**
42143
+ * This function takes an unknown error and normalizes it to an Error object
42144
+ */
42145
+ function normalizeError$1(error) {
42146
+ if (typeof error === 'object' && error instanceof Error) {
42147
+ return error;
42148
+ }
42149
+ else if (typeof error === 'string') {
42150
+ return new Error(error);
42151
+ }
42152
+ return new Error(stringify$5(error));
42153
+ }
42154
+ /**
42155
+ * Maximum number of times an action's upload will be retried before the action is
42156
+ * transitioned to Error and surfaced to the consumer. Caps the otherwise-unbounded
42157
+ * retry loop that can wedge the single-worker draft queue when an upload fails
42158
+ * deterministically on every dispatch. Only enforced when the
42159
+ * `lmr.draft-queue-max-retry-attempts` gate is open.
42160
+ */
42161
+ const MAX_RETRY_ATTEMPTS = 5;
42162
+ const MAX_RETRY_ERROR_CODE = 'MAX_RETRY_ATTEMPTS_EXCEEDED';
42163
+ function isFetchResponse(failure) {
42164
+ // The union is exactly FetchResponse | Error, so "not an Error" is the FetchResponse.
42165
+ // This is safer than duck-typing on status/ok: a custom Error subclass that happens to
42166
+ // carry those properties would be misclassified by a structural check.
42167
+ return !(failure instanceof Error);
42168
+ }
42169
+ /**
42170
+ * Builds the FetchResponse-shaped error attached to an action that has exhausted its
42171
+ * upload retries. Shaped like a real network error response (status/statusText/ok/
42172
+ * headers/body) so downstream consumers (DraftManager, queue instrumentation) that
42173
+ * read `action.error.status`/`.body`/etc. behave consistently with a server error.
42174
+ *
42175
+ * The failure that caused the final attempt — a non-ok response (HTTP path) or a thrown
42176
+ * error (catch path) — is folded into the body so the surfaced error says WHAT failed,
42177
+ * not merely that the attempt cap was reached.
42178
+ */
42179
+ function buildMaxRetryError(lastFailure) {
42180
+ const capMessage = `Draft action exceeded ${MAX_RETRY_ATTEMPTS} upload retry attempts`;
42181
+ if (isFetchResponse(lastFailure)) {
42182
+ // Preserve the server's own error detail (status + body) so the surfaced error
42183
+ // carries the last response that caused us to give up retrying.
42184
+ return {
42185
+ status: HttpStatusCode$1.ServerError,
42186
+ statusText: capMessage,
42187
+ ok: false,
42188
+ headers: {},
42189
+ body: {
42190
+ errorCode: MAX_RETRY_ERROR_CODE,
42191
+ message: `${capMessage}. Last response: status=${lastFailure.status} ${lastFailure.statusText}`,
42192
+ lastResponseStatus: lastFailure.status,
42193
+ lastResponseBody: lastFailure.body,
42194
+ },
42195
+ };
42196
+ }
42197
+ return {
42198
+ status: HttpStatusCode$1.ServerError,
42199
+ statusText: capMessage,
42200
+ ok: false,
42201
+ headers: {},
42202
+ body: {
42203
+ errorCode: MAX_RETRY_ERROR_CODE,
42204
+ message: `${capMessage}. Last error: ${lastFailure.name}: ${lastFailure.message}`,
42205
+ lastErrorName: lastFailure.name,
42206
+ lastErrorMessage: lastFailure.message,
42207
+ },
42208
+ };
42209
+ }
42210
+
42211
+ function attachObserversToAdapterRequestContext(observers, adapterRequestContext) {
42212
+ if (adapterRequestContext === undefined) {
42213
+ return { eventObservers: observers };
42214
+ }
42215
+ if (adapterRequestContext.eventObservers === undefined) {
42216
+ return { ...adapterRequestContext, eventObservers: observers };
42217
+ }
42218
+ return {
42219
+ ...adapterRequestContext,
42220
+ eventObservers: adapterRequestContext.eventObservers.concat(observers),
42221
+ };
42222
+ }
42223
+ /**
42224
+ * Use this method to sanitize the unknown error object when
42225
+ * we are unsure of the type of the error thrown
42226
+ *
42227
+ * @param err Unknown object to sanitize
42228
+ * @returns an instance of error
42229
+ */
42230
+ function normalizeError(err) {
42231
+ if (err instanceof Error) {
42232
+ return err;
42233
+ }
42234
+ else if (typeof err === 'string') {
42235
+ return new Error(err);
42236
+ }
42237
+ return new Error(stringify$5(err));
42238
+ }
42239
+
42240
+ const O11Y_NAMESPACE_LDS_MOBILE = 'lds-mobile';
42241
+ // metrics
42242
+ /** GraphQL */
42243
+ const GRAPHQL_EVAL_ERROR = 'gql-eval-error';
42244
+ const GRAPHQL_EVAL_DB_READ_DURATION = 'gql-eval-db-read-duration';
42245
+ const GRAPHQL_EVAL_ROOT_QUERY_COUNT = 'gql-eval-root-query-count';
42246
+ const GRAPHQL_EVAL_TOTAL_QUERY_COUNT = 'gql-eval-total-query-count';
42247
+ const GRAPHQL_EVAL_MAX_CHILD_RELATIONSHIPS_COUNT = 'gql-eval-max-child-count';
42248
+ const GRAPHQL_EVAL_QUERY_RECORD_COUNT = 'gql-eval-query-record-count';
42249
+ const GRAPHQL_SNAPSHOT_REFRESH_UNDEFINED = 'gql-snapshot-refresh-undefined';
42250
+ /** Draft Queue */
42251
+ const DRAFT_QUEUE_STATE_STARTED = 'draft-queue-state-started';
42252
+ const DRAFT_QUEUE_STATE_ERROR = 'draft-queue-state-error';
42253
+ const DRAFT_QUEUE_STATE_WAITING = 'draft-queue-state-waiting';
42254
+ const DRAFT_QUEUE_STATE_STOPPED = 'draft-queue-state-stopped';
42255
+ const DRAFT_QUEUE_ACTION_ADDED = 'draft-queue-action-added';
42256
+ const DRAFT_QUEUE_ACTION_UPLOADING = 'draft-queue-action-uploading';
42257
+ const DRAFT_QUEUE_ACTION_COMPLETED = 'draft-queue-action-completed';
42258
+ const DRAFT_QUEUE_ACTION_DELETED = 'draft-queue-action-deleted';
42259
+ const DRAFT_QUEUE_ACTION_UPDATED = 'draft-queue-action-updated';
42260
+ const DRAFT_QUEUE_ACTION_FAILED = 'draft-queue-action-failed';
42261
+ const DRAFT_QUEUE_ACTION_UPLOAD_ERROR = 'draft-queue-action-upload-error';
42262
+ const DRAFT_QUEUE_TOTAL_MERGE_ACTIONS_CALLS = 'draft-queue-total-mergeActions-calls';
42263
+ /** Content Document */
42264
+ const CREATE_CONTENT_DOCUMENT_AND_VERSION_TOTAL_SYNTHESIZE_CALLS = 'content-document-version-total-synthesize-calls';
42265
+ const CREATE_CONTENT_DOCUMENT_AND_VERSION_DRAFT_SYNTHESIZE_ERROR = 'create-content-document-version-draft-synthesize-error';
42266
+ /** Priming */
42267
+ const PRIMING_TOTAL_SESSION_COUNT = 'priming-total-session-count';
42268
+ const PRIMING_TOTAL_ERROR_COUNT = 'priming-total-error-count';
42269
+ const PRIMING_TOTAL_PRIMED_COUNT = 'priming-total-primed-count';
42270
+ const PRIMING_TOTAL_CONFLICT_COUNT = 'priming-total-conflict-count';
42271
+ // logs
42272
+ const GRAPHQL_QUERY_PARSE_ERROR = 'gql-query-parse-error';
42273
+ const GRAPHQL_SQL_EVAL_PRECONDITION_ERROR = 'gql-sql-pre-eval-error';
42274
+ const GRAPHQL_CREATE_SNAPSHOT_ERROR = 'gql-create-snapshot-error';
42275
+ const DRAFT_AWARE_CREATE_CONTENT_DOCUMENT_AND_VERSION_ERROR = 'draft-aware-create-content-document-and-version-error';
42276
+ /** Garbage Collection */
42277
+ const GARBAGE_COLLECTION_AGGRESSIVE_TRIM_COUNT = 'garbage-collection-aggressive-trim-count';
42278
+ const GARBAGE_COLLECTION_AGGRESSIVE_TRIM_RECORDS_TRIMMED = 'garbage-collection-aggressive-trim-records-trimmed';
42279
+ const GARBAGE_COLLECTION_AGGRESSIVE_TRIM_TOTAL_RECORDS_BEFORE_TRIM = 'garbage-collection-aggressive-trim-total-records-before-trim';
42280
+ const ldsMobileInstrumentation = getInstrumentation(O11Y_NAMESPACE_LDS_MOBILE);
42281
+ const nimbusLogger = typeof __nimbus !== 'undefined' &&
42282
+ __nimbus.plugins !== undefined &&
42283
+ __nimbus.plugins.JSLoggerPlugin !== undefined
42284
+ ? __nimbus.plugins.JSLoggerPlugin
42285
+ : undefined;
42286
+ function reportGraphqlQueryParseError(err) {
42287
+ const error = normalizeError(err);
42288
+ const errorCode = GRAPHQL_QUERY_PARSE_ERROR;
42289
+ // Metric
42290
+ reportGraphqlAdapterError(errorCode);
42291
+ // Log
42292
+ ldsMobileInstrumentation.error(error, errorCode);
42293
+ }
42294
+ function reportGraphqlSqlEvalPreconditionError(err) {
42295
+ const error = normalizeError(err);
42296
+ const errorCode = GRAPHQL_SQL_EVAL_PRECONDITION_ERROR;
42297
+ // Metric
42298
+ reportGraphqlAdapterError(errorCode);
42299
+ // Log
42300
+ ldsMobileInstrumentation.error(error, errorCode);
42301
+ }
42302
+ function reportGraphqlCreateSnapshotError(err) {
42303
+ const error = normalizeError(err);
42304
+ const errorCode = GRAPHQL_CREATE_SNAPSHOT_ERROR;
42305
+ // Metric
42306
+ reportGraphqlAdapterError(errorCode);
42307
+ // Log
42308
+ ldsMobileInstrumentation.error(error, errorCode);
42309
+ }
42310
+ function reportGraphQlEvalDbReadDuration(duration) {
42311
+ ldsMobileInstrumentation.trackValue(GRAPHQL_EVAL_DB_READ_DURATION, duration);
42312
+ }
42313
+ function reportGraphqlAdapterError(errorCode) {
42314
+ // Increment overall count with errorCode as tag
42315
+ ldsMobileInstrumentation.incrementCounter(GRAPHQL_EVAL_ERROR, 1, true, { errorCode });
42316
+ }
42317
+ function reportGraphqlQueryInstrumentation(data) {
42318
+ const queryBuckets = [1, 2, 3, 4, 5, 10, 20, 50, 100];
42319
+ const recordBuckets = [
42320
+ 1, 5, 10, 20, 50, 100, 1000, 2000, 5000, 10000, 50000, 100000, 1000000,
42321
+ ];
42322
+ ldsMobileInstrumentation.bucketValue(GRAPHQL_EVAL_ROOT_QUERY_COUNT, data.rootQueryCount, queryBuckets);
42323
+ ldsMobileInstrumentation.bucketValue(GRAPHQL_EVAL_TOTAL_QUERY_COUNT, data.totalQueryCount, queryBuckets);
42324
+ ldsMobileInstrumentation.bucketValue(GRAPHQL_EVAL_MAX_CHILD_RELATIONSHIPS_COUNT, data.maxChildRelationships, queryBuckets);
42325
+ ldsMobileInstrumentation.bucketValue(GRAPHQL_EVAL_QUERY_RECORD_COUNT, data.requestedRecordCount, recordBuckets);
42326
+ }
42327
+ function incrementGraphQLRefreshUndfined() {
42328
+ ldsMobileInstrumentation.incrementCounter(GRAPHQL_SNAPSHOT_REFRESH_UNDEFINED);
42329
+ }
42330
+ function reportDraftActionEvent(state, draftCount, message) {
42331
+ if (nimbusLogger) {
42332
+ nimbusLogger.logInfo(`Draft action event: ${state}, depth: ${draftCount}${message ? `, message: ${message}` : ''}`);
42333
+ }
42334
+ switch (state) {
42335
+ case 'added':
42336
+ ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_ACTION_ADDED);
42337
+ break;
42338
+ case 'uploading':
42339
+ ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_ACTION_UPLOADING);
42340
+ break;
42341
+ case 'completed':
42342
+ ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_ACTION_COMPLETED);
42343
+ break;
42344
+ case 'deleted':
42345
+ ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_ACTION_DELETED);
42346
+ break;
42347
+ case 'failed':
42348
+ ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_ACTION_FAILED, 1, true);
42349
+ break;
42350
+ case 'updated':
42351
+ ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_ACTION_UPDATED);
42352
+ break;
42353
+ }
42354
+ }
42355
+ /**
42356
+ * Reports an exception thrown while uploading a draft action. The thrown error is
42357
+ * otherwise swallowed by the upload handler's retry path, leaving the failure
42358
+ * invisible in telemetry; this surfaces it to o11y (log + counter) so a deterministic
42359
+ * upload failure can be diagnosed instead of silently retried forever.
42360
+ *
42361
+ * PII: the device log line carries only the handler id, retry attempt, and the error
42362
+ * NAME (e.g. "TypeError") — never the free-text error message, which can embed record
42363
+ * ids (URL path segments) or server field-validation strings. The full normalized error
42364
+ * (message + stack) is handed only to `ldsMobileInstrumentation.error`, the structured
42365
+ * o11y error pipeline responsible for scrubbing/handling that detail. Also does NOT log
42366
+ * the action's targetId/tag or any request body/field values.
42367
+ */
42368
+ function reportDraftActionUploadError(error, handlerId, attempt) {
42369
+ const normalized = normalizeError(error);
42370
+ if (nimbusLogger) {
42371
+ nimbusLogger.logError(`Draft action upload error: handler=${handlerId}, attempt=${attempt}, name=${normalized.name}`);
42372
+ }
42373
+ ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_ACTION_UPLOAD_ERROR, 1, true, {
42374
+ handlerId,
42375
+ });
42376
+ ldsMobileInstrumentation.error(normalized, DRAFT_QUEUE_ACTION_UPLOAD_ERROR);
42377
+ }
42378
+ function reportDraftQueueState(state, draftCount) {
42379
+ if (nimbusLogger) {
42380
+ nimbusLogger.logInfo(`Draft state changed: ${state}, depth: ${draftCount}`);
42381
+ }
42382
+ switch (state) {
42383
+ case 'started':
42384
+ ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_STATE_STARTED);
42385
+ break;
42386
+ case 'error':
42387
+ ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_STATE_ERROR, 1, true);
42388
+ break;
42389
+ case 'waiting':
42390
+ ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_STATE_WAITING);
42391
+ break;
42392
+ case 'stopped':
42393
+ ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_STATE_STOPPED);
42394
+ break;
42395
+ }
42396
+ }
42397
+ function reportDraftAwareContentDocumentVersionSynthesizeError(err) {
42398
+ let error;
42399
+ if (err.body !== undefined) {
42400
+ error = err.body;
42401
+ }
42402
+ else {
42403
+ error = normalizeError(err);
42404
+ }
42405
+ const errorCode = DRAFT_AWARE_CREATE_CONTENT_DOCUMENT_AND_VERSION_ERROR;
42406
+ const errorType = error.errorType;
42407
+ const tags = {
42408
+ errorCode,
42409
+ };
42410
+ if (errorType !== undefined) {
42411
+ tags.errorType = errorType;
42412
+ }
42413
+ // Metric
42414
+ ldsMobileInstrumentation.incrementCounter(CREATE_CONTENT_DOCUMENT_AND_VERSION_DRAFT_SYNTHESIZE_ERROR, 1, true, tags);
42415
+ // Log
42416
+ ldsMobileInstrumentation.error(error, errorCode);
42417
+ }
42418
+ function reportDraftAwareContentVersionSynthesizeCalls(mimeType) {
42419
+ ldsMobileInstrumentation.incrementCounter(CREATE_CONTENT_DOCUMENT_AND_VERSION_TOTAL_SYNTHESIZE_CALLS, 1, undefined, { mimeType });
42420
+ }
42421
+ /** Priming */
42422
+ function reportPrimingSessionCreated() {
42423
+ ldsMobileInstrumentation.incrementCounter(PRIMING_TOTAL_SESSION_COUNT, 1, undefined, {});
42424
+ }
42425
+ function reportPrimingError(errorType, recordCount) {
42426
+ ldsMobileInstrumentation.incrementCounter(PRIMING_TOTAL_ERROR_COUNT, recordCount, undefined, {
42427
+ errorType,
42428
+ });
42429
+ }
42430
+ function reportPrimingSuccess(recordCount) {
42431
+ ldsMobileInstrumentation.incrementCounter(PRIMING_TOTAL_PRIMED_COUNT, recordCount, undefined);
42432
+ }
42433
+ function reportPrimingConflict(resolutionType, recordCount) {
42434
+ ldsMobileInstrumentation.incrementCounter(PRIMING_TOTAL_CONFLICT_COUNT, recordCount, undefined, {
42435
+ resolutionType,
42436
+ });
42437
+ }
42438
+ /** Network */
42439
+ function reportChunkCandidateUrlLength(urlLength) {
42440
+ const buckets = [8000, 10000, 12000, 14000, 16000];
42441
+ ldsMobileInstrumentation.bucketValue('chunk-candidate-url-length-histogram', urlLength, buckets);
42442
+ }
42443
+ /** Garbage Collection */
42444
+ function reportAggressiveTrimTriggered(trimCount, beforeTrimRecordCount, totalRecordsTrimmed) {
42445
+ ldsMobileInstrumentation.trackValue(GARBAGE_COLLECTION_AGGRESSIVE_TRIM_COUNT, trimCount);
42446
+ ldsMobileInstrumentation.trackValue(GARBAGE_COLLECTION_AGGRESSIVE_TRIM_TOTAL_RECORDS_BEFORE_TRIM, beforeTrimRecordCount);
42447
+ ldsMobileInstrumentation.trackValue(GARBAGE_COLLECTION_AGGRESSIVE_TRIM_RECORDS_TRIMMED, totalRecordsTrimmed);
42448
+ }
42449
+ /** One Store Cache Purge */
42450
+ const ONESTORE_CACHE_PURGING_RECORDS_PURGED = 'onestore-cache-purging-records-purged';
42451
+ const ONESTORE_CACHE_PURGING_RECORDS_ERROR = 'onestore-cache-purging-records-error';
42452
+ function reportOneStoreRecordsPurgedFromCache(recordsPurged) {
42453
+ ldsMobileInstrumentation.trackValue(ONESTORE_CACHE_PURGING_RECORDS_PURGED, recordsPurged);
42454
+ }
42455
+ function reportOneStoreCachePurgingError(error) {
42456
+ const errorMessage = normalizeError(error);
42457
+ ldsMobileInstrumentation.error(errorMessage, ONESTORE_CACHE_PURGING_RECORDS_ERROR);
42458
+ }
42459
+
42130
42460
  const HTTP_HEADER_RETRY_AFTER = 'Retry-After';
42131
42461
  const HTTP_HEADER_IDEMPOTENCY_KEY = 'Idempotency-Key';
42132
42462
  const ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED = 'IDEMPOTENCY_FEATURE_NOT_ENABLED';
@@ -42277,20 +42607,89 @@ class AbstractResourceRequestActionHandler {
42277
42607
  shouldRetry = true;
42278
42608
  actionDataChanged = true;
42279
42609
  }
42280
- await actionErrored(shouldRetry
42281
- ? updatedAction
42282
- : {
42610
+ if (shouldRetry) {
42611
+ // Funnel the retry through the cap: when the gate is open this persists
42612
+ // the attempt count and errors out once the cap is hit; when the gate is
42613
+ // closed it behaves exactly as before. The non-ok response is the failure
42614
+ // that would otherwise be retried.
42615
+ await this.retryOrCap(updatedAction, response, actionErrored, retryDelayInMs, actionDataChanged);
42616
+ }
42617
+ else {
42618
+ await actionErrored({
42283
42619
  ...updatedAction,
42284
42620
  error: response,
42285
42621
  status: DraftActionStatus.Error,
42286
- }, shouldRetry, retryDelayInMs, actionDataChanged);
42622
+ }, false, retryDelayInMs, actionDataChanged);
42623
+ }
42287
42624
  return ProcessActionResult.ACTION_ERRORED;
42288
42625
  }
42289
42626
  catch (e) {
42290
- await actionErrored(action, true);
42627
+ // #3 (ungated): the thrown exception is otherwise swallowed here, leaving a
42628
+ // deterministic upload failure invisible in telemetry. Surface it to o11y
42629
+ // before deciding whether to retry.
42630
+ const error = normalizeError$1(e);
42631
+ const attempt = this.getUploadRetryCount(action) + 1;
42632
+ reportDraftActionUploadError(error, this.handlerId, attempt);
42633
+ await this.retryOrCap(action, error, actionErrored);
42291
42634
  return ProcessActionResult.NETWORK_ERROR;
42292
42635
  }
42293
42636
  }
42637
+ /**
42638
+ * Reads the persisted upload retry-attempt count from the action's durable metadata
42639
+ * bag. The count lives in metadata (rather than in-memory) so it survives app
42640
+ * restarts and queue re-creation — the failure modes that let the unbounded retry
42641
+ * loop run for days.
42642
+ */
42643
+ getUploadRetryCount(action) {
42644
+ const parsed = Number(action.metadata?.[DRAFT_ACTION_RETRY_COUNT_METADATA_KEY]);
42645
+ // Guard against a missing/corrupt persisted value: a NaN, fractional, or negative
42646
+ // count must never weaken the cap. Floor to a non-negative integer, defaulting to 0.
42647
+ if (!Number.isFinite(parsed) || parsed < 0) {
42648
+ return 0;
42649
+ }
42650
+ return Math.floor(parsed);
42651
+ }
42652
+ /**
42653
+ * Enforces the upload retry-attempt cap around the queue's `actionErrored` callback.
42654
+ * Both retry paths — a retryable HTTP error and a thrown exception — funnel through
42655
+ * here.
42656
+ *
42657
+ * The entire cap behavior is gated behind `lmr.draft-queue-max-retry-attempts`. When
42658
+ * the gate is CLOSED this is a transparent passthrough: it issues the same
42659
+ * `actionErrored(action, true, ...)` retry the queue made before this change, and
42660
+ * writes no extra metadata. When the gate is OPEN it persists an incremented attempt
42661
+ * count to the action's durable metadata and, once the action has failed
42662
+ * {@link MAX_RETRY_ATTEMPTS} times, transitions it to Error (carrying a
42663
+ * FetchResponse-shaped error that names the failure that exhausted the retries)
42664
+ * instead of scheduling yet another retry.
42665
+ */
42666
+ async retryOrCap(action, lastFailure, actionErrored, retryDelayInMs, actionDataChanged) {
42667
+ if (!draftQueueMaxRetryAttemptsGate.isOpen({ fallback: false })) {
42668
+ // Gate closed: preserve the pre-existing unbounded-retry behavior exactly.
42669
+ await actionErrored(action, true, retryDelayInMs, actionDataChanged);
42670
+ return;
42671
+ }
42672
+ const attempt = this.getUploadRetryCount(action) + 1;
42673
+ if (attempt >= MAX_RETRY_ATTEMPTS) {
42674
+ await actionErrored({
42675
+ ...action,
42676
+ error: buildMaxRetryError(lastFailure),
42677
+ status: DraftActionStatus.Error,
42678
+ }, false);
42679
+ return;
42680
+ }
42681
+ // Persist the incremented attempt count so it survives restarts. Forcing
42682
+ // actionDataChanged ensures the queue writes the updated metadata before the
42683
+ // action is rescheduled for retry.
42684
+ const retryingAction = {
42685
+ ...action,
42686
+ metadata: {
42687
+ ...action.metadata,
42688
+ [DRAFT_ACTION_RETRY_COUNT_METADATA_KEY]: String(attempt),
42689
+ },
42690
+ };
42691
+ await actionErrored(retryingAction, true, retryDelayInMs, true);
42692
+ }
42294
42693
  async handleActionEnqueued(action) {
42295
42694
  const impactedKeys = await this.recordService.setSideEffectsForActions([action]);
42296
42695
  await this.recordService.reapplyRecordSideEffects(impactedKeys);
@@ -42454,6 +42853,13 @@ class AbstractResourceRequestActionHandler {
42454
42853
  this.isActionOfType(sourceAction)) {
42455
42854
  targetAction.status = DraftActionStatus.Pending;
42456
42855
  targetAction.data = sourceAction.data;
42856
+ // Replacing an action's data re-queues it as a fresh upload, so the persisted
42857
+ // upload retry-attempt count must not carry over — otherwise a previously-
42858
+ // throttled action could immediately re-hit the cap on its first new attempt.
42859
+ // (Mirrors the same clear in mergeActions.)
42860
+ if (targetAction.metadata !== undefined) {
42861
+ delete targetAction.metadata[DRAFT_ACTION_RETRY_COUNT_METADATA_KEY];
42862
+ }
42457
42863
  return targetAction;
42458
42864
  }
42459
42865
  else {
@@ -42493,6 +42899,11 @@ class AbstractResourceRequestActionHandler {
42493
42899
  }
42494
42900
  // overlay metadata
42495
42901
  merged.metadata = { ...targetMetadata, ...sourceMetadata };
42902
+ // A merge folds new user data into the action and re-queues it as a fresh upload,
42903
+ // so the persisted upload retry-attempt count must not carry over — otherwise a
42904
+ // previously-throttled action could immediately re-hit the cap on its first new
42905
+ // attempt.
42906
+ delete merged.metadata[DRAFT_ACTION_RETRY_COUNT_METADATA_KEY];
42496
42907
  // put status back to pending to auto upload if queue is active and targed is at the head.
42497
42908
  merged.status = DraftActionStatus.Pending;
42498
42909
  return merged;
@@ -43103,231 +43514,6 @@ class AbstractQuickActionHandler extends AbstractResourceRequestActionHandler {
43103
43514
  }
43104
43515
  }
43105
43516
 
43106
- function attachObserversToAdapterRequestContext(observers, adapterRequestContext) {
43107
- if (adapterRequestContext === undefined) {
43108
- return { eventObservers: observers };
43109
- }
43110
- if (adapterRequestContext.eventObservers === undefined) {
43111
- return { ...adapterRequestContext, eventObservers: observers };
43112
- }
43113
- return {
43114
- ...adapterRequestContext,
43115
- eventObservers: adapterRequestContext.eventObservers.concat(observers),
43116
- };
43117
- }
43118
- /**
43119
- * Use this method to sanitize the unknown error object when
43120
- * we are unsure of the type of the error thrown
43121
- *
43122
- * @param err Unknown object to sanitize
43123
- * @returns an instance of error
43124
- */
43125
- function normalizeError(err) {
43126
- if (err instanceof Error) {
43127
- return err;
43128
- }
43129
- else if (typeof err === 'string') {
43130
- return new Error(err);
43131
- }
43132
- return new Error(stringify$5(err));
43133
- }
43134
-
43135
- const O11Y_NAMESPACE_LDS_MOBILE = 'lds-mobile';
43136
- // metrics
43137
- /** GraphQL */
43138
- const GRAPHQL_EVAL_ERROR = 'gql-eval-error';
43139
- const GRAPHQL_EVAL_DB_READ_DURATION = 'gql-eval-db-read-duration';
43140
- const GRAPHQL_EVAL_ROOT_QUERY_COUNT = 'gql-eval-root-query-count';
43141
- const GRAPHQL_EVAL_TOTAL_QUERY_COUNT = 'gql-eval-total-query-count';
43142
- const GRAPHQL_EVAL_MAX_CHILD_RELATIONSHIPS_COUNT = 'gql-eval-max-child-count';
43143
- const GRAPHQL_EVAL_QUERY_RECORD_COUNT = 'gql-eval-query-record-count';
43144
- const GRAPHQL_SNAPSHOT_REFRESH_UNDEFINED = 'gql-snapshot-refresh-undefined';
43145
- /** Draft Queue */
43146
- const DRAFT_QUEUE_STATE_STARTED = 'draft-queue-state-started';
43147
- const DRAFT_QUEUE_STATE_ERROR = 'draft-queue-state-error';
43148
- const DRAFT_QUEUE_STATE_WAITING = 'draft-queue-state-waiting';
43149
- const DRAFT_QUEUE_STATE_STOPPED = 'draft-queue-state-stopped';
43150
- const DRAFT_QUEUE_ACTION_ADDED = 'draft-queue-action-added';
43151
- const DRAFT_QUEUE_ACTION_UPLOADING = 'draft-queue-action-uploading';
43152
- const DRAFT_QUEUE_ACTION_COMPLETED = 'draft-queue-action-completed';
43153
- const DRAFT_QUEUE_ACTION_DELETED = 'draft-queue-action-deleted';
43154
- const DRAFT_QUEUE_ACTION_UPDATED = 'draft-queue-action-updated';
43155
- const DRAFT_QUEUE_ACTION_FAILED = 'draft-queue-action-failed';
43156
- const DRAFT_QUEUE_TOTAL_MERGE_ACTIONS_CALLS = 'draft-queue-total-mergeActions-calls';
43157
- /** Content Document */
43158
- const CREATE_CONTENT_DOCUMENT_AND_VERSION_TOTAL_SYNTHESIZE_CALLS = 'content-document-version-total-synthesize-calls';
43159
- const CREATE_CONTENT_DOCUMENT_AND_VERSION_DRAFT_SYNTHESIZE_ERROR = 'create-content-document-version-draft-synthesize-error';
43160
- /** Priming */
43161
- const PRIMING_TOTAL_SESSION_COUNT = 'priming-total-session-count';
43162
- const PRIMING_TOTAL_ERROR_COUNT = 'priming-total-error-count';
43163
- const PRIMING_TOTAL_PRIMED_COUNT = 'priming-total-primed-count';
43164
- const PRIMING_TOTAL_CONFLICT_COUNT = 'priming-total-conflict-count';
43165
- // logs
43166
- const GRAPHQL_QUERY_PARSE_ERROR = 'gql-query-parse-error';
43167
- const GRAPHQL_SQL_EVAL_PRECONDITION_ERROR = 'gql-sql-pre-eval-error';
43168
- const GRAPHQL_CREATE_SNAPSHOT_ERROR = 'gql-create-snapshot-error';
43169
- const DRAFT_AWARE_CREATE_CONTENT_DOCUMENT_AND_VERSION_ERROR = 'draft-aware-create-content-document-and-version-error';
43170
- /** Garbage Collection */
43171
- const GARBAGE_COLLECTION_AGGRESSIVE_TRIM_COUNT = 'garbage-collection-aggressive-trim-count';
43172
- const GARBAGE_COLLECTION_AGGRESSIVE_TRIM_RECORDS_TRIMMED = 'garbage-collection-aggressive-trim-records-trimmed';
43173
- const GARBAGE_COLLECTION_AGGRESSIVE_TRIM_TOTAL_RECORDS_BEFORE_TRIM = 'garbage-collection-aggressive-trim-total-records-before-trim';
43174
- const ldsMobileInstrumentation = getInstrumentation(O11Y_NAMESPACE_LDS_MOBILE);
43175
- const nimbusLogger = typeof __nimbus !== 'undefined' &&
43176
- __nimbus.plugins !== undefined &&
43177
- __nimbus.plugins.JSLoggerPlugin !== undefined
43178
- ? __nimbus.plugins.JSLoggerPlugin
43179
- : undefined;
43180
- function reportGraphqlQueryParseError(err) {
43181
- const error = normalizeError(err);
43182
- const errorCode = GRAPHQL_QUERY_PARSE_ERROR;
43183
- // Metric
43184
- reportGraphqlAdapterError(errorCode);
43185
- // Log
43186
- ldsMobileInstrumentation.error(error, errorCode);
43187
- }
43188
- function reportGraphqlSqlEvalPreconditionError(err) {
43189
- const error = normalizeError(err);
43190
- const errorCode = GRAPHQL_SQL_EVAL_PRECONDITION_ERROR;
43191
- // Metric
43192
- reportGraphqlAdapterError(errorCode);
43193
- // Log
43194
- ldsMobileInstrumentation.error(error, errorCode);
43195
- }
43196
- function reportGraphqlCreateSnapshotError(err) {
43197
- const error = normalizeError(err);
43198
- const errorCode = GRAPHQL_CREATE_SNAPSHOT_ERROR;
43199
- // Metric
43200
- reportGraphqlAdapterError(errorCode);
43201
- // Log
43202
- ldsMobileInstrumentation.error(error, errorCode);
43203
- }
43204
- function reportGraphQlEvalDbReadDuration(duration) {
43205
- ldsMobileInstrumentation.trackValue(GRAPHQL_EVAL_DB_READ_DURATION, duration);
43206
- }
43207
- function reportGraphqlAdapterError(errorCode) {
43208
- // Increment overall count with errorCode as tag
43209
- ldsMobileInstrumentation.incrementCounter(GRAPHQL_EVAL_ERROR, 1, true, { errorCode });
43210
- }
43211
- function reportGraphqlQueryInstrumentation(data) {
43212
- const queryBuckets = [1, 2, 3, 4, 5, 10, 20, 50, 100];
43213
- const recordBuckets = [
43214
- 1, 5, 10, 20, 50, 100, 1000, 2000, 5000, 10000, 50000, 100000, 1000000,
43215
- ];
43216
- ldsMobileInstrumentation.bucketValue(GRAPHQL_EVAL_ROOT_QUERY_COUNT, data.rootQueryCount, queryBuckets);
43217
- ldsMobileInstrumentation.bucketValue(GRAPHQL_EVAL_TOTAL_QUERY_COUNT, data.totalQueryCount, queryBuckets);
43218
- ldsMobileInstrumentation.bucketValue(GRAPHQL_EVAL_MAX_CHILD_RELATIONSHIPS_COUNT, data.maxChildRelationships, queryBuckets);
43219
- ldsMobileInstrumentation.bucketValue(GRAPHQL_EVAL_QUERY_RECORD_COUNT, data.requestedRecordCount, recordBuckets);
43220
- }
43221
- function incrementGraphQLRefreshUndfined() {
43222
- ldsMobileInstrumentation.incrementCounter(GRAPHQL_SNAPSHOT_REFRESH_UNDEFINED);
43223
- }
43224
- function reportDraftActionEvent(state, draftCount, message) {
43225
- if (nimbusLogger) {
43226
- nimbusLogger.logInfo(`Draft action event: ${state}, depth: ${draftCount}${message ? `, message: ${message}` : ''}`);
43227
- }
43228
- switch (state) {
43229
- case 'added':
43230
- ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_ACTION_ADDED);
43231
- break;
43232
- case 'uploading':
43233
- ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_ACTION_UPLOADING);
43234
- break;
43235
- case 'completed':
43236
- ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_ACTION_COMPLETED);
43237
- break;
43238
- case 'deleted':
43239
- ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_ACTION_DELETED);
43240
- break;
43241
- case 'failed':
43242
- ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_ACTION_FAILED, 1, true);
43243
- break;
43244
- case 'updated':
43245
- ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_ACTION_UPDATED);
43246
- break;
43247
- }
43248
- }
43249
- function reportDraftQueueState(state, draftCount) {
43250
- if (nimbusLogger) {
43251
- nimbusLogger.logInfo(`Draft state changed: ${state}, depth: ${draftCount}`);
43252
- }
43253
- switch (state) {
43254
- case 'started':
43255
- ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_STATE_STARTED);
43256
- break;
43257
- case 'error':
43258
- ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_STATE_ERROR, 1, true);
43259
- break;
43260
- case 'waiting':
43261
- ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_STATE_WAITING);
43262
- break;
43263
- case 'stopped':
43264
- ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_STATE_STOPPED);
43265
- break;
43266
- }
43267
- }
43268
- function reportDraftAwareContentDocumentVersionSynthesizeError(err) {
43269
- let error;
43270
- if (err.body !== undefined) {
43271
- error = err.body;
43272
- }
43273
- else {
43274
- error = normalizeError(err);
43275
- }
43276
- const errorCode = DRAFT_AWARE_CREATE_CONTENT_DOCUMENT_AND_VERSION_ERROR;
43277
- const errorType = error.errorType;
43278
- const tags = {
43279
- errorCode,
43280
- };
43281
- if (errorType !== undefined) {
43282
- tags.errorType = errorType;
43283
- }
43284
- // Metric
43285
- ldsMobileInstrumentation.incrementCounter(CREATE_CONTENT_DOCUMENT_AND_VERSION_DRAFT_SYNTHESIZE_ERROR, 1, true, tags);
43286
- // Log
43287
- ldsMobileInstrumentation.error(error, errorCode);
43288
- }
43289
- function reportDraftAwareContentVersionSynthesizeCalls(mimeType) {
43290
- ldsMobileInstrumentation.incrementCounter(CREATE_CONTENT_DOCUMENT_AND_VERSION_TOTAL_SYNTHESIZE_CALLS, 1, undefined, { mimeType });
43291
- }
43292
- /** Priming */
43293
- function reportPrimingSessionCreated() {
43294
- ldsMobileInstrumentation.incrementCounter(PRIMING_TOTAL_SESSION_COUNT, 1, undefined, {});
43295
- }
43296
- function reportPrimingError(errorType, recordCount) {
43297
- ldsMobileInstrumentation.incrementCounter(PRIMING_TOTAL_ERROR_COUNT, recordCount, undefined, {
43298
- errorType,
43299
- });
43300
- }
43301
- function reportPrimingSuccess(recordCount) {
43302
- ldsMobileInstrumentation.incrementCounter(PRIMING_TOTAL_PRIMED_COUNT, recordCount, undefined);
43303
- }
43304
- function reportPrimingConflict(resolutionType, recordCount) {
43305
- ldsMobileInstrumentation.incrementCounter(PRIMING_TOTAL_CONFLICT_COUNT, recordCount, undefined, {
43306
- resolutionType,
43307
- });
43308
- }
43309
- /** Network */
43310
- function reportChunkCandidateUrlLength(urlLength) {
43311
- const buckets = [8000, 10000, 12000, 14000, 16000];
43312
- ldsMobileInstrumentation.bucketValue('chunk-candidate-url-length-histogram', urlLength, buckets);
43313
- }
43314
- /** Garbage Collection */
43315
- function reportAggressiveTrimTriggered(trimCount, beforeTrimRecordCount, totalRecordsTrimmed) {
43316
- ldsMobileInstrumentation.trackValue(GARBAGE_COLLECTION_AGGRESSIVE_TRIM_COUNT, trimCount);
43317
- ldsMobileInstrumentation.trackValue(GARBAGE_COLLECTION_AGGRESSIVE_TRIM_TOTAL_RECORDS_BEFORE_TRIM, beforeTrimRecordCount);
43318
- ldsMobileInstrumentation.trackValue(GARBAGE_COLLECTION_AGGRESSIVE_TRIM_RECORDS_TRIMMED, totalRecordsTrimmed);
43319
- }
43320
- /** One Store Cache Purge */
43321
- const ONESTORE_CACHE_PURGING_RECORDS_PURGED = 'onestore-cache-purging-records-purged';
43322
- const ONESTORE_CACHE_PURGING_RECORDS_ERROR = 'onestore-cache-purging-records-error';
43323
- function reportOneStoreRecordsPurgedFromCache(recordsPurged) {
43324
- ldsMobileInstrumentation.trackValue(ONESTORE_CACHE_PURGING_RECORDS_PURGED, recordsPurged);
43325
- }
43326
- function reportOneStoreCachePurgingError(error) {
43327
- const errorMessage = normalizeError(error);
43328
- ldsMobileInstrumentation.error(errorMessage, ONESTORE_CACHE_PURGING_RECORDS_ERROR);
43329
- }
43330
-
43331
43517
  const QUICK_ACTION_HANDLER = 'QUICK_ACTION_HANDLER';
43332
43518
  class QuickActionExecutionRepresentationHandler extends AbstractQuickActionHandler {
43333
43519
  constructor(getLuvio, draftRecordService, draftQueue, networkAdapter, isDraftId, sideEffectService, objectInfoService, getRecord) {
@@ -58934,7 +59120,7 @@ function buildServiceDescriptor$b(luvio) {
58934
59120
  },
58935
59121
  };
58936
59122
  }
58937
- // version: 1.428.0-dev14-450676496d
59123
+ // version: 1.428.0-dev16-07fd0f90d6
58938
59124
 
58939
59125
  /**
58940
59126
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -58960,7 +59146,7 @@ function buildServiceDescriptor$a(notifyRecordUpdateAvailable, getNormalizedLuvi
58960
59146
  },
58961
59147
  };
58962
59148
  }
58963
- // version: 1.428.0-dev14-450676496d
59149
+ // version: 1.428.0-dev16-07fd0f90d6
58964
59150
 
58965
59151
  /*!
58966
59152
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -61697,4 +61883,4 @@ register({
61697
61883
  });
61698
61884
 
61699
61885
  export { O11Y_NAMESPACE_LDS_MOBILE, getRuntime, ingest$1o as ingestDenormalizedRecordRepresentation, initializeOneStore, registerReportObserver, reportGraphqlQueryParseError };
61700
- // version: 1.428.0-dev14-d04322589b
61886
+ // version: 1.428.0-dev16-22ad12fb68