Skip to content

Commit

Permalink
wire up simulation store with non-optimal TS
Browse files Browse the repository at this point in the history
  • Loading branch information
jbolda committed May 30, 2024
1 parent 12234f5 commit 0b9d3a3
Show file tree
Hide file tree
Showing 8 changed files with 495 additions and 83 deletions.
291 changes: 269 additions & 22 deletions package-lock.json

Large diffs are not rendered by default.

115 changes: 66 additions & 49 deletions packages/foundation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,87 @@ import express from "express";
import { merge } from "lodash";
import type { Handler, Request, Document } from "openapi-backend";
import OpenAPIBackend from "openapi-backend";
import type { SimulationStore } from "./store";
import { createSimulationStore } from "./store";
import type { SimulationInputSchema } from "./store/schema";

type RecursivePartial<T> = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
[P in keyof T]?: RecursivePartial<T[P]>;
};

export async function startServerStandalone({
oas,
handlers,
openapi,
port = 9000,
apiRoot,
extendStore = { actions: {}, schema: {} },
extend,
}: {
oas: Document | [Document, Partial<Document>];
handlers: Record<string, Handler | Record<string, Handler>>;
openapi?: {
document: Document | [Document, RecursivePartial<Document>];
handlers: ({
simulationStore,
}: {
simulationStore: SimulationStore;
}) => Record<string, Handler | Record<string, Handler>>;
apiRoot?: string;
};
port: number;
apiRoot?: string;
extend?(router: express.Router): void;
extendStore?: { actions: any; schema: SimulationInputSchema };
extend?(router: express.Router, simulationStore: SimulationStore): void;
}) {
// TODO init store

let mergedOAS = Array.isArray(oas) ? merge(oas[0], oas[1]) : oas;
let app = express();
app.use(express.json());
let simulationStore = createSimulationStore(extendStore);
if (extend) {
extend(app, simulationStore);
}

let api = new OpenAPIBackend({ definition: mergedOAS, apiRoot });
if (openapi) {
let { document, handlers, apiRoot } = openapi;
let mergedOAS = Array.isArray(document)
? merge(document[0], document[1])
: document;

// register your framework specific request handlers here
let handlerObjectRegistration = (
handlerEntries: Record<string, Handler | Record<string, Handler>>,
prefix?: string
) => {
for (let [key, handler] of Object.entries(handlerEntries)) {
if (typeof handler === "object") {
handlerObjectRegistration(handler, key);
} else {
api.register(`${prefix ? `${prefix}/` : ""}${key}`, handler);
}
}
};
handlerObjectRegistration(handlers); // TODO pass store to handlers
let api = new OpenAPIBackend({ definition: mergedOAS, apiRoot });

api.register({
// getPets: (c, req, res) => res.status(200).json({ result: "ok" }),
validationFail: (c, req, res) =>
res.status(400).json({ err: c.validation.errors }),
notFound: (c, req, res) => res.status(404).json({ err: "not found" }),
notImplemented: (c, req, res) => {
if (!c.operation.operationId) {
return res
.status(404)
.json({ status: 501, err: "No handler registered for operation" });
// register your framework specific request handlers here
let handlerObjectRegistration = (
handlerEntries: Record<string, Handler | Record<string, Handler>>,
prefix?: string
) => {
for (let [key, handler] of Object.entries(handlerEntries)) {
if (typeof handler === "object") {
handlerObjectRegistration(handler, key);
} else {
api.register(`${prefix ? `${prefix}/` : ""}${key}`, handler);
}
}
let { status, mock } = c.api.mockResponseForOperation(
c.operation.operationId
);
return res.status(status).json(mock);
},
});
};
handlerObjectRegistration(handlers({ simulationStore }));

// initalize the backend
api.init();

let app = express();
app.use(express.json());
api.register({
validationFail: (c, req, res) =>
res.status(400).json({ err: c.validation.errors }),
notFound: (c, req, res) => res.status(404).json({ error: "not found" }),
notImplemented: (c, req, res) => {
if (!c?.operation?.operationId) {
return res.status(404).json({
status: 501,
error: "No handler registered for operation",
});
}
let { status, mock } = c.api.mockResponseForOperation(
c.operation.operationId
);
return res.status(status).json(mock);
},
});

if (extend) {
extend(app); // TODO implement and pass in store
// initalize the backend
api.init();
app.use((req, res) => api.handleRequest(req as Request, req, res));
}

app.use((req, res) => api.handleRequest(req as Request, req, res));
let server = app.listen(port, () =>
console.info(`api listening at http://localhost:${port}`)
);
Expand Down
3 changes: 2 additions & 1 deletion packages/foundation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"express": "^4.19.2",
"openapi-backend": "^5.10.6",
"openapi-merge": "^1.3.2"
"openapi-merge": "^1.3.2",
"starfx": "^0.12.0"
}
}
51 changes: 40 additions & 11 deletions packages/foundation/server.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import { startServerStandalone } from "./index";
import type { SimulationSlice } from "./store/schema";

const oas1 = {
openapi: "3.0.0",
info: {
title: "API",
version: "1.0.0",
},
paths: {},
paths: {
"/dogs": {
get: {
summary: "Get the dogs",
operationId: "getDogs",
responses: {
200: {
description: "All of the dogs",
},
},
},
},
},
};

const oas2 = {
Expand All @@ -15,21 +28,37 @@ const oas2 = {
title: "API",
version: "1.0.0",
},
"/dogs": {
get: {
summary: "Get the dogs",
responses: {
200: {
description: "All of the dogs",
},
paths: {
"/dogs": {
get: {
operationId: "getDogs",
},
},
},
};

startServerStandalone({
oas: [oas1, oas2],
handlers: {},
openapi: {
document: [oas1, oas2],
// eslint-disable-next-line @typescript-eslint/no-unused-vars
handlers({ simulationStore }) {
return {
getDogs: (c, req, res) => {
res.sendStatus(200);
},
};
},
apiRoot: "/api",
},
extendStore: {
actions: () => ({}),
schema: ({ slice }: { slice: SimulationSlice }) => {
let slices = {
test: slice.table(),
booping: slice.str(),
};
return slices;
},
},
port: 9999,
apiRoot: "/api",
});
44 changes: 44 additions & 0 deletions packages/foundation/store/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import "./thunks";
import { setupStore } from "./setup";
import { thunks } from "./thunks";
import type { StoreUpdater } from "starfx";
import { updateStore } from "starfx";
import type { SimulationInputSchema } from "./schema";

let updater = thunks.create<StoreUpdater<any>[]>(
"update",
function* (ctx, next) {
yield* updateStore(ctx.payload);
yield* next();
}
);

type CreateSimulationStore = typeof createSimulationStore;
export type SimulationStore = ReturnType<CreateSimulationStore>;
export function createSimulationStore(
{
actions: inputActions,
schema: inputSchema,
}: { actions: any; schema: SimulationInputSchema } = {
actions: () => ({}),
schema: () => ({}),
}
) {
let additionalTasks = [thunks.bootup];
let { store, schema } = setupStore({
logs: false,
additionalTasks,
inputSchema,
});

let actions = {
updater,
...inputActions({ thunks, store, schema }),
};

return {
store,
schema,
actions,
};
}
23 changes: 23 additions & 0 deletions packages/foundation/store/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createSchema, slice as immerSlice } from "starfx";

export type SimulationSlice = typeof immerSlice;
export type SimulationInputSchema = Parameters<
typeof generateSchemaWithInputSlices
>[0];
type SimulationSchemaSlices = Omit<
Parameters<typeof createSchema>[0],
"loaders" | "cache"
>;

export function generateSchemaWithInputSlices(
inputSchema: (args: { slice: SimulationSlice }) => SimulationSchemaSlices
) {
let slices = inputSchema({ slice: immerSlice });

return createSchema({
cache: immerSlice.table({ empty: {} }),
loaders: immerSlice.loaders(),
boop: immerSlice.num(),
...slices,
});
}
42 changes: 42 additions & 0 deletions packages/foundation/store/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Callable } from "starfx";
import { parallel, take, createStore } from "starfx";
import type { SimulationInputSchema } from "./schema";
import { generateSchemaWithInputSlices } from "./schema";

export function setupStore({
logs = true,
additionalTasks = [],
initialState,
inputSchema,
}: {
logs: boolean;
initialState?: Record<string, any>;
additionalTasks?: Callable<unknown>[];
inputSchema: SimulationInputSchema;
}) {
let [schema, schemaInitialState] = generateSchemaWithInputSlices(inputSchema);
let store = createStore({
initialState: {
...schemaInitialState,
...initialState,
},
});

let tsks: Callable<unknown>[] = [...additionalTasks];
if (logs) {
// log all actions dispatched
tsks.push(function* logActions() {
while (true) {
let action = yield* take("*");
console.dir(action, { depth: 5 });
}
});
}

store.run(function* () {
let group = yield* parallel(tsks);
yield* group;
});

return { store, schema };
}
9 changes: 9 additions & 0 deletions packages/foundation/store/thunks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createThunks, mdw } from 'starfx';

const thunks = createThunks();
// catch errors from task and logs them with extra info
thunks.use(mdw.err);
// where all the thunks get called in the middleware stack
thunks.use(thunks.routes());

export { thunks };

0 comments on commit 0b9d3a3

Please sign in to comment.