@salesforce/lds-runtime-mobile 1.252.0 → 1.256.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/main.js CHANGED
@@ -18,6 +18,7 @@ import excludeStaleRecordsGate from '@salesforce/gate/lds.graphqlEvalExcludeStal
18
18
  import { parseAndVisit, Kind, buildSchema, isObjectType, defaultFieldResolver, visit, execute, parse as parse$7, extendSchema, isScalarType } from '@luvio/graphql-parser';
19
19
  import { RECORD_ID_PREFIX, RECORD_FIELDS_KEY_JUNCTION, isStoreKeyRecordViewEntity, getRecordId18, RECORD_REPRESENTATION_NAME, extractRecordIdFromStoreKey, keyBuilderQuickActionExecutionRepresentation, ingestQuickActionExecutionRepresentation, keyBuilderContentDocumentCompositeRepresentation, getResponseCacheKeysContentDocumentCompositeRepresentation, keyBuilderFromTypeContentDocumentCompositeRepresentation, ingestContentDocumentCompositeRepresentation, keyBuilderRecord, RECORD_VIEW_ENTITY_ID_PREFIX, getTypeCacheKeysRecord, keyBuilderFromTypeRecordRepresentation, ingestRecord, RecordRepresentationRepresentationType, ObjectInfoRepresentationType, getRecordAdapterFactory, getObjectInfoAdapterFactory, getObjectInfosAdapterFactory, getObjectInfoDirectoryAdapterFactory, UiApiNamespace, RecordRepresentationType, RecordRepresentationTTL, RecordRepresentationVersion, getRecordsAdapterFactory } from '@salesforce/lds-adapters-uiapi';
20
20
  import ldsIdempotencyWriteDisabled from '@salesforce/gate/lds.idempotencyWriteDisabled';
21
+ import ldsBackdatingEnabled from '@salesforce/gate/lds.backdatingEnabled';
21
22
  import caseSensitiveUserId from '@salesforce/user/Id';
22
23
  import { idleDetector, getInstrumentation } from 'o11y/client';
23
24
  import ldsUseShortUrlGate from '@salesforce/gate/lds.useShortUrl';
@@ -4365,8 +4366,9 @@ function rootRecordQuery(selection, input) {
4365
4366
  // If there is no metadata for this query or it somehow lacks a timestamp
4366
4367
  // skip setting the root timestamp
4367
4368
  if (queryMetadata !== undefined && queryMetadata.ingestionTimestamp !== undefined) {
4368
- // subtract 1000ms from timestamp to account for ingestion processing time
4369
- input.rootTimestamp = queryMetadata.ingestionTimestamp - 1000;
4369
+ // adjust the timestamp to account for ingestion processing time
4370
+ // 30s is used because this is the default record TTL
4371
+ input.rootTimestamp = queryMetadata.ingestionTimestamp - 30000;
4370
4372
  }
4371
4373
  }
4372
4374
  return recordQuery(selection, alias, apiName, [], input);
@@ -6049,6 +6051,8 @@ class DurableDraftStore {
6049
6051
  }
6050
6052
  }
6051
6053
 
6054
+ const DEFAULT_FIELD_LAST_MODIFIED_DATE$1 = 'LastModifiedDate';
6055
+ const DEFAULT_FIELD_CREATED_DATE$1 = 'CreatedDate';
6052
6056
  class AbstractResourceRequestActionHandler {
6053
6057
  constructor(draftQueue, networkAdapter, getLuvio) {
6054
6058
  this.draftQueue = draftQueue;
@@ -6067,6 +6071,7 @@ class AbstractResourceRequestActionHandler {
6067
6071
  this.isLdsIdempotencyWriteDisabled = ldsIdempotencyWriteDisabled.isOpen({
6068
6072
  fallback: false,
6069
6073
  });
6074
+ this.isBackdatingEnabled = ldsBackdatingEnabled.isOpen({ fallback: false });
6070
6075
  }
6071
6076
  enqueue(data) {
6072
6077
  return this.draftQueue.enqueue(this.handlerId, data);
@@ -6137,6 +6142,17 @@ class AbstractResourceRequestActionHandler {
6137
6142
  }
6138
6143
  }
6139
6144
  }
6145
+ if (this.isBackdatingEnabled &&
6146
+ response.status === HttpStatusCode.BadRequest &&
6147
+ this.isBackdatingError(response.body, action)) {
6148
+ updatedAction.timestamp = Date.now();
6149
+ updatedAction.data.body.fields = {
6150
+ ...updatedAction.data.body.fields,
6151
+ LastModifiedDate: new Date(updatedAction.timestamp).toISOString(),
6152
+ };
6153
+ shouldRetry = true;
6154
+ actionDataChanged = true;
6155
+ }
6140
6156
  await actionErrored(shouldRetry
6141
6157
  ? updatedAction
6142
6158
  : {
@@ -6170,7 +6186,21 @@ class AbstractResourceRequestActionHandler {
6170
6186
  }
6171
6187
  // checks if the body is an array of UiApiError. Sometimes the body has `enhancedErrorType` field as an error indicator(one example is the field validation failure). In such case Action being processed updates to an Error Action.
6172
6188
  isUiApiErrors(body) {
6173
- return body !== undefined && Array.isArray(body) && body.length > 0 && body[0].errorCode;
6189
+ return body !== undefined && isArray$3(body) && body.length > 0 && body[0].errorCode;
6190
+ }
6191
+ isBackdatingError(body, action) {
6192
+ if (body.enhancedErrorType &&
6193
+ body.enhancedErrorType === 'RecordError' &&
6194
+ body.output &&
6195
+ body.output.errors &&
6196
+ isArray$3(body.output.errors) &&
6197
+ body.output.errors.length > 0 &&
6198
+ action.data.body &&
6199
+ action.data.body.fields &&
6200
+ action.data.body.fields[DEFAULT_FIELD_LAST_MODIFIED_DATE$1]) {
6201
+ return body.output.errors.some((error) => error.errorCode === 'CollisionDetectedException');
6202
+ }
6203
+ return false;
6174
6204
  }
6175
6205
  async buildPendingAction(request, queue) {
6176
6206
  const targetId = await this.getIdFromRequest(request);
@@ -7112,7 +7142,7 @@ function isArrayLike(x) {
7112
7142
 
7113
7143
  const { create: create$4, keys: keys$4, values: values$2, entries: entries$3, assign: assign$4 } = Object;
7114
7144
  const { stringify: stringify$4, parse: parse$4 } = JSON;
7115
- const { isArray: isArray$2, from: from$1 } = Array;
7145
+ const { isArray: isArray$2, from: from$2 } = Array;
7116
7146
 
7117
7147
  function recordLoaderFactory(query) {
7118
7148
  async function batchRecordQuery(ids) {
@@ -9326,8 +9356,9 @@ async function fetchIngestionTimeStampFromDatabase(apiName, info, args, query) {
9326
9356
  const results = await query(sql, [key]);
9327
9357
  const [timestamp] = results.rows.map((row) => row[0]);
9328
9358
  if (timestamp !== null && typeof timestamp === 'number') {
9329
- //go back 1000 ms to adjust for margin of error when top level query is stored and when raml objects are stored
9330
- ingestionTimestamp = timestamp - 1000;
9359
+ // adjust the timestamp to account for ingestion processing time
9360
+ // 30s is used because this is the default record TTL
9361
+ ingestionTimestamp = timestamp - 30000;
9331
9362
  }
9332
9363
  }
9333
9364
  return ingestionTimestamp;
@@ -9483,7 +9514,7 @@ function generateRecordQueries(schema, objectInfoMap) {
9483
9514
  recordQueries: extensionWrapper,
9484
9515
  recordConnections,
9485
9516
  recordExtensions,
9486
- polyFieldTypeNameArr: from$1(allPolymorphicFieldTypeNames),
9517
+ polyFieldTypeNameArr: from$2(allPolymorphicFieldTypeNames),
9487
9518
  };
9488
9519
  }
9489
9520
  /**
@@ -11457,7 +11488,7 @@ function referenceIdFieldForRelationship(relationshipName) {
11457
11488
  const { keys: keys$3, values: values$1, create: create$3, assign: assign$3, freeze } = Object;
11458
11489
  const { stringify: stringify$3, parse: parse$3 } = JSON;
11459
11490
  const { shift } = Array.prototype;
11460
- const { isArray: isArray$1 } = Array;
11491
+ const { isArray: isArray$1, from: from$1 } = Array;
11461
11492
 
11462
11493
  function isFieldLink(field) {
11463
11494
  const { value } = field;
@@ -12251,7 +12282,7 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
12251
12282
  }
12252
12283
  async buildPendingAction(request, queue) {
12253
12284
  const resolvedRequest = this.resolveResourceRequest(request);
12254
- const pendingAction = (await super.buildPendingAction(resolvedRequest, queue));
12285
+ let pendingAction = (await super.buildPendingAction(resolvedRequest, queue));
12255
12286
  const { tag, targetId } = pendingAction;
12256
12287
  const targetApiName = await this.getApiNameForRecordId(targetId, tag, resolvedRequest);
12257
12288
  pendingAction.metadata[LDS_ACTION_METADATA_API_NAME] = targetApiName;
@@ -12297,8 +12328,49 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
12297
12328
  this.fetchReferenceRecord(referenceFields);
12298
12329
  }
12299
12330
  }
12331
+ // handles backdating
12332
+ if (this.isBackdatingEnabled &&
12333
+ pendingAction.data &&
12334
+ pendingAction.data.method !== 'delete' && //'delete' action does not have fields
12335
+ metaDataResult.data &&
12336
+ metaDataResult.data.objectInfos) {
12337
+ const objectInfo = metaDataResult.data.objectInfos.get(targetApiName);
12338
+ if (objectInfo === undefined) {
12339
+ throw Error(`Could not generate draft. Object info is missing`);
12340
+ }
12341
+ const appendedFields = this.getBackdatingFields(objectInfo, resolvedRequest.method, pendingAction);
12342
+ if (keys$3(appendedFields).length > 0) {
12343
+ pendingAction.data.body = {
12344
+ ...pendingAction.data.body,
12345
+ fields: {
12346
+ ...pendingAction.data.body.fields,
12347
+ ...appendedFields,
12348
+ },
12349
+ };
12350
+ }
12351
+ }
12300
12352
  return pendingAction;
12301
12353
  }
12354
+ getBackdatingFields(objectInfo, requestMethod, pendingAction) {
12355
+ const fields = {};
12356
+ const actionFieldNames = keys$3(pendingAction.data.body.fields);
12357
+ if (requestMethod === 'post') {
12358
+ // `CreateRecord` with `CreatedDate` field
12359
+ if (isBackdatingFieldEditable(objectInfo, DEFAULT_FIELD_CREATED_DATE$1, 'createable', actionFieldNames)) {
12360
+ fields[DEFAULT_FIELD_CREATED_DATE$1] = new Date(pendingAction.timestamp).toISOString();
12361
+ }
12362
+ // `CreateRecord` with `LastModifiedDate` field
12363
+ if (isBackdatingFieldEditable(objectInfo, DEFAULT_FIELD_LAST_MODIFIED_DATE$1, 'createable', actionFieldNames)) {
12364
+ fields[DEFAULT_FIELD_LAST_MODIFIED_DATE$1] = new Date(pendingAction.timestamp).toISOString();
12365
+ }
12366
+ }
12367
+ // `UpdateRecord` with `LastModifedDate` field
12368
+ if (requestMethod === 'patch' &&
12369
+ isBackdatingFieldEditable(objectInfo, DEFAULT_FIELD_LAST_MODIFIED_DATE$1, 'updateable', actionFieldNames)) {
12370
+ fields[DEFAULT_FIELD_LAST_MODIFIED_DATE$1] = new Date(pendingAction.timestamp).toISOString();
12371
+ }
12372
+ return fields;
12373
+ }
12302
12374
  async fetchReferenceRecord(referenceFields) {
12303
12375
  const promises = referenceFields.map(async (referenceFieldInfo) => {
12304
12376
  const apiName = await this.identifyApiName(referenceFieldInfo.id, referenceFieldInfo.field);
@@ -12589,12 +12661,26 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
12589
12661
  mergeRequestBody(targetBody, sourceBody) {
12590
12662
  // IMPORTANT: spread operator isn't deep clone so we have to individually
12591
12663
  // spread nested objects (just "fields" for RecordInputReps)
12664
+ const targetInput = targetBody;
12665
+ const sourceInput = sourceBody;
12666
+ // choose the max if both source and target have the `LastModifiedDate` property.
12667
+ // there is no need to check 'CreatedDate` since source and target cannot be `create` action at the same time
12668
+ const lastModifiedFields = {};
12669
+ const targetLastModifiedField = targetInput.fields[DEFAULT_FIELD_LAST_MODIFIED_DATE$1];
12670
+ const sourceLastModifiedField = sourceInput.fields[DEFAULT_FIELD_LAST_MODIFIED_DATE$1];
12671
+ if (targetLastModifiedField && sourceLastModifiedField) {
12672
+ lastModifiedFields[DEFAULT_FIELD_LAST_MODIFIED_DATE$1] =
12673
+ targetLastModifiedField > sourceLastModifiedField
12674
+ ? targetLastModifiedField
12675
+ : sourceLastModifiedField;
12676
+ }
12592
12677
  return {
12593
12678
  ...targetBody,
12594
12679
  ...sourceBody,
12595
12680
  fields: {
12596
12681
  ...targetBody.fields,
12597
12682
  ...sourceBody.fields,
12683
+ ...lastModifiedFields,
12598
12684
  },
12599
12685
  };
12600
12686
  }
@@ -12602,6 +12688,13 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
12602
12688
  function isField(key, data) {
12603
12689
  return isStoreKeyRecordField(key);
12604
12690
  }
12691
+ // true if `createable/updateable` property of backdating field is true and draft does not include that fields. If the customer specifies
12692
+ // `LastModifiedDate`, it is not overwritten.
12693
+ function isBackdatingFieldEditable(objectInfo, backdatingFieldName, attributeName, draftActionFieldNames) {
12694
+ return (objectInfo.fields[backdatingFieldName] &&
12695
+ objectInfo.fields[backdatingFieldName][attributeName] &&
12696
+ !draftActionFieldNames.includes(backdatingFieldName));
12697
+ }
12605
12698
 
12606
12699
  /**
12607
12700
  * Records are stored in the durable store with scalar fields denormalized. This function takes that denoramlized
@@ -13636,7 +13729,7 @@ class ContentDocumentCompositeRepresentationActionHandler extends AbstractResour
13636
13729
  });
13637
13730
  pendingAction.metadata[CONTENT_DOCUMENT_LINK_DRAFT_ID_KEY] = contentDocumentLinkId;
13638
13731
  // assert that object infos and references exist
13639
- const metaDataResult = await this.draftRecordService.getRecordDraftMetadata(targetId, pendingAction);
13732
+ const metaDataResult = await this.draftRecordService.getRecordDraftMetadata(contentVersionId, pendingAction);
13640
13733
  if (metaDataResult === undefined) {
13641
13734
  throw Error('No metadata for draft');
13642
13735
  }
@@ -13650,8 +13743,30 @@ class ContentDocumentCompositeRepresentationActionHandler extends AbstractResour
13650
13743
  throw new DraftSynthesisError(`unknown draft precondition failed, metaDataResult: ${JSON.stringify(metaDataResult)}`, 'UNKNOWN');
13651
13744
  }
13652
13745
  }
13746
+ else if (this.isBackdatingEnabled) {
13747
+ const contentVersionObjectInfo = metaDataResult.data.objectInfos.get(CONTENT_VERSION_API_NAME);
13748
+ if (contentVersionObjectInfo === undefined) {
13749
+ throw Error(`Could not generate draft. Object info is missing`);
13750
+ }
13751
+ // `ContentVersion` support back-dating. Ui-api team has WI @W-14219481 to support `CreatedDate` and `LastModifiedDate`.
13752
+ // right now, these two fields are ignored by server
13753
+ pendingAction.data.body.namedEntries = [
13754
+ ...pendingAction.data.body.namedEntries,
13755
+ ...this.getBackdatingNameEntries(contentVersionObjectInfo, pendingAction.timestamp),
13756
+ ];
13757
+ }
13653
13758
  return pendingAction;
13654
13759
  }
13760
+ getBackdatingNameEntries(objectInfo, timestamp) {
13761
+ return [DEFAULT_FIELD_CREATED_DATE$1, DEFAULT_FIELD_LAST_MODIFIED_DATE$1]
13762
+ .filter((fieldName) => objectInfo.fields[fieldName] && objectInfo.fields[fieldName].createable)
13763
+ .map((fieldValue) => {
13764
+ return {
13765
+ name: fieldValue,
13766
+ value: new Date(timestamp).toISOString(),
13767
+ };
13768
+ });
13769
+ }
13655
13770
  /* istanbul ignore next */
13656
13771
  canHandlePublish(_key) {
13657
13772
  // no need to touch publishing
@@ -17558,5 +17673,5 @@ register({
17558
17673
  instrument: instrument,
17559
17674
  });
17560
17675
 
17561
- export { getRuntime, registerReportObserver, reportGraphqlQueryParseError };
17562
- // version: 1.252.0-a960aa048
17676
+ export { O11Y_NAMESPACE_LDS_MOBILE, getRuntime, registerReportObserver, reportGraphqlQueryParseError };
17677
+ // version: 1.256.0-ad6a66c18
@@ -1,5 +1,6 @@
1
1
  import { getRuntime } from './runtime';
2
- export { getRuntime };
2
+ import { O11Y_NAMESPACE_LDS_MOBILE } from './instrumentation/metrics';
3
+ export { getRuntime, O11Y_NAMESPACE_LDS_MOBILE };
3
4
  export type { ObjectInfoService } from './utils/ObjectInfoService';
4
5
  export type { ObservabilityContext } from '@salesforce/nimbus-plugin-lds';
5
6
  export { registerReportObserver } from './instrumentation/instrumentMobileAdapter';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/lds-runtime-mobile",
3
- "version": "1.252.0",
3
+ "version": "1.256.0",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "description": "LDS runtime for mobile/hybrid environments.",
6
6
  "main": "dist/main.js",
@@ -32,25 +32,25 @@
32
32
  "release:corejar": "yarn build && ../core-build/scripts/core.js --name=lds-runtime-mobile"
33
33
  },
34
34
  "dependencies": {
35
- "@salesforce/lds-adapters-uiapi": "*",
36
- "@salesforce/lds-bindings": "*",
37
- "@salesforce/lds-instrumentation": "*",
38
- "@salesforce/lds-priming": "*",
35
+ "@salesforce/lds-adapters-uiapi": "^1.256.0",
36
+ "@salesforce/lds-bindings": "^1.256.0",
37
+ "@salesforce/lds-instrumentation": "^1.256.0",
38
+ "@salesforce/lds-priming": "^1.256.0",
39
39
  "@salesforce/user": "0.0.21",
40
40
  "o11y": "244.0.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@salesforce/lds-adapters-graphql": "*",
44
- "@salesforce/lds-drafts": "*",
45
- "@salesforce/lds-drafts-adapters-uiapi": "*",
46
- "@salesforce/lds-graphql-eval": "*",
47
- "@salesforce/lds-network-adapter": "*",
48
- "@salesforce/lds-network-nimbus": "*",
49
- "@salesforce/lds-store-nimbus": "*",
50
- "@salesforce/lds-store-binary": "*",
51
- "@salesforce/lds-store-sql": "*",
52
- "@salesforce/lds-utils-adapters": "*",
53
- "@salesforce/nimbus-plugin-lds": "*",
43
+ "@salesforce/lds-adapters-graphql": "^1.256.0",
44
+ "@salesforce/lds-drafts": "^1.256.0",
45
+ "@salesforce/lds-drafts-adapters-uiapi": "^1.256.0",
46
+ "@salesforce/lds-graphql-eval": "^1.256.0",
47
+ "@salesforce/lds-network-adapter": "^1.256.0",
48
+ "@salesforce/lds-network-nimbus": "^1.256.0",
49
+ "@salesforce/lds-store-binary": "^1.256.0",
50
+ "@salesforce/lds-store-nimbus": "^1.256.0",
51
+ "@salesforce/lds-store-sql": "^1.256.0",
52
+ "@salesforce/lds-utils-adapters": "^1.256.0",
53
+ "@salesforce/nimbus-plugin-lds": "^1.256.0",
54
54
  "babel-plugin-dynamic-import-node": "^2.3.3",
55
55
  "wait-for-expect": "^3.0.2"
56
56
  },
package/sfdc/main.js CHANGED
@@ -18,6 +18,7 @@ import excludeStaleRecordsGate from '@salesforce/gate/lds.graphqlEvalExcludeStal
18
18
  import { parseAndVisit, Kind, buildSchema, isObjectType, defaultFieldResolver, visit, execute, parse as parse$7, extendSchema, isScalarType } from 'force/ldsGraphqlParser';
19
19
  import { RECORD_ID_PREFIX, RECORD_FIELDS_KEY_JUNCTION, isStoreKeyRecordViewEntity, getRecordId18, RECORD_REPRESENTATION_NAME, extractRecordIdFromStoreKey, keyBuilderQuickActionExecutionRepresentation, ingestQuickActionExecutionRepresentation, keyBuilderContentDocumentCompositeRepresentation, getResponseCacheKeysContentDocumentCompositeRepresentation, keyBuilderFromTypeContentDocumentCompositeRepresentation, ingestContentDocumentCompositeRepresentation, keyBuilderRecord, RECORD_VIEW_ENTITY_ID_PREFIX, getTypeCacheKeysRecord, keyBuilderFromTypeRecordRepresentation, ingestRecord, RecordRepresentationRepresentationType, ObjectInfoRepresentationType, getRecordAdapterFactory, getObjectInfoAdapterFactory, getObjectInfosAdapterFactory, getObjectInfoDirectoryAdapterFactory, UiApiNamespace, RecordRepresentationType, RecordRepresentationTTL, RecordRepresentationVersion, getRecordsAdapterFactory } from 'force/ldsAdaptersUiapi';
20
20
  import ldsIdempotencyWriteDisabled from '@salesforce/gate/lds.idempotencyWriteDisabled';
21
+ import ldsBackdatingEnabled from '@salesforce/gate/lds.backdatingEnabled';
21
22
  import caseSensitiveUserId from '@salesforce/user/Id';
22
23
  import { idleDetector, getInstrumentation } from 'o11y/client';
23
24
  import ldsUseShortUrlGate from '@salesforce/gate/lds.useShortUrl';
@@ -4365,8 +4366,9 @@ function rootRecordQuery(selection, input) {
4365
4366
  // If there is no metadata for this query or it somehow lacks a timestamp
4366
4367
  // skip setting the root timestamp
4367
4368
  if (queryMetadata !== undefined && queryMetadata.ingestionTimestamp !== undefined) {
4368
- // subtract 1000ms from timestamp to account for ingestion processing time
4369
- input.rootTimestamp = queryMetadata.ingestionTimestamp - 1000;
4369
+ // adjust the timestamp to account for ingestion processing time
4370
+ // 30s is used because this is the default record TTL
4371
+ input.rootTimestamp = queryMetadata.ingestionTimestamp - 30000;
4370
4372
  }
4371
4373
  }
4372
4374
  return recordQuery(selection, alias, apiName, [], input);
@@ -6049,6 +6051,8 @@ class DurableDraftStore {
6049
6051
  }
6050
6052
  }
6051
6053
 
6054
+ const DEFAULT_FIELD_LAST_MODIFIED_DATE$1 = 'LastModifiedDate';
6055
+ const DEFAULT_FIELD_CREATED_DATE$1 = 'CreatedDate';
6052
6056
  class AbstractResourceRequestActionHandler {
6053
6057
  constructor(draftQueue, networkAdapter, getLuvio) {
6054
6058
  this.draftQueue = draftQueue;
@@ -6067,6 +6071,7 @@ class AbstractResourceRequestActionHandler {
6067
6071
  this.isLdsIdempotencyWriteDisabled = ldsIdempotencyWriteDisabled.isOpen({
6068
6072
  fallback: false,
6069
6073
  });
6074
+ this.isBackdatingEnabled = ldsBackdatingEnabled.isOpen({ fallback: false });
6070
6075
  }
6071
6076
  enqueue(data) {
6072
6077
  return this.draftQueue.enqueue(this.handlerId, data);
@@ -6137,6 +6142,17 @@ class AbstractResourceRequestActionHandler {
6137
6142
  }
6138
6143
  }
6139
6144
  }
6145
+ if (this.isBackdatingEnabled &&
6146
+ response.status === HttpStatusCode.BadRequest &&
6147
+ this.isBackdatingError(response.body, action)) {
6148
+ updatedAction.timestamp = Date.now();
6149
+ updatedAction.data.body.fields = {
6150
+ ...updatedAction.data.body.fields,
6151
+ LastModifiedDate: new Date(updatedAction.timestamp).toISOString(),
6152
+ };
6153
+ shouldRetry = true;
6154
+ actionDataChanged = true;
6155
+ }
6140
6156
  await actionErrored(shouldRetry
6141
6157
  ? updatedAction
6142
6158
  : {
@@ -6170,7 +6186,21 @@ class AbstractResourceRequestActionHandler {
6170
6186
  }
6171
6187
  // checks if the body is an array of UiApiError. Sometimes the body has `enhancedErrorType` field as an error indicator(one example is the field validation failure). In such case Action being processed updates to an Error Action.
6172
6188
  isUiApiErrors(body) {
6173
- return body !== undefined && Array.isArray(body) && body.length > 0 && body[0].errorCode;
6189
+ return body !== undefined && isArray$3(body) && body.length > 0 && body[0].errorCode;
6190
+ }
6191
+ isBackdatingError(body, action) {
6192
+ if (body.enhancedErrorType &&
6193
+ body.enhancedErrorType === 'RecordError' &&
6194
+ body.output &&
6195
+ body.output.errors &&
6196
+ isArray$3(body.output.errors) &&
6197
+ body.output.errors.length > 0 &&
6198
+ action.data.body &&
6199
+ action.data.body.fields &&
6200
+ action.data.body.fields[DEFAULT_FIELD_LAST_MODIFIED_DATE$1]) {
6201
+ return body.output.errors.some((error) => error.errorCode === 'CollisionDetectedException');
6202
+ }
6203
+ return false;
6174
6204
  }
6175
6205
  async buildPendingAction(request, queue) {
6176
6206
  const targetId = await this.getIdFromRequest(request);
@@ -7112,7 +7142,7 @@ function isArrayLike(x) {
7112
7142
 
7113
7143
  const { create: create$4, keys: keys$4, values: values$2, entries: entries$3, assign: assign$4 } = Object;
7114
7144
  const { stringify: stringify$4, parse: parse$4 } = JSON;
7115
- const { isArray: isArray$2, from: from$1 } = Array;
7145
+ const { isArray: isArray$2, from: from$2 } = Array;
7116
7146
 
7117
7147
  function recordLoaderFactory(query) {
7118
7148
  async function batchRecordQuery(ids) {
@@ -9326,8 +9356,9 @@ async function fetchIngestionTimeStampFromDatabase(apiName, info, args, query) {
9326
9356
  const results = await query(sql, [key]);
9327
9357
  const [timestamp] = results.rows.map((row) => row[0]);
9328
9358
  if (timestamp !== null && typeof timestamp === 'number') {
9329
- //go back 1000 ms to adjust for margin of error when top level query is stored and when raml objects are stored
9330
- ingestionTimestamp = timestamp - 1000;
9359
+ // adjust the timestamp to account for ingestion processing time
9360
+ // 30s is used because this is the default record TTL
9361
+ ingestionTimestamp = timestamp - 30000;
9331
9362
  }
9332
9363
  }
9333
9364
  return ingestionTimestamp;
@@ -9483,7 +9514,7 @@ function generateRecordQueries(schema, objectInfoMap) {
9483
9514
  recordQueries: extensionWrapper,
9484
9515
  recordConnections,
9485
9516
  recordExtensions,
9486
- polyFieldTypeNameArr: from$1(allPolymorphicFieldTypeNames),
9517
+ polyFieldTypeNameArr: from$2(allPolymorphicFieldTypeNames),
9487
9518
  };
9488
9519
  }
9489
9520
  /**
@@ -11457,7 +11488,7 @@ function referenceIdFieldForRelationship(relationshipName) {
11457
11488
  const { keys: keys$3, values: values$1, create: create$3, assign: assign$3, freeze } = Object;
11458
11489
  const { stringify: stringify$3, parse: parse$3 } = JSON;
11459
11490
  const { shift } = Array.prototype;
11460
- const { isArray: isArray$1 } = Array;
11491
+ const { isArray: isArray$1, from: from$1 } = Array;
11461
11492
 
11462
11493
  function isFieldLink(field) {
11463
11494
  const { value } = field;
@@ -12251,7 +12282,7 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
12251
12282
  }
12252
12283
  async buildPendingAction(request, queue) {
12253
12284
  const resolvedRequest = this.resolveResourceRequest(request);
12254
- const pendingAction = (await super.buildPendingAction(resolvedRequest, queue));
12285
+ let pendingAction = (await super.buildPendingAction(resolvedRequest, queue));
12255
12286
  const { tag, targetId } = pendingAction;
12256
12287
  const targetApiName = await this.getApiNameForRecordId(targetId, tag, resolvedRequest);
12257
12288
  pendingAction.metadata[LDS_ACTION_METADATA_API_NAME] = targetApiName;
@@ -12297,8 +12328,49 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
12297
12328
  this.fetchReferenceRecord(referenceFields);
12298
12329
  }
12299
12330
  }
12331
+ // handles backdating
12332
+ if (this.isBackdatingEnabled &&
12333
+ pendingAction.data &&
12334
+ pendingAction.data.method !== 'delete' && //'delete' action does not have fields
12335
+ metaDataResult.data &&
12336
+ metaDataResult.data.objectInfos) {
12337
+ const objectInfo = metaDataResult.data.objectInfos.get(targetApiName);
12338
+ if (objectInfo === undefined) {
12339
+ throw Error(`Could not generate draft. Object info is missing`);
12340
+ }
12341
+ const appendedFields = this.getBackdatingFields(objectInfo, resolvedRequest.method, pendingAction);
12342
+ if (keys$3(appendedFields).length > 0) {
12343
+ pendingAction.data.body = {
12344
+ ...pendingAction.data.body,
12345
+ fields: {
12346
+ ...pendingAction.data.body.fields,
12347
+ ...appendedFields,
12348
+ },
12349
+ };
12350
+ }
12351
+ }
12300
12352
  return pendingAction;
12301
12353
  }
12354
+ getBackdatingFields(objectInfo, requestMethod, pendingAction) {
12355
+ const fields = {};
12356
+ const actionFieldNames = keys$3(pendingAction.data.body.fields);
12357
+ if (requestMethod === 'post') {
12358
+ // `CreateRecord` with `CreatedDate` field
12359
+ if (isBackdatingFieldEditable(objectInfo, DEFAULT_FIELD_CREATED_DATE$1, 'createable', actionFieldNames)) {
12360
+ fields[DEFAULT_FIELD_CREATED_DATE$1] = new Date(pendingAction.timestamp).toISOString();
12361
+ }
12362
+ // `CreateRecord` with `LastModifiedDate` field
12363
+ if (isBackdatingFieldEditable(objectInfo, DEFAULT_FIELD_LAST_MODIFIED_DATE$1, 'createable', actionFieldNames)) {
12364
+ fields[DEFAULT_FIELD_LAST_MODIFIED_DATE$1] = new Date(pendingAction.timestamp).toISOString();
12365
+ }
12366
+ }
12367
+ // `UpdateRecord` with `LastModifedDate` field
12368
+ if (requestMethod === 'patch' &&
12369
+ isBackdatingFieldEditable(objectInfo, DEFAULT_FIELD_LAST_MODIFIED_DATE$1, 'updateable', actionFieldNames)) {
12370
+ fields[DEFAULT_FIELD_LAST_MODIFIED_DATE$1] = new Date(pendingAction.timestamp).toISOString();
12371
+ }
12372
+ return fields;
12373
+ }
12302
12374
  async fetchReferenceRecord(referenceFields) {
12303
12375
  const promises = referenceFields.map(async (referenceFieldInfo) => {
12304
12376
  const apiName = await this.identifyApiName(referenceFieldInfo.id, referenceFieldInfo.field);
@@ -12589,12 +12661,26 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
12589
12661
  mergeRequestBody(targetBody, sourceBody) {
12590
12662
  // IMPORTANT: spread operator isn't deep clone so we have to individually
12591
12663
  // spread nested objects (just "fields" for RecordInputReps)
12664
+ const targetInput = targetBody;
12665
+ const sourceInput = sourceBody;
12666
+ // choose the max if both source and target have the `LastModifiedDate` property.
12667
+ // there is no need to check 'CreatedDate` since source and target cannot be `create` action at the same time
12668
+ const lastModifiedFields = {};
12669
+ const targetLastModifiedField = targetInput.fields[DEFAULT_FIELD_LAST_MODIFIED_DATE$1];
12670
+ const sourceLastModifiedField = sourceInput.fields[DEFAULT_FIELD_LAST_MODIFIED_DATE$1];
12671
+ if (targetLastModifiedField && sourceLastModifiedField) {
12672
+ lastModifiedFields[DEFAULT_FIELD_LAST_MODIFIED_DATE$1] =
12673
+ targetLastModifiedField > sourceLastModifiedField
12674
+ ? targetLastModifiedField
12675
+ : sourceLastModifiedField;
12676
+ }
12592
12677
  return {
12593
12678
  ...targetBody,
12594
12679
  ...sourceBody,
12595
12680
  fields: {
12596
12681
  ...targetBody.fields,
12597
12682
  ...sourceBody.fields,
12683
+ ...lastModifiedFields,
12598
12684
  },
12599
12685
  };
12600
12686
  }
@@ -12602,6 +12688,13 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
12602
12688
  function isField(key, data) {
12603
12689
  return isStoreKeyRecordField(key);
12604
12690
  }
12691
+ // true if `createable/updateable` property of backdating field is true and draft does not include that fields. If the customer specifies
12692
+ // `LastModifiedDate`, it is not overwritten.
12693
+ function isBackdatingFieldEditable(objectInfo, backdatingFieldName, attributeName, draftActionFieldNames) {
12694
+ return (objectInfo.fields[backdatingFieldName] &&
12695
+ objectInfo.fields[backdatingFieldName][attributeName] &&
12696
+ !draftActionFieldNames.includes(backdatingFieldName));
12697
+ }
12605
12698
 
12606
12699
  /**
12607
12700
  * Records are stored in the durable store with scalar fields denormalized. This function takes that denoramlized
@@ -13636,7 +13729,7 @@ class ContentDocumentCompositeRepresentationActionHandler extends AbstractResour
13636
13729
  });
13637
13730
  pendingAction.metadata[CONTENT_DOCUMENT_LINK_DRAFT_ID_KEY] = contentDocumentLinkId;
13638
13731
  // assert that object infos and references exist
13639
- const metaDataResult = await this.draftRecordService.getRecordDraftMetadata(targetId, pendingAction);
13732
+ const metaDataResult = await this.draftRecordService.getRecordDraftMetadata(contentVersionId, pendingAction);
13640
13733
  if (metaDataResult === undefined) {
13641
13734
  throw Error('No metadata for draft');
13642
13735
  }
@@ -13650,8 +13743,30 @@ class ContentDocumentCompositeRepresentationActionHandler extends AbstractResour
13650
13743
  throw new DraftSynthesisError(`unknown draft precondition failed, metaDataResult: ${JSON.stringify(metaDataResult)}`, 'UNKNOWN');
13651
13744
  }
13652
13745
  }
13746
+ else if (this.isBackdatingEnabled) {
13747
+ const contentVersionObjectInfo = metaDataResult.data.objectInfos.get(CONTENT_VERSION_API_NAME);
13748
+ if (contentVersionObjectInfo === undefined) {
13749
+ throw Error(`Could not generate draft. Object info is missing`);
13750
+ }
13751
+ // `ContentVersion` support back-dating. Ui-api team has WI @W-14219481 to support `CreatedDate` and `LastModifiedDate`.
13752
+ // right now, these two fields are ignored by server
13753
+ pendingAction.data.body.namedEntries = [
13754
+ ...pendingAction.data.body.namedEntries,
13755
+ ...this.getBackdatingNameEntries(contentVersionObjectInfo, pendingAction.timestamp),
13756
+ ];
13757
+ }
13653
13758
  return pendingAction;
13654
13759
  }
13760
+ getBackdatingNameEntries(objectInfo, timestamp) {
13761
+ return [DEFAULT_FIELD_CREATED_DATE$1, DEFAULT_FIELD_LAST_MODIFIED_DATE$1]
13762
+ .filter((fieldName) => objectInfo.fields[fieldName] && objectInfo.fields[fieldName].createable)
13763
+ .map((fieldValue) => {
13764
+ return {
13765
+ name: fieldValue,
13766
+ value: new Date(timestamp).toISOString(),
13767
+ };
13768
+ });
13769
+ }
13655
13770
  /* istanbul ignore next */
13656
13771
  canHandlePublish(_key) {
13657
13772
  // no need to touch publishing
@@ -17558,5 +17673,5 @@ register({
17558
17673
  instrument: instrument,
17559
17674
  });
17560
17675
 
17561
- export { getRuntime, registerReportObserver, reportGraphqlQueryParseError };
17562
- // version: 1.252.0-a960aa048
17676
+ export { O11Y_NAMESPACE_LDS_MOBILE, getRuntime, registerReportObserver, reportGraphqlQueryParseError };
17677
+ // version: 1.256.0-ad6a66c18
@@ -1,5 +1,6 @@
1
1
  import { getRuntime } from './runtime';
2
- export { getRuntime };
2
+ import { O11Y_NAMESPACE_LDS_MOBILE } from './instrumentation/metrics';
3
+ export { getRuntime, O11Y_NAMESPACE_LDS_MOBILE };
3
4
  export type { ObjectInfoService } from './utils/ObjectInfoService';
4
5
  export type { ObservabilityContext } from '@salesforce/nimbus-plugin-lds';
5
6
  export { registerReportObserver } from './instrumentation/instrumentMobileAdapter';