Repository: pmcelhaney/counterfact
Pull request #59 (2022-06-29 16:22:47)

44 user defined context instead of store
- renamed store to context
- add a second argument to counterfact() for setting the initial context



Patch:
diff --git a/.changeset/spicy-items-begin.md b/.changeset/spicy-items-begin.md
new file mode 100644
index 0000000..7ee3646
--- /dev/null
+++ b/.changeset/spicy-items-begin.md
@@ -0,0 +1,5 @@
+---
+"counterfact": patch
+---
+
+allow the intial context (nee "store") to be passed as the second argument to `counterfact()`
diff --git a/README.md b/README.md
index 02a3b2b..2dc3ec5 100644
--- a/README.md
+++ b/README.md
@@ -40,8 +40,11 @@ const PORT = 3100;
 
 const app = new Koa();
 
+const initialContext = {};
+
 const { koaMiddleware } = await counterfact(
-  fileURLToPath(new URL("routes/", import.meta.url))
+  fileURLToPath(new URL("routes/", import.meta.url)),
+  initialContext
 );
 
 app.use(koaMiddleware);
@@ -114,15 +117,15 @@ This feature was [inspired by Next.js](https://nextjs.org/docs/routing/dynamic-r
 
 ### State
 
-State management is handled through a plain old JavaScript object called `store`. The store is initialized as an empty object (`{}`). You can read and write its keys however you like. Changes will persist from one request to another as long as the server is running.
+State management is handled through a plain old JavaScript object called `context`, which is passed as the second argument to the `counterfact()` function (default value = `{}`). You can modify the context object however you like. Changes will persist from one request to another as long as the server is running.
 
-There are no rules around how you manipulate the store. Yes, you read that right.
+There are no rules around how you manipulate the context. Yes, you read that right.
 
 ```js
-export function GET({ greeting, path, store }) {
-  store.visits ??= {};
-  store.visits[path.name] ??= 0;
-  store.visits[path.name] += 1;
+export function GET({ greeting, path, context }) {
+  context.visits ??= {};
+  context.visits[path.name] ??= 0;
+  context.visits[path.name] += 1;
 
   return {
     body: "${query.greeting}, ${path.name}!",
@@ -135,23 +138,23 @@ export function GET({ greeting, path, store }) {
 So far we've only covered `GET` requests. What about `POST`, `PUT`, `PATCH` and `DELETE`? All HTTP request methods are supported. It's a matter exporting functions with the corresponding names.
 
 ```js
-  export function GET({ path, store }) {
+  export function GET({ path, context }) {
 
-    store.friends ??= {};
-    store.friends[path.name] ??= {
+    context.friends ??= {};
+    context.friends[path.name] ??= {
       appearance: "lovely"
     };
 
     return {
-      body: "Hello, ${path.name}. You look ${store.friends[name].appearance} today!"
+      body: "Hello, ${path.name}. You look ${context.friends[name].appearance} today!"
     }
   }
 
-  export function POST(path, store, body) {
+  export function POST(path, context, body) {
 
-    store.friends ??= {};
-    store.friends[path.name] ??= {};
-    store.friends[appearance] = body.appearance;
+    context.friends ??= {};
+    context.friends[path.name] ??= {};
+    context.friends[appearance] = body.appearance;
 
     return {
       body: {
diff --git a/demo-ts/context/Store.ts b/demo-ts/context/Store.ts
index 6cddb9a..2de7a63 100644
--- a/demo-ts/context/Store.ts
+++ b/demo-ts/context/Store.ts
@@ -4,9 +4,9 @@ interface Visits {
   [name: string]: number;
 }
 
-export class Store {
+export class Context {
   public visits: Visits = {};
 }
 
 // Counterfact will load this object and use it as the initial state.
-export const store = new Store();
+export const store = new Context();
diff --git a/demo-ts/context/context.ts b/demo-ts/context/context.ts
new file mode 100644
index 0000000..4d083cf
--- /dev/null
+++ b/demo-ts/context/context.ts
@@ -0,0 +1,12 @@
+// This file will be maintained by hand.
+
+interface Visits {
+  [name: string]: number;
+}
+
+export class Context {
+  public visits?: Visits = {};
+}
+
+// Counterfact will load this object and use it as the initial state.
+export const context = new Context();
diff --git a/demo-ts/routes/#types.ts b/demo-ts/routes/#types.ts
index dc9cb13..36bc3c1 100644
--- a/demo-ts/routes/#types.ts
+++ b/demo-ts/routes/#types.ts
@@ -1,7 +1,7 @@
 import type { HttpResponseStatusCode } from "../types/Http";
-import type { Store } from "../context/Store";
+import type { Context } from "../context/context";
 
-export type Get_count = (request: { store: Store }) => {
+export type Get_count = (request: { context: Context }) => {
   body: string;
   status?: HttpResponseStatusCode;
 };
diff --git a/demo-ts/routes/count.ts b/demo-ts/routes/count.ts
index 3b5253d..0356f14 100644
--- a/demo-ts/routes/count.ts
+++ b/demo-ts/routes/count.ts
@@ -1,16 +1,18 @@
 import type { Get_count } from "./#types";
 
-export const GET: Get_count = ({ store }) => {
-  // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/no-unnecessary-condition
-  if (!store.visits) {
+export const GET: Get_count = ({ context }) => {
+  if (context.visits === undefined) {
     return {
       body: "You have not visited anyone yet.",
     };
   }
 
   return {
-    body: Object.entries(store.visits)
-      .map(([page, count]) => `You visited ${page} ${count} times.`)
+    body: Object.entries(context.visits)
+      .map(
+        ([page, count]: [string, number]) =>
+          `You visited ${page} ${count} times.`
+      )
       .join("\n"),
   };
 };
diff --git a/demo-ts/routes/hello/#types.ts b/demo-ts/routes/hello/#types.ts
index a1769bb..3901a64 100644
--- a/demo-ts/routes/hello/#types.ts
+++ b/demo-ts/routes/hello/#types.ts
@@ -1,16 +1,16 @@
-import type { Store } from "../../context/Store";
+import type { Context } from "../../context/context";
 import type { HttpResponseStatusCode } from "../../types/Http";
 import type { Greeting } from "../../types/Greeting";
 import type { HtmlImgTag } from "../../types/HtmlImgTag";
 
 export type Get_name = (request: {
-  store: Store;
+  context: Context;
   query: { greeting?: string };
   path: { name: string };
 }) => { body: Greeting; status?: HttpResponseStatusCode };
 
 export type Get_kitty = (request: {
-  store: Store;
+  context: Context;
   query: { greeting?: string };
   path: { name: string };
 }) => { body: HtmlImgTag; status?: HttpResponseStatusCode };
diff --git a/demo-ts/routes/hello/[name].ts b/demo-ts/routes/hello/[name].ts
index d855f0c..d4caa8f 100644
--- a/demo-ts/routes/hello/[name].ts
+++ b/demo-ts/routes/hello/[name].ts
@@ -1,9 +1,9 @@
 import type { Get_name } from "./#types";
 
-export const GET: Get_name = ({ path, store, query }) => {
-  store.visits ??= {};
-  store.visits[path.name] ??= 0;
-  store.visits[path.name] += 1;
+export const GET: Get_name = ({ path, context, query }) => {
+  context.visits ??= {};
+  context.visits[path.name] ??= 0;
+  context.visits[path.name] += 1;
 
   return {
     body: `${query.greeting ?? "Hello"}, ${path.name}!`,
diff --git a/demo/routes/count.js b/demo/routes/count.js
index d78254e..13ed7f6 100644
--- a/demo/routes/count.js
+++ b/demo/routes/count.js
@@ -1,12 +1,12 @@
-export function GET({ store }) {
-  if (!store.visits) {
+export function GET({ context }) {
+  if (!context.visits) {
     return {
       body: "You have not visited anyone yet.",
     };
   }
 
   return {
-    body: Object.entries(store.visits)
+    body: Object.entries(context.visits)
       .map(([page, count]) => `You visited ${page} ${count} times.`)
       .join("\n"),
   };
diff --git a/demo/routes/hello/[name].js b/demo/routes/hello/[name].js
index 4ac07cd..ec3a01f 100644
--- a/demo/routes/hello/[name].js
+++ b/demo/routes/hello/[name].js
@@ -1,7 +1,7 @@
-export function GET({ path, store, query }) {
-  store.visits ??= {};
-  store.visits[path.name] ??= 0;
-  store.visits[path.name] += 1;
+export function GET({ path, context, query }) {
+  context.visits ??= {};
+  context.visits[path.name] ??= 0;
+  context.visits[path.name] += 1;
 
   if (!path) {
     return { body: "Hello, stranger!" };
diff --git a/src/counterfact.js b/src/counterfact.js
index 0f95288..a3f46f7 100644
--- a/src/counterfact.js
+++ b/src/counterfact.js
@@ -3,8 +3,8 @@ import { Dispatcher } from "./dispatcher.js";
 import { koaMiddleware } from "./koa-middleware.js";
 import { ModuleLoader } from "./module-loader.js";
 
-export async function counterfact(basePath) {
-  const registry = new Registry();
+export async function counterfact(basePath, context = {}) {
+  const registry = new Registry(context);
   const dispatcher = new Dispatcher(registry);
   const moduleLoader = new ModuleLoader(basePath, registry);
 
diff --git a/src/dispatcher.js b/src/dispatcher.js
index ce35c4b..341c9cf 100644
--- a/src/dispatcher.js
+++ b/src/dispatcher.js
@@ -13,10 +13,10 @@ export class Dispatcher {
       // path: parts.slice(remainingParts).join("/"),
 
       reduce: (reducer) => {
-        this.registry.store = reducer(this.registry.store);
+        this.registry.context = reducer(this.registry.context);
       },
 
-      store: this.registry.store,
+      context: this.registry.context,
       body,
       query,
     });
diff --git a/src/registry.js b/src/registry.js
index d1705be..34af852 100644
--- a/src/registry.js
+++ b/src/registry.js
@@ -5,10 +5,10 @@ export class Registry {
     children: {},
   };
 
-  store;
+  context;
 
-  constructor(store = {}) {
-    this.store = store;
+  constructor(context = {}) {
+    this.context = context;
   }
 
   add(url, module) {


Test patch:
diff --git a/test/counterfact.test.js b/test/counterfact.test.js
index 66a3258..3c7d970 100644
--- a/test/counterfact.test.js
+++ b/test/counterfact.test.js
@@ -43,4 +43,31 @@ describe("integration test", () => {
       await moduleLoader.stopWatching();
     });
   });
+
+  it("loads the initial context", async () => {
+    const app = new Koa();
+    const request = supertest(app.callback());
+    const files = {
+      "paths/hello.mjs": `
+        export async function GET({context}) {
+          return await Promise.resolve({ body: "Hello " + context.name });
+        }
+      `,
+    };
+
+    await withTemporaryFiles(files, async (basePath) => {
+      const { koaMiddleware, moduleLoader } = await counterfact(
+        `${basePath}/paths`,
+        { name: "World" }
+      );
+
+      app.use(koaMiddleware);
+
+      const getResponse = await request.get("/hello");
+
+      expect(getResponse.text).toBe("Hello World");
+
+      await moduleLoader.stopWatching();
+    });
+  });
 });
diff --git a/test/dispatcher.test.js b/test/dispatcher.test.js
index 4663e97..405742b 100644
--- a/test/dispatcher.test.js
+++ b/test/dispatcher.test.js
@@ -100,8 +100,8 @@ describe("a dispatcher", () => {
       GET({ reduce, path }) {
         const amountToIncrement = Number.parseInt(path.value, 10);
 
-        reduce((store) => ({
-          value: store.value + amountToIncrement,
+        reduce((context) => ({
+          value: context.value + amountToIncrement,
         }));
 
         return { body: "incremented" };
@@ -116,7 +116,7 @@ describe("a dispatcher", () => {
       body: "",
     });
 
-    expect(registry.store.value).toBe(1);
+    expect(registry.context.value).toBe(1);
 
     await dispatcher.request({
       method: "GET",
@@ -124,18 +124,17 @@ describe("a dispatcher", () => {
       body: "",
     });
 
-    expect(registry.store.value).toBe(3);
+    expect(registry.context.value).toBe(3);
   });
 
   it("allows the store to be mutated directly", async () => {
     const registry = new Registry({ value: 0 });
 
     registry.add("/increment/{value}", {
-      GET({ store, path }) {
+      GET({ context, path }) {
         const amountToIncrement = Number.parseInt(path.value, 10);
 
-        // eslint-disable-next-line no-param-reassign
-        store.value += amountToIncrement;
+        context.value += amountToIncrement;
 
         return { body: "incremented" };
       },
@@ -151,7 +150,7 @@ describe("a dispatcher", () => {
 
     expect(result.body).toBe("incremented");
 
-    expect(registry.store.value).toBe(1);
+    expect(registry.context.value).toBe(1);
 
     await dispatcher.request({
       method: "GET",
@@ -159,7 +158,7 @@ describe("a dispatcher", () => {
       body: "",
     });
 
-    expect(registry.store.value).toBe(3);
+    expect(registry.context.value).toBe(3);
   });
 });
 
diff --git a/test/module-loader.test.js b/test/module-loader.test.js
index 2dee138..f2ccff2 100644
--- a/test/module-loader.test.js
+++ b/test/module-loader.test.js
@@ -137,7 +137,7 @@ describe("a module loader", () => {
         const response = await registry.endpoint(
           "GET",
           "/change"
-        )({ path: "", reduce: () => undefined, store: {} });
+        )({ path: "", reduce: () => undefined, context: {} });
 
         expect(response.body).toBe("after change");
         expect(registry.exists("GET", "/late/addition")).toBe(true);
diff --git a/test/registry.test.js b/test/registry.test.js
index 4bbee55..ae8791a 100644
--- a/test/registry.test.js
+++ b/test/registry.test.js
@@ -56,7 +56,7 @@ describe("a scripted server", () => {
         return foo;
       },
 
-      store: {},
+      context: {},
     };
     const getA = await registry.endpoint("GET", "/a")(props);
     const getB = await registry.endpoint("GET", "/b")(props);