UNPKG

resolve-local-event-broker

Version:

The reSolve framework's event broker for applications on a local machine.

184 lines (163 loc) 7.35 kB
import { BATCHES_TABLE_NAME, NOTIFICATIONS_TABLE_NAME, SUBSCRIBERS_TABLE_NAME, DeliveryStrategy, NotificationStatus, ConsumerMethod, PrivateOperationType, LazinessStrategy } from '../constants'; const retryRollback = new Error('Retrying rollback marker'); const acknowledgeBatch = async (pool, payload) => { const { database: { runRawQuery, runQuery, escapeStr, escapeId }, parseSubscription, getNextCursor, invokeConsumer, invokeOperation, serializeError } = pool; const { batchId, result } = payload; const notificationsTableNameAsId = escapeId(NOTIFICATIONS_TABLE_NAME); const subscribersTableNameAsId = escapeId(SUBSCRIBERS_TABLE_NAME); const batchesTableNameAsId = escapeId(BATCHES_TABLE_NAME); const [applyingEvents, affectedNotifications] = await Promise.all([runQuery(` SELECT * FROM ${batchesTableNameAsId} WHERE ${batchesTableNameAsId}."batchId" = ${escapeStr(batchId)} ORDER BY ${batchesTableNameAsId}."eventIndex" ASC `), runQuery(` SELECT ${subscribersTableNameAsId}."subscriptionId" AS "subscriptionId", ${subscribersTableNameAsId}."eventSubscriber" AS "eventSubscriber", ${subscribersTableNameAsId}."deliveryStrategy" AS "deliveryStrategy", ${subscribersTableNameAsId}."successEvent" AS "successEvent", ${subscribersTableNameAsId}."cursor" AS "cursor", ${notificationsTableNameAsId}."status" AS "runStatus", ${notificationsTableNameAsId}."xaTransactionId" AS "xaTransactionId", ${notificationsTableNameAsId}."batchId" AS "batchId" FROM ${notificationsTableNameAsId} LEFT JOIN ${subscribersTableNameAsId} ON ${subscribersTableNameAsId}."subscriptionId" = ${notificationsTableNameAsId}."subscriptionId" WHERE ${notificationsTableNameAsId}."batchId" = ${escapeStr(batchId)} LIMIT 1 `)]); if (affectedNotifications == null || affectedNotifications.length === 0) { return; } const subscriptionDescription = parseSubscription(affectedNotifications[0]); if (subscriptionDescription.runStatus !== NotificationStatus.PROCESSING && subscriptionDescription.runStatus !== NotificationStatus.ACKNOWLEDGE_ENTERING && subscriptionDescription.runStatus !== NotificationStatus.ACKNOWLEDGE_XA_COMMITING && subscriptionDescription.runStatus !== NotificationStatus.ACKNOWLEDGE_XA_ROLLBACKING) { return; } if (subscriptionDescription.runStatus === NotificationStatus.PROCESSING) { await runRawQuery(` UPDATE ${notificationsTableNameAsId} SET "status" = ${escapeStr(NotificationStatus.ACKNOWLEDGE_ENTERING)} WHERE ${notificationsTableNameAsId}."batchId" = ${escapeStr(batchId)} AND ${notificationsTableNameAsId}."status" = ${escapeStr(NotificationStatus.PROCESSING)}; COMMIT; BEGIN IMMEDIATE; `); const result = await runQuery(` SELECT ${notificationsTableNameAsId}."status" AS "runStatus" FROM ${notificationsTableNameAsId} WHERE ${notificationsTableNameAsId}."batchId" = ${escapeStr(batchId)} AND ${notificationsTableNameAsId}."status" = ${escapeStr(NotificationStatus.ACKNOWLEDGE_ENTERING)} LIMIT 1 `); if (result == null || result.length === 0) { return; } subscriptionDescription.runStatus = NotificationStatus.ACKNOWLEDGE_ENTERING; } const activeBatch = { eventSubscriber: subscriptionDescription.eventSubscriber, subscriptionId: subscriptionDescription.subscriptionId, batchId }; try { let isXaCommitOk = true; const { successEvent, failedEvent, error } = result; const lastSuccessEventIdx = successEvent != null ? applyingEvents.findIndex(({ aggregateIdAndVersion }) => aggregateIdAndVersion === `${successEvent.aggregateId}:${successEvent.aggregateVersion}`) + 1 : 0; const nextCursor = await getNextCursor(subscriptionDescription.cursor, applyingEvents.slice(0, lastSuccessEventIdx)); if (subscriptionDescription.deliveryStrategy === DeliveryStrategy.ACTIVE_XA && subscriptionDescription.xaTransactionId != null) { if (subscriptionDescription.runStatus === NotificationStatus.ACKNOWLEDGE_ENTERING || subscriptionDescription.runStatus === NotificationStatus.ACKNOWLEDGE_XA_COMMITING) { if (subscriptionDescription.runStatus === NotificationStatus.ACKNOWLEDGE_ENTERING) { await runRawQuery(` UPDATE ${notificationsTableNameAsId} SET "status" = ${escapeStr(NotificationStatus.ACKNOWLEDGE_XA_COMMITING)} WHERE "batchId" = ${escapeStr(batchId)}; COMMIT; BEGIN IMMEDIATE; `); } isXaCommitOk = await invokeConsumer(pool, ConsumerMethod.CommitXATransaction, { eventSubscriber: subscriptionDescription.eventSubscriber, xaTransactionId: subscriptionDescription.xaTransactionId, batchId }); } else if (subscriptionDescription.runStatus === NotificationStatus.ACKNOWLEDGE_XA_ROLLBACKING) { throw retryRollback; } else { throw new Error(`Inconsistent XA-state ${subscriptionDescription.runStatus}`); } } const input = { type: PrivateOperationType.FINALIZE_BATCH, payload: { activeBatch, result: { cursor: nextCursor, successEvent: isXaCommitOk ? successEvent : subscriptionDescription.successEvent, failedEvent, error: serializeError(error) } } }; await invokeOperation(pool, LazinessStrategy.EAGER, input); } catch (error) { let compositeError = error; if (subscriptionDescription.deliveryStrategy === DeliveryStrategy.ACTIVE_XA && subscriptionDescription.xaTransactionId != null) { compositeError = new Error(error.message); compositeError.stack = error.stack; if (subscriptionDescription.runStatus === NotificationStatus.ACKNOWLEDGE_ENTERING || subscriptionDescription.runStatus === NotificationStatus.ACKNOWLEDGE_XA_COMMITING) { await runRawQuery(` UPDATE ${notificationsTableNameAsId} SET "status" = ${escapeStr(NotificationStatus.ACKNOWLEDGE_XA_ROLLBACKING)} WHERE "batchId" = ${escapeStr(batchId)}; COMMIT; BEGIN IMMEDIATE; `); } try { const isXaRollbackOk = await invokeConsumer(pool, ConsumerMethod.RollbackXATransaction, { eventSubscriber: subscriptionDescription.eventSubscriber, xaTransactionId: subscriptionDescription.xaTransactionId, batchId }); if (!isXaRollbackOk) { throw new Error(`Xa-transaction ${subscriptionDescription.xaTransactionId} early marked to rollback, but was auto-committed`); } } catch (rollbackError) { compositeError.message = `${compositeError.message}\n${rollbackError.message}`; compositeError.stack = `${compositeError.stack}\n${rollbackError.stack}`; } } const input = { type: PrivateOperationType.FINALIZE_BATCH, payload: { activeBatch, result: { error: serializeError(compositeError) } } }; await invokeOperation(pool, LazinessStrategy.EAGER, input); } }; export default acknowledgeBatch; //# sourceMappingURL=acknowledge-batch.js.map