@salesforce/lds-runtime-mobile 1.354.0-dev6 → 1.354.0-dev7

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
@@ -44335,7 +44335,75 @@ function filterToPredicates(where, recordType, alias, objectInfoMap, joins, draf
44335
44335
  // in 'Account' has the 'Owner' as the 'RelationshipName', the api name in 'referenceToInfos' is 'User'
44336
44336
  if (fieldInfo.referenceToInfos.length === 1) {
44337
44337
  const childRecordType = fieldInfo.referenceToInfos[0].apiName;
44338
- predicates.push(...filterToPredicates(where[field], childRecordType, childAlias, objectInfoMap, joins));
44338
+ let p = filterToPredicates(where[field], childRecordType, childAlias, objectInfoMap, joins);
44339
+ if (isSinglePredicate(p[0]) && p[0].value === null) {
44340
+ // @W-18311580 - this is a reference field with a null value. We need to ensure that we handle the case
44341
+ // where we are joining to a record that may not exist in our offline database, so we need to detect if
44342
+ // the value is really a null, or if it is a missing record in the offline database.
44343
+ //
44344
+ // This effecitvely adds the following predicates to a "where Contact.Account.Name IS NULL" query:
44345
+ //
44346
+ // AND (
44347
+ // (
44348
+ // json_extract("Contact__c_Account__r".data, '$.fields.Id.value') IS NOT NULL (a)
44349
+ // AND json_extract("Contact__c".data, '$.fields.AccountId.value') IS NOT NULL (b)
44350
+ // )
44351
+ // OR json_extract("Contact__c".data, '$.fields.AccountId.value') IS NULL (c)
44352
+ // )
44353
+ //
44354
+ // Where:
44355
+ // (a) AND (b) ensures we have an account record when it is supposed to exist,
44356
+ // OR (c) if AccountId is null, we know we are not referencing an account record.
44357
+ let notNullPredicate = {
44358
+ type: PredicateType.compound,
44359
+ operator: 'and',
44360
+ children: [
44361
+ {
44362
+ alias: alias,
44363
+ leftPath: leftPath,
44364
+ operator: 'IS NOT',
44365
+ value: null,
44366
+ dataType: 'String',
44367
+ type: PredicateType.single,
44368
+ isCaseSensitive: true,
44369
+ },
44370
+ {
44371
+ alias: childAlias,
44372
+ leftPath: `$.fields.Id.value`,
44373
+ operator: 'IS NOT',
44374
+ value: null,
44375
+ dataType: 'String',
44376
+ type: PredicateType.single,
44377
+ isCaseSensitive: true,
44378
+ },
44379
+ ],
44380
+ };
44381
+ let nullPredicate = {
44382
+ type: PredicateType.compound,
44383
+ operator: 'or',
44384
+ children: [
44385
+ notNullPredicate,
44386
+ {
44387
+ alias: alias,
44388
+ leftPath: leftPath,
44389
+ operator: 'IS',
44390
+ value: null,
44391
+ dataType: 'String',
44392
+ type: PredicateType.single,
44393
+ isCaseSensitive: true,
44394
+ },
44395
+ ],
44396
+ };
44397
+ let compoundPredicate = {
44398
+ type: PredicateType.compound,
44399
+ operator: 'and',
44400
+ children: [...p, nullPredicate],
44401
+ };
44402
+ predicates.push(compoundPredicate);
44403
+ }
44404
+ else {
44405
+ predicates.push(...p);
44406
+ }
44339
44407
  }
44340
44408
  else {
44341
44409
  // @W-12618378 polymorphic query sometimes does not work as expected on server. The GQL on certain entities could fail.
@@ -52496,6 +52564,7 @@ function generateTypedBatches(work, batchSize) {
52496
52564
  return batches;
52497
52565
  }
52498
52566
 
52567
+ const skippedCompoundFieldTypes = ['Address', 'Location'];
52499
52568
  function getMissingElementsFromSuperset(superset, subset) {
52500
52569
  return subset.filter((val) => !superset.includes(val));
52501
52570
  }
@@ -52512,6 +52581,7 @@ function buildFieldUnionArray(existingRecord, incomingRecord, objectInfo) {
52512
52581
  const allFields = Array.from(new Set([...Object.keys(existingRecord.fields), ...Object.keys(incomingRecord.fields)]));
52513
52582
  const fieldUnion = [];
52514
52583
  let includesSpanningFields = false;
52584
+ let includesSkippedFields = false;
52515
52585
  allFields.forEach((fieldName) => {
52516
52586
  const objectInfoField = objectInfo.fields[fieldName];
52517
52587
  if (objectInfoField === undefined) {
@@ -52529,10 +52599,17 @@ function buildFieldUnionArray(existingRecord, incomingRecord, objectInfo) {
52529
52599
  }
52530
52600
  }
52531
52601
  else {
52602
+ if (skippedCompoundFieldTypes.includes(objectInfo.fields[fieldName].dataType)) {
52603
+ includesSkippedFields = true;
52604
+ }
52532
52605
  fieldUnion.push(fieldName);
52533
52606
  }
52534
52607
  });
52535
- return { fields: fieldUnion, includesSpanningFields };
52608
+ return {
52609
+ fields: fieldUnion,
52610
+ includesSpanningFields,
52611
+ includesSkippedFields,
52612
+ };
52536
52613
  }
52537
52614
  /**
52538
52615
  * Merges (if possible) an incoming record from a priming session with an existing record in the durable store.
@@ -52595,9 +52672,15 @@ function mergeRecord(existingRecord, incomingRecord, objectInfo) {
52595
52672
  }
52596
52673
  else {
52597
52674
  const missingFields = getMissingElementsFromSuperset(Object.keys(incomingRecord.fields), Object.keys(existingRecord.fields));
52598
- // if the only missing fields are spanning fields and their corresponding lookup fields match, we can merge
52599
- // since none of the changed fields are part of the incoming record
52600
- if (missingFields.every((field) => {
52675
+ // tests for missing optional fields, pending fields, and spanning fields that can be merged
52676
+ const fieldIsMergeable = (field) => {
52677
+ // missing and pending fields in the existing record are mergeable
52678
+ const fieldState = existingRecord.fields[field].__state;
52679
+ if (fieldState && (fieldState.pending === true || fieldState.isMissing === true)) {
52680
+ return true;
52681
+ }
52682
+ // if the only missing fields are spanning fields and their corresponding lookup fields match, we can merge
52683
+ // since none of the changed fields are part of the incoming record
52601
52684
  const referenceFieldName = findReferenceFieldForSpanningField(field, objectInfo);
52602
52685
  if (referenceFieldName === undefined) {
52603
52686
  return false;
@@ -52606,7 +52689,8 @@ function mergeRecord(existingRecord, incomingRecord, objectInfo) {
52606
52689
  incomingRecord.fields[referenceFieldName] &&
52607
52690
  incomingRecord.fields[referenceFieldName].value ===
52608
52691
  existingRecord.fields[referenceFieldName].value);
52609
- })) {
52692
+ };
52693
+ if (missingFields.every(fieldIsMergeable)) {
52610
52694
  return {
52611
52695
  ok: true,
52612
52696
  needsWrite: true,
@@ -52626,8 +52710,8 @@ function mergeRecord(existingRecord, incomingRecord, objectInfo) {
52626
52710
  };
52627
52711
  }
52628
52712
  // If Etags do not match and the incoming record does not contain all fields, re-request the record
52629
- const { fields, includesSpanningFields } = buildFieldUnionArray(existingRecord, incomingRecord, objectInfo);
52630
- if (includesSpanningFields) {
52713
+ const { fields, includesSpanningFields, includesSkippedFields } = buildFieldUnionArray(existingRecord, incomingRecord, objectInfo);
52714
+ if (includesSpanningFields || includesSkippedFields) {
52631
52715
  return {
52632
52716
  ok: false,
52633
52717
  code: 'conflict-spanning-record',
@@ -53617,7 +53701,15 @@ class RecordLoaderSOQLComposite extends NetworkRecordLoader {
53617
53701
  if (Object.keys(recordTypeInfos).length > 1) {
53618
53702
  fieldSet.add('RecordTypeId');
53619
53703
  }
53620
- const fields = Array.from(fieldSet);
53704
+ const fields = Array.from(fieldSet).filter((field) => {
53705
+ const fieldMetadata = batch.objectInfo.fields[field];
53706
+ // skip Address and Location compound fields
53707
+ if (fieldMetadata.compound &&
53708
+ skippedCompoundFieldTypes.includes(fieldMetadata.dataType)) {
53709
+ return false;
53710
+ }
53711
+ return true;
53712
+ });
53621
53713
  // We will have SOQL format specific data types for us by adding a format() value.
53622
53714
  // The field alias will have ___display added to the end.
53623
53715
  for (const field of fieldSet) {
@@ -53636,7 +53728,6 @@ class RecordLoaderSOQLComposite extends NetworkRecordLoader {
53636
53728
  }
53637
53729
  }
53638
53730
  const query = `SELECT ${fields.join(',')} FROM ${batch.type} `;
53639
- // console.log(`DUSTIN: soql batch query: ${query}`);
53640
53731
  return query;
53641
53732
  }
53642
53733
  generateWhere(ids) {
@@ -55676,4 +55767,4 @@ register({
55676
55767
  });
55677
55768
 
55678
55769
  export { O11Y_NAMESPACE_LDS_MOBILE, getRuntime, ingest$1o as ingestDenormalizedRecordRepresentation, registerReportObserver, reportGraphqlQueryParseError };
55679
- // version: 1.354.0-dev6-a32f37c654
55770
+ // version: 1.354.0-dev7-d25082f814
@@ -14,6 +14,7 @@ export interface MergeResultConflict {
14
14
  fieldUnion: string[];
15
15
  }
16
16
  type MergeResult = MergeResultSuccess | MergeResultConflict;
17
+ export declare const skippedCompoundFieldTypes: string[];
17
18
  /**
18
19
  * Merges (if possible) an incoming record from a priming session with an existing record in the durable store.
19
20
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/lds-runtime-mobile",
3
- "version": "1.354.0-dev6",
3
+ "version": "1.354.0-dev7",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "description": "LDS runtime for mobile/hybrid environments.",
6
6
  "main": "dist/main.js",
@@ -32,24 +32,24 @@
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": "^1.354.0-dev6",
36
- "@salesforce/lds-bindings": "^1.354.0-dev6",
37
- "@salesforce/lds-instrumentation": "^1.354.0-dev6",
35
+ "@salesforce/lds-adapters-uiapi": "^1.354.0-dev7",
36
+ "@salesforce/lds-bindings": "^1.354.0-dev7",
37
+ "@salesforce/lds-instrumentation": "^1.354.0-dev7",
38
38
  "@salesforce/user": "0.0.21",
39
39
  "o11y": "250.7.0",
40
40
  "o11y_schema": "256.126.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@salesforce/lds-adapters-graphql": "^1.354.0-dev6",
44
- "@salesforce/lds-drafts": "^1.354.0-dev6",
45
- "@salesforce/lds-durable-records": "^1.354.0-dev6",
46
- "@salesforce/lds-network-adapter": "^1.354.0-dev6",
47
- "@salesforce/lds-network-nimbus": "^1.354.0-dev6",
48
- "@salesforce/lds-store-binary": "^1.354.0-dev6",
49
- "@salesforce/lds-store-nimbus": "^1.354.0-dev6",
50
- "@salesforce/lds-store-sql": "^1.354.0-dev6",
51
- "@salesforce/lds-utils-adapters": "^1.354.0-dev6",
52
- "@salesforce/nimbus-plugin-lds": "^1.354.0-dev6",
43
+ "@salesforce/lds-adapters-graphql": "^1.354.0-dev7",
44
+ "@salesforce/lds-drafts": "^1.354.0-dev7",
45
+ "@salesforce/lds-durable-records": "^1.354.0-dev7",
46
+ "@salesforce/lds-network-adapter": "^1.354.0-dev7",
47
+ "@salesforce/lds-network-nimbus": "^1.354.0-dev7",
48
+ "@salesforce/lds-store-binary": "^1.354.0-dev7",
49
+ "@salesforce/lds-store-nimbus": "^1.354.0-dev7",
50
+ "@salesforce/lds-store-sql": "^1.354.0-dev7",
51
+ "@salesforce/lds-utils-adapters": "^1.354.0-dev7",
52
+ "@salesforce/nimbus-plugin-lds": "^1.354.0-dev7",
53
53
  "babel-plugin-dynamic-import-node": "^2.3.3",
54
54
  "wait-for-expect": "^3.0.2"
55
55
  },
@@ -57,7 +57,7 @@
57
57
  {
58
58
  "path": "./dist/main.js",
59
59
  "maxSize": {
60
- "none": "2200 kB",
60
+ "none": "2205 kB",
61
61
  "min": "1000 kB",
62
62
  "compressed": "250 kB"
63
63
  }
@@ -65,7 +65,7 @@
65
65
  {
66
66
  "path": "./sfdc/main.js",
67
67
  "maxSize": {
68
- "none": "2200 kB",
68
+ "none": "2205 kB",
69
69
  "min": "1000 kB",
70
70
  "compressed": "250 kB"
71
71
  }
package/sfdc/main.js CHANGED
@@ -44335,7 +44335,75 @@ function filterToPredicates(where, recordType, alias, objectInfoMap, joins, draf
44335
44335
  // in 'Account' has the 'Owner' as the 'RelationshipName', the api name in 'referenceToInfos' is 'User'
44336
44336
  if (fieldInfo.referenceToInfos.length === 1) {
44337
44337
  const childRecordType = fieldInfo.referenceToInfos[0].apiName;
44338
- predicates.push(...filterToPredicates(where[field], childRecordType, childAlias, objectInfoMap, joins));
44338
+ let p = filterToPredicates(where[field], childRecordType, childAlias, objectInfoMap, joins);
44339
+ if (isSinglePredicate(p[0]) && p[0].value === null) {
44340
+ // @W-18311580 - this is a reference field with a null value. We need to ensure that we handle the case
44341
+ // where we are joining to a record that may not exist in our offline database, so we need to detect if
44342
+ // the value is really a null, or if it is a missing record in the offline database.
44343
+ //
44344
+ // This effecitvely adds the following predicates to a "where Contact.Account.Name IS NULL" query:
44345
+ //
44346
+ // AND (
44347
+ // (
44348
+ // json_extract("Contact__c_Account__r".data, '$.fields.Id.value') IS NOT NULL (a)
44349
+ // AND json_extract("Contact__c".data, '$.fields.AccountId.value') IS NOT NULL (b)
44350
+ // )
44351
+ // OR json_extract("Contact__c".data, '$.fields.AccountId.value') IS NULL (c)
44352
+ // )
44353
+ //
44354
+ // Where:
44355
+ // (a) AND (b) ensures we have an account record when it is supposed to exist,
44356
+ // OR (c) if AccountId is null, we know we are not referencing an account record.
44357
+ let notNullPredicate = {
44358
+ type: PredicateType.compound,
44359
+ operator: 'and',
44360
+ children: [
44361
+ {
44362
+ alias: alias,
44363
+ leftPath: leftPath,
44364
+ operator: 'IS NOT',
44365
+ value: null,
44366
+ dataType: 'String',
44367
+ type: PredicateType.single,
44368
+ isCaseSensitive: true,
44369
+ },
44370
+ {
44371
+ alias: childAlias,
44372
+ leftPath: `$.fields.Id.value`,
44373
+ operator: 'IS NOT',
44374
+ value: null,
44375
+ dataType: 'String',
44376
+ type: PredicateType.single,
44377
+ isCaseSensitive: true,
44378
+ },
44379
+ ],
44380
+ };
44381
+ let nullPredicate = {
44382
+ type: PredicateType.compound,
44383
+ operator: 'or',
44384
+ children: [
44385
+ notNullPredicate,
44386
+ {
44387
+ alias: alias,
44388
+ leftPath: leftPath,
44389
+ operator: 'IS',
44390
+ value: null,
44391
+ dataType: 'String',
44392
+ type: PredicateType.single,
44393
+ isCaseSensitive: true,
44394
+ },
44395
+ ],
44396
+ };
44397
+ let compoundPredicate = {
44398
+ type: PredicateType.compound,
44399
+ operator: 'and',
44400
+ children: [...p, nullPredicate],
44401
+ };
44402
+ predicates.push(compoundPredicate);
44403
+ }
44404
+ else {
44405
+ predicates.push(...p);
44406
+ }
44339
44407
  }
44340
44408
  else {
44341
44409
  // @W-12618378 polymorphic query sometimes does not work as expected on server. The GQL on certain entities could fail.
@@ -52496,6 +52564,7 @@ function generateTypedBatches(work, batchSize) {
52496
52564
  return batches;
52497
52565
  }
52498
52566
 
52567
+ const skippedCompoundFieldTypes = ['Address', 'Location'];
52499
52568
  function getMissingElementsFromSuperset(superset, subset) {
52500
52569
  return subset.filter((val) => !superset.includes(val));
52501
52570
  }
@@ -52512,6 +52581,7 @@ function buildFieldUnionArray(existingRecord, incomingRecord, objectInfo) {
52512
52581
  const allFields = Array.from(new Set([...Object.keys(existingRecord.fields), ...Object.keys(incomingRecord.fields)]));
52513
52582
  const fieldUnion = [];
52514
52583
  let includesSpanningFields = false;
52584
+ let includesSkippedFields = false;
52515
52585
  allFields.forEach((fieldName) => {
52516
52586
  const objectInfoField = objectInfo.fields[fieldName];
52517
52587
  if (objectInfoField === undefined) {
@@ -52529,10 +52599,17 @@ function buildFieldUnionArray(existingRecord, incomingRecord, objectInfo) {
52529
52599
  }
52530
52600
  }
52531
52601
  else {
52602
+ if (skippedCompoundFieldTypes.includes(objectInfo.fields[fieldName].dataType)) {
52603
+ includesSkippedFields = true;
52604
+ }
52532
52605
  fieldUnion.push(fieldName);
52533
52606
  }
52534
52607
  });
52535
- return { fields: fieldUnion, includesSpanningFields };
52608
+ return {
52609
+ fields: fieldUnion,
52610
+ includesSpanningFields,
52611
+ includesSkippedFields,
52612
+ };
52536
52613
  }
52537
52614
  /**
52538
52615
  * Merges (if possible) an incoming record from a priming session with an existing record in the durable store.
@@ -52595,9 +52672,15 @@ function mergeRecord(existingRecord, incomingRecord, objectInfo) {
52595
52672
  }
52596
52673
  else {
52597
52674
  const missingFields = getMissingElementsFromSuperset(Object.keys(incomingRecord.fields), Object.keys(existingRecord.fields));
52598
- // if the only missing fields are spanning fields and their corresponding lookup fields match, we can merge
52599
- // since none of the changed fields are part of the incoming record
52600
- if (missingFields.every((field) => {
52675
+ // tests for missing optional fields, pending fields, and spanning fields that can be merged
52676
+ const fieldIsMergeable = (field) => {
52677
+ // missing and pending fields in the existing record are mergeable
52678
+ const fieldState = existingRecord.fields[field].__state;
52679
+ if (fieldState && (fieldState.pending === true || fieldState.isMissing === true)) {
52680
+ return true;
52681
+ }
52682
+ // if the only missing fields are spanning fields and their corresponding lookup fields match, we can merge
52683
+ // since none of the changed fields are part of the incoming record
52601
52684
  const referenceFieldName = findReferenceFieldForSpanningField(field, objectInfo);
52602
52685
  if (referenceFieldName === undefined) {
52603
52686
  return false;
@@ -52606,7 +52689,8 @@ function mergeRecord(existingRecord, incomingRecord, objectInfo) {
52606
52689
  incomingRecord.fields[referenceFieldName] &&
52607
52690
  incomingRecord.fields[referenceFieldName].value ===
52608
52691
  existingRecord.fields[referenceFieldName].value);
52609
- })) {
52692
+ };
52693
+ if (missingFields.every(fieldIsMergeable)) {
52610
52694
  return {
52611
52695
  ok: true,
52612
52696
  needsWrite: true,
@@ -52626,8 +52710,8 @@ function mergeRecord(existingRecord, incomingRecord, objectInfo) {
52626
52710
  };
52627
52711
  }
52628
52712
  // If Etags do not match and the incoming record does not contain all fields, re-request the record
52629
- const { fields, includesSpanningFields } = buildFieldUnionArray(existingRecord, incomingRecord, objectInfo);
52630
- if (includesSpanningFields) {
52713
+ const { fields, includesSpanningFields, includesSkippedFields } = buildFieldUnionArray(existingRecord, incomingRecord, objectInfo);
52714
+ if (includesSpanningFields || includesSkippedFields) {
52631
52715
  return {
52632
52716
  ok: false,
52633
52717
  code: 'conflict-spanning-record',
@@ -53617,7 +53701,15 @@ class RecordLoaderSOQLComposite extends NetworkRecordLoader {
53617
53701
  if (Object.keys(recordTypeInfos).length > 1) {
53618
53702
  fieldSet.add('RecordTypeId');
53619
53703
  }
53620
- const fields = Array.from(fieldSet);
53704
+ const fields = Array.from(fieldSet).filter((field) => {
53705
+ const fieldMetadata = batch.objectInfo.fields[field];
53706
+ // skip Address and Location compound fields
53707
+ if (fieldMetadata.compound &&
53708
+ skippedCompoundFieldTypes.includes(fieldMetadata.dataType)) {
53709
+ return false;
53710
+ }
53711
+ return true;
53712
+ });
53621
53713
  // We will have SOQL format specific data types for us by adding a format() value.
53622
53714
  // The field alias will have ___display added to the end.
53623
53715
  for (const field of fieldSet) {
@@ -53636,7 +53728,6 @@ class RecordLoaderSOQLComposite extends NetworkRecordLoader {
53636
53728
  }
53637
53729
  }
53638
53730
  const query = `SELECT ${fields.join(',')} FROM ${batch.type} `;
53639
- // console.log(`DUSTIN: soql batch query: ${query}`);
53640
53731
  return query;
53641
53732
  }
53642
53733
  generateWhere(ids) {
@@ -55676,4 +55767,4 @@ register({
55676
55767
  });
55677
55768
 
55678
55769
  export { O11Y_NAMESPACE_LDS_MOBILE, getRuntime, ingest$1o as ingestDenormalizedRecordRepresentation, registerReportObserver, reportGraphqlQueryParseError };
55679
- // version: 1.354.0-dev6-a32f37c654
55770
+ // version: 1.354.0-dev7-d25082f814
@@ -14,6 +14,7 @@ export interface MergeResultConflict {
14
14
  fieldUnion: string[];
15
15
  }
16
16
  type MergeResult = MergeResultSuccess | MergeResultConflict;
17
+ export declare const skippedCompoundFieldTypes: string[];
17
18
  /**
18
19
  * Merges (if possible) an incoming record from a priming session with an existing record in the durable store.
19
20
  *