@vue-storefront/middleware
Advanced tools
Comparing version 5.0.0 to 5.1.0-rc.0
@@ -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 |
@@ -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
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
116256
76
2157
1
2
+ Added@vue-storefront/logger@1.0.0-rc.3(transitive)
+ Addedconsola@3.4.0(transitive)
+ Addeddotenv@16.4.7(transitive)
- Removedconsola@^2.15.3
- Removedconsola@2.15.3(transitive)