scenario-mock-server
Version:
Mock server powered by scenarios
1,017 lines (997 loc) • 30.8 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
createExpressApp: () => createExpressApp,
run: () => run
});
module.exports = __toCommonJS(src_exports);
// src/run.ts
var import_server_with_kill = require("server-with-kill");
// src/express.ts
var import_node_path = __toESM(require("path"));
var import_cookie_parser = __toESM(require("cookie-parser"));
var import_cors = __toESM(require("cors"));
var import_express = __toESM(require("express"));
var import_zod3 = __toESM(require("zod"));
// src/ui.tsx
var import_react2 = __toESM(require("react"));
var import_server = require("react-dom/server");
// src/Html.tsx
var import_react = __toESM(require("react"));
function Html({
updatedScenarioName,
uiPath,
scenarios,
groups
}) {
return /* @__PURE__ */ import_react.default.createElement("html", { lang: "en" }, /* @__PURE__ */ import_react.default.createElement("head", null, /* @__PURE__ */ import_react.default.createElement("meta", { charSet: "utf-8" }), /* @__PURE__ */ import_react.default.createElement("meta", { name: "viewport", content: "width=device-width,initial-scale=1" }), /* @__PURE__ */ import_react.default.createElement("title", null, `${updatedScenarioName ? "Updated - " : ""}Scenarios - Scenario Mock
Server`), /* @__PURE__ */ import_react.default.createElement(
"link",
{
rel: "stylesheet",
href: `${uiPath}${uiPath.slice(-1) === "/" ? "" : "/"}index.css`
}
)), /* @__PURE__ */ import_react.default.createElement("body", null, /* @__PURE__ */ import_react.default.createElement("main", null, /* @__PURE__ */ import_react.default.createElement(ScenarioUpdateInfo, { updatedScenarioName }), /* @__PURE__ */ import_react.default.createElement("form", { className: "stack-1", method: "POST", action: uiPath }, /* @__PURE__ */ import_react.default.createElement("p", null, /* @__PURE__ */ import_react.default.createElement("a", { href: uiPath }, "Refresh page")), /* @__PURE__ */ import_react.default.createElement(CallToActionButton, null), /* @__PURE__ */ import_react.default.createElement("fieldset", { className: "stack-3" }, /* @__PURE__ */ import_react.default.createElement("legend", null, /* @__PURE__ */ import_react.default.createElement("h1", null, "Scenarios")), scenarios.some(({ group }) => group !== null) ? /* @__PURE__ */ import_react.default.createElement(GroupedScenarios, { groups, scenarios }) : /* @__PURE__ */ import_react.default.createElement(ScenarioList, { scenarios })), /* @__PURE__ */ import_react.default.createElement(CallToActionButton, null)))));
}
function ScenarioUpdateInfo({
updatedScenarioName
}) {
if (!updatedScenarioName) {
return null;
}
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, "Updated to the following scenario: ", updatedScenarioName);
}
function CallToActionButton() {
return /* @__PURE__ */ import_react.default.createElement("button", { type: "submit", name: "button", value: "modify" }, "Select scenario");
}
var NULL_GROUP_ID = "sms-other";
function GroupedScenarios({
groups,
scenarios
}) {
const groupedScenarios = {};
scenarios.forEach((scenario) => {
const group = scenario.group === null ? NULL_GROUP_ID : scenario.group;
groupedScenarios[group] = groupedScenarios[group] || [];
groupedScenarios[group].push(scenario);
});
const groupsWithLabelIds = Object.keys(groups);
const groupsWithoutLabelIds = Object.keys(groupedScenarios).filter(
(groupId) => !groupsWithLabelIds.includes(groupId) && groupId !== NULL_GROUP_ID
);
const groupEntries = Object.entries(groups).concat(groupsWithoutLabelIds.map((groupId) => [groupId, groupId])).concat([[NULL_GROUP_ID, "Other"]]);
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, groupEntries.map(([groupId, groupName]) => /* @__PURE__ */ import_react.default.createElement("div", { key: groupId }, /* @__PURE__ */ import_react.default.createElement("h2", null, groupName), /* @__PURE__ */ import_react.default.createElement(ScenarioList, { scenarios: groupedScenarios[groupId] }))));
}
function ScenarioList({ scenarios }) {
return /* @__PURE__ */ import_react.default.createElement("div", { className: "stack-3" }, scenarios.map((scenario) => /* @__PURE__ */ import_react.default.createElement("div", { key: scenario.id }, /* @__PURE__ */ import_react.default.createElement(
"input",
{
type: "radio",
id: scenario.id,
name: "scenarioId",
value: scenario.id,
defaultChecked: scenario.selected
}
), /* @__PURE__ */ import_react.default.createElement("label", { htmlFor: scenario.id }, scenario.name), scenario.description ? /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("br", null), /* @__PURE__ */ import_react.default.createElement("details", null, /* @__PURE__ */ import_react.default.createElement("summary", null, "Description"), /* @__PURE__ */ import_react.default.createElement("div", { className: "description" }, scenario.description))) : null)));
}
// src/cookies.ts
var import_zod = __toESM(require("zod"));
var CONTEXT_AND_SCENARIO_COOKIE_NAME = "scenario-mock-server";
function getScenarioIdFromCookie({
getCookie,
setCookie,
defaultValue
}) {
let cookieValue = defaultValue;
const cookie = getCookie(CONTEXT_AND_SCENARIO_COOKIE_NAME);
if (cookie) {
try {
cookieValue = JSON.parse(cookie);
} catch (error) {
setCookie(CONTEXT_AND_SCENARIO_COOKIE_NAME, JSON.stringify(cookieValue));
}
}
return cookieValue.scenarioId;
}
var cookieValueSchema = import_zod.default.object({
context: import_zod.default.object({}).passthrough(),
scenarioId: import_zod.default.string()
});
function getScenarioMockServerCookie({
getCookie,
initialScenarioId,
initialContext
}) {
const cookie = getCookie(CONTEXT_AND_SCENARIO_COOKIE_NAME);
if (cookie) {
try {
const parsedCookie = JSON.parse(cookie);
return cookieValueSchema.parse(parsedCookie);
} catch (error) {
console.error("Cookie value could not be parsed");
}
}
return {
scenarioId: initialScenarioId,
context: initialContext
};
}
function setScenarioMockServerCookie({
setCookie,
value
}) {
setCookie(CONTEXT_AND_SCENARIO_COOKIE_NAME, JSON.stringify(value));
}
// src/utils/get-scenario-ids.ts
function getScenarioIds(scenarioIds, scenario, scenarioMap) {
const scenarioIdsResult = [scenario.id].concat(scenarioIds);
if (scenario.extend) {
const scenarioToExtend = scenarioMap[scenario.extend];
if (scenarioToExtend) {
return getScenarioIds(scenarioIdsResult, scenarioToExtend, scenarioMap);
}
}
return scenarioIdsResult;
}
// src/utils/get-context-from-scenario.ts
function getContextFromScenario(scenario, scenarioMap) {
const scenarioIds = getScenarioIds([], scenario, scenarioMap);
let context = {};
for (const scenarioId of scenarioIds) {
const scenario2 = scenarioMap[scenarioId];
if (scenario2.context) {
context = __spreadValues(__spreadValues({}, context), scenario2.context);
}
}
return context;
}
// src/apis.ts
function getScenarios({
getCookie,
setCookie,
getServerScenarioId,
cookieMode,
initialScenarioId,
initialContext,
scenarios
}) {
const scenarioId = getScenarioId({
getCookie,
setCookie,
getServerScenarioId,
cookieMode,
initialScenarioId,
initialContext
});
const allScenarios = scenarios.map(
({ id, name, description, group }) => ({
id,
name,
description: description === void 0 ? null : description,
selected: id === scenarioId,
group: group === void 0 ? null : group
})
);
return {
status: 200,
headers: {
"content-type": "application/json"
},
data: allScenarios
};
}
function selectScenario({
scenarioId,
scenarioMap,
cookieMode,
setCookie,
setServerContext,
setServerScenarioId
}) {
const updatedScenario = scenarioMap[scenarioId];
if (!updatedScenario) {
return {
status: 400,
headers: {
"content-type": "application/json"
},
data: {
message: `Scenario id "${scenarioId}" does not exist`
}
};
}
updateScenarioAndContext({
cookieMode,
scenarioId,
scenarioMap,
setCookie,
setServerContext,
setServerScenarioId
});
return { status: 204 };
}
function updateScenarioAndContext({
scenarioId,
scenarioMap,
cookieMode,
setCookie,
setServerContext,
setServerScenarioId
}) {
const context = getContextFromScenario(scenarioMap[scenarioId], scenarioMap);
if (cookieMode) {
setScenarioMockServerCookie({
setCookie,
value: {
scenarioId,
context
}
});
return;
}
setServerContext(context);
setServerScenarioId(scenarioId);
}
function getScenarioId({
getCookie,
setCookie,
getServerScenarioId,
cookieMode,
initialScenarioId,
initialContext
}) {
if (cookieMode) {
return getScenarioIdFromCookie({
getCookie,
setCookie,
defaultValue: {
scenarioId: initialScenarioId,
context: initialContext
}
});
}
return getServerScenarioId();
}
// src/ui.tsx
function getUi({
uiPath,
cookieMode,
scenarios,
initialScenarioId,
initialContext,
getCookie,
setCookie,
getServerScenarioId,
groups
}) {
const { data } = getScenarios({
cookieMode,
initialContext,
initialScenarioId,
scenarios,
getCookie,
getServerScenarioId,
setCookie
});
const html = (0, import_server.renderToStaticMarkup)(
/* @__PURE__ */ import_react2.default.createElement(Html, { uiPath, scenarios: data, groups })
);
return "<!DOCTYPE html>\n" + html;
}
function updateUi({
scenarioId,
uiPath,
scenarioMap,
scenarios,
cookieMode,
setCookie,
setServerContext,
setServerScenarioId,
groups
}) {
const updatedScenarioName = scenarioMap[scenarioId].name;
selectScenario({
cookieMode,
scenarioId,
scenarioMap,
setCookie,
setServerContext,
setServerScenarioId
});
const allScenarios = scenarios.map(({ id, name, description, group }) => ({
id,
name,
description: description === void 0 ? null : description,
selected: id === scenarioId,
group: group === void 0 ? null : group
}));
const html = (0, import_server.renderToStaticMarkup)(
/* @__PURE__ */ import_react2.default.createElement(
Html,
{
uiPath,
scenarios: allScenarios,
groups,
updatedScenarioName
}
)
);
return "<!DOCTYPE html>\n" + html;
}
// src/handle-request.ts
var import_crypto = require("crypto");
// src/graph-ql.ts
var import_graphql = require("graphql");
var import_graphql_tag = __toESM(require("graphql-tag"));
var import_zod2 = __toESM(require("zod"));
// src/create-handler.ts
var DEFAULT_STATUS = 200;
var DEFAULT_DELAY = 0;
function createHandler({
response = {},
updateContext: updateContext2,
getContext
}) {
return async (req) => {
const result = isResponseFunction(response) ? await response(__spreadProps(__spreadValues({}, req), {
updateContext: updateContext2,
context: getContext()
})) : response;
const { status = DEFAULT_STATUS, data, delay = DEFAULT_DELAY } = result;
const headers = lowerCaseKeys(result.headers || {});
await wait(delay);
if (data !== void 0 && !headers["content-type"]) {
headers["content-type"] = "application/json";
}
return {
status,
data,
headers
};
};
}
function wait(responseDelay) {
return new Promise((res) => setTimeout(res, responseDelay));
}
function isResponseFunction(response) {
return typeof response === "function";
}
function lowerCaseKeys(obj) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value])
);
}
// src/graph-ql.ts
function isGraphQlMock(mock) {
return mock.method === "GRAPHQL";
}
function getGraphQlMocks(mocks) {
const graphQlMocksByPathAndOperations = {};
mocks.filter(isGraphQlMock).forEach(({ path: path2, operations }) => {
const operationsByTypeAndName = graphQlMocksByPathAndOperations[path2] ? graphQlMocksByPathAndOperations[path2] : {};
operations.forEach((operation) => {
operationsByTypeAndName[`${operation.type}${operation.name}`] = operation;
});
graphQlMocksByPathAndOperations[path2] = operationsByTypeAndName;
});
return Object.entries(graphQlMocksByPathAndOperations).map(
([path2, operationsByTypeAndName]) => ({
method: "GRAPHQL",
path: path2,
operations: Object.values(operationsByTypeAndName)
})
);
}
var bodySchema = import_zod2.default.object({
query: import_zod2.default.string().optional(),
operationName: import_zod2.default.string().optional(),
variables: import_zod2.default.object({}).passthrough().optional()
}).default({}).catch({});
function getGraphQlMock(path2, graphqlMocks) {
return graphqlMocks.find((graphQlMock) => graphQlMock.path === path2) || null;
}
function isOperationDefinition(definition) {
return definition.kind === import_graphql.Kind.OPERATION_DEFINITION;
}
async function graphQlRequestHandler({
req,
graphQlMock,
updateContext: updateContext2,
getContext
}) {
var _a;
if (!["GET", "POST"].includes(req.method)) {
return { status: 404 };
}
let query;
const body = bodySchema.parse(req.body);
if (req.headers["content-type"] === "application/graphql") {
query = typeof req.body === "string" ? req.body : "";
} else {
query = body.query || (Array.isArray(req.query.query) ? "" : req.query.query) || "";
}
let graphqlAst;
try {
graphqlAst = (0, import_graphql_tag.default)(query);
} catch (error) {
return {
status: 400,
headers: {
"Content-Type": "application/json"
},
data: { message: `query "${query}" is not a valid GraphQL query` }
};
}
const operationDefitions = graphqlAst.definitions.filter(
isOperationDefinition
);
if (operationDefitions.length > 1 && !body.operationName && !req.query.operationName) {
return {
status: 400,
headers: {
"Content-Type": "application/json"
},
data: {
message: `operationName required`
}
};
}
const operationName = body.operationName || req.query.operationName || ((_a = operationDefitions[0].name) == null ? void 0 : _a.value);
if (typeof operationName !== "string") {
return {
status: 400,
headers: {
"Content-Type": "application/json"
},
data: {
message: `Operation name required`
}
};
}
const operationDefinition = operationDefitions.find(
(definition) => {
var _a2;
return ((_a2 = definition.name) == null ? void 0 : _a2.value) === operationName;
}
);
if (!operationDefinition) {
return {
status: 400,
headers: {
"Content-Type": "application/json"
},
data: {
message: `Operation "${operationName}" could not be found`
}
};
}
const operationType = operationDefinition.operation;
if (operationType === "subscription") {
return {
status: 400,
headers: {
"Content-Type": "application/json"
},
data: {
message: `Subscriptions are not supported`
}
};
}
if (operationType === "mutation" && req.method === "GET") {
return {
status: 400,
headers: {
"Content-Type": "application/json"
},
data: {
message: `Mutations cannot be resolved over GET`
}
};
}
let variables = body.variables;
if (variables === void 0 && req.query.variables && !Array.isArray(req.query.variables)) {
try {
variables = JSON.parse(req.query.variables);
} catch (e) {
}
}
variables = variables || {};
const operation = graphQlMock.operations.find(
({ type, name }) => operationType === type && name === operationName
);
if (!operation) {
return { status: 404 };
}
const handler = createHandler({
getContext,
updateContext: updateContext2,
response: operation.response
});
return handler({ variables, headers: req.headers });
}
// src/http.ts
var import_path_to_regexp = require("path-to-regexp");
function isHttpMock(mock) {
return mock.method !== "GRAPHQL";
}
function getHttpMocks(mocks) {
const httpMocksByPathAndMethod = {};
mocks.filter(isHttpMock).forEach((mock) => {
const { path: path2, method } = mock;
httpMocksByPathAndMethod[`${String(path2)}${method}`] = mock;
});
return Object.values(httpMocksByPathAndMethod);
}
function httpRequestHandler({
req,
httpMock,
params,
updateContext: updateContext2,
getContext
}) {
const _a = req, { body } = _a, restOfReq = __objRest(_a, ["body"]);
const handler = createHandler(__spreadProps(__spreadValues({}, httpMock), {
getContext,
updateContext: updateContext2
}));
return handler(__spreadProps(__spreadValues({}, restOfReq), {
params,
body: typeof body === "string" ? {} : body
}));
}
function getHttpMockAndParams(req, httpMocks) {
for (const httpMock of httpMocks) {
if (httpMock.method !== req.method) {
continue;
}
const { match, params } = getMatchAndParams(req.path, httpMock.path);
if (match) {
return {
httpMock,
params
};
}
}
return {
httpMock: null,
params: {}
};
}
function getMatchAndParams(reqPath, mockUrl) {
const params = {};
const keys = [];
const regex = (0, import_path_to_regexp.pathToRegexp)(mockUrl, keys);
const match = regex.exec(reqPath);
if (!match) {
return {
match: false,
params
};
}
for (let i = 1; i < match.length; i++) {
const key = keys[i - 1];
const prop = key.name;
params[prop] = decodeURIComponent(match[i]);
}
return {
match: true,
params
};
}
// src/handle-request.ts
var HEADER_SCENARIO_ID = "sms-scenario-id";
var HEADER_CONTEXT_ID = "sms-context-id";
function updateContext(context, partialContext) {
const newContext = __spreadValues(__spreadValues({}, context), typeof partialContext === "function" ? partialContext(context) : partialContext);
return newContext;
}
function getMocksFromScenario(scenario, scenarioMap) {
const scenarioIds = getScenarioIds([], scenario, scenarioMap);
const mocks = scenarioIds.map((scenarioId) => scenarioMap[scenarioId].mocks).reduce((result, mocks2) => result.concat(mocks2), []);
const httpMocks = getHttpMocks(mocks);
const graphQlMocks = getGraphQlMocks(mocks);
return { httpMocks, graphQlMocks };
}
async function handleRequest({
req,
getServerScenarioId,
initialScenarioId,
initialContext,
scenarioMap,
getServerContext,
setServerContext,
getCookie,
cookieMode,
setCookie,
contextCache
}) {
const headerScenarioId = req.headers[HEADER_SCENARIO_ID];
const headerContextId = req.headers[HEADER_CONTEXT_ID] || (0, import_crypto.randomUUID)();
if (headerScenarioId && cookieMode) {
return {
status: 400,
data: {
message: `Cannot use "${HEADER_SCENARIO_ID}" header when cookie mode is enabled`
}
};
}
const scenarioMockServerCookie = getScenarioMockServerCookie({
initialContext,
initialScenarioId,
getCookie
});
const getScenarioId2 = headerScenarioId ? () => headerScenarioId : cookieMode ? () => scenarioMockServerCookie.scenarioId : getServerScenarioId;
const getContext = headerScenarioId ? () => {
const context = contextCache.get(headerContextId);
if (!context) {
return getContextFromScenario(
scenarioMap[headerScenarioId],
scenarioMap
);
}
return context;
} : cookieMode ? () => scenarioMockServerCookie.context : getServerContext;
const setContext = req.headers[HEADER_SCENARIO_ID] ? (context) => {
contextCache.set(headerContextId, context);
} : cookieMode ? (context) => {
scenarioMockServerCookie.context = context;
} : setServerContext;
const scenarioId = getScenarioId2();
const scenario = scenarioMap[scenarioId];
const { httpMocks, graphQlMocks } = getMocksFromScenario(
scenario,
scenarioMap
);
const graphQlMock = getGraphQlMock(req.path, graphQlMocks);
let result = { status: 404 };
if (graphQlMock) {
result = await graphQlRequestHandler({
req,
graphQlMock,
updateContext: localUpdateContext,
getContext
});
} else {
const { httpMock, params } = getHttpMockAndParams(req, httpMocks);
if (httpMock) {
result = await httpRequestHandler({
req,
httpMock,
params,
getContext,
updateContext: localUpdateContext
});
}
}
if (cookieMode) {
setScenarioMockServerCookie({ setCookie, value: scenarioMockServerCookie });
}
return result;
function localUpdateContext(partialContext) {
const newContext = updateContext(getContext(), partialContext);
setContext(newContext);
return newContext;
}
}
// src/utils/lru-cache.ts
var LruCache = class {
constructor(size) {
this._map = /* @__PURE__ */ new Map();
if (size <= 0 || size - Math.floor(size) != 0) {
throw new Error("size must be a positive integer");
}
this._size = size;
}
get(id) {
if (!this._map.has(id)) {
return null;
}
const value = this._map.get(id);
this._map.delete(id);
this._map.set(id, value);
return value;
}
set(id, value) {
if (this._map.has(id)) {
this._map.delete(id);
}
if (this._map.size === this._size) {
const [[id2]] = this._map.entries();
this._map.delete(id2);
}
this._map.set(id, value);
}
delete(id) {
this._map.delete(id);
}
clear() {
this._map.clear();
}
};
// src/express.ts
function scenarioHasGroup(scenario) {
return !Array.isArray(scenario) && scenario.group != null;
}
function validateAllGroupsHaveNames({
scenarios,
groups
}) {
const uniqueGroups = Array.from(
Object.values(scenarios).filter(scenarioHasGroup).reduce((set, { group }) => {
set.add(group);
return set;
}, /* @__PURE__ */ new Set()).values()
);
const groupsWithoutNames = uniqueGroups.filter((group) => !groups[group]);
if (groupsWithoutNames.length > 0) {
console.warn(
`The following groups do not have a name: ${groupsWithoutNames.join(
", "
)}`
);
}
}
function createExpressApp({
scenarios: externalScenarioMap,
options = {},
groups = {}
}) {
const {
uiPath = "/",
selectScenarioPath = "/select-scenario",
scenariosPath = "/scenarios",
groupsPath = "/groups",
cookieMode = false,
parallelContextSize = 10
} = options;
validateAllGroupsHaveNames({ scenarios: externalScenarioMap, groups });
const { scenarios, scenarioMap } = generateScenarios(externalScenarioMap);
const { initialScenarioId, initialContext } = generatInitialValues(
scenarios,
scenarioMap
);
let serverScenarioId = initialScenarioId;
let serverContext = initialContext;
const contextCache = new LruCache(parallelContextSize);
const app = (0, import_express.default)();
app.use((0, import_cors.default)({ credentials: true }));
app.use((0, import_cookie_parser.default)());
app.use(uiPath, import_express.default.static(import_node_path.default.join(__dirname, "assets")));
app.use(import_express.default.urlencoded({ extended: false }));
app.use(import_express.default.json());
app.use(import_express.default.text({ type: "application/graphql" }));
app.get(uiPath, (req, res) => {
const html = getUi({
uiPath,
cookieMode,
initialScenarioId,
initialContext,
getCookie: expressGetCookie(req),
setCookie: expressSetCookie(res),
getServerScenarioId,
scenarios,
groups
});
res.send(html);
});
app.post(uiPath, ({ body }, res) => {
const scenarioId = import_zod3.default.string().parse(body.scenarioId);
const html = updateUi({
uiPath,
initialScenarioId,
scenarioId,
scenarioMap,
scenarios,
cookieMode,
setCookie: expressSetCookie(res),
setServerContext,
setServerScenarioId,
groups
});
res.send(html);
});
app.put(selectScenarioPath, ({ body }, res) => {
const scenarioId = import_zod3.default.string().parse(body.scenarioId);
const result = selectScenario({
scenarioId,
cookieMode,
scenarioMap,
setCookie: expressSetCookie(res),
setServerContext,
setServerScenarioId
});
expressResponse(res, result);
});
app.get(scenariosPath, (req, res) => {
const result = getScenarios({
getCookie: expressGetCookie(req),
setCookie: expressSetCookie(res),
getServerScenarioId,
cookieMode,
initialScenarioId,
initialContext,
scenarios
});
expressResponse(res, result);
});
app.get(groupsPath, (_, res) => {
expressResponse(res, {
status: 200,
headers: {
"content-type": "application/json"
},
data: Object.entries(groups).map(([id, name]) => ({ id, name }))
});
});
app.use(async (req, res) => {
const internalRequest = {
body: req.body,
headers: expressCleanHeaders(req.headers || {}),
method: req.method,
path: req.path,
query: req.query
};
try {
const result = await handleRequest({
req: internalRequest,
getServerScenarioId,
initialScenarioId,
initialContext,
scenarioMap,
getServerContext,
setServerContext,
cookieMode,
getCookie: expressGetCookie(req),
setCookie: expressSetCookie(res),
contextCache
});
expressResponse(res, result);
} catch (error) {
if (error instanceof Error) {
expressResponse(res, {
status: 500,
data: { message: error.message }
});
return;
}
console.error(error);
expressResponse(res, {
status: 500,
data: { message: "Unknown error - check logs" }
});
}
});
return app;
function getServerScenarioId() {
return serverScenarioId;
}
function getServerContext() {
return serverContext;
}
function setServerContext(context) {
serverContext = context;
}
function setServerScenarioId(scenarioId) {
serverScenarioId = scenarioId;
}
}
function generateScenarios(externalScenarioMap) {
const scenarioMap = {};
const scenarios = [];
for (const [id, scenario] of Object.entries(externalScenarioMap)) {
let internalScenario = { id, name: id, mocks: [] };
if (Array.isArray(scenario)) {
internalScenario.mocks = scenario;
} else {
internalScenario = __spreadValues(__spreadValues({}, internalScenario), scenario);
}
scenarioMap[id] = internalScenario;
scenarios.push(internalScenario);
}
if (scenarios.length === 0) {
throw new Error("No scenarios defined");
}
return { scenarios, scenarioMap };
}
function generatInitialValues(scenarios, scenarioMap) {
const initialScenario = scenarios[0];
const initialScenarioId = initialScenario.id;
const initialContext = getContextFromScenario(initialScenario, scenarioMap);
return { initialScenarioId, initialContext };
}
function expressSetCookie(res) {
return (cookieName, cookieValue) => {
res.cookie(cookieName, cookieValue, { encode: String });
};
}
function expressGetCookie(req) {
return (cookieName) => req.cookies[cookieName];
}
function expressCleanHeaders(headers) {
return Object.fromEntries(
Object.entries(headers).filter(
(keyValuePair) => typeof keyValuePair[1] === "string"
)
);
}
function expressResponse(res, { status, headers, data }) {
res.set(headers).status(status).send(
headers && headers["content-type"] === "application/json" ? JSON.stringify(data) : data
);
}
// src/run.ts
function run({
scenarios,
options = {},
groups = {}
}) {
const _a = options, { port = 3e3 } = _a, restOfOptions = __objRest(_a, ["port"]);
const app = createExpressApp({
scenarios,
options: restOfOptions,
groups
});
return (0, import_server_with_kill.transform)(
app.listen(port, () => {
console.log(`Server running on port ${port}`);
})
);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createExpressApp,
run
});