@salesforce/lds-runtime-mobile 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.
- package/dist/main.js +726 -276
- package/dist/types/drafts/records/actionHandlers/SideEffectHook.d.ts +4 -3
- package/dist/types/drafts/sideEffects/SideEffectHandler.d.ts +5 -1
- package/dist/types/drafts/sideEffects/sideEffects.d.ts +6 -1
- package/dist/types/durableStore/MemoizingRecordReader.d.ts +29 -0
- package/dist/types/sfs/ProductConsumedSideEffects.d.ts +46 -3
- package/dist/types/sfs/ProductItemSideEffects.d.ts +46 -0
- package/dist/types/sfs/ProductTransferSideEffects.d.ts +49 -0
- package/dist/types/sfs/sfsIsProduct2Serialized.d.ts +88 -0
- package/package.json +37 -37
- package/sfdc/main.js +726 -276
- package/sfdc/types/drafts/records/actionHandlers/SideEffectHook.d.ts +4 -3
- package/sfdc/types/drafts/sideEffects/SideEffectHandler.d.ts +5 -1
- package/sfdc/types/drafts/sideEffects/sideEffects.d.ts +6 -1
- package/sfdc/types/durableStore/MemoizingRecordReader.d.ts +29 -0
- package/sfdc/types/sfs/ProductConsumedSideEffects.d.ts +46 -3
- package/sfdc/types/sfs/ProductItemSideEffects.d.ts +46 -0
- package/sfdc/types/sfs/ProductTransferSideEffects.d.ts +49 -0
- package/sfdc/types/sfs/sfsIsProduct2Serialized.d.ts +88 -0
package/sfdc/main.js
CHANGED
|
@@ -43,17 +43,14 @@ import shortTimeFormat from '@salesforce/i18n/dateTime.shortTimeFormat';
|
|
|
43
43
|
import shortDateTimeFormat from '@salesforce/i18n/dateTime.shortDateTimeFormat';
|
|
44
44
|
import { getDateTimeFormat, getDateTimeISO8601Parser, getNumberFormat } from 'lightning/i18nService';
|
|
45
45
|
import formattingOptions from 'lightning/i18nCldrOptions';
|
|
46
|
-
import ldsPrimingGraphqlBatch from '@salesforce/gate/lds.primingGraphqlBatch';
|
|
47
|
-
import lmrPrimingUseSoql from '@salesforce/gate/lmr.primingUseSoql';
|
|
48
46
|
import aggressiveTrimCountKillSwitchGate from '@salesforce/gate/lmr.disableAggresiveTrimCountChange';
|
|
49
47
|
import graphqlL2AdapterGate from '@salesforce/gate/lmr.graphqlL2Adapter';
|
|
50
|
-
import useOneStore from '@salesforce/gate/lmr.useOneStore';
|
|
51
48
|
import { setServices } from 'force/luvioServiceProvisioner1';
|
|
52
49
|
import 'force/luvioTypeNormalization1';
|
|
53
50
|
import { Kind as Kind$2, visit as visit$2, print as print$1, resolveAndValidateGraphQLConfig, toGraphQLErrorResponse } from 'force/luvioOnestoreGraphqlParser';
|
|
54
51
|
import draftQueueSideEffectMapGate from '@salesforce/gate/lmr.draft-queue-sideeffect-map';
|
|
55
52
|
import productConsumedSideEffects from '@salesforce/gate/com.salesforce.fieldservice.vanStockLDSBypass256';
|
|
56
|
-
import
|
|
53
|
+
import isProduct2SerializedSideEffectKillSwitch from '@salesforce/gate/com.salesforce.fieldservice.productConsumedSideEffectsKillSwitch';
|
|
57
54
|
|
|
58
55
|
/**
|
|
59
56
|
* Copyright (c) 2022, Salesforce, Inc.,
|
|
@@ -43595,6 +43592,35 @@ async function getAdapterData(adapter, config) {
|
|
|
43595
43592
|
return snapshot.data;
|
|
43596
43593
|
}
|
|
43597
43594
|
|
|
43595
|
+
/**
|
|
43596
|
+
* A read-through cache over `DurableRecordStore.getRecord` scoped to a single draft-action
|
|
43597
|
+
* dispatch. Multiple side-effect hooks fire for one action (e.g. the van-stock and
|
|
43598
|
+
* IsProduct2Serialized ProductConsumed hooks) and are awaited one at a time, so they otherwise
|
|
43599
|
+
* each re-read the same draft record — and the IsProduct2Serialized chain re-reads the same
|
|
43600
|
+
* related ProductItem / Product2 — from the durable store. Sharing one reader across the
|
|
43601
|
+
* action's hooks collapses those repeated reads to a single durable call per key.
|
|
43602
|
+
*
|
|
43603
|
+
* The cache stores the in-flight promise (not the resolved value) so concurrent reads of the
|
|
43604
|
+
* same key share one durable call, and it caches `undefined` results so a miss is not re-fetched.
|
|
43605
|
+
* A reader must not outlive one action dispatch: durable state changes between actions, so a
|
|
43606
|
+
* longer-lived cache would return stale records.
|
|
43607
|
+
*/
|
|
43608
|
+
class MemoizingRecordReader {
|
|
43609
|
+
constructor(durableRecordStore) {
|
|
43610
|
+
this.durableRecordStore = durableRecordStore;
|
|
43611
|
+
this.cache = new Map();
|
|
43612
|
+
}
|
|
43613
|
+
getRecord(key) {
|
|
43614
|
+
const cached = this.cache.get(key);
|
|
43615
|
+
if (cached !== undefined) {
|
|
43616
|
+
return cached;
|
|
43617
|
+
}
|
|
43618
|
+
const promise = this.durableRecordStore.getRecord(key);
|
|
43619
|
+
this.cache.set(key, promise);
|
|
43620
|
+
return promise;
|
|
43621
|
+
}
|
|
43622
|
+
}
|
|
43623
|
+
|
|
43598
43624
|
const LDS_ACTION_HANDLER_ID = 'LDS_ACTION_HANDLER';
|
|
43599
43625
|
const LDS_ACTION_METADATA_API_NAME = 'LDS_ACTION_METADATA_API_NAME';
|
|
43600
43626
|
class UiApiActionHandler extends AbstractResourceRequestActionHandler {
|
|
@@ -43715,9 +43741,9 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
|
|
|
43715
43741
|
return super.getRedirectMappings(action);
|
|
43716
43742
|
}
|
|
43717
43743
|
async handleActionCompleted(completedAction, queueOperations) {
|
|
43718
|
-
|
|
43719
|
-
|
|
43720
|
-
|
|
43744
|
+
const { method } = completedAction.data;
|
|
43745
|
+
if (completedAction.response.status === 204 && (method === 'post' || method === 'patch')) {
|
|
43746
|
+
await this.recordService.removeSideEffectsForAction(completedAction);
|
|
43721
43747
|
await this.evictKey(completedAction.tag);
|
|
43722
43748
|
return;
|
|
43723
43749
|
}
|
|
@@ -43798,8 +43824,15 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
|
|
|
43798
43824
|
break;
|
|
43799
43825
|
}
|
|
43800
43826
|
}
|
|
43827
|
+
// One reader shared across every hook for this action so repeated reads of the same
|
|
43828
|
+
// record (the draft record, and any related records the hooks resolve) collapse to a
|
|
43829
|
+
// single durable call. Scoped to this dispatch only — durable state changes between
|
|
43830
|
+
// actions, so it must not be reused across them. Built only when hooks will run.
|
|
43831
|
+
const recordReader = hookTriggers.length > 0
|
|
43832
|
+
? new MemoizingRecordReader(this.durableRecordStore)
|
|
43833
|
+
: undefined;
|
|
43801
43834
|
for (const trigger of hookTriggers) {
|
|
43802
|
-
const hookEffects = await trigger.then(action.id, action.timestamp, action.targetId, action.data.body?.fields ?? {});
|
|
43835
|
+
const hookEffects = await trigger.then(action.id, action.timestamp, action.targetId, action.data.body?.fields ?? {}, recordReader);
|
|
43803
43836
|
for (const hookEffect of hookEffects) {
|
|
43804
43837
|
effects.push(hookEffect);
|
|
43805
43838
|
}
|
|
@@ -55004,211 +55037,6 @@ class NetworkRecordLoader {
|
|
|
55004
55037
|
}
|
|
55005
55038
|
}
|
|
55006
55039
|
|
|
55007
|
-
const apiPath$1 = basePath + '/graphql';
|
|
55008
|
-
const requiredPrefix = `required_`;
|
|
55009
|
-
const requiredFieldMap = {
|
|
55010
|
-
ApiName: 'ApiName',
|
|
55011
|
-
Id: 'Id',
|
|
55012
|
-
LastModifiedById: 'LastModifiedById { value }',
|
|
55013
|
-
LastModifiedDate: 'LastModifiedDate { value }',
|
|
55014
|
-
RecordTypeId: 'RecordTypeId(fallback: true) { value }',
|
|
55015
|
-
SystemModstamp: 'SystemModstamp { value }',
|
|
55016
|
-
WeakEtag: 'WeakEtag',
|
|
55017
|
-
};
|
|
55018
|
-
class RecordLoaderGraphQL extends NetworkRecordLoader {
|
|
55019
|
-
makeRequest(batch, observabilityContext) {
|
|
55020
|
-
const query = this.generateGraphQLQuery(batch.type, batch.fields);
|
|
55021
|
-
return {
|
|
55022
|
-
request: {
|
|
55023
|
-
method: 'POST',
|
|
55024
|
-
path: apiPath$1,
|
|
55025
|
-
body: JSON.stringify({
|
|
55026
|
-
query,
|
|
55027
|
-
variables: {
|
|
55028
|
-
ids: batch.ids,
|
|
55029
|
-
first: batch.ids.length,
|
|
55030
|
-
},
|
|
55031
|
-
}),
|
|
55032
|
-
headers: {},
|
|
55033
|
-
queryParams: {},
|
|
55034
|
-
priority: 'background',
|
|
55035
|
-
observabilityContext: observabilityContext !== undefined ? observabilityContext : {},
|
|
55036
|
-
},
|
|
55037
|
-
context: undefined,
|
|
55038
|
-
};
|
|
55039
|
-
}
|
|
55040
|
-
generateFetchResults(repResult, batchInput, _context) {
|
|
55041
|
-
const { data, errors } = repResult;
|
|
55042
|
-
if (errors !== undefined && errors.length > 0) {
|
|
55043
|
-
// right now if there are any errors in the batch we throw out the entire batch
|
|
55044
|
-
// for now this is ok all errors will originate on the same node so there shouldn't be a mix of errors and data
|
|
55045
|
-
return [
|
|
55046
|
-
{
|
|
55047
|
-
missingIds: batchInput.ids,
|
|
55048
|
-
ok: false,
|
|
55049
|
-
error: 'request-error',
|
|
55050
|
-
messages: errors.map((x) => x.message),
|
|
55051
|
-
},
|
|
55052
|
-
];
|
|
55053
|
-
}
|
|
55054
|
-
// this shouldn't really happen if there's no errors
|
|
55055
|
-
if (!data || !data.uiapi) {
|
|
55056
|
-
return [
|
|
55057
|
-
{
|
|
55058
|
-
ok: false,
|
|
55059
|
-
error: 'unknown',
|
|
55060
|
-
messages: ['unexpected response retrieved from graphql endpoint'],
|
|
55061
|
-
missingIds: batchInput.ids,
|
|
55062
|
-
},
|
|
55063
|
-
];
|
|
55064
|
-
}
|
|
55065
|
-
const objectInfo = batchInput.objectInfo;
|
|
55066
|
-
const seenRecords = new Set(batchInput.ids);
|
|
55067
|
-
const records = data.uiapi.query[batchInput.type].edges.map((edge) => {
|
|
55068
|
-
const record = this.generateDurableRecordRepresentation(edge.node, objectInfo);
|
|
55069
|
-
seenRecords.delete(record.id);
|
|
55070
|
-
return record;
|
|
55071
|
-
});
|
|
55072
|
-
return [
|
|
55073
|
-
{
|
|
55074
|
-
ok: true,
|
|
55075
|
-
records,
|
|
55076
|
-
missingIds: Array.from(seenRecords),
|
|
55077
|
-
},
|
|
55078
|
-
];
|
|
55079
|
-
}
|
|
55080
|
-
generateGraphQLQuery(type, fields) {
|
|
55081
|
-
const fieldList = Object.keys(requiredFieldMap)
|
|
55082
|
-
.map((field) => {
|
|
55083
|
-
return `${requiredPrefix}${field}: ${requiredFieldMap[field]}`;
|
|
55084
|
-
})
|
|
55085
|
-
.join('\n');
|
|
55086
|
-
return /* GraphQL */ `
|
|
55087
|
-
fragment requiredRecordFields on Record {
|
|
55088
|
-
${fieldList}
|
|
55089
|
-
}
|
|
55090
|
-
query prime${type}($ids: [ID], $first: Int){
|
|
55091
|
-
uiapi {
|
|
55092
|
-
query {
|
|
55093
|
-
${type} (where: {Id: {in: $ids}} first: $first) {
|
|
55094
|
-
edges {
|
|
55095
|
-
node {
|
|
55096
|
-
...requiredRecordFields
|
|
55097
|
-
${fields
|
|
55098
|
-
.filter((x) => x !== 'Id' && x !== 'RecordTypeId')
|
|
55099
|
-
.map((field) => `${field} { value displayValue }`)}
|
|
55100
|
-
}
|
|
55101
|
-
}
|
|
55102
|
-
}
|
|
55103
|
-
}
|
|
55104
|
-
}
|
|
55105
|
-
}
|
|
55106
|
-
`;
|
|
55107
|
-
}
|
|
55108
|
-
generateDurableRecordRepresentation(node, objectInfo) {
|
|
55109
|
-
const standardRecordFields = new Set(Object.keys(requiredFieldMap).map((x) => `${requiredPrefix}${x}`));
|
|
55110
|
-
const id = node[`${requiredPrefix}Id`];
|
|
55111
|
-
const recordTypeId = node[`${requiredPrefix}RecordTypeId`].value;
|
|
55112
|
-
const requestedFields = Object.keys(node).filter((field) => !standardRecordFields.has(field));
|
|
55113
|
-
const fields = requestedFields.reduce((acc, field) => {
|
|
55114
|
-
const fieldNode = node[field];
|
|
55115
|
-
if (fieldNode.displayValue === undefined) {
|
|
55116
|
-
fieldNode.displayValue = null;
|
|
55117
|
-
}
|
|
55118
|
-
acc[field] = node[field];
|
|
55119
|
-
return acc;
|
|
55120
|
-
}, {});
|
|
55121
|
-
fields['Id'] = { value: id, displayValue: null };
|
|
55122
|
-
if (objectInfo.fields['RecordTypeId'] !== undefined) {
|
|
55123
|
-
fields['RecordTypeId'] = { value: recordTypeId, displayValue: null };
|
|
55124
|
-
}
|
|
55125
|
-
let recordTypeInfo = null;
|
|
55126
|
-
if (recordTypeId !== null &&
|
|
55127
|
-
objectInfo.recordTypeInfos &&
|
|
55128
|
-
objectInfo.recordTypeInfos[recordTypeId]) {
|
|
55129
|
-
const appliedRecordTypeInfo = objectInfo.recordTypeInfos[recordTypeId];
|
|
55130
|
-
// ui-api only applies the record type if its not the default type, otherwise it sets it to null
|
|
55131
|
-
if (appliedRecordTypeInfo.defaultRecordTypeMapping === false) {
|
|
55132
|
-
recordTypeInfo = appliedRecordTypeInfo;
|
|
55133
|
-
}
|
|
55134
|
-
}
|
|
55135
|
-
return {
|
|
55136
|
-
apiName: node[`${requiredPrefix}ApiName`],
|
|
55137
|
-
childRelationships: {},
|
|
55138
|
-
eTag: '',
|
|
55139
|
-
id,
|
|
55140
|
-
lastModifiedById: node[`${requiredPrefix}LastModifiedById`] &&
|
|
55141
|
-
node[`${requiredPrefix}LastModifiedById`].value
|
|
55142
|
-
? node[`${requiredPrefix}LastModifiedById`].value
|
|
55143
|
-
: null,
|
|
55144
|
-
lastModifiedDate: node[`${requiredPrefix}LastModifiedDate`] &&
|
|
55145
|
-
node[`${requiredPrefix}LastModifiedDate`].value
|
|
55146
|
-
? node[`${requiredPrefix}LastModifiedDate`].value
|
|
55147
|
-
: null,
|
|
55148
|
-
recordTypeId,
|
|
55149
|
-
recordTypeInfo: recordTypeInfo,
|
|
55150
|
-
systemModstamp: node[`${requiredPrefix}SystemModstamp`] &&
|
|
55151
|
-
node[`${requiredPrefix}SystemModstamp`].value
|
|
55152
|
-
? node[`${requiredPrefix}SystemModstamp`].value
|
|
55153
|
-
: null,
|
|
55154
|
-
weakEtag: node[`${requiredPrefix}WeakEtag`],
|
|
55155
|
-
fields,
|
|
55156
|
-
links: {},
|
|
55157
|
-
};
|
|
55158
|
-
}
|
|
55159
|
-
}
|
|
55160
|
-
|
|
55161
|
-
const DEFAULT_GQL_QUERY_BATCH_SIZE = 100;
|
|
55162
|
-
const apiPath = basePath + '/graphql/batch';
|
|
55163
|
-
class RecordLoaderGraphQLBatch extends RecordLoaderGraphQL {
|
|
55164
|
-
makeRequest(batch, observabilityContext) {
|
|
55165
|
-
const query = this.generateGraphQLQuery(batch.type, batch.fields);
|
|
55166
|
-
const chunks = chunk(batch.ids, DEFAULT_GQL_QUERY_BATCH_SIZE);
|
|
55167
|
-
const batches = chunks.map((chunk) => {
|
|
55168
|
-
return {
|
|
55169
|
-
query,
|
|
55170
|
-
variables: {
|
|
55171
|
-
ids: chunk,
|
|
55172
|
-
first: chunk.length,
|
|
55173
|
-
},
|
|
55174
|
-
};
|
|
55175
|
-
});
|
|
55176
|
-
return {
|
|
55177
|
-
request: {
|
|
55178
|
-
method: 'POST',
|
|
55179
|
-
path: apiPath,
|
|
55180
|
-
body: JSON.stringify({
|
|
55181
|
-
batchQuery: batches,
|
|
55182
|
-
}),
|
|
55183
|
-
headers: {},
|
|
55184
|
-
queryParams: {},
|
|
55185
|
-
priority: 'background',
|
|
55186
|
-
observabilityContext: observabilityContext !== undefined ? observabilityContext : {},
|
|
55187
|
-
},
|
|
55188
|
-
context: chunks,
|
|
55189
|
-
};
|
|
55190
|
-
}
|
|
55191
|
-
generateFetchResults(body, batchInput, context) {
|
|
55192
|
-
const { results } = body;
|
|
55193
|
-
const fetchResults = context.flatMap((chunk, index) => {
|
|
55194
|
-
const result = results[index];
|
|
55195
|
-
if (!result || result.statusCode !== 200) {
|
|
55196
|
-
return {
|
|
55197
|
-
ok: false,
|
|
55198
|
-
error: 'request-error',
|
|
55199
|
-
messages: ['unexpected response from graphql batch endpoint'],
|
|
55200
|
-
missingIds: chunk,
|
|
55201
|
-
};
|
|
55202
|
-
}
|
|
55203
|
-
return super.generateFetchResults(result.result, {
|
|
55204
|
-
...batchInput,
|
|
55205
|
-
ids: chunk,
|
|
55206
|
-
}, undefined);
|
|
55207
|
-
});
|
|
55208
|
-
return fetchResults;
|
|
55209
|
-
}
|
|
55210
|
-
}
|
|
55211
|
-
|
|
55212
55040
|
class SoqlRecordConverter {
|
|
55213
55041
|
/**
|
|
55214
55042
|
*
|
|
@@ -55701,16 +55529,7 @@ class LdsPrimingRecordRefresher {
|
|
|
55701
55529
|
function primingSessionFactory(config) {
|
|
55702
55530
|
const { store, objectInfoService, getLuvio } = config;
|
|
55703
55531
|
const networkAdapter = new NimbusPrimingNetworkAdapter();
|
|
55704
|
-
|
|
55705
|
-
if (lmrPrimingUseSoql.isOpen({ fallback: false })) {
|
|
55706
|
-
recordLoader = new RecordLoaderSOQLComposite(networkAdapter);
|
|
55707
|
-
}
|
|
55708
|
-
else if (ldsPrimingGraphqlBatch.isOpen({ fallback: false })) {
|
|
55709
|
-
recordLoader = new RecordLoaderGraphQLBatch(networkAdapter);
|
|
55710
|
-
}
|
|
55711
|
-
else {
|
|
55712
|
-
recordLoader = new RecordLoaderGraphQL(networkAdapter);
|
|
55713
|
-
}
|
|
55532
|
+
const recordLoader = new RecordLoaderSOQLComposite(networkAdapter);
|
|
55714
55533
|
const primingStore = new SqlitePrimingStore(getLuvio, store);
|
|
55715
55534
|
const recordIngestor = new RecordIngestor(primingStore, getLuvio);
|
|
55716
55535
|
const session = new PrimingSession({
|
|
@@ -56384,6 +56203,38 @@ function buildCachedSnapshotCachePolicy(context, storeLookup) {
|
|
|
56384
56203
|
}
|
|
56385
56204
|
|
|
56386
56205
|
const SIDE_EFFECT_SEGMENT = 'RECORD_SIDE_EFFECTS';
|
|
56206
|
+
// Base-record effects rebuild (or remove) the entire record for a key, so they must be
|
|
56207
|
+
// applied before any field-level effect on that same key — otherwise a field effect that
|
|
56208
|
+
// runs first is discarded when the base record is (re)built. keyToEffectMap ordering is not
|
|
56209
|
+
// stable: it is re-materialized from the durable store on reload / onChanged replay, where
|
|
56210
|
+
// entry order is not guaranteed. So two field-set effects on the same key from different
|
|
56211
|
+
// actions (e.g. two queued edits of one field) can come back in either order; applied out of
|
|
56212
|
+
// order the OLDER value wins and the newer edit is lost. Effects must therefore be applied in
|
|
56213
|
+
// action chronology (by timestamp), with base-record ops ordered ahead of field ops only as
|
|
56214
|
+
// the tie-breaker within one action — create/field effects on a draft-created record share a
|
|
56215
|
+
// key AND a timestamp, so timestamp alone can't order those.
|
|
56216
|
+
const BASE_RECORD_EFFECT_TYPES = new Set(['record-create', 'record-update', 'record-delete']);
|
|
56217
|
+
function orderEffectsForApply(effects) {
|
|
56218
|
+
// Nothing to reorder for a single effect; return the original array to avoid allocating on
|
|
56219
|
+
// the hot read-out path.
|
|
56220
|
+
if (effects.length <= 1) {
|
|
56221
|
+
return effects;
|
|
56222
|
+
}
|
|
56223
|
+
// Primary key: timestamp (action chronology). Secondary key: base-record ops before field
|
|
56224
|
+
// ops, which only matters within one action's shared timestamp. Array.prototype.sort is
|
|
56225
|
+
// stable (ES2019), so effects that tie on both keys keep their existing relative order.
|
|
56226
|
+
return [...effects].sort((a, b) => {
|
|
56227
|
+
if (a.timestamp !== b.timestamp) {
|
|
56228
|
+
return a.timestamp - b.timestamp;
|
|
56229
|
+
}
|
|
56230
|
+
const aIsBase = BASE_RECORD_EFFECT_TYPES.has(a.type);
|
|
56231
|
+
const bIsBase = BASE_RECORD_EFFECT_TYPES.has(b.type);
|
|
56232
|
+
if (aIsBase === bIsBase) {
|
|
56233
|
+
return 0;
|
|
56234
|
+
}
|
|
56235
|
+
return aIsBase ? -1 : 1;
|
|
56236
|
+
});
|
|
56237
|
+
}
|
|
56387
56238
|
class SideEffectStore {
|
|
56388
56239
|
constructor(durableStore) {
|
|
56389
56240
|
this.durableStore = durableStore;
|
|
@@ -56462,7 +56313,7 @@ class SideEffectStore {
|
|
|
56462
56313
|
if (effects === undefined) {
|
|
56463
56314
|
return [];
|
|
56464
56315
|
}
|
|
56465
|
-
return effects;
|
|
56316
|
+
return orderEffectsForApply(effects);
|
|
56466
56317
|
}
|
|
56467
56318
|
getEffectsByTag(tag) {
|
|
56468
56319
|
// This scan is still O(N) over every effect regardless of the incremental-map
|
|
@@ -56959,6 +56810,58 @@ class RecordFieldIncrementHandler {
|
|
|
56959
56810
|
return record;
|
|
56960
56811
|
}
|
|
56961
56812
|
}
|
|
56813
|
+
class RecordFieldSetHandler {
|
|
56814
|
+
constructor() {
|
|
56815
|
+
this.logger = typeof __nimbus !== 'undefined' &&
|
|
56816
|
+
__nimbus.plugins !== undefined &&
|
|
56817
|
+
__nimbus.plugins.JSLoggerPlugin !== undefined
|
|
56818
|
+
? __nimbus.plugins.JSLoggerPlugin
|
|
56819
|
+
: undefined;
|
|
56820
|
+
}
|
|
56821
|
+
apply(record, effect) {
|
|
56822
|
+
if (record === undefined) {
|
|
56823
|
+
if (this.logger) {
|
|
56824
|
+
this.logger.logWarning('RecordFieldSetHandler - cannot apply set side effect to non existing record');
|
|
56825
|
+
}
|
|
56826
|
+
return undefined;
|
|
56827
|
+
}
|
|
56828
|
+
// fieldName is an unconstrained string that has round-tripped through the durable
|
|
56829
|
+
// store, so a value like `__proto__` would hit the prototype setter on the plain
|
|
56830
|
+
// `fields`/`serverValues` objects below rather than creating an own field (prototype
|
|
56831
|
+
// pollution). Reject dangerous keys before any dynamic read/write and skip the effect.
|
|
56832
|
+
if (isUnsafeFieldName(effect.fieldName)) {
|
|
56833
|
+
if (this.logger) {
|
|
56834
|
+
this.logger.logError(`RecordFieldSetHandler - unsafe field name ${effect.fieldName}, skip applying side effect`);
|
|
56835
|
+
}
|
|
56836
|
+
return record;
|
|
56837
|
+
}
|
|
56838
|
+
applyDraftNode(record, effect);
|
|
56839
|
+
record.drafts.edited = true;
|
|
56840
|
+
// capture the server value so it can be restored when the draft syncs.
|
|
56841
|
+
// unlike the increment handler, the field may not yet exist on the record
|
|
56842
|
+
// (e.g. IsProduct2Serialized is not part of the draft-created record), in
|
|
56843
|
+
// which case we record `null` to indicate we never had the field.
|
|
56844
|
+
if (record.drafts.created === false) {
|
|
56845
|
+
const serverValue = record.drafts.serverValues[effect.fieldName];
|
|
56846
|
+
if (serverValue === undefined) {
|
|
56847
|
+
const existingField = record.fields[effect.fieldName];
|
|
56848
|
+
record.drafts.serverValues[effect.fieldName] =
|
|
56849
|
+
existingField === undefined ? null : existingField;
|
|
56850
|
+
}
|
|
56851
|
+
}
|
|
56852
|
+
record.fields[effect.fieldName] = {
|
|
56853
|
+
value: effect.value,
|
|
56854
|
+
displayValue: null,
|
|
56855
|
+
};
|
|
56856
|
+
return record;
|
|
56857
|
+
}
|
|
56858
|
+
}
|
|
56859
|
+
// Field names that would target the prototype chain rather than an own property when used
|
|
56860
|
+
// to index a plain object, enabling prototype pollution on the record's field containers.
|
|
56861
|
+
const UNSAFE_FIELD_NAMES = new Set(['__proto__', 'constructor', 'prototype']);
|
|
56862
|
+
function isUnsafeFieldName(fieldName) {
|
|
56863
|
+
return UNSAFE_FIELD_NAMES.has(fieldName);
|
|
56864
|
+
}
|
|
56962
56865
|
function applyDraftNode(record, effect) {
|
|
56963
56866
|
if (record.drafts === undefined) {
|
|
56964
56867
|
record.drafts = {
|
|
@@ -56993,6 +56896,7 @@ const handlerMap = {
|
|
|
56993
56896
|
'record-update': new UpdateRecordEffectHandler(),
|
|
56994
56897
|
'record-delete': new DeleteRecordEffectHandler(),
|
|
56995
56898
|
'record-field-increment': new RecordFieldIncrementHandler(),
|
|
56899
|
+
'record-field-set': new RecordFieldSetHandler(),
|
|
56996
56900
|
};
|
|
56997
56901
|
/**
|
|
56998
56902
|
* Removes any existing effects on a record and re-applies the given ordered list and returns the record
|
|
@@ -57157,14 +57061,181 @@ function isStoreRecordError(storeRecord) {
|
|
|
57157
57061
|
}
|
|
57158
57062
|
|
|
57159
57063
|
const instrumentation = getInstrumentation(O11Y_NAMESPACE_LDS_MOBILE);
|
|
57064
|
+
const ProductItem$2 = {
|
|
57065
|
+
Fields: {
|
|
57066
|
+
PRODUCT2_ID: 'Product2Id',
|
|
57067
|
+
IS_PRODUCT2_SERIALIZED: 'IsProduct2Serialized',
|
|
57068
|
+
},
|
|
57069
|
+
};
|
|
57070
|
+
const Product2 = {
|
|
57071
|
+
Fields: {
|
|
57072
|
+
IS_SERIALIZED: 'IsSerialized',
|
|
57073
|
+
},
|
|
57074
|
+
};
|
|
57075
|
+
var CounterResult;
|
|
57076
|
+
(function (CounterResult) {
|
|
57077
|
+
// A side effect was created.
|
|
57078
|
+
CounterResult["Created"] = "Created";
|
|
57079
|
+
// The hook exited early.
|
|
57080
|
+
CounterResult["Exited"] = "Exited";
|
|
57081
|
+
// An unexpected error occurred.
|
|
57082
|
+
CounterResult["Error"] = "Error";
|
|
57083
|
+
})(CounterResult || (CounterResult = {}));
|
|
57084
|
+
function incrementCounter(name, result) {
|
|
57085
|
+
instrumentation.incrementCounter('SFSSideEffect', 1, result === CounterResult.Error, {
|
|
57086
|
+
Name: name,
|
|
57087
|
+
Result: result,
|
|
57088
|
+
});
|
|
57089
|
+
}
|
|
57090
|
+
/**
|
|
57091
|
+
* Reads a string-valued field off a draft-create `fields` map (the raw create body, where
|
|
57092
|
+
* values are the primitives themselves rather than `{ value }` wrappers). Returns `undefined`
|
|
57093
|
+
* when the field is absent or not a string.
|
|
57094
|
+
*/
|
|
57095
|
+
function getStringDraftField(fields, fieldName) {
|
|
57096
|
+
const field = fields[fieldName];
|
|
57097
|
+
if (field === undefined || typeof field !== 'string') {
|
|
57098
|
+
return undefined;
|
|
57099
|
+
}
|
|
57100
|
+
return field;
|
|
57101
|
+
}
|
|
57102
|
+
/**
|
|
57103
|
+
* Reads a string-valued field off a durable record's `fields` map (where each value is a
|
|
57104
|
+
* `{ value }` wrapper). Returns `undefined` when the field is absent, null, or not a string.
|
|
57105
|
+
*/
|
|
57106
|
+
function getStringRecordField(fields, fieldName) {
|
|
57107
|
+
const field = fields[fieldName];
|
|
57108
|
+
if (field === undefined || field.value === undefined || typeof field.value !== 'string') {
|
|
57109
|
+
return undefined;
|
|
57110
|
+
}
|
|
57111
|
+
return field.value;
|
|
57112
|
+
}
|
|
57113
|
+
/**
|
|
57114
|
+
* Resolves the reference id that points at a related record for a draft edit.
|
|
57115
|
+
*
|
|
57116
|
+
* On a `patch` the body only carries the fields being changed, so the link field may be
|
|
57117
|
+
* absent, present with a new id, or present but explicitly `null` (the link is being cleared).
|
|
57118
|
+
* These three cases are distinguished via `hasOwnProperty` because they demand different
|
|
57119
|
+
* behavior — falling back to the original durable-store id on an explicit `null` would resolve
|
|
57120
|
+
* `IsProduct2Serialized` from a relationship the record no longer has:
|
|
57121
|
+
*
|
|
57122
|
+
* - present + string -> `resolved` with the reassigned id.
|
|
57123
|
+
* - present + null -> `cleared`.
|
|
57124
|
+
* - absent -> fall back to the original record; `resolved` when it carries a usable
|
|
57125
|
+
* string, otherwise `unresolved`.
|
|
57126
|
+
*/
|
|
57127
|
+
function resolveReferenceIdForUpdate(patchFields, originalFields, fieldName) {
|
|
57128
|
+
if (Object.prototype.hasOwnProperty.call(patchFields, fieldName)) {
|
|
57129
|
+
const patchValue = patchFields[fieldName];
|
|
57130
|
+
if (patchValue === null) {
|
|
57131
|
+
return { kind: 'cleared' };
|
|
57132
|
+
}
|
|
57133
|
+
const patchId = getStringDraftField(patchFields, fieldName);
|
|
57134
|
+
if (patchId !== undefined) {
|
|
57135
|
+
return { kind: 'resolved', id: patchId, source: 'patch' };
|
|
57136
|
+
}
|
|
57137
|
+
// Present but neither a string nor null (unexpected shape): treat as an absent link
|
|
57138
|
+
// and fall through to the original record rather than guessing.
|
|
57139
|
+
}
|
|
57140
|
+
const originalId = getStringRecordField(originalFields, fieldName);
|
|
57141
|
+
if (originalId !== undefined) {
|
|
57142
|
+
return { kind: 'resolved', id: originalId, source: 'original' };
|
|
57143
|
+
}
|
|
57144
|
+
return { kind: 'unresolved' };
|
|
57145
|
+
}
|
|
57146
|
+
/**
|
|
57147
|
+
* Resolves the server-derived `IsProduct2Serialized` boolean for a draft-created record by
|
|
57148
|
+
* following the related `ProductItem`:
|
|
57149
|
+
*
|
|
57150
|
+
* 1. If the cached `ProductItem` already carries `IsProduct2Serialized`, use it directly
|
|
57151
|
+
* (it mirrors the linked product's serialization, and avoids a second durable read).
|
|
57152
|
+
* 2. Otherwise fall back to the linked `Product2`:
|
|
57153
|
+
* `ProductItem.Product2Id` -> `Product2.IsSerialized`.
|
|
57154
|
+
*
|
|
57155
|
+
* All lookups go through the given `RecordReader` (the durable store, or a per-action memoizer
|
|
57156
|
+
* that dedups reads). Returns `undefined` when a link in the chain is missing so the caller can
|
|
57157
|
+
* exit without an effect and leave the field synthesized (null). Callers pass their own
|
|
57158
|
+
* `counterName` so instrumentation is attributed to the owning hook.
|
|
57159
|
+
*/
|
|
57160
|
+
async function resolveIsProduct2SerializedFromProductItem(recordReader, productItemId, counterName) {
|
|
57161
|
+
const productItemRecord = await recordReader.getRecord(buildRecordRepKeyFromId(productItemId));
|
|
57162
|
+
if (productItemRecord === undefined) {
|
|
57163
|
+
incrementCounter(counterName, CounterResult.Exited);
|
|
57164
|
+
return undefined;
|
|
57165
|
+
}
|
|
57166
|
+
// Prefer IsProduct2Serialized straight off the ProductItem when it is cached there;
|
|
57167
|
+
// it saves a second durable read. Fall back to the linked Product2 otherwise.
|
|
57168
|
+
const fromProductItem = getIsProduct2SerializedFromRecordFields(productItemRecord.fields);
|
|
57169
|
+
if (fromProductItem !== undefined) {
|
|
57170
|
+
incrementCounter(counterName, CounterResult.Created);
|
|
57171
|
+
return fromProductItem;
|
|
57172
|
+
}
|
|
57173
|
+
const product2Id = getProduct2IdFromRecordFields(productItemRecord.fields);
|
|
57174
|
+
if (product2Id === undefined) {
|
|
57175
|
+
incrementCounter(counterName, CounterResult.Exited);
|
|
57176
|
+
return undefined;
|
|
57177
|
+
}
|
|
57178
|
+
return resolveIsSerializedFromProduct2Id(recordReader, product2Id, counterName);
|
|
57179
|
+
}
|
|
57180
|
+
/**
|
|
57181
|
+
* Resolves the `Product2.IsSerialized` boolean for a given `Product2Id` by reading the linked
|
|
57182
|
+
* `Product2` record through the given `RecordReader`. Used directly by the `ProductItem` hooks
|
|
57183
|
+
* (which carry `Product2Id` themselves) and as the fallback tail of
|
|
57184
|
+
* `resolveIsProduct2SerializedFromProductItem`.
|
|
57185
|
+
*
|
|
57186
|
+
* Returns `undefined` when the `Product2` record is missing so the caller can exit without an
|
|
57187
|
+
* effect and leave the field synthesized (null). Callers pass their own `counterName` so
|
|
57188
|
+
* instrumentation is attributed to the owning hook.
|
|
57189
|
+
*/
|
|
57190
|
+
async function resolveIsSerializedFromProduct2Id(recordReader, product2Id, counterName) {
|
|
57191
|
+
const product2Record = await recordReader.getRecord(buildRecordRepKeyFromId(product2Id));
|
|
57192
|
+
if (product2Record === undefined) {
|
|
57193
|
+
incrementCounter(counterName, CounterResult.Exited);
|
|
57194
|
+
return undefined;
|
|
57195
|
+
}
|
|
57196
|
+
const isSerialized = getIsSerializedFromRecordFields(product2Record.fields);
|
|
57197
|
+
if (isSerialized === undefined) {
|
|
57198
|
+
incrementCounter(counterName, CounterResult.Error);
|
|
57199
|
+
// Keep the message a stable literal and append the variable parts as key=value
|
|
57200
|
+
// pairs, so the log line stays low-cardinality and greppable in Splunk (the raw
|
|
57201
|
+
// record id is a discrete field rather than baked into the message text).
|
|
57202
|
+
instrumentation.error(`SFS - Product2 IsSerialized not found: hook=${counterName}, product2Id=${product2Id}`);
|
|
57203
|
+
return undefined;
|
|
57204
|
+
}
|
|
57205
|
+
incrementCounter(counterName, CounterResult.Created);
|
|
57206
|
+
return isSerialized;
|
|
57207
|
+
}
|
|
57208
|
+
function getProduct2IdFromRecordFields(fields) {
|
|
57209
|
+
const field = fields[ProductItem$2.Fields.PRODUCT2_ID];
|
|
57210
|
+
if (field === undefined || field.value === undefined || typeof field.value !== 'string') {
|
|
57211
|
+
return undefined;
|
|
57212
|
+
}
|
|
57213
|
+
return field.value;
|
|
57214
|
+
}
|
|
57215
|
+
function getIsProduct2SerializedFromRecordFields(fields) {
|
|
57216
|
+
const field = fields[ProductItem$2.Fields.IS_PRODUCT2_SERIALIZED];
|
|
57217
|
+
if (field === undefined || field.value === undefined || typeof field.value !== 'boolean') {
|
|
57218
|
+
return undefined;
|
|
57219
|
+
}
|
|
57220
|
+
return field.value;
|
|
57221
|
+
}
|
|
57222
|
+
function getIsSerializedFromRecordFields(fields) {
|
|
57223
|
+
const field = fields[Product2.Fields.IS_SERIALIZED];
|
|
57224
|
+
if (field === undefined || field.value === undefined || typeof field.value !== 'boolean') {
|
|
57225
|
+
return undefined;
|
|
57226
|
+
}
|
|
57227
|
+
return field.value;
|
|
57228
|
+
}
|
|
57229
|
+
|
|
57160
57230
|
const ProductConsumed = {
|
|
57161
57231
|
API_NAME: 'ProductConsumed',
|
|
57162
57232
|
Fields: {
|
|
57163
57233
|
PRODUCT_ITEM_ID: 'ProductItemId',
|
|
57164
57234
|
QUANTITY_CONSUMED: 'QuantityConsumed',
|
|
57235
|
+
IS_PRODUCT2_SERIALIZED: 'IsProduct2Serialized',
|
|
57165
57236
|
},
|
|
57166
57237
|
};
|
|
57167
|
-
const ProductItem = {
|
|
57238
|
+
const ProductItem$1 = {
|
|
57168
57239
|
Fields: {
|
|
57169
57240
|
QUANTITY_ON_HAND: 'QuantityOnHand',
|
|
57170
57241
|
},
|
|
@@ -57175,7 +57246,7 @@ class ProductConsumedCreatedHook {
|
|
|
57175
57246
|
this.apiName = ProductConsumed.API_NAME;
|
|
57176
57247
|
}
|
|
57177
57248
|
async then(draftActionId, draftActionTimestamp, _draftRecordId, fields) {
|
|
57178
|
-
const productItemId =
|
|
57249
|
+
const productItemId = getStringDraftField(fields, ProductConsumed.Fields.PRODUCT_ITEM_ID);
|
|
57179
57250
|
const quantityConsumed = getQuantityConsumed(fields);
|
|
57180
57251
|
if (productItemId === undefined) {
|
|
57181
57252
|
incrementCounter(ProductConsumedCreatedHook.COUNTER_NAME, CounterResult.Exited);
|
|
@@ -57188,7 +57259,7 @@ class ProductConsumedCreatedHook {
|
|
|
57188
57259
|
incrementCounter(ProductConsumedCreatedHook.COUNTER_NAME, CounterResult.Created);
|
|
57189
57260
|
const effect = {
|
|
57190
57261
|
type: 'record-field-increment',
|
|
57191
|
-
fieldName: ProductItem.Fields.QUANTITY_ON_HAND,
|
|
57262
|
+
fieldName: ProductItem$1.Fields.QUANTITY_ON_HAND,
|
|
57192
57263
|
value: -quantityConsumed,
|
|
57193
57264
|
key: buildRecordRepKeyFromId(productItemId),
|
|
57194
57265
|
tag: draftActionId,
|
|
@@ -57205,16 +57276,17 @@ class ProductConsumedUpdatedHook {
|
|
|
57205
57276
|
this.on = 'patch';
|
|
57206
57277
|
this.apiName = ProductConsumed.API_NAME;
|
|
57207
57278
|
}
|
|
57208
|
-
async then(draftActionId, draftActionTimestamp, draftRecordId, fields) {
|
|
57279
|
+
async then(draftActionId, draftActionTimestamp, draftRecordId, fields, reader) {
|
|
57209
57280
|
const quantityConsumed = getQuantityConsumed(fields);
|
|
57210
57281
|
if (quantityConsumed === undefined) {
|
|
57211
57282
|
incrementCounter(ProductConsumedUpdatedHook.COUNTER_NAME, CounterResult.Exited);
|
|
57212
57283
|
return [];
|
|
57213
57284
|
}
|
|
57214
|
-
const
|
|
57285
|
+
const recordReader = reader ?? this.durableRecordStore;
|
|
57286
|
+
const originalRecord = await recordReader.getRecord(buildRecordRepKeyFromId(draftRecordId));
|
|
57215
57287
|
if (originalRecord === undefined) {
|
|
57216
57288
|
incrementCounter(ProductConsumedUpdatedHook.COUNTER_NAME, CounterResult.Error);
|
|
57217
|
-
instrumentation.error(`SFS - ProductConsumedUpdatedHook
|
|
57289
|
+
instrumentation.error(`SFS - ProductConsumedUpdatedHook original record not found: draftRecordId=${draftRecordId}`);
|
|
57218
57290
|
return [];
|
|
57219
57291
|
}
|
|
57220
57292
|
const originalProductItemId = getProductItemIdFromRecordFields(originalRecord.fields);
|
|
@@ -57225,13 +57297,13 @@ class ProductConsumedUpdatedHook {
|
|
|
57225
57297
|
}
|
|
57226
57298
|
if (originalQuantityConsumed === undefined) {
|
|
57227
57299
|
incrementCounter(ProductConsumedUpdatedHook.COUNTER_NAME, CounterResult.Error);
|
|
57228
|
-
instrumentation.error(`SFS - ProductConsumedUpdatedHook
|
|
57300
|
+
instrumentation.error(`SFS - ProductConsumedUpdatedHook original record QuantityConsumed not found: draftRecordId=${draftRecordId}`);
|
|
57229
57301
|
return [];
|
|
57230
57302
|
}
|
|
57231
57303
|
incrementCounter(ProductConsumedUpdatedHook.COUNTER_NAME, CounterResult.Created);
|
|
57232
57304
|
const effect = {
|
|
57233
57305
|
type: 'record-field-increment',
|
|
57234
|
-
fieldName: ProductItem.Fields.QUANTITY_ON_HAND,
|
|
57306
|
+
fieldName: ProductItem$1.Fields.QUANTITY_ON_HAND,
|
|
57235
57307
|
value: originalQuantityConsumed - quantityConsumed,
|
|
57236
57308
|
key: buildRecordRepKeyFromId(originalProductItemId),
|
|
57237
57309
|
tag: draftActionId,
|
|
@@ -57248,11 +57320,12 @@ class ProductConsumedDeletedHook {
|
|
|
57248
57320
|
this.on = 'delete';
|
|
57249
57321
|
this.apiName = ProductConsumed.API_NAME;
|
|
57250
57322
|
}
|
|
57251
|
-
async then(draftActionId, draftActionTimestamp, draftRecordId) {
|
|
57252
|
-
const
|
|
57323
|
+
async then(draftActionId, draftActionTimestamp, draftRecordId, _fields, reader) {
|
|
57324
|
+
const recordReader = reader ?? this.durableRecordStore;
|
|
57325
|
+
const originalRecord = await recordReader.getRecord(buildRecordRepKeyFromId(draftRecordId));
|
|
57253
57326
|
if (originalRecord === undefined) {
|
|
57254
57327
|
incrementCounter(ProductConsumedDeletedHook.COUNTER_NAME, CounterResult.Error);
|
|
57255
|
-
instrumentation.error(`SFS - ProductConsumedDeletedHook
|
|
57328
|
+
instrumentation.error(`SFS - ProductConsumedDeletedHook original record not found: draftRecordId=${draftRecordId}`);
|
|
57256
57329
|
return [];
|
|
57257
57330
|
}
|
|
57258
57331
|
const productItemId = getProductItemIdFromRecordFields(originalRecord.fields);
|
|
@@ -57263,13 +57336,13 @@ class ProductConsumedDeletedHook {
|
|
|
57263
57336
|
}
|
|
57264
57337
|
if (quantityConsumed === undefined) {
|
|
57265
57338
|
incrementCounter(ProductConsumedDeletedHook.COUNTER_NAME, CounterResult.Error);
|
|
57266
|
-
instrumentation.error(`SFS - ProductConsumedDeletedHook
|
|
57339
|
+
instrumentation.error(`SFS - ProductConsumedDeletedHook original record QuantityConsumed not found: draftRecordId=${draftRecordId}`);
|
|
57267
57340
|
return [];
|
|
57268
57341
|
}
|
|
57269
57342
|
incrementCounter(ProductConsumedDeletedHook.COUNTER_NAME, CounterResult.Created);
|
|
57270
57343
|
const effect = {
|
|
57271
57344
|
type: 'record-field-increment',
|
|
57272
|
-
fieldName: ProductItem.Fields.QUANTITY_ON_HAND,
|
|
57345
|
+
fieldName: ProductItem$1.Fields.QUANTITY_ON_HAND,
|
|
57273
57346
|
value: quantityConsumed,
|
|
57274
57347
|
key: buildRecordRepKeyFromId(productItemId),
|
|
57275
57348
|
tag: draftActionId,
|
|
@@ -57280,13 +57353,115 @@ class ProductConsumedDeletedHook {
|
|
|
57280
57353
|
}
|
|
57281
57354
|
}
|
|
57282
57355
|
ProductConsumedDeletedHook.COUNTER_NAME = 'ProductConsumedDeletedHook';
|
|
57283
|
-
|
|
57284
|
-
|
|
57285
|
-
|
|
57286
|
-
|
|
57356
|
+
/**
|
|
57357
|
+
* Replicates the server-derived `IsProduct2Serialized` boolean on a draft-created
|
|
57358
|
+
* `ProductConsumed` record.
|
|
57359
|
+
*
|
|
57360
|
+
* `Product2Id` is not createable on `ProductConsumed`, so the draft only carries the
|
|
57361
|
+
* `ProductItemId`. The value is resolved through the related `ProductItem` record (see
|
|
57362
|
+
* `resolveIsProduct2SerializedFromProductItem`): the ProductItem's own
|
|
57363
|
+
* `IsProduct2Serialized` when cached, otherwise `ProductItem.Product2Id` ->
|
|
57364
|
+
* `Product2.IsSerialized`. When the chain cannot be resolved the hook exits without an
|
|
57365
|
+
* effect and the field is left as synthesized (null).
|
|
57366
|
+
*/
|
|
57367
|
+
class ProductConsumedIsSerializedCreatedHook {
|
|
57368
|
+
constructor(durableRecordStore) {
|
|
57369
|
+
this.durableRecordStore = durableRecordStore;
|
|
57370
|
+
this.on = 'post';
|
|
57371
|
+
this.apiName = ProductConsumed.API_NAME;
|
|
57372
|
+
}
|
|
57373
|
+
async then(draftActionId, draftActionTimestamp, draftRecordId, fields, reader) {
|
|
57374
|
+
const productItemId = getStringDraftField(fields, ProductConsumed.Fields.PRODUCT_ITEM_ID);
|
|
57375
|
+
if (productItemId === undefined) {
|
|
57376
|
+
incrementCounter(ProductConsumedIsSerializedCreatedHook.COUNTER_NAME, CounterResult.Exited);
|
|
57377
|
+
return [];
|
|
57378
|
+
}
|
|
57379
|
+
const isSerialized = await resolveIsProduct2SerializedFromProductItem(reader ?? this.durableRecordStore, productItemId, ProductConsumedIsSerializedCreatedHook.COUNTER_NAME);
|
|
57380
|
+
if (isSerialized === undefined) {
|
|
57381
|
+
return [];
|
|
57382
|
+
}
|
|
57383
|
+
const effect = {
|
|
57384
|
+
type: 'record-field-set',
|
|
57385
|
+
fieldName: ProductConsumed.Fields.IS_PRODUCT2_SERIALIZED,
|
|
57386
|
+
value: isSerialized,
|
|
57387
|
+
key: buildRecordRepKeyFromId(draftRecordId),
|
|
57388
|
+
tag: draftActionId,
|
|
57389
|
+
uniqueId: `${draftActionId}-IsProduct2Serialized`,
|
|
57390
|
+
timestamp: draftActionTimestamp,
|
|
57391
|
+
};
|
|
57392
|
+
return [effect];
|
|
57287
57393
|
}
|
|
57288
|
-
return field;
|
|
57289
57394
|
}
|
|
57395
|
+
ProductConsumedIsSerializedCreatedHook.COUNTER_NAME = 'ProductConsumedIsSerializedCreatedHook';
|
|
57396
|
+
/**
|
|
57397
|
+
* Replicates the server-derived `IsProduct2Serialized` boolean on a draft-edited
|
|
57398
|
+
* `ProductConsumed` record.
|
|
57399
|
+
*
|
|
57400
|
+
* A `patch` body only carries changed fields, so the related `ProductItemId` may be absent.
|
|
57401
|
+
* The id is resolved by preferring the patch body (the link is being reassigned) and falling
|
|
57402
|
+
* back to the original record in the durable store, then the value is resolved through that
|
|
57403
|
+
* `ProductItem` exactly as on create (see `resolveIsProduct2SerializedFromProductItem`).
|
|
57404
|
+
*
|
|
57405
|
+
* When the patch explicitly clears `ProductItemId` (sets it to `null`) the record no longer
|
|
57406
|
+
* derives from any `ProductItem`, so `IsProduct2Serialized` is cleared (set to `null`) rather
|
|
57407
|
+
* than resolved from the now-stale original link. Likewise, when the patch reassigns
|
|
57408
|
+
* `ProductItemId` to a new id whose chain isn't cached, the stored value reflects the old link
|
|
57409
|
+
* and is stale, so it is cleared. When the link is unchanged and its chain cannot be resolved,
|
|
57410
|
+
* the stored value is still correct so the hook exits without an effect and leaves it unchanged.
|
|
57411
|
+
*/
|
|
57412
|
+
class ProductConsumedIsSerializedUpdatedHook {
|
|
57413
|
+
constructor(durableRecordStore) {
|
|
57414
|
+
this.durableRecordStore = durableRecordStore;
|
|
57415
|
+
this.on = 'patch';
|
|
57416
|
+
this.apiName = ProductConsumed.API_NAME;
|
|
57417
|
+
}
|
|
57418
|
+
async then(draftActionId, draftActionTimestamp, draftRecordId, fields, reader) {
|
|
57419
|
+
const recordReader = reader ?? this.durableRecordStore;
|
|
57420
|
+
const originalRecord = await recordReader.getRecord(buildRecordRepKeyFromId(draftRecordId));
|
|
57421
|
+
if (originalRecord === undefined) {
|
|
57422
|
+
incrementCounter(ProductConsumedIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Error);
|
|
57423
|
+
instrumentation.error(`SFS - ProductConsumedIsSerializedUpdatedHook original record not found: draftRecordId=${draftRecordId}`);
|
|
57424
|
+
return [];
|
|
57425
|
+
}
|
|
57426
|
+
const reference = resolveReferenceIdForUpdate(fields, originalRecord.fields, ProductConsumed.Fields.PRODUCT_ITEM_ID);
|
|
57427
|
+
if (reference.kind === 'unresolved') {
|
|
57428
|
+
incrementCounter(ProductConsumedIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Exited);
|
|
57429
|
+
return [];
|
|
57430
|
+
}
|
|
57431
|
+
let isSerialized;
|
|
57432
|
+
if (reference.kind === 'cleared') {
|
|
57433
|
+
// The link was explicitly cleared: the record derives from no ProductItem, so clear
|
|
57434
|
+
// IsProduct2Serialized rather than resolving it from the stale original link.
|
|
57435
|
+
incrementCounter(ProductConsumedIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Created);
|
|
57436
|
+
isSerialized = null;
|
|
57437
|
+
}
|
|
57438
|
+
else {
|
|
57439
|
+
isSerialized = await resolveIsProduct2SerializedFromProductItem(recordReader, reference.id, ProductConsumedIsSerializedUpdatedHook.COUNTER_NAME);
|
|
57440
|
+
if (isSerialized === undefined && reference.source === 'patch') {
|
|
57441
|
+
// The link was reassigned to a new ProductItem whose chain isn't cached yet, so
|
|
57442
|
+
// the stored IsProduct2Serialized still reflects the OLD link and is now stale.
|
|
57443
|
+
// Clear it rather than leaving the stale value alongside the new relationship;
|
|
57444
|
+
// the server recomputes the real value on sync.
|
|
57445
|
+
incrementCounter(ProductConsumedIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Created);
|
|
57446
|
+
isSerialized = null;
|
|
57447
|
+
}
|
|
57448
|
+
}
|
|
57449
|
+
if (isSerialized === undefined) {
|
|
57450
|
+
return [];
|
|
57451
|
+
}
|
|
57452
|
+
const effect = {
|
|
57453
|
+
type: 'record-field-set',
|
|
57454
|
+
fieldName: ProductConsumed.Fields.IS_PRODUCT2_SERIALIZED,
|
|
57455
|
+
value: isSerialized,
|
|
57456
|
+
key: buildRecordRepKeyFromId(draftRecordId),
|
|
57457
|
+
tag: draftActionId,
|
|
57458
|
+
uniqueId: `${draftActionId}-IsProduct2Serialized`,
|
|
57459
|
+
timestamp: draftActionTimestamp,
|
|
57460
|
+
};
|
|
57461
|
+
return [effect];
|
|
57462
|
+
}
|
|
57463
|
+
}
|
|
57464
|
+
ProductConsumedIsSerializedUpdatedHook.COUNTER_NAME = 'ProductConsumedIsSerializedUpdatedHook';
|
|
57290
57465
|
function getProductItemIdFromRecordFields(fields) {
|
|
57291
57466
|
const field = fields[ProductConsumed.Fields.PRODUCT_ITEM_ID];
|
|
57292
57467
|
if (field === undefined || field.value === undefined || typeof field.value !== 'string') {
|
|
@@ -57308,29 +57483,265 @@ function getQuantityConsumedFromRecordFields(fields) {
|
|
|
57308
57483
|
}
|
|
57309
57484
|
return field.value;
|
|
57310
57485
|
}
|
|
57311
|
-
|
|
57312
|
-
|
|
57313
|
-
|
|
57314
|
-
|
|
57315
|
-
|
|
57316
|
-
|
|
57317
|
-
|
|
57318
|
-
|
|
57319
|
-
|
|
57320
|
-
|
|
57321
|
-
|
|
57322
|
-
|
|
57323
|
-
|
|
57324
|
-
|
|
57486
|
+
|
|
57487
|
+
const ProductTransfer = {
|
|
57488
|
+
API_NAME: 'ProductTransfer',
|
|
57489
|
+
Fields: {
|
|
57490
|
+
SOURCE_PRODUCT_ITEM_ID: 'SourceProductItemId',
|
|
57491
|
+
IS_PRODUCT2_SERIALIZED: 'IsProduct2Serialized',
|
|
57492
|
+
},
|
|
57493
|
+
};
|
|
57494
|
+
/**
|
|
57495
|
+
* Replicates the server-derived `IsProduct2Serialized` boolean on a draft-created
|
|
57496
|
+
* `ProductTransfer` record.
|
|
57497
|
+
*
|
|
57498
|
+
* The value is resolved through the related `SourceProductItem` record (a `ProductItem`),
|
|
57499
|
+
* mirroring the `ProductConsumed` hook: the ProductItem's own `IsProduct2Serialized` when
|
|
57500
|
+
* cached, otherwise `ProductItem.Product2Id` -> `Product2.IsSerialized` (see
|
|
57501
|
+
* `resolveIsProduct2SerializedFromProductItem`). When the chain cannot be resolved the hook
|
|
57502
|
+
* exits without an effect and the field is left as synthesized (null).
|
|
57503
|
+
*/
|
|
57504
|
+
class ProductTransferIsSerializedCreatedHook {
|
|
57505
|
+
constructor(durableRecordStore) {
|
|
57506
|
+
this.durableRecordStore = durableRecordStore;
|
|
57507
|
+
this.on = 'post';
|
|
57508
|
+
this.apiName = ProductTransfer.API_NAME;
|
|
57509
|
+
}
|
|
57510
|
+
async then(draftActionId, draftActionTimestamp, draftRecordId, fields, reader) {
|
|
57511
|
+
const sourceProductItemId = getStringDraftField(fields, ProductTransfer.Fields.SOURCE_PRODUCT_ITEM_ID);
|
|
57512
|
+
if (sourceProductItemId === undefined) {
|
|
57513
|
+
incrementCounter(ProductTransferIsSerializedCreatedHook.COUNTER_NAME, CounterResult.Exited);
|
|
57514
|
+
return [];
|
|
57515
|
+
}
|
|
57516
|
+
const isSerialized = await resolveIsProduct2SerializedFromProductItem(reader ?? this.durableRecordStore, sourceProductItemId, ProductTransferIsSerializedCreatedHook.COUNTER_NAME);
|
|
57517
|
+
if (isSerialized === undefined) {
|
|
57518
|
+
return [];
|
|
57519
|
+
}
|
|
57520
|
+
const effect = {
|
|
57521
|
+
type: 'record-field-set',
|
|
57522
|
+
fieldName: ProductTransfer.Fields.IS_PRODUCT2_SERIALIZED,
|
|
57523
|
+
value: isSerialized,
|
|
57524
|
+
key: buildRecordRepKeyFromId(draftRecordId),
|
|
57525
|
+
tag: draftActionId,
|
|
57526
|
+
uniqueId: `${draftActionId}-IsProduct2Serialized`,
|
|
57527
|
+
timestamp: draftActionTimestamp,
|
|
57528
|
+
};
|
|
57529
|
+
return [effect];
|
|
57530
|
+
}
|
|
57325
57531
|
}
|
|
57532
|
+
ProductTransferIsSerializedCreatedHook.COUNTER_NAME = 'ProductTransferIsSerializedCreatedHook';
|
|
57533
|
+
/**
|
|
57534
|
+
* Replicates the server-derived `IsProduct2Serialized` boolean on a draft-edited
|
|
57535
|
+
* `ProductTransfer` record.
|
|
57536
|
+
*
|
|
57537
|
+
* ProductTransfer can only be edited (not created) on mobile, so this is the hook that fires
|
|
57538
|
+
* in practice. A `patch` body only carries changed fields, so the related `SourceProductItemId`
|
|
57539
|
+
* may be absent; the id is resolved by preferring the patch body (the link is being reassigned)
|
|
57540
|
+
* and falling back to the original record in the durable store, then the value is resolved
|
|
57541
|
+
* through that `ProductItem` exactly as on create (see
|
|
57542
|
+
* `resolveIsProduct2SerializedFromProductItem`).
|
|
57543
|
+
*
|
|
57544
|
+
* When the patch explicitly clears `SourceProductItemId` (sets it to `null`) the record no
|
|
57545
|
+
* longer derives from any `ProductItem`, so `IsProduct2Serialized` is cleared (set to `null`)
|
|
57546
|
+
* rather than resolved from the now-stale original link. Likewise, when the patch reassigns
|
|
57547
|
+
* `SourceProductItemId` to a new id whose chain isn't cached, the stored value reflects the old
|
|
57548
|
+
* link and is stale, so it is cleared. When the link is unchanged and its chain cannot be
|
|
57549
|
+
* resolved, the stored value is still correct so the hook exits without an effect and leaves it
|
|
57550
|
+
* unchanged.
|
|
57551
|
+
*/
|
|
57552
|
+
class ProductTransferIsSerializedUpdatedHook {
|
|
57553
|
+
constructor(durableRecordStore) {
|
|
57554
|
+
this.durableRecordStore = durableRecordStore;
|
|
57555
|
+
this.on = 'patch';
|
|
57556
|
+
this.apiName = ProductTransfer.API_NAME;
|
|
57557
|
+
}
|
|
57558
|
+
async then(draftActionId, draftActionTimestamp, draftRecordId, fields, reader) {
|
|
57559
|
+
const recordReader = reader ?? this.durableRecordStore;
|
|
57560
|
+
const originalRecord = await recordReader.getRecord(buildRecordRepKeyFromId(draftRecordId));
|
|
57561
|
+
if (originalRecord === undefined) {
|
|
57562
|
+
incrementCounter(ProductTransferIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Error);
|
|
57563
|
+
instrumentation.error(`SFS - ProductTransferIsSerializedUpdatedHook original record not found: draftRecordId=${draftRecordId}`);
|
|
57564
|
+
return [];
|
|
57565
|
+
}
|
|
57566
|
+
const reference = resolveReferenceIdForUpdate(fields, originalRecord.fields, ProductTransfer.Fields.SOURCE_PRODUCT_ITEM_ID);
|
|
57567
|
+
if (reference.kind === 'unresolved') {
|
|
57568
|
+
incrementCounter(ProductTransferIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Exited);
|
|
57569
|
+
return [];
|
|
57570
|
+
}
|
|
57571
|
+
let isSerialized;
|
|
57572
|
+
if (reference.kind === 'cleared') {
|
|
57573
|
+
// The link was explicitly cleared: the record derives from no ProductItem, so clear
|
|
57574
|
+
// IsProduct2Serialized rather than resolving it from the stale original link.
|
|
57575
|
+
incrementCounter(ProductTransferIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Created);
|
|
57576
|
+
isSerialized = null;
|
|
57577
|
+
}
|
|
57578
|
+
else {
|
|
57579
|
+
isSerialized = await resolveIsProduct2SerializedFromProductItem(recordReader, reference.id, ProductTransferIsSerializedUpdatedHook.COUNTER_NAME);
|
|
57580
|
+
if (isSerialized === undefined && reference.source === 'patch') {
|
|
57581
|
+
// The link was reassigned to a new ProductItem whose chain isn't cached yet, so
|
|
57582
|
+
// the stored IsProduct2Serialized still reflects the OLD link and is now stale.
|
|
57583
|
+
// Clear it rather than leaving the stale value alongside the new relationship;
|
|
57584
|
+
// the server recomputes the real value on sync.
|
|
57585
|
+
incrementCounter(ProductTransferIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Created);
|
|
57586
|
+
isSerialized = null;
|
|
57587
|
+
}
|
|
57588
|
+
}
|
|
57589
|
+
if (isSerialized === undefined) {
|
|
57590
|
+
return [];
|
|
57591
|
+
}
|
|
57592
|
+
const effect = {
|
|
57593
|
+
type: 'record-field-set',
|
|
57594
|
+
fieldName: ProductTransfer.Fields.IS_PRODUCT2_SERIALIZED,
|
|
57595
|
+
value: isSerialized,
|
|
57596
|
+
key: buildRecordRepKeyFromId(draftRecordId),
|
|
57597
|
+
tag: draftActionId,
|
|
57598
|
+
uniqueId: `${draftActionId}-IsProduct2Serialized`,
|
|
57599
|
+
timestamp: draftActionTimestamp,
|
|
57600
|
+
};
|
|
57601
|
+
return [effect];
|
|
57602
|
+
}
|
|
57603
|
+
}
|
|
57604
|
+
ProductTransferIsSerializedUpdatedHook.COUNTER_NAME = 'ProductTransferIsSerializedUpdatedHook';
|
|
57605
|
+
|
|
57606
|
+
const ProductItem = {
|
|
57607
|
+
API_NAME: 'ProductItem',
|
|
57608
|
+
Fields: {
|
|
57609
|
+
PRODUCT2_ID: 'Product2Id',
|
|
57610
|
+
IS_PRODUCT2_SERIALIZED: 'IsProduct2Serialized',
|
|
57611
|
+
},
|
|
57612
|
+
};
|
|
57613
|
+
/**
|
|
57614
|
+
* Replicates the server-derived `IsProduct2Serialized` boolean on a draft-created
|
|
57615
|
+
* `ProductItem` record.
|
|
57616
|
+
*
|
|
57617
|
+
* Unlike `ProductConsumed`/`ProductTransfer` — which reach the product through a related
|
|
57618
|
+
* `ProductItem` — a `ProductItem` carries `Product2Id` directly, so the value is resolved as
|
|
57619
|
+
* `Product2Id` -> `Product2.IsSerialized` (see `resolveIsSerializedFromProduct2Id`). When the
|
|
57620
|
+
* chain cannot be resolved the hook exits without an effect and the field is left as
|
|
57621
|
+
* synthesized (null).
|
|
57622
|
+
*/
|
|
57623
|
+
class ProductItemIsSerializedCreatedHook {
|
|
57624
|
+
constructor(durableRecordStore) {
|
|
57625
|
+
this.durableRecordStore = durableRecordStore;
|
|
57626
|
+
this.on = 'post';
|
|
57627
|
+
this.apiName = ProductItem.API_NAME;
|
|
57628
|
+
}
|
|
57629
|
+
async then(draftActionId, draftActionTimestamp, draftRecordId, fields, reader) {
|
|
57630
|
+
const product2Id = getStringDraftField(fields, ProductItem.Fields.PRODUCT2_ID);
|
|
57631
|
+
if (product2Id === undefined) {
|
|
57632
|
+
incrementCounter(ProductItemIsSerializedCreatedHook.COUNTER_NAME, CounterResult.Exited);
|
|
57633
|
+
return [];
|
|
57634
|
+
}
|
|
57635
|
+
const isSerialized = await resolveIsSerializedFromProduct2Id(reader ?? this.durableRecordStore, product2Id, ProductItemIsSerializedCreatedHook.COUNTER_NAME);
|
|
57636
|
+
if (isSerialized === undefined) {
|
|
57637
|
+
return [];
|
|
57638
|
+
}
|
|
57639
|
+
const effect = {
|
|
57640
|
+
type: 'record-field-set',
|
|
57641
|
+
fieldName: ProductItem.Fields.IS_PRODUCT2_SERIALIZED,
|
|
57642
|
+
value: isSerialized,
|
|
57643
|
+
key: buildRecordRepKeyFromId(draftRecordId),
|
|
57644
|
+
tag: draftActionId,
|
|
57645
|
+
uniqueId: `${draftActionId}-IsProduct2Serialized`,
|
|
57646
|
+
timestamp: draftActionTimestamp,
|
|
57647
|
+
};
|
|
57648
|
+
return [effect];
|
|
57649
|
+
}
|
|
57650
|
+
}
|
|
57651
|
+
ProductItemIsSerializedCreatedHook.COUNTER_NAME = 'ProductItemIsSerializedCreatedHook';
|
|
57652
|
+
/**
|
|
57653
|
+
* Replicates the server-derived `IsProduct2Serialized` boolean on a draft-edited
|
|
57654
|
+
* `ProductItem` record.
|
|
57655
|
+
*
|
|
57656
|
+
* A `patch` body only carries changed fields, so `Product2Id` may be absent; it is resolved by
|
|
57657
|
+
* preferring the patch body (the product is being reassigned) and falling back to the original
|
|
57658
|
+
* record in the durable store, then resolved as `Product2Id` -> `Product2.IsSerialized` (see
|
|
57659
|
+
* `resolveIsSerializedFromProduct2Id`).
|
|
57660
|
+
*
|
|
57661
|
+
* When the patch explicitly clears `Product2Id` (sets it to `null`) the record no longer
|
|
57662
|
+
* derives from any `Product2`, so `IsProduct2Serialized` is cleared (set to `null`) rather than
|
|
57663
|
+
* resolved from the now-stale original link. Likewise, when the patch reassigns `Product2Id` to
|
|
57664
|
+
* a new id whose `Product2` isn't cached, the stored value reflects the old link and is stale,
|
|
57665
|
+
* so it is cleared. When the link is unchanged and its `Product2` cannot be resolved, the stored
|
|
57666
|
+
* value is still correct so the hook exits without an effect and leaves it unchanged.
|
|
57667
|
+
*/
|
|
57668
|
+
class ProductItemIsSerializedUpdatedHook {
|
|
57669
|
+
constructor(durableRecordStore) {
|
|
57670
|
+
this.durableRecordStore = durableRecordStore;
|
|
57671
|
+
this.on = 'patch';
|
|
57672
|
+
this.apiName = ProductItem.API_NAME;
|
|
57673
|
+
}
|
|
57674
|
+
async then(draftActionId, draftActionTimestamp, draftRecordId, fields, reader) {
|
|
57675
|
+
const recordReader = reader ?? this.durableRecordStore;
|
|
57676
|
+
const originalRecord = await recordReader.getRecord(buildRecordRepKeyFromId(draftRecordId));
|
|
57677
|
+
if (originalRecord === undefined) {
|
|
57678
|
+
incrementCounter(ProductItemIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Error);
|
|
57679
|
+
instrumentation.error(`SFS - ProductItemIsSerializedUpdatedHook original record not found: draftRecordId=${draftRecordId}`);
|
|
57680
|
+
return [];
|
|
57681
|
+
}
|
|
57682
|
+
const reference = resolveReferenceIdForUpdate(fields, originalRecord.fields, ProductItem.Fields.PRODUCT2_ID);
|
|
57683
|
+
if (reference.kind === 'unresolved') {
|
|
57684
|
+
incrementCounter(ProductItemIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Exited);
|
|
57685
|
+
return [];
|
|
57686
|
+
}
|
|
57687
|
+
let isSerialized;
|
|
57688
|
+
if (reference.kind === 'cleared') {
|
|
57689
|
+
// The link was explicitly cleared: the record derives from no Product2, so clear
|
|
57690
|
+
// IsProduct2Serialized rather than resolving it from the stale original link.
|
|
57691
|
+
incrementCounter(ProductItemIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Created);
|
|
57692
|
+
isSerialized = null;
|
|
57693
|
+
}
|
|
57694
|
+
else {
|
|
57695
|
+
isSerialized = await resolveIsSerializedFromProduct2Id(recordReader, reference.id, ProductItemIsSerializedUpdatedHook.COUNTER_NAME);
|
|
57696
|
+
if (isSerialized === undefined && reference.source === 'patch') {
|
|
57697
|
+
// The link was reassigned to a new Product2 that isn't cached yet, so the stored
|
|
57698
|
+
// IsProduct2Serialized still reflects the OLD link and is now stale. Clear it
|
|
57699
|
+
// rather than leaving the stale value alongside the new relationship; the server
|
|
57700
|
+
// recomputes the real value on sync.
|
|
57701
|
+
incrementCounter(ProductItemIsSerializedUpdatedHook.COUNTER_NAME, CounterResult.Created);
|
|
57702
|
+
isSerialized = null;
|
|
57703
|
+
}
|
|
57704
|
+
}
|
|
57705
|
+
if (isSerialized === undefined) {
|
|
57706
|
+
return [];
|
|
57707
|
+
}
|
|
57708
|
+
const effect = {
|
|
57709
|
+
type: 'record-field-set',
|
|
57710
|
+
fieldName: ProductItem.Fields.IS_PRODUCT2_SERIALIZED,
|
|
57711
|
+
value: isSerialized,
|
|
57712
|
+
key: buildRecordRepKeyFromId(draftRecordId),
|
|
57713
|
+
tag: draftActionId,
|
|
57714
|
+
uniqueId: `${draftActionId}-IsProduct2Serialized`,
|
|
57715
|
+
timestamp: draftActionTimestamp,
|
|
57716
|
+
};
|
|
57717
|
+
return [effect];
|
|
57718
|
+
}
|
|
57719
|
+
}
|
|
57720
|
+
ProductItemIsSerializedUpdatedHook.COUNTER_NAME = 'ProductItemIsSerializedUpdatedHook';
|
|
57326
57721
|
|
|
57327
57722
|
function createSfsSideEffectHooks(durableRecordStore) {
|
|
57328
57723
|
if (productConsumedSideEffects.isOpen({ fallback: false })) {
|
|
57329
|
-
|
|
57724
|
+
const hooks = [
|
|
57330
57725
|
new ProductConsumedCreatedHook(),
|
|
57331
57726
|
new ProductConsumedUpdatedHook(durableRecordStore),
|
|
57332
57727
|
new ProductConsumedDeletedHook(durableRecordStore),
|
|
57333
57728
|
];
|
|
57729
|
+
// The IsProduct2Serialized side effects (ProductConsumed, ProductTransfer and
|
|
57730
|
+
// ProductItem) ship on by default and can be disabled independently of the van stock
|
|
57731
|
+
// hooks above via a shared kill switch: the gate is closed (fallback false) by default,
|
|
57732
|
+
// which keeps the effects enabled, and opening the gate turns them off.
|
|
57733
|
+
//
|
|
57734
|
+
// Each object gets both a create (post) and edit (patch) hook so the field is
|
|
57735
|
+
// synthesized on either draft action, keeping the behavior uniform across the objects.
|
|
57736
|
+
if (!isProduct2SerializedSideEffectKillSwitch.isOpen({ fallback: false })) {
|
|
57737
|
+
hooks.push(new ProductConsumedIsSerializedCreatedHook(durableRecordStore));
|
|
57738
|
+
hooks.push(new ProductConsumedIsSerializedUpdatedHook(durableRecordStore));
|
|
57739
|
+
hooks.push(new ProductTransferIsSerializedCreatedHook(durableRecordStore));
|
|
57740
|
+
hooks.push(new ProductTransferIsSerializedUpdatedHook(durableRecordStore));
|
|
57741
|
+
hooks.push(new ProductItemIsSerializedCreatedHook(durableRecordStore));
|
|
57742
|
+
hooks.push(new ProductItemIsSerializedUpdatedHook(durableRecordStore));
|
|
57743
|
+
}
|
|
57744
|
+
return hooks;
|
|
57334
57745
|
}
|
|
57335
57746
|
return [];
|
|
57336
57747
|
}
|
|
@@ -57654,6 +58065,15 @@ function buildServiceDescriptor$m() {
|
|
|
57654
58065
|
};
|
|
57655
58066
|
}
|
|
57656
58067
|
|
|
58068
|
+
function applyOperationRequestOptions$1(init, operationType) {
|
|
58069
|
+
if (operationType === void 0) {
|
|
58070
|
+
return init;
|
|
58071
|
+
}
|
|
58072
|
+
return {
|
|
58073
|
+
...init,
|
|
58074
|
+
onestore: { ...init?.onestore, retrySafe: operationType === "query" }
|
|
58075
|
+
};
|
|
58076
|
+
}
|
|
57657
58077
|
class AuraNetworkCommand extends NetworkCommand$1 {
|
|
57658
58078
|
constructor(services) {
|
|
57659
58079
|
super(services);
|
|
@@ -57672,6 +58092,18 @@ class AuraNetworkCommand extends NetworkCommand$1 {
|
|
|
57672
58092
|
"Fetch parameters must be specified when using HTTP transport on an Aura adapter"
|
|
57673
58093
|
);
|
|
57674
58094
|
}
|
|
58095
|
+
/**
|
|
58096
|
+
* The command's operation type. Generated command subclasses override this
|
|
58097
|
+
* getter with their concrete operation (see the generator's
|
|
58098
|
+
* `generateOperationType`); the base returns `undefined` for commands that
|
|
58099
|
+
* don't declare one. On the HTTP fallback path (`fetch`) it derives the
|
|
58100
|
+
* request's `onestore.retrySafe` — reads (`query`) are retry-safe, writes
|
|
58101
|
+
* (`mutation`) are not. Mirrors `FetchNetworkCommand.operationType`, which
|
|
58102
|
+
* this class does not inherit (it extends `NetworkCommand` directly).
|
|
58103
|
+
*/
|
|
58104
|
+
get operationType() {
|
|
58105
|
+
return void 0;
|
|
58106
|
+
}
|
|
57675
58107
|
coerceAuraErrors(auraErrors) {
|
|
57676
58108
|
return new UserVisibleError(getFetchResponseFromAuraError(auraErrors[0]));
|
|
57677
58109
|
}
|
|
@@ -57773,9 +58205,11 @@ class AuraNetworkCommand extends NetworkCommand$1 {
|
|
|
57773
58205
|
this.coerceAuraErrors
|
|
57774
58206
|
);
|
|
57775
58207
|
} else if (this.shouldUseFetch()) {
|
|
57776
|
-
const
|
|
58208
|
+
const [input, init] = this.fetchParams;
|
|
58209
|
+
const initWithOptions = applyOperationRequestOptions$1(init, this.operationType);
|
|
57777
58210
|
try {
|
|
57778
|
-
|
|
58211
|
+
const fetchCall = initWithOptions === void 0 ? this.services.fetch(input) : this.services.fetch(input, initWithOptions);
|
|
58212
|
+
return this.convertFetchResponseToData(fetchCall);
|
|
57779
58213
|
} catch (reason) {
|
|
57780
58214
|
return resolvedPromiseLike$2(err$1(toError(reason)));
|
|
57781
58215
|
}
|
|
@@ -58522,6 +58956,15 @@ class NetworkCommand extends BaseCommand {
|
|
|
58522
58956
|
async afterRequestHooks(_options) {
|
|
58523
58957
|
}
|
|
58524
58958
|
}
|
|
58959
|
+
function applyOperationRequestOptions(init, operationType) {
|
|
58960
|
+
if (operationType === void 0) {
|
|
58961
|
+
return init;
|
|
58962
|
+
}
|
|
58963
|
+
return {
|
|
58964
|
+
...init,
|
|
58965
|
+
onestore: { ...init?.onestore, retrySafe: operationType === "query" }
|
|
58966
|
+
};
|
|
58967
|
+
}
|
|
58525
58968
|
function isAbortableCommand(command) {
|
|
58526
58969
|
return !!command && typeof command === "object" && "isAbortableCommand" in command && command.isAbortableCommand === true;
|
|
58527
58970
|
}
|
|
@@ -58611,7 +59054,8 @@ const _FetchNetworkCommand = class _FetchNetworkCommand extends NetworkCommand {
|
|
|
58611
59054
|
* `generateOperationType`); the base returns `undefined` for commands that
|
|
58612
59055
|
* don't declare one (e.g. hand-written commands). Transport-layer
|
|
58613
59056
|
* interceptors use it to tell a read (`query`) apart from a write — see
|
|
58614
|
-
* `fetch`, which folds it into the context seed
|
|
59057
|
+
* `fetch`, which folds it into the context seed and derives
|
|
59058
|
+
* `onestore.retrySafe` from it.
|
|
58615
59059
|
*/
|
|
58616
59060
|
get operationType() {
|
|
58617
59061
|
return void 0;
|
|
@@ -58620,8 +59064,9 @@ const _FetchNetworkCommand = class _FetchNetworkCommand extends NetworkCommand {
|
|
|
58620
59064
|
try {
|
|
58621
59065
|
const [input, init] = this.fetchParams;
|
|
58622
59066
|
const operationType = this.operationType;
|
|
59067
|
+
const initWithOptions = applyOperationRequestOptions(init, operationType);
|
|
58623
59068
|
const seed = operationType === void 0 ? contextSeed : { operationType, ...contextSeed };
|
|
58624
|
-
const initWithSeed = seed === void 0 ?
|
|
59069
|
+
const initWithSeed = seed === void 0 ? initWithOptions : { ...initWithOptions, __contextSeed: seed };
|
|
58625
59070
|
const fetchCall = initWithSeed === void 0 ? this.services.fetch(input) : this.services.fetch(input, initWithSeed);
|
|
58626
59071
|
return this.convertFetchResponseToData(fetchCall);
|
|
58627
59072
|
} catch (reason) {
|
|
@@ -59715,7 +60160,7 @@ function buildServiceDescriptor$b(luvio) {
|
|
|
59715
60160
|
},
|
|
59716
60161
|
};
|
|
59717
60162
|
}
|
|
59718
|
-
// version: 1.
|
|
60163
|
+
// version: 1.463.0-ea9ecd17fc
|
|
59719
60164
|
|
|
59720
60165
|
/**
|
|
59721
60166
|
* Copyright (c) 2022, Salesforce, Inc.,
|
|
@@ -59741,7 +60186,7 @@ function buildServiceDescriptor$a(notifyRecordUpdateAvailable, getNormalizedLuvi
|
|
|
59741
60186
|
},
|
|
59742
60187
|
};
|
|
59743
60188
|
}
|
|
59744
|
-
// version: 1.
|
|
60189
|
+
// version: 1.463.0-ea9ecd17fc
|
|
59745
60190
|
|
|
59746
60191
|
function findExecutableOperation(input) {
|
|
59747
60192
|
const operations = input.query.definitions.filter(
|
|
@@ -60322,9 +60767,18 @@ function validateDiscriminatedOneOf(data, discriminator, path, document, visited
|
|
|
60322
60767
|
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
60323
60768
|
return invalidSchemaResponseWithError(incorrectTypeError("object", typeof data, path));
|
|
60324
60769
|
}
|
|
60325
|
-
const { propertyName, mapping } = discriminator;
|
|
60770
|
+
const { propertyName, mapping, defaultMapping } = discriminator;
|
|
60326
60771
|
const discriminatorValue = data[propertyName];
|
|
60327
60772
|
if (discriminatorValue === void 0) {
|
|
60773
|
+
if (defaultMapping !== void 0) {
|
|
60774
|
+
return validateJsonSchemaInternal(
|
|
60775
|
+
data,
|
|
60776
|
+
{ $ref: defaultMapping },
|
|
60777
|
+
path,
|
|
60778
|
+
document,
|
|
60779
|
+
visited
|
|
60780
|
+
);
|
|
60781
|
+
}
|
|
60328
60782
|
return invalidSchemaResponseWithError(
|
|
60329
60783
|
new MissingRequiredPropertyError(
|
|
60330
60784
|
`Object at path '${path}' is missing required discriminator property '${propertyName}'.`
|
|
@@ -60336,7 +60790,7 @@ function validateDiscriminatedOneOf(data, discriminator, path, document, visited
|
|
|
60336
60790
|
incorrectTypeError("string", typeof discriminatorValue, `${path}.${propertyName}`)
|
|
60337
60791
|
);
|
|
60338
60792
|
}
|
|
60339
|
-
const targetRef = mapping[discriminatorValue];
|
|
60793
|
+
const targetRef = mapping[discriminatorValue] ?? defaultMapping;
|
|
60340
60794
|
if (targetRef === void 0) {
|
|
60341
60795
|
return invalidSchemaResponseWithError(
|
|
60342
60796
|
new JsonSchemaViolationError(
|
|
@@ -62130,9 +62584,6 @@ class NimbusSqliteOneStoreCache {
|
|
|
62130
62584
|
|
|
62131
62585
|
const PURGE_CACHE_DAYS = 30;
|
|
62132
62586
|
function initializeOneStore(sqliteStore, luvio) {
|
|
62133
|
-
if (!useOneStore.isOpen({ fallback: true })) {
|
|
62134
|
-
return;
|
|
62135
|
-
}
|
|
62136
62587
|
// Initialize performance.now() fallback if not available
|
|
62137
62588
|
if (typeof globalThis.performance === 'undefined') {
|
|
62138
62589
|
globalThis.performance = { now: () => Date.now() };
|
|
@@ -62319,12 +62770,6 @@ function getRuntime({ primeUser = false } = {}) {
|
|
|
62319
62770
|
lazyInternalLuvio = internalLuvio;
|
|
62320
62771
|
lazyObjectInfoService = new ObjectInfoService(getObjectInfo, getObjectInfos, getObjectInfoDirectory, lazyDurableStore);
|
|
62321
62772
|
const baseEnv = new Environment(store, lazyNetworkAdapter);
|
|
62322
|
-
const shouldFilterFieldsOnRevive = (key) => {
|
|
62323
|
-
if (reviveOnlyRequestedFields.isOpen({ fallback: true })) {
|
|
62324
|
-
return isStoreKeyRecordId(key);
|
|
62325
|
-
}
|
|
62326
|
-
return false;
|
|
62327
|
-
};
|
|
62328
62773
|
const gqlEnv = makeEnvironmentGraphqlAware(baseEnv);
|
|
62329
62774
|
const durableEnv = makeDurable(gqlEnv, {
|
|
62330
62775
|
durableStore: lazyDurableStore,
|
|
@@ -62332,7 +62777,12 @@ function getRuntime({ primeUser = false } = {}) {
|
|
|
62332
62777
|
// disable luvio deep freeze in headless environments
|
|
62333
62778
|
disableDeepFreeze: typeof window === 'undefined',
|
|
62334
62779
|
shouldFlush,
|
|
62335
|
-
|
|
62780
|
+
// Disabling field filtering on revive for now (previously gated behind
|
|
62781
|
+
// the lmr.reviveOnlyRequestedFields gate). The code path is retained
|
|
62782
|
+
// below should we need to re-enable this behavior in the future:
|
|
62783
|
+
shouldFilterFieldsOnRevive: (_key) => {
|
|
62784
|
+
return false;
|
|
62785
|
+
},
|
|
62336
62786
|
});
|
|
62337
62787
|
// draft queue
|
|
62338
62788
|
lazyDraftQueue = buildLdsDraftQueue(lazyDurableStore);
|
|
@@ -62453,4 +62903,4 @@ register({
|
|
|
62453
62903
|
});
|
|
62454
62904
|
|
|
62455
62905
|
export { O11Y_NAMESPACE_LDS_MOBILE, getRuntime, ingest$1o as ingestDenormalizedRecordRepresentation, initializeOneStore, registerReportObserver, reportGraphqlQueryParseError };
|
|
62456
|
-
// version: 1.
|
|
62906
|
+
// version: 1.463.0-ea9ecd17fc
|