@salesforce/lds-network-fetch 1.461.0 → 1.463.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.
@@ -909,13 +909,11 @@ function instrument(newInstrumentation) {
909
909
  }
910
910
 
911
911
  /**
912
- * Canonical registry of the UIAPI url paths LDS sends over HTTP and their
913
- * read/write nature — the single source of truth for every runtime's transport
914
- * allowlist and read-POST retry classifier. A leaf module (no local imports) so
915
- * both can consume it without an import cycle: `network-fetch.ts` imports the
916
- * retry interceptor, so the retry layer must import paths from here, not back
917
- * from `network-fetch.ts`. Templates use `{segment}` placeholders (see
918
- * `compilePathMatchers`); callers normalize trailing slashes.
912
+ * Canonical registry of UIAPI url paths LDS sends over HTTP, and their
913
+ * read/write nature — shared by every runtime's transport allowlist and
914
+ * read-POST retry classifier. A leaf module (no local imports), since
915
+ * `network-fetch.ts` imports the retry interceptor, which must import paths
916
+ * from here rather than back from `network-fetch.ts`.
919
917
  */
920
918
  /**
921
919
  * Public UIAPI paths verified for HTTP transport: no `UiTier` Connect
@@ -933,10 +931,13 @@ const UIAPI_PUBLIC_PATH_ENTRIES = [
933
931
  // getPicklistValuesByRecordType — IPicklistValuesByRecordTypeResource
934
932
  { path: '/ui-api/object-info/{objectApiName}/picklist-values/{recordTypeId}' },
935
933
  // ----- Records -----
936
- // createRecord (POST) — IRecordDataCollectionResource
937
- { path: '/ui-api/records' },
938
- // getRecord / updateRecord (PATCH) / deleteRecord (DELETE) — IRecordDataResource
939
- { path: '/ui-api/records/{recordId}' },
934
+ // createRecord (POST) — IRecordDataCollectionResource.
935
+ // Onboarded to the server Idempotency-Key feature: the interceptor mints a
936
+ // key for the POST so a throttled create can replay safely (dedupe by key).
937
+ { path: '/ui-api/records', idempotentWrite: true },
938
+ // getRecord / updateRecord (PATCH) / deleteRecord (DELETE) — IRecordDataResource.
939
+ // Onboarded to the server Idempotency-Key feature (PATCH/DELETE writes).
940
+ { path: '/ui-api/records/{recordId}', idempotentWrite: true },
940
941
  // getRecords (batch GET) — IBatchRecordDataResource
941
942
  { path: '/ui-api/records/batch/{recordIds}' },
942
943
  // executeBatchRecordOperations (POST, no recordIds segment) — IBatchRecordResource
@@ -1127,6 +1128,34 @@ function isRetryableReadPost(basePath) {
1127
1128
  }
1128
1129
  return RETRYABLE_READ_POST_MATCHERS.some((matcher) => matcher.test(path));
1129
1130
  }
1131
+ // The idempotent-write subset, precompiled once at module load from the same
1132
+ // entries so a flagged path is always also a registered transport path.
1133
+ const IDEMPOTENT_WRITE_MATCHERS = compilePathMatchers([...UIAPI_PUBLIC_PATH_ENTRIES, ...UIAPI_PRIVATE_PATH_ENTRIES]
1134
+ .filter((entry) => entry.idempotentWrite)
1135
+ .map((entry) => entry.path));
1136
+ // Non-flagged templates that structurally collide with a flagged one (e.g.
1137
+ // `/ui-api/records/batch` vs `/ui-api/records/{recordId}`) — a match here means
1138
+ // the concrete path is the sibling, not an idempotent write. Derived from the
1139
+ // registry so a new sibling can't silently inherit minting.
1140
+ const IDEMPOTENT_WRITE_EXCLUSION_MATCHERS = compilePathMatchers([...UIAPI_PUBLIC_PATH_ENTRIES, ...UIAPI_PRIVATE_PATH_ENTRIES]
1141
+ .filter((entry) => !entry.idempotentWrite)
1142
+ .map((entry) => entry.path));
1143
+ /**
1144
+ * True when the request path is onboarded to the Idempotency-Key feature.
1145
+ * Gates minting only; must match a flagged template and not a more-specific
1146
+ * non-flagged sibling (see IDEMPOTENT_WRITE_EXCLUSION_MATCHERS).
1147
+ */
1148
+ function isIdempotentWrite(basePath) {
1149
+ if (!basePath) {
1150
+ return false;
1151
+ }
1152
+ let path = basePath.trim();
1153
+ if (path.endsWith('/')) {
1154
+ path = path.substring(0, path.length - 1);
1155
+ }
1156
+ return (IDEMPOTENT_WRITE_MATCHERS.some((matcher) => matcher.test(path)) &&
1157
+ !IDEMPOTENT_WRITE_EXCLUSION_MATCHERS.some((matcher) => matcher.test(path)));
1158
+ }
1130
1159
 
1131
1160
  /**
1132
1161
  * Extracts the HTTP method from FetchParameters, matching the Fetch API's own
@@ -1165,10 +1194,11 @@ function isRetryableOperationType(operationType) {
1165
1194
  return operationType === 'query';
1166
1195
  }
1167
1196
  /**
1168
- * Determines if the HTTP method requires CSRF protection.
1169
- * Only mutating operations (POST, PUT, PATCH, DELETE) require CSRF tokens.
1197
+ * True for a mutating HTTP method (POST/PUT/PATCH/DELETE) — gates both CSRF
1198
+ * token attachment and Idempotency-Key minting, since both apply to the same
1199
+ * set of methods.
1170
1200
  */
1171
- function isCsrfMethod(method) {
1201
+ function isMutatingMethod(method) {
1172
1202
  if (!method) {
1173
1203
  return false;
1174
1204
  }
@@ -1178,6 +1208,48 @@ function isCsrfMethod(method) {
1178
1208
  normalizedMethod === 'patch' ||
1179
1209
  normalizedMethod === 'delete');
1180
1210
  }
1211
+ const HTTP_HEADER_RETRY_AFTER = 'retry-after';
1212
+ 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$/;
1213
+ /**
1214
+ * Parses a `Retry-After` header (case-insensitive lookup) into milliseconds.
1215
+ * Accepts fractional delay-seconds — the idempotency handler and the mobile
1216
+ * reference parser both emit them — or an IMF-fixdate. `Number.isFinite`
1217
+ * rejects `Infinity`/`-Infinity`, which otherwise round-trip cleanly through
1218
+ * `parseFloat` and would produce a non-finite delay.
1219
+ */
1220
+ function parseRetryAfterMs(headers) {
1221
+ if (!headers) {
1222
+ return undefined;
1223
+ }
1224
+ let value;
1225
+ for (const key in headers) {
1226
+ if (key.toLowerCase() === HTTP_HEADER_RETRY_AFTER) {
1227
+ value = headers[key];
1228
+ break;
1229
+ }
1230
+ }
1231
+ if (!value) {
1232
+ return undefined;
1233
+ }
1234
+ const trimmed = value.trim();
1235
+ const seconds = parseFloat(trimmed);
1236
+ if (Number.isFinite(seconds) && String(seconds) === trimmed) {
1237
+ return seconds >= 0 ? Math.round(seconds * 1000) : undefined;
1238
+ }
1239
+ if (!IMF_FIXDATE.test(trimmed)) {
1240
+ return undefined;
1241
+ }
1242
+ const millis = Date.parse(trimmed);
1243
+ return Number.isFinite(millis) ? Math.max(0, millis - Date.now()) : undefined;
1244
+ }
1245
+ /**
1246
+ * Reads the `errorCode` off a UiApiError body. UIAPI returns either a single
1247
+ * `{ errorCode, message }` object or a `[{ errorCode }]` array; handle both.
1248
+ */
1249
+ function readUiApiErrorCode(response) {
1250
+ const body = response.body;
1251
+ return Array.isArray(body) ? body[0]?.errorCode : body?.errorCode;
1252
+ }
1181
1253
 
1182
- export { UIAPI_PRIVATE_PATHS, UIAPI_PUBLIC_PATHS, compilePathMatchers, getFetchMethod, instrument, isCsrfMethod, isRetryableMethod, isRetryableOperationType, isRetryableReadPost, setupFetchNetworkAdapter, setupLexNetworkAdapter };
1183
- // version: 1.461.0-37b1c9d1cb
1254
+ export { UIAPI_PRIVATE_PATHS, UIAPI_PUBLIC_PATHS, compilePathMatchers, getFetchMethod, instrument, isIdempotentWrite, isMutatingMethod, isRetryableMethod, isRetryableOperationType, isRetryableReadPost, parseRetryAfterMs, readUiApiErrorCode, setupFetchNetworkAdapter, setupLexNetworkAdapter };
1255
+ // version: 1.463.0-ea9ecd17fc
@@ -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
- * Determines if the HTTP method requires CSRF protection.
28
- * Only mutating operations (POST, PUT, PATCH, DELETE) require CSRF tokens.
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 isCsrfMethod(method: string | undefined): boolean;
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;
@@ -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, isCsrfMethod, } from './http-utils';
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 the UIAPI url paths LDS sends over HTTP and their
3
- * read/write nature — the single source of truth for every runtime's transport
4
- * allowlist and read-POST retry classifier. A leaf module (no local imports) so
5
- * both can consume it without an import cycle: `network-fetch.ts` imports the
6
- * retry interceptor, so the retry layer must import paths from here, not back
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.461.0",
3
+ "version": "1.463.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.28.0",
37
+ "@conduit-client/service-fetch-network": "3.30.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.461.0"
42
+ "@salesforce/lds-network-aura": "^1.463.0"
43
43
  },
44
44
  "volta": {
45
45
  "extends": "../../package.json"