trifid-core
Version:
Trifid Core
156 lines (155 loc) • 6.38 kB
JavaScript
import "./otel.js";
import EventEmitter from 'node:events';
import pino from 'pino';
import fastify from 'fastify';
import fastifyCompress from '@fastify/compress';
import fastifyCors from '@fastify/cors';
import fastifyCookie from '@fastify/cookie';
import fastifyAccepts from '@fastify/accepts';
import fastifyFormBody from '@fastify/formbody';
import handler from "./lib/config/handler.js";
import { defaultHost, defaultLogLevel, defaultLogFormat, defaultPort, } from "./lib/config/default.js";
import pluginsAssembler from "./lib/plugins/assembler.js";
import applyPlugins from "./lib/plugins/apply.js";
import templateEngine from "./lib/templateEngine.js";
import { errorsHandler, notFoundHandler } from "./lib/handlers/index.js";
// Export some useful functions to work with SPARQL
export { supportedTypes as sparqlSupportedTypes, serializeFormattedStream as sparqlSerializeFormattedStream, serializeQuadStream as sparqlSerializeQuadStream, getRewriteConfiguration as sparqlGetRewriteConfiguration, } from "./lib/sparql.js";
// Export some functions that can be used for testing
export { assertRejection, getListenerURL, } from "./lib/test.js";
/**
* Create a new Trifid instance.
*
* @param config Trifid configuration.
* @param additionalPlugins Add additional plugins.
* @returns Trifid instance.
*/
const trifid = async (config, additionalPlugins = {}) => {
const trifidEvents = new EventEmitter();
const fullConfig = await handler(config ?? {});
const serverOptions = fullConfig?.server?.options || {};
// Template configuration
const template = fullConfig?.template || {};
// Dynamic server configuration
const portFromConfig = fullConfig?.server?.listener?.port;
const port = (portFromConfig === 0 || portFromConfig === '0') ? 0 : (portFromConfig || defaultPort);
const host = fullConfig?.server?.listener?.host || defaultHost;
const portNumber = typeof port === 'string' ? parseInt(port, 10) : port;
// Logger configuration
const logLevel = fullConfig?.server?.logLevel || defaultLogLevel;
const logFormat = fullConfig?.server?.logFormat || defaultLogFormat;
const loggerConfig = {
name: 'trifid-core',
level: logLevel,
};
if (logFormat === 'pretty') {
loggerConfig.transport = {
target: 'pino-pretty',
};
}
// Custom logger instance
const logger = pino(loggerConfig);
const server = await fastify({
logger: false,
trustProxy: true,
...serverOptions,
});
// Register fastifyCompress and add custom compressible mime-types (in addition to mime-db)
await server.register(fastifyCompress, { customTypes: /(turtle|n-triples|n-quads|trig|json)(;.+)?$/ });
// Add support for `application/sparql-query` content type
server.addContentTypeParser('application/sparql-query', (_request, payload, done) => {
const data = [];
payload.on('data', (chunk) => data.push(chunk));
payload.on('end', () => {
try {
const parsed = data.join('');
done(null, parsed);
}
catch (err) {
done(err, undefined);
}
});
});
// This can be used to pass data from multiple plugins
const trifidLocals = new Map();
server.decorate('locals', trifidLocals);
/**
* Handler to add a session to the request.
*
* @param request Request.
* @param _reply Reply.
* @param done Done.
*/
const addSessionHandler = (request, _reply, done) => {
request.session = new Map();
done();
};
server.addHook('onRequest', addSessionHandler);
// Add required plugins
server.register(fastifyCors, {
credentials: true,
origin: true,
});
// Add support for cookies
server.register(fastifyCookie);
// Add support for Accept header parser
server.register(fastifyAccepts);
// Add support for `application/x-www-form-urlencoded` content type
server.register(fastifyFormBody);
// Template engine configuration
const templateEngineInstance = await templateEngine(template, trifidLocals);
const { render } = templateEngineInstance;
// Add error and not found handlers (requires template engine to be ready)
server.setErrorHandler(errorsHandler);
const notFoundHandlerFn = await notFoundHandler({ render });
server.setNotFoundHandler(notFoundHandlerFn);
// Wrapper exposed to plugins: uses reply.callNotFound() (which switches the
// route context to the 404 context, bypassing per-route onSend hooks such as
// compression) and waits for the raw response to be fully written before
// resolving. This lets async plugin handlers return safely without
// wrapThenable racing and sending a second, empty response.
const notFound = (_request, reply) => new Promise((resolve) => {
const onDone = () => {
reply.raw.off('finish', onDone);
reply.raw.off('close', onDone);
resolve();
};
reply.raw.once('finish', onDone);
reply.raw.once('close', onDone);
reply.callNotFound();
});
const plugins = await pluginsAssembler(fullConfig, additionalPlugins);
await applyPlugins(server, fullConfig.globals ?? {}, plugins, logger, templateEngineInstance, `http://${host}:${portNumber}/`, trifidEvents, notFound);
const start = async () => {
// Forward server events to the Trifid plugins
server.addHook('onListen', () => {
trifidEvents.emit('listen');
});
server.addHook('onClose', () => {
trifidEvents.emit('close');
});
server.addHook('onReady', () => {
trifidEvents.emit('ready');
});
// Start server
await server.listen({
port: portNumber,
host,
});
// Log server address
const fastifyAddresses = server.addresses().map((address) => {
if (typeof address === 'string') {
return address;
}
return `http://${address.address}:${address.port}`;
});
logger.info(`Server listening on ${fastifyAddresses.join(', ')}`);
return server;
};
return {
start,
server,
config: fullConfig,
};
};
export default trifid;