@salesforce/lds-worker-api 1.461.0 → 1.463.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4282,7 +4282,7 @@
4282
4282
  }
4283
4283
  callbacks.push(callback);
4284
4284
  }
4285
- // version: 1.461.0-37b1c9d1cb
4285
+ // version: 1.463.0-ea9ecd17fc
4286
4286
 
4287
4287
  // TODO [TD-0081508]: once that TD is fulfilled we can probably change this file
4288
4288
  function instrumentAdapter$1(createFunction, _metadata) {
@@ -5326,7 +5326,7 @@
5326
5326
  const { apiFamily, name } = metadata;
5327
5327
  return createGraphQLWireAdapterConstructor$1(adapter, `${apiFamily}.${name}`, luvio, astResolver);
5328
5328
  }
5329
- // version: 1.461.0-37b1c9d1cb
5329
+ // version: 1.463.0-ea9ecd17fc
5330
5330
 
5331
5331
  function isSupportedEntity(_objectApiName) {
5332
5332
  return true;
@@ -32553,7 +32553,7 @@
32553
32553
  throttle(60, 60000, setupNotifyAllListRecordUpdateAvailable(luvio));
32554
32554
  throttle(60, 60000, setupNotifyAllListInfoSummaryUpdateAvailable(luvio));
32555
32555
  });
32556
- // version: 1.461.0-55ae54e753
32556
+ // version: 1.463.0-8515018faf
32557
32557
 
32558
32558
  var draftQueueMaxRetryAttemptsGate = {
32559
32559
  isOpen: function (e) {
@@ -44185,24 +44185,6 @@
44185
44185
  return pattern;
44186
44186
  }
44187
44187
 
44188
- var ldsPrimingGraphqlBatch = {
44189
- isOpen: function (e) {
44190
- return e.fallback;
44191
- },
44192
- hasError: function () {
44193
- return !0;
44194
- },
44195
- };
44196
-
44197
- var lmrPrimingUseSoql = {
44198
- isOpen: function (e) {
44199
- return e.fallback;
44200
- },
44201
- hasError: function () {
44202
- return !0;
44203
- },
44204
- };
44205
-
44206
44188
  var aggressiveTrimCountKillSwitchGate = {
44207
44189
  isOpen: function (e) {
44208
44190
  return e.fallback;
@@ -44221,15 +44203,6 @@
44221
44203
  },
44222
44204
  };
44223
44205
 
44224
- var useOneStore = {
44225
- isOpen: function (e) {
44226
- return e.fallback;
44227
- },
44228
- hasError: function () {
44229
- return !0;
44230
- },
44231
- };
44232
-
44233
44206
  const { create: create$7, freeze: freeze$4, keys: keys$8, entries: entries$5 } = Object;
44234
44207
  const { hasOwnProperty: hasOwnProperty$3 } = Object.prototype;
44235
44208
  const { isArray: isArray$7 } = Array;
@@ -44774,9 +44747,18 @@
44774
44747
  if (data === null || typeof data !== "object" || Array.isArray(data)) {
44775
44748
  return invalidSchemaResponseWithError$1(incorrectTypeError$1("object", typeof data, path));
44776
44749
  }
44777
- const { propertyName, mapping } = discriminator;
44750
+ const { propertyName, mapping, defaultMapping } = discriminator;
44778
44751
  const discriminatorValue = data[propertyName];
44779
44752
  if (discriminatorValue === void 0) {
44753
+ if (defaultMapping !== void 0) {
44754
+ return validateJsonSchemaInternal$1(
44755
+ data,
44756
+ { $ref: defaultMapping },
44757
+ path,
44758
+ document,
44759
+ visited
44760
+ );
44761
+ }
44780
44762
  return invalidSchemaResponseWithError$1(
44781
44763
  new MissingRequiredPropertyError$1(
44782
44764
  `Object at path '${path}' is missing required discriminator property '${propertyName}'.`
@@ -44788,7 +44770,7 @@
44788
44770
  incorrectTypeError$1("string", typeof discriminatorValue, `${path}.${propertyName}`)
44789
44771
  );
44790
44772
  }
44791
- const targetRef = mapping[discriminatorValue];
44773
+ const targetRef = mapping[discriminatorValue] ?? defaultMapping;
44792
44774
  if (targetRef === void 0) {
44793
44775
  return invalidSchemaResponseWithError$1(
44794
44776
  new JsonSchemaViolationError$1(
@@ -45949,7 +45931,7 @@
45949
45931
  },
45950
45932
  };
45951
45933
 
45952
- var reviveOnlyRequestedFields = {
45934
+ var isProduct2SerializedSideEffectKillSwitch = {
45953
45935
  isOpen: function (e) {
45954
45936
  return e.fallback;
45955
45937
  },
@@ -81470,6 +81452,35 @@
81470
81452
  return snapshot.data;
81471
81453
  }
81472
81454
 
81455
+ /**
81456
+ * A read-through cache over `DurableRecordStore.getRecord` scoped to a single draft-action
81457
+ * dispatch. Multiple side-effect hooks fire for one action (e.g. the van-stock and
81458
+ * IsProduct2Serialized ProductConsumed hooks) and are awaited one at a time, so they otherwise
81459
+ * each re-read the same draft record — and the IsProduct2Serialized chain re-reads the same
81460
+ * related ProductItem / Product2 — from the durable store. Sharing one reader across the
81461
+ * action's hooks collapses those repeated reads to a single durable call per key.
81462
+ *
81463
+ * The cache stores the in-flight promise (not the resolved value) so concurrent reads of the
81464
+ * same key share one durable call, and it caches `undefined` results so a miss is not re-fetched.
81465
+ * A reader must not outlive one action dispatch: durable state changes between actions, so a
81466
+ * longer-lived cache would return stale records.
81467
+ */
81468
+ class MemoizingRecordReader {
81469
+ constructor(durableRecordStore) {
81470
+ this.durableRecordStore = durableRecordStore;
81471
+ this.cache = new Map();
81472
+ }
81473
+ getRecord(key) {
81474
+ const cached = this.cache.get(key);
81475
+ if (cached !== undefined) {
81476
+ return cached;
81477
+ }
81478
+ const promise = this.durableRecordStore.getRecord(key);
81479
+ this.cache.set(key, promise);
81480
+ return promise;
81481
+ }
81482
+ }
81483
+
81473
81484
  const LDS_ACTION_HANDLER_ID = 'LDS_ACTION_HANDLER';
81474
81485
  const LDS_ACTION_METADATA_API_NAME = 'LDS_ACTION_METADATA_API_NAME';
81475
81486
  class UiApiActionHandler extends AbstractResourceRequestActionHandler {
@@ -81590,9 +81601,9 @@
81590
81601
  return super.getRedirectMappings(action);
81591
81602
  }
81592
81603
  async handleActionCompleted(completedAction, queueOperations) {
81593
- if (completedAction.response.status === 204 && completedAction.data.method === 'post') {
81594
- // if we get a 204 it means the record creation was successful but we don't have the record to ingest
81595
- // remove the synthesized draft and do not try to ingest the response
81604
+ const { method } = completedAction.data;
81605
+ if (completedAction.response.status === 204 && (method === 'post' || method === 'patch')) {
81606
+ await this.recordService.removeSideEffectsForAction(completedAction);
81596
81607
  await this.evictKey(completedAction.tag);
81597
81608
  return;
81598
81609
  }
@@ -81673,8 +81684,15 @@
81673
81684
  break;
81674
81685
  }
81675
81686
  }
81687
+ // One reader shared across every hook for this action so repeated reads of the same
81688
+ // record (the draft record, and any related records the hooks resolve) collapse to a
81689
+ // single durable call. Scoped to this dispatch only — durable state changes between
81690
+ // actions, so it must not be reused across them. Built only when hooks will run.
81691
+ const recordReader = hookTriggers.length > 0
81692
+ ? new MemoizingRecordReader(this.durableRecordStore)
81693
+ : undefined;
81676
81694
  for (const trigger of hookTriggers) {
81677
- const hookEffects = await trigger.then(action.id, action.timestamp, action.targetId, action.data.body?.fields ?? {});
81695
+ const hookEffects = await trigger.then(action.id, action.timestamp, action.targetId, action.data.body?.fields ?? {}, recordReader);
81678
81696
  for (const hookEffect of hookEffects) {
81679
81697
  effects.push(hookEffect);
81680
81698
  }
@@ -92746,211 +92764,6 @@
92746
92764
  }
92747
92765
  }
92748
92766
 
92749
- const apiPath$1 = basePath + '/graphql';
92750
- const requiredPrefix = `required_`;
92751
- const requiredFieldMap = {
92752
- ApiName: 'ApiName',
92753
- Id: 'Id',
92754
- LastModifiedById: 'LastModifiedById { value }',
92755
- LastModifiedDate: 'LastModifiedDate { value }',
92756
- RecordTypeId: 'RecordTypeId(fallback: true) { value }',
92757
- SystemModstamp: 'SystemModstamp { value }',
92758
- WeakEtag: 'WeakEtag',
92759
- };
92760
- class RecordLoaderGraphQL extends NetworkRecordLoader {
92761
- makeRequest(batch, observabilityContext) {
92762
- const query = this.generateGraphQLQuery(batch.type, batch.fields);
92763
- return {
92764
- request: {
92765
- method: 'POST',
92766
- path: apiPath$1,
92767
- body: JSON.stringify({
92768
- query,
92769
- variables: {
92770
- ids: batch.ids,
92771
- first: batch.ids.length,
92772
- },
92773
- }),
92774
- headers: {},
92775
- queryParams: {},
92776
- priority: 'background',
92777
- observabilityContext: observabilityContext !== undefined ? observabilityContext : {},
92778
- },
92779
- context: undefined,
92780
- };
92781
- }
92782
- generateFetchResults(repResult, batchInput, _context) {
92783
- const { data, errors } = repResult;
92784
- if (errors !== undefined && errors.length > 0) {
92785
- // right now if there are any errors in the batch we throw out the entire batch
92786
- // for now this is ok all errors will originate on the same node so there shouldn't be a mix of errors and data
92787
- return [
92788
- {
92789
- missingIds: batchInput.ids,
92790
- ok: false,
92791
- error: 'request-error',
92792
- messages: errors.map((x) => x.message),
92793
- },
92794
- ];
92795
- }
92796
- // this shouldn't really happen if there's no errors
92797
- if (!data || !data.uiapi) {
92798
- return [
92799
- {
92800
- ok: false,
92801
- error: 'unknown',
92802
- messages: ['unexpected response retrieved from graphql endpoint'],
92803
- missingIds: batchInput.ids,
92804
- },
92805
- ];
92806
- }
92807
- const objectInfo = batchInput.objectInfo;
92808
- const seenRecords = new Set(batchInput.ids);
92809
- const records = data.uiapi.query[batchInput.type].edges.map((edge) => {
92810
- const record = this.generateDurableRecordRepresentation(edge.node, objectInfo);
92811
- seenRecords.delete(record.id);
92812
- return record;
92813
- });
92814
- return [
92815
- {
92816
- ok: true,
92817
- records,
92818
- missingIds: Array.from(seenRecords),
92819
- },
92820
- ];
92821
- }
92822
- generateGraphQLQuery(type, fields) {
92823
- const fieldList = Object.keys(requiredFieldMap)
92824
- .map((field) => {
92825
- return `${requiredPrefix}${field}: ${requiredFieldMap[field]}`;
92826
- })
92827
- .join('\n');
92828
- return /* GraphQL */ `
92829
- fragment requiredRecordFields on Record {
92830
- ${fieldList}
92831
- }
92832
- query prime${type}($ids: [ID], $first: Int){
92833
- uiapi {
92834
- query {
92835
- ${type} (where: {Id: {in: $ids}} first: $first) {
92836
- edges {
92837
- node {
92838
- ...requiredRecordFields
92839
- ${fields
92840
- .filter((x) => x !== 'Id' && x !== 'RecordTypeId')
92841
- .map((field) => `${field} { value displayValue }`)}
92842
- }
92843
- }
92844
- }
92845
- }
92846
- }
92847
- }
92848
- `;
92849
- }
92850
- generateDurableRecordRepresentation(node, objectInfo) {
92851
- const standardRecordFields = new Set(Object.keys(requiredFieldMap).map((x) => `${requiredPrefix}${x}`));
92852
- const id = node[`${requiredPrefix}Id`];
92853
- const recordTypeId = node[`${requiredPrefix}RecordTypeId`].value;
92854
- const requestedFields = Object.keys(node).filter((field) => !standardRecordFields.has(field));
92855
- const fields = requestedFields.reduce((acc, field) => {
92856
- const fieldNode = node[field];
92857
- if (fieldNode.displayValue === undefined) {
92858
- fieldNode.displayValue = null;
92859
- }
92860
- acc[field] = node[field];
92861
- return acc;
92862
- }, {});
92863
- fields['Id'] = { value: id, displayValue: null };
92864
- if (objectInfo.fields['RecordTypeId'] !== undefined) {
92865
- fields['RecordTypeId'] = { value: recordTypeId, displayValue: null };
92866
- }
92867
- let recordTypeInfo = null;
92868
- if (recordTypeId !== null &&
92869
- objectInfo.recordTypeInfos &&
92870
- objectInfo.recordTypeInfos[recordTypeId]) {
92871
- const appliedRecordTypeInfo = objectInfo.recordTypeInfos[recordTypeId];
92872
- // ui-api only applies the record type if its not the default type, otherwise it sets it to null
92873
- if (appliedRecordTypeInfo.defaultRecordTypeMapping === false) {
92874
- recordTypeInfo = appliedRecordTypeInfo;
92875
- }
92876
- }
92877
- return {
92878
- apiName: node[`${requiredPrefix}ApiName`],
92879
- childRelationships: {},
92880
- eTag: '',
92881
- id,
92882
- lastModifiedById: node[`${requiredPrefix}LastModifiedById`] &&
92883
- node[`${requiredPrefix}LastModifiedById`].value
92884
- ? node[`${requiredPrefix}LastModifiedById`].value
92885
- : null,
92886
- lastModifiedDate: node[`${requiredPrefix}LastModifiedDate`] &&
92887
- node[`${requiredPrefix}LastModifiedDate`].value
92888
- ? node[`${requiredPrefix}LastModifiedDate`].value
92889
- : null,
92890
- recordTypeId,
92891
- recordTypeInfo: recordTypeInfo,
92892
- systemModstamp: node[`${requiredPrefix}SystemModstamp`] &&
92893
- node[`${requiredPrefix}SystemModstamp`].value
92894
- ? node[`${requiredPrefix}SystemModstamp`].value
92895
- : null,
92896
- weakEtag: node[`${requiredPrefix}WeakEtag`],
92897
- fields,
92898
- links: {},
92899
- };
92900
- }
92901
- }
92902
-
92903
- const DEFAULT_GQL_QUERY_BATCH_SIZE = 100;
92904
- const apiPath = basePath + '/graphql/batch';
92905
- class RecordLoaderGraphQLBatch extends RecordLoaderGraphQL {
92906
- makeRequest(batch, observabilityContext) {
92907
- const query = this.generateGraphQLQuery(batch.type, batch.fields);
92908
- const chunks = chunk(batch.ids, DEFAULT_GQL_QUERY_BATCH_SIZE);
92909
- const batches = chunks.map((chunk) => {
92910
- return {
92911
- query,
92912
- variables: {
92913
- ids: chunk,
92914
- first: chunk.length,
92915
- },
92916
- };
92917
- });
92918
- return {
92919
- request: {
92920
- method: 'POST',
92921
- path: apiPath,
92922
- body: JSON.stringify({
92923
- batchQuery: batches,
92924
- }),
92925
- headers: {},
92926
- queryParams: {},
92927
- priority: 'background',
92928
- observabilityContext: observabilityContext !== undefined ? observabilityContext : {},
92929
- },
92930
- context: chunks,
92931
- };
92932
- }
92933
- generateFetchResults(body, batchInput, context) {
92934
- const { results } = body;
92935
- const fetchResults = context.flatMap((chunk, index) => {
92936
- const result = results[index];
92937
- if (!result || result.statusCode !== 200) {
92938
- return {
92939
- ok: false,
92940
- error: 'request-error',
92941
- messages: ['unexpected response from graphql batch endpoint'],
92942
- missingIds: chunk,
92943
- };
92944
- }
92945
- return super.generateFetchResults(result.result, {
92946
- ...batchInput,
92947
- ids: chunk,
92948
- }, undefined);
92949
- });
92950
- return fetchResults;
92951
- }
92952
- }
92953
-
92954
92767
  class SoqlRecordConverter {
92955
92768
  /**
92956
92769
  *
@@ -93443,16 +93256,7 @@
93443
93256
  function primingSessionFactory(config) {
93444
93257
  const { store, objectInfoService, getLuvio } = config;
93445
93258
  const networkAdapter = new NimbusPrimingNetworkAdapter();
93446
- let recordLoader;
93447
- if (lmrPrimingUseSoql.isOpen({ fallback: false })) {
93448
- recordLoader = new RecordLoaderSOQLComposite(networkAdapter);
93449
- }
93450
- else if (ldsPrimingGraphqlBatch.isOpen({ fallback: false })) {
93451
- recordLoader = new RecordLoaderGraphQLBatch(networkAdapter);
93452
- }
93453
- else {
93454
- recordLoader = new RecordLoaderGraphQL(networkAdapter);
93455
- }
93259
+ const recordLoader = new RecordLoaderSOQLComposite(networkAdapter);
93456
93260
  const primingStore = new SqlitePrimingStore(getLuvio, store);
93457
93261
  const recordIngestor = new RecordIngestor(primingStore, getLuvio);
93458
93262
  const session = new PrimingSession({
@@ -94105,6 +93909,38 @@
94105
93909
  }
94106
93910
 
94107
93911
  const SIDE_EFFECT_SEGMENT = 'RECORD_SIDE_EFFECTS';
93912
+ // Base-record effects rebuild (or remove) the entire record for a key, so they must be
93913
+ // applied before any field-level effect on that same key — otherwise a field effect that
93914
+ // runs first is discarded when the base record is (re)built. keyToEffectMap ordering is not
93915
+ // stable: it is re-materialized from the durable store on reload / onChanged replay, where
93916
+ // entry order is not guaranteed. So two field-set effects on the same key from different
93917
+ // actions (e.g. two queued edits of one field) can come back in either order; applied out of
93918
+ // order the OLDER value wins and the newer edit is lost. Effects must therefore be applied in
93919
+ // action chronology (by timestamp), with base-record ops ordered ahead of field ops only as
93920
+ // the tie-breaker within one action — create/field effects on a draft-created record share a
93921
+ // key AND a timestamp, so timestamp alone can't order those.
93922
+ const BASE_RECORD_EFFECT_TYPES = new Set(['record-create', 'record-update', 'record-delete']);
93923
+ function orderEffectsForApply(effects) {
93924
+ // Nothing to reorder for a single effect; return the original array to avoid allocating on
93925
+ // the hot read-out path.
93926
+ if (effects.length <= 1) {
93927
+ return effects;
93928
+ }
93929
+ // Primary key: timestamp (action chronology). Secondary key: base-record ops before field
93930
+ // ops, which only matters within one action's shared timestamp. Array.prototype.sort is
93931
+ // stable (ES2019), so effects that tie on both keys keep their existing relative order.
93932
+ return [...effects].sort((a, b) => {
93933
+ if (a.timestamp !== b.timestamp) {
93934
+ return a.timestamp - b.timestamp;
93935
+ }
93936
+ const aIsBase = BASE_RECORD_EFFECT_TYPES.has(a.type);
93937
+ const bIsBase = BASE_RECORD_EFFECT_TYPES.has(b.type);
93938
+ if (aIsBase === bIsBase) {
93939
+ return 0;
93940
+ }
93941
+ return aIsBase ? -1 : 1;
93942
+ });
93943
+ }
94108
93944
  class SideEffectStore {
94109
93945
  constructor(durableStore) {
94110
93946
  this.durableStore = durableStore;
@@ -94183,7 +94019,7 @@
94183
94019
  if (effects === undefined) {
94184
94020
  return [];
94185
94021
  }
94186
- return effects;
94022
+ return orderEffectsForApply(effects);
94187
94023
  }
94188
94024
  getEffectsByTag(tag) {
94189
94025
  // This scan is still O(N) over every effect regardless of the incremental-map
@@ -94680,6 +94516,58 @@
94680
94516
  return record;
94681
94517
  }
94682
94518
  }
94519
+ class RecordFieldSetHandler {
94520
+ constructor() {
94521
+ this.logger = typeof __nimbus !== 'undefined' &&
94522
+ __nimbus.plugins !== undefined &&
94523
+ __nimbus.plugins.JSLoggerPlugin !== undefined
94524
+ ? __nimbus.plugins.JSLoggerPlugin
94525
+ : undefined;
94526
+ }
94527
+ apply(record, effect) {
94528
+ if (record === undefined) {
94529
+ if (this.logger) {
94530
+ this.logger.logWarning('RecordFieldSetHandler - cannot apply set side effect to non existing record');
94531
+ }
94532
+ return undefined;
94533
+ }
94534
+ // fieldName is an unconstrained string that has round-tripped through the durable
94535
+ // store, so a value like `__proto__` would hit the prototype setter on the plain
94536
+ // `fields`/`serverValues` objects below rather than creating an own field (prototype
94537
+ // pollution). Reject dangerous keys before any dynamic read/write and skip the effect.
94538
+ if (isUnsafeFieldName(effect.fieldName)) {
94539
+ if (this.logger) {
94540
+ this.logger.logError(`RecordFieldSetHandler - unsafe field name ${effect.fieldName}, skip applying side effect`);
94541
+ }
94542
+ return record;
94543
+ }
94544
+ applyDraftNode(record, effect);
94545
+ record.drafts.edited = true;
94546
+ // capture the server value so it can be restored when the draft syncs.
94547
+ // unlike the increment handler, the field may not yet exist on the record
94548
+ // (e.g. IsProduct2Serialized is not part of the draft-created record), in
94549
+ // which case we record `null` to indicate we never had the field.
94550
+ if (record.drafts.created === false) {
94551
+ const serverValue = record.drafts.serverValues[effect.fieldName];
94552
+ if (serverValue === undefined) {
94553
+ const existingField = record.fields[effect.fieldName];
94554
+ record.drafts.serverValues[effect.fieldName] =
94555
+ existingField === undefined ? null : existingField;
94556
+ }
94557
+ }
94558
+ record.fields[effect.fieldName] = {
94559
+ value: effect.value,
94560
+ displayValue: null,
94561
+ };
94562
+ return record;
94563
+ }
94564
+ }
94565
+ // Field names that would target the prototype chain rather than an own property when used
94566
+ // to index a plain object, enabling prototype pollution on the record's field containers.
94567
+ const UNSAFE_FIELD_NAMES = new Set(['__proto__', 'constructor', 'prototype']);
94568
+ function isUnsafeFieldName(fieldName) {
94569
+ return UNSAFE_FIELD_NAMES.has(fieldName);
94570
+ }
94683
94571
  function applyDraftNode(record, effect) {
94684
94572
  if (record.drafts === undefined) {
94685
94573
  record.drafts = {
@@ -94714,6 +94602,7 @@
94714
94602
  'record-update': new UpdateRecordEffectHandler(),
94715
94603
  'record-delete': new DeleteRecordEffectHandler(),
94716
94604
  'record-field-increment': new RecordFieldIncrementHandler(),
94605
+ 'record-field-set': new RecordFieldSetHandler(),
94717
94606
  };
94718
94607
  /**
94719
94608
  * Removes any existing effects on a record and re-applies the given ordered list and returns the record
@@ -94878,14 +94767,181 @@
94878
94767
  }
94879
94768
 
94880
94769
  const instrumentation$4 = getInstrumentation();
94770
+ const ProductItem$2 = {
94771
+ Fields: {
94772
+ PRODUCT2_ID: 'Product2Id',
94773
+ IS_PRODUCT2_SERIALIZED: 'IsProduct2Serialized',
94774
+ },
94775
+ };
94776
+ const Product2 = {
94777
+ Fields: {
94778
+ IS_SERIALIZED: 'IsSerialized',
94779
+ },
94780
+ };
94781
+ var CounterResult;
94782
+ (function (CounterResult) {
94783
+ // A side effect was created.
94784
+ CounterResult["Created"] = "Created";
94785
+ // The hook exited early.
94786
+ CounterResult["Exited"] = "Exited";
94787
+ // An unexpected error occurred.
94788
+ CounterResult["Error"] = "Error";
94789
+ })(CounterResult || (CounterResult = {}));
94790
+ function incrementCounter(name, result) {
94791
+ instrumentation$4.incrementCounter('SFSSideEffect', 1, result === CounterResult.Error, {
94792
+ Name: name,
94793
+ Result: result,
94794
+ });
94795
+ }
94796
+ /**
94797
+ * Reads a string-valued field off a draft-create `fields` map (the raw create body, where
94798
+ * values are the primitives themselves rather than `{ value }` wrappers). Returns `undefined`
94799
+ * when the field is absent or not a string.
94800
+ */
94801
+ function getStringDraftField(fields, fieldName) {
94802
+ const field = fields[fieldName];
94803
+ if (field === undefined || typeof field !== 'string') {
94804
+ return undefined;
94805
+ }
94806
+ return field;
94807
+ }
94808
+ /**
94809
+ * Reads a string-valued field off a durable record's `fields` map (where each value is a
94810
+ * `{ value }` wrapper). Returns `undefined` when the field is absent, null, or not a string.
94811
+ */
94812
+ function getStringRecordField(fields, fieldName) {
94813
+ const field = fields[fieldName];
94814
+ if (field === undefined || field.value === undefined || typeof field.value !== 'string') {
94815
+ return undefined;
94816
+ }
94817
+ return field.value;
94818
+ }
94819
+ /**
94820
+ * Resolves the reference id that points at a related record for a draft edit.
94821
+ *
94822
+ * On a `patch` the body only carries the fields being changed, so the link field may be
94823
+ * absent, present with a new id, or present but explicitly `null` (the link is being cleared).
94824
+ * These three cases are distinguished via `hasOwnProperty` because they demand different
94825
+ * behavior — falling back to the original durable-store id on an explicit `null` would resolve
94826
+ * `IsProduct2Serialized` from a relationship the record no longer has:
94827
+ *
94828
+ * - present + string -> `resolved` with the reassigned id.
94829
+ * - present + null -> `cleared`.
94830
+ * - absent -> fall back to the original record; `resolved` when it carries a usable
94831
+ * string, otherwise `unresolved`.
94832
+ */
94833
+ function resolveReferenceIdForUpdate(patchFields, originalFields, fieldName) {
94834
+ if (Object.prototype.hasOwnProperty.call(patchFields, fieldName)) {
94835
+ const patchValue = patchFields[fieldName];
94836
+ if (patchValue === null) {
94837
+ return { kind: 'cleared' };
94838
+ }
94839
+ const patchId = getStringDraftField(patchFields, fieldName);
94840
+ if (patchId !== undefined) {
94841
+ return { kind: 'resolved', id: patchId, source: 'patch' };
94842
+ }
94843
+ // Present but neither a string nor null (unexpected shape): treat as an absent link
94844
+ // and fall through to the original record rather than guessing.
94845
+ }
94846
+ const originalId = getStringRecordField(originalFields, fieldName);
94847
+ if (originalId !== undefined) {
94848
+ return { kind: 'resolved', id: originalId, source: 'original' };
94849
+ }
94850
+ return { kind: 'unresolved' };
94851
+ }
94852
+ /**
94853
+ * Resolves the server-derived `IsProduct2Serialized` boolean for a draft-created record by
94854
+ * following the related `ProductItem`:
94855
+ *
94856
+ * 1. If the cached `ProductItem` already carries `IsProduct2Serialized`, use it directly
94857
+ * (it mirrors the linked product's serialization, and avoids a second durable read).
94858
+ * 2. Otherwise fall back to the linked `Product2`:
94859
+ * `ProductItem.Product2Id` -> `Product2.IsSerialized`.
94860
+ *
94861
+ * All lookups go through the given `RecordReader` (the durable store, or a per-action memoizer
94862
+ * that dedups reads). Returns `undefined` when a link in the chain is missing so the caller can
94863
+ * exit without an effect and leave the field synthesized (null). Callers pass their own
94864
+ * `counterName` so instrumentation is attributed to the owning hook.
94865
+ */
94866
+ async function resolveIsProduct2SerializedFromProductItem(recordReader, productItemId, counterName) {
94867
+ const productItemRecord = await recordReader.getRecord(buildRecordRepKeyFromId$1(productItemId));
94868
+ if (productItemRecord === undefined) {
94869
+ incrementCounter(counterName, CounterResult.Exited);
94870
+ return undefined;
94871
+ }
94872
+ // Prefer IsProduct2Serialized straight off the ProductItem when it is cached there;
94873
+ // it saves a second durable read. Fall back to the linked Product2 otherwise.
94874
+ const fromProductItem = getIsProduct2SerializedFromRecordFields(productItemRecord.fields);
94875
+ if (fromProductItem !== undefined) {
94876
+ incrementCounter(counterName, CounterResult.Created);
94877
+ return fromProductItem;
94878
+ }
94879
+ const product2Id = getProduct2IdFromRecordFields(productItemRecord.fields);
94880
+ if (product2Id === undefined) {
94881
+ incrementCounter(counterName, CounterResult.Exited);
94882
+ return undefined;
94883
+ }
94884
+ return resolveIsSerializedFromProduct2Id(recordReader, product2Id, counterName);
94885
+ }
94886
+ /**
94887
+ * Resolves the `Product2.IsSerialized` boolean for a given `Product2Id` by reading the linked
94888
+ * `Product2` record through the given `RecordReader`. Used directly by the `ProductItem` hooks
94889
+ * (which carry `Product2Id` themselves) and as the fallback tail of
94890
+ * `resolveIsProduct2SerializedFromProductItem`.
94891
+ *
94892
+ * Returns `undefined` when the `Product2` record is missing so the caller can exit without an
94893
+ * effect and leave the field synthesized (null). Callers pass their own `counterName` so
94894
+ * instrumentation is attributed to the owning hook.
94895
+ */
94896
+ async function resolveIsSerializedFromProduct2Id(recordReader, product2Id, counterName) {
94897
+ const product2Record = await recordReader.getRecord(buildRecordRepKeyFromId$1(product2Id));
94898
+ if (product2Record === undefined) {
94899
+ incrementCounter(counterName, CounterResult.Exited);
94900
+ return undefined;
94901
+ }
94902
+ const isSerialized = getIsSerializedFromRecordFields(product2Record.fields);
94903
+ if (isSerialized === undefined) {
94904
+ incrementCounter(counterName, CounterResult.Error);
94905
+ // Keep the message a stable literal and append the variable parts as key=value
94906
+ // pairs, so the log line stays low-cardinality and greppable in Splunk (the raw
94907
+ // record id is a discrete field rather than baked into the message text).
94908
+ instrumentation$4.error(`SFS - Product2 IsSerialized not found: hook=${counterName}, product2Id=${product2Id}`);
94909
+ return undefined;
94910
+ }
94911
+ incrementCounter(counterName, CounterResult.Created);
94912
+ return isSerialized;
94913
+ }
94914
+ function getProduct2IdFromRecordFields(fields) {
94915
+ const field = fields[ProductItem$2.Fields.PRODUCT2_ID];
94916
+ if (field === undefined || field.value === undefined || typeof field.value !== 'string') {
94917
+ return undefined;
94918
+ }
94919
+ return field.value;
94920
+ }
94921
+ function getIsProduct2SerializedFromRecordFields(fields) {
94922
+ const field = fields[ProductItem$2.Fields.IS_PRODUCT2_SERIALIZED];
94923
+ if (field === undefined || field.value === undefined || typeof field.value !== 'boolean') {
94924
+ return undefined;
94925
+ }
94926
+ return field.value;
94927
+ }
94928
+ function getIsSerializedFromRecordFields(fields) {
94929
+ const field = fields[Product2.Fields.IS_SERIALIZED];
94930
+ if (field === undefined || field.value === undefined || typeof field.value !== 'boolean') {
94931
+ return undefined;
94932
+ }
94933
+ return field.value;
94934
+ }
94935
+
94881
94936
  const ProductConsumed = {
94882
94937
  API_NAME: 'ProductConsumed',
94883
94938
  Fields: {
94884
94939
  PRODUCT_ITEM_ID: 'ProductItemId',
94885
94940
  QUANTITY_CONSUMED: 'QuantityConsumed',
94941
+ IS_PRODUCT2_SERIALIZED: 'IsProduct2Serialized',
94886
94942
  },
94887
94943
  };
94888
- const ProductItem = {
94944
+ const ProductItem$1 = {
94889
94945
  Fields: {
94890
94946
  QUANTITY_ON_HAND: 'QuantityOnHand',
94891
94947
  },
@@ -94896,7 +94952,7 @@
94896
94952
  this.apiName = ProductConsumed.API_NAME;
94897
94953
  }
94898
94954
  async then(draftActionId, draftActionTimestamp, _draftRecordId, fields) {
94899
- const productItemId = getProductItemId(fields);
94955
+ const productItemId = getStringDraftField(fields, ProductConsumed.Fields.PRODUCT_ITEM_ID);
94900
94956
  const quantityConsumed = getQuantityConsumed(fields);
94901
94957
  if (productItemId === undefined) {
94902
94958
  incrementCounter(ProductConsumedCreatedHook.COUNTER_NAME, CounterResult.Exited);
@@ -94909,7 +94965,7 @@
94909
94965
  incrementCounter(ProductConsumedCreatedHook.COUNTER_NAME, CounterResult.Created);
94910
94966
  const effect = {
94911
94967
  type: 'record-field-increment',
94912
- fieldName: ProductItem.Fields.QUANTITY_ON_HAND,
94968
+ fieldName: ProductItem$1.Fields.QUANTITY_ON_HAND,
94913
94969
  value: -quantityConsumed,
94914
94970
  key: buildRecordRepKeyFromId$1(productItemId),
94915
94971
  tag: draftActionId,
@@ -94926,16 +94982,17 @@
94926
94982
  this.on = 'patch';
94927
94983
  this.apiName = ProductConsumed.API_NAME;
94928
94984
  }
94929
- async then(draftActionId, draftActionTimestamp, draftRecordId, fields) {
94985
+ async then(draftActionId, draftActionTimestamp, draftRecordId, fields, reader) {
94930
94986
  const quantityConsumed = getQuantityConsumed(fields);
94931
94987
  if (quantityConsumed === undefined) {
94932
94988
  incrementCounter(ProductConsumedUpdatedHook.COUNTER_NAME, CounterResult.Exited);
94933
94989
  return [];
94934
94990
  }
94935
- const originalRecord = await this.durableRecordStore.getRecord(buildRecordRepKeyFromId$1(draftRecordId));
94991
+ const recordReader = reader ?? this.durableRecordStore;
94992
+ const originalRecord = await recordReader.getRecord(buildRecordRepKeyFromId$1(draftRecordId));
94936
94993
  if (originalRecord === undefined) {
94937
94994
  incrementCounter(ProductConsumedUpdatedHook.COUNTER_NAME, CounterResult.Error);
94938
- instrumentation$4.error(`SFS - ProductConsumedUpdatedHook - Original record ${draftRecordId} not found`);
94995
+ instrumentation$4.error(`SFS - ProductConsumedUpdatedHook original record not found: draftRecordId=${draftRecordId}`);
94939
94996
  return [];
94940
94997
  }
94941
94998
  const originalProductItemId = getProductItemIdFromRecordFields(originalRecord.fields);
@@ -94946,13 +95003,13 @@
94946
95003
  }
94947
95004
  if (originalQuantityConsumed === undefined) {
94948
95005
  incrementCounter(ProductConsumedUpdatedHook.COUNTER_NAME, CounterResult.Error);
94949
- instrumentation$4.error(`SFS - ProductConsumedUpdatedHook - Original record ${draftRecordId} QuantityConsumed not found`);
95006
+ instrumentation$4.error(`SFS - ProductConsumedUpdatedHook original record QuantityConsumed not found: draftRecordId=${draftRecordId}`);
94950
95007
  return [];
94951
95008
  }
94952
95009
  incrementCounter(ProductConsumedUpdatedHook.COUNTER_NAME, CounterResult.Created);
94953
95010
  const effect = {
94954
95011
  type: 'record-field-increment',
94955
- fieldName: ProductItem.Fields.QUANTITY_ON_HAND,
95012
+ fieldName: ProductItem$1.Fields.QUANTITY_ON_HAND,
94956
95013
  value: originalQuantityConsumed - quantityConsumed,
94957
95014
  key: buildRecordRepKeyFromId$1(originalProductItemId),
94958
95015
  tag: draftActionId,
@@ -94969,11 +95026,12 @@
94969
95026
  this.on = 'delete';
94970
95027
  this.apiName = ProductConsumed.API_NAME;
94971
95028
  }
94972
- async then(draftActionId, draftActionTimestamp, draftRecordId) {
94973
- const originalRecord = await this.durableRecordStore.getRecord(buildRecordRepKeyFromId$1(draftRecordId));
95029
+ async then(draftActionId, draftActionTimestamp, draftRecordId, _fields, reader) {
95030
+ const recordReader = reader ?? this.durableRecordStore;
95031
+ const originalRecord = await recordReader.getRecord(buildRecordRepKeyFromId$1(draftRecordId));
94974
95032
  if (originalRecord === undefined) {
94975
95033
  incrementCounter(ProductConsumedDeletedHook.COUNTER_NAME, CounterResult.Error);
94976
- instrumentation$4.error(`SFS - ProductConsumedDeletedHook - Original record ${draftRecordId} not found`);
95034
+ instrumentation$4.error(`SFS - ProductConsumedDeletedHook original record not found: draftRecordId=${draftRecordId}`);
94977
95035
  return [];
94978
95036
  }
94979
95037
  const productItemId = getProductItemIdFromRecordFields(originalRecord.fields);
@@ -94984,13 +95042,13 @@
94984
95042
  }
94985
95043
  if (quantityConsumed === undefined) {
94986
95044
  incrementCounter(ProductConsumedDeletedHook.COUNTER_NAME, CounterResult.Error);
94987
- instrumentation$4.error(`SFS - ProductConsumedDeletedHook - Original record ${draftRecordId} QuantityConsumed not found`);
95045
+ instrumentation$4.error(`SFS - ProductConsumedDeletedHook original record QuantityConsumed not found: draftRecordId=${draftRecordId}`);
94988
95046
  return [];
94989
95047
  }
94990
95048
  incrementCounter(ProductConsumedDeletedHook.COUNTER_NAME, CounterResult.Created);
94991
95049
  const effect = {
94992
95050
  type: 'record-field-increment',
94993
- fieldName: ProductItem.Fields.QUANTITY_ON_HAND,
95051
+ fieldName: ProductItem$1.Fields.QUANTITY_ON_HAND,
94994
95052
  value: quantityConsumed,
94995
95053
  key: buildRecordRepKeyFromId$1(productItemId),
94996
95054
  tag: draftActionId,
@@ -95001,13 +95059,115 @@
95001
95059
  }
95002
95060
  }
95003
95061
  ProductConsumedDeletedHook.COUNTER_NAME = 'ProductConsumedDeletedHook';
95004
- function getProductItemId(fields) {
95005
- const field = fields[ProductConsumed.Fields.PRODUCT_ITEM_ID];
95006
- if (field === undefined || typeof field !== 'string') {
95007
- return undefined;
95062
+ /**
95063
+ * Replicates the server-derived `IsProduct2Serialized` boolean on a draft-created
95064
+ * `ProductConsumed` record.
95065
+ *
95066
+ * `Product2Id` is not createable on `ProductConsumed`, so the draft only carries the
95067
+ * `ProductItemId`. The value is resolved through the related `ProductItem` record (see
95068
+ * `resolveIsProduct2SerializedFromProductItem`): the ProductItem's own
95069
+ * `IsProduct2Serialized` when cached, otherwise `ProductItem.Product2Id` ->
95070
+ * `Product2.IsSerialized`. When the chain cannot be resolved the hook exits without an
95071
+ * effect and the field is left as synthesized (null).
95072
+ */
95073
+ class ProductConsumedIsSerializedCreatedHook {
95074
+ constructor(durableRecordStore) {
95075
+ this.durableRecordStore = durableRecordStore;
95076
+ this.on = 'post';
95077
+ this.apiName = ProductConsumed.API_NAME;
95078
+ }
95079
+ async then(draftActionId, draftActionTimestamp, draftRecordId, fields, reader) {
95080
+ const productItemId = getStringDraftField(fields, ProductConsumed.Fields.PRODUCT_ITEM_ID);
95081
+ if (productItemId === undefined) {
95082
+ incrementCounter(ProductConsumedIsSerializedCreatedHook.COUNTER_NAME, CounterResult.Exited);
95083
+ return [];
95084
+ }
95085
+ const isSerialized = await resolveIsProduct2SerializedFromProductItem(reader ?? this.durableRecordStore, productItemId, ProductConsumedIsSerializedCreatedHook.COUNTER_NAME);
95086
+ if (isSerialized === undefined) {
95087
+ return [];
95088
+ }
95089
+ const effect = {
95090
+ type: 'record-field-set',
95091
+ fieldName: ProductConsumed.Fields.IS_PRODUCT2_SERIALIZED,
95092
+ value: isSerialized,
95093
+ key: buildRecordRepKeyFromId$1(draftRecordId),
95094
+ tag: draftActionId,
95095
+ uniqueId: `${draftActionId}-IsProduct2Serialized`,
95096
+ timestamp: draftActionTimestamp,
95097
+ };
95098
+ return [effect];
95099
+ }
95100
+ }
95101
+ ProductConsumedIsSerializedCreatedHook.COUNTER_NAME = 'ProductConsumedIsSerializedCreatedHook';
95102
+ /**
95103
+ * Replicates the server-derived `IsProduct2Serialized` boolean on a draft-edited
95104
+ * `ProductConsumed` record.
95105
+ *
95106
+ * A `patch` body only carries changed fields, so the related `ProductItemId` may be absent.
95107
+ * The id is resolved by preferring the patch body (the link is being reassigned) and falling
95108
+ * back to the original record in the durable store, then the value is resolved through that
95109
+ * `ProductItem` exactly as on create (see `resolveIsProduct2SerializedFromProductItem`).
95110
+ *
95111
+ * When the patch explicitly clears `ProductItemId` (sets it to `null`) the record no longer
95112
+ * derives from any `ProductItem`, so `IsProduct2Serialized` is cleared (set to `null`) rather
95113
+ * than resolved from the now-stale original link. Likewise, when the patch reassigns
95114
+ * `ProductItemId` to a new id whose chain isn't cached, the stored value reflects the old link
95115
+ * and is stale, so it is cleared. When the link is unchanged and its chain cannot be resolved,
95116
+ * the stored value is still correct so the hook exits without an effect and leaves it unchanged.
95117
+ */
95118
+ class ProductConsumedIsSerializedUpdatedHook {
95119
+ constructor(durableRecordStore) {
95120
+ this.durableRecordStore = durableRecordStore;
95121
+ this.on = 'patch';
95122
+ this.apiName = ProductConsumed.API_NAME;
95123
+ }
95124
+ async then(draftActionId, draftActionTimestamp, draftRecordId, fields, reader) {
95125
+ const recordReader = reader ?? this.durableRecordStore;
95126
+ const originalRecord = await recordReader.getRecord(buildRecordRepKeyFromId$1(draftRecordId));
95127
+ if (originalRecord === undefined) {
95128
+ incrementCounter(ProductConsumedIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Error);
95129
+ instrumentation$4.error(`SFS - ProductConsumedIsSerializedUpdatedHook original record not found: draftRecordId=${draftRecordId}`);
95130
+ return [];
95131
+ }
95132
+ const reference = resolveReferenceIdForUpdate(fields, originalRecord.fields, ProductConsumed.Fields.PRODUCT_ITEM_ID);
95133
+ if (reference.kind === 'unresolved') {
95134
+ incrementCounter(ProductConsumedIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Exited);
95135
+ return [];
95136
+ }
95137
+ let isSerialized;
95138
+ if (reference.kind === 'cleared') {
95139
+ // The link was explicitly cleared: the record derives from no ProductItem, so clear
95140
+ // IsProduct2Serialized rather than resolving it from the stale original link.
95141
+ incrementCounter(ProductConsumedIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Created);
95142
+ isSerialized = null;
95143
+ }
95144
+ else {
95145
+ isSerialized = await resolveIsProduct2SerializedFromProductItem(recordReader, reference.id, ProductConsumedIsSerializedUpdatedHook.COUNTER_NAME);
95146
+ if (isSerialized === undefined && reference.source === 'patch') {
95147
+ // The link was reassigned to a new ProductItem whose chain isn't cached yet, so
95148
+ // the stored IsProduct2Serialized still reflects the OLD link and is now stale.
95149
+ // Clear it rather than leaving the stale value alongside the new relationship;
95150
+ // the server recomputes the real value on sync.
95151
+ incrementCounter(ProductConsumedIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Created);
95152
+ isSerialized = null;
95153
+ }
95154
+ }
95155
+ if (isSerialized === undefined) {
95156
+ return [];
95157
+ }
95158
+ const effect = {
95159
+ type: 'record-field-set',
95160
+ fieldName: ProductConsumed.Fields.IS_PRODUCT2_SERIALIZED,
95161
+ value: isSerialized,
95162
+ key: buildRecordRepKeyFromId$1(draftRecordId),
95163
+ tag: draftActionId,
95164
+ uniqueId: `${draftActionId}-IsProduct2Serialized`,
95165
+ timestamp: draftActionTimestamp,
95166
+ };
95167
+ return [effect];
95008
95168
  }
95009
- return field;
95010
95169
  }
95170
+ ProductConsumedIsSerializedUpdatedHook.COUNTER_NAME = 'ProductConsumedIsSerializedUpdatedHook';
95011
95171
  function getProductItemIdFromRecordFields(fields) {
95012
95172
  const field = fields[ProductConsumed.Fields.PRODUCT_ITEM_ID];
95013
95173
  if (field === undefined || field.value === undefined || typeof field.value !== 'string') {
@@ -95029,29 +95189,265 @@
95029
95189
  }
95030
95190
  return field.value;
95031
95191
  }
95032
- var CounterResult;
95033
- (function (CounterResult) {
95034
- // A side effect was created.
95035
- CounterResult["Created"] = "Created";
95036
- // The hook exited early.
95037
- CounterResult["Exited"] = "Exited";
95038
- // An unexpected error occurred.
95039
- CounterResult["Error"] = "Error";
95040
- })(CounterResult || (CounterResult = {}));
95041
- function incrementCounter(name, result) {
95042
- instrumentation$4.incrementCounter('SFSSideEffect', 1, result === CounterResult.Error, {
95043
- Name: name,
95044
- Result: result,
95045
- });
95192
+
95193
+ const ProductTransfer = {
95194
+ API_NAME: 'ProductTransfer',
95195
+ Fields: {
95196
+ SOURCE_PRODUCT_ITEM_ID: 'SourceProductItemId',
95197
+ IS_PRODUCT2_SERIALIZED: 'IsProduct2Serialized',
95198
+ },
95199
+ };
95200
+ /**
95201
+ * Replicates the server-derived `IsProduct2Serialized` boolean on a draft-created
95202
+ * `ProductTransfer` record.
95203
+ *
95204
+ * The value is resolved through the related `SourceProductItem` record (a `ProductItem`),
95205
+ * mirroring the `ProductConsumed` hook: the ProductItem's own `IsProduct2Serialized` when
95206
+ * cached, otherwise `ProductItem.Product2Id` -> `Product2.IsSerialized` (see
95207
+ * `resolveIsProduct2SerializedFromProductItem`). When the chain cannot be resolved the hook
95208
+ * exits without an effect and the field is left as synthesized (null).
95209
+ */
95210
+ class ProductTransferIsSerializedCreatedHook {
95211
+ constructor(durableRecordStore) {
95212
+ this.durableRecordStore = durableRecordStore;
95213
+ this.on = 'post';
95214
+ this.apiName = ProductTransfer.API_NAME;
95215
+ }
95216
+ async then(draftActionId, draftActionTimestamp, draftRecordId, fields, reader) {
95217
+ const sourceProductItemId = getStringDraftField(fields, ProductTransfer.Fields.SOURCE_PRODUCT_ITEM_ID);
95218
+ if (sourceProductItemId === undefined) {
95219
+ incrementCounter(ProductTransferIsSerializedCreatedHook.COUNTER_NAME, CounterResult.Exited);
95220
+ return [];
95221
+ }
95222
+ const isSerialized = await resolveIsProduct2SerializedFromProductItem(reader ?? this.durableRecordStore, sourceProductItemId, ProductTransferIsSerializedCreatedHook.COUNTER_NAME);
95223
+ if (isSerialized === undefined) {
95224
+ return [];
95225
+ }
95226
+ const effect = {
95227
+ type: 'record-field-set',
95228
+ fieldName: ProductTransfer.Fields.IS_PRODUCT2_SERIALIZED,
95229
+ value: isSerialized,
95230
+ key: buildRecordRepKeyFromId$1(draftRecordId),
95231
+ tag: draftActionId,
95232
+ uniqueId: `${draftActionId}-IsProduct2Serialized`,
95233
+ timestamp: draftActionTimestamp,
95234
+ };
95235
+ return [effect];
95236
+ }
95237
+ }
95238
+ ProductTransferIsSerializedCreatedHook.COUNTER_NAME = 'ProductTransferIsSerializedCreatedHook';
95239
+ /**
95240
+ * Replicates the server-derived `IsProduct2Serialized` boolean on a draft-edited
95241
+ * `ProductTransfer` record.
95242
+ *
95243
+ * ProductTransfer can only be edited (not created) on mobile, so this is the hook that fires
95244
+ * in practice. A `patch` body only carries changed fields, so the related `SourceProductItemId`
95245
+ * may be absent; the id is resolved by preferring the patch body (the link is being reassigned)
95246
+ * and falling back to the original record in the durable store, then the value is resolved
95247
+ * through that `ProductItem` exactly as on create (see
95248
+ * `resolveIsProduct2SerializedFromProductItem`).
95249
+ *
95250
+ * When the patch explicitly clears `SourceProductItemId` (sets it to `null`) the record no
95251
+ * longer derives from any `ProductItem`, so `IsProduct2Serialized` is cleared (set to `null`)
95252
+ * rather than resolved from the now-stale original link. Likewise, when the patch reassigns
95253
+ * `SourceProductItemId` to a new id whose chain isn't cached, the stored value reflects the old
95254
+ * link and is stale, so it is cleared. When the link is unchanged and its chain cannot be
95255
+ * resolved, the stored value is still correct so the hook exits without an effect and leaves it
95256
+ * unchanged.
95257
+ */
95258
+ class ProductTransferIsSerializedUpdatedHook {
95259
+ constructor(durableRecordStore) {
95260
+ this.durableRecordStore = durableRecordStore;
95261
+ this.on = 'patch';
95262
+ this.apiName = ProductTransfer.API_NAME;
95263
+ }
95264
+ async then(draftActionId, draftActionTimestamp, draftRecordId, fields, reader) {
95265
+ const recordReader = reader ?? this.durableRecordStore;
95266
+ const originalRecord = await recordReader.getRecord(buildRecordRepKeyFromId$1(draftRecordId));
95267
+ if (originalRecord === undefined) {
95268
+ incrementCounter(ProductTransferIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Error);
95269
+ instrumentation$4.error(`SFS - ProductTransferIsSerializedUpdatedHook original record not found: draftRecordId=${draftRecordId}`);
95270
+ return [];
95271
+ }
95272
+ const reference = resolveReferenceIdForUpdate(fields, originalRecord.fields, ProductTransfer.Fields.SOURCE_PRODUCT_ITEM_ID);
95273
+ if (reference.kind === 'unresolved') {
95274
+ incrementCounter(ProductTransferIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Exited);
95275
+ return [];
95276
+ }
95277
+ let isSerialized;
95278
+ if (reference.kind === 'cleared') {
95279
+ // The link was explicitly cleared: the record derives from no ProductItem, so clear
95280
+ // IsProduct2Serialized rather than resolving it from the stale original link.
95281
+ incrementCounter(ProductTransferIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Created);
95282
+ isSerialized = null;
95283
+ }
95284
+ else {
95285
+ isSerialized = await resolveIsProduct2SerializedFromProductItem(recordReader, reference.id, ProductTransferIsSerializedUpdatedHook.COUNTER_NAME);
95286
+ if (isSerialized === undefined && reference.source === 'patch') {
95287
+ // The link was reassigned to a new ProductItem whose chain isn't cached yet, so
95288
+ // the stored IsProduct2Serialized still reflects the OLD link and is now stale.
95289
+ // Clear it rather than leaving the stale value alongside the new relationship;
95290
+ // the server recomputes the real value on sync.
95291
+ incrementCounter(ProductTransferIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Created);
95292
+ isSerialized = null;
95293
+ }
95294
+ }
95295
+ if (isSerialized === undefined) {
95296
+ return [];
95297
+ }
95298
+ const effect = {
95299
+ type: 'record-field-set',
95300
+ fieldName: ProductTransfer.Fields.IS_PRODUCT2_SERIALIZED,
95301
+ value: isSerialized,
95302
+ key: buildRecordRepKeyFromId$1(draftRecordId),
95303
+ tag: draftActionId,
95304
+ uniqueId: `${draftActionId}-IsProduct2Serialized`,
95305
+ timestamp: draftActionTimestamp,
95306
+ };
95307
+ return [effect];
95308
+ }
95046
95309
  }
95310
+ ProductTransferIsSerializedUpdatedHook.COUNTER_NAME = 'ProductTransferIsSerializedUpdatedHook';
95311
+
95312
+ const ProductItem = {
95313
+ API_NAME: 'ProductItem',
95314
+ Fields: {
95315
+ PRODUCT2_ID: 'Product2Id',
95316
+ IS_PRODUCT2_SERIALIZED: 'IsProduct2Serialized',
95317
+ },
95318
+ };
95319
+ /**
95320
+ * Replicates the server-derived `IsProduct2Serialized` boolean on a draft-created
95321
+ * `ProductItem` record.
95322
+ *
95323
+ * Unlike `ProductConsumed`/`ProductTransfer` — which reach the product through a related
95324
+ * `ProductItem` — a `ProductItem` carries `Product2Id` directly, so the value is resolved as
95325
+ * `Product2Id` -> `Product2.IsSerialized` (see `resolveIsSerializedFromProduct2Id`). When the
95326
+ * chain cannot be resolved the hook exits without an effect and the field is left as
95327
+ * synthesized (null).
95328
+ */
95329
+ class ProductItemIsSerializedCreatedHook {
95330
+ constructor(durableRecordStore) {
95331
+ this.durableRecordStore = durableRecordStore;
95332
+ this.on = 'post';
95333
+ this.apiName = ProductItem.API_NAME;
95334
+ }
95335
+ async then(draftActionId, draftActionTimestamp, draftRecordId, fields, reader) {
95336
+ const product2Id = getStringDraftField(fields, ProductItem.Fields.PRODUCT2_ID);
95337
+ if (product2Id === undefined) {
95338
+ incrementCounter(ProductItemIsSerializedCreatedHook.COUNTER_NAME, CounterResult.Exited);
95339
+ return [];
95340
+ }
95341
+ const isSerialized = await resolveIsSerializedFromProduct2Id(reader ?? this.durableRecordStore, product2Id, ProductItemIsSerializedCreatedHook.COUNTER_NAME);
95342
+ if (isSerialized === undefined) {
95343
+ return [];
95344
+ }
95345
+ const effect = {
95346
+ type: 'record-field-set',
95347
+ fieldName: ProductItem.Fields.IS_PRODUCT2_SERIALIZED,
95348
+ value: isSerialized,
95349
+ key: buildRecordRepKeyFromId$1(draftRecordId),
95350
+ tag: draftActionId,
95351
+ uniqueId: `${draftActionId}-IsProduct2Serialized`,
95352
+ timestamp: draftActionTimestamp,
95353
+ };
95354
+ return [effect];
95355
+ }
95356
+ }
95357
+ ProductItemIsSerializedCreatedHook.COUNTER_NAME = 'ProductItemIsSerializedCreatedHook';
95358
+ /**
95359
+ * Replicates the server-derived `IsProduct2Serialized` boolean on a draft-edited
95360
+ * `ProductItem` record.
95361
+ *
95362
+ * A `patch` body only carries changed fields, so `Product2Id` may be absent; it is resolved by
95363
+ * preferring the patch body (the product is being reassigned) and falling back to the original
95364
+ * record in the durable store, then resolved as `Product2Id` -> `Product2.IsSerialized` (see
95365
+ * `resolveIsSerializedFromProduct2Id`).
95366
+ *
95367
+ * When the patch explicitly clears `Product2Id` (sets it to `null`) the record no longer
95368
+ * derives from any `Product2`, so `IsProduct2Serialized` is cleared (set to `null`) rather than
95369
+ * resolved from the now-stale original link. Likewise, when the patch reassigns `Product2Id` to
95370
+ * a new id whose `Product2` isn't cached, the stored value reflects the old link and is stale,
95371
+ * so it is cleared. When the link is unchanged and its `Product2` cannot be resolved, the stored
95372
+ * value is still correct so the hook exits without an effect and leaves it unchanged.
95373
+ */
95374
+ class ProductItemIsSerializedUpdatedHook {
95375
+ constructor(durableRecordStore) {
95376
+ this.durableRecordStore = durableRecordStore;
95377
+ this.on = 'patch';
95378
+ this.apiName = ProductItem.API_NAME;
95379
+ }
95380
+ async then(draftActionId, draftActionTimestamp, draftRecordId, fields, reader) {
95381
+ const recordReader = reader ?? this.durableRecordStore;
95382
+ const originalRecord = await recordReader.getRecord(buildRecordRepKeyFromId$1(draftRecordId));
95383
+ if (originalRecord === undefined) {
95384
+ incrementCounter(ProductItemIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Error);
95385
+ instrumentation$4.error(`SFS - ProductItemIsSerializedUpdatedHook original record not found: draftRecordId=${draftRecordId}`);
95386
+ return [];
95387
+ }
95388
+ const reference = resolveReferenceIdForUpdate(fields, originalRecord.fields, ProductItem.Fields.PRODUCT2_ID);
95389
+ if (reference.kind === 'unresolved') {
95390
+ incrementCounter(ProductItemIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Exited);
95391
+ return [];
95392
+ }
95393
+ let isSerialized;
95394
+ if (reference.kind === 'cleared') {
95395
+ // The link was explicitly cleared: the record derives from no Product2, so clear
95396
+ // IsProduct2Serialized rather than resolving it from the stale original link.
95397
+ incrementCounter(ProductItemIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Created);
95398
+ isSerialized = null;
95399
+ }
95400
+ else {
95401
+ isSerialized = await resolveIsSerializedFromProduct2Id(recordReader, reference.id, ProductItemIsSerializedUpdatedHook.COUNTER_NAME);
95402
+ if (isSerialized === undefined && reference.source === 'patch') {
95403
+ // The link was reassigned to a new Product2 that isn't cached yet, so the stored
95404
+ // IsProduct2Serialized still reflects the OLD link and is now stale. Clear it
95405
+ // rather than leaving the stale value alongside the new relationship; the server
95406
+ // recomputes the real value on sync.
95407
+ incrementCounter(ProductItemIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Created);
95408
+ isSerialized = null;
95409
+ }
95410
+ }
95411
+ if (isSerialized === undefined) {
95412
+ return [];
95413
+ }
95414
+ const effect = {
95415
+ type: 'record-field-set',
95416
+ fieldName: ProductItem.Fields.IS_PRODUCT2_SERIALIZED,
95417
+ value: isSerialized,
95418
+ key: buildRecordRepKeyFromId$1(draftRecordId),
95419
+ tag: draftActionId,
95420
+ uniqueId: `${draftActionId}-IsProduct2Serialized`,
95421
+ timestamp: draftActionTimestamp,
95422
+ };
95423
+ return [effect];
95424
+ }
95425
+ }
95426
+ ProductItemIsSerializedUpdatedHook.COUNTER_NAME = 'ProductItemIsSerializedUpdatedHook';
95047
95427
 
95048
95428
  function createSfsSideEffectHooks(durableRecordStore) {
95049
95429
  if (productConsumedSideEffects.isOpen({ fallback: false })) {
95050
- return [
95430
+ const hooks = [
95051
95431
  new ProductConsumedCreatedHook(),
95052
95432
  new ProductConsumedUpdatedHook(durableRecordStore),
95053
95433
  new ProductConsumedDeletedHook(durableRecordStore),
95054
95434
  ];
95435
+ // The IsProduct2Serialized side effects (ProductConsumed, ProductTransfer and
95436
+ // ProductItem) ship on by default and can be disabled independently of the van stock
95437
+ // hooks above via a shared kill switch: the gate is closed (fallback false) by default,
95438
+ // which keeps the effects enabled, and opening the gate turns them off.
95439
+ //
95440
+ // Each object gets both a create (post) and edit (patch) hook so the field is
95441
+ // synthesized on either draft action, keeping the behavior uniform across the objects.
95442
+ if (!isProduct2SerializedSideEffectKillSwitch.isOpen({ fallback: false })) {
95443
+ hooks.push(new ProductConsumedIsSerializedCreatedHook(durableRecordStore));
95444
+ hooks.push(new ProductConsumedIsSerializedUpdatedHook(durableRecordStore));
95445
+ hooks.push(new ProductTransferIsSerializedCreatedHook(durableRecordStore));
95446
+ hooks.push(new ProductTransferIsSerializedUpdatedHook(durableRecordStore));
95447
+ hooks.push(new ProductItemIsSerializedCreatedHook(durableRecordStore));
95448
+ hooks.push(new ProductItemIsSerializedUpdatedHook(durableRecordStore));
95449
+ }
95450
+ return hooks;
95055
95451
  }
95056
95452
  return [];
95057
95453
  }
@@ -95375,6 +95771,15 @@
95375
95771
  };
95376
95772
  }
95377
95773
 
95774
+ function applyOperationRequestOptions$1(init, operationType) {
95775
+ if (operationType === void 0) {
95776
+ return init;
95777
+ }
95778
+ return {
95779
+ ...init,
95780
+ onestore: { ...init?.onestore, retrySafe: operationType === "query" }
95781
+ };
95782
+ }
95378
95783
  class AuraNetworkCommand extends NetworkCommand$1 {
95379
95784
  constructor(services) {
95380
95785
  super(services);
@@ -95393,6 +95798,18 @@
95393
95798
  "Fetch parameters must be specified when using HTTP transport on an Aura adapter"
95394
95799
  );
95395
95800
  }
95801
+ /**
95802
+ * The command's operation type. Generated command subclasses override this
95803
+ * getter with their concrete operation (see the generator's
95804
+ * `generateOperationType`); the base returns `undefined` for commands that
95805
+ * don't declare one. On the HTTP fallback path (`fetch`) it derives the
95806
+ * request's `onestore.retrySafe` — reads (`query`) are retry-safe, writes
95807
+ * (`mutation`) are not. Mirrors `FetchNetworkCommand.operationType`, which
95808
+ * this class does not inherit (it extends `NetworkCommand` directly).
95809
+ */
95810
+ get operationType() {
95811
+ return void 0;
95812
+ }
95396
95813
  coerceAuraErrors(auraErrors) {
95397
95814
  return new UserVisibleError(getFetchResponseFromAuraError(auraErrors[0]));
95398
95815
  }
@@ -95494,9 +95911,11 @@
95494
95911
  this.coerceAuraErrors
95495
95912
  );
95496
95913
  } else if (this.shouldUseFetch()) {
95497
- const params = this.fetchParams;
95914
+ const [input, init] = this.fetchParams;
95915
+ const initWithOptions = applyOperationRequestOptions$1(init, this.operationType);
95498
95916
  try {
95499
- return this.convertFetchResponseToData(this.services.fetch(...params));
95917
+ const fetchCall = initWithOptions === void 0 ? this.services.fetch(input) : this.services.fetch(input, initWithOptions);
95918
+ return this.convertFetchResponseToData(fetchCall);
95500
95919
  } catch (reason) {
95501
95920
  return resolvedPromiseLike$2(err$1(toError(reason)));
95502
95921
  }
@@ -96243,6 +96662,15 @@
96243
96662
  async afterRequestHooks(_options) {
96244
96663
  }
96245
96664
  }
96665
+ function applyOperationRequestOptions(init, operationType) {
96666
+ if (operationType === void 0) {
96667
+ return init;
96668
+ }
96669
+ return {
96670
+ ...init,
96671
+ onestore: { ...init?.onestore, retrySafe: operationType === "query" }
96672
+ };
96673
+ }
96246
96674
  function isAbortableCommand(command) {
96247
96675
  return !!command && typeof command === "object" && "isAbortableCommand" in command && command.isAbortableCommand === true;
96248
96676
  }
@@ -96315,7 +96743,8 @@
96315
96743
  * `generateOperationType`); the base returns `undefined` for commands that
96316
96744
  * don't declare one (e.g. hand-written commands). Transport-layer
96317
96745
  * interceptors use it to tell a read (`query`) apart from a write — see
96318
- * `fetch`, which folds it into the context seed.
96746
+ * `fetch`, which folds it into the context seed and derives
96747
+ * `onestore.retrySafe` from it.
96319
96748
  */
96320
96749
  get operationType() {
96321
96750
  return void 0;
@@ -96324,8 +96753,9 @@
96324
96753
  try {
96325
96754
  const [input, init] = this.fetchParams;
96326
96755
  const operationType = this.operationType;
96756
+ const initWithOptions = applyOperationRequestOptions(init, operationType);
96327
96757
  const seed = operationType === void 0 ? contextSeed : { operationType, ...contextSeed };
96328
- const initWithSeed = seed === void 0 ? init : { ...init, __contextSeed: seed };
96758
+ const initWithSeed = seed === void 0 ? initWithOptions : { ...initWithOptions, __contextSeed: seed };
96329
96759
  const fetchCall = initWithSeed === void 0 ? this.services.fetch(input) : this.services.fetch(input, initWithSeed);
96330
96760
  return this.convertFetchResponseToData(fetchCall);
96331
96761
  } catch (reason) {
@@ -97419,7 +97849,7 @@
97419
97849
  },
97420
97850
  };
97421
97851
  }
97422
- // version: 1.461.0-37b1c9d1cb
97852
+ // version: 1.463.0-ea9ecd17fc
97423
97853
 
97424
97854
  /**
97425
97855
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -97445,7 +97875,7 @@
97445
97875
  },
97446
97876
  };
97447
97877
  }
97448
- // version: 1.461.0-37b1c9d1cb
97878
+ // version: 1.463.0-ea9ecd17fc
97449
97879
 
97450
97880
  function findExecutableOperation$1(input) {
97451
97881
  const operations = input.query.definitions.filter(
@@ -98026,9 +98456,18 @@
98026
98456
  if (data === null || typeof data !== "object" || Array.isArray(data)) {
98027
98457
  return invalidSchemaResponseWithError(incorrectTypeError("object", typeof data, path));
98028
98458
  }
98029
- const { propertyName, mapping } = discriminator;
98459
+ const { propertyName, mapping, defaultMapping } = discriminator;
98030
98460
  const discriminatorValue = data[propertyName];
98031
98461
  if (discriminatorValue === void 0) {
98462
+ if (defaultMapping !== void 0) {
98463
+ return validateJsonSchemaInternal(
98464
+ data,
98465
+ { $ref: defaultMapping },
98466
+ path,
98467
+ document,
98468
+ visited
98469
+ );
98470
+ }
98032
98471
  return invalidSchemaResponseWithError(
98033
98472
  new MissingRequiredPropertyError(
98034
98473
  `Object at path '${path}' is missing required discriminator property '${propertyName}'.`
@@ -98040,7 +98479,7 @@
98040
98479
  incorrectTypeError("string", typeof discriminatorValue, `${path}.${propertyName}`)
98041
98480
  );
98042
98481
  }
98043
- const targetRef = mapping[discriminatorValue];
98482
+ const targetRef = mapping[discriminatorValue] ?? defaultMapping;
98044
98483
  if (targetRef === void 0) {
98045
98484
  return invalidSchemaResponseWithError(
98046
98485
  new JsonSchemaViolationError(
@@ -99834,9 +100273,6 @@
99834
100273
 
99835
100274
  const PURGE_CACHE_DAYS = 30;
99836
100275
  function initializeOneStore(sqliteStore, luvio) {
99837
- if (!useOneStore.isOpen({ fallback: true })) {
99838
- return;
99839
- }
99840
100276
  // Initialize performance.now() fallback if not available
99841
100277
  if (typeof globalThis.performance === 'undefined') {
99842
100278
  globalThis.performance = { now: () => Date.now() };
@@ -100023,12 +100459,6 @@
100023
100459
  lazyInternalLuvio = internalLuvio;
100024
100460
  lazyObjectInfoService = new ObjectInfoService(getObjectInfo, getObjectInfos, getObjectInfoDirectory, lazyDurableStore);
100025
100461
  const baseEnv = new Environment(store, lazyNetworkAdapter);
100026
- const shouldFilterFieldsOnRevive = (key) => {
100027
- if (reviveOnlyRequestedFields.isOpen({ fallback: true })) {
100028
- return isStoreKeyRecordId(key);
100029
- }
100030
- return false;
100031
- };
100032
100462
  const gqlEnv = makeEnvironmentGraphqlAware(baseEnv);
100033
100463
  const durableEnv = makeDurable(gqlEnv, {
100034
100464
  durableStore: lazyDurableStore,
@@ -100036,7 +100466,12 @@
100036
100466
  // disable luvio deep freeze in headless environments
100037
100467
  disableDeepFreeze: typeof window === 'undefined',
100038
100468
  shouldFlush,
100039
- shouldFilterFieldsOnRevive,
100469
+ // Disabling field filtering on revive for now (previously gated behind
100470
+ // the lmr.reviveOnlyRequestedFields gate). The code path is retained
100471
+ // below should we need to re-enable this behavior in the future:
100472
+ shouldFilterFieldsOnRevive: (_key) => {
100473
+ return false;
100474
+ },
100040
100475
  });
100041
100476
  // draft queue
100042
100477
  lazyDraftQueue = buildLdsDraftQueue(lazyDurableStore);
@@ -100154,7 +100589,7 @@
100154
100589
  id: '@salesforce/lds-network-adapter',
100155
100590
  instrument: instrument$2,
100156
100591
  });
100157
- // version: 1.461.0-37b1c9d1cb
100592
+ // version: 1.463.0-ea9ecd17fc
100158
100593
 
100159
100594
  const { create: create$2, keys: keys$2 } = Object;
100160
100595
  const { stringify, parse } = JSON;
@@ -107706,7 +108141,7 @@
107706
108141
  cb(graphql_v1_import, graphql_imperative$1, graphql_imperative_legacy_v1_import, graphql_state_manager, useOneStoreGraphQL);
107707
108142
  }
107708
108143
  }
107709
- // version: 1.461.0-55ae54e753
108144
+ // version: 1.463.0-8515018faf
107710
108145
 
107711
108146
  function createFragmentMap(documentNode) {
107712
108147
  const fragments = {};
@@ -136949,7 +137384,7 @@
136949
137384
  configuration: { ...configurationForGraphQLAdapters$1 },
136950
137385
  instrument: instrument$1,
136951
137386
  });
136952
- // version: 1.461.0-55ae54e753
137387
+ // version: 1.463.0-8515018faf
136953
137388
 
136954
137389
  // On core the unstable adapters are re-exported with different names,
136955
137390
  // we want to match them here.
@@ -137101,7 +137536,7 @@
137101
137536
  unstable_graphQL_imperative = createImperativeAdapter(luvio, createInstrumentedAdapter(ldsAdapter, adapterMetadata), adapterMetadata);
137102
137537
  graphQLImperative = ldsAdapter;
137103
137538
  });
137104
- // version: 1.461.0-55ae54e753
137539
+ // version: 1.463.0-8515018faf
137105
137540
 
137106
137541
  var gqlApi = /*#__PURE__*/Object.freeze({
137107
137542
  __proto__: null,
@@ -137424,19 +137859,7 @@
137424
137859
  function invokeAdapterWithDraftToReplace(adapterId, config, draftIdToReplace, onResponse, nativeAdapterRequestContext) {
137425
137860
  invokeAdapterWithDraftToMerge(adapterId, config, draftIdToReplace, onResponse, nativeAdapterRequestContext);
137426
137861
  }
137427
- /**
137428
- * Executes the specified adapter with the given @param adapterId and @param config. Then
137429
- * it merges the draft with the given id with the draft generated by
137430
- * the mutating adapter. Will call onResult callback once with data or error.
137431
- *
137432
- * This function returns an error if the given adapterId cannot be found, or if the
137433
- * adapterId is not a mutating adapter if a draft isn't created, or
137434
- * if it fails to parse the given config string.
137435
- *
137436
- * If the @param adapterId is deleteRecod, the invocation of it will generate a delele draft.
137437
- * The newly generated delete draft will replace the draft specified by @param targetDraftId
137438
- */
137439
- function invokeAdapterWithDraftToMerge(adapterId, config, targetDraftId, onResponse, nativeAdapterRequestContext) {
137862
+ function invokeAdapterWithDraftToMergeAndMetadata(adapterId, config, targetDraftId, metadata, onResponse, nativeAdapterRequestContext) {
137440
137863
  const adapter = getDMLAdapterFromName(adapterId);
137441
137864
  if (adapter === undefined) {
137442
137865
  // Check if the adapter is non-mutating adapter and create proper error message.
@@ -137452,10 +137875,12 @@
137452
137875
  // deleteRecord adapter call will generate a delete draft and
137453
137876
  // the newly generate draft will replace the target draft
137454
137877
  if (adapterId === 'deleteRecord') {
137455
- invokeAdapterWithDraftToMergeDeleteRecord(adapter, config, targetDraftId, onResponse, nativeAdapterRequestContext);
137878
+ invokeAdapterWithDraftToMergeDeleteRecord(adapter, config, targetDraftId, metadata, onResponse, nativeAdapterRequestContext);
137456
137879
  return;
137457
137880
  }
137458
- draftManager.getQueue().then((draftInfo) => {
137881
+ draftManager
137882
+ .getQueue()
137883
+ .then((draftInfo) => {
137459
137884
  const draftIds = draftInfo.items.map((draft) => draft.id);
137460
137885
  if (draftIds.includes(targetDraftId) === false) {
137461
137886
  onResponse({
@@ -137472,14 +137897,32 @@
137472
137897
  const draftId = draftIds[draftIds.length - 1];
137473
137898
  draftManager
137474
137899
  .mergeActions(targetDraftId, draftId)
137475
- .then(() => {
137900
+ .then(async () => {
137476
137901
  if (responseValue.data === undefined ||
137477
137902
  responseValue.data.drafts === undefined) {
137478
137903
  onResponse(responseValue);
137479
137904
  return;
137480
137905
  }
137481
137906
  const newResponse = removeDraftNodeActionId(draftId, responseValue);
137482
- onResponse(newResponse);
137907
+ if (metadata !== undefined) {
137908
+ const mergedState = await draftManager.getQueue();
137909
+ const mergedItem = mergedState.items.find((item) => item.id === targetDraftId);
137910
+ const mergedMetadata = mergedItem?.metadata ?? {};
137911
+ draftManager
137912
+ .setMetadata(targetDraftId, {
137913
+ ...mergedMetadata,
137914
+ ...metadata,
137915
+ })
137916
+ .then(() => {
137917
+ onResponse(newResponse);
137918
+ })
137919
+ .catch((error) => {
137920
+ onResponse(convertErrorIntoNativeFetchError(error, 'Unable to set metadata on draft'));
137921
+ });
137922
+ }
137923
+ else {
137924
+ onResponse(newResponse);
137925
+ }
137483
137926
  })
137484
137927
  .catch((error) => {
137485
137928
  onResponse(convertErrorIntoNativeFetchError(error, `Unknown error merging draft`));
@@ -137491,8 +137934,26 @@
137491
137934
  onResponse(response);
137492
137935
  }
137493
137936
  }, nativeAdapterRequestContext);
137937
+ })
137938
+ .catch((error) => {
137939
+ onResponse(convertErrorIntoNativeFetchError(error, 'Unable to get draft queue'));
137494
137940
  });
137495
137941
  }
137942
+ /**
137943
+ * Executes the specified adapter with the given @param adapterId and @param config. Then
137944
+ * it merges the draft with the given id with the draft generated by
137945
+ * the mutating adapter. Will call onResult callback once with data or error.
137946
+ *
137947
+ * This function returns an error if the given adapterId cannot be found, or if the
137948
+ * adapterId is not a mutating adapter if a draft isn't created, or
137949
+ * if it fails to parse the given config string.
137950
+ *
137951
+ * If the @param adapterId is deleteRecod, the invocation of it will generate a delele draft.
137952
+ * The newly generated delete draft will replace the draft specified by @param targetDraftId
137953
+ */
137954
+ function invokeAdapterWithDraftToMerge(adapterId, config, targetDraftId, onResponse, nativeAdapterRequestContext) {
137955
+ invokeAdapterWithDraftToMergeAndMetadata(adapterId, config, targetDraftId, undefined, onResponse, nativeAdapterRequestContext);
137956
+ }
137496
137957
  /**
137497
137958
  * Executes the specified adapter with the given adapterId and config. Then
137498
137959
  * it sets the given metadata on the draft created by the mutating adapter. Will call
@@ -137628,18 +138089,20 @@
137628
138089
  which should only be used for the deleteRecord wire adapter, since it does not
137629
138090
  contain record data in the result and has to do special querying of the draft queue
137630
138091
  */
137631
- function invokeAdapterWithDraftToMergeDeleteRecord(adapter, config, draftIdToReplace, onResponse, nativeAdapterRequestContext) {
138092
+ function invokeAdapterWithDraftToMergeDeleteRecord(adapter, config, draftIdToReplace, metadata, onResponse, nativeAdapterRequestContext) {
137632
138093
  const targetedRecordId = parse(config);
137633
138094
  let priorDraftIds;
137634
- draftManager.getQueue().then((draftState) => {
137635
- priorDraftIds = draftState.items.map((item) => {
137636
- return item.id;
137637
- });
138095
+ draftManager
138096
+ .getQueue()
138097
+ .then((draftState) => {
138098
+ priorDraftIds = draftState.items.map((item) => item.id);
137638
138099
  invokeDmlAdapter(adapter, parse(config), (responseValue) => {
137639
138100
  if (responseValue.error === undefined &&
137640
138101
  responseValue.errors === undefined &&
137641
138102
  responseValue.data === undefined) {
137642
- draftManager.getQueue().then((newState) => {
138103
+ draftManager
138104
+ .getQueue()
138105
+ .then((newState) => {
137643
138106
  const draftIdsToFilter = priorDraftIds ? priorDraftIds : [];
137644
138107
  const newDrafts = newState.items;
137645
138108
  const addedDrafts = newDrafts.filter((item) => {
@@ -137656,13 +138119,35 @@
137656
138119
  else {
137657
138120
  draftManager
137658
138121
  .replaceAction(draftIdToReplace, addedDrafts[0].id)
137659
- .then(() => {
137660
- onResponse(responseValue);
138122
+ .then((replacedDraft) => {
138123
+ if (metadata !== undefined) {
138124
+ // Spread the metadata returned by replaceAction
138125
+ // (not the pre-replace snapshot) so that keys the
138126
+ // replacement clears — notably retryCount — stay
138127
+ // cleared instead of being reintroduced here.
138128
+ draftManager
138129
+ .setMetadata(draftIdToReplace, {
138130
+ ...replacedDraft.metadata,
138131
+ ...metadata,
138132
+ })
138133
+ .then(() => {
138134
+ onResponse(responseValue);
138135
+ })
138136
+ .catch((error) => {
138137
+ onResponse(convertErrorIntoNativeFetchError(error, 'Unable to set metadata on draft'));
138138
+ });
138139
+ }
138140
+ else {
138141
+ onResponse(responseValue);
138142
+ }
137661
138143
  })
137662
138144
  .catch((error) => {
137663
138145
  onResponse(convertErrorIntoNativeFetchError(error, 'Unknown error replacing action'));
137664
138146
  });
137665
138147
  }
138148
+ })
138149
+ .catch((error) => {
138150
+ onResponse(convertErrorIntoNativeFetchError(error, 'Unable to get draft queue'));
137666
138151
  });
137667
138152
  }
137668
138153
  else {
@@ -137671,6 +138156,9 @@
137671
138156
  onResponse(response);
137672
138157
  }
137673
138158
  }, nativeAdapterRequestContext);
138159
+ })
138160
+ .catch((error) => {
138161
+ onResponse(convertErrorIntoNativeFetchError(error, 'Unable to get draft queue'));
137674
138162
  });
137675
138163
  }
137676
138164
  function removeDraftNodeActionId(targetDraftId, responseValue) {
@@ -137900,7 +138388,7 @@
137900
138388
  function register(r) {
137901
138389
  callbacks$1.forEach((callback) => callback(r));
137902
138390
  }
137903
- // version: 1.461.0-37b1c9d1cb
138391
+ // version: 1.463.0-ea9ecd17fc
137904
138392
 
137905
138393
  /**
137906
138394
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -139200,6 +139688,7 @@
139200
139688
  exports.importOneStoreAdapterModule = importOneStoreAdapterModule;
139201
139689
  exports.invokeAdapter = invokeAdapter;
139202
139690
  exports.invokeAdapterWithDraftToMerge = invokeAdapterWithDraftToMerge;
139691
+ exports.invokeAdapterWithDraftToMergeAndMetadata = invokeAdapterWithDraftToMergeAndMetadata;
139203
139692
  exports.invokeAdapterWithDraftToReplace = invokeAdapterWithDraftToReplace;
139204
139693
  exports.invokeAdapterWithMetadata = invokeAdapterWithMetadata;
139205
139694
  exports.nimbusDraftQueue = nimbusDraftQueue;
@@ -139210,4 +139699,4 @@
139210
139699
  exports.subscribeToAdapter = subscribeToAdapter;
139211
139700
 
139212
139701
  }));
139213
- // version: 1.461.0-37b1c9d1cb
139702
+ // version: 1.463.0-ea9ecd17fc