@mojaloop/central-ledger 17.7.8 → 17.8.0-orb.1
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/.ncurc.yaml +3 -1
- package/.nvmrc +1 -1
- package/.nycrc.yml +16 -1
- package/Dockerfile +9 -10
- package/README.md +39 -9
- package/audit-ci.jsonc +15 -2
- package/config/default.json +17 -1
- package/docker/central-ledger/default.json +9 -0
- package/docker/config-modifier/configs/central-ledger.js +25 -0
- package/docker/env.sh +15 -0
- package/docker/kafka/scripts/provision.sh +4 -1
- package/docker/ml-api-adapter/default.json +4 -1
- package/docker-compose.yml +90 -2
- package/documentation/db/erd-transfer-timeout.png +0 -0
- package/documentation/db/erd-transfer-timeout.txt +81 -0
- package/documentation/fx-implementation/README.md +48 -0
- package/documentation/fx-implementation/assets/fx-position-movements.drawio.svg +4 -0
- package/documentation/fx-implementation/assets/test-scenario.drawio.svg +4 -0
- package/documentation/sequence-diagrams/Handler - FX timeout.plantuml +123 -0
- package/documentation/sequence-diagrams/Handler - FX timeout.png +0 -0
- package/documentation/sequence-diagrams/Handler - timeout.plantuml +81 -0
- package/documentation/sequence-diagrams/Handler - timeout.png +0 -0
- package/documentation/state-diagrams/transfer-ML-spec-states-diagram.png +0 -0
- package/documentation/state-diagrams/transfer-internal-states-diagram.png +0 -0
- package/documentation/state-diagrams/transfer-internal-states.plantuml +75 -0
- package/documentation/state-diagrams/transfer-states.plantuml +13 -0
- package/migrations/310204_transferParticipant-participantId.js +52 -0
- package/migrations/310403_participantPositionChange-participantCurrencyId.js +47 -0
- package/migrations/310404_participantPositionChange-change.js +46 -0
- package/migrations/600010_fxTransferType.js +43 -0
- package/migrations/600011_fxTransferType-indexes.js +38 -0
- package/migrations/600012_fxParticipantCurrencyType.js +43 -0
- package/migrations/600013_fxParticipantCurrencyType-indexes.js +38 -0
- package/migrations/600100_fxTransferDuplicateCheck.js +42 -0
- package/migrations/600110_fxTransferErrorDuplicateCheck.js.js +17 -0
- package/migrations/600200_fxTransfer.js +51 -0
- package/migrations/600201_fxTransfer-indexes.js +40 -0
- package/migrations/600400_fxTransferStateChange.js +46 -0
- package/migrations/600401_fxTransferStateChange-indexes.js +40 -0
- package/migrations/600501_fxWatchList.js +46 -0
- package/migrations/600502_fxWatchList-indexes.js +40 -0
- package/migrations/600600_fxTransferFulfilmentDuplicateCheck.js +43 -0
- package/migrations/600601_fxTransferFulfilmentDuplicateCheck-indexes.js +38 -0
- package/migrations/600700_fxTransferFulfilment.js +47 -0
- package/migrations/600701_fxTransferFulfilment-indexes.js +43 -0
- package/migrations/600800_fxTransferExtension.js +47 -0
- package/migrations/601400_fxTransferTimeout.js +43 -0
- package/migrations/601401_fxTransferTimeout-indexes.js +37 -0
- package/migrations/601500_fxTransferError.js +44 -0
- package/migrations/601501_fxTransferError-indexes.js +37 -0
- package/migrations/610200_fxTransferParticipant.js +52 -0
- package/migrations/610201_fxTransferParticipant-indexes.js +44 -0
- package/migrations/610202_fxTransferParticipant-participantId.js +52 -0
- package/migrations/610403_participantPositionChange-fxTransfer.js +46 -0
- package/migrations/910101_feature904DataMigration.js +46 -52
- package/migrations/910102_feature949DataMigration.js +219 -225
- package/migrations/950104_settlementModel-settlementAccountTypeId.js +15 -20
- package/migrations/950108_participantProxy.js +18 -0
- package/migrations/950109_fxQuote.js +19 -0
- package/migrations/950110_fxQuoteResponse.js +25 -0
- package/migrations/950111_fxQuoteError.js +23 -0
- package/migrations/950113_fxQuoteDuplicateCheck.js +18 -0
- package/migrations/950114_fxQuoteResponseDuplicateCheck.js +21 -0
- package/migrations/950115_fxQuoteConversionTerms.js +36 -0
- package/migrations/950116_fxQuoteConversionTermsExtension.js +21 -0
- package/migrations/950117_fxQuoteResponseConversionTerms.js +39 -0
- package/migrations/950118_fxQuoteResponseConversionTermsExtension.js +21 -0
- package/migrations/950119_fxCharge.js +27 -0
- package/migrations/960100_create_externalParticipant.js +47 -0
- package/migrations/960110_alter_transferParticipant__addFiled_externalParticipantId.js +50 -0
- package/migrations/960111_alter_fxTransferParticipant__addFiled_externalParticipantId.js +50 -0
- package/package.json +27 -20
- package/seeds/endpointType.js +18 -0
- package/seeds/fxParticipantCurrencyType.js +45 -0
- package/seeds/fxTransferType.js +45 -0
- package/seeds/participant.js +2 -1
- package/seeds/transferParticipantRoleType.js +9 -0
- package/seeds/transferState.js +10 -0
- package/src/api/interface/swagger.json +23 -30
- package/src/api/participants/handler.js +6 -2
- package/src/api/participants/routes.js +8 -7
- package/src/api/root/handler.js +15 -5
- package/src/domain/fx/cyril.js +466 -0
- package/src/domain/fx/index.js +107 -0
- package/src/domain/participant/index.js +108 -1
- package/src/domain/position/abort.js +215 -0
- package/src/domain/position/binProcessor.js +361 -99
- package/src/domain/position/fulfil.js +252 -111
- package/src/domain/position/fx-fulfil.js +138 -0
- package/src/domain/position/fx-prepare.js +280 -0
- package/src/domain/position/fx-timeout-reserved.js +159 -0
- package/src/domain/position/index.js +1 -0
- package/src/domain/position/prepare.js +69 -49
- package/src/domain/position/timeout-reserved.js +162 -0
- package/src/domain/timeout/index.js +26 -2
- package/src/domain/transfer/index.js +22 -5
- package/src/domain/transfer/transform.js +19 -6
- package/src/handlers/admin/handler.js +0 -2
- package/src/handlers/bulk/fulfil/handler.js +5 -5
- package/src/handlers/bulk/get/handler.js +5 -5
- package/src/handlers/bulk/prepare/handler.js +9 -9
- package/src/handlers/bulk/processing/handler.js +8 -7
- package/src/handlers/bulk/shared/validator.js +1 -1
- package/src/handlers/positions/handler.js +23 -10
- package/src/handlers/positions/handlerBatch.js +54 -26
- package/src/handlers/register.js +2 -1
- package/src/handlers/timeouts/handler.js +212 -68
- package/src/handlers/transfers/FxFulfilService.js +387 -0
- package/src/handlers/transfers/createRemittanceEntity.js +106 -0
- package/src/handlers/transfers/dto.js +53 -0
- package/src/handlers/transfers/handler.js +609 -571
- package/src/handlers/transfers/prepare.js +572 -0
- package/src/handlers/transfers/validator.js +75 -14
- package/src/lib/cache.js +1 -1
- package/src/lib/config.js +6 -3
- package/src/lib/healthCheck/subServiceHealth.js +12 -2
- package/src/lib/proxyCache.js +131 -0
- package/src/models/bulkTransfer/facade.js +58 -82
- package/src/models/fxTransfer/duplicateCheck.js +153 -0
- package/src/models/fxTransfer/fxTransfer.js +578 -0
- package/src/models/fxTransfer/fxTransferError.js +53 -0
- package/src/models/fxTransfer/fxTransferExtension.js +41 -0
- package/src/models/fxTransfer/fxTransferTimeout.js +68 -0
- package/src/models/fxTransfer/index.js +15 -0
- package/src/models/fxTransfer/stateChange.js +47 -0
- package/src/models/fxTransfer/watchList.js +49 -0
- package/src/models/ledgerAccountType/ledgerAccountType.js +8 -32
- package/src/models/misc/segment.js +0 -1
- package/src/models/participant/externalParticipant.js +96 -0
- package/src/models/participant/externalParticipantCached.js +148 -0
- package/src/models/participant/facade.js +192 -123
- package/src/models/participant/participant.js +2 -1
- package/src/models/participant/participantCurrency.js +1 -1
- package/src/models/participant/participantPosition.js +2 -8
- package/src/models/position/batch.js +90 -1
- package/src/models/position/facade.js +5 -5
- package/src/models/position/participantPositionChanges.js +68 -0
- package/src/models/settlement/settlementModel.js +5 -23
- package/src/models/transfer/facade.js +806 -447
- package/src/shared/constants.js +52 -0
- package/src/shared/fspiopErrorFactory.js +131 -0
- package/src/shared/logger/index.js +8 -0
- package/src/shared/loggingPlugin.js +43 -0
- package/src/shared/plugins.js +6 -0
- package/src/shared/setup.js +10 -0
- package/test-integration.Dockerfile +1 -1
- package/test.Dockerfile +1 -1
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
const { Enum } = require('@mojaloop/central-services-shared')
|
|
2
|
+
const ErrorHandler = require('@mojaloop/central-services-error-handling')
|
|
3
|
+
const Config = require('../../lib/config')
|
|
4
|
+
const Utility = require('@mojaloop/central-services-shared').Util
|
|
5
|
+
const MLNumber = require('@mojaloop/ml-number')
|
|
6
|
+
const Logger = require('@mojaloop/central-services-logger')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @function processPositionAbortBin
|
|
10
|
+
*
|
|
11
|
+
* @async
|
|
12
|
+
* @description This is the domain function to process a bin of abort / fx-abort messages of a single participant account.
|
|
13
|
+
*
|
|
14
|
+
* @param {array} abortBins - an array containing abort / fx-abort action bins
|
|
15
|
+
* @param {object} options
|
|
16
|
+
* @param {number} accumulatedPositionValue - value of position accumulated so far from previous bin processing
|
|
17
|
+
* @param {number} accumulatedPositionReservedValue - value of position reserved accumulated so far, not used but kept for consistency
|
|
18
|
+
* @param {object} accumulatedTransferStates - object with transfer id keys and transfer state id values. Used to check if transfer is in correct state for processing. Clone and update states for output.
|
|
19
|
+
* @param {object} transferInfoList - object with transfer id keys and transfer info values. Used to pass transfer info to domain function.
|
|
20
|
+
* @param {boolean} changePositions - whether to change positions or not
|
|
21
|
+
* @returns {object} - Returns an object containing accumulatedPositionValue, accumulatedPositionReservedValue, accumulatedTransferStateChanges, accumulatedTransferStates, resultMessages, limitAlarms or throws an error if failed
|
|
22
|
+
*/
|
|
23
|
+
const processPositionAbortBin = async (
|
|
24
|
+
abortBins,
|
|
25
|
+
{
|
|
26
|
+
accumulatedPositionValue,
|
|
27
|
+
accumulatedPositionReservedValue,
|
|
28
|
+
accumulatedTransferStates,
|
|
29
|
+
accumulatedFxTransferStates,
|
|
30
|
+
isFx,
|
|
31
|
+
changePositions = true
|
|
32
|
+
}
|
|
33
|
+
) => {
|
|
34
|
+
const transferStateChanges = []
|
|
35
|
+
const participantPositionChanges = []
|
|
36
|
+
const resultMessages = []
|
|
37
|
+
const followupMessages = []
|
|
38
|
+
const fxTransferStateChanges = []
|
|
39
|
+
const accumulatedTransferStatesCopy = Object.assign({}, accumulatedTransferStates)
|
|
40
|
+
const accumulatedFxTransferStatesCopy = Object.assign({}, accumulatedFxTransferStates)
|
|
41
|
+
let runningPosition = new MLNumber(accumulatedPositionValue)
|
|
42
|
+
|
|
43
|
+
if (abortBins && abortBins.length > 0) {
|
|
44
|
+
for (const binItem of abortBins) {
|
|
45
|
+
Logger.isDebugEnabled && Logger.debug(`processPositionAbortBin::binItem: ${JSON.stringify(binItem.message.value)}`)
|
|
46
|
+
if (isFx) {
|
|
47
|
+
// If the transfer is not in `RECEIVED_ERROR`, a position fx-abort message was incorrectly published.
|
|
48
|
+
// i.e Something has gone extremely wrong.
|
|
49
|
+
if (accumulatedFxTransferStates[binItem.message.value.content.uriParams.id] !== Enum.Transfers.TransferInternalState.RECEIVED_ERROR) {
|
|
50
|
+
throw ErrorHandler.Factory.createInternalServerFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.INTERNAL_SERVER_ERROR.message)
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
// If the transfer is not in `RECEIVED_ERROR`, a position abort message was incorrectly published.
|
|
54
|
+
// i.e Something has gone extremely wrong.
|
|
55
|
+
if (accumulatedTransferStates[binItem.message.value.content.uriParams.id] !== Enum.Transfers.TransferInternalState.RECEIVED_ERROR) {
|
|
56
|
+
throw ErrorHandler.Factory.createInternalServerFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.INTERNAL_SERVER_ERROR.message)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const cyrilResult = binItem.message.value.content.context?.cyrilResult
|
|
61
|
+
if (!cyrilResult || !cyrilResult.positionChanges || cyrilResult.positionChanges.length === 0) {
|
|
62
|
+
throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.INTERNAL_SERVER_ERROR)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Handle position movements
|
|
66
|
+
// Iterate through positionChanges and handle each position movement, mark as done and publish a position-commit kafka message again for the next item
|
|
67
|
+
// Find out the first item to be processed
|
|
68
|
+
const positionChangeIndex = cyrilResult.positionChanges.findIndex(positionChange => !positionChange.isDone)
|
|
69
|
+
const positionChangeToBeProcessed = cyrilResult.positionChanges[positionChangeIndex]
|
|
70
|
+
if (positionChangeToBeProcessed.isFxTransferStateChange) {
|
|
71
|
+
const { participantPositionChange, fxTransferStateChange, transferStateId, updatedRunningPosition } =
|
|
72
|
+
_handleParticipantPositionChangeFx(runningPosition, positionChangeToBeProcessed.amount, positionChangeToBeProcessed.commitRequestId, accumulatedPositionReservedValue)
|
|
73
|
+
runningPosition = updatedRunningPosition
|
|
74
|
+
participantPositionChanges.push(participantPositionChange)
|
|
75
|
+
fxTransferStateChanges.push(fxTransferStateChange)
|
|
76
|
+
accumulatedFxTransferStatesCopy[positionChangeToBeProcessed.commitRequestId] = transferStateId
|
|
77
|
+
} else {
|
|
78
|
+
const { participantPositionChange, transferStateChange, transferStateId, updatedRunningPosition } =
|
|
79
|
+
_handleParticipantPositionChange(runningPosition, positionChangeToBeProcessed.amount, positionChangeToBeProcessed.transferId, accumulatedPositionReservedValue)
|
|
80
|
+
runningPosition = updatedRunningPosition
|
|
81
|
+
participantPositionChanges.push(participantPositionChange)
|
|
82
|
+
transferStateChanges.push(transferStateChange)
|
|
83
|
+
accumulatedTransferStatesCopy[positionChangeToBeProcessed.transferId] = transferStateId
|
|
84
|
+
}
|
|
85
|
+
binItem.result = { success: true }
|
|
86
|
+
const from = binItem.message.value.from
|
|
87
|
+
cyrilResult.positionChanges[positionChangeIndex].isDone = true
|
|
88
|
+
const nextIndex = cyrilResult.positionChanges.findIndex(positionChange => !positionChange.isDone)
|
|
89
|
+
if (nextIndex === -1) {
|
|
90
|
+
// All position changes are done, we need to inform all the participants about the abort
|
|
91
|
+
// Construct a list of messages excluding the original message as it will notified anyway
|
|
92
|
+
for (const positionChange of cyrilResult.positionChanges) {
|
|
93
|
+
if (positionChange.isFxTransferStateChange) {
|
|
94
|
+
// Construct notification message for fx transfer state change
|
|
95
|
+
const resultMessage = _constructAbortResultMessage(binItem, positionChange.commitRequestId, from, positionChange.notifyTo)
|
|
96
|
+
resultMessages.push({ binItem, message: resultMessage })
|
|
97
|
+
} else {
|
|
98
|
+
// Construct notification message for transfer state change
|
|
99
|
+
const resultMessage = _constructAbortResultMessage(binItem, positionChange.transferId, from, positionChange.notifyTo)
|
|
100
|
+
resultMessages.push({ binItem, message: resultMessage })
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
// There are still position changes to be processed
|
|
105
|
+
// Send position-commit kafka message again for the next item
|
|
106
|
+
const participantCurrencyId = cyrilResult.positionChanges[nextIndex].participantCurrencyId
|
|
107
|
+
// const followupMessage = _constructTransferAbortFollowupMessage(binItem, transferId, payerFsp, payeeFsp, transfer)
|
|
108
|
+
// Pass down the context to the followup message with mutated cyrilResult
|
|
109
|
+
const followupMessage = { ...binItem.message.value }
|
|
110
|
+
// followupMessage.content.context = binItem.message.value.content.context
|
|
111
|
+
followupMessages.push({ binItem, messageKey: participantCurrencyId.toString(), message: followupMessage })
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
accumulatedPositionValue: changePositions ? runningPosition.toNumber() : accumulatedPositionValue,
|
|
118
|
+
accumulatedTransferStates: accumulatedTransferStatesCopy, // finalized transfer state after fulfil processing
|
|
119
|
+
accumulatedPositionReservedValue, // not used but kept for consistency
|
|
120
|
+
accumulatedTransferStateChanges: transferStateChanges, // transfer state changes to be persisted in order
|
|
121
|
+
accumulatedFxTransferStates: accumulatedFxTransferStatesCopy, // finalized fx transfer state after fulfil processing
|
|
122
|
+
accumulatedFxTransferStateChanges: fxTransferStateChanges, // fx transfer state changes to be persisted in order
|
|
123
|
+
accumulatedPositionChanges: changePositions ? participantPositionChanges : [], // participant position changes to be persisted in order
|
|
124
|
+
notifyMessages: resultMessages, // array of objects containing bin item and result message. {binItem, message}
|
|
125
|
+
followupMessages // array of objects containing bin item, message key and followup message. {binItem, messageKey, message}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const _constructAbortResultMessage = (binItem, id, from, notifyTo) => {
|
|
130
|
+
let apiErrorCode = ErrorHandler.Enums.FSPIOPErrorCodes.PAYEE_REJECTION
|
|
131
|
+
let fromCalculated = from
|
|
132
|
+
if (binItem.message?.value.metadata.event.action === Enum.Events.Event.Action.FX_ABORT_VALIDATION || binItem.message?.value.metadata.event.action === Enum.Events.Event.Action.ABORT_VALIDATION) {
|
|
133
|
+
fromCalculated = Config.HUB_NAME
|
|
134
|
+
apiErrorCode = ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR
|
|
135
|
+
}
|
|
136
|
+
const fspiopError = ErrorHandler.Factory.createFSPIOPError(
|
|
137
|
+
apiErrorCode,
|
|
138
|
+
null,
|
|
139
|
+
null,
|
|
140
|
+
null,
|
|
141
|
+
null
|
|
142
|
+
).toApiErrorObject(Config.ERROR_HANDLING)
|
|
143
|
+
|
|
144
|
+
const state = Utility.StreamingProtocol.createEventState(
|
|
145
|
+
Enum.Events.EventStatus.FAILURE.status,
|
|
146
|
+
fspiopError.errorInformation.errorCode,
|
|
147
|
+
fspiopError.errorInformation.errorDescription
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
// Create metadata for the message
|
|
151
|
+
const metadata = Utility.StreamingProtocol.createMetadataWithCorrelatedEvent(
|
|
152
|
+
id,
|
|
153
|
+
Enum.Kafka.Topics.POSITION,
|
|
154
|
+
binItem.message?.value.metadata.event.action, // This will be replaced anyway in Kafka.produceGeneralMessage function
|
|
155
|
+
state
|
|
156
|
+
)
|
|
157
|
+
const resultMessage = Utility.StreamingProtocol.createMessage(
|
|
158
|
+
id,
|
|
159
|
+
notifyTo,
|
|
160
|
+
fromCalculated,
|
|
161
|
+
metadata,
|
|
162
|
+
binItem.message.value.content.headers, // Headers don't really matter here. ml-api-adapter will ignore them and create their own.
|
|
163
|
+
fspiopError,
|
|
164
|
+
{ id },
|
|
165
|
+
'application/json'
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return resultMessage
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const _handleParticipantPositionChange = (runningPosition, transferAmount, transferId, accumulatedPositionReservedValue) => {
|
|
172
|
+
const transferStateId = Enum.Transfers.TransferInternalState.ABORTED_ERROR
|
|
173
|
+
const updatedRunningPosition = new MLNumber(runningPosition.add(transferAmount).toFixed(Config.AMOUNT.SCALE))
|
|
174
|
+
|
|
175
|
+
const participantPositionChange = {
|
|
176
|
+
transferId, // Need to delete this in bin processor while updating transferStateChangeId
|
|
177
|
+
transferStateChangeId: null, // Need to update this in bin processor while executing queries
|
|
178
|
+
value: updatedRunningPosition.toNumber(),
|
|
179
|
+
change: transferAmount,
|
|
180
|
+
reservedValue: accumulatedPositionReservedValue
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Construct transfer state change object
|
|
184
|
+
const transferStateChange = {
|
|
185
|
+
transferId,
|
|
186
|
+
transferStateId,
|
|
187
|
+
reason: null
|
|
188
|
+
}
|
|
189
|
+
return { participantPositionChange, transferStateChange, transferStateId, updatedRunningPosition }
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const _handleParticipantPositionChangeFx = (runningPosition, transferAmount, commitRequestId, accumulatedPositionReservedValue) => {
|
|
193
|
+
const transferStateId = Enum.Transfers.TransferInternalState.ABORTED_ERROR
|
|
194
|
+
// Amounts in `transferParticipant` for the payee are stored as negative values
|
|
195
|
+
const updatedRunningPosition = new MLNumber(runningPosition.add(transferAmount).toFixed(Config.AMOUNT.SCALE))
|
|
196
|
+
|
|
197
|
+
const participantPositionChange = {
|
|
198
|
+
commitRequestId, // Need to delete this in bin processor while updating fxTransferStateChangeId
|
|
199
|
+
fxTransferStateChangeId: null, // Need to update this in bin processor while executing queries
|
|
200
|
+
value: updatedRunningPosition.toNumber(),
|
|
201
|
+
change: transferAmount,
|
|
202
|
+
reservedValue: accumulatedPositionReservedValue
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const fxTransferStateChange = {
|
|
206
|
+
commitRequestId,
|
|
207
|
+
transferStateId,
|
|
208
|
+
reason: null
|
|
209
|
+
}
|
|
210
|
+
return { participantPositionChange, fxTransferStateChange, transferStateId, updatedRunningPosition }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = {
|
|
214
|
+
processPositionAbortBin
|
|
215
|
+
}
|