@salesforce/lds-runtime-one-app 1.462.0 → 1.464.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.
@@ -16,6 +16,7 @@ import { HttpStatusCode as HttpStatusCode$2, InMemoryStore, Environment, Luvio,
16
16
  import usePredictiveLoading from '@salesforce/gate/lds.usePredictiveLoading';
17
17
  import useLocalStorage from '@salesforce/gate/lds.pdl.useLocalStorage';
18
18
  import useOneStoreGraphql from '@salesforce/gate/lds.oneStoreGraphqlEnabled.ltng';
19
+ import usePersistedQueries from '@salesforce/gate/lds.graphqlusepersistedqueries';
19
20
  import canCombineRecordLayoutRequests from '@salesforce/gate/lds.pdl.canCombineRecordLayoutRequests';
20
21
  import { GetApexWireAdapterFactory, registerPrefetcher as registerPrefetcher$1 } from 'force/ldsAdaptersApex';
21
22
  import { instrument, getRecordAvatarsAdapterFactory, getRecordAdapterFactory, coerceFieldIdArray, coerceLayoutTypeArray, coerceLayoutModeArray, getRecordsAdapterFactory, getRecordActionsAdapterFactory, getObjectInfosAdapterFactory, coerceObjectIdArray, getObjectInfoAdapterFactory, coerceObjectId, getRelatedListsActionsAdapterFactory, getRelatedListInfoBatchAdapterFactory, getRelatedListInfoAdapterFactory, getRelatedListRecordsBatchAdapterFactory, getRelatedListRecordsAdapterFactory, getListInfoByNameAdapterFactory, getListInfosByObjectNameAdapterFactory, getListRecordsByNameAdapterFactory, getListObjectInfoAdapterFactory, getRelatedListsInfoAdapterFactory, getRelatedListActionsAdapterFactory, getRecordId18Array, buildRecordRepKeyFromId, configuration, InMemoryRecordRepresentationQueryEvaluator, UiApiNamespace, RecordRepresentationRepresentationType, registerPrefetcher, RecordRepresentationVersion } from 'force/ldsAdaptersUiapi';
@@ -28,7 +29,7 @@ import { dispatchGlobalEvent as dispatchGlobalEvent$1, executeGlobalControllerRa
28
29
  import auraNetworkAdapter, { dispatchAuraAction, defaultActionConfig, instrument as instrument$1, forceRecordTransactionsDisabled as forceRecordTransactionsDisabled$1, ldsNetworkAdapterInstrument, CrudEventState, CrudEventType, UIAPI_RECORDS_PATH, UIAPI_RELATED_LIST_RECORDS_BATCH_PATH, UIAPI_RELATED_LIST_RECORDS_PATH } from 'force/ldsNetwork';
29
30
  import { ThirdPartyTracker } from 'instrumentation:thirdPartyTracker';
30
31
  import { markStart, markEnd, counter, registerCacheStats, perfStart, perfEnd, registerPeriodicLogger, interaction, timer, mark } from 'instrumentation/service';
31
- import { instrument as instrument$2, isCsrfMethod, getFetchMethod, isRetryableMethod, isRetryableReadPost, compilePathMatchers, setupLexNetworkAdapter, UIAPI_PUBLIC_PATHS, UIAPI_PRIVATE_PATHS, isRetryableOperationType } from 'force/ldsNetworkFetch';
32
+ import { instrument as instrument$2, isMutatingMethod, getFetchMethod, isIdempotentWrite, readUiApiErrorCode, isRetryableMethod, isRetryableReadPost, parseRetryAfterMs, compilePathMatchers, setupLexNetworkAdapter, UIAPI_PUBLIC_PATHS, UIAPI_PRIVATE_PATHS, isRetryableOperationType } from 'force/ldsNetworkFetch';
32
33
  import { REFRESH_ADAPTER_EVENT, ADAPTER_UNFULFILLED_ERROR, instrument as instrument$3 } from 'force/ldsBindings';
33
34
  import { stateManagerInstrumentation, OperationTypeValue, LRUCache, instrumentAdapter, instrumentLuvio, logMessage as logMessage$1, setupInstrumentation as setupInstrumentation$1, logObjectInfoChanged as logObjectInfoChanged$1, updatePercentileHistogramMetric, incrementCounterMetric, incrementGetRecordNotifyChangeAllowCount, incrementGetRecordNotifyChangeDropCount, incrementNotifyRecordUpdateAvailableAllowCount, incrementNotifyRecordUpdateAvailableDropCount, setLdsAdaptersUiapiInstrumentation, logError as logError$2, setLdsNetworkAdapterInstrumentation, executeAsyncActivity, METRIC_KEYS, onIdleDetected } from 'force/ldsInstrumentation';
34
35
  import { instrument as instrument$4 } from 'force/adsBridge';
@@ -388,6 +389,15 @@ function buildServiceDescriptor$q() {
388
389
  };
389
390
  }
390
391
 
392
+ function applyOperationRequestOptions$1(init, operationType) {
393
+ if (operationType === void 0) {
394
+ return init;
395
+ }
396
+ return {
397
+ ...init,
398
+ onestore: { ...init?.onestore, retrySafe: operationType === "query" }
399
+ };
400
+ }
391
401
  class AuraNetworkCommand extends NetworkCommand$1 {
392
402
  constructor(services) {
393
403
  super(services);
@@ -406,6 +416,18 @@ class AuraNetworkCommand extends NetworkCommand$1 {
406
416
  "Fetch parameters must be specified when using HTTP transport on an Aura adapter"
407
417
  );
408
418
  }
419
+ /**
420
+ * The command's operation type. Generated command subclasses override this
421
+ * getter with their concrete operation (see the generator's
422
+ * `generateOperationType`); the base returns `undefined` for commands that
423
+ * don't declare one. On the HTTP fallback path (`fetch`) it derives the
424
+ * request's `onestore.retrySafe` — reads (`query`) are retry-safe, writes
425
+ * (`mutation`) are not. Mirrors `FetchNetworkCommand.operationType`, which
426
+ * this class does not inherit (it extends `NetworkCommand` directly).
427
+ */
428
+ get operationType() {
429
+ return void 0;
430
+ }
409
431
  coerceAuraErrors(auraErrors) {
410
432
  return new UserVisibleError(getFetchResponseFromAuraError(auraErrors[0]));
411
433
  }
@@ -507,9 +529,11 @@ class AuraNetworkCommand extends NetworkCommand$1 {
507
529
  this.coerceAuraErrors
508
530
  );
509
531
  } else if (this.shouldUseFetch()) {
510
- const params = this.fetchParams;
532
+ const [input, init] = this.fetchParams;
533
+ const initWithOptions = applyOperationRequestOptions$1(init, this.operationType);
511
534
  try {
512
- return this.convertFetchResponseToData(this.services.fetch(...params));
535
+ const fetchCall = initWithOptions === void 0 ? this.services.fetch(input) : this.services.fetch(input, initWithOptions);
536
+ return this.convertFetchResponseToData(fetchCall);
513
537
  } catch (reason) {
514
538
  return resolvedPromiseLike$2(err$1(toError(reason)));
515
539
  }
@@ -1363,6 +1387,15 @@ class NetworkCommand extends BaseCommand {
1363
1387
  async afterRequestHooks(_options) {
1364
1388
  }
1365
1389
  }
1390
+ function applyOperationRequestOptions(init, operationType) {
1391
+ if (operationType === void 0) {
1392
+ return init;
1393
+ }
1394
+ return {
1395
+ ...init,
1396
+ onestore: { ...init?.onestore, retrySafe: operationType === "query" }
1397
+ };
1398
+ }
1366
1399
  function isAbortableCommand(command) {
1367
1400
  return !!command && typeof command === "object" && "isAbortableCommand" in command && command.isAbortableCommand === true;
1368
1401
  }
@@ -1452,7 +1485,8 @@ const _FetchNetworkCommand = class _FetchNetworkCommand extends NetworkCommand {
1452
1485
  * `generateOperationType`); the base returns `undefined` for commands that
1453
1486
  * don't declare one (e.g. hand-written commands). Transport-layer
1454
1487
  * interceptors use it to tell a read (`query`) apart from a write — see
1455
- * `fetch`, which folds it into the context seed.
1488
+ * `fetch`, which folds it into the context seed and derives
1489
+ * `onestore.retrySafe` from it.
1456
1490
  */
1457
1491
  get operationType() {
1458
1492
  return void 0;
@@ -1461,8 +1495,9 @@ const _FetchNetworkCommand = class _FetchNetworkCommand extends NetworkCommand {
1461
1495
  try {
1462
1496
  const [input, init] = this.fetchParams;
1463
1497
  const operationType = this.operationType;
1498
+ const initWithOptions = applyOperationRequestOptions(init, operationType);
1464
1499
  const seed = operationType === void 0 ? contextSeed : { operationType, ...contextSeed };
1465
- const initWithSeed = seed === void 0 ? init : { ...init, __contextSeed: seed };
1500
+ const initWithSeed = seed === void 0 ? initWithOptions : { ...initWithOptions, __contextSeed: seed };
1466
1501
  const fetchCall = initWithSeed === void 0 ? this.services.fetch(input) : this.services.fetch(input, initWithSeed);
1467
1502
  return this.convertFetchResponseToData(fetchCall);
1468
1503
  } catch (reason) {
@@ -2601,7 +2636,7 @@ function buildServiceDescriptor$d(luvio) {
2601
2636
  },
2602
2637
  };
2603
2638
  }
2604
- // version: 1.462.0-50d9a13ba4
2639
+ // version: 1.464.0-e54c238397
2605
2640
 
2606
2641
  class AuraGraphQLNormalizedCacheControlCommand extends AuraNormalizedCacheControlCommand {
2607
2642
  constructor(config, documentRootType, services) {
@@ -2940,7 +2975,7 @@ function buildServiceDescriptor$9(notifyRecordUpdateAvailable, getNormalizedLuvi
2940
2975
  },
2941
2976
  };
2942
2977
  }
2943
- // version: 1.462.0-50d9a13ba4
2978
+ // version: 1.464.0-e54c238397
2944
2979
 
2945
2980
  class RetryService {
2946
2981
  constructor(defaultRetryPolicy) {
@@ -4412,7 +4447,12 @@ function buildServiceDescriptor$2(interceptors = {
4412
4447
  }
4413
4448
  const fetchArgs = cleanInit === void 0 ? [input] : [input, cleanInit];
4414
4449
  const baseContext = interceptors.createContext?.();
4415
- const context = contextSeed === void 0 ? baseContext : { ...baseContext, ...contextSeed };
4450
+ const onestore = cleanInit?.onestore;
4451
+ const context = contextSeed === void 0 && onestore === void 0 ? baseContext : {
4452
+ ...baseContext,
4453
+ ...contextSeed,
4454
+ ...onestore !== void 0 && { onestore }
4455
+ };
4416
4456
  const {
4417
4457
  request: requestInterceptors = [],
4418
4458
  retry: retryInterceptor = void 0,
@@ -4460,10 +4500,14 @@ function getTextEncoder() {
4460
4500
  }
4461
4501
  return textEncoder;
4462
4502
  }
4463
- function buildCompressionInterceptor(config) {
4464
- const threshold = config.threshold ?? 1024;
4503
+ function buildCompressionInterceptor(defaultConfig) {
4465
4504
  return async (args) => {
4466
4505
  const [resource, options = {}] = args;
4506
+ const config = options.onestore?.compression ?? defaultConfig;
4507
+ if (config === void 0) {
4508
+ return resolvedPromiseLike$2(args);
4509
+ }
4510
+ const threshold = config.threshold ?? 1024;
4467
4511
  if (!options.body || typeof options.body !== "string") {
4468
4512
  return resolvedPromiseLike$2(args);
4469
4513
  }
@@ -5862,7 +5906,7 @@ function getEnvironmentSetting(name) {
5862
5906
  }
5863
5907
  return undefined;
5864
5908
  }
5865
- // version: 1.462.0-2919385b97
5909
+ // version: 1.464.0-b35c0e7723
5866
5910
 
5867
5911
  /**
5868
5912
  * Helpers for reaching the Aura framework from the LDS Aura runtime.
@@ -6277,7 +6321,13 @@ function getOrGenerateErrorId(responseBody, status, statusText) {
6277
6321
  return generateErrorId(context);
6278
6322
  }
6279
6323
  function buildLexRuntime5xxStatusResponseInterceptor(logger) {
6280
- return async (response) => {
6324
+ return async (response, context) => {
6325
+ // The caller opted in (`onestore.handleServerError`, exposed by the fetch service as
6326
+ // `context.onestore`) to receive the 5xx itself, so skip the gack and the throw and
6327
+ // let the response flow to the command's error handling.
6328
+ if (context?.onestore?.handleServerError === true) {
6329
+ return response;
6330
+ }
6281
6331
  if (response.status >= 500 && response.status < 600) {
6282
6332
  const internalLogMessage = `LEX runtime 5xx: ${response.status} ${response.statusText || ''}`.trim();
6283
6333
  logger.warn(internalLogMessage);
@@ -6481,7 +6531,7 @@ function buildCsrfTokenInterceptor() {
6481
6531
  return resolvedPromiseLike$2(fetchArgs);
6482
6532
  }
6483
6533
  // Only add CSRF token for mutating operations
6484
- if (isCsrfMethod(getFetchMethod(fetchArgs))) {
6534
+ if (isMutatingMethod(getFetchMethod(fetchArgs))) {
6485
6535
  const token = await getCsrfToken();
6486
6536
  if (token) {
6487
6537
  // eslint-disable-next-line no-param-reassign
@@ -6509,7 +6559,7 @@ function buildLuvioCsrfTokenInterceptor() {
6509
6559
  resourceRequest.headers = {};
6510
6560
  }
6511
6561
  // Only add CSRF token for mutating operations
6512
- if (isCsrfMethod(resourceRequest.method)) {
6562
+ if (isMutatingMethod(resourceRequest.method)) {
6513
6563
  // Don't overwrite existing CSRF token header if it already exists
6514
6564
  if (!resourceRequest.headers[CSRF_TOKEN_HEADER]) {
6515
6565
  const token = await getCsrfToken();
@@ -6522,6 +6572,53 @@ function buildLuvioCsrfTokenInterceptor() {
6522
6572
  };
6523
6573
  }
6524
6574
 
6575
+ const IDEMPOTENCY_KEY_HEADER = 'Idempotency-Key';
6576
+ // Server-side idempotency error codes (UI SDK / UI-API `ApiExceptionCode`); the
6577
+ // mobile draft-queue handler is the reference consumer.
6578
+ const ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED = 'IDEMPOTENCY_FEATURE_NOT_ENABLED';
6579
+ const ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED = 'IDEMPOTENCY_NOT_SUPPORTED';
6580
+ const ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR = 'IDEMPOTENCY_BACKEND_OPERATION_ERROR';
6581
+ const ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER = 'IDEMPOTENCY_KEY_USED_DIFFERENT_USER';
6582
+ const ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST = 'IDEMPOTENCY_CONCURRENT_REQUEST';
6583
+ const ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED = 'IDEMPOTENCY_KEY_ALREADY_USED';
6584
+ /**
6585
+ * Generates an RFC4122 v4 UUID for use as an idempotency key, preferring
6586
+ * `crypto.randomUUID()` and falling back to a `Math.random()` builder if it is
6587
+ * unavailable (matches the SFAP request-id approach in `network-sfap`).
6588
+ */
6589
+ function mintIdempotencyKey() {
6590
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
6591
+ return crypto.randomUUID();
6592
+ }
6593
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
6594
+ const r = (Math.random() * 16) | 0;
6595
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
6596
+ return v.toString(16);
6597
+ });
6598
+ }
6599
+ /**
6600
+ * Builds a Luvio request interceptor that mints and attaches an `Idempotency-Key`
6601
+ * on mutations to onboarded endpoints, once per logical write, so the retry
6602
+ * service re-sends the same key on every replay and the server can dedupe.
6603
+ *
6604
+ * Minting is gated here; retry *eligibility* is decided separately by
6605
+ * `LuvioIdempotencyRetryPolicy` from the header's presence, so a stale
6606
+ * registry flag can never cause an unsafe replay.
6607
+ */
6608
+ function buildLuvioIdempotencyKeyInterceptor() {
6609
+ return async (resourceRequest) => {
6610
+ if (!resourceRequest.headers) {
6611
+ resourceRequest.headers = {};
6612
+ }
6613
+ if (isMutatingMethod(resourceRequest.method) &&
6614
+ isIdempotentWrite(resourceRequest.basePath) &&
6615
+ !resourceRequest.headers[IDEMPOTENCY_KEY_HEADER]) {
6616
+ resourceRequest.headers[IDEMPOTENCY_KEY_HEADER] = mintIdempotencyKey();
6617
+ }
6618
+ return resolvedPromiseLike$2(resourceRequest);
6619
+ };
6620
+ }
6621
+
6525
6622
  const ENTITY_ENCODING_HEADER = 'X-Chatter-Entity-Encoding';
6526
6623
  const ENTITY_ENCODING_DISABLED_VALUE = 'false';
6527
6624
  /**
@@ -6818,11 +6915,11 @@ function buildLuvioThirdPartyTrackerFinishInterceptor() {
6818
6915
  };
6819
6916
  }
6820
6917
 
6821
- const DEFAULT_CONFIG$3 = {
6918
+ const DEFAULT_CONFIG$4 = {
6822
6919
  maxRetries: 1,
6823
6920
  };
6824
6921
  class LuvioCsrfTokenRetryPolicy extends RetryPolicy {
6825
- constructor(config = DEFAULT_CONFIG$3) {
6922
+ constructor(config = DEFAULT_CONFIG$4) {
6826
6923
  super();
6827
6924
  this.config = config;
6828
6925
  }
@@ -6866,21 +6963,12 @@ class LuvioCsrfTokenRetryPolicy extends RetryPolicy {
6866
6963
  this.requestContext.request = { ...req, headers };
6867
6964
  }
6868
6965
  }
6869
- /**
6870
- * Returns true when a Luvio FetchResponse indicates a CSRF token error.
6871
- * Body is already parsed JSON on FetchResponse, so no clone/json() needed.
6872
- *
6873
- * UIAPI error bodies come in both shapes: an object `{ errorCode, message }`
6874
- * for single errors, or an array `[{ errorCode, message }]` for endpoints that
6875
- * return collections of errors. Handle both.
6876
- */
6966
+ /** True when a Luvio FetchResponse indicates a CSRF token error. */
6877
6967
  function isCsrfError$1(response) {
6878
- const body = response.body;
6879
- const errorCode = Array.isArray(body) ? body[0]?.errorCode : body?.errorCode;
6880
- return errorCode === 'INVALID_ACCESS_TOKEN';
6968
+ return readUiApiErrorCode(response) === 'INVALID_ACCESS_TOKEN';
6881
6969
  }
6882
6970
 
6883
- const DEFAULT_CONFIG$2 = {
6971
+ const DEFAULT_CONFIG$3 = {
6884
6972
  maxRetries: 3,
6885
6973
  maxTimeToRetry: 10000,
6886
6974
  baseDelay: 250,
@@ -6894,7 +6982,7 @@ class LuvioFetchThrottlingRetryPolicy extends RetryPolicy {
6894
6982
  // is why we take the plain `ResourceRequest` rather than the mutable-container
6895
6983
  // pattern the CSRF policy uses — that policy rewrites the request (new token)
6896
6984
  // between attempts; this one never mutates it.
6897
- constructor(config = DEFAULT_CONFIG$2, request) {
6985
+ constructor(config = DEFAULT_CONFIG$3, request) {
6898
6986
  super();
6899
6987
  this.config = config;
6900
6988
  this.request = request;
@@ -6922,57 +7010,157 @@ class LuvioFetchThrottlingRetryPolicy extends RetryPolicy {
6922
7010
  return Math.max(0, delay + jitter);
6923
7011
  }
6924
7012
  parseRetryAfterHeader(result) {
6925
- const headers = result.headers;
6926
- if (!headers) {
6927
- return undefined;
6928
- }
6929
- // FetchResponse headers is a plain { [key: string]: string } object
6930
- // (no Headers.get available), so do a case-insensitive lookup manually.
6931
- let value;
6932
- for (const key in headers) {
6933
- if (key.toLowerCase() === 'retry-after') {
6934
- value = headers[key];
6935
- break;
7013
+ return parseRetryAfterMs(result.headers);
7014
+ }
7015
+ }
7016
+
7017
+ const DEFAULT_CONFIG$2 = {
7018
+ maxRetries: 3,
7019
+ };
7020
+ // The idempotency handler never emits 429 (that's throttling's domain). Its 503
7021
+ // is a keyed-write lock-acquire failure (scenario 7), not throttling's keyless
7022
+ // GET/read-POST 503 — the two never contend (see classifyResponse).
7023
+ const HTTP_STATUS_BAD_REQUEST = 400;
7024
+ const HTTP_STATUS_REQUEST_TIMEOUT = 408;
7025
+ const HTTP_STATUS_CONFLICT = 409;
7026
+ const HTTP_STATUS_UNPROCESSABLE_ENTITY = 422;
7027
+ const HTTP_STATUS_SERVER_ERROR = 500;
7028
+ const HTTP_STATUS_SERVICE_UNAVAILABLE = 503;
7029
+ // Terminal cases (IDEMPOTENCY_AUTHENTICATION_FAILED on 401, INVALID_IDEMPOTENCY_KEY
7030
+ // on 400) are deliberately absent so they fall through to 'none'.
7031
+ const STRIP_ERROR_CODES = [
7032
+ ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED,
7033
+ ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED,
7034
+ ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR,
7035
+ ];
7036
+ const ROTATE_ERROR_CODES = [
7037
+ ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER,
7038
+ ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED,
7039
+ ];
7040
+ /**
7041
+ * Maps an idempotent-write response to its disposition. Status alone is
7042
+ * ambiguous (e.g. 409 is same-key for a concurrent request but rotate for a
7043
+ * different user), so pair it with the body's errorCode.
7044
+ */
7045
+ function classifyResponse(response) {
7046
+ const errorCode = readUiApiErrorCode(response);
7047
+ // FetchResponse.status is typed as HttpStatusCode, which omits 408/409/422
7048
+ // even though the idempotency handler emits them; widen to match.
7049
+ const status = response.status;
7050
+ switch (status) {
7051
+ case HTTP_STATUS_SERVER_ERROR:
7052
+ return errorCode === ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR ? 'strip' : 'none';
7053
+ case HTTP_STATUS_BAD_REQUEST:
7054
+ // FEATURE_NOT_ENABLED / NOT_SUPPORTED → strip. Any other 400 (incl.
7055
+ // INVALID_IDEMPOTENCY_KEY and CSRF's INVALID_ACCESS_TOKEN) is terminal.
7056
+ return errorCode !== undefined && STRIP_ERROR_CODES.includes(errorCode)
7057
+ ? 'strip'
7058
+ : 'none';
7059
+ case HTTP_STATUS_REQUEST_TIMEOUT:
7060
+ // Backend connection timeout — the write may have already committed;
7061
+ // replay same-key so the server can dedupe.
7062
+ return 'same-key';
7063
+ case HTTP_STATUS_SERVICE_UNAVAILABLE:
7064
+ // Lock-acquire failure (scenario 7): the write never committed. Only a
7065
+ // keyed write reaches here (shouldRetry gates on key presence) — a
7066
+ // keyless 503 is the throttling policy's GET/read-POST case.
7067
+ return 'same-key';
7068
+ case HTTP_STATUS_CONFLICT:
7069
+ // KEY_USED_DIFFERENT_USER → rotate; CONCURRENT_REQUEST → same key.
7070
+ if (errorCode !== undefined && ROTATE_ERROR_CODES.includes(errorCode)) {
7071
+ return 'rotate';
6936
7072
  }
7073
+ return errorCode === ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST ? 'same-key' : 'none';
7074
+ case HTTP_STATUS_UNPROCESSABLE_ENTITY:
7075
+ // Same key reused for a different request body → needs a new key.
7076
+ return errorCode === ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED ? 'rotate' : 'none';
7077
+ default:
7078
+ return 'none';
7079
+ }
7080
+ }
7081
+ /**
7082
+ * Retry policy for writes carrying an `Idempotency-Key`. A peer of the CSRF and
7083
+ * throttling policies in the composed retry chain, not a modification of
7084
+ * either: its 503 (keyed-write lock-acquire) and throttling's 503 (keyless
7085
+ * GET/read-POST) never contend because they key off different requests, and it
7086
+ * only claims a 400/409 when the body carries an idempotency-specific
7087
+ * errorCode, so it never steals CSRF's INVALID_ACCESS_TOKEN.
7088
+ *
7089
+ * Eligibility follows from the header's *presence* on the request, not from
7090
+ * the mint interceptor's registry flag — so a stale flag can never cause an
7091
+ * unsafe replay.
7092
+ */
7093
+ class LuvioIdempotencyRetryPolicy extends RetryPolicy {
7094
+ constructor(config = DEFAULT_CONFIG$2) {
7095
+ super();
7096
+ this.config = config;
7097
+ }
7098
+ setRequestContext(context) {
7099
+ this.requestContext = context;
7100
+ }
7101
+ async shouldRetry(result, context) {
7102
+ if (context.attempt >= this.config.maxRetries) {
7103
+ return false;
6937
7104
  }
6938
- if (!value) {
6939
- return undefined;
7105
+ const headers = this.requestContext?.request.headers;
7106
+ if (!headers || !headers[IDEMPOTENCY_KEY_HEADER]) {
7107
+ return false;
6940
7108
  }
6941
- const trimmed = value.trim();
6942
- if (/^\d+$/.test(trimmed)) {
6943
- const seconds = Number(trimmed);
6944
- if (Number.isFinite(seconds) && seconds >= 0) {
6945
- return seconds * 1000;
6946
- }
6947
- return undefined;
7109
+ return classifyResponse(result) !== 'none';
7110
+ }
7111
+ async calculateDelay(result, _context) {
7112
+ // Only same-key replays (408/409/503) carry a Retry-After to honor;
7113
+ // strip/rotate retry immediately.
7114
+ if (classifyResponse(result) !== 'same-key') {
7115
+ return 0;
6948
7116
  }
6949
- const IMF_FIXDATE = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT$/;
6950
- if (!IMF_FIXDATE.test(trimmed)) {
6951
- return undefined;
7117
+ // Honor the server's Retry-After in full, like the mobile handler — it's
7118
+ // a lock-hold hint, not a client-owned backoff (unlike the throttling
7119
+ // sibling's capped delay). maxRetries bounds replays instead.
7120
+ return parseRetryAfterMs(result.headers) ?? 0;
7121
+ }
7122
+ async prepareRetry(result, _context) {
7123
+ if (!this.requestContext) {
7124
+ return;
6952
7125
  }
6953
- const millis = Date.parse(trimmed);
6954
- if (Number.isFinite(millis)) {
6955
- return Math.max(0, millis - Date.now());
7126
+ const disposition = classifyResponse(result);
7127
+ if (disposition === 'same-key' || disposition === 'none') {
7128
+ return;
6956
7129
  }
6957
- return undefined;
7130
+ const req = this.requestContext.request;
7131
+ // Drop the existing key via immutable spread (matches the CSRF policy).
7132
+ const { [IDEMPOTENCY_KEY_HEADER]: _stale, ...remainingHeaders } = req.headers ?? {};
7133
+ const headers = disposition === 'rotate'
7134
+ ? { ...remainingHeaders, [IDEMPOTENCY_KEY_HEADER]: mintIdempotencyKey() }
7135
+ : // 'strip': retry plain — the feature is unavailable for this request.
7136
+ remainingHeaders;
7137
+ this.requestContext.request = { ...req, headers };
6958
7138
  }
6959
7139
  }
6960
7140
 
6961
7141
  /**
6962
- * Builds the LuvioRetryInterceptor used by the UIAPI fetch adapter.
7142
+ * Builds the LuvioRetryInterceptor used by the UIAPI fetch adapter. A fresh
7143
+ * RetryService and policies are created per request to keep the
7144
+ * mutableRequest context isolated across concurrent requests.
6963
7145
  *
6964
- * A fresh RetryService and policy instances are created per request to keep the
6965
- * CSRF policy's mutableRequest context isolated from concurrent requests.
7146
+ * Policy order matters: ComposedRetryPolicy uses the first matching policy's
7147
+ * shouldRetry/delay. A keyed-write 503 fails throttling's method gate, so it
7148
+ * falls to the idempotency policy to replay same-key.
6966
7149
  */
6967
7150
  function buildLuvioFetchRetryInterceptor() {
6968
7151
  return (request, doFetch) => {
7152
+ const idempotencyPolicy = new LuvioIdempotencyRetryPolicy();
6969
7153
  const csrfPolicy = new LuvioCsrfTokenRetryPolicy();
6970
7154
  const composedPolicy = new ComposedRetryPolicy([
7155
+ idempotencyPolicy,
6971
7156
  new LuvioFetchThrottlingRetryPolicy(undefined, request),
6972
7157
  csrfPolicy,
6973
7158
  ]);
6974
7159
  const retryService = new RetryService(composedPolicy);
6975
7160
  const mutableRequest = { request };
7161
+ // Both policies rewrite headers on the shared request between attempts
7162
+ // (idempotency strips/rotates the key; CSRF refreshes the token).
7163
+ idempotencyPolicy.setRequestContext(mutableRequest);
6976
7164
  csrfPolicy.setRequestContext(mutableRequest);
6977
7165
  return retryService.applyRetry(() => doFetch(mutableRequest.request));
6978
7166
  };
@@ -7184,6 +7372,7 @@ const composedFetchNetworkAdapter = {
7184
7372
  buildLuvioTransportMarksSendInterceptor(),
7185
7373
  buildLuvioPageScopedCacheRequestInterceptor(),
7186
7374
  buildLuvioCsrfTokenInterceptor(),
7375
+ buildLuvioIdempotencyKeyInterceptor(),
7187
7376
  buildLuvioFirstPartyHeaderInterceptor(),
7188
7377
  buildLuvioEntityEncodingInterceptor(),
7189
7378
  ],
@@ -7453,15 +7642,21 @@ function buildFetchRetryInterceptor() {
7453
7642
  }
7454
7643
 
7455
7644
  /**
7456
- * The context-seed key under which a custom command forwards its JWT mint
7645
+ * Legacy context-seed key under which a custom command forwards its JWT mint
7457
7646
  * parameters. The framework's `buildServiceDescriptor` merges the request init's
7458
7647
  * `__contextSeed` onto the per-request interceptor context, so this interceptor
7459
- * reads the params off `context[JWT_MINT_PARAMS_SEED_KEY]`.
7648
+ * can read the params off `context[JWT_MINT_PARAMS_SEED_KEY]`.
7649
+ *
7650
+ * **Transitional.** The first-class channel is now the typed
7651
+ * `init.onestore.mintParams` field (onestore 3.29.0), which this interceptor
7652
+ * prefers. This seed key remains only as a fallback while the core
7653
+ * `onestore-custom-adapters` producers still write `__contextSeed`; it — together
7654
+ * with the `context` fallback and the interceptor's `context` parameter — is
7655
+ * removed once those producers emit `onestore.mintParams`. That removal is the
7656
+ * follow-up PR #8314 (tracked under W-24472492).
7460
7657
  *
7461
- * This is a cross-team contract: the custom command writes this key, this
7462
- * interceptor reads it. It is intentionally an opaque key — OneStore does not
7463
- * define it and never inspects the param shape; the typed shape lives in the
7464
- * resolver.
7658
+ * It is intentionally an opaque key — OneStore does not define it and never
7659
+ * inspects the param shape; the typed shape lives in the resolver.
7465
7660
  */
7466
7661
  const JWT_MINT_PARAMS_SEED_KEY = 'jwtMintParams';
7467
7662
  /**
@@ -7492,15 +7687,17 @@ function hasAuthorizationHeader([resource, options]) {
7492
7687
  * Builds the request interceptor that bridges the per-request context seed to the
7493
7688
  * dispatching SFAP JwtManager for the **parameterized** mint path.
7494
7689
  *
7495
- * For a parameterized SFAP command, the context carries `jwtMintParams`. This
7496
- * interceptor reads them, calls `jwtManager.getJwt(params)` (which the dispatching
7497
- * resolver routes to the parameterized resolver), attaches the `Authorization`
7498
- * header, and rewrites the request URL via the minted `baseUri`. The presence of
7499
- * that `Authorization` header is the signal the descriptor's guarded legacy
7500
- * interceptor uses to skip — so the legacy path does not mint a second time.
7690
+ * For a parameterized SFAP command, the mint params arrive on the typed
7691
+ * `init.onestore.mintParams` field (preferred) or, transitionally, under the
7692
+ * legacy `context[jwtMintParams]` seed. This interceptor reads them (onestore
7693
+ * first), calls `jwtManager.getJwt(params)` (which the dispatching resolver routes
7694
+ * to the parameterized resolver), attaches the `Authorization` header, and
7695
+ * rewrites the request URL via the minted `baseUri`. The presence of that
7696
+ * `Authorization` header is the signal the descriptor's guarded legacy interceptor
7697
+ * uses to skip — so the legacy path does not mint a second time.
7501
7698
  *
7502
- * For a legacy parameterless command the context carries no `jwtMintParams`, so
7503
- * this interceptor **early-returns on its first line** and the request flows
7699
+ * For a legacy parameterless command neither channel carries mint params, so this
7700
+ * interceptor **early-returns on its first statement** and the request flows
7504
7701
  * unchanged to the legacy `buildJwtRequestHeaderInterceptor` — guaranteeing zero
7505
7702
  * behavior change for non-opted-in adapters.
7506
7703
  *
@@ -7514,8 +7711,11 @@ function hasAuthorizationHeader([resource, options]) {
7514
7711
  */
7515
7712
  function buildJwtParameterizationInterceptor(jwtManager, jwtRequestModifier = (_extraInfo, fetchArgs) => fetchArgs) {
7516
7713
  return (fetchArgs, context) => {
7517
- const jwtContext = context;
7518
- const mintParams = jwtContext?.[JWT_MINT_PARAMS_SEED_KEY];
7714
+ // Prefer the typed `init.onestore.mintParams`; fall back to the legacy
7715
+ // `context[JWT_MINT_PARAMS_SEED_KEY]` seed while the core producers still
7716
+ // write it (transitional dual-read — see JWT_MINT_PARAMS_SEED_KEY).
7717
+ const mintParams = fetchArgs[1]?.onestore?.mintParams ??
7718
+ context?.[JWT_MINT_PARAMS_SEED_KEY];
7519
7719
  // No mint params → not a parameterized request. Do nothing; the legacy
7520
7720
  // interceptor handles it. This MUST be the first statement so non-opted-in
7521
7721
  // SFAP commands observe no work at all.
@@ -7610,7 +7810,8 @@ function buildJwtAuthorizedSfapFetchServiceDescriptor(logger) {
7610
7810
  *
7611
7811
  * It reuses the module's `sfapJwtManager`: `cdp_url` is emitted on the same
7612
7812
  * `SFAP_API`-scope JWT, so the Data360 command mints the same kind of token — it
7613
- * just forwards the data-cloud scopes via the context-seed `jwtMintParams`.
7813
+ * just forwards the data-cloud scopes via `onestore.mintParams` (or, transitionally,
7814
+ * the legacy context-seed `jwtMintParams`).
7614
7815
  *
7615
7816
  * Gated behind its own `data_360_cdp_url_rewrite` auth-scope tag so it binds ONLY to
7616
7817
  * the Data360 command — the SFAP fleet never flows past this interceptor.
@@ -7741,10 +7942,11 @@ function buildJwtRequestInterceptor(logger) {
7741
7942
  * (`cdp_url`), and modifiers only receive `extraInfo`. So — like the copilot
7742
7943
  * interceptor that reads `decodedInfo.iss` — it holds the token directly:
7743
7944
  *
7744
- * 1. Read the command's `jwtMintParams` off the per-request context seed and
7745
- * mint (via the parameterized SFAP resolver, routed by the dispatching
7746
- * resolver inside `sfapJwtManager`). Without mint params there is no token
7747
- * to authorize with, so leave the request untouched.
7945
+ * 1. Read the command's mint params from `init.onestore.mintParams` (preferred)
7946
+ * or the legacy per-request context seed, and mint (via the parameterized
7947
+ * SFAP resolver, routed by the dispatching resolver inside `sfapJwtManager`).
7948
+ * Without mint params there is no token to authorize with, so leave the
7949
+ * request untouched.
7748
7950
  * 2. Decode `cdp_url` from the JWT and resolve the tenant-specific endpoint
7749
7951
  * (TSE). `cdp_url` is a bare host, so a scheme is prepended before parsing
7750
7952
  * and the protocol is forced to `https:`.
@@ -7763,7 +7965,11 @@ function buildJwtRequestInterceptor(logger) {
7763
7965
  */
7764
7966
  function buildData360HostRewriteInterceptor(logger) {
7765
7967
  return (fetchArgs, context) => {
7766
- const mintParams = context?.[JWT_MINT_PARAMS_SEED_KEY];
7968
+ // Prefer the typed `init.onestore.mintParams`; fall back to the legacy
7969
+ // `context[JWT_MINT_PARAMS_SEED_KEY]` seed while the core producers still
7970
+ // write it (transitional dual-read — see JWT_MINT_PARAMS_SEED_KEY).
7971
+ const mintParams = fetchArgs[1]?.onestore?.mintParams ??
7972
+ context?.[JWT_MINT_PARAMS_SEED_KEY];
7767
7973
  // No mint params → not a parameterized Data360 request. Nothing to do.
7768
7974
  if (mintParams === undefined) {
7769
7975
  return resolvedPromiseLike$2(fetchArgs);
@@ -10478,6 +10684,10 @@ function getLexRuntimeDefaultInterceptorConfig(logger) {
10478
10684
  buildCsrfTokenInterceptor(),
10479
10685
  buildEntityEncodingInterceptor(),
10480
10686
  buildFirstPartyHeaderInterceptor(),
10687
+ // Per-request gzip: compresses only operations that opt in via
10688
+ // `init.onestore.compression`; a pass-through no-op otherwise. Last in
10689
+ // the chain so it compresses the final body, after body-modifying interceptors.
10690
+ buildCompressionInterceptor(),
10481
10691
  ],
10482
10692
  retry: buildFetchRetryInterceptor(),
10483
10693
  response: [
@@ -11319,6 +11529,8 @@ function initializeOneStore(luvio) {
11319
11529
  // across LEX and sites. Paired with the HTTP UIAPI fetch path being
11320
11530
  // disabled on sites (see network-fetch.ts).
11321
11531
  featureFlagsService.set('graphQLUseAura', true);
11532
+ // Persisted (named) GraphQL queries; no consumers yet (W-23971639).
11533
+ featureFlagsService.set('graphQLUsePersistedQueries', usePersistedQueries.isOpen({ fallback: false }));
11322
11534
  const services = [
11323
11535
  instrumentationServiceDescriptor,
11324
11536
  buildLexRuntimeDefaultFetchServiceDescriptor(loggerService, retryService),
@@ -11379,4 +11591,4 @@ function ldsEngineCreator() {
11379
11591
  }
11380
11592
 
11381
11593
  export { LexRequestStrategy, PDL_ENGINE_REGISTRATION_ID, PdlPrefetcherEventType, PdlRequestPriority, buildPredictorForContext, configService, ldsEngineCreator as default, initializeLDS, initializeOneStore, notifyUpdateAvailableFactory, registerRequestStrategy, saveRequestAsPrediction, subscribeToPrefetcherEvents, unregisterRequestStrategy, whenPredictionsReady };
11382
- // version: 1.462.0-50d9a13ba4
11594
+ // version: 1.464.0-e54c238397
@@ -20,7 +20,8 @@ export declare function buildJwtAuthorizedSfapFetchServiceDescriptor(logger: Log
20
20
  *
21
21
  * It reuses the module's `sfapJwtManager`: `cdp_url` is emitted on the same
22
22
  * `SFAP_API`-scope JWT, so the Data360 command mints the same kind of token — it
23
- * just forwards the data-cloud scopes via the context-seed `jwtMintParams`.
23
+ * just forwards the data-cloud scopes via `onestore.mintParams` (or, transitionally,
24
+ * the legacy context-seed `jwtMintParams`).
24
25
  *
25
26
  * Gated behind its own `data_360_cdp_url_rewrite` auth-scope tag so it binds ONLY to
26
27
  * the Data360 command — the SFAP fleet never flows past this interceptor.
@@ -0,0 +1,24 @@
1
+ import type { ResourceRequest } from '@luvio/engine';
2
+ export declare const IDEMPOTENCY_KEY_HEADER = "Idempotency-Key";
3
+ export declare const ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED = "IDEMPOTENCY_FEATURE_NOT_ENABLED";
4
+ export declare const ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED = "IDEMPOTENCY_NOT_SUPPORTED";
5
+ export declare const ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR = "IDEMPOTENCY_BACKEND_OPERATION_ERROR";
6
+ export declare const ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER = "IDEMPOTENCY_KEY_USED_DIFFERENT_USER";
7
+ export declare const ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST = "IDEMPOTENCY_CONCURRENT_REQUEST";
8
+ export declare const ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED = "IDEMPOTENCY_KEY_ALREADY_USED";
9
+ /**
10
+ * Generates an RFC4122 v4 UUID for use as an idempotency key, preferring
11
+ * `crypto.randomUUID()` and falling back to a `Math.random()` builder if it is
12
+ * unavailable (matches the SFAP request-id approach in `network-sfap`).
13
+ */
14
+ export declare function mintIdempotencyKey(): string;
15
+ /**
16
+ * Builds a Luvio request interceptor that mints and attaches an `Idempotency-Key`
17
+ * on mutations to onboarded endpoints, once per logical write, so the retry
18
+ * service re-sends the same key on every replay and the server can dedupe.
19
+ *
20
+ * Minting is gated here; retry *eligibility* is decided separately by
21
+ * `LuvioIdempotencyRetryPolicy` from the header's presence, so a stale
22
+ * registry flag can never cause an unsafe replay.
23
+ */
24
+ export declare function buildLuvioIdempotencyKeyInterceptor(): (resourceRequest: ResourceRequest) => Promise<ResourceRequest>;
@@ -3,15 +3,21 @@ import { type JwtRequestModifier } from '@conduit-client/service-fetch-network/v
3
3
  import type { JwtManager } from '@conduit-client/jwt-manager';
4
4
  import type { ExtraInfo } from '../network-sfap';
5
5
  /**
6
- * The context-seed key under which a custom command forwards its JWT mint
6
+ * Legacy context-seed key under which a custom command forwards its JWT mint
7
7
  * parameters. The framework's `buildServiceDescriptor` merges the request init's
8
8
  * `__contextSeed` onto the per-request interceptor context, so this interceptor
9
- * reads the params off `context[JWT_MINT_PARAMS_SEED_KEY]`.
9
+ * can read the params off `context[JWT_MINT_PARAMS_SEED_KEY]`.
10
10
  *
11
- * This is a cross-team contract: the custom command writes this key, this
12
- * interceptor reads it. It is intentionally an opaque key — OneStore does not
13
- * define it and never inspects the param shape; the typed shape lives in the
14
- * resolver.
11
+ * **Transitional.** The first-class channel is now the typed
12
+ * `init.onestore.mintParams` field (onestore 3.29.0), which this interceptor
13
+ * prefers. This seed key remains only as a fallback while the core
14
+ * `onestore-custom-adapters` producers still write `__contextSeed`; it — together
15
+ * with the `context` fallback and the interceptor's `context` parameter — is
16
+ * removed once those producers emit `onestore.mintParams`. That removal is the
17
+ * follow-up PR #8314 (tracked under W-24472492).
18
+ *
19
+ * It is intentionally an opaque key — OneStore does not define it and never
20
+ * inspects the param shape; the typed shape lives in the resolver.
15
21
  */
16
22
  export declare const JWT_MINT_PARAMS_SEED_KEY = "jwtMintParams";
17
23
  /**
@@ -27,15 +33,17 @@ export declare function hasAuthorizationHeader([resource, options]: FetchParamet
27
33
  * Builds the request interceptor that bridges the per-request context seed to the
28
34
  * dispatching SFAP JwtManager for the **parameterized** mint path.
29
35
  *
30
- * For a parameterized SFAP command, the context carries `jwtMintParams`. This
31
- * interceptor reads them, calls `jwtManager.getJwt(params)` (which the dispatching
32
- * resolver routes to the parameterized resolver), attaches the `Authorization`
33
- * header, and rewrites the request URL via the minted `baseUri`. The presence of
34
- * that `Authorization` header is the signal the descriptor's guarded legacy
35
- * interceptor uses to skip — so the legacy path does not mint a second time.
36
+ * For a parameterized SFAP command, the mint params arrive on the typed
37
+ * `init.onestore.mintParams` field (preferred) or, transitionally, under the
38
+ * legacy `context[jwtMintParams]` seed. This interceptor reads them (onestore
39
+ * first), calls `jwtManager.getJwt(params)` (which the dispatching resolver routes
40
+ * to the parameterized resolver), attaches the `Authorization` header, and
41
+ * rewrites the request URL via the minted `baseUri`. The presence of that
42
+ * `Authorization` header is the signal the descriptor's guarded legacy interceptor
43
+ * uses to skip — so the legacy path does not mint a second time.
36
44
  *
37
- * For a legacy parameterless command the context carries no `jwtMintParams`, so
38
- * this interceptor **early-returns on its first line** and the request flows
45
+ * For a legacy parameterless command neither channel carries mint params, so this
46
+ * interceptor **early-returns on its first statement** and the request flows
39
47
  * unchanged to the legacy `buildJwtRequestHeaderInterceptor` — guaranteeing zero
40
48
  * behavior change for non-opted-in adapters.
41
49
  *
@@ -1,8 +1,11 @@
1
1
  import type { LuvioRetryInterceptor } from '@salesforce/lds-network-fetch';
2
2
  /**
3
- * Builds the LuvioRetryInterceptor used by the UIAPI fetch adapter.
3
+ * Builds the LuvioRetryInterceptor used by the UIAPI fetch adapter. A fresh
4
+ * RetryService and policies are created per request to keep the
5
+ * mutableRequest context isolated across concurrent requests.
4
6
  *
5
- * A fresh RetryService and policy instances are created per request to keep the
6
- * CSRF policy's mutableRequest context isolated from concurrent requests.
7
+ * Policy order matters: ComposedRetryPolicy uses the first matching policy's
8
+ * shouldRetry/delay. A keyed-write 503 fails throttling's method gate, so it
9
+ * falls to the idempotency policy to replay same-key.
7
10
  */
8
11
  export declare function buildLuvioFetchRetryInterceptor(): LuvioRetryInterceptor;
@@ -19,13 +19,6 @@ export declare class LuvioCsrfTokenRetryPolicy extends RetryPolicy<FetchResponse
19
19
  calculateDelay(_result: FetchResponse<any>, _context: RetryContext<FetchResponse<any>>): Promise<number>;
20
20
  prepareRetry(_result: FetchResponse<any>, _context: RetryContext<FetchResponse<any>>): Promise<void>;
21
21
  }
22
- /**
23
- * Returns true when a Luvio FetchResponse indicates a CSRF token error.
24
- * Body is already parsed JSON on FetchResponse, so no clone/json() needed.
25
- *
26
- * UIAPI error bodies come in both shapes: an object `{ errorCode, message }`
27
- * for single errors, or an array `[{ errorCode, message }]` for endpoints that
28
- * return collections of errors. Handle both.
29
- */
22
+ /** True when a Luvio FetchResponse indicates a CSRF token error. */
30
23
  export declare function isCsrfError(response: FetchResponse<any>): boolean;
31
24
  export {};
@@ -0,0 +1,29 @@
1
+ import type { RetryContext } from '@conduit-client/service-retry/v1';
2
+ import { RetryPolicy } from '@conduit-client/service-retry/v1';
3
+ import type { FetchResponse } from '@luvio/engine';
4
+ import type { MutableLuvioRequest } from './luvio-csrf-token-retry-policy';
5
+ type LuvioIdempotencyRetryPolicyConfig = {
6
+ maxRetries: number;
7
+ };
8
+ /**
9
+ * Retry policy for writes carrying an `Idempotency-Key`. A peer of the CSRF and
10
+ * throttling policies in the composed retry chain, not a modification of
11
+ * either: its 503 (keyed-write lock-acquire) and throttling's 503 (keyless
12
+ * GET/read-POST) never contend because they key off different requests, and it
13
+ * only claims a 400/409 when the body carries an idempotency-specific
14
+ * errorCode, so it never steals CSRF's INVALID_ACCESS_TOKEN.
15
+ *
16
+ * Eligibility follows from the header's *presence* on the request, not from
17
+ * the mint interceptor's registry flag — so a stale flag can never cause an
18
+ * unsafe replay.
19
+ */
20
+ export declare class LuvioIdempotencyRetryPolicy extends RetryPolicy<FetchResponse<any>> {
21
+ private config;
22
+ private requestContext?;
23
+ constructor(config?: LuvioIdempotencyRetryPolicyConfig);
24
+ setRequestContext(context: MutableLuvioRequest): void;
25
+ shouldRetry(result: FetchResponse<any>, context: RetryContext<FetchResponse<any>>): Promise<boolean>;
26
+ calculateDelay(result: FetchResponse<any>, _context: RetryContext<FetchResponse<any>>): Promise<number>;
27
+ prepareRetry(result: FetchResponse<any>, _context: RetryContext<FetchResponse<any>>): Promise<void>;
28
+ }
29
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/lds-runtime-one-app",
3
- "version": "1.462.0",
3
+ "version": "1.464.0",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "description": "LDS engine for the one.app (LEX) runtime.",
6
6
  "main": "dist/oneAppLdsEngineCreator.js",
@@ -34,53 +34,53 @@
34
34
  "release:corejar": "yarn build && ../core-build/scripts/core.js --name=lds-runtime-one-app"
35
35
  },
36
36
  "devDependencies": {
37
- "@conduit-client/service-provisioner": "3.29.0",
38
- "@conduit-client/tools-core": "3.29.0",
39
- "@salesforce/lds-adapters-apex": "^1.462.0",
40
- "@salesforce/lds-adapters-uiapi": "^1.462.0",
41
- "@salesforce/lds-ads-bridge": "^1.462.0",
42
- "@salesforce/lds-aura-storage": "^1.462.0",
43
- "@salesforce/lds-bindings": "^1.462.0",
44
- "@salesforce/lds-instrumentation": "^1.462.0",
45
- "@salesforce/lds-network-adapter": "^1.462.0",
46
- "@salesforce/lds-network-aura": "^1.462.0",
47
- "@salesforce/lds-network-fetch": "^1.462.0",
37
+ "@conduit-client/service-provisioner": "3.32.0",
38
+ "@conduit-client/tools-core": "3.32.0",
39
+ "@salesforce/lds-adapters-apex": "^1.464.0",
40
+ "@salesforce/lds-adapters-uiapi": "^1.464.0",
41
+ "@salesforce/lds-ads-bridge": "^1.464.0",
42
+ "@salesforce/lds-aura-storage": "^1.464.0",
43
+ "@salesforce/lds-bindings": "^1.464.0",
44
+ "@salesforce/lds-instrumentation": "^1.464.0",
45
+ "@salesforce/lds-network-adapter": "^1.464.0",
46
+ "@salesforce/lds-network-aura": "^1.464.0",
47
+ "@salesforce/lds-network-fetch": "^1.464.0",
48
48
  "jwt-encode": "1.0.1"
49
49
  },
50
50
  "dependencies": {
51
- "@conduit-client/command-aura-graphql-normalized-cache-control": "3.29.0",
52
- "@conduit-client/command-aura-network": "3.29.0",
53
- "@conduit-client/command-aura-normalized-cache-control": "3.29.0",
54
- "@conduit-client/command-fetch-network": "3.29.0",
55
- "@conduit-client/command-http-graphql-normalized-cache-control": "3.29.0",
56
- "@conduit-client/command-http-normalized-cache-control": "3.29.0",
57
- "@conduit-client/command-ndjson": "3.29.0",
58
- "@conduit-client/command-network": "3.29.0",
59
- "@conduit-client/command-sse": "3.29.0",
60
- "@conduit-client/command-streaming": "3.29.0",
61
- "@conduit-client/jwt-manager": "3.29.0",
62
- "@conduit-client/service-aura-network": "3.29.0",
63
- "@conduit-client/service-bindings-imperative": "3.29.0",
64
- "@conduit-client/service-bindings-lwc": "3.29.0",
65
- "@conduit-client/service-cache": "3.29.0",
66
- "@conduit-client/service-cache-control": "3.29.0",
67
- "@conduit-client/service-cache-inclusion-policy": "3.29.0",
68
- "@conduit-client/service-config": "3.29.0",
69
- "@conduit-client/service-feature-flags": "3.29.0",
70
- "@conduit-client/service-fetch-network": "3.29.0",
71
- "@conduit-client/service-instrument-command": "3.29.0",
72
- "@conduit-client/service-pubsub": "3.29.0",
73
- "@conduit-client/service-renewable-resource-manager": "3.29.0",
74
- "@conduit-client/service-store": "3.29.0",
75
- "@conduit-client/utils": "3.29.0",
51
+ "@conduit-client/command-aura-graphql-normalized-cache-control": "3.32.0",
52
+ "@conduit-client/command-aura-network": "3.32.0",
53
+ "@conduit-client/command-aura-normalized-cache-control": "3.32.0",
54
+ "@conduit-client/command-fetch-network": "3.32.0",
55
+ "@conduit-client/command-http-graphql-normalized-cache-control": "3.32.0",
56
+ "@conduit-client/command-http-normalized-cache-control": "3.32.0",
57
+ "@conduit-client/command-ndjson": "3.32.0",
58
+ "@conduit-client/command-network": "3.32.0",
59
+ "@conduit-client/command-sse": "3.32.0",
60
+ "@conduit-client/command-streaming": "3.32.0",
61
+ "@conduit-client/jwt-manager": "3.32.0",
62
+ "@conduit-client/service-aura-network": "3.32.0",
63
+ "@conduit-client/service-bindings-imperative": "3.32.0",
64
+ "@conduit-client/service-bindings-lwc": "3.32.0",
65
+ "@conduit-client/service-cache": "3.32.0",
66
+ "@conduit-client/service-cache-control": "3.32.0",
67
+ "@conduit-client/service-cache-inclusion-policy": "3.32.0",
68
+ "@conduit-client/service-config": "3.32.0",
69
+ "@conduit-client/service-feature-flags": "3.32.0",
70
+ "@conduit-client/service-fetch-network": "3.32.0",
71
+ "@conduit-client/service-instrument-command": "3.32.0",
72
+ "@conduit-client/service-pubsub": "3.32.0",
73
+ "@conduit-client/service-renewable-resource-manager": "3.32.0",
74
+ "@conduit-client/service-store": "3.32.0",
75
+ "@conduit-client/utils": "3.32.0",
76
76
  "@luvio/network-adapter-composable": "0.161.2",
77
77
  "@luvio/network-adapter-fetch": "0.161.2",
78
78
  "@lwc/state": "^0.29.0",
79
- "@salesforce/lds-adapters-onestore-graphql": "^1.462.0",
79
+ "@salesforce/lds-adapters-onestore-graphql": "^1.464.0",
80
80
  "@salesforce/lds-adapters-uiapi-lex": "^1.415.0",
81
- "@salesforce/lds-durable-storage": "^1.462.0",
82
- "@salesforce/lds-luvio-service": "^1.462.0",
83
- "@salesforce/lds-luvio-uiapi-records-service": "^1.462.0"
81
+ "@salesforce/lds-durable-storage": "^1.464.0",
82
+ "@salesforce/lds-luvio-service": "^1.464.0",
83
+ "@salesforce/lds-luvio-uiapi-records-service": "^1.464.0"
84
84
  },
85
85
  "luvioBundlesize": [
86
86
  {