New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@vue-storefront/middleware

Package Overview
Dependencies
Maintainers
0
Versions
102
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vue-storefront/middleware - npm Package Compare versions

Comparing version 5.0.0 to 5.1.0-rc.0

lib/handlers/prepareLogger/index.d.ts

2

lib/handlers/index.d.ts

@@ -5,2 +5,4 @@ export * from "./callApiFunction";

export * from "./prepareArguments";
export * from "./prepareMetadataStorage";
export * from "./prepareLogger";
//# sourceMappingURL=index.d.ts.map
import { AnyFunction } from "../types";
import type { ExtensionEndpointHandler } from "../types";
export declare const isFunction: (x: unknown) => x is AnyFunction;
export declare function includes<T extends U, U>(coll: ReadonlyArray<T>, el: U): el is T;
export declare function isExtensionEndpointHandler(obj: object): obj is ExtensionEndpointHandler;
//# sourceMappingURL=utils.d.ts.map

@@ -5,3 +5,2 @@ 'use strict';

var consola = require('consola');
var cookieParser = require('cookie-parser');

@@ -13,2 +12,3 @@ var cors = require('cors');

var terminus = require('@godaddy/terminus');
var logger = require('@vue-storefront/logger');
var promises = require('node:timers/promises');

@@ -18,3 +18,2 @@

var consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
var cookieParser__default = /*#__PURE__*/_interopDefaultLegacy(cookieParser);

@@ -26,6 +25,110 @@ var cors__default = /*#__PURE__*/_interopDefaultLegacy(cors);

const GLOBAL_MIDDLEWARE_CFG_KEY = Symbol("GLOBAL_MIDDLEWARE_CFG_KEY");
/**
* Internal class responsible for spawning minimal amount of Logger's instances
* on boostrap of the application.
*
* @example Spawning minimal amount of Logger's instances and accessing it
* ```ts
* const config = // ... it's whole middleware's config
* const buildLogger = (loggerConfig) => LoggerFactory.create(LoggerType.ConsolaGcp, loggerConfig); // function responsible for creating new instance of Logger, it gets already resolved configuration for new instance as an argument
*
* const loggerManager = new LoggerManager(
* config,
* buildLogger
* );
* ```
*/
class LoggerManager {
constructor(config, buildLogger) {
this.instances = {};
// Global Logger
this.instances[GLOBAL_MIDDLEWARE_CFG_KEY] = this.selectLogger(config, buildLogger);
// Logger for every integration having custom logger config
for (const [integrationName, integrationEntry] of Object.entries(config.integrations)) {
if (this.hasCustomLoggerConfig(integrationEntry)) {
this.instances[integrationName] = this.selectLogger(integrationEntry, buildLogger, integrationName);
}
}
}
/**
* Selects custom logger provided by user with fallback to default logger.
* Prints console.warn if provided redudant custom options with custom handler.
*/
selectLogger(config, buildLogger, tag = "global") {
const logger = config.logger?.handler || buildLogger(config.logger);
if (config.logger?.handler) {
if (Object.keys(config.logger).length > 1) {
/**
* This codeblock will be triggered if end-developer provided custom logger and custom options.
* As they might think custom options will be provided to the logger, then their logger could stop working.
* That's why we decided to use console instead of created logger here.
*/
// eslint-disable-next-line no-console
console.warn(`Both handler and options are provided to ${tag} logger's options. Using handler, options will be ignored.`);
}
}
return logger;
}
/**
* Check if integration's config has configuration for dedicated logger instance.
*/
hasCustomLoggerConfig(integrationEntry) {
return Boolean(integrationEntry.logger);
}
/**
* Returns instance of logger for requested integration from internal storage.
* Called without an argument returns instance of global logger.
*/
get(integrationName) {
return (this.instances[integrationName] ||
this.instances[GLOBAL_MIDDLEWARE_CFG_KEY]);
}
}
function isAlokaiContainer(source) {
return "logger" in source;
}
function isContext(source) {
return "res" in source;
}
function findLogger(source) {
if (isAlokaiContainer(source)) {
return source?.logger;
}
const base = isContext(source) ? source?.res : source;
return base?.locals?.alokai?.logger;
}
function getLogger(source) {
const logger = findLogger(source);
if (!logger) {
throw new Error("Logger instance could not be determined");
}
return logger;
}
const METHODS_TO_SKIP = ["log"];
function injectMetadata(logger, metadataGetter) {
return new Proxy(logger, {
get(target, prop) {
const shouldSkipMethod = typeof target[prop] !== "function" ||
METHODS_TO_SKIP.includes(prop);
if (!shouldSkipMethod) {
return (...args) => {
const [message, metadata] = args;
target[prop](message, {
...metadata,
...metadataGetter(metadata),
});
};
}
return target[prop];
},
});
}
/**
* Resolves dependencies based on the current working directory, not relative to this package.
*/
function resolveDependency(name) {
function resolveDependency(name, alokai) {
try {

@@ -36,3 +139,3 @@ // eslint-disable-next-line

catch (error) {
consola__default["default"].error(error);
alokai.logger.error(error);
throw new Error(`Could not resolve integration "${name}". See the error above for more details.`);

@@ -45,6 +148,8 @@ }

*/
function lookUpExternal(extension) {
return typeof extension === "string"
? resolveDependency(extension)
: [extension];
function lookUpExternal(alokai) {
return function (extension) {
return typeof extension === "string"
? resolveDependency(extension, alokai)
: [extension];
};
}

@@ -55,4 +160,4 @@

*/
function createExtensions(rawExtensions) {
return rawExtensions.flatMap(lookUpExternal);
function createExtensions(rawExtensions, alokai) {
return rawExtensions.flatMap(lookUpExternal(alokai));
}

@@ -75,2 +180,5 @@ /**

}
function isExtensionEndpointHandler(obj) {
return "_extensionName" in obj;
}

@@ -153,8 +261,8 @@ const STATUS_FIELDS = ["status", "statusCode"];

async function getInitConfig({ apiClient, tag, integration, }) {
async function getInitConfig({ apiClient, tag, integration, alokai, }) {
if (isFunction(apiClient?.init)) {
try {
consola__default["default"].success(`- Integration: ${tag} init function Start!`);
const initConfig = await apiClient.init(integration.configuration);
consola__default["default"].success(`- Integration: ${tag} init function Done!`);
alokai.logger.debug(`- Integration: ${tag} init function Start!`);
const initConfig = await apiClient.init(integration.configuration, alokai);
alokai.logger.debug(`- Integration: ${tag} init function Done!`);
return initConfig;

@@ -177,3 +285,4 @@ }

const defaultErrorHandler = (error, req, res) => {
consola__default["default"].error(error);
const logger = getLogger(res);
logger.error(error);
const status = getAgnosticStatusCode(error);

@@ -196,33 +305,65 @@ res.status(status);

async function registerIntegrations(app, integrations) {
return await Object.entries(integrations).reduce(async (prevAsync, [tag, integration]) => {
consola__default["default"].info(`- Loading: ${tag} ${integration.location}`);
const prev = await prevAsync;
const apiClient = resolveDependency(integration.location);
const rawExtensions = createRawExtensions(apiClient, integration);
const extensions = createExtensions(rawExtensions);
const initConfig = await getInitConfig({ apiClient, integration, tag });
const configuration = {
...integration.configuration,
integrationName: tag,
};
for (const { name, extendApp } of extensions) {
consola__default["default"].info(`- Loading: ${tag} extension: ${name}`);
if (extendApp) {
await extendApp({ app, configuration });
}
function buildAlokaiContainer(tag, loggerManager) {
const logger = loggerManager.get(tag);
const loggerWithMetadata = injectMetadata(logger, (metadata) => ({
context: "middleware",
...metadata,
}));
return { logger: loggerWithMetadata };
}
async function triggerExtendAppHook(tag, extensions, app, configuration, alokai) {
const logger = getLogger(alokai);
for (const { name, extendApp } of extensions) {
logger.debug(`- Loading: ${tag} extension: ${name}`);
if (extendApp) {
const loggerWithMetadata = injectMetadata(logger, () => ({
scope: {
extensionName: name,
hookName: "extendApp",
},
}));
await extendApp({ app, configuration, logger: loggerWithMetadata });
}
consola__default["default"].success(`- Integration: ${tag} loaded!`);
return {
...prev,
[tag]: {
apiClient,
extensions,
initConfig,
configuration,
customQueries: integration.customQueries,
errorHandler: integration.errorHandler ?? defaultErrorHandler,
},
}
}
async function loadIntegration(tag, integration, app, alokai) {
const apiClient = resolveDependency(integration.location, alokai);
const rawExtensions = createRawExtensions(apiClient, integration);
const extensions = createExtensions(rawExtensions, alokai);
const initConfig = await getInitConfig({
apiClient,
integration,
tag,
alokai,
});
const configuration = {
...integration.configuration,
integrationName: tag,
};
await triggerExtendAppHook(tag, extensions, app, configuration, alokai);
return {
apiClient,
extensions,
initConfig,
configuration,
};
}
async function registerIntegrations(app, integrations, loggerManager) {
const loadedIntegrations = {};
for (const [tag, integration] of Object.entries(integrations)) {
const alokai = buildAlokaiContainer(tag, loggerManager);
const logger = getLogger(alokai);
logger.debug(`- Loading: ${tag} ${integration.location}`);
const { apiClient, extensions, initConfig, configuration } = await loadIntegration(tag, integration, app, alokai);
loadedIntegrations[tag] = {
apiClient,
extensions,
initConfig,
configuration,
customQueries: integration.customQueries,
errorHandler: integration.errorHandler ?? defaultErrorHandler,
};
}, Promise.resolve({}));
logger.debug(`- Integration: ${tag} loaded!`);
}
return loadedIntegrations;
}

@@ -274,2 +415,10 @@

}
res.locals.alokai.metadata = {
...res.locals?.alokai?.metadata,
scope: {
integrationName,
extensionName,
functionName,
},
};
const { apiClient, configuration, extensions, customQueries = {}, initConfig, } = integrations[integrationName];

@@ -352,2 +501,72 @@ const middlewareContext = {

/**
* Middleware responsible for adding metadata storage to the res.locals.alokai,
* it's used by logger to provide valuable insights about source of the log.
*
* @remarks How to access the metadata?
* Metadata is available inside res.locals.alokai.metadata. It's adjusted with
* new data in different moments of request's flow. If you are not using
* it's value instantly but want to pass a reference then create
* a getter function to always access the fresh value.
*
* ```ts
* // Example of getter
* (res) => res.locals.alokai.metadata
* ```
*
* @remarks How to extend the metadata?
* Append new fields to the metadata object. Remember to not overwrite
* whole object as it's adjusted with new valuable data in different
* moments of request's flow.
*
* ```ts
* res.locals.alokai.metadata = {
* ...res.locals.alokai.metadata,
* myNewField: 'hello'
* };
*
* // or
* res.locals.alokai.metadata.myNewField = 'hello';
* ```
*/
async function prepareMetadataStorage(req, res, next) {
if (!res.locals) {
res.locals = {};
}
if (!res.locals.alokai) {
res.locals.alokai = {};
}
res.locals.alokai.metadata = {
context: "middleware",
};
next();
}
function prepareLogger(loggerManager) {
return function (req, res, next) {
if (!res.locals) {
res.locals = {};
}
if (!res.locals.alokai) {
res.locals.alokai = {};
}
const logger = loggerManager.get(req?.params?.integrationName);
if (!req?.params?.integrationName) {
console.error("prepareLogger middleware used for unsupported route");
}
const loggerWithMetadata = injectMetadata(logger, (metadata) => {
return {
...res.locals?.alokai?.metadata,
...metadata,
scope: {
...res.locals?.alokai?.metadata?.scope,
...metadata?.scope,
},
};
});
res.locals.alokai.logger = loggerWithMetadata;
next();
};
}
const createReadyzHandler = (readinessChecks) => async () => {

@@ -382,2 +601,6 @@ // call all provided readiness checks in parallel

async function createServer(config, options = {}) {
const loggerManager = new LoggerManager(config, (loggerConfig) => logger.LoggerFactory.create(logger.LoggerType.ConsolaGcp, loggerConfig));
const logger$1 = injectMetadata(loggerManager.get(), () => ({
context: "middleware",
}));
const app = express__default["default"]();

@@ -390,3 +613,3 @@ app.use(express__default["default"].json(options.bodyParser));

app.disable("x-powered-by");
consola__default["default"].info("Middleware starting....");
logger$1.info("Middleware starting....");
const helmetOptions = {

@@ -405,9 +628,9 @@ contentSecurityPolicy: false,

app.use(helmet__default["default"](helmetOptions));
consola__default["default"].info("VSF `Helmet` middleware added");
logger$1.debug("VSF `Helmet` middleware added");
}
consola__default["default"].info("Loading integrations...");
const integrations = await registerIntegrations(app, config.integrations);
consola__default["default"].success("Integrations loaded!");
app.post("/:integrationName/:extensionName?/:functionName", prepareApiFunction(integrations), prepareErrorHandler(integrations), prepareArguments, callApiFunction);
app.get("/:integrationName/:extensionName?/:functionName", prepareApiFunction(integrations), prepareErrorHandler(integrations), prepareArguments, callApiFunction);
logger$1.debug("Loading integrations...");
const integrations = await registerIntegrations(app, config.integrations, loggerManager);
logger$1.debug("Integrations loaded!");
app.post("/:integrationName/:extensionName?/:functionName", prepareMetadataStorage, prepareLogger(loggerManager), prepareApiFunction(integrations), prepareErrorHandler(integrations), prepareArguments, callApiFunction);
app.get("/:integrationName/:extensionName?/:functionName", prepareMetadataStorage, prepareLogger(loggerManager), prepareApiFunction(integrations), prepareErrorHandler(integrations), prepareArguments, callApiFunction);
// This could instead be implemented as a healthcheck within terminus, but we don't want /healthz to change response if app received SIGTERM

@@ -419,3 +642,3 @@ app.get("/healthz", (_req, res) => {

terminus.createTerminus(server, createTerminusOptions(options.readinessProbes));
consola__default["default"].success("Middleware created!");
logger$1.info("Middleware created!");
return server;

@@ -455,2 +678,23 @@ }

const apiClientContext = { ...context, extendQuery };
if (isExtensionEndpointHandler(fn)) {
if (context.res.locals?.alokai?.metadata?.scope) {
context.res.locals.alokai.metadata.scope.extensionName =
fn._extensionName;
}
else {
const logger = getLogger(context.res);
logger.warning(`Alokai's metadata object is missing in the context under 'res.locals.alokai'.
This could indicate that the extension's scope or metadata has not been properly initialized.
Without this metadata, certain custom API client functionalities may not work as expected,
including tracking of extension-specific actions or data.
Please ensure that the Alokai metadata object is correctly set up in 'res.locals.alokai' before proceeding with the request.
Steps to troubleshoot:
1. Verify if content of res.locals hasn't been overwritten, instead of extended.
2. If you're unsure, please consult Alokai's team for further assistance or review the source code implementation.
Call Name: ${callName}
Function Name: ${fn.name || "Unnamed function"}`);
}
}
const response = await fn(apiClientContext, ...transformedArgs);

@@ -466,2 +710,10 @@ const transformedResponse = await hooks.after({

/**
* Function marking endpoint added or overwritten by extension's extendApiMethod hook
* with information about source extension's name
*/
function markWithExtensionName(apiMethod, extensionName) {
apiMethod._extensionName = extensionName;
return apiMethod;
}
const apiClientFactory = (factoryParams) => {

@@ -476,5 +728,22 @@ const resolveApi = async (api, settings) => {

const rawExtensions = this?.middleware?.extensions || [];
const logger = getLogger(this.middleware.res);
const lifecycles = await Promise.all(rawExtensions
.filter((extension) => isFunction(extension?.hooks))
.map(async ({ hooks }) => hooks(this?.middleware?.req, this?.middleware?.res)));
.map(async ({ name, hooks }) => {
// Attaching extension related metadata to the logger
// We cannot assign it to res.locals as we would end up
// with incorrect logger for hook functions (like beforeCreate)
// in case of multiple extensions using hooks property
const loggerWithMetadata = injectMetadata(logger, (metadata) => ({
...metadata,
scope: {
...metadata?.scope,
extensionName: name,
extensionNamePointsHookSource: true, // If we have hook on custom endpoint, extensionName value is confusing without this field
},
}));
return hooks(this?.middleware?.req, this?.middleware?.res, {
logger: loggerWithMetadata,
});
}));
const _config = await lifecycles

@@ -489,3 +758,3 @@ .filter((extension) => isFunction(extension?.beforeCreate))

const settings = (await factoryParams.onCreate)
? await factoryParams.onCreate(_config)
? await factoryParams.onCreate(_config, { logger })
: { config, client: config.client };

@@ -539,5 +808,11 @@ settings.config = await lifecycles

else {
const markedExtendedApiMethods = Object.entries(extendedApiMethods).reduce((total, [name, fn]) => {
return {
...total,
[name]: markWithExtensionName(fn, extension.name),
};
}, {});
sharedExtensions = {
...sharedExtensions,
...extendedApiMethods,
...markedExtendedApiMethods,
};

@@ -583,2 +858,3 @@ }

exports.createTerminusOptions = createTerminusOptions;
exports.getLogger = getLogger;
//# sourceMappingURL=index.cjs.js.map

@@ -5,2 +5,3 @@ export * from "./types";

export * from "./terminus";
export { getLogger } from "./logger";
//# sourceMappingURL=index.d.ts.map

@@ -1,2 +0,1 @@

import consola from 'consola';
import cookieParser from 'cookie-parser';

@@ -8,8 +7,113 @@ import cors from 'cors';

import { HealthCheckError, createTerminus } from '@godaddy/terminus';
import { LoggerFactory, LoggerType } from '@vue-storefront/logger';
import { setTimeout } from 'node:timers/promises';
const GLOBAL_MIDDLEWARE_CFG_KEY = Symbol("GLOBAL_MIDDLEWARE_CFG_KEY");
/**
* Internal class responsible for spawning minimal amount of Logger's instances
* on boostrap of the application.
*
* @example Spawning minimal amount of Logger's instances and accessing it
* ```ts
* const config = // ... it's whole middleware's config
* const buildLogger = (loggerConfig) => LoggerFactory.create(LoggerType.ConsolaGcp, loggerConfig); // function responsible for creating new instance of Logger, it gets already resolved configuration for new instance as an argument
*
* const loggerManager = new LoggerManager(
* config,
* buildLogger
* );
* ```
*/
class LoggerManager {
constructor(config, buildLogger) {
this.instances = {};
// Global Logger
this.instances[GLOBAL_MIDDLEWARE_CFG_KEY] = this.selectLogger(config, buildLogger);
// Logger for every integration having custom logger config
for (const [integrationName, integrationEntry] of Object.entries(config.integrations)) {
if (this.hasCustomLoggerConfig(integrationEntry)) {
this.instances[integrationName] = this.selectLogger(integrationEntry, buildLogger, integrationName);
}
}
}
/**
* Selects custom logger provided by user with fallback to default logger.
* Prints console.warn if provided redudant custom options with custom handler.
*/
selectLogger(config, buildLogger, tag = "global") {
const logger = config.logger?.handler || buildLogger(config.logger);
if (config.logger?.handler) {
if (Object.keys(config.logger).length > 1) {
/**
* This codeblock will be triggered if end-developer provided custom logger and custom options.
* As they might think custom options will be provided to the logger, then their logger could stop working.
* That's why we decided to use console instead of created logger here.
*/
// eslint-disable-next-line no-console
console.warn(`Both handler and options are provided to ${tag} logger's options. Using handler, options will be ignored.`);
}
}
return logger;
}
/**
* Check if integration's config has configuration for dedicated logger instance.
*/
hasCustomLoggerConfig(integrationEntry) {
return Boolean(integrationEntry.logger);
}
/**
* Returns instance of logger for requested integration from internal storage.
* Called without an argument returns instance of global logger.
*/
get(integrationName) {
return (this.instances[integrationName] ||
this.instances[GLOBAL_MIDDLEWARE_CFG_KEY]);
}
}
function isAlokaiContainer(source) {
return "logger" in source;
}
function isContext(source) {
return "res" in source;
}
function findLogger(source) {
if (isAlokaiContainer(source)) {
return source?.logger;
}
const base = isContext(source) ? source?.res : source;
return base?.locals?.alokai?.logger;
}
function getLogger(source) {
const logger = findLogger(source);
if (!logger) {
throw new Error("Logger instance could not be determined");
}
return logger;
}
const METHODS_TO_SKIP = ["log"];
function injectMetadata(logger, metadataGetter) {
return new Proxy(logger, {
get(target, prop) {
const shouldSkipMethod = typeof target[prop] !== "function" ||
METHODS_TO_SKIP.includes(prop);
if (!shouldSkipMethod) {
return (...args) => {
const [message, metadata] = args;
target[prop](message, {
...metadata,
...metadataGetter(metadata),
});
};
}
return target[prop];
},
});
}
/**
* Resolves dependencies based on the current working directory, not relative to this package.
*/
function resolveDependency(name) {
function resolveDependency(name, alokai) {
try {

@@ -20,3 +124,3 @@ // eslint-disable-next-line

catch (error) {
consola.error(error);
alokai.logger.error(error);
throw new Error(`Could not resolve integration "${name}". See the error above for more details.`);

@@ -29,6 +133,8 @@ }

*/
function lookUpExternal(extension) {
return typeof extension === "string"
? resolveDependency(extension)
: [extension];
function lookUpExternal(alokai) {
return function (extension) {
return typeof extension === "string"
? resolveDependency(extension, alokai)
: [extension];
};
}

@@ -39,4 +145,4 @@

*/
function createExtensions(rawExtensions) {
return rawExtensions.flatMap(lookUpExternal);
function createExtensions(rawExtensions, alokai) {
return rawExtensions.flatMap(lookUpExternal(alokai));
}

@@ -59,2 +165,5 @@ /**

}
function isExtensionEndpointHandler(obj) {
return "_extensionName" in obj;
}

@@ -137,8 +246,8 @@ const STATUS_FIELDS = ["status", "statusCode"];

async function getInitConfig({ apiClient, tag, integration, }) {
async function getInitConfig({ apiClient, tag, integration, alokai, }) {
if (isFunction(apiClient?.init)) {
try {
consola.success(`- Integration: ${tag} init function Start!`);
const initConfig = await apiClient.init(integration.configuration);
consola.success(`- Integration: ${tag} init function Done!`);
alokai.logger.debug(`- Integration: ${tag} init function Start!`);
const initConfig = await apiClient.init(integration.configuration, alokai);
alokai.logger.debug(`- Integration: ${tag} init function Done!`);
return initConfig;

@@ -161,3 +270,4 @@ }

const defaultErrorHandler = (error, req, res) => {
consola.error(error);
const logger = getLogger(res);
logger.error(error);
const status = getAgnosticStatusCode(error);

@@ -180,33 +290,65 @@ res.status(status);

async function registerIntegrations(app, integrations) {
return await Object.entries(integrations).reduce(async (prevAsync, [tag, integration]) => {
consola.info(`- Loading: ${tag} ${integration.location}`);
const prev = await prevAsync;
const apiClient = resolveDependency(integration.location);
const rawExtensions = createRawExtensions(apiClient, integration);
const extensions = createExtensions(rawExtensions);
const initConfig = await getInitConfig({ apiClient, integration, tag });
const configuration = {
...integration.configuration,
integrationName: tag,
};
for (const { name, extendApp } of extensions) {
consola.info(`- Loading: ${tag} extension: ${name}`);
if (extendApp) {
await extendApp({ app, configuration });
}
function buildAlokaiContainer(tag, loggerManager) {
const logger = loggerManager.get(tag);
const loggerWithMetadata = injectMetadata(logger, (metadata) => ({
context: "middleware",
...metadata,
}));
return { logger: loggerWithMetadata };
}
async function triggerExtendAppHook(tag, extensions, app, configuration, alokai) {
const logger = getLogger(alokai);
for (const { name, extendApp } of extensions) {
logger.debug(`- Loading: ${tag} extension: ${name}`);
if (extendApp) {
const loggerWithMetadata = injectMetadata(logger, () => ({
scope: {
extensionName: name,
hookName: "extendApp",
},
}));
await extendApp({ app, configuration, logger: loggerWithMetadata });
}
consola.success(`- Integration: ${tag} loaded!`);
return {
...prev,
[tag]: {
apiClient,
extensions,
initConfig,
configuration,
customQueries: integration.customQueries,
errorHandler: integration.errorHandler ?? defaultErrorHandler,
},
}
}
async function loadIntegration(tag, integration, app, alokai) {
const apiClient = resolveDependency(integration.location, alokai);
const rawExtensions = createRawExtensions(apiClient, integration);
const extensions = createExtensions(rawExtensions, alokai);
const initConfig = await getInitConfig({
apiClient,
integration,
tag,
alokai,
});
const configuration = {
...integration.configuration,
integrationName: tag,
};
await triggerExtendAppHook(tag, extensions, app, configuration, alokai);
return {
apiClient,
extensions,
initConfig,
configuration,
};
}
async function registerIntegrations(app, integrations, loggerManager) {
const loadedIntegrations = {};
for (const [tag, integration] of Object.entries(integrations)) {
const alokai = buildAlokaiContainer(tag, loggerManager);
const logger = getLogger(alokai);
logger.debug(`- Loading: ${tag} ${integration.location}`);
const { apiClient, extensions, initConfig, configuration } = await loadIntegration(tag, integration, app, alokai);
loadedIntegrations[tag] = {
apiClient,
extensions,
initConfig,
configuration,
customQueries: integration.customQueries,
errorHandler: integration.errorHandler ?? defaultErrorHandler,
};
}, Promise.resolve({}));
logger.debug(`- Integration: ${tag} loaded!`);
}
return loadedIntegrations;
}

@@ -258,2 +400,10 @@

}
res.locals.alokai.metadata = {
...res.locals?.alokai?.metadata,
scope: {
integrationName,
extensionName,
functionName,
},
};
const { apiClient, configuration, extensions, customQueries = {}, initConfig, } = integrations[integrationName];

@@ -336,2 +486,72 @@ const middlewareContext = {

/**
* Middleware responsible for adding metadata storage to the res.locals.alokai,
* it's used by logger to provide valuable insights about source of the log.
*
* @remarks How to access the metadata?
* Metadata is available inside res.locals.alokai.metadata. It's adjusted with
* new data in different moments of request's flow. If you are not using
* it's value instantly but want to pass a reference then create
* a getter function to always access the fresh value.
*
* ```ts
* // Example of getter
* (res) => res.locals.alokai.metadata
* ```
*
* @remarks How to extend the metadata?
* Append new fields to the metadata object. Remember to not overwrite
* whole object as it's adjusted with new valuable data in different
* moments of request's flow.
*
* ```ts
* res.locals.alokai.metadata = {
* ...res.locals.alokai.metadata,
* myNewField: 'hello'
* };
*
* // or
* res.locals.alokai.metadata.myNewField = 'hello';
* ```
*/
async function prepareMetadataStorage(req, res, next) {
if (!res.locals) {
res.locals = {};
}
if (!res.locals.alokai) {
res.locals.alokai = {};
}
res.locals.alokai.metadata = {
context: "middleware",
};
next();
}
function prepareLogger(loggerManager) {
return function (req, res, next) {
if (!res.locals) {
res.locals = {};
}
if (!res.locals.alokai) {
res.locals.alokai = {};
}
const logger = loggerManager.get(req?.params?.integrationName);
if (!req?.params?.integrationName) {
console.error("prepareLogger middleware used for unsupported route");
}
const loggerWithMetadata = injectMetadata(logger, (metadata) => {
return {
...res.locals?.alokai?.metadata,
...metadata,
scope: {
...res.locals?.alokai?.metadata?.scope,
...metadata?.scope,
},
};
});
res.locals.alokai.logger = loggerWithMetadata;
next();
};
}
const createReadyzHandler = (readinessChecks) => async () => {

@@ -366,2 +586,6 @@ // call all provided readiness checks in parallel

async function createServer(config, options = {}) {
const loggerManager = new LoggerManager(config, (loggerConfig) => LoggerFactory.create(LoggerType.ConsolaGcp, loggerConfig));
const logger = injectMetadata(loggerManager.get(), () => ({
context: "middleware",
}));
const app = express();

@@ -374,3 +598,3 @@ app.use(express.json(options.bodyParser));

app.disable("x-powered-by");
consola.info("Middleware starting....");
logger.info("Middleware starting....");
const helmetOptions = {

@@ -389,9 +613,9 @@ contentSecurityPolicy: false,

app.use(helmet(helmetOptions));
consola.info("VSF `Helmet` middleware added");
logger.debug("VSF `Helmet` middleware added");
}
consola.info("Loading integrations...");
const integrations = await registerIntegrations(app, config.integrations);
consola.success("Integrations loaded!");
app.post("/:integrationName/:extensionName?/:functionName", prepareApiFunction(integrations), prepareErrorHandler(integrations), prepareArguments, callApiFunction);
app.get("/:integrationName/:extensionName?/:functionName", prepareApiFunction(integrations), prepareErrorHandler(integrations), prepareArguments, callApiFunction);
logger.debug("Loading integrations...");
const integrations = await registerIntegrations(app, config.integrations, loggerManager);
logger.debug("Integrations loaded!");
app.post("/:integrationName/:extensionName?/:functionName", prepareMetadataStorage, prepareLogger(loggerManager), prepareApiFunction(integrations), prepareErrorHandler(integrations), prepareArguments, callApiFunction);
app.get("/:integrationName/:extensionName?/:functionName", prepareMetadataStorage, prepareLogger(loggerManager), prepareApiFunction(integrations), prepareErrorHandler(integrations), prepareArguments, callApiFunction);
// This could instead be implemented as a healthcheck within terminus, but we don't want /healthz to change response if app received SIGTERM

@@ -403,3 +627,3 @@ app.get("/healthz", (_req, res) => {

createTerminus(server, createTerminusOptions(options.readinessProbes));
consola.success("Middleware created!");
logger.info("Middleware created!");
return server;

@@ -439,2 +663,23 @@ }

const apiClientContext = { ...context, extendQuery };
if (isExtensionEndpointHandler(fn)) {
if (context.res.locals?.alokai?.metadata?.scope) {
context.res.locals.alokai.metadata.scope.extensionName =
fn._extensionName;
}
else {
const logger = getLogger(context.res);
logger.warning(`Alokai's metadata object is missing in the context under 'res.locals.alokai'.
This could indicate that the extension's scope or metadata has not been properly initialized.
Without this metadata, certain custom API client functionalities may not work as expected,
including tracking of extension-specific actions or data.
Please ensure that the Alokai metadata object is correctly set up in 'res.locals.alokai' before proceeding with the request.
Steps to troubleshoot:
1. Verify if content of res.locals hasn't been overwritten, instead of extended.
2. If you're unsure, please consult Alokai's team for further assistance or review the source code implementation.
Call Name: ${callName}
Function Name: ${fn.name || "Unnamed function"}`);
}
}
const response = await fn(apiClientContext, ...transformedArgs);

@@ -450,2 +695,10 @@ const transformedResponse = await hooks.after({

/**
* Function marking endpoint added or overwritten by extension's extendApiMethod hook
* with information about source extension's name
*/
function markWithExtensionName(apiMethod, extensionName) {
apiMethod._extensionName = extensionName;
return apiMethod;
}
const apiClientFactory = (factoryParams) => {

@@ -460,5 +713,22 @@ const resolveApi = async (api, settings) => {

const rawExtensions = this?.middleware?.extensions || [];
const logger = getLogger(this.middleware.res);
const lifecycles = await Promise.all(rawExtensions
.filter((extension) => isFunction(extension?.hooks))
.map(async ({ hooks }) => hooks(this?.middleware?.req, this?.middleware?.res)));
.map(async ({ name, hooks }) => {
// Attaching extension related metadata to the logger
// We cannot assign it to res.locals as we would end up
// with incorrect logger for hook functions (like beforeCreate)
// in case of multiple extensions using hooks property
const loggerWithMetadata = injectMetadata(logger, (metadata) => ({
...metadata,
scope: {
...metadata?.scope,
extensionName: name,
extensionNamePointsHookSource: true, // If we have hook on custom endpoint, extensionName value is confusing without this field
},
}));
return hooks(this?.middleware?.req, this?.middleware?.res, {
logger: loggerWithMetadata,
});
}));
const _config = await lifecycles

@@ -473,3 +743,3 @@ .filter((extension) => isFunction(extension?.beforeCreate))

const settings = (await factoryParams.onCreate)
? await factoryParams.onCreate(_config)
? await factoryParams.onCreate(_config, { logger })
: { config, client: config.client };

@@ -523,5 +793,11 @@ settings.config = await lifecycles

else {
const markedExtendedApiMethods = Object.entries(extendedApiMethods).reduce((total, [name, fn]) => {
return {
...total,
[name]: markWithExtensionName(fn, extension.name),
};
}, {});
sharedExtensions = {
...sharedExtensions,
...extendedApiMethods,
...markedExtendedApiMethods,
};

@@ -563,3 +839,3 @@ }

export { apiClientFactory, createReadyzHandler, createServer, createTerminusOptions };
export { apiClientFactory, createReadyzHandler, createServer, createTerminusOptions, getLogger };
//# sourceMappingURL=index.es.js.map

4

lib/integrations/helpers/createExtensions.d.ts

@@ -1,6 +0,6 @@

import type { Integration, ApiClientFactory, ApiClientExtension, ApiMethods, TObject } from "../../types";
import type { Integration, ApiClientFactory, ApiClientExtension, ApiMethods, TObject, AlokaiContainer } from "../../types";
/**
* Imports extensions if they're represented as strings.
*/
export declare function createExtensions(rawExtensions: (ApiClientExtension | string)[]): ApiClientExtension[];
export declare function createExtensions(rawExtensions: (ApiClientExtension | string)[], alokai: AlokaiContainer): ApiClientExtension[];
/**

@@ -7,0 +7,0 @@ * Creates an array of extensions schemas or their paths.

import { LoadInitConfigProps, TObject } from "../../types";
export declare function getInitConfig({ apiClient, tag, integration, }: LoadInitConfigProps): Promise<TObject>;
export declare function getInitConfig({ apiClient, tag, integration, alokai, }: LoadInitConfigProps): Promise<TObject>;
//# sourceMappingURL=getInitConfig.d.ts.map

@@ -1,6 +0,6 @@

import type { ApiClientExtension } from "../../types";
import type { AlokaiContainer, ApiClientExtension } from "../../types";
/**
* Imports extensions from the current working directory if they're represented as strings.
*/
export declare function lookUpExternal(extension: string | ApiClientExtension): ApiClientExtension[];
export declare function lookUpExternal(alokai: AlokaiContainer): (extension: string | ApiClientExtension) => ApiClientExtension[];
//# sourceMappingURL=lookUpExternal.d.ts.map

@@ -0,5 +1,6 @@

import { AlokaiContainer } from "../../types";
/**
* Resolves dependencies based on the current working directory, not relative to this package.
*/
export declare function resolveDependency<T>(name: string): T;
export declare function resolveDependency<T>(name: string, alokai: AlokaiContainer): T;
//# sourceMappingURL=resolveDependency.d.ts.map
import type { Express } from "express";
import type { Integrations, IntegrationsLoaded } from "../types";
export declare function registerIntegrations(app: Express, integrations: Integrations): Promise<IntegrationsLoaded>;
import { LoggerManager } from "../logger";
export declare function registerIntegrations(app: Express, integrations: Integrations, loggerManager: LoggerManager): Promise<IntegrationsLoaded>;
//# sourceMappingURL=registerIntegrations.d.ts.map
import type { Express, Request, Response } from "express";
import { LoggerInterface } from "@vue-storefront/logger";
import { LoggerOptions } from "./config";
import { ApiClientMethod, ContextQuery, CustomQuery, CustomQueryFunction, TObject } from "./base";
import { WithRequired } from "./index";
import { ApiClient, ApiClientConfig, ApiClientFactory } from "./server";
export type ApiMethods = Record<string, ApiClientMethod>;
export type ExtensionEndpointHandler = ApiClientMethod & {
_extensionName?: string;
};
export type ApiMethods = Record<string, ApiClientMethod | ExtensionEndpointHandler>;
export type ApiMethodsFactory<API extends ApiMethods, CONFIG extends ApiClientConfig> = (configuration: CONFIG) => API;

@@ -34,2 +39,5 @@ export type ApiClientMethodWithContext<CONTEXT> = (context: CONTEXT, ...args: any) => any;

}
export type AlokaiContainer = {
logger: LoggerInterface;
};
export interface ApiClientExtension<API = any, CONTEXT = any, CONFIG = any> {

@@ -42,4 +50,5 @@ name: string;

configuration: any;
logger: LoggerInterface;
}) => Promise<void> | void;
hooks?: (req: Request, res: Response) => ApiClientExtensionHooks;
hooks?: (req: Request, res: Response, hooksContext: AlokaiContainer) => ApiClientExtensionHooks;
}

@@ -49,2 +58,3 @@ export interface Integration<CONFIG extends TObject = any, API extends ApiMethods = any, CONTEXT extends TObject = any> {

configuration: CONFIG;
logger?: LoggerOptions;
extensions?: <T extends ApiClientMethodWithContext<CONTEXT>>(extensions: ApiClientExtension<API, CONTEXT>[]) => ApiClientExtension<API & T, CONTEXT>[];

@@ -71,2 +81,3 @@ customQueries?: Record<string, CustomQueryFunction>;

tag: string;
alokai: AlokaiContainer;
}

@@ -73,0 +84,0 @@ export type IntegrationsLoaded<CONFIG extends ApiClientConfig = any, API extends ApiMethods = any> = Record<string, IntegrationLoaded<CONFIG, API>>;

import type { HelmetOptions } from "helmet";
import { LoggerInterface } from "@vue-storefront/logger";
import { Integration } from "./common";
import { TObject } from "./base";
import { IntegrationContext } from "./server";
/**
* Based on the syslog levels defined in RFC 5424.
*
* @see https://datatracker.ietf.org/doc/html/rfc5424
*/
export type LogLevel = "emergency" | "alert" | "critical" | "error" | "warning" | "notice" | "info" | "debug";
/**
* Options for the logger.
*/
export interface LoggerOptions {
/**
* The log level aligned with RFC5424.
*/
level?: LogLevel;
/**
* Whether to include the stack trace in the log message.
*/
includeStackTrace?: boolean;
/**
* Own implementation of logger to be used internally.
*
* @remarks If provided then other options won't be used.
*/
handler?: LoggerInterface;
}
export interface Helmet extends HelmetOptions {

@@ -12,2 +38,3 @@ helmet?: boolean | HelmetOptions;

export interface MiddlewareConfig<TIntegrationsContext extends Record<string, IntegrationContext> = Record<string, IntegrationContext>> {
logger?: LoggerOptions;
integrations: Integrations<TIntegrationsContext>;

@@ -14,0 +41,0 @@ helmet?: boolean | Readonly<HelmetOptions>;

@@ -5,3 +5,3 @@ import { CorsOptions, CorsOptionsDelegate } from "cors";

import { TObject } from "./base";
import { ApiClientExtension, ApiMethods, ApiMethodsFactory, MiddlewareContext } from "./common";
import { AlokaiContainer, ApiClientExtension, ApiMethods, ApiMethodsFactory, MiddlewareContext } from "./common";
export interface ClientContext<CLIENT = any, CONFIG = any> {

@@ -62,3 +62,3 @@ client: CLIENT;

isProxy?: boolean;
onCreate: (config: CONFIG, headers?: Record<string, string>) => Promise<{
onCreate: (config: CONFIG, alokai: AlokaiContainer) => Promise<{
client: CLIENT;

@@ -77,3 +77,3 @@ config: ApiClientConfig;

*/
init?: (configuration: TObject) => TObject;
init?: (configuration: TObject, alokai: AlokaiContainer) => TObject;
}

@@ -80,0 +80,0 @@ export type CreateApiProxyFn = <CONFIG, API, CLIENT>(givenConfig: any, customApi?: any) => ApiInstance<CONFIG, API, CLIENT>;

@@ -1,21 +0,13 @@

MIT License
# BSD 3-Clause License
Copyright (c) 2021 Vue Storefront
Copyright (c) 2024 Alokai
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
1. Redistributions of source code must retain the above copyright notice, this list of conditions, and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## Disclaimer
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
{
"name": "@vue-storefront/middleware",
"version": "5.0.0",
"version": "5.1.0-rc.0",
"main": "lib/index.cjs.js",

@@ -23,3 +23,3 @@ "module": "lib/index.es.js",

"dependencies": {
"consola": "^2.15.3",
"@vue-storefront/logger": "1.0.0-rc.3",
"cookie-parser": "^1.4.6",

@@ -26,0 +26,0 @@ "cors": "^2.8.5",

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc