@salesforce/lds-runtime-aura 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.
- package/dist/ldsEngineCreator.js +294 -82
- package/dist/types/fetch-service-descriptors/jwt-authorized-fetch-service.d.ts +2 -1
- package/dist/types/request-interceptors/idempotency-key.d.ts +24 -0
- package/dist/types/request-interceptors/jwt-parameterization.d.ts +22 -14
- package/dist/types/retry-interceptors/luvio-fetch-retry.d.ts +6 -3
- package/dist/types/retry-policies/luvio-csrf-token-retry-policy.d.ts +1 -8
- package/dist/types/retry-policies/luvio-idempotency-retry-policy.d.ts +29 -0
- package/package.json +41 -41
package/dist/ldsEngineCreator.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
/* proxy-compat-disable */
|
|
15
15
|
import { HttpStatusCode as HttpStatusCode$2, InMemoryStore, Environment, Luvio, InMemoryStoreQueryEvaluator } from 'force/luvioEngine';
|
|
16
16
|
import useOneStoreGraphql from '@salesforce/gate/lds.oneStoreGraphqlEnabled.ltng';
|
|
17
|
+
import usePersistedQueries from '@salesforce/gate/lds.graphqlusepersistedqueries';
|
|
17
18
|
import { instrument, buildRecordRepKeyFromId, configuration, InMemoryRecordRepresentationQueryEvaluator, UiApiNamespace, RecordRepresentationRepresentationType, RecordRepresentationVersion } from 'force/ldsAdaptersUiapi';
|
|
18
19
|
import { getInstrumentation } from 'o11y/client';
|
|
19
20
|
import { findExecutableOperation, buildGraphQLInputExtension, addTypenameToDocument } from 'force/luvioGraphqlNormalization';
|
|
@@ -24,7 +25,7 @@ import { assertIsValid, JsonSchemaViolationError, MissingRequiredPropertyError }
|
|
|
24
25
|
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';
|
|
25
26
|
import { ThirdPartyTracker } from 'instrumentation:thirdPartyTracker';
|
|
26
27
|
import { markStart, markEnd, counter, registerCacheStats, perfStart, perfEnd, registerPeriodicLogger, interaction, timer, mark } from 'instrumentation/service';
|
|
27
|
-
import { instrument as instrument$2,
|
|
28
|
+
import { instrument as instrument$2, isMutatingMethod, getFetchMethod, isIdempotentWrite, readUiApiErrorCode, isRetryableMethod, isRetryableReadPost, parseRetryAfterMs, compilePathMatchers, setupLexNetworkAdapter, UIAPI_PUBLIC_PATHS, UIAPI_PRIVATE_PATHS, isRetryableOperationType } from 'force/ldsNetworkFetch';
|
|
28
29
|
import { REFRESH_ADAPTER_EVENT, ADAPTER_UNFULFILLED_ERROR, instrument as instrument$3 } from 'force/ldsBindings';
|
|
29
30
|
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 } from 'force/ldsInstrumentation';
|
|
30
31
|
import { instrument as instrument$4 } from 'force/adsBridge';
|
|
@@ -383,6 +384,15 @@ function buildServiceDescriptor$q() {
|
|
|
383
384
|
};
|
|
384
385
|
}
|
|
385
386
|
|
|
387
|
+
function applyOperationRequestOptions$1(init, operationType) {
|
|
388
|
+
if (operationType === void 0) {
|
|
389
|
+
return init;
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
...init,
|
|
393
|
+
onestore: { ...init?.onestore, retrySafe: operationType === "query" }
|
|
394
|
+
};
|
|
395
|
+
}
|
|
386
396
|
class AuraNetworkCommand extends NetworkCommand$1 {
|
|
387
397
|
constructor(services) {
|
|
388
398
|
super(services);
|
|
@@ -401,6 +411,18 @@ class AuraNetworkCommand extends NetworkCommand$1 {
|
|
|
401
411
|
"Fetch parameters must be specified when using HTTP transport on an Aura adapter"
|
|
402
412
|
);
|
|
403
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* The command's operation type. Generated command subclasses override this
|
|
416
|
+
* getter with their concrete operation (see the generator's
|
|
417
|
+
* `generateOperationType`); the base returns `undefined` for commands that
|
|
418
|
+
* don't declare one. On the HTTP fallback path (`fetch`) it derives the
|
|
419
|
+
* request's `onestore.retrySafe` — reads (`query`) are retry-safe, writes
|
|
420
|
+
* (`mutation`) are not. Mirrors `FetchNetworkCommand.operationType`, which
|
|
421
|
+
* this class does not inherit (it extends `NetworkCommand` directly).
|
|
422
|
+
*/
|
|
423
|
+
get operationType() {
|
|
424
|
+
return void 0;
|
|
425
|
+
}
|
|
404
426
|
coerceAuraErrors(auraErrors) {
|
|
405
427
|
return new UserVisibleError(getFetchResponseFromAuraError(auraErrors[0]));
|
|
406
428
|
}
|
|
@@ -502,9 +524,11 @@ class AuraNetworkCommand extends NetworkCommand$1 {
|
|
|
502
524
|
this.coerceAuraErrors
|
|
503
525
|
);
|
|
504
526
|
} else if (this.shouldUseFetch()) {
|
|
505
|
-
const
|
|
527
|
+
const [input, init] = this.fetchParams;
|
|
528
|
+
const initWithOptions = applyOperationRequestOptions$1(init, this.operationType);
|
|
506
529
|
try {
|
|
507
|
-
|
|
530
|
+
const fetchCall = initWithOptions === void 0 ? this.services.fetch(input) : this.services.fetch(input, initWithOptions);
|
|
531
|
+
return this.convertFetchResponseToData(fetchCall);
|
|
508
532
|
} catch (reason) {
|
|
509
533
|
return resolvedPromiseLike$2(err$1(toError(reason)));
|
|
510
534
|
}
|
|
@@ -1358,6 +1382,15 @@ class NetworkCommand extends BaseCommand {
|
|
|
1358
1382
|
async afterRequestHooks(_options) {
|
|
1359
1383
|
}
|
|
1360
1384
|
}
|
|
1385
|
+
function applyOperationRequestOptions(init, operationType) {
|
|
1386
|
+
if (operationType === void 0) {
|
|
1387
|
+
return init;
|
|
1388
|
+
}
|
|
1389
|
+
return {
|
|
1390
|
+
...init,
|
|
1391
|
+
onestore: { ...init?.onestore, retrySafe: operationType === "query" }
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1361
1394
|
function isAbortableCommand(command) {
|
|
1362
1395
|
return !!command && typeof command === "object" && "isAbortableCommand" in command && command.isAbortableCommand === true;
|
|
1363
1396
|
}
|
|
@@ -1447,7 +1480,8 @@ const _FetchNetworkCommand = class _FetchNetworkCommand extends NetworkCommand {
|
|
|
1447
1480
|
* `generateOperationType`); the base returns `undefined` for commands that
|
|
1448
1481
|
* don't declare one (e.g. hand-written commands). Transport-layer
|
|
1449
1482
|
* interceptors use it to tell a read (`query`) apart from a write — see
|
|
1450
|
-
* `fetch`, which folds it into the context seed
|
|
1483
|
+
* `fetch`, which folds it into the context seed and derives
|
|
1484
|
+
* `onestore.retrySafe` from it.
|
|
1451
1485
|
*/
|
|
1452
1486
|
get operationType() {
|
|
1453
1487
|
return void 0;
|
|
@@ -1456,8 +1490,9 @@ const _FetchNetworkCommand = class _FetchNetworkCommand extends NetworkCommand {
|
|
|
1456
1490
|
try {
|
|
1457
1491
|
const [input, init] = this.fetchParams;
|
|
1458
1492
|
const operationType = this.operationType;
|
|
1493
|
+
const initWithOptions = applyOperationRequestOptions(init, operationType);
|
|
1459
1494
|
const seed = operationType === void 0 ? contextSeed : { operationType, ...contextSeed };
|
|
1460
|
-
const initWithSeed = seed === void 0 ?
|
|
1495
|
+
const initWithSeed = seed === void 0 ? initWithOptions : { ...initWithOptions, __contextSeed: seed };
|
|
1461
1496
|
const fetchCall = initWithSeed === void 0 ? this.services.fetch(input) : this.services.fetch(input, initWithSeed);
|
|
1462
1497
|
return this.convertFetchResponseToData(fetchCall);
|
|
1463
1498
|
} catch (reason) {
|
|
@@ -2596,7 +2631,7 @@ function buildServiceDescriptor$d(luvio) {
|
|
|
2596
2631
|
},
|
|
2597
2632
|
};
|
|
2598
2633
|
}
|
|
2599
|
-
// version: 1.
|
|
2634
|
+
// version: 1.464.0-e54c238397
|
|
2600
2635
|
|
|
2601
2636
|
class AuraGraphQLNormalizedCacheControlCommand extends AuraNormalizedCacheControlCommand {
|
|
2602
2637
|
constructor(config, documentRootType, services) {
|
|
@@ -2935,7 +2970,7 @@ function buildServiceDescriptor$9(notifyRecordUpdateAvailable, getNormalizedLuvi
|
|
|
2935
2970
|
},
|
|
2936
2971
|
};
|
|
2937
2972
|
}
|
|
2938
|
-
// version: 1.
|
|
2973
|
+
// version: 1.464.0-e54c238397
|
|
2939
2974
|
|
|
2940
2975
|
class RetryService {
|
|
2941
2976
|
constructor(defaultRetryPolicy) {
|
|
@@ -4491,7 +4526,12 @@ function buildServiceDescriptor$2(interceptors = {
|
|
|
4491
4526
|
}
|
|
4492
4527
|
const fetchArgs = cleanInit === void 0 ? [input] : [input, cleanInit];
|
|
4493
4528
|
const baseContext = interceptors.createContext?.();
|
|
4494
|
-
const
|
|
4529
|
+
const onestore = cleanInit?.onestore;
|
|
4530
|
+
const context = contextSeed === void 0 && onestore === void 0 ? baseContext : {
|
|
4531
|
+
...baseContext,
|
|
4532
|
+
...contextSeed,
|
|
4533
|
+
...onestore !== void 0 && { onestore }
|
|
4534
|
+
};
|
|
4495
4535
|
const {
|
|
4496
4536
|
request: requestInterceptors = [],
|
|
4497
4537
|
retry: retryInterceptor = void 0,
|
|
@@ -4539,10 +4579,14 @@ function getTextEncoder() {
|
|
|
4539
4579
|
}
|
|
4540
4580
|
return textEncoder;
|
|
4541
4581
|
}
|
|
4542
|
-
function buildCompressionInterceptor(
|
|
4543
|
-
const threshold = config.threshold ?? 1024;
|
|
4582
|
+
function buildCompressionInterceptor(defaultConfig) {
|
|
4544
4583
|
return async (args) => {
|
|
4545
4584
|
const [resource, options = {}] = args;
|
|
4585
|
+
const config = options.onestore?.compression ?? defaultConfig;
|
|
4586
|
+
if (config === void 0) {
|
|
4587
|
+
return resolvedPromiseLike$2(args);
|
|
4588
|
+
}
|
|
4589
|
+
const threshold = config.threshold ?? 1024;
|
|
4546
4590
|
if (!options.body || typeof options.body !== "string") {
|
|
4547
4591
|
return resolvedPromiseLike$2(args);
|
|
4548
4592
|
}
|
|
@@ -5941,7 +5985,7 @@ function getEnvironmentSetting(name) {
|
|
|
5941
5985
|
}
|
|
5942
5986
|
return undefined;
|
|
5943
5987
|
}
|
|
5944
|
-
// version: 1.
|
|
5988
|
+
// version: 1.464.0-b35c0e7723
|
|
5945
5989
|
|
|
5946
5990
|
const auraClientService = getAuraClientService();
|
|
5947
5991
|
const defaultConfig = {
|
|
@@ -6315,7 +6359,13 @@ function getOrGenerateErrorId(responseBody, status, statusText) {
|
|
|
6315
6359
|
return generateErrorId(context);
|
|
6316
6360
|
}
|
|
6317
6361
|
function buildLexRuntime5xxStatusResponseInterceptor(logger) {
|
|
6318
|
-
return async (response) => {
|
|
6362
|
+
return async (response, context) => {
|
|
6363
|
+
// The caller opted in (`onestore.handleServerError`, exposed by the fetch service as
|
|
6364
|
+
// `context.onestore`) to receive the 5xx itself, so skip the gack and the throw and
|
|
6365
|
+
// let the response flow to the command's error handling.
|
|
6366
|
+
if (context?.onestore?.handleServerError === true) {
|
|
6367
|
+
return response;
|
|
6368
|
+
}
|
|
6319
6369
|
if (response.status >= 500 && response.status < 600) {
|
|
6320
6370
|
const internalLogMessage = `LEX runtime 5xx: ${response.status} ${response.statusText || ''}`.trim();
|
|
6321
6371
|
logger.warn(internalLogMessage);
|
|
@@ -6519,7 +6569,7 @@ function buildCsrfTokenInterceptor() {
|
|
|
6519
6569
|
return resolvedPromiseLike$2(fetchArgs);
|
|
6520
6570
|
}
|
|
6521
6571
|
// Only add CSRF token for mutating operations
|
|
6522
|
-
if (
|
|
6572
|
+
if (isMutatingMethod(getFetchMethod(fetchArgs))) {
|
|
6523
6573
|
const token = await getCsrfToken();
|
|
6524
6574
|
if (token) {
|
|
6525
6575
|
// eslint-disable-next-line no-param-reassign
|
|
@@ -6547,7 +6597,7 @@ function buildLuvioCsrfTokenInterceptor() {
|
|
|
6547
6597
|
resourceRequest.headers = {};
|
|
6548
6598
|
}
|
|
6549
6599
|
// Only add CSRF token for mutating operations
|
|
6550
|
-
if (
|
|
6600
|
+
if (isMutatingMethod(resourceRequest.method)) {
|
|
6551
6601
|
// Don't overwrite existing CSRF token header if it already exists
|
|
6552
6602
|
if (!resourceRequest.headers[CSRF_TOKEN_HEADER]) {
|
|
6553
6603
|
const token = await getCsrfToken();
|
|
@@ -6560,6 +6610,53 @@ function buildLuvioCsrfTokenInterceptor() {
|
|
|
6560
6610
|
};
|
|
6561
6611
|
}
|
|
6562
6612
|
|
|
6613
|
+
const IDEMPOTENCY_KEY_HEADER = 'Idempotency-Key';
|
|
6614
|
+
// Server-side idempotency error codes (UI SDK / UI-API `ApiExceptionCode`); the
|
|
6615
|
+
// mobile draft-queue handler is the reference consumer.
|
|
6616
|
+
const ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED = 'IDEMPOTENCY_FEATURE_NOT_ENABLED';
|
|
6617
|
+
const ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED = 'IDEMPOTENCY_NOT_SUPPORTED';
|
|
6618
|
+
const ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR = 'IDEMPOTENCY_BACKEND_OPERATION_ERROR';
|
|
6619
|
+
const ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER = 'IDEMPOTENCY_KEY_USED_DIFFERENT_USER';
|
|
6620
|
+
const ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST = 'IDEMPOTENCY_CONCURRENT_REQUEST';
|
|
6621
|
+
const ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED = 'IDEMPOTENCY_KEY_ALREADY_USED';
|
|
6622
|
+
/**
|
|
6623
|
+
* Generates an RFC4122 v4 UUID for use as an idempotency key, preferring
|
|
6624
|
+
* `crypto.randomUUID()` and falling back to a `Math.random()` builder if it is
|
|
6625
|
+
* unavailable (matches the SFAP request-id approach in `network-sfap`).
|
|
6626
|
+
*/
|
|
6627
|
+
function mintIdempotencyKey() {
|
|
6628
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
6629
|
+
return crypto.randomUUID();
|
|
6630
|
+
}
|
|
6631
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
6632
|
+
const r = (Math.random() * 16) | 0;
|
|
6633
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
6634
|
+
return v.toString(16);
|
|
6635
|
+
});
|
|
6636
|
+
}
|
|
6637
|
+
/**
|
|
6638
|
+
* Builds a Luvio request interceptor that mints and attaches an `Idempotency-Key`
|
|
6639
|
+
* on mutations to onboarded endpoints, once per logical write, so the retry
|
|
6640
|
+
* service re-sends the same key on every replay and the server can dedupe.
|
|
6641
|
+
*
|
|
6642
|
+
* Minting is gated here; retry *eligibility* is decided separately by
|
|
6643
|
+
* `LuvioIdempotencyRetryPolicy` from the header's presence, so a stale
|
|
6644
|
+
* registry flag can never cause an unsafe replay.
|
|
6645
|
+
*/
|
|
6646
|
+
function buildLuvioIdempotencyKeyInterceptor() {
|
|
6647
|
+
return async (resourceRequest) => {
|
|
6648
|
+
if (!resourceRequest.headers) {
|
|
6649
|
+
resourceRequest.headers = {};
|
|
6650
|
+
}
|
|
6651
|
+
if (isMutatingMethod(resourceRequest.method) &&
|
|
6652
|
+
isIdempotentWrite(resourceRequest.basePath) &&
|
|
6653
|
+
!resourceRequest.headers[IDEMPOTENCY_KEY_HEADER]) {
|
|
6654
|
+
resourceRequest.headers[IDEMPOTENCY_KEY_HEADER] = mintIdempotencyKey();
|
|
6655
|
+
}
|
|
6656
|
+
return resolvedPromiseLike$2(resourceRequest);
|
|
6657
|
+
};
|
|
6658
|
+
}
|
|
6659
|
+
|
|
6563
6660
|
const ENTITY_ENCODING_HEADER = 'X-Chatter-Entity-Encoding';
|
|
6564
6661
|
const ENTITY_ENCODING_DISABLED_VALUE = 'false';
|
|
6565
6662
|
/**
|
|
@@ -6856,11 +6953,11 @@ function buildLuvioThirdPartyTrackerFinishInterceptor() {
|
|
|
6856
6953
|
};
|
|
6857
6954
|
}
|
|
6858
6955
|
|
|
6859
|
-
const DEFAULT_CONFIG$
|
|
6956
|
+
const DEFAULT_CONFIG$4 = {
|
|
6860
6957
|
maxRetries: 1,
|
|
6861
6958
|
};
|
|
6862
6959
|
class LuvioCsrfTokenRetryPolicy extends RetryPolicy {
|
|
6863
|
-
constructor(config = DEFAULT_CONFIG$
|
|
6960
|
+
constructor(config = DEFAULT_CONFIG$4) {
|
|
6864
6961
|
super();
|
|
6865
6962
|
this.config = config;
|
|
6866
6963
|
}
|
|
@@ -6904,21 +7001,12 @@ class LuvioCsrfTokenRetryPolicy extends RetryPolicy {
|
|
|
6904
7001
|
this.requestContext.request = { ...req, headers };
|
|
6905
7002
|
}
|
|
6906
7003
|
}
|
|
6907
|
-
/**
|
|
6908
|
-
* Returns true when a Luvio FetchResponse indicates a CSRF token error.
|
|
6909
|
-
* Body is already parsed JSON on FetchResponse, so no clone/json() needed.
|
|
6910
|
-
*
|
|
6911
|
-
* UIAPI error bodies come in both shapes: an object `{ errorCode, message }`
|
|
6912
|
-
* for single errors, or an array `[{ errorCode, message }]` for endpoints that
|
|
6913
|
-
* return collections of errors. Handle both.
|
|
6914
|
-
*/
|
|
7004
|
+
/** True when a Luvio FetchResponse indicates a CSRF token error. */
|
|
6915
7005
|
function isCsrfError$1(response) {
|
|
6916
|
-
|
|
6917
|
-
const errorCode = Array.isArray(body) ? body[0]?.errorCode : body?.errorCode;
|
|
6918
|
-
return errorCode === 'INVALID_ACCESS_TOKEN';
|
|
7006
|
+
return readUiApiErrorCode(response) === 'INVALID_ACCESS_TOKEN';
|
|
6919
7007
|
}
|
|
6920
7008
|
|
|
6921
|
-
const DEFAULT_CONFIG$
|
|
7009
|
+
const DEFAULT_CONFIG$3 = {
|
|
6922
7010
|
maxRetries: 3,
|
|
6923
7011
|
maxTimeToRetry: 10000,
|
|
6924
7012
|
baseDelay: 250,
|
|
@@ -6932,7 +7020,7 @@ class LuvioFetchThrottlingRetryPolicy extends RetryPolicy {
|
|
|
6932
7020
|
// is why we take the plain `ResourceRequest` rather than the mutable-container
|
|
6933
7021
|
// pattern the CSRF policy uses — that policy rewrites the request (new token)
|
|
6934
7022
|
// between attempts; this one never mutates it.
|
|
6935
|
-
constructor(config = DEFAULT_CONFIG$
|
|
7023
|
+
constructor(config = DEFAULT_CONFIG$3, request) {
|
|
6936
7024
|
super();
|
|
6937
7025
|
this.config = config;
|
|
6938
7026
|
this.request = request;
|
|
@@ -6960,57 +7048,157 @@ class LuvioFetchThrottlingRetryPolicy extends RetryPolicy {
|
|
|
6960
7048
|
return Math.max(0, delay + jitter);
|
|
6961
7049
|
}
|
|
6962
7050
|
parseRetryAfterHeader(result) {
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
7051
|
+
return parseRetryAfterMs(result.headers);
|
|
7052
|
+
}
|
|
7053
|
+
}
|
|
7054
|
+
|
|
7055
|
+
const DEFAULT_CONFIG$2 = {
|
|
7056
|
+
maxRetries: 3,
|
|
7057
|
+
};
|
|
7058
|
+
// The idempotency handler never emits 429 (that's throttling's domain). Its 503
|
|
7059
|
+
// is a keyed-write lock-acquire failure (scenario 7), not throttling's keyless
|
|
7060
|
+
// GET/read-POST 503 — the two never contend (see classifyResponse).
|
|
7061
|
+
const HTTP_STATUS_BAD_REQUEST = 400;
|
|
7062
|
+
const HTTP_STATUS_REQUEST_TIMEOUT = 408;
|
|
7063
|
+
const HTTP_STATUS_CONFLICT = 409;
|
|
7064
|
+
const HTTP_STATUS_UNPROCESSABLE_ENTITY = 422;
|
|
7065
|
+
const HTTP_STATUS_SERVER_ERROR = 500;
|
|
7066
|
+
const HTTP_STATUS_SERVICE_UNAVAILABLE = 503;
|
|
7067
|
+
// Terminal cases (IDEMPOTENCY_AUTHENTICATION_FAILED on 401, INVALID_IDEMPOTENCY_KEY
|
|
7068
|
+
// on 400) are deliberately absent so they fall through to 'none'.
|
|
7069
|
+
const STRIP_ERROR_CODES = [
|
|
7070
|
+
ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED,
|
|
7071
|
+
ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED,
|
|
7072
|
+
ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR,
|
|
7073
|
+
];
|
|
7074
|
+
const ROTATE_ERROR_CODES = [
|
|
7075
|
+
ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER,
|
|
7076
|
+
ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED,
|
|
7077
|
+
];
|
|
7078
|
+
/**
|
|
7079
|
+
* Maps an idempotent-write response to its disposition. Status alone is
|
|
7080
|
+
* ambiguous (e.g. 409 is same-key for a concurrent request but rotate for a
|
|
7081
|
+
* different user), so pair it with the body's errorCode.
|
|
7082
|
+
*/
|
|
7083
|
+
function classifyResponse(response) {
|
|
7084
|
+
const errorCode = readUiApiErrorCode(response);
|
|
7085
|
+
// FetchResponse.status is typed as HttpStatusCode, which omits 408/409/422
|
|
7086
|
+
// even though the idempotency handler emits them; widen to match.
|
|
7087
|
+
const status = response.status;
|
|
7088
|
+
switch (status) {
|
|
7089
|
+
case HTTP_STATUS_SERVER_ERROR:
|
|
7090
|
+
return errorCode === ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR ? 'strip' : 'none';
|
|
7091
|
+
case HTTP_STATUS_BAD_REQUEST:
|
|
7092
|
+
// FEATURE_NOT_ENABLED / NOT_SUPPORTED → strip. Any other 400 (incl.
|
|
7093
|
+
// INVALID_IDEMPOTENCY_KEY and CSRF's INVALID_ACCESS_TOKEN) is terminal.
|
|
7094
|
+
return errorCode !== undefined && STRIP_ERROR_CODES.includes(errorCode)
|
|
7095
|
+
? 'strip'
|
|
7096
|
+
: 'none';
|
|
7097
|
+
case HTTP_STATUS_REQUEST_TIMEOUT:
|
|
7098
|
+
// Backend connection timeout — the write may have already committed;
|
|
7099
|
+
// replay same-key so the server can dedupe.
|
|
7100
|
+
return 'same-key';
|
|
7101
|
+
case HTTP_STATUS_SERVICE_UNAVAILABLE:
|
|
7102
|
+
// Lock-acquire failure (scenario 7): the write never committed. Only a
|
|
7103
|
+
// keyed write reaches here (shouldRetry gates on key presence) — a
|
|
7104
|
+
// keyless 503 is the throttling policy's GET/read-POST case.
|
|
7105
|
+
return 'same-key';
|
|
7106
|
+
case HTTP_STATUS_CONFLICT:
|
|
7107
|
+
// KEY_USED_DIFFERENT_USER → rotate; CONCURRENT_REQUEST → same key.
|
|
7108
|
+
if (errorCode !== undefined && ROTATE_ERROR_CODES.includes(errorCode)) {
|
|
7109
|
+
return 'rotate';
|
|
6974
7110
|
}
|
|
7111
|
+
return errorCode === ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST ? 'same-key' : 'none';
|
|
7112
|
+
case HTTP_STATUS_UNPROCESSABLE_ENTITY:
|
|
7113
|
+
// Same key reused for a different request body → needs a new key.
|
|
7114
|
+
return errorCode === ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED ? 'rotate' : 'none';
|
|
7115
|
+
default:
|
|
7116
|
+
return 'none';
|
|
7117
|
+
}
|
|
7118
|
+
}
|
|
7119
|
+
/**
|
|
7120
|
+
* Retry policy for writes carrying an `Idempotency-Key`. A peer of the CSRF and
|
|
7121
|
+
* throttling policies in the composed retry chain, not a modification of
|
|
7122
|
+
* either: its 503 (keyed-write lock-acquire) and throttling's 503 (keyless
|
|
7123
|
+
* GET/read-POST) never contend because they key off different requests, and it
|
|
7124
|
+
* only claims a 400/409 when the body carries an idempotency-specific
|
|
7125
|
+
* errorCode, so it never steals CSRF's INVALID_ACCESS_TOKEN.
|
|
7126
|
+
*
|
|
7127
|
+
* Eligibility follows from the header's *presence* on the request, not from
|
|
7128
|
+
* the mint interceptor's registry flag — so a stale flag can never cause an
|
|
7129
|
+
* unsafe replay.
|
|
7130
|
+
*/
|
|
7131
|
+
class LuvioIdempotencyRetryPolicy extends RetryPolicy {
|
|
7132
|
+
constructor(config = DEFAULT_CONFIG$2) {
|
|
7133
|
+
super();
|
|
7134
|
+
this.config = config;
|
|
7135
|
+
}
|
|
7136
|
+
setRequestContext(context) {
|
|
7137
|
+
this.requestContext = context;
|
|
7138
|
+
}
|
|
7139
|
+
async shouldRetry(result, context) {
|
|
7140
|
+
if (context.attempt >= this.config.maxRetries) {
|
|
7141
|
+
return false;
|
|
6975
7142
|
}
|
|
6976
|
-
|
|
6977
|
-
|
|
7143
|
+
const headers = this.requestContext?.request.headers;
|
|
7144
|
+
if (!headers || !headers[IDEMPOTENCY_KEY_HEADER]) {
|
|
7145
|
+
return false;
|
|
6978
7146
|
}
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
return
|
|
7147
|
+
return classifyResponse(result) !== 'none';
|
|
7148
|
+
}
|
|
7149
|
+
async calculateDelay(result, _context) {
|
|
7150
|
+
// Only same-key replays (408/409/503) carry a Retry-After to honor;
|
|
7151
|
+
// strip/rotate retry immediately.
|
|
7152
|
+
if (classifyResponse(result) !== 'same-key') {
|
|
7153
|
+
return 0;
|
|
6986
7154
|
}
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
7155
|
+
// Honor the server's Retry-After in full, like the mobile handler — it's
|
|
7156
|
+
// a lock-hold hint, not a client-owned backoff (unlike the throttling
|
|
7157
|
+
// sibling's capped delay). maxRetries bounds replays instead.
|
|
7158
|
+
return parseRetryAfterMs(result.headers) ?? 0;
|
|
7159
|
+
}
|
|
7160
|
+
async prepareRetry(result, _context) {
|
|
7161
|
+
if (!this.requestContext) {
|
|
7162
|
+
return;
|
|
6990
7163
|
}
|
|
6991
|
-
const
|
|
6992
|
-
if (
|
|
6993
|
-
return
|
|
7164
|
+
const disposition = classifyResponse(result);
|
|
7165
|
+
if (disposition === 'same-key' || disposition === 'none') {
|
|
7166
|
+
return;
|
|
6994
7167
|
}
|
|
6995
|
-
|
|
7168
|
+
const req = this.requestContext.request;
|
|
7169
|
+
// Drop the existing key via immutable spread (matches the CSRF policy).
|
|
7170
|
+
const { [IDEMPOTENCY_KEY_HEADER]: _stale, ...remainingHeaders } = req.headers ?? {};
|
|
7171
|
+
const headers = disposition === 'rotate'
|
|
7172
|
+
? { ...remainingHeaders, [IDEMPOTENCY_KEY_HEADER]: mintIdempotencyKey() }
|
|
7173
|
+
: // 'strip': retry plain — the feature is unavailable for this request.
|
|
7174
|
+
remainingHeaders;
|
|
7175
|
+
this.requestContext.request = { ...req, headers };
|
|
6996
7176
|
}
|
|
6997
7177
|
}
|
|
6998
7178
|
|
|
6999
7179
|
/**
|
|
7000
|
-
* Builds the LuvioRetryInterceptor used by the UIAPI fetch adapter.
|
|
7180
|
+
* Builds the LuvioRetryInterceptor used by the UIAPI fetch adapter. A fresh
|
|
7181
|
+
* RetryService and policies are created per request to keep the
|
|
7182
|
+
* mutableRequest context isolated across concurrent requests.
|
|
7001
7183
|
*
|
|
7002
|
-
*
|
|
7003
|
-
*
|
|
7184
|
+
* Policy order matters: ComposedRetryPolicy uses the first matching policy's
|
|
7185
|
+
* shouldRetry/delay. A keyed-write 503 fails throttling's method gate, so it
|
|
7186
|
+
* falls to the idempotency policy to replay same-key.
|
|
7004
7187
|
*/
|
|
7005
7188
|
function buildLuvioFetchRetryInterceptor() {
|
|
7006
7189
|
return (request, doFetch) => {
|
|
7190
|
+
const idempotencyPolicy = new LuvioIdempotencyRetryPolicy();
|
|
7007
7191
|
const csrfPolicy = new LuvioCsrfTokenRetryPolicy();
|
|
7008
7192
|
const composedPolicy = new ComposedRetryPolicy([
|
|
7193
|
+
idempotencyPolicy,
|
|
7009
7194
|
new LuvioFetchThrottlingRetryPolicy(undefined, request),
|
|
7010
7195
|
csrfPolicy,
|
|
7011
7196
|
]);
|
|
7012
7197
|
const retryService = new RetryService(composedPolicy);
|
|
7013
7198
|
const mutableRequest = { request };
|
|
7199
|
+
// Both policies rewrite headers on the shared request between attempts
|
|
7200
|
+
// (idempotency strips/rotates the key; CSRF refreshes the token).
|
|
7201
|
+
idempotencyPolicy.setRequestContext(mutableRequest);
|
|
7014
7202
|
csrfPolicy.setRequestContext(mutableRequest);
|
|
7015
7203
|
return retryService.applyRetry(() => doFetch(mutableRequest.request));
|
|
7016
7204
|
};
|
|
@@ -7244,6 +7432,7 @@ const composedFetchNetworkAdapter = {
|
|
|
7244
7432
|
buildLuvioTransportMarksSendInterceptor(),
|
|
7245
7433
|
buildLuvioPageScopedCacheRequestInterceptor(),
|
|
7246
7434
|
buildLuvioCsrfTokenInterceptor(),
|
|
7435
|
+
buildLuvioIdempotencyKeyInterceptor(),
|
|
7247
7436
|
buildLuvioFirstPartyHeaderInterceptor(),
|
|
7248
7437
|
buildLuvioEntityEncodingInterceptor(),
|
|
7249
7438
|
],
|
|
@@ -7507,15 +7696,21 @@ function buildFetchRetryInterceptor() {
|
|
|
7507
7696
|
}
|
|
7508
7697
|
|
|
7509
7698
|
/**
|
|
7510
|
-
*
|
|
7699
|
+
* Legacy context-seed key under which a custom command forwards its JWT mint
|
|
7511
7700
|
* parameters. The framework's `buildServiceDescriptor` merges the request init's
|
|
7512
7701
|
* `__contextSeed` onto the per-request interceptor context, so this interceptor
|
|
7513
|
-
*
|
|
7702
|
+
* can read the params off `context[JWT_MINT_PARAMS_SEED_KEY]`.
|
|
7703
|
+
*
|
|
7704
|
+
* **Transitional.** The first-class channel is now the typed
|
|
7705
|
+
* `init.onestore.mintParams` field (onestore 3.29.0), which this interceptor
|
|
7706
|
+
* prefers. This seed key remains only as a fallback while the core
|
|
7707
|
+
* `onestore-custom-adapters` producers still write `__contextSeed`; it — together
|
|
7708
|
+
* with the `context` fallback and the interceptor's `context` parameter — is
|
|
7709
|
+
* removed once those producers emit `onestore.mintParams`. That removal is the
|
|
7710
|
+
* follow-up PR #8314 (tracked under W-24472492).
|
|
7514
7711
|
*
|
|
7515
|
-
*
|
|
7516
|
-
*
|
|
7517
|
-
* define it and never inspects the param shape; the typed shape lives in the
|
|
7518
|
-
* resolver.
|
|
7712
|
+
* It is intentionally an opaque key — OneStore does not define it and never
|
|
7713
|
+
* inspects the param shape; the typed shape lives in the resolver.
|
|
7519
7714
|
*/
|
|
7520
7715
|
const JWT_MINT_PARAMS_SEED_KEY = 'jwtMintParams';
|
|
7521
7716
|
/**
|
|
@@ -7546,15 +7741,17 @@ function hasAuthorizationHeader([resource, options]) {
|
|
|
7546
7741
|
* Builds the request interceptor that bridges the per-request context seed to the
|
|
7547
7742
|
* dispatching SFAP JwtManager for the **parameterized** mint path.
|
|
7548
7743
|
*
|
|
7549
|
-
* For a parameterized SFAP command, the
|
|
7550
|
-
*
|
|
7551
|
-
*
|
|
7552
|
-
*
|
|
7553
|
-
*
|
|
7554
|
-
*
|
|
7744
|
+
* For a parameterized SFAP command, the mint params arrive on the typed
|
|
7745
|
+
* `init.onestore.mintParams` field (preferred) or, transitionally, under the
|
|
7746
|
+
* legacy `context[jwtMintParams]` seed. This interceptor reads them (onestore
|
|
7747
|
+
* first), calls `jwtManager.getJwt(params)` (which the dispatching resolver routes
|
|
7748
|
+
* to the parameterized resolver), attaches the `Authorization` header, and
|
|
7749
|
+
* rewrites the request URL via the minted `baseUri`. The presence of that
|
|
7750
|
+
* `Authorization` header is the signal the descriptor's guarded legacy interceptor
|
|
7751
|
+
* uses to skip — so the legacy path does not mint a second time.
|
|
7555
7752
|
*
|
|
7556
|
-
* For a legacy parameterless command
|
|
7557
|
-
*
|
|
7753
|
+
* For a legacy parameterless command neither channel carries mint params, so this
|
|
7754
|
+
* interceptor **early-returns on its first statement** and the request flows
|
|
7558
7755
|
* unchanged to the legacy `buildJwtRequestHeaderInterceptor` — guaranteeing zero
|
|
7559
7756
|
* behavior change for non-opted-in adapters.
|
|
7560
7757
|
*
|
|
@@ -7568,8 +7765,11 @@ function hasAuthorizationHeader([resource, options]) {
|
|
|
7568
7765
|
*/
|
|
7569
7766
|
function buildJwtParameterizationInterceptor(jwtManager, jwtRequestModifier = (_extraInfo, fetchArgs) => fetchArgs) {
|
|
7570
7767
|
return (fetchArgs, context) => {
|
|
7571
|
-
|
|
7572
|
-
|
|
7768
|
+
// Prefer the typed `init.onestore.mintParams`; fall back to the legacy
|
|
7769
|
+
// `context[JWT_MINT_PARAMS_SEED_KEY]` seed while the core producers still
|
|
7770
|
+
// write it (transitional dual-read — see JWT_MINT_PARAMS_SEED_KEY).
|
|
7771
|
+
const mintParams = fetchArgs[1]?.onestore?.mintParams ??
|
|
7772
|
+
context?.[JWT_MINT_PARAMS_SEED_KEY];
|
|
7573
7773
|
// No mint params → not a parameterized request. Do nothing; the legacy
|
|
7574
7774
|
// interceptor handles it. This MUST be the first statement so non-opted-in
|
|
7575
7775
|
// SFAP commands observe no work at all.
|
|
@@ -7664,7 +7864,8 @@ function buildJwtAuthorizedSfapFetchServiceDescriptor(logger) {
|
|
|
7664
7864
|
*
|
|
7665
7865
|
* It reuses the module's `sfapJwtManager`: `cdp_url` is emitted on the same
|
|
7666
7866
|
* `SFAP_API`-scope JWT, so the Data360 command mints the same kind of token — it
|
|
7667
|
-
* just forwards the data-cloud scopes via
|
|
7867
|
+
* just forwards the data-cloud scopes via `onestore.mintParams` (or, transitionally,
|
|
7868
|
+
* the legacy context-seed `jwtMintParams`).
|
|
7668
7869
|
*
|
|
7669
7870
|
* Gated behind its own `data_360_cdp_url_rewrite` auth-scope tag so it binds ONLY to
|
|
7670
7871
|
* the Data360 command — the SFAP fleet never flows past this interceptor.
|
|
@@ -7795,10 +7996,11 @@ function buildJwtRequestInterceptor(logger) {
|
|
|
7795
7996
|
* (`cdp_url`), and modifiers only receive `extraInfo`. So — like the copilot
|
|
7796
7997
|
* interceptor that reads `decodedInfo.iss` — it holds the token directly:
|
|
7797
7998
|
*
|
|
7798
|
-
* 1. Read the command's
|
|
7799
|
-
*
|
|
7800
|
-
* resolver inside `sfapJwtManager`).
|
|
7801
|
-
* to authorize with, so leave the
|
|
7999
|
+
* 1. Read the command's mint params from `init.onestore.mintParams` (preferred)
|
|
8000
|
+
* or the legacy per-request context seed, and mint (via the parameterized
|
|
8001
|
+
* SFAP resolver, routed by the dispatching resolver inside `sfapJwtManager`).
|
|
8002
|
+
* Without mint params there is no token to authorize with, so leave the
|
|
8003
|
+
* request untouched.
|
|
7802
8004
|
* 2. Decode `cdp_url` from the JWT and resolve the tenant-specific endpoint
|
|
7803
8005
|
* (TSE). `cdp_url` is a bare host, so a scheme is prepended before parsing
|
|
7804
8006
|
* and the protocol is forced to `https:`.
|
|
@@ -7817,7 +8019,11 @@ function buildJwtRequestInterceptor(logger) {
|
|
|
7817
8019
|
*/
|
|
7818
8020
|
function buildData360HostRewriteInterceptor(logger) {
|
|
7819
8021
|
return (fetchArgs, context) => {
|
|
7820
|
-
|
|
8022
|
+
// Prefer the typed `init.onestore.mintParams`; fall back to the legacy
|
|
8023
|
+
// `context[JWT_MINT_PARAMS_SEED_KEY]` seed while the core producers still
|
|
8024
|
+
// write it (transitional dual-read — see JWT_MINT_PARAMS_SEED_KEY).
|
|
8025
|
+
const mintParams = fetchArgs[1]?.onestore?.mintParams ??
|
|
8026
|
+
context?.[JWT_MINT_PARAMS_SEED_KEY];
|
|
7821
8027
|
// No mint params → not a parameterized Data360 request. Nothing to do.
|
|
7822
8028
|
if (mintParams === undefined) {
|
|
7823
8029
|
return resolvedPromiseLike$2(fetchArgs);
|
|
@@ -8462,6 +8668,10 @@ function getLexRuntimeDefaultInterceptorConfig(logger) {
|
|
|
8462
8668
|
buildCsrfTokenInterceptor(),
|
|
8463
8669
|
buildEntityEncodingInterceptor(),
|
|
8464
8670
|
buildFirstPartyHeaderInterceptor(),
|
|
8671
|
+
// Per-request gzip: compresses only operations that opt in via
|
|
8672
|
+
// `init.onestore.compression`; a pass-through no-op otherwise. Last in
|
|
8673
|
+
// the chain so it compresses the final body, after body-modifying interceptors.
|
|
8674
|
+
buildCompressionInterceptor(),
|
|
8465
8675
|
],
|
|
8466
8676
|
retry: buildFetchRetryInterceptor(),
|
|
8467
8677
|
response: [
|
|
@@ -9171,6 +9381,8 @@ function initializeOneStore(luvio) {
|
|
|
9171
9381
|
// across LEX and sites. Paired with the HTTP UIAPI fetch path being
|
|
9172
9382
|
// disabled on sites (see network-fetch.ts).
|
|
9173
9383
|
featureFlagsService.set('graphQLUseAura', true);
|
|
9384
|
+
// Persisted (named) GraphQL queries; no consumers yet (W-23971639).
|
|
9385
|
+
featureFlagsService.set('graphQLUsePersistedQueries', usePersistedQueries.isOpen({ fallback: false }));
|
|
9174
9386
|
const services = [
|
|
9175
9387
|
instrumentationServiceDescriptor,
|
|
9176
9388
|
buildLexRuntimeDefaultFetchServiceDescriptor(loggerService, retryService),
|
|
@@ -9237,4 +9449,4 @@ function ldsEngineCreator() {
|
|
|
9237
9449
|
}
|
|
9238
9450
|
|
|
9239
9451
|
export { PDL_ENGINE_REGISTRATION_ID, buildPredictorForContext, configService, ldsEngineCreator as default, initializeLDS, initializeOneStore, notifyUpdateAvailableFactory };
|
|
9240
|
-
// version: 1.
|
|
9452
|
+
// 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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
9
|
+
* can read the params off `context[JWT_MINT_PARAMS_SEED_KEY]`.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
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
|
|
38
|
-
*
|
|
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
|
-
*
|
|
6
|
-
*
|
|
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-aura",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.464.0",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
5
5
|
"description": "LDS engine for Aura runtime.",
|
|
6
6
|
"main": "dist/ldsEngineCreator.js",
|
|
@@ -34,53 +34,53 @@
|
|
|
34
34
|
"release:corejar": "yarn build && ../core-build/scripts/core.js --name=lds-runtime-aura"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@conduit-client/service-provisioner": "3.
|
|
38
|
-
"@conduit-client/tools-core": "3.
|
|
39
|
-
"@salesforce/lds-adapters-apex": "^1.
|
|
40
|
-
"@salesforce/lds-adapters-uiapi": "^1.
|
|
41
|
-
"@salesforce/lds-ads-bridge": "^1.
|
|
42
|
-
"@salesforce/lds-aura-storage": "^1.
|
|
43
|
-
"@salesforce/lds-bindings": "^1.
|
|
44
|
-
"@salesforce/lds-instrumentation": "^1.
|
|
45
|
-
"@salesforce/lds-network-adapter": "^1.
|
|
46
|
-
"@salesforce/lds-network-aura": "^1.
|
|
47
|
-
"@salesforce/lds-network-fetch": "^1.
|
|
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.
|
|
52
|
-
"@conduit-client/command-aura-network": "3.
|
|
53
|
-
"@conduit-client/command-aura-normalized-cache-control": "3.
|
|
54
|
-
"@conduit-client/command-fetch-network": "3.
|
|
55
|
-
"@conduit-client/command-http-graphql-normalized-cache-control": "3.
|
|
56
|
-
"@conduit-client/command-http-normalized-cache-control": "3.
|
|
57
|
-
"@conduit-client/command-ndjson": "3.
|
|
58
|
-
"@conduit-client/command-network": "3.
|
|
59
|
-
"@conduit-client/command-sse": "3.
|
|
60
|
-
"@conduit-client/command-streaming": "3.
|
|
61
|
-
"@conduit-client/jwt-manager": "3.
|
|
62
|
-
"@conduit-client/service-aura-network": "3.
|
|
63
|
-
"@conduit-client/service-bindings-imperative": "3.
|
|
64
|
-
"@conduit-client/service-bindings-lwc": "3.
|
|
65
|
-
"@conduit-client/service-cache": "3.
|
|
66
|
-
"@conduit-client/service-cache-control": "3.
|
|
67
|
-
"@conduit-client/service-cache-inclusion-policy": "3.
|
|
68
|
-
"@conduit-client/service-config": "3.
|
|
69
|
-
"@conduit-client/service-feature-flags": "3.
|
|
70
|
-
"@conduit-client/service-fetch-network": "3.
|
|
71
|
-
"@conduit-client/service-instrument-command": "3.
|
|
72
|
-
"@conduit-client/service-pubsub": "3.
|
|
73
|
-
"@conduit-client/service-renewable-resource-manager": "3.
|
|
74
|
-
"@conduit-client/service-store": "3.
|
|
75
|
-
"@conduit-client/utils": "3.
|
|
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.
|
|
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.
|
|
82
|
-
"@salesforce/lds-luvio-service": "^1.
|
|
83
|
-
"@salesforce/lds-luvio-uiapi-records-service": "^1.
|
|
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
|
{
|