From 48a0c781ef440c319e87963b70b4093bf5c93a2e Mon Sep 17 00:00:00 2001 From: Patrick McElhaney Date: Fri, 28 Jun 2024 12:41:20 -0400 Subject: [PATCH] ability at runtime for a context object to access other context objects --- .changeset/tiny-chefs-greet.md | 5 +++++ docs/usage.md | 11 +++++++++++ src/server/module-loader.ts | 4 +++- test/server/module-loader.test.ts | 30 ++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 .changeset/tiny-chefs-greet.md diff --git a/.changeset/tiny-chefs-greet.md b/.changeset/tiny-chefs-greet.md new file mode 100644 index 00000000..8bb5ae63 --- /dev/null +++ b/.changeset/tiny-chefs-greet.md @@ -0,0 +1,5 @@ +--- +"counterfact": minor +--- + +ability at runtime for a context object to access other context objects diff --git a/docs/usage.md b/docs/usage.md index f11b8352..48980a72 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -192,6 +192,17 @@ By default, each `_.context.ts` delegates to its parent directory, so you can de > [!TIP] > You can make the context objects do whatever you want, including things like writing to databases. But remember that Counterfact is meant for testing, so holding on to data between sessions is an anti-pattern. Keeping everything in memory also makes it fast. +> [!TIP] +> An object with loadContext() function is passed to the constructor of a context class. You can use it load the context from another directory at runtime. This is an advanced use case. +> +> ```ts +> class Context { +> constructor({ loadContext }) { +> this.rootContext = loadContext("/"); +> } +> } +> ``` + ### Security: the `$.auth` object If a username and password are sent via basic authentication, the username and password can be found via `$.auth.username` and `$.auth.password` respectively. diff --git a/src/server/module-loader.ts b/src/server/module-loader.ts index 89f242a7..0bc9243e 100644 --- a/src/server/module-loader.ts +++ b/src/server/module-loader.ts @@ -175,7 +175,9 @@ export class ModuleLoader extends EventTarget { // @ts-expect-error TS says Context has no constructable signatures but that's not true? // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - new endpoint.Context(), + new endpoint.Context({ + loadContext: (path: string) => this.contextRegistry.find(path), + }), ); } } else { diff --git a/test/server/module-loader.test.ts b/test/server/module-loader.test.ts index d7ad893c..8c0222a3 100644 --- a/test/server/module-loader.test.ts +++ b/test/server/module-loader.test.ts @@ -190,6 +190,36 @@ describe("a module loader", () => { }); }); + it("provides the parent context if the local _.context.ts doesn't export a default", async () => { + await usingTemporaryFiles(async ($) => { + await $.add( + "_.context.js", + "export class Context { constructor({loadContext}) { this.loadContext = loadContext } }", + ); + await $.add("a/_.context.js", "export class Context { name = 'a' }"); + await $.add("package.json", '{ "type": "module" }'); + + const registry: Registry = new Registry(); + + const contextRegistry: ContextRegistry = new ContextRegistry(); + + const loader: ModuleLoader = new ModuleLoader( + $.path("."), + + registry, + contextRegistry, + ); + + await loader.load(); + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any + const rootContext = contextRegistry.find("/") as any; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + expect(rootContext?.loadContext("/a")?.name).toBe("a"); + }); + }); + // can't test because I can't get Jest to refresh modules it.skip("updates the registry when a dependency is updated", async () => { await usingTemporaryFiles(async ($) => {