@getanthill/datastore
Version:
Event-Sourced Datastore
127 lines (102 loc) • 3.18 kB
text/typescript
import type { NextFunction, Request, Response } from 'express';
import type { Services } from '../../typings';
import { ok } from 'assert';
import get from 'lodash/get';
import set from 'lodash/set';
import { Datastore, Aggregator } from '../../sdk';
/**
* @alpha
*
* Aggregation route
*
* @param services Services
*/
export function aggregate(services: Services) {
const fheStep = async (
step: {
type: 'fhe';
script: string;
args: Array<string>;
public_key?: string;
destination?: string;
},
data: any,
) => {
const publicKey =
get(data, step.public_key ?? 'public_key') ?? step.public_key;
const another = await services.fhe.clone();
await another.connect();
const UploadedPublicKey = another.seal.PublicKey();
UploadedPublicKey.load(another.context!, publicKey);
another.keys!.public = UploadedPublicKey;
const asyncFn = another.compile(step.script);
const args = step.args.map((arg) => get(data, arg));
const res = await another.compute(asyncFn, ...args);
const destination = step.destination ?? step.type;
set(data, destination, res);
return data;
};
return async (req: Request, res: Response, next: NextFunction) => {
let aggregator: Aggregator;
try {
services.telemetry.logger.debug(
'[aggregate] Datastores initialization...',
);
const datastores = new Map<string, Datastore>(
services.config.datastores.map((c: any) => {
ok(!!c.name, 'Missing Datastore configuration name');
return [
c.name as string,
new Datastore({
...c.config,
telemetry: services.telemetry,
timeout: parseInt(req.header('timeout') || '30000', 10),
token: req.header('Authorization') || '',
}),
];
}),
);
aggregator = new Aggregator(datastores);
if (services.config.features.fhe.isEnabled === true) {
aggregator.addStepType('fhe', {
handler: fheStep,
});
}
let pipeline = [];
let initialData = {};
if (Array.isArray(req.body)) {
pipeline = req.body;
} else {
pipeline = req.body.pipeline;
initialData = req.body.data;
}
services.telemetry.logger.debug('[aggregate] Starting the aggregation', {
steps_count: pipeline.length,
});
const aggregation = await aggregator.aggregate(pipeline, initialData);
if (req.header('logs') === 'true') {
// @ts-ignore
res.body = { aggregation, logs: aggregator.logs };
} else {
// @ts-ignore
res.body = aggregation;
}
// @ts-ignore
res.json(res.body);
} catch (err: any) {
if (err === Aggregator.ERROR_INVALID_PIPELINE_DEFINITION) {
err.status = 400;
/* @ts-ignore */
err.details = aggregator?.logs;
return next(err);
}
if (Aggregator.ERRORS.includes(err)) {
err.status = 422;
/* @ts-ignore */
err.details = aggregator?.logs;
return next(err);
}
next(err);
}
};
}