Skip to content

Commit 121b301

Browse files
authored
Foundation Handles Generic Tasks and Webhooks (#338)
* Foundation Handles Generic Tasks and Webhooks * store args are optional * massage the typescript
1 parent 7dc3615 commit 121b301

File tree

25 files changed

+482
-242
lines changed

25 files changed

+482
-242
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@simulacrum/foundation-simulator": minor:feat
3+
---
4+
5+
Add tasks to handle generic functions that would run in an `effection` scope. This enables the ability to handle webhooks. A webhook is an action that can be triggered directly and makes a `POST` to a specified endpoint, but can also watch and trigger on updates to the store.

packages/auth0/src/handlers/auth0-handlers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ export const createAuth0Handlers = (
235235
},
236236

237237
["/userinfo"]: function (req, res) {
238-
let token = null;
238+
let token: string | null = null;
239239
if (req.headers.authorization) {
240240
let authorizationHeader = req.headers.authorization;
241241
token = authorizationHeader?.split(" ")?.[1];
@@ -272,7 +272,7 @@ export const createAuth0Handlers = (
272272
logger.log({ "/passwordless/start": { body: req.body } });
273273

274274
try {
275-
const { client_id, connection, email, phone_number, send } = req.body;
275+
const { client_id, connection, email, phone_number } = req.body;
276276

277277
// Validate required fields
278278
if (!client_id) {

packages/auth0/src/index.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import {
33
type SimulationHandlers,
44
type FoundationSimulator,
55
} from "@simulacrum/foundation-simulator";
6-
import { type ExtendedSimulationStore, extendStore } from "./store/index.ts";
6+
import type { ExtendedSimulationStore } from "./store/index.ts";
7+
import { extendStore } from "./store/index.ts";
8+
import type { Router } from "express";
9+
import type { Auth0ExtendStoreInput } from "./store/index.ts";
710
import { extendRouter } from "./handlers/index.ts";
811
import {
912
type Auth0InitialStore,
@@ -16,16 +19,18 @@ export type Auth0Simulator = (args?: {
1619
debug?: boolean;
1720
initialState?: Auth0InitialStore;
1821
extend?: {
19-
extendStore?: SimulationInput["extendStore"];
22+
extendStore?: Auth0ExtendStoreInput;
2023
openapiHandlers?: (
2124
simulationStore: ExtendedSimulationStore
2225
) => SimulationHandlers;
23-
extendRouter?: SimulationInput["extendRouter"];
26+
extendRouter?: (
27+
router: Router,
28+
simulationStore: ExtendedSimulationStore
29+
) => void;
2430
};
2531
options?: Partial<Auth0Configuration>;
2632
}) => FoundationSimulator<ExtendedSimulationStore>;
2733

28-
type SimulationInput = Parameters<typeof createFoundationSimulationServer>[0];
2934
export const simulation: Auth0Simulator = (args = {}) => {
3035
const config = getConfig(args.options);
3136
const parsedInitialState = !args?.initialState

packages/auth0/src/store/index.ts

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import type {
88
ExtendSimulationSelectorsInput,
99
TableOutput,
1010
AnyState,
11+
ExtendSimulationActionsInputLoose,
12+
ExtendSimulationSelectorsInputLoose,
13+
ExtendStoreConfig,
1114
} from "@simulacrum/foundation-simulator";
1215
import {
1316
convertInitialStateToStoreState,
@@ -25,10 +28,14 @@ export type ExtendedSchema = ({ slice }: ExtendSimulationSchema) => {
2528
};
2629
type ExtendActions = typeof inputActions;
2730
type ExtendSelectors = typeof inputSelectors;
31+
export type Auth0Schema = ReturnType<ExtendedSchema>;
32+
export type Auth0Actions = ReturnType<ExtendActions>;
33+
export type Auth0Selectors = ReturnType<ExtendSelectors>;
34+
2835
export type ExtendedSimulationStore = SimulationStore<
29-
ReturnType<ExtendedSchema>,
30-
ReturnType<ExtendActions>,
31-
ReturnType<ExtendSelectors>
36+
Auth0Schema,
37+
Auth0Actions,
38+
Auth0Selectors
3239
>;
3340

3441
const inputSchema =
@@ -56,49 +63,63 @@ const inputSchema =
5663
return slices;
5764
};
5865

59-
const inputActions = (args: ExtendSimulationActions<ExtendedSchema>) => {
60-
return {};
66+
const inputActions = (_args: ExtendSimulationActions<ExtendedSchema>) => {
67+
return {} as ExtendSimulationActions<ExtendedSchema>;
6168
};
6269

6370
const extendActions =
64-
(extendedActions?: ExtendSimulationActionsInput<any, ExtendedSchema>) =>
71+
(
72+
extendedActions?: ExtendSimulationActionsInputLoose<
73+
Auth0Actions,
74+
Auth0Schema
75+
>
76+
) =>
6577
(args: ExtendSimulationActions<ExtendedSchema>) => {
66-
return extendedActions
67-
? // @ts-expect-error schema is cyclical, ignore extension for now
68-
{ ...inputActions(args), ...extendedActions(args) }
69-
: inputActions(args);
78+
const base = inputActions(args);
79+
if (!extendedActions) return base;
80+
const extResult = extendedActions(args);
81+
return {
82+
...(base as object),
83+
...(extResult as object),
84+
} as Auth0Actions;
7085
};
7186

72-
const inputSelectors = (args: ExtendSimulationSelectors<ExtendedSchema>) => {
73-
const { createSelector, schema } = args;
74-
return {};
87+
const inputSelectors = (_args: ExtendSimulationSelectors<ExtendedSchema>) => {
88+
return {} as ExtendSimulationSelectors<ExtendedSchema>;
7589
};
7690

7791
const extendSelectors =
78-
(extendedSelectors?: ExtendSimulationSelectorsInput<any, ExtendedSchema>) =>
92+
(
93+
extendedSelectors?: ExtendSimulationSelectorsInputLoose<
94+
Auth0Selectors,
95+
Auth0Schema
96+
>
97+
) =>
7998
(args: ExtendSimulationSelectors<ExtendedSchema>) => {
80-
return extendedSelectors
81-
? // @ts-expect-error schema is cyclical, ignore extension for now
82-
{ ...inputSelectors(args), ...extendedSelectors(args) }
83-
: inputSelectors(args);
99+
const base = inputSelectors(args);
100+
if (!extendedSelectors) return base;
101+
const extResult = extendedSelectors(args);
102+
return {
103+
...(base as object),
104+
...(extResult as object),
105+
} as Auth0Selectors;
84106
};
85107

86-
export const extendStore = <T>(
108+
export type Auth0ExtendStoreInput = ExtendStoreConfig<
109+
Auth0Schema,
110+
Auth0Actions,
111+
Auth0Selectors
112+
>;
113+
114+
export const extendStore = (
87115
initialState: Auth0InitialStore | undefined,
88-
extended:
89-
| {
90-
actions: ExtendSimulationActionsInput<
91-
any,
92-
ExtendSimulationSchemaInput<T>
93-
>;
94-
selectors: ExtendSimulationSelectorsInput<
95-
any,
96-
ExtendSimulationSchemaInput<T>
97-
>;
98-
schema?: ExtendSimulationSchemaInput<T>;
99-
}
100-
| undefined
101-
) => ({
116+
extended?: Auth0ExtendStoreInput
117+
): {
118+
schema: ExtendSimulationSchemaInput<Auth0Schema>;
119+
actions?: ExtendSimulationActionsInput<Auth0Actions, Auth0Schema>;
120+
selectors?: ExtendSimulationSelectorsInput<Auth0Selectors, Auth0Schema>;
121+
logs?: boolean;
122+
} => ({
102123
actions: extendActions(extended?.actions),
103124
selectors: extendSelectors(extended?.selectors),
104125
schema: inputSchema(initialState, extended?.schema),

packages/auth0/test/helpers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ export const frontendSimulation: ReturnType<
55
> = createFoundationSimulationServer({
66
port: 3000,
77
// dummy route so it returns a 200 at `/`
8-
extendRouter(router, simulationStore) {
9-
router.get("/", (req, res) => {
8+
extendRouter(router, _simulationStore) {
9+
router.get("/", (_req, res) => {
1010
res.sendStatus(200);
1111
});
1212
},

packages/auth0/test/rules.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ describe("rules", () => {
8585
let code: string;
8686
let server: FoundationSimulatorListening<unknown>;
8787

88-
beforeEach(async ({ task }) => {
88+
beforeEach(async ({ task: _task }) => {
8989
({ code, server } = await createSimulation(
9090
"test/fixtures/rules-access-token"
9191
));

packages/foundation/example/extensiveServer/extend-api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export const extendRouter = (
55
router: Router,
66
simulationStore: ExtendedSimulationStore
77
) => {
8-
router.get("/extended-route", (req, res) => {
8+
router.get("/extended-route", (_req, res) => {
99
let dogs = simulationStore.schema.dogs.select(
1010
simulationStore.store.getState()
1111
);

packages/foundation/example/extensiveServer/openapi.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ const openapiSchemaFromRealEndpoint = {
2222
},
2323
},
2424
},
25+
"/trigger-webhook": {
26+
post: {
27+
summary: "Trigger a webhook",
28+
operationId: "triggerWebhook",
29+
responses: {
30+
200: {
31+
description: "webhook sent",
32+
},
33+
},
34+
},
35+
},
2536
},
2637
};
2738

@@ -130,7 +141,7 @@ function handlers(
130141
simulationStore: ExtendedSimulationStore
131142
): SimulationHandlers {
132143
return {
133-
getDogs: (_c, request, response, _next, routeMetadata) => {
144+
getDogs: (_c, _request, response, _next, routeMetadata) => {
134145
let dogs = simulationStore.schema.dogs.select(
135146
simulationStore.store.getState()
136147
);
@@ -140,7 +151,7 @@ function handlers(
140151
response.sendStatus(routeMetadata.defaultCode);
141152
}
142153
},
143-
putDogs: (c, req, response) => {
154+
putDogs: (_c, _req, response) => {
144155
simulationStore.store.dispatch(
145156
simulationStore.actions.batchUpdater([
146157
simulationStore.schema.dogs.increment(),
@@ -169,6 +180,12 @@ function handlers(
169180
);
170181
response.status(200).json({ dogs });
171182
},
183+
triggerWebhook: (c, _request, response) => {
184+
simulationStore.store.dispatch(
185+
simulationStore.actions.webhooks.onTest(c.request.body)
186+
);
187+
response.status(200).json({ status: "ok" });
188+
},
172189
};
173190
}
174191

packages/foundation/example/extensiveServer/store.ts

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,35 @@ import type {
22
SimulationStore,
33
ExtendSimulationActions,
44
ExtendSimulationSelectors,
5+
ExtendSimulationTasks,
56
ExtendSimulationSchema,
67
AnyState,
8+
ExtendStoreConfig,
79
} from "../../src/index.ts";
810

9-
export type ExtendedSchema = typeof inputSchema;
10-
type ExtendActions = typeof inputActions;
11-
type ExtendSelectors = typeof inputSelectors;
11+
export type ExtendedSchema = typeof schema;
12+
export type ExtendActions = typeof actions;
13+
export type ExtendSelectors = typeof selectors;
14+
// `tasks` is a function that returns { tasks: Callable[]; actions: Actions }
15+
// Export the Actions portion as the `ExtendTasks` type so it can be used
16+
// as the fourth generic parameter to `SimulationStore` (which expects the
17+
// actions shape, not the whole return type of the tasks function).
18+
export type ExtendTasks = ReturnType<typeof tasks>["actions"];
1219
export type ExtendedSimulationStore = SimulationStore<
1320
ReturnType<ExtendedSchema>,
1421
ReturnType<ExtendActions>,
15-
ReturnType<ExtendSelectors>
22+
ReturnType<ExtendSelectors>,
23+
ExtendTasks
1624
>;
1725

18-
const inputSchema = ({ slice }: ExtendSimulationSchema) => {
26+
const schema = ({ slice }: ExtendSimulationSchema) => {
1927
let slices = {
2028
dogs: slice.num(),
2129
};
2230
return slices;
2331
};
2432

25-
const inputActions = ({
33+
const actions = ({
2634
thunks,
2735
schema,
2836
}: ExtendSimulationActions<ExtendedSchema>) => {
@@ -38,7 +46,7 @@ const inputActions = ({
3846
return { addLotsOfDogs };
3947
};
4048

41-
const inputSelectors = ({
49+
const selectors = ({
4250
createSelector,
4351
schema,
4452
}: ExtendSimulationSelectors<ExtendedSchema>) => {
@@ -53,9 +61,35 @@ const inputSelectors = ({
5361
return { booleanSpecificNumbers };
5462
};
5563

56-
export const extendStore = {
64+
const tasks = ({ createWebhook }: ExtendSimulationTasks<ExtendedSchema>) => {
65+
const webhook = createWebhook("https://example.com/webhook");
66+
const onTest = webhook.create<{ id: string; name: string }>(
67+
"webhook:test",
68+
function* (ctx, next) {
69+
// the following would send off that request
70+
// but we don't want to post in tests
71+
ctx.request = ctx.req({
72+
body: JSON.stringify(ctx.payload),
73+
});
74+
75+
// calling this will proceed through the middleware chain
76+
// and actually send the request
77+
yield* next();
78+
}
79+
);
80+
81+
return { tasks: [webhook.task], actions: { webhooks: { onTest } } };
82+
};
83+
84+
export const extendStore: ExtendStoreConfig<
85+
ReturnType<ExtendedSchema>,
86+
ReturnType<ExtendActions>,
87+
ReturnType<ExtendSelectors>,
88+
ReturnType<typeof tasks>["actions"]
89+
> = {
5790
logs: false,
58-
actions: inputActions,
59-
selectors: inputSelectors,
60-
schema: inputSchema,
91+
actions,
92+
selectors,
93+
tasks,
94+
schema,
6195
};

packages/foundation/example/singleFileServer/index.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,12 @@ export function simulation(): FoundationSimulator<any> {
8282
],
8383
handlers({ store, schema, actions }) {
8484
return {
85-
getDogs: (c, req, res) => {
85+
getDogs: (_c, _req, res) => {
8686
let dogs = schema.boop.select(store.getState());
8787
res.status(200).json({ dogs });
8888
},
89-
putDogs: (c, req, res) => {
89+
putDogs: (_c, _req, res) => {
9090
store.dispatch(actions.batchUpdater([schema.boop.increment()]));
91-
// TODO the looped around TS does not seem to tackle this well
92-
// store.dispatch(actions.upsertTest({ ["1"]: { name: "Friend" } }));
9391
res.sendStatus(200);
9492
},
9593
};
@@ -100,7 +98,6 @@ export function simulation(): FoundationSimulator<any> {
10098
extendStore: {
10199
logs: false,
102100
actions: ({ thunks, schema }) => {
103-
// TODO attempt to remove this type as a requirement
104101
let upsertTest = thunks.create<AnyState>(
105102
"user:upsert",
106103
function* boop(ctx, next) {
@@ -114,9 +111,7 @@ export function simulation(): FoundationSimulator<any> {
114111

115112
return { upsertTest };
116113
},
117-
selectors: () => ({}),
118114
schema: ({ slice }: ExtendSimulationSchema) => {
119-
// TODO attempt to remove this type as a requirement
120115
let slices = {
121116
test: slice.table(),
122117
booping: slice.str(),
@@ -126,7 +121,7 @@ export function simulation(): FoundationSimulator<any> {
126121
},
127122
},
128123
extendRouter(router, simulationStore) {
129-
router.get("/extended-route", (req, res) => {
124+
router.get("/extended-route", (_req, res) => {
130125
let dogs = simulationStore.schema.boop.select(
131126
simulationStore.store.getState()
132127
);

0 commit comments

Comments
 (0)