@salesforce/lds-runtime-mobile 1.100.3 → 1.100.5

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.
@@ -7,7 +7,10 @@ import type { DraftAction, DraftQueue, ProcessActionResult, DraftQueueState, Dra
7
7
  * to a concrete implementation running in jscore.
8
8
  */
9
9
  export declare class NimbusDraftQueue implements DraftQueue {
10
- enqueue<Response>(handlerId: string, data: unknown): Promise<PendingDraftAction<Response>>;
10
+ enqueue<Data, Response>(handlerId: string, data: unknown): Promise<{
11
+ action: PendingDraftAction<Data>;
12
+ data: Response;
13
+ }>;
11
14
  registerOnChangedListener(_listener: DraftQueueChangeListener): () => Promise<void>;
12
15
  processNextAction(): Promise<ProcessActionResult>;
13
16
  getQueueActions(): Promise<DraftAction<unknown, unknown>[]>;
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.
@@ -4528,14 +4566,13 @@ const { isArray: isArray$3 } = Array;
4528
4566
  function buildLuvioOverrideForDraftAdapters(luvio, handler, extractTargetIdFromCacheKey, options = {}) {
4529
4567
  // override this to create and enqueue a new draft action, and return synthetic response
4530
4568
  const dispatchResourceRequest = async function (resourceRequest, _context) {
4531
- const action = await handler.enqueue(resourceRequest).catch((err) => {
4569
+ const { data } = await handler.enqueue(resourceRequest).catch((err) => {
4532
4570
  throw transformErrorToDraftSynthesisError(err);
4533
4571
  });
4534
- const record = await handler.getDataForAction(action);
4535
- if (record === undefined) {
4572
+ if (data === undefined) {
4536
4573
  return Promise.reject(createDraftSynthesisErrorResponse());
4537
4574
  }
4538
- return createOkResponse(record);
4575
+ return createOkResponse(data);
4539
4576
  };
4540
4577
  // override this to use an infinitely large ttl so the cache entry never expires
4541
4578
  const publishStoreMetadata = function (key, storeMetadataParams) {
@@ -4762,6 +4799,7 @@ class DurableDraftQueue {
4762
4799
  this.timeoutHandler = undefined;
4763
4800
  this.handlers = {};
4764
4801
  this.draftStore = draftStore;
4802
+ this.workerPool = new AsyncWorkerPool(1);
4765
4803
  }
4766
4804
  addHandler(handler) {
4767
4805
  const id = handler.handlerId;
@@ -4779,10 +4817,6 @@ class DurableDraftQueue {
4779
4817
  const handler = customActionHandler(executor, id, this);
4780
4818
  return this.addHandler(handler);
4781
4819
  }
4782
- async getDataForAction(action) {
4783
- const handler = this.getHandler(action.handler);
4784
- return handler.getDataForAction(action);
4785
- }
4786
4820
  getQueueState() {
4787
4821
  return this.state;
4788
4822
  }
@@ -4861,20 +4895,26 @@ class DurableDraftQueue {
4861
4895
  });
4862
4896
  }
4863
4897
  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,
4898
+ return this.workerPool.push(async () => {
4899
+ let queue = await this.getQueueActions();
4900
+ const handler = this.getHandler(handlerId);
4901
+ const pendingAction = (await handler.buildPendingAction(data, queue));
4902
+ await this.draftStore.writeAction(pendingAction);
4903
+ queue = await this.getQueueActions();
4904
+ await this.notifyChangedListeners({
4905
+ type: DraftQueueEventType.ActionAdded,
4906
+ action: pendingAction,
4907
+ });
4908
+ await handler.handleActionEnqueued(pendingAction, queue);
4909
+ if (this.state === DraftQueueState.Started) {
4910
+ this.processNextAction();
4911
+ }
4912
+ const actionData = (await handler.getDataForAction(pendingAction));
4913
+ return {
4914
+ action: pendingAction,
4915
+ data: actionData,
4916
+ };
4872
4917
  });
4873
- await handler.handleActionEnqueued(pendingAction, queue);
4874
- if (this.state === DraftQueueState.Started) {
4875
- this.processNextAction();
4876
- }
4877
- return pendingAction;
4878
4918
  }
4879
4919
  registerOnChangedListener(listener) {
4880
4920
  this.draftQueueChangedListeners.push(listener);
@@ -4886,27 +4926,29 @@ class DurableDraftQueue {
4886
4926
  };
4887
4927
  }
4888
4928
  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 };
4929
+ return this.workerPool.push(async () => {
4930
+ const handler = this.getHandler(action.handler);
4931
+ let queue = await this.getQueueActions();
4932
+ const queueOperations = handler.getQueueOperationsForCompletingDrafts(queue, action);
4933
+ const idAndKeyMappings = handler.getRedirectMappings(action);
4934
+ const keyMappings = idAndKeyMappings === undefined
4935
+ ? undefined
4936
+ : idAndKeyMappings.map((m) => {
4937
+ return { draftKey: m.draftKey, canonicalKey: m.canonicalKey };
4938
+ });
4939
+ await this.draftStore.completeAction(queueOperations, keyMappings);
4940
+ queue = await this.getQueueActions();
4941
+ this.retryIntervalMilliseconds = 0;
4942
+ this.uploadingActionId = undefined;
4943
+ await handler.handleActionCompleted(action, queueOperations, queue, values$1(this.handlers));
4944
+ await this.notifyChangedListeners({
4945
+ type: DraftQueueEventType.ActionCompleted,
4946
+ action,
4897
4947
  });
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,
4948
+ if (this.state === DraftQueueState.Started) {
4949
+ this.processNextAction();
4950
+ }
4906
4951
  });
4907
- if (this.state === DraftQueueState.Started) {
4908
- this.processNextAction();
4909
- }
4910
4952
  }
4911
4953
  async actionFailed(action, retry) {
4912
4954
  this.uploadingActionId = undefined;
@@ -5356,6 +5398,13 @@ class AbstractResourceRequestActionHandler {
5356
5398
  this.draftQueue = draftQueue;
5357
5399
  this.networkAdapter = networkAdapter;
5358
5400
  this.getLuvio = getLuvio;
5401
+ // NOTE[W-12567340]: This property stores in-memory mappings between draft
5402
+ // ids and canonical ids for the current session. Having a local copy of
5403
+ // these mappings is necessary to avoid a race condition between publishing
5404
+ // new mappings to the durable store and those mappings being loaded into
5405
+ // the luvio store redirect table, during which a new draft might be enqueued
5406
+ // which would not see a necessary mapping.
5407
+ this.ephemeralRedirects = {};
5359
5408
  }
5360
5409
  enqueue(data) {
5361
5410
  return this.draftQueue.enqueue(this.handlerId, data);
@@ -5423,7 +5472,8 @@ class AbstractResourceRequestActionHandler {
5423
5472
  }
5424
5473
  getQueueOperationsForCompletingDrafts(queue, action) {
5425
5474
  const queueOperations = [];
5426
- if (action.data.method === 'post') {
5475
+ const redirects = this.getRedirectMappings(action);
5476
+ if (redirects !== undefined) {
5427
5477
  const { length } = queue;
5428
5478
  for (let i = 0; i < length; i++) {
5429
5479
  const queueAction = queue[i];
@@ -5433,69 +5483,64 @@ class AbstractResourceRequestActionHandler {
5433
5483
  continue;
5434
5484
  }
5435
5485
  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
- }
5486
+ let queueOperationMutated = false;
5487
+ let updatedActionTag = undefined;
5488
+ let updatedActionTargetId = undefined;
5489
+ const { tag: queueActionTag, data: queueActionRequest, id: queueActionId, } = queueAction;
5490
+ let { basePath, body } = queueActionRequest;
5491
+ let stringifiedBody = stringify$4(body);
5492
+ // for each redirected ID/key we loop over the operation to see if it needs
5493
+ // to be updated
5494
+ for (const { draftId, draftKey, canonicalId, canonicalKey } of redirects) {
5495
+ if (basePath.search(draftId) >= 0 || stringifiedBody.search(draftId) >= 0) {
5496
+ basePath = basePath.replace(draftId, canonicalId);
5497
+ stringifiedBody = stringifiedBody.replace(draftId, canonicalId);
5498
+ queueOperationMutated = true;
5459
5499
  }
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
- }
5500
+ // if the action is performed on a previous draft id, we need to replace the action
5501
+ // with a new one at the updated canonical key
5502
+ if (queueActionTag === draftKey) {
5503
+ updatedActionTag = canonicalKey;
5504
+ updatedActionTargetId = canonicalId;
5505
+ }
5506
+ }
5507
+ if (queueOperationMutated) {
5508
+ if (updatedActionTag !== undefined && updatedActionTargetId !== undefined) {
5509
+ const updatedAction = {
5510
+ ...queueAction,
5511
+ tag: updatedActionTag,
5512
+ targetId: updatedActionTargetId,
5513
+ data: {
5514
+ ...queueActionRequest,
5515
+ basePath: basePath,
5516
+ body: parse$4(stringifiedBody),
5517
+ },
5518
+ };
5519
+ // item needs to be replaced with a new item at the new record key
5520
+ queueOperations.push({
5521
+ type: QueueOperationType.Delete,
5522
+ id: queueActionId,
5523
+ });
5524
+ queueOperations.push({
5525
+ type: QueueOperationType.Add,
5526
+ action: updatedAction,
5527
+ });
5528
+ }
5529
+ else {
5530
+ const updatedAction = {
5531
+ ...queueAction,
5532
+ data: {
5533
+ ...queueActionRequest,
5534
+ basePath: basePath,
5535
+ body: parse$4(stringifiedBody),
5536
+ },
5537
+ };
5538
+ // item needs to be updated
5539
+ queueOperations.push({
5540
+ type: QueueOperationType.Update,
5541
+ id: queueActionId,
5542
+ action: updatedAction,
5543
+ });
5499
5544
  }
5500
5545
  }
5501
5546
  }
@@ -5515,6 +5560,9 @@ class AbstractResourceRequestActionHandler {
5515
5560
  const body = action.response.body;
5516
5561
  const canonicalId = this.getIdFromResponseBody(body);
5517
5562
  const draftId = action.targetId;
5563
+ if (draftId !== undefined && canonicalId !== undefined && draftId !== canonicalId) {
5564
+ this.ephemeralRedirects[draftId] = canonicalId;
5565
+ }
5518
5566
  return [
5519
5567
  {
5520
5568
  draftId,
@@ -5809,7 +5857,9 @@ class DraftManager {
5809
5857
  targetId,
5810
5858
  tag,
5811
5859
  })
5812
- .then(this.buildDraftQueueItem);
5860
+ .then((result) => {
5861
+ return this.buildDraftQueueItem(result.action);
5862
+ });
5813
5863
  }
5814
5864
  /**
5815
5865
  * Get the current state of each of the DraftActions in the DraftQueue
@@ -11296,12 +11346,18 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
11296
11346
  const fieldName = fieldNames[i];
11297
11347
  const fieldValue = bodyFields[fieldName];
11298
11348
  if (typeof fieldValue === 'string' && this.isDraftId(fieldValue)) {
11299
- const draftKey = this.buildTagForTargetId(fieldValue);
11300
- const canonicalKey = this.getLuvio().storeGetCanonicalKey(draftKey);
11301
- if (draftKey !== canonicalKey) {
11302
- const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11303
- if (canonicalId !== undefined) {
11304
- bodyFields[fieldName] = canonicalId;
11349
+ const canonicalId = this.ephemeralRedirects[fieldValue];
11350
+ if (canonicalId !== undefined) {
11351
+ bodyFields[fieldName] = canonicalId;
11352
+ }
11353
+ else {
11354
+ const draftKey = this.buildTagForTargetId(fieldValue);
11355
+ const canonicalKey = this.getLuvio().storeGetCanonicalKey(draftKey);
11356
+ if (draftKey !== canonicalKey) {
11357
+ const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11358
+ if (canonicalId !== undefined) {
11359
+ bodyFields[fieldName] = canonicalId;
11360
+ }
11305
11361
  }
11306
11362
  }
11307
11363
  }
@@ -11309,17 +11365,24 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
11309
11365
  }
11310
11366
  if (request.method === 'patch' || request.method === 'delete') {
11311
11367
  const recordId = request.urlParams['recordId'];
11312
- const recordKey = this.buildTagForTargetId(recordId);
11313
- const canonicalKey = this.getLuvio().storeGetCanonicalKey(recordKey);
11314
- if (recordKey !== canonicalKey) {
11315
- const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11316
- if (canonicalId === undefined) {
11317
- // could not resolve id from request -- return the request un-modified
11318
- return request;
11319
- }
11368
+ const canonicalId = this.ephemeralRedirects[recordId];
11369
+ if (canonicalId !== undefined) {
11320
11370
  resolvedBasePath = resolvedBasePath.replace(recordId, canonicalId);
11321
11371
  resolvedUrlParams = { ...resolvedUrlParams, recordId: canonicalId };
11322
11372
  }
11373
+ else {
11374
+ const recordKey = this.buildTagForTargetId(recordId);
11375
+ const canonicalKey = this.getLuvio().storeGetCanonicalKey(recordKey);
11376
+ if (recordKey !== canonicalKey) {
11377
+ const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11378
+ if (canonicalId === undefined) {
11379
+ // could not resolve id from request -- return the request un-modified
11380
+ return request;
11381
+ }
11382
+ resolvedBasePath = resolvedBasePath.replace(recordId, canonicalId);
11383
+ resolvedUrlParams = { ...resolvedUrlParams, recordId: canonicalId };
11384
+ }
11385
+ }
11323
11386
  }
11324
11387
  return {
11325
11388
  ...request,
@@ -11655,14 +11718,13 @@ function performQuickActionDraftEnvironment(luvio, env, handler) {
11655
11718
  // only override requests to createRecord endpoint
11656
11719
  return env.dispatchResourceRequest(request, context, eventObservers);
11657
11720
  }
11658
- const action = await handler.enqueue(request).catch((err) => {
11721
+ const { data } = await handler.enqueue(request).catch((err) => {
11659
11722
  throw createDraftSynthesisErrorResponse(err.message);
11660
11723
  });
11661
- const record = await handler.getDataForAction(action);
11662
- if (record === undefined) {
11724
+ if (data === undefined) {
11663
11725
  return Promise.reject(createDraftSynthesisErrorResponse());
11664
11726
  }
11665
- return createOkResponse(record);
11727
+ return createOkResponse(data);
11666
11728
  };
11667
11729
  return create$2(env, {
11668
11730
  dispatchResourceRequest: { value: dispatchResourceRequest },
@@ -14667,34 +14729,6 @@ function setupInspection(luvio) {
14667
14729
  * For full license text, see the LICENSE.txt file
14668
14730
  */
14669
14731
 
14670
- class AsyncWorkerPool {
14671
- constructor(concurrency) {
14672
- this.queue = [];
14673
- this.activeWorkers = 0;
14674
- this.concurrency = concurrency;
14675
- }
14676
- push(workFn) {
14677
- this.queue.push(workFn);
14678
- this.work();
14679
- }
14680
- cancel() {
14681
- this.queue = [];
14682
- // TODO [W-12513105]: thread cancellation through to active workers
14683
- }
14684
- work() {
14685
- while (this.queue.length > 0 && this.activeWorkers < this.concurrency) {
14686
- this.activeWorkers += 1;
14687
- const next = this.queue.shift();
14688
- if (next) {
14689
- next().finally(() => {
14690
- this.activeWorkers -= 1;
14691
- this.work();
14692
- });
14693
- }
14694
- }
14695
- }
14696
- }
14697
-
14698
14732
  class EventEmitter {
14699
14733
  constructor() {
14700
14734
  // @ts-ignore typescript doesn't like us setting this to an empty object for some reason
@@ -15247,4 +15281,4 @@ register({
15247
15281
  });
15248
15282
 
15249
15283
  export { getRuntime, registerReportObserver, reportGraphqlQueryParseError };
15250
- // version: 1.100.3-8176c84f6
15284
+ // version: 1.100.5-bd2d67d6a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/lds-runtime-mobile",
3
- "version": "1.100.3",
3
+ "version": "1.100.5",
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.3",
36
- "@salesforce/lds-bindings": "^1.100.3",
37
- "@salesforce/lds-instrumentation": "^1.100.3",
38
- "@salesforce/lds-priming": "^1.100.3",
35
+ "@salesforce/lds-adapters-uiapi": "^1.100.5",
36
+ "@salesforce/lds-bindings": "^1.100.5",
37
+ "@salesforce/lds-instrumentation": "^1.100.5",
38
+ "@salesforce/lds-priming": "^1.100.5",
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.3",
47
- "@salesforce/lds-drafts": "^1.100.3",
48
- "@salesforce/lds-drafts-adapters-uiapi": "^1.100.3",
49
- "@salesforce/lds-graphql-eval": "^1.100.3",
50
- "@salesforce/lds-network-adapter": "^1.100.3",
51
- "@salesforce/lds-network-nimbus": "^1.100.3",
52
- "@salesforce/lds-store-binary": "^1.100.3",
53
- "@salesforce/lds-store-sql": "^1.100.3",
54
- "@salesforce/lds-utils-adapters": "^1.100.3",
55
- "@salesforce/nimbus-plugin-lds": "^1.100.3",
46
+ "@salesforce/lds-adapters-graphql": "^1.100.5",
47
+ "@salesforce/lds-drafts": "^1.100.5",
48
+ "@salesforce/lds-drafts-adapters-uiapi": "^1.100.5",
49
+ "@salesforce/lds-graphql-eval": "^1.100.5",
50
+ "@salesforce/lds-network-adapter": "^1.100.5",
51
+ "@salesforce/lds-network-nimbus": "^1.100.5",
52
+ "@salesforce/lds-store-binary": "^1.100.5",
53
+ "@salesforce/lds-store-sql": "^1.100.5",
54
+ "@salesforce/lds-utils-adapters": "^1.100.5",
55
+ "@salesforce/nimbus-plugin-lds": "^1.100.5",
56
56
  "babel-plugin-dynamic-import-node": "^2.3.3",
57
57
  "wait-for-expect": "^3.0.2"
58
58
  },
@@ -7,7 +7,10 @@ import type { DraftAction, DraftQueue, ProcessActionResult, DraftQueueState, Dra
7
7
  * to a concrete implementation running in jscore.
8
8
  */
9
9
  export declare class NimbusDraftQueue implements DraftQueue {
10
- enqueue<Response>(handlerId: string, data: unknown): Promise<PendingDraftAction<Response>>;
10
+ enqueue<Data, Response>(handlerId: string, data: unknown): Promise<{
11
+ action: PendingDraftAction<Data>;
12
+ data: Response;
13
+ }>;
11
14
  registerOnChangedListener(_listener: DraftQueueChangeListener): () => Promise<void>;
12
15
  processNextAction(): Promise<ProcessActionResult>;
13
16
  getQueueActions(): Promise<DraftAction<unknown, unknown>[]>;
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.
@@ -4528,14 +4566,13 @@ const { isArray: isArray$3 } = Array;
4528
4566
  function buildLuvioOverrideForDraftAdapters(luvio, handler, extractTargetIdFromCacheKey, options = {}) {
4529
4567
  // override this to create and enqueue a new draft action, and return synthetic response
4530
4568
  const dispatchResourceRequest = async function (resourceRequest, _context) {
4531
- const action = await handler.enqueue(resourceRequest).catch((err) => {
4569
+ const { data } = await handler.enqueue(resourceRequest).catch((err) => {
4532
4570
  throw transformErrorToDraftSynthesisError(err);
4533
4571
  });
4534
- const record = await handler.getDataForAction(action);
4535
- if (record === undefined) {
4572
+ if (data === undefined) {
4536
4573
  return Promise.reject(createDraftSynthesisErrorResponse());
4537
4574
  }
4538
- return createOkResponse(record);
4575
+ return createOkResponse(data);
4539
4576
  };
4540
4577
  // override this to use an infinitely large ttl so the cache entry never expires
4541
4578
  const publishStoreMetadata = function (key, storeMetadataParams) {
@@ -4762,6 +4799,7 @@ class DurableDraftQueue {
4762
4799
  this.timeoutHandler = undefined;
4763
4800
  this.handlers = {};
4764
4801
  this.draftStore = draftStore;
4802
+ this.workerPool = new AsyncWorkerPool(1);
4765
4803
  }
4766
4804
  addHandler(handler) {
4767
4805
  const id = handler.handlerId;
@@ -4779,10 +4817,6 @@ class DurableDraftQueue {
4779
4817
  const handler = customActionHandler(executor, id, this);
4780
4818
  return this.addHandler(handler);
4781
4819
  }
4782
- async getDataForAction(action) {
4783
- const handler = this.getHandler(action.handler);
4784
- return handler.getDataForAction(action);
4785
- }
4786
4820
  getQueueState() {
4787
4821
  return this.state;
4788
4822
  }
@@ -4861,20 +4895,26 @@ class DurableDraftQueue {
4861
4895
  });
4862
4896
  }
4863
4897
  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,
4898
+ return this.workerPool.push(async () => {
4899
+ let queue = await this.getQueueActions();
4900
+ const handler = this.getHandler(handlerId);
4901
+ const pendingAction = (await handler.buildPendingAction(data, queue));
4902
+ await this.draftStore.writeAction(pendingAction);
4903
+ queue = await this.getQueueActions();
4904
+ await this.notifyChangedListeners({
4905
+ type: DraftQueueEventType.ActionAdded,
4906
+ action: pendingAction,
4907
+ });
4908
+ await handler.handleActionEnqueued(pendingAction, queue);
4909
+ if (this.state === DraftQueueState.Started) {
4910
+ this.processNextAction();
4911
+ }
4912
+ const actionData = (await handler.getDataForAction(pendingAction));
4913
+ return {
4914
+ action: pendingAction,
4915
+ data: actionData,
4916
+ };
4872
4917
  });
4873
- await handler.handleActionEnqueued(pendingAction, queue);
4874
- if (this.state === DraftQueueState.Started) {
4875
- this.processNextAction();
4876
- }
4877
- return pendingAction;
4878
4918
  }
4879
4919
  registerOnChangedListener(listener) {
4880
4920
  this.draftQueueChangedListeners.push(listener);
@@ -4886,27 +4926,29 @@ class DurableDraftQueue {
4886
4926
  };
4887
4927
  }
4888
4928
  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 };
4929
+ return this.workerPool.push(async () => {
4930
+ const handler = this.getHandler(action.handler);
4931
+ let queue = await this.getQueueActions();
4932
+ const queueOperations = handler.getQueueOperationsForCompletingDrafts(queue, action);
4933
+ const idAndKeyMappings = handler.getRedirectMappings(action);
4934
+ const keyMappings = idAndKeyMappings === undefined
4935
+ ? undefined
4936
+ : idAndKeyMappings.map((m) => {
4937
+ return { draftKey: m.draftKey, canonicalKey: m.canonicalKey };
4938
+ });
4939
+ await this.draftStore.completeAction(queueOperations, keyMappings);
4940
+ queue = await this.getQueueActions();
4941
+ this.retryIntervalMilliseconds = 0;
4942
+ this.uploadingActionId = undefined;
4943
+ await handler.handleActionCompleted(action, queueOperations, queue, values$1(this.handlers));
4944
+ await this.notifyChangedListeners({
4945
+ type: DraftQueueEventType.ActionCompleted,
4946
+ action,
4897
4947
  });
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,
4948
+ if (this.state === DraftQueueState.Started) {
4949
+ this.processNextAction();
4950
+ }
4906
4951
  });
4907
- if (this.state === DraftQueueState.Started) {
4908
- this.processNextAction();
4909
- }
4910
4952
  }
4911
4953
  async actionFailed(action, retry) {
4912
4954
  this.uploadingActionId = undefined;
@@ -5356,6 +5398,13 @@ class AbstractResourceRequestActionHandler {
5356
5398
  this.draftQueue = draftQueue;
5357
5399
  this.networkAdapter = networkAdapter;
5358
5400
  this.getLuvio = getLuvio;
5401
+ // NOTE[W-12567340]: This property stores in-memory mappings between draft
5402
+ // ids and canonical ids for the current session. Having a local copy of
5403
+ // these mappings is necessary to avoid a race condition between publishing
5404
+ // new mappings to the durable store and those mappings being loaded into
5405
+ // the luvio store redirect table, during which a new draft might be enqueued
5406
+ // which would not see a necessary mapping.
5407
+ this.ephemeralRedirects = {};
5359
5408
  }
5360
5409
  enqueue(data) {
5361
5410
  return this.draftQueue.enqueue(this.handlerId, data);
@@ -5423,7 +5472,8 @@ class AbstractResourceRequestActionHandler {
5423
5472
  }
5424
5473
  getQueueOperationsForCompletingDrafts(queue, action) {
5425
5474
  const queueOperations = [];
5426
- if (action.data.method === 'post') {
5475
+ const redirects = this.getRedirectMappings(action);
5476
+ if (redirects !== undefined) {
5427
5477
  const { length } = queue;
5428
5478
  for (let i = 0; i < length; i++) {
5429
5479
  const queueAction = queue[i];
@@ -5433,69 +5483,64 @@ class AbstractResourceRequestActionHandler {
5433
5483
  continue;
5434
5484
  }
5435
5485
  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
- }
5486
+ let queueOperationMutated = false;
5487
+ let updatedActionTag = undefined;
5488
+ let updatedActionTargetId = undefined;
5489
+ const { tag: queueActionTag, data: queueActionRequest, id: queueActionId, } = queueAction;
5490
+ let { basePath, body } = queueActionRequest;
5491
+ let stringifiedBody = stringify$4(body);
5492
+ // for each redirected ID/key we loop over the operation to see if it needs
5493
+ // to be updated
5494
+ for (const { draftId, draftKey, canonicalId, canonicalKey } of redirects) {
5495
+ if (basePath.search(draftId) >= 0 || stringifiedBody.search(draftId) >= 0) {
5496
+ basePath = basePath.replace(draftId, canonicalId);
5497
+ stringifiedBody = stringifiedBody.replace(draftId, canonicalId);
5498
+ queueOperationMutated = true;
5459
5499
  }
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
- }
5500
+ // if the action is performed on a previous draft id, we need to replace the action
5501
+ // with a new one at the updated canonical key
5502
+ if (queueActionTag === draftKey) {
5503
+ updatedActionTag = canonicalKey;
5504
+ updatedActionTargetId = canonicalId;
5505
+ }
5506
+ }
5507
+ if (queueOperationMutated) {
5508
+ if (updatedActionTag !== undefined && updatedActionTargetId !== undefined) {
5509
+ const updatedAction = {
5510
+ ...queueAction,
5511
+ tag: updatedActionTag,
5512
+ targetId: updatedActionTargetId,
5513
+ data: {
5514
+ ...queueActionRequest,
5515
+ basePath: basePath,
5516
+ body: parse$4(stringifiedBody),
5517
+ },
5518
+ };
5519
+ // item needs to be replaced with a new item at the new record key
5520
+ queueOperations.push({
5521
+ type: QueueOperationType.Delete,
5522
+ id: queueActionId,
5523
+ });
5524
+ queueOperations.push({
5525
+ type: QueueOperationType.Add,
5526
+ action: updatedAction,
5527
+ });
5528
+ }
5529
+ else {
5530
+ const updatedAction = {
5531
+ ...queueAction,
5532
+ data: {
5533
+ ...queueActionRequest,
5534
+ basePath: basePath,
5535
+ body: parse$4(stringifiedBody),
5536
+ },
5537
+ };
5538
+ // item needs to be updated
5539
+ queueOperations.push({
5540
+ type: QueueOperationType.Update,
5541
+ id: queueActionId,
5542
+ action: updatedAction,
5543
+ });
5499
5544
  }
5500
5545
  }
5501
5546
  }
@@ -5515,6 +5560,9 @@ class AbstractResourceRequestActionHandler {
5515
5560
  const body = action.response.body;
5516
5561
  const canonicalId = this.getIdFromResponseBody(body);
5517
5562
  const draftId = action.targetId;
5563
+ if (draftId !== undefined && canonicalId !== undefined && draftId !== canonicalId) {
5564
+ this.ephemeralRedirects[draftId] = canonicalId;
5565
+ }
5518
5566
  return [
5519
5567
  {
5520
5568
  draftId,
@@ -5809,7 +5857,9 @@ class DraftManager {
5809
5857
  targetId,
5810
5858
  tag,
5811
5859
  })
5812
- .then(this.buildDraftQueueItem);
5860
+ .then((result) => {
5861
+ return this.buildDraftQueueItem(result.action);
5862
+ });
5813
5863
  }
5814
5864
  /**
5815
5865
  * Get the current state of each of the DraftActions in the DraftQueue
@@ -11296,12 +11346,18 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
11296
11346
  const fieldName = fieldNames[i];
11297
11347
  const fieldValue = bodyFields[fieldName];
11298
11348
  if (typeof fieldValue === 'string' && this.isDraftId(fieldValue)) {
11299
- const draftKey = this.buildTagForTargetId(fieldValue);
11300
- const canonicalKey = this.getLuvio().storeGetCanonicalKey(draftKey);
11301
- if (draftKey !== canonicalKey) {
11302
- const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11303
- if (canonicalId !== undefined) {
11304
- bodyFields[fieldName] = canonicalId;
11349
+ const canonicalId = this.ephemeralRedirects[fieldValue];
11350
+ if (canonicalId !== undefined) {
11351
+ bodyFields[fieldName] = canonicalId;
11352
+ }
11353
+ else {
11354
+ const draftKey = this.buildTagForTargetId(fieldValue);
11355
+ const canonicalKey = this.getLuvio().storeGetCanonicalKey(draftKey);
11356
+ if (draftKey !== canonicalKey) {
11357
+ const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11358
+ if (canonicalId !== undefined) {
11359
+ bodyFields[fieldName] = canonicalId;
11360
+ }
11305
11361
  }
11306
11362
  }
11307
11363
  }
@@ -11309,17 +11365,24 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
11309
11365
  }
11310
11366
  if (request.method === 'patch' || request.method === 'delete') {
11311
11367
  const recordId = request.urlParams['recordId'];
11312
- const recordKey = this.buildTagForTargetId(recordId);
11313
- const canonicalKey = this.getLuvio().storeGetCanonicalKey(recordKey);
11314
- if (recordKey !== canonicalKey) {
11315
- const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11316
- if (canonicalId === undefined) {
11317
- // could not resolve id from request -- return the request un-modified
11318
- return request;
11319
- }
11368
+ const canonicalId = this.ephemeralRedirects[recordId];
11369
+ if (canonicalId !== undefined) {
11320
11370
  resolvedBasePath = resolvedBasePath.replace(recordId, canonicalId);
11321
11371
  resolvedUrlParams = { ...resolvedUrlParams, recordId: canonicalId };
11322
11372
  }
11373
+ else {
11374
+ const recordKey = this.buildTagForTargetId(recordId);
11375
+ const canonicalKey = this.getLuvio().storeGetCanonicalKey(recordKey);
11376
+ if (recordKey !== canonicalKey) {
11377
+ const canonicalId = extractRecordIdFromStoreKey(canonicalKey);
11378
+ if (canonicalId === undefined) {
11379
+ // could not resolve id from request -- return the request un-modified
11380
+ return request;
11381
+ }
11382
+ resolvedBasePath = resolvedBasePath.replace(recordId, canonicalId);
11383
+ resolvedUrlParams = { ...resolvedUrlParams, recordId: canonicalId };
11384
+ }
11385
+ }
11323
11386
  }
11324
11387
  return {
11325
11388
  ...request,
@@ -11655,14 +11718,13 @@ function performQuickActionDraftEnvironment(luvio, env, handler) {
11655
11718
  // only override requests to createRecord endpoint
11656
11719
  return env.dispatchResourceRequest(request, context, eventObservers);
11657
11720
  }
11658
- const action = await handler.enqueue(request).catch((err) => {
11721
+ const { data } = await handler.enqueue(request).catch((err) => {
11659
11722
  throw createDraftSynthesisErrorResponse(err.message);
11660
11723
  });
11661
- const record = await handler.getDataForAction(action);
11662
- if (record === undefined) {
11724
+ if (data === undefined) {
11663
11725
  return Promise.reject(createDraftSynthesisErrorResponse());
11664
11726
  }
11665
- return createOkResponse(record);
11727
+ return createOkResponse(data);
11666
11728
  };
11667
11729
  return create$2(env, {
11668
11730
  dispatchResourceRequest: { value: dispatchResourceRequest },
@@ -14667,34 +14729,6 @@ function setupInspection(luvio) {
14667
14729
  * For full license text, see the LICENSE.txt file
14668
14730
  */
14669
14731
 
14670
- class AsyncWorkerPool {
14671
- constructor(concurrency) {
14672
- this.queue = [];
14673
- this.activeWorkers = 0;
14674
- this.concurrency = concurrency;
14675
- }
14676
- push(workFn) {
14677
- this.queue.push(workFn);
14678
- this.work();
14679
- }
14680
- cancel() {
14681
- this.queue = [];
14682
- // TODO [W-12513105]: thread cancellation through to active workers
14683
- }
14684
- work() {
14685
- while (this.queue.length > 0 && this.activeWorkers < this.concurrency) {
14686
- this.activeWorkers += 1;
14687
- const next = this.queue.shift();
14688
- if (next) {
14689
- next().finally(() => {
14690
- this.activeWorkers -= 1;
14691
- this.work();
14692
- });
14693
- }
14694
- }
14695
- }
14696
- }
14697
-
14698
14732
  class EventEmitter {
14699
14733
  constructor() {
14700
14734
  // @ts-ignore typescript doesn't like us setting this to an empty object for some reason
@@ -15247,4 +15281,4 @@ register({
15247
15281
  });
15248
15282
 
15249
15283
  export { getRuntime, registerReportObserver, reportGraphqlQueryParseError };
15250
- // version: 1.100.3-8176c84f6
15284
+ // version: 1.100.5-bd2d67d6a