@salesforce/lds-runtime-mobile 1.100.2 → 1.100.4

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.
Files changed (3) hide show
  1. package/dist/main.js +187 -145
  2. package/package.json +15 -15
  3. package/sfdc/main.js +187 -145
package/dist/main.js CHANGED
@@ -4344,6 +4344,44 @@ function makeStoreEval(preconditioner, objectInfoService, userId, contextProvide
4344
4344
  return wrapStartEndEvents(storeEval);
4345
4345
  }
4346
4346
 
4347
+ /**
4348
+ * Copyright (c) 2022, Salesforce, Inc.,
4349
+ * All rights reserved.
4350
+ * For full license text, see the LICENSE.txt file
4351
+ */
4352
+
4353
+ class AsyncWorkerPool {
4354
+ constructor(concurrency) {
4355
+ this.queue = [];
4356
+ this.activeWorkers = 0;
4357
+ this.concurrency = concurrency;
4358
+ }
4359
+ push(workFn) {
4360
+ return new Promise((resolve, reject) => {
4361
+ this.queue.push(async () => {
4362
+ return workFn().then(resolve).catch(reject);
4363
+ });
4364
+ this.work();
4365
+ });
4366
+ }
4367
+ cancel() {
4368
+ this.queue = [];
4369
+ // TODO [W-12513105]: thread cancellation through to active workers
4370
+ }
4371
+ work() {
4372
+ while (this.queue.length > 0 && this.activeWorkers < this.concurrency) {
4373
+ this.activeWorkers += 1;
4374
+ const next = this.queue.shift();
4375
+ if (next) {
4376
+ next().finally(() => {
4377
+ this.activeWorkers -= 1;
4378
+ this.work();
4379
+ });
4380
+ }
4381
+ }
4382
+ }
4383
+ }
4384
+
4347
4385
  /**
4348
4386
  * Copyright (c) 2022, Salesforce, Inc.,
4349
4387
  * All rights reserved.
@@ -4762,6 +4800,7 @@ class DurableDraftQueue {
4762
4800
  this.timeoutHandler = undefined;
4763
4801
  this.handlers = {};
4764
4802
  this.draftStore = draftStore;
4803
+ this.workerPool = new AsyncWorkerPool(1);
4765
4804
  }
4766
4805
  addHandler(handler) {
4767
4806
  const id = handler.handlerId;
@@ -4861,20 +4900,22 @@ class DurableDraftQueue {
4861
4900
  });
4862
4901
  }
4863
4902
  async enqueue(handlerId, data) {
4864
- let queue = await this.getQueueActions();
4865
- const handler = this.getHandler(handlerId);
4866
- const pendingAction = (await handler.buildPendingAction(data, queue));
4867
- await this.draftStore.writeAction(pendingAction);
4868
- queue = await this.getQueueActions();
4869
- await this.notifyChangedListeners({
4870
- type: DraftQueueEventType.ActionAdded,
4871
- action: pendingAction,
4903
+ return this.workerPool.push(async () => {
4904
+ let queue = await this.getQueueActions();
4905
+ const handler = this.getHandler(handlerId);
4906
+ const pendingAction = (await handler.buildPendingAction(data, queue));
4907
+ await this.draftStore.writeAction(pendingAction);
4908
+ queue = await this.getQueueActions();
4909
+ await this.notifyChangedListeners({
4910
+ type: DraftQueueEventType.ActionAdded,
4911
+ action: pendingAction,
4912
+ });
4913
+ await handler.handleActionEnqueued(pendingAction, queue);
4914
+ if (this.state === DraftQueueState.Started) {
4915
+ this.processNextAction();
4916
+ }
4917
+ return pendingAction;
4872
4918
  });
4873
- await handler.handleActionEnqueued(pendingAction, queue);
4874
- if (this.state === DraftQueueState.Started) {
4875
- this.processNextAction();
4876
- }
4877
- return pendingAction;
4878
4919
  }
4879
4920
  registerOnChangedListener(listener) {
4880
4921
  this.draftQueueChangedListeners.push(listener);
@@ -4886,27 +4927,29 @@ class DurableDraftQueue {
4886
4927
  };
4887
4928
  }
4888
4929
  async actionCompleted(action) {
4889
- const handler = this.getHandler(action.handler);
4890
- let queue = await this.getQueueActions();
4891
- const queueOperations = handler.getQueueOperationsForCompletingDrafts(queue, action);
4892
- const idAndKeyMappings = handler.getRedirectMappings(action);
4893
- const keyMappings = idAndKeyMappings === undefined
4894
- ? undefined
4895
- : idAndKeyMappings.map((m) => {
4896
- return { draftKey: m.draftKey, canonicalKey: m.canonicalKey };
4930
+ return this.workerPool.push(async () => {
4931
+ const handler = this.getHandler(action.handler);
4932
+ let queue = await this.getQueueActions();
4933
+ const queueOperations = handler.getQueueOperationsForCompletingDrafts(queue, action);
4934
+ const idAndKeyMappings = handler.getRedirectMappings(action);
4935
+ const keyMappings = idAndKeyMappings === undefined
4936
+ ? undefined
4937
+ : idAndKeyMappings.map((m) => {
4938
+ return { draftKey: m.draftKey, canonicalKey: m.canonicalKey };
4939
+ });
4940
+ await this.draftStore.completeAction(queueOperations, keyMappings);
4941
+ queue = await this.getQueueActions();
4942
+ this.retryIntervalMilliseconds = 0;
4943
+ this.uploadingActionId = undefined;
4944
+ await handler.handleActionCompleted(action, queueOperations, queue, values$1(this.handlers));
4945
+ await this.notifyChangedListeners({
4946
+ type: DraftQueueEventType.ActionCompleted,
4947
+ action,
4897
4948
  });
4898
- await this.draftStore.completeAction(queueOperations, keyMappings);
4899
- queue = await this.getQueueActions();
4900
- this.retryIntervalMilliseconds = 0;
4901
- this.uploadingActionId = undefined;
4902
- await handler.handleActionCompleted(action, queueOperations, queue, values$1(this.handlers));
4903
- await this.notifyChangedListeners({
4904
- type: DraftQueueEventType.ActionCompleted,
4905
- action,
4949
+ if (this.state === DraftQueueState.Started) {
4950
+ this.processNextAction();
4951
+ }
4906
4952
  });
4907
- if (this.state === DraftQueueState.Started) {
4908
- this.processNextAction();
4909
- }
4910
4953
  }
4911
4954
  async actionFailed(action, retry) {
4912
4955
  this.uploadingActionId = undefined;
@@ -5356,6 +5399,13 @@ class AbstractResourceRequestActionHandler {
5356
5399
  this.draftQueue = draftQueue;
5357
5400
  this.networkAdapter = networkAdapter;
5358
5401
  this.getLuvio = getLuvio;
5402
+ // NOTE[W-12567340]: This property stores in-memory mappings between draft
5403
+ // ids and canonical ids for the current session. Having a local copy of
5404
+ // these mappings is necessary to avoid a race condition between publishing
5405
+ // new mappings to the durable store and those mappings being loaded into
5406
+ // the luvio store redirect table, during which a new draft might be enqueued
5407
+ // which would not see a necessary mapping.
5408
+ this.ephemeralRedirects = {};
5359
5409
  }
5360
5410
  enqueue(data) {
5361
5411
  return this.draftQueue.enqueue(this.handlerId, data);
@@ -5423,7 +5473,8 @@ class AbstractResourceRequestActionHandler {
5423
5473
  }
5424
5474
  getQueueOperationsForCompletingDrafts(queue, action) {
5425
5475
  const queueOperations = [];
5426
- if (action.data.method === 'post') {
5476
+ const redirects = this.getRedirectMappings(action);
5477
+ if (redirects !== undefined) {
5427
5478
  const { length } = queue;
5428
5479
  for (let i = 0; i < length; i++) {
5429
5480
  const queueAction = queue[i];
@@ -5433,69 +5484,64 @@ class AbstractResourceRequestActionHandler {
5433
5484
  continue;
5434
5485
  }
5435
5486
  if (isResourceRequestAction(queueAction)) {
5436
- const redirects = this.getRedirectMappings(action);
5437
- if (redirects !== undefined) {
5438
- let queueOperationMutated = false;
5439
- let updatedActionTag = undefined;
5440
- let updatedActionTargetId = undefined;
5441
- const { tag: queueActionTag, data: queueActionRequest, id: queueActionId, } = queueAction;
5442
- let { basePath, body } = queueActionRequest;
5443
- let stringifiedBody = stringify$4(body);
5444
- // for each redirected ID/key we loop over the operation to see if it needs
5445
- // to be updated
5446
- for (const { draftId, draftKey, canonicalId, canonicalKey } of redirects) {
5447
- if (basePath.search(draftId) >= 0 ||
5448
- stringifiedBody.search(draftId) >= 0) {
5449
- basePath = basePath.replace(draftId, canonicalId);
5450
- stringifiedBody = stringifiedBody.replace(draftId, canonicalId);
5451
- queueOperationMutated = true;
5452
- }
5453
- // if the action is performed on a previous draft id, we need to replace the action
5454
- // with a new one at the updated canonical key
5455
- if (queueActionTag === draftKey) {
5456
- updatedActionTag = canonicalKey;
5457
- updatedActionTargetId = canonicalId;
5458
- }
5487
+ let queueOperationMutated = false;
5488
+ let updatedActionTag = undefined;
5489
+ let updatedActionTargetId = undefined;
5490
+ const { tag: queueActionTag, data: queueActionRequest, id: queueActionId, } = queueAction;
5491
+ let { basePath, body } = queueActionRequest;
5492
+ let stringifiedBody = stringify$4(body);
5493
+ // for each redirected ID/key we loop over the operation to see if it needs
5494
+ // to be updated
5495
+ for (const { draftId, draftKey, canonicalId, canonicalKey } of redirects) {
5496
+ if (basePath.search(draftId) >= 0 || stringifiedBody.search(draftId) >= 0) {
5497
+ basePath = basePath.replace(draftId, canonicalId);
5498
+ stringifiedBody = stringifiedBody.replace(draftId, canonicalId);
5499
+ queueOperationMutated = true;
5459
5500
  }
5460
- if (queueOperationMutated) {
5461
- if (updatedActionTag !== undefined &&
5462
- updatedActionTargetId !== undefined) {
5463
- const updatedAction = {
5464
- ...queueAction,
5465
- tag: updatedActionTag,
5466
- targetId: updatedActionTargetId,
5467
- data: {
5468
- ...queueActionRequest,
5469
- basePath: basePath,
5470
- body: parse$4(stringifiedBody),
5471
- },
5472
- };
5473
- // item needs to be replaced with a new item at the new record key
5474
- queueOperations.push({
5475
- type: QueueOperationType.Delete,
5476
- id: queueActionId,
5477
- });
5478
- queueOperations.push({
5479
- type: QueueOperationType.Add,
5480
- action: updatedAction,
5481
- });
5482
- }
5483
- else {
5484
- const updatedAction = {
5485
- ...queueAction,
5486
- data: {
5487
- ...queueActionRequest,
5488
- basePath: basePath,
5489
- body: parse$4(stringifiedBody),
5490
- },
5491
- };
5492
- // item needs to be updated
5493
- queueOperations.push({
5494
- type: QueueOperationType.Update,
5495
- id: queueActionId,
5496
- action: updatedAction,
5497
- });
5498
- }
5501
+ // if the action is performed on a previous draft id, we need to replace the action
5502
+ // with a new one at the updated canonical key
5503
+ if (queueActionTag === draftKey) {
5504
+ updatedActionTag = canonicalKey;
5505
+ updatedActionTargetId = canonicalId;
5506
+ }
5507
+ }
5508
+ if (queueOperationMutated) {
5509
+ if (updatedActionTag !== undefined && updatedActionTargetId !== undefined) {
5510
+ const updatedAction = {
5511
+ ...queueAction,
5512
+ tag: updatedActionTag,
5513
+ targetId: updatedActionTargetId,
5514
+ data: {
5515
+ ...queueActionRequest,
5516
+ basePath: basePath,
5517
+ body: parse$4(stringifiedBody),
5518
+ },
5519
+ };
5520
+ // item needs to be replaced with a new item at the new record key
5521
+ queueOperations.push({
5522
+ type: QueueOperationType.Delete,
5523
+ id: queueActionId,
5524
+ });
5525
+ queueOperations.push({
5526
+ type: QueueOperationType.Add,
5527
+ action: updatedAction,
5528
+ });
5529
+ }
5530
+ else {
5531
+ const updatedAction = {
5532
+ ...queueAction,
5533
+ data: {
5534
+ ...queueActionRequest,
5535
+ basePath: basePath,
5536
+ body: parse$4(stringifiedBody),
5537
+ },
5538
+ };
5539
+ // item needs to be updated
5540
+ queueOperations.push({
5541
+ type: QueueOperationType.Update,
5542
+ id: queueActionId,
5543
+ action: updatedAction,
5544
+ });
5499
5545
  }
5500
5546
  }
5501
5547
  }
@@ -5515,6 +5561,9 @@ class AbstractResourceRequestActionHandler {
5515
5561
  const body = action.response.body;
5516
5562
  const canonicalId = this.getIdFromResponseBody(body);
5517
5563
  const draftId = action.targetId;
5564
+ if (draftId !== undefined && canonicalId !== undefined && draftId !== canonicalId) {
5565
+ this.ephemeralRedirects[draftId] = canonicalId;
5566
+ }
5518
5567
  return [
5519
5568
  {
5520
5569
  draftId,
@@ -10220,7 +10269,8 @@ const assignedToMeFragmentSelections = [
10220
10269
  },
10221
10270
  ];
10222
10271
  function removeSyntheticFields(result, query) {
10223
- const connectionSelection = query.definitions
10272
+ // It is possible that there are multiple record query.
10273
+ const connectionSelections = query.definitions
10224
10274
  .filter(isOperationDefinitionNode)
10225
10275
  .reduce((accu, definition) => {
10226
10276
  return accu.concat(definition.selectionSet.selections);
@@ -10231,10 +10281,12 @@ function removeSyntheticFields(result, query) {
10231
10281
  .filter(isFieldNode)
10232
10282
  .filter(nodeIsNamed('query'))
10233
10283
  .reduce(extractSelections, [])
10234
- .filter(isFieldNode)[0];
10284
+ .filter(isFieldNode);
10235
10285
  // queries that are not RecordQuery will be undefined here.
10236
- // we need to also check that connectionSelection is not undefined.
10237
- if (!connectionSelection || !connectionSelection.selectionSet) {
10286
+ // we need to also check that connectionSelections are not undefined. If any connectionSelection has no `selectionSet` defined, that is a malformed query.
10287
+ if (!connectionSelections ||
10288
+ connectionSelections.length === 0 ||
10289
+ connectionSelections.some((connection) => connection.selectionSet === undefined)) {
10238
10290
  return result;
10239
10291
  }
10240
10292
  const nodeJson = result.data.uiapi.query;
@@ -10243,10 +10295,15 @@ function removeSyntheticFields(result, query) {
10243
10295
  const output = { ...result };
10244
10296
  const outputApiParent = output.data.uiapi.query;
10245
10297
  const keys$1 = keys$3(nodeJson);
10246
- keys$1.forEach((apiName) => {
10298
+ keys$1.forEach((recordName) => {
10247
10299
  const outputApi = {};
10248
- createUserJsonOutput(connectionSelection, nodeJson[apiName], outputApi);
10249
- outputApiParent[apiName] = outputApi;
10300
+ // Each connectionSelection's maps its name or alias to one of returned records. The record name could be `apiName' or alias
10301
+ const targetConnection = connectionSelections.find((connection) => connection.name.value === recordName ||
10302
+ (connection.alias !== undefined && connection.alias.value === recordName));
10303
+ if (targetConnection !== undefined) {
10304
+ createUserJsonOutput(targetConnection, nodeJson[recordName], outputApi);
10305
+ outputApiParent[recordName] = outputApi;
10306
+ }
10250
10307
  });
10251
10308
  return output;
10252
10309
  }
@@ -11288,12 +11345,18 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
11288
11345
  const fieldName = fieldNames[i];
11289
11346
  const fieldValue = bodyFields[fieldName];
11290
11347
  if (typeof fieldValue === 'string' && this.isDraftId(fieldValue)) {
11291
- const draftKey = this.buildTagForTargetId(fieldValue);
11292
- const canonicalKey = this.getLuvio().storeGetCanonicalKey(draftKey);
11293
- if (draftKey !== canonicalKey) {
11294
- const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11295
- if (canonicalId !== undefined) {
11296
- bodyFields[fieldName] = canonicalId;
11348
+ const canonicalId = this.ephemeralRedirects[fieldValue];
11349
+ if (canonicalId !== undefined) {
11350
+ bodyFields[fieldName] = canonicalId;
11351
+ }
11352
+ else {
11353
+ const draftKey = this.buildTagForTargetId(fieldValue);
11354
+ const canonicalKey = this.getLuvio().storeGetCanonicalKey(draftKey);
11355
+ if (draftKey !== canonicalKey) {
11356
+ const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11357
+ if (canonicalId !== undefined) {
11358
+ bodyFields[fieldName] = canonicalId;
11359
+ }
11297
11360
  }
11298
11361
  }
11299
11362
  }
@@ -11301,17 +11364,24 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
11301
11364
  }
11302
11365
  if (request.method === 'patch' || request.method === 'delete') {
11303
11366
  const recordId = request.urlParams['recordId'];
11304
- const recordKey = this.buildTagForTargetId(recordId);
11305
- const canonicalKey = this.getLuvio().storeGetCanonicalKey(recordKey);
11306
- if (recordKey !== canonicalKey) {
11307
- const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11308
- if (canonicalId === undefined) {
11309
- // could not resolve id from request -- return the request un-modified
11310
- return request;
11311
- }
11367
+ const canonicalId = this.ephemeralRedirects[recordId];
11368
+ if (canonicalId !== undefined) {
11312
11369
  resolvedBasePath = resolvedBasePath.replace(recordId, canonicalId);
11313
11370
  resolvedUrlParams = { ...resolvedUrlParams, recordId: canonicalId };
11314
11371
  }
11372
+ else {
11373
+ const recordKey = this.buildTagForTargetId(recordId);
11374
+ const canonicalKey = this.getLuvio().storeGetCanonicalKey(recordKey);
11375
+ if (recordKey !== canonicalKey) {
11376
+ const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11377
+ if (canonicalId === undefined) {
11378
+ // could not resolve id from request -- return the request un-modified
11379
+ return request;
11380
+ }
11381
+ resolvedBasePath = resolvedBasePath.replace(recordId, canonicalId);
11382
+ resolvedUrlParams = { ...resolvedUrlParams, recordId: canonicalId };
11383
+ }
11384
+ }
11315
11385
  }
11316
11386
  return {
11317
11387
  ...request,
@@ -14659,34 +14729,6 @@ function setupInspection(luvio) {
14659
14729
  * For full license text, see the LICENSE.txt file
14660
14730
  */
14661
14731
 
14662
- class AsyncWorkerPool {
14663
- constructor(concurrency) {
14664
- this.queue = [];
14665
- this.activeWorkers = 0;
14666
- this.concurrency = concurrency;
14667
- }
14668
- push(workFn) {
14669
- this.queue.push(workFn);
14670
- this.work();
14671
- }
14672
- cancel() {
14673
- this.queue = [];
14674
- // TODO [W-12513105]: thread cancellation through to active workers
14675
- }
14676
- work() {
14677
- while (this.queue.length > 0 && this.activeWorkers < this.concurrency) {
14678
- this.activeWorkers += 1;
14679
- const next = this.queue.shift();
14680
- if (next) {
14681
- next().finally(() => {
14682
- this.activeWorkers -= 1;
14683
- this.work();
14684
- });
14685
- }
14686
- }
14687
- }
14688
- }
14689
-
14690
14732
  class EventEmitter {
14691
14733
  constructor() {
14692
14734
  // @ts-ignore typescript doesn't like us setting this to an empty object for some reason
@@ -15239,4 +15281,4 @@ register({
15239
15281
  });
15240
15282
 
15241
15283
  export { getRuntime, registerReportObserver, reportGraphqlQueryParseError };
15242
- // version: 1.100.2-ca56bb821
15284
+ // version: 1.100.4-ce1be23b7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/lds-runtime-mobile",
3
- "version": "1.100.2",
3
+ "version": "1.100.4",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "description": "LDS runtime for mobile/hybrid environments.",
6
6
  "main": "dist/main.js",
@@ -32,10 +32,10 @@
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.100.2",
36
- "@salesforce/lds-bindings": "^1.100.2",
37
- "@salesforce/lds-instrumentation": "^1.100.2",
38
- "@salesforce/lds-priming": "^1.100.2",
35
+ "@salesforce/lds-adapters-uiapi": "^1.100.4",
36
+ "@salesforce/lds-bindings": "^1.100.4",
37
+ "@salesforce/lds-instrumentation": "^1.100.4",
38
+ "@salesforce/lds-priming": "^1.100.4",
39
39
  "@salesforce/user": "0.0.12",
40
40
  "o11y": "244.0.0"
41
41
  },
@@ -43,16 +43,16 @@
43
43
  "@luvio/engine": "0.135.4",
44
44
  "@luvio/environments": "0.135.4",
45
45
  "@luvio/graphql-parser": "0.135.4",
46
- "@salesforce/lds-adapters-graphql": "^1.100.2",
47
- "@salesforce/lds-drafts": "^1.100.2",
48
- "@salesforce/lds-drafts-adapters-uiapi": "^1.100.2",
49
- "@salesforce/lds-graphql-eval": "^1.100.2",
50
- "@salesforce/lds-network-adapter": "^1.100.2",
51
- "@salesforce/lds-network-nimbus": "^1.100.2",
52
- "@salesforce/lds-store-binary": "^1.100.2",
53
- "@salesforce/lds-store-sql": "^1.100.2",
54
- "@salesforce/lds-utils-adapters": "^1.100.2",
55
- "@salesforce/nimbus-plugin-lds": "^1.100.2",
46
+ "@salesforce/lds-adapters-graphql": "^1.100.4",
47
+ "@salesforce/lds-drafts": "^1.100.4",
48
+ "@salesforce/lds-drafts-adapters-uiapi": "^1.100.4",
49
+ "@salesforce/lds-graphql-eval": "^1.100.4",
50
+ "@salesforce/lds-network-adapter": "^1.100.4",
51
+ "@salesforce/lds-network-nimbus": "^1.100.4",
52
+ "@salesforce/lds-store-binary": "^1.100.4",
53
+ "@salesforce/lds-store-sql": "^1.100.4",
54
+ "@salesforce/lds-utils-adapters": "^1.100.4",
55
+ "@salesforce/nimbus-plugin-lds": "^1.100.4",
56
56
  "babel-plugin-dynamic-import-node": "^2.3.3",
57
57
  "wait-for-expect": "^3.0.2"
58
58
  },
package/sfdc/main.js CHANGED
@@ -4344,6 +4344,44 @@ function makeStoreEval(preconditioner, objectInfoService, userId, contextProvide
4344
4344
  return wrapStartEndEvents(storeEval);
4345
4345
  }
4346
4346
 
4347
+ /**
4348
+ * Copyright (c) 2022, Salesforce, Inc.,
4349
+ * All rights reserved.
4350
+ * For full license text, see the LICENSE.txt file
4351
+ */
4352
+
4353
+ class AsyncWorkerPool {
4354
+ constructor(concurrency) {
4355
+ this.queue = [];
4356
+ this.activeWorkers = 0;
4357
+ this.concurrency = concurrency;
4358
+ }
4359
+ push(workFn) {
4360
+ return new Promise((resolve, reject) => {
4361
+ this.queue.push(async () => {
4362
+ return workFn().then(resolve).catch(reject);
4363
+ });
4364
+ this.work();
4365
+ });
4366
+ }
4367
+ cancel() {
4368
+ this.queue = [];
4369
+ // TODO [W-12513105]: thread cancellation through to active workers
4370
+ }
4371
+ work() {
4372
+ while (this.queue.length > 0 && this.activeWorkers < this.concurrency) {
4373
+ this.activeWorkers += 1;
4374
+ const next = this.queue.shift();
4375
+ if (next) {
4376
+ next().finally(() => {
4377
+ this.activeWorkers -= 1;
4378
+ this.work();
4379
+ });
4380
+ }
4381
+ }
4382
+ }
4383
+ }
4384
+
4347
4385
  /**
4348
4386
  * Copyright (c) 2022, Salesforce, Inc.,
4349
4387
  * All rights reserved.
@@ -4762,6 +4800,7 @@ class DurableDraftQueue {
4762
4800
  this.timeoutHandler = undefined;
4763
4801
  this.handlers = {};
4764
4802
  this.draftStore = draftStore;
4803
+ this.workerPool = new AsyncWorkerPool(1);
4765
4804
  }
4766
4805
  addHandler(handler) {
4767
4806
  const id = handler.handlerId;
@@ -4861,20 +4900,22 @@ class DurableDraftQueue {
4861
4900
  });
4862
4901
  }
4863
4902
  async enqueue(handlerId, data) {
4864
- let queue = await this.getQueueActions();
4865
- const handler = this.getHandler(handlerId);
4866
- const pendingAction = (await handler.buildPendingAction(data, queue));
4867
- await this.draftStore.writeAction(pendingAction);
4868
- queue = await this.getQueueActions();
4869
- await this.notifyChangedListeners({
4870
- type: DraftQueueEventType.ActionAdded,
4871
- action: pendingAction,
4903
+ return this.workerPool.push(async () => {
4904
+ let queue = await this.getQueueActions();
4905
+ const handler = this.getHandler(handlerId);
4906
+ const pendingAction = (await handler.buildPendingAction(data, queue));
4907
+ await this.draftStore.writeAction(pendingAction);
4908
+ queue = await this.getQueueActions();
4909
+ await this.notifyChangedListeners({
4910
+ type: DraftQueueEventType.ActionAdded,
4911
+ action: pendingAction,
4912
+ });
4913
+ await handler.handleActionEnqueued(pendingAction, queue);
4914
+ if (this.state === DraftQueueState.Started) {
4915
+ this.processNextAction();
4916
+ }
4917
+ return pendingAction;
4872
4918
  });
4873
- await handler.handleActionEnqueued(pendingAction, queue);
4874
- if (this.state === DraftQueueState.Started) {
4875
- this.processNextAction();
4876
- }
4877
- return pendingAction;
4878
4919
  }
4879
4920
  registerOnChangedListener(listener) {
4880
4921
  this.draftQueueChangedListeners.push(listener);
@@ -4886,27 +4927,29 @@ class DurableDraftQueue {
4886
4927
  };
4887
4928
  }
4888
4929
  async actionCompleted(action) {
4889
- const handler = this.getHandler(action.handler);
4890
- let queue = await this.getQueueActions();
4891
- const queueOperations = handler.getQueueOperationsForCompletingDrafts(queue, action);
4892
- const idAndKeyMappings = handler.getRedirectMappings(action);
4893
- const keyMappings = idAndKeyMappings === undefined
4894
- ? undefined
4895
- : idAndKeyMappings.map((m) => {
4896
- return { draftKey: m.draftKey, canonicalKey: m.canonicalKey };
4930
+ return this.workerPool.push(async () => {
4931
+ const handler = this.getHandler(action.handler);
4932
+ let queue = await this.getQueueActions();
4933
+ const queueOperations = handler.getQueueOperationsForCompletingDrafts(queue, action);
4934
+ const idAndKeyMappings = handler.getRedirectMappings(action);
4935
+ const keyMappings = idAndKeyMappings === undefined
4936
+ ? undefined
4937
+ : idAndKeyMappings.map((m) => {
4938
+ return { draftKey: m.draftKey, canonicalKey: m.canonicalKey };
4939
+ });
4940
+ await this.draftStore.completeAction(queueOperations, keyMappings);
4941
+ queue = await this.getQueueActions();
4942
+ this.retryIntervalMilliseconds = 0;
4943
+ this.uploadingActionId = undefined;
4944
+ await handler.handleActionCompleted(action, queueOperations, queue, values$1(this.handlers));
4945
+ await this.notifyChangedListeners({
4946
+ type: DraftQueueEventType.ActionCompleted,
4947
+ action,
4897
4948
  });
4898
- await this.draftStore.completeAction(queueOperations, keyMappings);
4899
- queue = await this.getQueueActions();
4900
- this.retryIntervalMilliseconds = 0;
4901
- this.uploadingActionId = undefined;
4902
- await handler.handleActionCompleted(action, queueOperations, queue, values$1(this.handlers));
4903
- await this.notifyChangedListeners({
4904
- type: DraftQueueEventType.ActionCompleted,
4905
- action,
4949
+ if (this.state === DraftQueueState.Started) {
4950
+ this.processNextAction();
4951
+ }
4906
4952
  });
4907
- if (this.state === DraftQueueState.Started) {
4908
- this.processNextAction();
4909
- }
4910
4953
  }
4911
4954
  async actionFailed(action, retry) {
4912
4955
  this.uploadingActionId = undefined;
@@ -5356,6 +5399,13 @@ class AbstractResourceRequestActionHandler {
5356
5399
  this.draftQueue = draftQueue;
5357
5400
  this.networkAdapter = networkAdapter;
5358
5401
  this.getLuvio = getLuvio;
5402
+ // NOTE[W-12567340]: This property stores in-memory mappings between draft
5403
+ // ids and canonical ids for the current session. Having a local copy of
5404
+ // these mappings is necessary to avoid a race condition between publishing
5405
+ // new mappings to the durable store and those mappings being loaded into
5406
+ // the luvio store redirect table, during which a new draft might be enqueued
5407
+ // which would not see a necessary mapping.
5408
+ this.ephemeralRedirects = {};
5359
5409
  }
5360
5410
  enqueue(data) {
5361
5411
  return this.draftQueue.enqueue(this.handlerId, data);
@@ -5423,7 +5473,8 @@ class AbstractResourceRequestActionHandler {
5423
5473
  }
5424
5474
  getQueueOperationsForCompletingDrafts(queue, action) {
5425
5475
  const queueOperations = [];
5426
- if (action.data.method === 'post') {
5476
+ const redirects = this.getRedirectMappings(action);
5477
+ if (redirects !== undefined) {
5427
5478
  const { length } = queue;
5428
5479
  for (let i = 0; i < length; i++) {
5429
5480
  const queueAction = queue[i];
@@ -5433,69 +5484,64 @@ class AbstractResourceRequestActionHandler {
5433
5484
  continue;
5434
5485
  }
5435
5486
  if (isResourceRequestAction(queueAction)) {
5436
- const redirects = this.getRedirectMappings(action);
5437
- if (redirects !== undefined) {
5438
- let queueOperationMutated = false;
5439
- let updatedActionTag = undefined;
5440
- let updatedActionTargetId = undefined;
5441
- const { tag: queueActionTag, data: queueActionRequest, id: queueActionId, } = queueAction;
5442
- let { basePath, body } = queueActionRequest;
5443
- let stringifiedBody = stringify$4(body);
5444
- // for each redirected ID/key we loop over the operation to see if it needs
5445
- // to be updated
5446
- for (const { draftId, draftKey, canonicalId, canonicalKey } of redirects) {
5447
- if (basePath.search(draftId) >= 0 ||
5448
- stringifiedBody.search(draftId) >= 0) {
5449
- basePath = basePath.replace(draftId, canonicalId);
5450
- stringifiedBody = stringifiedBody.replace(draftId, canonicalId);
5451
- queueOperationMutated = true;
5452
- }
5453
- // if the action is performed on a previous draft id, we need to replace the action
5454
- // with a new one at the updated canonical key
5455
- if (queueActionTag === draftKey) {
5456
- updatedActionTag = canonicalKey;
5457
- updatedActionTargetId = canonicalId;
5458
- }
5487
+ let queueOperationMutated = false;
5488
+ let updatedActionTag = undefined;
5489
+ let updatedActionTargetId = undefined;
5490
+ const { tag: queueActionTag, data: queueActionRequest, id: queueActionId, } = queueAction;
5491
+ let { basePath, body } = queueActionRequest;
5492
+ let stringifiedBody = stringify$4(body);
5493
+ // for each redirected ID/key we loop over the operation to see if it needs
5494
+ // to be updated
5495
+ for (const { draftId, draftKey, canonicalId, canonicalKey } of redirects) {
5496
+ if (basePath.search(draftId) >= 0 || stringifiedBody.search(draftId) >= 0) {
5497
+ basePath = basePath.replace(draftId, canonicalId);
5498
+ stringifiedBody = stringifiedBody.replace(draftId, canonicalId);
5499
+ queueOperationMutated = true;
5459
5500
  }
5460
- if (queueOperationMutated) {
5461
- if (updatedActionTag !== undefined &&
5462
- updatedActionTargetId !== undefined) {
5463
- const updatedAction = {
5464
- ...queueAction,
5465
- tag: updatedActionTag,
5466
- targetId: updatedActionTargetId,
5467
- data: {
5468
- ...queueActionRequest,
5469
- basePath: basePath,
5470
- body: parse$4(stringifiedBody),
5471
- },
5472
- };
5473
- // item needs to be replaced with a new item at the new record key
5474
- queueOperations.push({
5475
- type: QueueOperationType.Delete,
5476
- id: queueActionId,
5477
- });
5478
- queueOperations.push({
5479
- type: QueueOperationType.Add,
5480
- action: updatedAction,
5481
- });
5482
- }
5483
- else {
5484
- const updatedAction = {
5485
- ...queueAction,
5486
- data: {
5487
- ...queueActionRequest,
5488
- basePath: basePath,
5489
- body: parse$4(stringifiedBody),
5490
- },
5491
- };
5492
- // item needs to be updated
5493
- queueOperations.push({
5494
- type: QueueOperationType.Update,
5495
- id: queueActionId,
5496
- action: updatedAction,
5497
- });
5498
- }
5501
+ // if the action is performed on a previous draft id, we need to replace the action
5502
+ // with a new one at the updated canonical key
5503
+ if (queueActionTag === draftKey) {
5504
+ updatedActionTag = canonicalKey;
5505
+ updatedActionTargetId = canonicalId;
5506
+ }
5507
+ }
5508
+ if (queueOperationMutated) {
5509
+ if (updatedActionTag !== undefined && updatedActionTargetId !== undefined) {
5510
+ const updatedAction = {
5511
+ ...queueAction,
5512
+ tag: updatedActionTag,
5513
+ targetId: updatedActionTargetId,
5514
+ data: {
5515
+ ...queueActionRequest,
5516
+ basePath: basePath,
5517
+ body: parse$4(stringifiedBody),
5518
+ },
5519
+ };
5520
+ // item needs to be replaced with a new item at the new record key
5521
+ queueOperations.push({
5522
+ type: QueueOperationType.Delete,
5523
+ id: queueActionId,
5524
+ });
5525
+ queueOperations.push({
5526
+ type: QueueOperationType.Add,
5527
+ action: updatedAction,
5528
+ });
5529
+ }
5530
+ else {
5531
+ const updatedAction = {
5532
+ ...queueAction,
5533
+ data: {
5534
+ ...queueActionRequest,
5535
+ basePath: basePath,
5536
+ body: parse$4(stringifiedBody),
5537
+ },
5538
+ };
5539
+ // item needs to be updated
5540
+ queueOperations.push({
5541
+ type: QueueOperationType.Update,
5542
+ id: queueActionId,
5543
+ action: updatedAction,
5544
+ });
5499
5545
  }
5500
5546
  }
5501
5547
  }
@@ -5515,6 +5561,9 @@ class AbstractResourceRequestActionHandler {
5515
5561
  const body = action.response.body;
5516
5562
  const canonicalId = this.getIdFromResponseBody(body);
5517
5563
  const draftId = action.targetId;
5564
+ if (draftId !== undefined && canonicalId !== undefined && draftId !== canonicalId) {
5565
+ this.ephemeralRedirects[draftId] = canonicalId;
5566
+ }
5518
5567
  return [
5519
5568
  {
5520
5569
  draftId,
@@ -10220,7 +10269,8 @@ const assignedToMeFragmentSelections = [
10220
10269
  },
10221
10270
  ];
10222
10271
  function removeSyntheticFields(result, query) {
10223
- const connectionSelection = query.definitions
10272
+ // It is possible that there are multiple record query.
10273
+ const connectionSelections = query.definitions
10224
10274
  .filter(isOperationDefinitionNode)
10225
10275
  .reduce((accu, definition) => {
10226
10276
  return accu.concat(definition.selectionSet.selections);
@@ -10231,10 +10281,12 @@ function removeSyntheticFields(result, query) {
10231
10281
  .filter(isFieldNode)
10232
10282
  .filter(nodeIsNamed('query'))
10233
10283
  .reduce(extractSelections, [])
10234
- .filter(isFieldNode)[0];
10284
+ .filter(isFieldNode);
10235
10285
  // queries that are not RecordQuery will be undefined here.
10236
- // we need to also check that connectionSelection is not undefined.
10237
- if (!connectionSelection || !connectionSelection.selectionSet) {
10286
+ // we need to also check that connectionSelections are not undefined. If any connectionSelection has no `selectionSet` defined, that is a malformed query.
10287
+ if (!connectionSelections ||
10288
+ connectionSelections.length === 0 ||
10289
+ connectionSelections.some((connection) => connection.selectionSet === undefined)) {
10238
10290
  return result;
10239
10291
  }
10240
10292
  const nodeJson = result.data.uiapi.query;
@@ -10243,10 +10295,15 @@ function removeSyntheticFields(result, query) {
10243
10295
  const output = { ...result };
10244
10296
  const outputApiParent = output.data.uiapi.query;
10245
10297
  const keys$1 = keys$3(nodeJson);
10246
- keys$1.forEach((apiName) => {
10298
+ keys$1.forEach((recordName) => {
10247
10299
  const outputApi = {};
10248
- createUserJsonOutput(connectionSelection, nodeJson[apiName], outputApi);
10249
- outputApiParent[apiName] = outputApi;
10300
+ // Each connectionSelection's maps its name or alias to one of returned records. The record name could be `apiName' or alias
10301
+ const targetConnection = connectionSelections.find((connection) => connection.name.value === recordName ||
10302
+ (connection.alias !== undefined && connection.alias.value === recordName));
10303
+ if (targetConnection !== undefined) {
10304
+ createUserJsonOutput(targetConnection, nodeJson[recordName], outputApi);
10305
+ outputApiParent[recordName] = outputApi;
10306
+ }
10250
10307
  });
10251
10308
  return output;
10252
10309
  }
@@ -11288,12 +11345,18 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
11288
11345
  const fieldName = fieldNames[i];
11289
11346
  const fieldValue = bodyFields[fieldName];
11290
11347
  if (typeof fieldValue === 'string' && this.isDraftId(fieldValue)) {
11291
- const draftKey = this.buildTagForTargetId(fieldValue);
11292
- const canonicalKey = this.getLuvio().storeGetCanonicalKey(draftKey);
11293
- if (draftKey !== canonicalKey) {
11294
- const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11295
- if (canonicalId !== undefined) {
11296
- bodyFields[fieldName] = canonicalId;
11348
+ const canonicalId = this.ephemeralRedirects[fieldValue];
11349
+ if (canonicalId !== undefined) {
11350
+ bodyFields[fieldName] = canonicalId;
11351
+ }
11352
+ else {
11353
+ const draftKey = this.buildTagForTargetId(fieldValue);
11354
+ const canonicalKey = this.getLuvio().storeGetCanonicalKey(draftKey);
11355
+ if (draftKey !== canonicalKey) {
11356
+ const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11357
+ if (canonicalId !== undefined) {
11358
+ bodyFields[fieldName] = canonicalId;
11359
+ }
11297
11360
  }
11298
11361
  }
11299
11362
  }
@@ -11301,17 +11364,24 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
11301
11364
  }
11302
11365
  if (request.method === 'patch' || request.method === 'delete') {
11303
11366
  const recordId = request.urlParams['recordId'];
11304
- const recordKey = this.buildTagForTargetId(recordId);
11305
- const canonicalKey = this.getLuvio().storeGetCanonicalKey(recordKey);
11306
- if (recordKey !== canonicalKey) {
11307
- const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11308
- if (canonicalId === undefined) {
11309
- // could not resolve id from request -- return the request un-modified
11310
- return request;
11311
- }
11367
+ const canonicalId = this.ephemeralRedirects[recordId];
11368
+ if (canonicalId !== undefined) {
11312
11369
  resolvedBasePath = resolvedBasePath.replace(recordId, canonicalId);
11313
11370
  resolvedUrlParams = { ...resolvedUrlParams, recordId: canonicalId };
11314
11371
  }
11372
+ else {
11373
+ const recordKey = this.buildTagForTargetId(recordId);
11374
+ const canonicalKey = this.getLuvio().storeGetCanonicalKey(recordKey);
11375
+ if (recordKey !== canonicalKey) {
11376
+ const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11377
+ if (canonicalId === undefined) {
11378
+ // could not resolve id from request -- return the request un-modified
11379
+ return request;
11380
+ }
11381
+ resolvedBasePath = resolvedBasePath.replace(recordId, canonicalId);
11382
+ resolvedUrlParams = { ...resolvedUrlParams, recordId: canonicalId };
11383
+ }
11384
+ }
11315
11385
  }
11316
11386
  return {
11317
11387
  ...request,
@@ -14659,34 +14729,6 @@ function setupInspection(luvio) {
14659
14729
  * For full license text, see the LICENSE.txt file
14660
14730
  */
14661
14731
 
14662
- class AsyncWorkerPool {
14663
- constructor(concurrency) {
14664
- this.queue = [];
14665
- this.activeWorkers = 0;
14666
- this.concurrency = concurrency;
14667
- }
14668
- push(workFn) {
14669
- this.queue.push(workFn);
14670
- this.work();
14671
- }
14672
- cancel() {
14673
- this.queue = [];
14674
- // TODO [W-12513105]: thread cancellation through to active workers
14675
- }
14676
- work() {
14677
- while (this.queue.length > 0 && this.activeWorkers < this.concurrency) {
14678
- this.activeWorkers += 1;
14679
- const next = this.queue.shift();
14680
- if (next) {
14681
- next().finally(() => {
14682
- this.activeWorkers -= 1;
14683
- this.work();
14684
- });
14685
- }
14686
- }
14687
- }
14688
- }
14689
-
14690
14732
  class EventEmitter {
14691
14733
  constructor() {
14692
14734
  // @ts-ignore typescript doesn't like us setting this to an empty object for some reason
@@ -15239,4 +15281,4 @@ register({
15239
15281
  });
15240
15282
 
15241
15283
  export { getRuntime, registerReportObserver, reportGraphqlQueryParseError };
15242
- // version: 1.100.2-ca56bb821
15284
+ // version: 1.100.4-ce1be23b7