integreat
Version:
Node.js integration layer
147 lines • 6.15 kB
JavaScript
import pProgress from 'p-progress';
import debugLib from 'debug';
import { QUEUE_SYMBOL } from './handlers/index.js';
import setupGetService from './utils/getService.js';
import { removeQueueFlag, setErrorOnAction, setResponseOnAction, setOptionsOnAction, setActionIds, } from './utils/action.js';
import { createErrorResponse, setOrigin } from './utils/response.js';
import { completeIdentOnAction } from './utils/completeIdent.js';
import { composeMiddleware } from './utils/composeMiddleware.js';
const debug = debugLib('great');
const shouldCompleteIdent = (action, options) => options.identConfig?.completeIdent && action.type !== 'GET_IDENT';
function getActionHandlerFromType(type, handlers) {
const handler = type ? handlers[type] : undefined;
if (typeof handler === 'function') {
return handler;
}
else {
return undefined;
}
}
function cleanUpActionAndSetIds({ payload: { service, ...payload }, meta: { auth, ...meta } = {}, ...action }) {
return setActionIds({
...action,
payload: { ...(service && { targetService: service }), ...payload },
meta: { ...meta, dispatchedAt: Date.now() },
});
}
const cleanUpResponseAndSetAccessAndOrigin = (response, ident) => ({
...response,
access: { ident, ...response.access },
...(response.status !== 'ok'
? { origin: response.origin || 'dispatch' }
: {}),
});
const adjustActionAfterIncomingMutation = ({ payload: { sourceService, ...payload } = {}, ...action }, originalAction) => ({
...action,
payload,
meta: {
...action.meta,
queue: action.meta?.queue ?? originalAction.meta?.queue,
},
});
async function mutateIncomingAction(action, getService) {
const { sourceService } = action.payload;
if (typeof sourceService !== 'string') {
return { action };
}
const service = getService(undefined, sourceService);
if (!service) {
return {
action: setErrorOnAction(action, `Source service '${sourceService}' not found`, 'dispatch', 'badrequest'),
};
}
const endpoint = await service.endpointFromAction(action, true);
if (!endpoint) {
return {
action: setErrorOnAction(action, `No matching endpoint for incoming mapping on service '${sourceService}'`, 'dispatch', 'badrequest'),
};
}
let mutatedAction;
const validateResponse = await endpoint.validateAction(action);
if (validateResponse) {
mutatedAction = setResponseOnAction(action, validateResponse);
}
else {
mutatedAction = adjustActionAfterIncomingMutation(await service.mutateIncomingRequest(setOptionsOnAction(action, endpoint), endpoint), action);
}
return { action: mutatedAction, service, endpoint };
}
async function mutateIncomingResponse(action, service, endpoint) {
return service && endpoint
? await service.mutateIncomingResponse(setOptionsOnAction(action, endpoint), endpoint)
: action.response || {};
}
async function handleAction(handlerType, action, resources, handlers) {
const handler = getActionHandlerFromType(handlerType, handlers);
if (!handler) {
return createErrorResponse(`No handler for ${String(handlerType)} action`, 'dispatch', 'badrequest');
}
return setOrigin(await handler(action, resources), typeof handlerType === 'string'
? `handler:${handlerType}`
: 'handler:queue');
}
function prepareActionForQueue(action, options) {
if (action.meta?.queue) {
if (options.queueService) {
return [action, true];
}
else {
return [removeQueueFlag(action), false];
}
}
else {
return [action, false];
}
}
function addActionId(actionIds, action) {
const actionId = action.meta?.id;
actionIds.add(actionId);
return actionId;
}
function removeActionId(actionIds, actionId, emit) {
actionIds.delete(actionId);
if (actionIds.size === 0) {
emit('done');
}
}
export default function createDispatch({ handlers = {}, schemas = new Map(), services = {}, middleware = [], options, actionIds, emit, }) {
const getService = setupGetService(schemas, services);
const middlewareFn = middleware.length > 0
? composeMiddleware(...middleware)
: (next) => async (action) => next(action);
const dispatch = (originalAction) => pProgress(async (setProgress) => {
debug('Dispatch: %o', originalAction);
if (!originalAction) {
return {
status: 'noaction',
error: 'Dispatched no action',
origin: 'dispatch',
};
}
let cleanedUpAction = cleanUpActionAndSetIds(originalAction);
const actionId = addActionId(actionIds, cleanedUpAction);
if (shouldCompleteIdent(cleanedUpAction, options)) {
cleanedUpAction = await completeIdentOnAction(cleanedUpAction, dispatch);
}
const { action, service: incomingService, endpoint: incomingEndpoint, } = await mutateIncomingAction(cleanedUpAction, getService);
let response;
if (action.response?.status) {
response = action.response;
}
else {
try {
const [nextAction, doQueue] = prepareActionForQueue(action, options);
const next = async (action) => handleAction(doQueue ? QUEUE_SYMBOL : action.type, action, { dispatch, getService, options, setProgress }, handlers);
response = setOrigin(await middlewareFn(next)(nextAction), 'middleware:dispatch');
}
catch (err) {
response = createErrorResponse(`Error thrown in dispatch: ${err instanceof Error ? err.message : String(err)}`, 'dispatch');
}
}
const cleanedUpResponse = cleanUpResponseAndSetAccessAndOrigin(await mutateIncomingResponse(setResponseOnAction(action, response), incomingService, incomingEndpoint), action.meta?.ident);
removeActionId(actionIds, actionId, emit);
return cleanedUpResponse;
});
return dispatch;
}
//# sourceMappingURL=dispatch.js.map