From ad2dbc97b5068b4662ae8d95f188ae9dac80be0c Mon Sep 17 00:00:00 2001 From: Jacob Bolda Date: Mon, 22 Jul 2024 14:30:10 -0500 Subject: [PATCH 1/9] add simulationRoutes to store, display at base / route --- .changes/foundation-simulator-route-list.md | 5 ++ .../example/extensiveServer/openapi.ts | 11 ++- packages/foundation/src/index.ts | 76 +++++++++++++++-- packages/foundation/src/routeTemplate.ts | 85 +++++++++++++++++++ packages/foundation/src/store/schema.ts | 19 +++++ 5 files changed, 189 insertions(+), 7 deletions(-) create mode 100644 .changes/foundation-simulator-route-list.md create mode 100644 packages/foundation/src/routeTemplate.ts diff --git a/.changes/foundation-simulator-route-list.md b/.changes/foundation-simulator-route-list.md new file mode 100644 index 00000000..8b55d4e1 --- /dev/null +++ b/.changes/foundation-simulator-route-list.md @@ -0,0 +1,5 @@ +--- +"@simulacrum/foundation-simulator": minor:feat +--- + +To improve transparency and flexibility, we now include a page at the root that lists all of the routes, and the ability to signal which response to return. diff --git a/packages/foundation/example/extensiveServer/openapi.ts b/packages/foundation/example/extensiveServer/openapi.ts index d25bd46c..7ee48661 100644 --- a/packages/foundation/example/extensiveServer/openapi.ts +++ b/packages/foundation/example/extensiveServer/openapi.ts @@ -16,6 +16,9 @@ const openapiSchemaFromRealEndpoint = { 200: { description: "All of the dogs", }, + 404: { + description: "The dogs have gone missing!", + }, }, }, }, @@ -127,11 +130,15 @@ function handlers( simulationStore: ExtendedSimulationStore ): SimulationHandlers { return { - getDogs: (_c, _r, response) => { + getDogs: (_c, request, response, _next, routeMetadata) => { let dogs = simulationStore.schema.dogs.select( simulationStore.store.getState() ); - response.status(200).json({ dogs }); + if (routeMetadata.defaultCode === 200) { + response.status(200).json({ dogs }); + } else { + response.sendStatus(routeMetadata.defaultCode); + } }, putDogs: (c, req, response) => { simulationStore.store.dispatch( diff --git a/packages/foundation/src/index.ts b/packages/foundation/src/index.ts index aa2febda..abb55098 100644 --- a/packages/foundation/src/index.ts +++ b/packages/foundation/src/index.ts @@ -2,6 +2,7 @@ import express from "express"; import type { Request as ExpressRequest, Response as ExpressResponse, + NextFunction as ExpressNextFunction, } from "express"; import { fdir } from "fdir"; import fs from "node:fs"; @@ -26,13 +27,17 @@ import type { import type { ExtendSimulationSchemaInput, ExtendSimulationSchema, + SimulationRoute, } from "./store/schema"; import type { RecursivePartial } from "./store/types"; +import { generateRoutesHTML } from "./routeTemplate"; type SimulationHandlerFunctions = ( context: OpenAPIBackendContext, request: ExpressRequest, - response: ExpressResponse + response: ExpressResponse, + next: ExpressNextFunction, + routeMetadata: SimulationRoute ) => void; export type SimulationHandlers = Record; export type { @@ -92,6 +97,7 @@ export function createFoundationSimulationServer< return () => { let app = express(); app.use(express.json()); + app.use(express.urlencoded({ extended: false })); let simulationStore = createSimulationStore(extendStore); if (serveJsonFiles) { @@ -165,13 +171,73 @@ export function createFoundationSimulationServer< }); // initalize the backend - api.init(); - app.use((req, res, next) => - api.handleRequest(req as Request, req, res, next) - ); + api.init().then((init) => { + const router = init.router; + const operations = router.getOperations(); + const simulationRoutes = operations.reduce((routes, operation) => { + const url = `${router.apiRoot}${operation.path}`; + routes[`${operation.method}:${url}`] = { + type: "OpenAPI", + url, + method: operation.method as SimulationRoute["method"], + calls: 0, + defaultCode: 200, + responses: Object.keys(operation.responses ?? {}).map((key) => + parseInt(key) + ), + }; + return routes; + }, {} as Record); + simulationStore.store.dispatch( + simulationStore.actions.batchUpdater([ + simulationStore.schema.simulationRoutes.add(simulationRoutes), + ]) + ); + return init; + }); + app.use((req, res, next) => { + const routeId = `${req.method.toLowerCase()}:${req.path}`; + const routeMetadata = + simulationStore.schema.simulationRoutes.selectById( + simulationStore.store.getState(), + { + id: routeId, + } + ); + return api.handleRequest( + req as Request, + req, + res, + next, + routeMetadata + ); + }); } } + app.get("/", (req, res) => { + let routes = simulationStore.schema.simulationRoutes.selectTableAsList( + simulationStore.store.getState() + ); + if (routes.length === 0) { + res.sendStatus(404); + } else { + res.status(200).send(generateRoutesHTML(routes)); + } + }); + app.post("/", (req, res) => { + const formValue = req.body; + const entries = {} as Record>; + for (let [key, value] of Object.entries(formValue)) { + entries[key] = { defaultCode: parseInt(value as string) }; + } + simulationStore.store.dispatch( + simulationStore.actions.batchUpdater([ + simulationStore.schema.simulationRoutes.patch(entries), + ]) + ); + res.redirect("/"); + }); // if no extendRouter routes or openapi routes handle this, return 404 app.all("*", (req, res) => res.status(404).json({ error: "not found" })); diff --git a/packages/foundation/src/routeTemplate.ts b/packages/foundation/src/routeTemplate.ts new file mode 100644 index 00000000..5837206f --- /dev/null +++ b/packages/foundation/src/routeTemplate.ts @@ -0,0 +1,85 @@ +import type { SimulationRoute } from "./store/schema"; + +const responseSubmit = (routeId: string, response: number) => /* HTML */ `
+ +
`; +const routeToId = (route: SimulationRoute) => `${route.method}:${route.url}`; + +export const generateRoutesHTML = (routes: SimulationRoute[]) => { + return /* HTML */ ` + + + + Simulation Server Routes + + + +
+

Simulation Routes

+
+ ${routes + .map( + (route) => + `${route.method.toUpperCase()}${route.url}returns: ${ + route.defaultCode + }, called ${ + route.calls + } times
${route.responses + .map((response) => + responseSubmit(routeToId(route), response) + ) + .join("")}
` + ) + .join("\n")} +
+
+ + `; +}; diff --git a/packages/foundation/src/store/schema.ts b/packages/foundation/src/store/schema.ts index 4d148756..cbd5eecb 100644 --- a/packages/foundation/src/store/schema.ts +++ b/packages/foundation/src/store/schema.ts @@ -8,6 +8,15 @@ export type ExtendSimulationSchemaInput = ({ slice, }: ExtendSimulationSchema) => T; +export interface SimulationRoute { + type: "OpenAPI" | "Explicit"; + url: string; + method: "get" | "post" | "delete" | "patch"; + calls: number; + defaultCode: number; + responses: number[]; +} + export function generateSchemaWithInputSlices( inputSchema: ExtendSimulationSchemaInput ) { @@ -16,6 +25,16 @@ export function generateSchemaWithInputSlices( let schemaAndInitialState = createSchema({ cache: immerSlice.table({ empty: {} }), loaders: immerSlice.loaders(), + simulationRoutes: immerSlice.table({ + empty: { + type: "Explicit", + url: "", + method: "get", + calls: 0, + defaultCode: 200, + responses: [200], + }, + }), ...slices, }); From dcbcfa53a941f8040462f39c91e1f6eb29465988 Mon Sep 17 00:00:00 2001 From: Jacob Bolda Date: Mon, 22 Jul 2024 15:03:19 -0500 Subject: [PATCH 2/9] make columns more dynamic --- packages/foundation/src/routeTemplate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/foundation/src/routeTemplate.ts b/packages/foundation/src/routeTemplate.ts index 5837206f..b2bbfe09 100644 --- a/packages/foundation/src/routeTemplate.ts +++ b/packages/foundation/src/routeTemplate.ts @@ -34,7 +34,7 @@ export const generateRoutesHTML = (routes: SimulationRoute[]) => { .routes { display: grid; - grid-template-columns: 20px auto auto auto; + grid-template-columns: 1fr 6fr 2fr auto; column-gap: 15px; } .route-actions { From 5e0c9ccb82aef9d1b146faf8688b37cd3d42d6d9 Mon Sep 17 00:00:00 2001 From: Jacob Bolda Date: Mon, 12 Aug 2024 21:36:22 -0500 Subject: [PATCH 3/9] add JSON routes --- packages/foundation/src/index.ts | 22 ++++++++++++++++++++-- packages/foundation/src/routeTemplate.ts | 14 ++++++++++---- packages/foundation/src/store/schema.ts | 2 +- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/packages/foundation/src/index.ts b/packages/foundation/src/index.ts index abb55098..981e9935 100644 --- a/packages/foundation/src/index.ts +++ b/packages/foundation/src/index.ts @@ -109,14 +109,32 @@ export function createFoundationSimulationServer< .sync(); if (jsonFiles.length > 0) { + const simulationRoutes = []; for (let jsonFile of jsonFiles) { - const route = jsonFile.slice(0, jsonFile.length - 5); + const route = `/${jsonFile.slice(0, jsonFile.length - 5)}`; const filename = path.join(serveJsonFiles, jsonFile); - app.get(`/${route}`, (_req, res) => { + app.get(route, (_req, res) => { res.setHeader("content-type", "application/json"); fs.createReadStream(filename).pipe(res); }); + + simulationRoutes.push( + simulationStore.schema.simulationRoutes.add({ + [`get:${route}`]: { + type: "JSON", + url: route, + method: "get", + calls: 0, + defaultCode: 200, + responses: [200], + }, + }) + ); } + + simulationStore.store.dispatch( + simulationStore.actions.batchUpdater(simulationRoutes) + ); } } diff --git a/packages/foundation/src/routeTemplate.ts b/packages/foundation/src/routeTemplate.ts index b2bbfe09..025219ad 100644 --- a/packages/foundation/src/routeTemplate.ts +++ b/packages/foundation/src/routeTemplate.ts @@ -34,7 +34,7 @@ export const generateRoutesHTML = (routes: SimulationRoute[]) => { .routes { display: grid; - grid-template-columns: 1fr 6fr 2fr auto; + grid-template-columns: 1fr 5fr 1fr 1fr 2fr; column-gap: 15px; } .route-actions { @@ -54,6 +54,7 @@ export const generateRoutesHTML = (routes: SimulationRoute[]) => { } a { + color: aqua; } } @@ -62,16 +63,21 @@ export const generateRoutesHTML = (routes: SimulationRoute[]) => {

Simulation Routes

+ Method + URL + Status + Metrics + Response Options ${routes .map( (route) => `${route.method.toUpperCase()}${route.url}returns: ${ + }>${route.url}code ${ route.defaultCode - }, called ${ + }${ route.calls - } times
${route.responses + } calls
${route.responses .map((response) => responseSubmit(routeToId(route), response) ) diff --git a/packages/foundation/src/store/schema.ts b/packages/foundation/src/store/schema.ts index cbd5eecb..ddf3e099 100644 --- a/packages/foundation/src/store/schema.ts +++ b/packages/foundation/src/store/schema.ts @@ -9,7 +9,7 @@ export type ExtendSimulationSchemaInput = ({ }: ExtendSimulationSchema) => T; export interface SimulationRoute { - type: "OpenAPI" | "Explicit"; + type: "JSON" | "OpenAPI" | "Explicit"; url: string; method: "get" | "post" | "delete" | "patch"; calls: number; From ab1c61663582bed118cecb61ed3321227ad0eb3f Mon Sep 17 00:00:00 2001 From: Jacob Bolda Date: Mon, 12 Aug 2024 22:11:53 -0500 Subject: [PATCH 4/9] add simulation logger --- .changes/foundation-sim-logger.md | 5 +++++ package-lock.json | 5 +++++ .../example/extensiveServer/store.ts | 1 + .../example/singleFileServer/index.ts | 1 + packages/foundation/package.json | 1 + packages/foundation/src/index.ts | 20 +++++++++++++++++- packages/foundation/src/routeTemplate.ts | 14 ++++++++++--- packages/foundation/src/store/index.ts | 21 +++++++++++++++++++ packages/foundation/src/store/schema.ts | 8 +++++++ 9 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 .changes/foundation-sim-logger.md diff --git a/.changes/foundation-sim-logger.md b/.changes/foundation-sim-logger.md new file mode 100644 index 00000000..20a89e80 --- /dev/null +++ b/.changes/foundation-sim-logger.md @@ -0,0 +1,5 @@ +--- +"@simulacrum/foundation-simulator": minor:feat +--- + +All routes now add a log to the simulation state on every visit. This assists in tracking hits on each simulation route. diff --git a/package-lock.json b/package-lock.json index 1206b162..54c04ec6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4784,7 +4784,9 @@ }, "node_modules/cors": { "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", "dependencies": { "object-assign": "^4", "vary": "^1" @@ -9401,6 +9403,7 @@ "license": "MIT", "dependencies": { "ajv-formats": "^3.0.1", + "cors": "^2.8.5", "express": "^4.19.2", "fdir": "^6.2.0", "openapi-backend": "^5.10.6", @@ -11947,6 +11950,7 @@ "requires": { "@types/cors": "^2.8.17", "ajv-formats": "^3.0.1", + "cors": "^2.8.5", "express": "^4.19.2", "fdir": "^6.2.0", "openapi-backend": "^5.10.6", @@ -13214,6 +13218,7 @@ }, "cors": { "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "requires": { "object-assign": "^4", diff --git a/packages/foundation/example/extensiveServer/store.ts b/packages/foundation/example/extensiveServer/store.ts index 558c22f3..e8761804 100644 --- a/packages/foundation/example/extensiveServer/store.ts +++ b/packages/foundation/example/extensiveServer/store.ts @@ -54,6 +54,7 @@ const inputSelectors = ({ }; export const extendStore = { + logs: false, actions: inputActions, selectors: inputSelectors, schema: inputSchema, diff --git a/packages/foundation/example/singleFileServer/index.ts b/packages/foundation/example/singleFileServer/index.ts index d474bd35..3618c916 100644 --- a/packages/foundation/example/singleFileServer/index.ts +++ b/packages/foundation/example/singleFileServer/index.ts @@ -93,6 +93,7 @@ export const simulation = createFoundationSimulationServer({ }, ], extendStore: { + logs: false, actions: ({ thunks, schema }) => { // TODO attempt to remove this type as a requirement let upsertTest = thunks.create( diff --git a/packages/foundation/package.json b/packages/foundation/package.json index 869f6af9..30aced25 100644 --- a/packages/foundation/package.json +++ b/packages/foundation/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "ajv-formats": "^3.0.1", + "cors": "^2.8.5", "express": "^4.19.2", "fdir": "^6.2.0", "openapi-backend": "^5.10.6", diff --git a/packages/foundation/src/index.ts b/packages/foundation/src/index.ts index 981e9935..399f6d76 100644 --- a/packages/foundation/src/index.ts +++ b/packages/foundation/src/index.ts @@ -1,4 +1,5 @@ import express from "express"; +import cors from "cors"; import type { Request as ExpressRequest, Response as ExpressResponse, @@ -96,10 +97,23 @@ export function createFoundationSimulationServer< }) { return () => { let app = express(); + app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: false })); let simulationStore = createSimulationStore(extendStore); + app.use((req, res, next) => { + simulationStore.store.dispatch( + simulationStore.actions.simulationLog({ + method: req.method, + url: req.url, + query: req.query, + body: req.body, + }) + ); + next(); + }); + if (serveJsonFiles) { const jsonFiles = new fdir() .filter((path, _isDirectory) => path.endsWith(".json")) @@ -237,10 +251,13 @@ export function createFoundationSimulationServer< let routes = simulationStore.schema.simulationRoutes.selectTableAsList( simulationStore.store.getState() ); + let logs = simulationStore.schema.simulationLogs.selectTableAsList( + simulationStore.store.getState() + ); if (routes.length === 0) { res.sendStatus(404); } else { - res.status(200).send(generateRoutesHTML(routes)); + res.status(200).send(generateRoutesHTML(routes, logs)); } }); app.post("/", (req, res) => { @@ -272,6 +289,7 @@ export function createFoundationSimulationServer< return { server, + simulationStore, ensureClose: async () => { await new Promise((resolve) => { server.once("close", resolve); diff --git a/packages/foundation/src/routeTemplate.ts b/packages/foundation/src/routeTemplate.ts index 025219ad..078ffd09 100644 --- a/packages/foundation/src/routeTemplate.ts +++ b/packages/foundation/src/routeTemplate.ts @@ -1,4 +1,4 @@ -import type { SimulationRoute } from "./store/schema"; +import type { SimulationLog, SimulationRoute } from "./store/schema"; const responseSubmit = (routeId: string, response: number) => /* HTML */ `
/* HTML */ ``; const routeToId = (route: SimulationRoute) => `${route.method}:${route.url}`; -export const generateRoutesHTML = (routes: SimulationRoute[]) => { +export const generateRoutesHTML = ( + routes: SimulationRoute[], + logs: SimulationLog[] +) => { return /* HTML */ ` @@ -61,7 +64,8 @@ export const generateRoutesHTML = (routes: SimulationRoute[]) => {
-

Simulation Routes

+

Simulation

+

Routes

Method URL @@ -85,6 +89,10 @@ export const generateRoutesHTML = (routes: SimulationRoute[]) => { ) .join("\n")}
+

Logs

+
+ ${logs.map((log) => `
${log.message}
`).join("")} +
`; diff --git a/packages/foundation/src/store/index.ts b/packages/foundation/src/store/index.ts index 92c1806c..283ae2d1 100644 --- a/packages/foundation/src/store/index.ts +++ b/packages/foundation/src/store/index.ts @@ -74,6 +74,26 @@ export function createSimulationStore< yield* next(); } ); + let simulationLog = thunks.create<{ + method: string; + url: string; + query: Record; + body: any; + }>("simulationLog", function* (ctx, next) { + const { method, url, query, body } = ctx.payload; + const timestamp = Date.now(); + yield* schema.update( + schema.simulationLogs.add({ + [timestamp]: { + timestamp, + level: "info", + message: `${method} ${url}`, + meta: { method, url, query, body }, + }, + }) + ); + yield* next(); + }); let additionalTasks = [thunks.bootup]; @@ -86,6 +106,7 @@ export function createSimulationStore< let inputedActions = inputActions({ thunks, store, schema }); let actions = { + simulationLog, batchUpdater, ...inputedActions, }; diff --git a/packages/foundation/src/store/schema.ts b/packages/foundation/src/store/schema.ts index ddf3e099..b8e4c887 100644 --- a/packages/foundation/src/store/schema.ts +++ b/packages/foundation/src/store/schema.ts @@ -8,6 +8,13 @@ export type ExtendSimulationSchemaInput = ({ slice, }: ExtendSimulationSchema) => T; +export interface SimulationLog { + timestamp: number; + level: "debug" | "info" | "error"; + message: string; + meta?: Record; +} + export interface SimulationRoute { type: "JSON" | "OpenAPI" | "Explicit"; url: string; @@ -25,6 +32,7 @@ export function generateSchemaWithInputSlices( let schemaAndInitialState = createSchema({ cache: immerSlice.table({ empty: {} }), loaders: immerSlice.loaders(), + simulationLogs: immerSlice.table(), simulationRoutes: immerSlice.table({ empty: { type: "Explicit", From 98cd93e62f19fc5e16556275370b6a92e0dfc588 Mon Sep 17 00:00:00 2001 From: Jacob Bolda Date: Mon, 12 Aug 2024 23:07:20 -0500 Subject: [PATCH 5/9] wider route list --- packages/foundation/src/routeTemplate.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/foundation/src/routeTemplate.ts b/packages/foundation/src/routeTemplate.ts index 078ffd09..efdfe6ca 100644 --- a/packages/foundation/src/routeTemplate.ts +++ b/packages/foundation/src/routeTemplate.ts @@ -26,8 +26,7 @@ export const generateRoutesHTML = ( } body { margin: 0 auto; - max-width: 720px; - padding: 0 16px; + padding: 0 12ch; font-family: sans-serif; } From 239c10083cd6708cd42704feac95f2b84badf7d4 Mon Sep 17 00:00:00 2001 From: Jacob Bolda Date: Mon, 12 Aug 2024 23:07:58 -0500 Subject: [PATCH 6/9] fix route list for default apiRoot --- packages/foundation/src/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/foundation/src/index.ts b/packages/foundation/src/index.ts index 399f6d76..e344e91b 100644 --- a/packages/foundation/src/index.ts +++ b/packages/foundation/src/index.ts @@ -207,7 +207,9 @@ export function createFoundationSimulationServer< const router = init.router; const operations = router.getOperations(); const simulationRoutes = operations.reduce((routes, operation) => { - const url = `${router.apiRoot}${operation.path}`; + const url = `${router.apiRoot === "/" ? "" : router.apiRoot}${ + operation.path + }`; routes[`${operation.method}:${url}`] = { type: "OpenAPI", url, From 48bf36246440ebd6ab43b19139ce6e081f6804a5 Mon Sep 17 00:00:00 2001 From: Jacob Bolda Date: Tue, 27 Aug 2024 16:34:53 -0500 Subject: [PATCH 7/9] add extendRouter routes to list --- packages/foundation/src/index.ts | 39 ++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/packages/foundation/src/index.ts b/packages/foundation/src/index.ts index e344e91b..c80f9f46 100644 --- a/packages/foundation/src/index.ts +++ b/packages/foundation/src/index.ts @@ -5,6 +5,7 @@ import type { Response as ExpressResponse, NextFunction as ExpressNextFunction, } from "express"; +import type { ILayer, IRoute } from "express-serve-static-core"; import { fdir } from "fdir"; import fs from "node:fs"; import path from "node:path"; @@ -114,6 +115,38 @@ export function createFoundationSimulationServer< next(); }); + if (extendRouter) { + extendRouter(app, simulationStore); + + if (app?._router?.stack) { + const layers: IRoute[] = app._router.stack + .map((stack: ILayer) => stack.route) + .filter(Boolean); + + const simulationRoutes = []; + for (let layer of layers) { + for (let stack of layer.stack) { + simulationRoutes.push( + simulationStore.schema.simulationRoutes.add({ + [`${stack.method}:${layer.path}`]: { + type: "JSON", + url: layer.path, + method: stack.method as SimulationRoute["method"], + calls: 0, + defaultCode: 200, + responses: [200], + }, + }) + ); + } + } + + simulationStore.store.dispatch( + simulationStore.actions.batchUpdater(simulationRoutes) + ); + } + } + if (serveJsonFiles) { const jsonFiles = new fdir() .filter((path, _isDirectory) => path.endsWith(".json")) @@ -127,7 +160,7 @@ export function createFoundationSimulationServer< for (let jsonFile of jsonFiles) { const route = `/${jsonFile.slice(0, jsonFile.length - 5)}`; const filename = path.join(serveJsonFiles, jsonFile); - app.get(route, (_req, res) => { + app.get(route, function staticJson(_req, res) { res.setHeader("content-type", "application/json"); fs.createReadStream(filename).pipe(res); }); @@ -152,10 +185,6 @@ export function createFoundationSimulationServer< } } - if (extendRouter) { - extendRouter(app, simulationStore); - } - if (openapi) { for (let spec of openapi) { let { document, handlers, apiRoot } = spec; From 2f42d898d4bbe8227aff140407eb64dd230e924c Mon Sep 17 00:00:00 2001 From: Jacob Bolda Date: Wed, 28 Aug 2024 10:43:38 -0500 Subject: [PATCH 8/9] increment calls --- packages/foundation/src/index.ts | 2 ++ packages/foundation/src/store/index.ts | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/foundation/src/index.ts b/packages/foundation/src/index.ts index c80f9f46..3726fabb 100644 --- a/packages/foundation/src/index.ts +++ b/packages/foundation/src/index.ts @@ -104,6 +104,7 @@ export function createFoundationSimulationServer< let simulationStore = createSimulationStore(extendStore); app.use((req, res, next) => { + // add each response to the internal log simulationStore.store.dispatch( simulationStore.actions.simulationLog({ method: req.method, @@ -278,6 +279,7 @@ export function createFoundationSimulationServer< } } + // return simulation helper page app.get("/", (req, res) => { let routes = simulationStore.schema.simulationRoutes.selectTableAsList( simulationStore.store.getState() diff --git a/packages/foundation/src/store/index.ts b/packages/foundation/src/store/index.ts index 283ae2d1..a7a09ca1 100644 --- a/packages/foundation/src/store/index.ts +++ b/packages/foundation/src/store/index.ts @@ -1,7 +1,7 @@ import { generateSchemaWithInputSlices } from "./schema"; import type { ExtendSimulationSchemaInput } from "./schema"; import type { AnyState, StoreUpdater, Callable } from "starfx"; -import { parallel, take, createStore, createSelector } from "starfx"; +import { parallel, take, select, createStore, createSelector } from "starfx"; import { updateStore, createThunks, mdw } from "starfx"; type StoreThunks = ReturnType; @@ -82,6 +82,7 @@ export function createSimulationStore< }>("simulationLog", function* (ctx, next) { const { method, url, query, body } = ctx.payload; const timestamp = Date.now(); + yield* schema.update( schema.simulationLogs.add({ [timestamp]: { @@ -92,6 +93,17 @@ export function createSimulationStore< }, }) ); + + // attempt to increment `route.calls` + const id = `${method.toLowerCase()}:${url}`; + const route = yield* select(schema.simulationRoutes.selectById, { + id, + }); + if (route.url !== "") + yield* schema.update( + schema.simulationRoutes.merge({ [id]: { calls: route.calls + 1 } }) + ); + yield* next(); }); From bba7385d0a26f7ebaf6c52138b05658d0f122e1a Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Sun, 1 Sep 2024 17:14:20 -0400 Subject: [PATCH 9/9] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1523b7dd..10afaf00 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Simulacrum removes these constraints from your process by allowing you to simula * [auth0](packages/auth0) - [@simulacrum/auth0-simulator](https://www.npmjs.com/package/@simulacrum/auth0-simulator) * [ldap](packages/ldap) - [@simulacrum/ldap-simulator](https://www.npmjs.com/package/@simulacrum/ldap-simulator) +* [github-api](packages/github-api) - [@simulacrum/github-api-simulator](https://www.npmjs.com/package/@simulacrum/github-api-simulator) ## Usage