@salesforce/lds-runtime-one-runtime 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 } from 'force/ldsNetworkFetch';
32
+ import { instrument as instrument$2, isMutatingMethod, getFetchMethod, isIdempotentWrite, readUiApiErrorCode, isRetryableMethod, isRetryableReadPost, parseRetryAfterMs, compilePathMatchers, setupLexNetworkAdapter, UIAPI_PUBLIC_PATHS, UIAPI_PRIVATE_PATHS } 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
  };
@@ -7186,6 +7374,7 @@ const composedFetchNetworkAdapter = {
7186
7374
  buildLuvioTransportMarksSendInterceptor(),
7187
7375
  buildLuvioPageScopedCacheRequestInterceptor(),
7188
7376
  buildLuvioCsrfTokenInterceptor(),
7377
+ buildLuvioIdempotencyKeyInterceptor(),
7189
7378
  buildLuvioFirstPartyHeaderInterceptor(),
7190
7379
  buildLuvioEntityEncodingInterceptor(),
7191
7380
  ],
@@ -7359,15 +7548,21 @@ function buildCsrfRetryInterceptor() {
7359
7548
  }
7360
7549
 
7361
7550
  /**
7362
- * The context-seed key under which a custom command forwards its JWT mint
7551
+ * Legacy context-seed key under which a custom command forwards its JWT mint
7363
7552
  * parameters. The framework's `buildServiceDescriptor` merges the request init's
7364
7553
  * `__contextSeed` onto the per-request interceptor context, so this interceptor
7365
- * reads the params off `context[JWT_MINT_PARAMS_SEED_KEY]`.
7554
+ * can read the params off `context[JWT_MINT_PARAMS_SEED_KEY]`.
7555
+ *
7556
+ * **Transitional.** The first-class channel is now the typed
7557
+ * `init.onestore.mintParams` field (onestore 3.29.0), which this interceptor
7558
+ * prefers. This seed key remains only as a fallback while the core
7559
+ * `onestore-custom-adapters` producers still write `__contextSeed`; it — together
7560
+ * with the `context` fallback and the interceptor's `context` parameter — is
7561
+ * removed once those producers emit `onestore.mintParams`. That removal is the
7562
+ * follow-up PR #8314 (tracked under W-24472492).
7366
7563
  *
7367
- * This is a cross-team contract: the custom command writes this key, this
7368
- * interceptor reads it. It is intentionally an opaque key — OneStore does not
7369
- * define it and never inspects the param shape; the typed shape lives in the
7370
- * resolver.
7564
+ * It is intentionally an opaque key — OneStore does not define it and never
7565
+ * inspects the param shape; the typed shape lives in the resolver.
7371
7566
  */
7372
7567
  const JWT_MINT_PARAMS_SEED_KEY = 'jwtMintParams';
7373
7568
  /**
@@ -7398,15 +7593,17 @@ function hasAuthorizationHeader([resource, options]) {
7398
7593
  * Builds the request interceptor that bridges the per-request context seed to the
7399
7594
  * dispatching SFAP JwtManager for the **parameterized** mint path.
7400
7595
  *
7401
- * For a parameterized SFAP command, the context carries `jwtMintParams`. This
7402
- * interceptor reads them, calls `jwtManager.getJwt(params)` (which the dispatching
7403
- * resolver routes to the parameterized resolver), attaches the `Authorization`
7404
- * header, and rewrites the request URL via the minted `baseUri`. The presence of
7405
- * that `Authorization` header is the signal the descriptor's guarded legacy
7406
- * interceptor uses to skip — so the legacy path does not mint a second time.
7596
+ * For a parameterized SFAP command, the mint params arrive on the typed
7597
+ * `init.onestore.mintParams` field (preferred) or, transitionally, under the
7598
+ * legacy `context[jwtMintParams]` seed. This interceptor reads them (onestore
7599
+ * first), calls `jwtManager.getJwt(params)` (which the dispatching resolver routes
7600
+ * to the parameterized resolver), attaches the `Authorization` header, and
7601
+ * rewrites the request URL via the minted `baseUri`. The presence of that
7602
+ * `Authorization` header is the signal the descriptor's guarded legacy interceptor
7603
+ * uses to skip — so the legacy path does not mint a second time.
7407
7604
  *
7408
- * For a legacy parameterless command the context carries no `jwtMintParams`, so
7409
- * this interceptor **early-returns on its first line** and the request flows
7605
+ * For a legacy parameterless command neither channel carries mint params, so this
7606
+ * interceptor **early-returns on its first statement** and the request flows
7410
7607
  * unchanged to the legacy `buildJwtRequestHeaderInterceptor` — guaranteeing zero
7411
7608
  * behavior change for non-opted-in adapters.
7412
7609
  *
@@ -7420,8 +7617,11 @@ function hasAuthorizationHeader([resource, options]) {
7420
7617
  */
7421
7618
  function buildJwtParameterizationInterceptor(jwtManager, jwtRequestModifier = (_extraInfo, fetchArgs) => fetchArgs) {
7422
7619
  return (fetchArgs, context) => {
7423
- const jwtContext = context;
7424
- const mintParams = jwtContext?.[JWT_MINT_PARAMS_SEED_KEY];
7620
+ // Prefer the typed `init.onestore.mintParams`; fall back to the legacy
7621
+ // `context[JWT_MINT_PARAMS_SEED_KEY]` seed while the core producers still
7622
+ // write it (transitional dual-read — see JWT_MINT_PARAMS_SEED_KEY).
7623
+ const mintParams = fetchArgs[1]?.onestore?.mintParams ??
7624
+ context?.[JWT_MINT_PARAMS_SEED_KEY];
7425
7625
  // No mint params → not a parameterized request. Do nothing; the legacy
7426
7626
  // interceptor handles it. This MUST be the first statement so non-opted-in
7427
7627
  // SFAP commands observe no work at all.
@@ -7516,7 +7716,8 @@ function buildJwtAuthorizedSfapFetchServiceDescriptor(logger) {
7516
7716
  *
7517
7717
  * It reuses the module's `sfapJwtManager`: `cdp_url` is emitted on the same
7518
7718
  * `SFAP_API`-scope JWT, so the Data360 command mints the same kind of token — it
7519
- * just forwards the data-cloud scopes via the context-seed `jwtMintParams`.
7719
+ * just forwards the data-cloud scopes via `onestore.mintParams` (or, transitionally,
7720
+ * the legacy context-seed `jwtMintParams`).
7520
7721
  *
7521
7722
  * Gated behind its own `data_360_cdp_url_rewrite` auth-scope tag so it binds ONLY to
7522
7723
  * the Data360 command — the SFAP fleet never flows past this interceptor.
@@ -7647,10 +7848,11 @@ function buildJwtRequestInterceptor(logger) {
7647
7848
  * (`cdp_url`), and modifiers only receive `extraInfo`. So — like the copilot
7648
7849
  * interceptor that reads `decodedInfo.iss` — it holds the token directly:
7649
7850
  *
7650
- * 1. Read the command's `jwtMintParams` off the per-request context seed and
7651
- * mint (via the parameterized SFAP resolver, routed by the dispatching
7652
- * resolver inside `sfapJwtManager`). Without mint params there is no token
7653
- * to authorize with, so leave the request untouched.
7851
+ * 1. Read the command's mint params from `init.onestore.mintParams` (preferred)
7852
+ * or the legacy per-request context seed, and mint (via the parameterized
7853
+ * SFAP resolver, routed by the dispatching resolver inside `sfapJwtManager`).
7854
+ * Without mint params there is no token to authorize with, so leave the
7855
+ * request untouched.
7654
7856
  * 2. Decode `cdp_url` from the JWT and resolve the tenant-specific endpoint
7655
7857
  * (TSE). `cdp_url` is a bare host, so a scheme is prepended before parsing
7656
7858
  * and the protocol is forced to `https:`.
@@ -7669,7 +7871,11 @@ function buildJwtRequestInterceptor(logger) {
7669
7871
  */
7670
7872
  function buildData360HostRewriteInterceptor(logger) {
7671
7873
  return (fetchArgs, context) => {
7672
- const mintParams = context?.[JWT_MINT_PARAMS_SEED_KEY];
7874
+ // Prefer the typed `init.onestore.mintParams`; fall back to the legacy
7875
+ // `context[JWT_MINT_PARAMS_SEED_KEY]` seed while the core producers still
7876
+ // write it (transitional dual-read — see JWT_MINT_PARAMS_SEED_KEY).
7877
+ const mintParams = fetchArgs[1]?.onestore?.mintParams ??
7878
+ context?.[JWT_MINT_PARAMS_SEED_KEY];
7673
7879
  // No mint params → not a parameterized Data360 request. Nothing to do.
7674
7880
  if (mintParams === undefined) {
7675
7881
  return resolvedPromiseLike$2(fetchArgs);
@@ -10384,6 +10590,10 @@ function getLexRuntimeDefaultInterceptorConfig(logger) {
10384
10590
  buildCsrfTokenInterceptor(),
10385
10591
  buildEntityEncodingInterceptor(),
10386
10592
  buildFirstPartyHeaderInterceptor(),
10593
+ // Per-request gzip: compresses only operations that opt in via
10594
+ // `init.onestore.compression`; a pass-through no-op otherwise. Last in
10595
+ // the chain so it compresses the final body, after body-modifying interceptors.
10596
+ buildCompressionInterceptor(),
10387
10597
  ],
10388
10598
  retry: buildCsrfRetryInterceptor(),
10389
10599
  response: [
@@ -11259,6 +11469,8 @@ function initializeOneStore(luvio) {
11259
11469
  // across LEX and sites. Paired with the HTTP UIAPI fetch path being
11260
11470
  // disabled on sites (see network-fetch.ts).
11261
11471
  featureFlagsService.set('graphQLUseAura', true);
11472
+ // Persisted (named) GraphQL queries; no consumers yet (W-23971639).
11473
+ featureFlagsService.set('graphQLUsePersistedQueries', usePersistedQueries.isOpen({ fallback: false }));
11262
11474
  const services = [
11263
11475
  instrumentationServiceDescriptor,
11264
11476
  buildLexRuntimeDefaultFetchServiceDescriptor(loggerService, retryService),
@@ -11319,4 +11531,4 @@ function ldsEngineCreator() {
11319
11531
  }
11320
11532
 
11321
11533
  export { LexRequestStrategy, PDL_ENGINE_REGISTRATION_ID, PdlPrefetcherEventType, PdlRequestPriority, buildPredictorForContext, configService, ldsEngineCreator as default, initializeLDS, initializeOneStore, notifyUpdateAvailableFactory, registerRequestStrategy, saveRequestAsPrediction, subscribeToPrefetcherEvents, unregisterRequestStrategy, whenPredictionsReady };
11322
- // version: 1.462.0-50d9a13ba4
11534
+ // 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-runtime",
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 LEX on One Runtime (LWR) runtime.",
6
6
  "main": "dist/oneRuntimeLdsEngineCreator.js",
@@ -34,61 +34,61 @@
34
34
  "release:corejar": "yarn build && ../core-build/scripts/core.js --name=lds-runtime-one-runtime"
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
  {
87
87
  "path": "./dist/oneRuntimeLdsEngineCreator.js",
88
88
  "maxSize": {
89
- "none": "416 kB",
89
+ "none": "419 kB",
90
90
  "min": "190 kB",
91
- "compressed": "73 kB"
91
+ "compressed": "75 kB"
92
92
  }
93
93
  }
94
94
  ],