@salesforce/lds-network-fetch 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/ldsNetworkFetch.js
CHANGED
|
@@ -850,7 +850,15 @@ async function handleSendFetchRequest(resourceRequest, resourceRequestContext, i
|
|
|
850
850
|
// the intial then here can also most likely be moved to a response interceptor as well
|
|
851
851
|
interceptedResponse
|
|
852
852
|
.then((value) => {
|
|
853
|
-
|
|
853
|
+
// Only persist successful responses. fetchNetworkAdapter resolves (does not reject)
|
|
854
|
+
// for 4xx/5xx and returns null/text bodies for 204/non-JSON, and a durable cache hit
|
|
855
|
+
// is replayed as a synthetic 200 (see createOkResponse above), so caching a failure
|
|
856
|
+
// body would later be ingested as a successful response.
|
|
857
|
+
if (storageAndLogger !== null &&
|
|
858
|
+
storageAndLogger.storage !== null &&
|
|
859
|
+
value.ok !== false &&
|
|
860
|
+
value.body !== null &&
|
|
861
|
+
value.body !== undefined) {
|
|
854
862
|
storageAndLogger.storage.set(transactionKey, value.body).catch((_error) => { });
|
|
855
863
|
}
|
|
856
864
|
internalRequestLogger.resolve(resourceRequest, value);
|
|
@@ -909,13 +917,11 @@ function instrument(newInstrumentation) {
|
|
|
909
917
|
}
|
|
910
918
|
|
|
911
919
|
/**
|
|
912
|
-
* Canonical registry of
|
|
913
|
-
* read/write nature —
|
|
914
|
-
*
|
|
915
|
-
*
|
|
916
|
-
*
|
|
917
|
-
* from `network-fetch.ts`. Templates use `{segment}` placeholders (see
|
|
918
|
-
* `compilePathMatchers`); callers normalize trailing slashes.
|
|
920
|
+
* Canonical registry of UIAPI url paths LDS sends over HTTP, and their
|
|
921
|
+
* read/write nature — shared by every runtime's transport allowlist and
|
|
922
|
+
* read-POST retry classifier. A leaf module (no local imports), since
|
|
923
|
+
* `network-fetch.ts` imports the retry interceptor, which must import paths
|
|
924
|
+
* from here rather than back from `network-fetch.ts`.
|
|
919
925
|
*/
|
|
920
926
|
/**
|
|
921
927
|
* Public UIAPI paths verified for HTTP transport: no `UiTier` Connect
|
|
@@ -933,10 +939,13 @@ const UIAPI_PUBLIC_PATH_ENTRIES = [
|
|
|
933
939
|
// getPicklistValuesByRecordType — IPicklistValuesByRecordTypeResource
|
|
934
940
|
{ path: '/ui-api/object-info/{objectApiName}/picklist-values/{recordTypeId}' },
|
|
935
941
|
// ----- Records -----
|
|
936
|
-
// createRecord (POST) — IRecordDataCollectionResource
|
|
937
|
-
|
|
938
|
-
//
|
|
939
|
-
{ path: '/ui-api/records
|
|
942
|
+
// createRecord (POST) — IRecordDataCollectionResource.
|
|
943
|
+
// Onboarded to the server Idempotency-Key feature: the interceptor mints a
|
|
944
|
+
// key for the POST so a throttled create can replay safely (dedupe by key).
|
|
945
|
+
{ path: '/ui-api/records', idempotentWrite: true },
|
|
946
|
+
// getRecord / updateRecord (PATCH) / deleteRecord (DELETE) — IRecordDataResource.
|
|
947
|
+
// Onboarded to the server Idempotency-Key feature (PATCH/DELETE writes).
|
|
948
|
+
{ path: '/ui-api/records/{recordId}', idempotentWrite: true },
|
|
940
949
|
// getRecords (batch GET) — IBatchRecordDataResource
|
|
941
950
|
{ path: '/ui-api/records/batch/{recordIds}' },
|
|
942
951
|
// executeBatchRecordOperations (POST, no recordIds segment) — IBatchRecordResource
|
|
@@ -1127,6 +1136,34 @@ function isRetryableReadPost(basePath) {
|
|
|
1127
1136
|
}
|
|
1128
1137
|
return RETRYABLE_READ_POST_MATCHERS.some((matcher) => matcher.test(path));
|
|
1129
1138
|
}
|
|
1139
|
+
// The idempotent-write subset, precompiled once at module load from the same
|
|
1140
|
+
// entries so a flagged path is always also a registered transport path.
|
|
1141
|
+
const IDEMPOTENT_WRITE_MATCHERS = compilePathMatchers([...UIAPI_PUBLIC_PATH_ENTRIES, ...UIAPI_PRIVATE_PATH_ENTRIES]
|
|
1142
|
+
.filter((entry) => entry.idempotentWrite)
|
|
1143
|
+
.map((entry) => entry.path));
|
|
1144
|
+
// Non-flagged templates that structurally collide with a flagged one (e.g.
|
|
1145
|
+
// `/ui-api/records/batch` vs `/ui-api/records/{recordId}`) — a match here means
|
|
1146
|
+
// the concrete path is the sibling, not an idempotent write. Derived from the
|
|
1147
|
+
// registry so a new sibling can't silently inherit minting.
|
|
1148
|
+
const IDEMPOTENT_WRITE_EXCLUSION_MATCHERS = compilePathMatchers([...UIAPI_PUBLIC_PATH_ENTRIES, ...UIAPI_PRIVATE_PATH_ENTRIES]
|
|
1149
|
+
.filter((entry) => !entry.idempotentWrite)
|
|
1150
|
+
.map((entry) => entry.path));
|
|
1151
|
+
/**
|
|
1152
|
+
* True when the request path is onboarded to the Idempotency-Key feature.
|
|
1153
|
+
* Gates minting only; must match a flagged template and not a more-specific
|
|
1154
|
+
* non-flagged sibling (see IDEMPOTENT_WRITE_EXCLUSION_MATCHERS).
|
|
1155
|
+
*/
|
|
1156
|
+
function isIdempotentWrite(basePath) {
|
|
1157
|
+
if (!basePath) {
|
|
1158
|
+
return false;
|
|
1159
|
+
}
|
|
1160
|
+
let path = basePath.trim();
|
|
1161
|
+
if (path.endsWith('/')) {
|
|
1162
|
+
path = path.substring(0, path.length - 1);
|
|
1163
|
+
}
|
|
1164
|
+
return (IDEMPOTENT_WRITE_MATCHERS.some((matcher) => matcher.test(path)) &&
|
|
1165
|
+
!IDEMPOTENT_WRITE_EXCLUSION_MATCHERS.some((matcher) => matcher.test(path)));
|
|
1166
|
+
}
|
|
1130
1167
|
|
|
1131
1168
|
/**
|
|
1132
1169
|
* Extracts the HTTP method from FetchParameters, matching the Fetch API's own
|
|
@@ -1165,10 +1202,11 @@ function isRetryableOperationType(operationType) {
|
|
|
1165
1202
|
return operationType === 'query';
|
|
1166
1203
|
}
|
|
1167
1204
|
/**
|
|
1168
|
-
*
|
|
1169
|
-
*
|
|
1205
|
+
* True for a mutating HTTP method (POST/PUT/PATCH/DELETE) — gates both CSRF
|
|
1206
|
+
* token attachment and Idempotency-Key minting, since both apply to the same
|
|
1207
|
+
* set of methods.
|
|
1170
1208
|
*/
|
|
1171
|
-
function
|
|
1209
|
+
function isMutatingMethod(method) {
|
|
1172
1210
|
if (!method) {
|
|
1173
1211
|
return false;
|
|
1174
1212
|
}
|
|
@@ -1178,6 +1216,48 @@ function isCsrfMethod(method) {
|
|
|
1178
1216
|
normalizedMethod === 'patch' ||
|
|
1179
1217
|
normalizedMethod === 'delete');
|
|
1180
1218
|
}
|
|
1219
|
+
const HTTP_HEADER_RETRY_AFTER = 'retry-after';
|
|
1220
|
+
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$/;
|
|
1221
|
+
/**
|
|
1222
|
+
* Parses a `Retry-After` header (case-insensitive lookup) into milliseconds.
|
|
1223
|
+
* Accepts fractional delay-seconds — the idempotency handler and the mobile
|
|
1224
|
+
* reference parser both emit them — or an IMF-fixdate. `Number.isFinite`
|
|
1225
|
+
* rejects `Infinity`/`-Infinity`, which otherwise round-trip cleanly through
|
|
1226
|
+
* `parseFloat` and would produce a non-finite delay.
|
|
1227
|
+
*/
|
|
1228
|
+
function parseRetryAfterMs(headers) {
|
|
1229
|
+
if (!headers) {
|
|
1230
|
+
return undefined;
|
|
1231
|
+
}
|
|
1232
|
+
let value;
|
|
1233
|
+
for (const key in headers) {
|
|
1234
|
+
if (key.toLowerCase() === HTTP_HEADER_RETRY_AFTER) {
|
|
1235
|
+
value = headers[key];
|
|
1236
|
+
break;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
if (!value) {
|
|
1240
|
+
return undefined;
|
|
1241
|
+
}
|
|
1242
|
+
const trimmed = value.trim();
|
|
1243
|
+
const seconds = parseFloat(trimmed);
|
|
1244
|
+
if (Number.isFinite(seconds) && String(seconds) === trimmed) {
|
|
1245
|
+
return seconds >= 0 ? Math.round(seconds * 1000) : undefined;
|
|
1246
|
+
}
|
|
1247
|
+
if (!IMF_FIXDATE.test(trimmed)) {
|
|
1248
|
+
return undefined;
|
|
1249
|
+
}
|
|
1250
|
+
const millis = Date.parse(trimmed);
|
|
1251
|
+
return Number.isFinite(millis) ? Math.max(0, millis - Date.now()) : undefined;
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Reads the `errorCode` off a UiApiError body. UIAPI returns either a single
|
|
1255
|
+
* `{ errorCode, message }` object or a `[{ errorCode }]` array; handle both.
|
|
1256
|
+
*/
|
|
1257
|
+
function readUiApiErrorCode(response) {
|
|
1258
|
+
const body = response.body;
|
|
1259
|
+
return Array.isArray(body) ? body[0]?.errorCode : body?.errorCode;
|
|
1260
|
+
}
|
|
1181
1261
|
|
|
1182
|
-
export { UIAPI_PRIVATE_PATHS, UIAPI_PUBLIC_PATHS, compilePathMatchers, getFetchMethod, instrument,
|
|
1183
|
-
// version: 1.
|
|
1262
|
+
export { UIAPI_PRIVATE_PATHS, UIAPI_PUBLIC_PATHS, compilePathMatchers, getFetchMethod, instrument, isIdempotentWrite, isMutatingMethod, isRetryableMethod, isRetryableOperationType, isRetryableReadPost, parseRetryAfterMs, readUiApiErrorCode, setupFetchNetworkAdapter, setupLexNetworkAdapter };
|
|
1263
|
+
// version: 1.464.0-e54c238397
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FetchParameters } from '@conduit-client/service-fetch-network/v1';
|
|
2
|
+
import type { FetchResponse } from '@luvio/engine';
|
|
2
3
|
/**
|
|
3
4
|
* Extracts the HTTP method from FetchParameters, matching the Fetch API's own
|
|
4
5
|
* precedence: an options.method override wins over a Request object's method
|
|
@@ -24,7 +25,21 @@ export declare function isRetryableMethod(method: string | undefined): boolean;
|
|
|
24
25
|
*/
|
|
25
26
|
export declare function isRetryableOperationType(operationType: string | undefined): boolean;
|
|
26
27
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
28
|
+
* True for a mutating HTTP method (POST/PUT/PATCH/DELETE) — gates both CSRF
|
|
29
|
+
* token attachment and Idempotency-Key minting, since both apply to the same
|
|
30
|
+
* set of methods.
|
|
29
31
|
*/
|
|
30
|
-
export declare function
|
|
32
|
+
export declare function isMutatingMethod(method: string | undefined): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Parses a `Retry-After` header (case-insensitive lookup) into milliseconds.
|
|
35
|
+
* Accepts fractional delay-seconds — the idempotency handler and the mobile
|
|
36
|
+
* reference parser both emit them — or an IMF-fixdate. `Number.isFinite`
|
|
37
|
+
* rejects `Infinity`/`-Infinity`, which otherwise round-trip cleanly through
|
|
38
|
+
* `parseFloat` and would produce a non-finite delay.
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseRetryAfterMs(headers: Record<string, string> | undefined): number | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Reads the `errorCode` off a UiApiError body. UIAPI returns either a single
|
|
43
|
+
* `{ errorCode, message }` object or a `[{ errorCode }]` array; handle both.
|
|
44
|
+
*/
|
|
45
|
+
export declare function readUiApiErrorCode(response: FetchResponse<any>): string | undefined;
|
package/dist/types/main.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export { setupFetchNetworkAdapter, setupLexNetworkAdapter } from './networkAdapt
|
|
|
2
2
|
export type { RequestLogger } from './networkAdapter';
|
|
3
3
|
export type { RequestInterceptor, ResponseInterceptor, LuvioRetryInterceptor, Interceptors, } from './utils';
|
|
4
4
|
export { instrument, FetchNetworkInstrumentation } from './instrumentation';
|
|
5
|
-
export { UIAPI_PUBLIC_PATHS, UIAPI_PRIVATE_PATHS, compilePathMatchers, isRetryableReadPost, } from './uiapi-http-paths';
|
|
6
|
-
export { getFetchMethod, isRetryableMethod, isRetryableOperationType,
|
|
5
|
+
export { UIAPI_PUBLIC_PATHS, UIAPI_PRIVATE_PATHS, compilePathMatchers, isRetryableReadPost, isIdempotentWrite, } from './uiapi-http-paths';
|
|
6
|
+
export { getFetchMethod, isRetryableMethod, isRetryableOperationType, isMutatingMethod, parseRetryAfterMs, readUiApiErrorCode, } from './http-utils';
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Canonical registry of
|
|
3
|
-
* read/write nature —
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* from `network-fetch.ts`. Templates use `{segment}` placeholders (see
|
|
8
|
-
* `compilePathMatchers`); callers normalize trailing slashes.
|
|
2
|
+
* Canonical registry of UIAPI url paths LDS sends over HTTP, and their
|
|
3
|
+
* read/write nature — shared by every runtime's transport allowlist and
|
|
4
|
+
* read-POST retry classifier. A leaf module (no local imports), since
|
|
5
|
+
* `network-fetch.ts` imports the retry interceptor, which must import paths
|
|
6
|
+
* from here rather than back from `network-fetch.ts`.
|
|
9
7
|
*/
|
|
10
8
|
/** Public transport paths, derived from the canonical registry above. */
|
|
11
9
|
export declare const UIAPI_PUBLIC_PATHS: string[];
|
|
@@ -23,3 +21,9 @@ export declare function compilePathMatchers(paths: string[]): RegExp[];
|
|
|
23
21
|
* the same way the transport allowlist does before matching.
|
|
24
22
|
*/
|
|
25
23
|
export declare function isRetryableReadPost(basePath: string | undefined): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* True when the request path is onboarded to the Idempotency-Key feature.
|
|
26
|
+
* Gates minting only; must match a flagged template and not a more-specific
|
|
27
|
+
* non-flagged sibling (see IDEMPOTENT_WRITE_EXCLUSION_MATCHERS).
|
|
28
|
+
*/
|
|
29
|
+
export declare function isIdempotentWrite(basePath: string | undefined): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/lds-network-fetch",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.464.0",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
5
5
|
"description": "LDS Network Adapter using fetch",
|
|
6
6
|
"main": "dist/ldsNetworkFetch.js",
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
"release:corejar": "yarn build && ../core-build/scripts/core.js --name=lds-network-fetch"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@conduit-client/service-fetch-network": "3.
|
|
37
|
+
"@conduit-client/service-fetch-network": "3.32.0",
|
|
38
38
|
"@luvio/engine": "0.161.2",
|
|
39
39
|
"@luvio/network-adapter-fetch": "0.161.2"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@salesforce/lds-network-aura": "^1.
|
|
42
|
+
"@salesforce/lds-network-aura": "^1.464.0"
|
|
43
43
|
},
|
|
44
44
|
"volta": {
|
|
45
45
|
"extends": "../../package.json"
|