Skip to main content

DI Integration

The ProxyExecutionContext class provides an IExecutionContext whose underlying execution context is resolved from a DI container.

The proxy supports the IExecutionContext operations:

  • run(invocable) resolves the execution context and runs the invocable inside its context.
  • Reads such as get, exists, missing, contains, getOr and getOrFail resolve the execution context for each operation.
  • Mutations such as put, add, update, remove and when return an IContext. A chain of mutations reuses a single resolution, so chained mutations are applied to the same execution context.

The proxy resolves the execution context lazily, so it supports the container's configured lifetimes. Scoped registrations must be used inside container.run(), while singleton and transient registrations can be used once container.init() has been awaited. Create the proxy before calling init().

Initial configuration​

Create the container, register the execution context, and wrap it with ProxyExecutionContext:

1. Create the dependency injection container​

./samples/container.ts
import { Container } from "eridu-tech/di";
import { ExecutionContext } from "eridu-tech/execution-context";
import { AlsExecutionContextAdapter } from "eridu-tech/execution-context/als-execution-context-adapter";

export const executionContext = new ExecutionContext(
new AlsExecutionContextAdapter(),
);
export const container = new Container({ executionContext });

2. Register the execution context and create the proxy​

./samples/execution-context-di.ts
import { genericToken, LIFETIME } from "eridu-tech/di/contracts";
import { ExecutionContext } from "eridu-tech/execution-context";
import { AlsExecutionContextAdapter } from "eridu-tech/execution-context/als-execution-context-adapter";
import { ProxyExecutionContext } from "eridu-tech/execution-context/di";
import { container } from "./container.js";

import type { IExecutionContext } from "eridu-tech/execution-context/contracts";

const executionContextToken =
genericToken<IExecutionContext>("executionContext");

container.registerFactory({
token: executionContextToken,
factory: () => new ExecutionContext(new AlsExecutionContextAdapter()),
deps: {},
lifetime: LIFETIME.SINGLETON,
});

// Create the proxy before container.init()
export const executionContext = new ProxyExecutionContext({
container,
executionContextToken,
});

await container.init();

Usage​

1. Running code with context​

You can run code within a context boundary through the proxy. The context values are accessible throughout the call chain:

./samples/execution-context-di-run.ts
import { contextToken } from "eridu-tech/execution-context/contracts";
import { executionContext } from "./execution-context-di.js";

const requestIdToken = contextToken<string>("requestId");

export function handleRequest(): string | null {
return executionContext.run(() => {
executionContext.put(requestIdToken, "req-123");

return executionContext.get(requestIdToken);
});
}

2. Reading context values​

Read operations are resolved from the container and delegated to the execution context:

./samples/execution-context-di-read.ts
import { contextToken } from "eridu-tech/execution-context/contracts";
import { executionContext } from "./execution-context-di.js";

type User = { id: string; name: string };

const userToken = contextToken<User>("user");
const userIdToken = contextToken<string>("userId");

executionContext.run(() => {
executionContext.put(userToken, { id: "123", name: "Alice" });

// { id: "123", name: "Alice" }
const user = executionContext.get(userToken);
// true
const hasUser = executionContext.exists(userToken);
// false
const hasUserId = executionContext.exists(userIdToken);
// "anonymous"
const userId = executionContext.getOr(userIdToken, "anonymous");
// Throws NotFoundExecutionContextError when the token is missing
const userOrFail = executionContext.getOrFail(userToken);

console.log("user:", user);
console.log("has user:", hasUser);
console.log("has user id:", hasUserId);
console.log("user id:", userId);
console.log("user or fail:", userOrFail);
});

getOrFail() throws a NotFoundExecutionContextError when the token is missing. Import it from eridu-tech/execution-context/contracts to catch it.

3. Chaining mutations​

Mutating operations return an IContext, so a group of mutations can be chained:

./samples/execution-context-di-chain.ts
import { contextToken } from "eridu-tech/execution-context/contracts";
import { executionContext } from "./execution-context-di.js";

type User = { id: string; name: string };

const userToken = contextToken<User>("user");
const requestIdToken = contextToken<string>("requestId");

executionContext.run(() => {
// Mutations return the context, so a group of mutations can be chained
executionContext
.put(userToken, { id: "123", name: "Alice" })
.put(requestIdToken, "req-123");
});

The chain keeps the resolution of the execution context, so every mutation in it targets the same instance.

4. Conditional updates​

You can conditionally update the context with when():

./samples/execution-context-di-conditional.ts
import { contextToken } from "eridu-tech/execution-context/contracts";
import { executionContext } from "./execution-context-di.js";

type User = { id: string; name: string };

const userToken = contextToken<User>("user");

executionContext.run(() => {
executionContext.when(true, (context) =>
context.put(userToken, { id: "123", name: "Alice" }),
);
});

5. Scoped lifetime​

A scoped registration is resolved for each operation, so the proxy has to be used inside container.run():

./samples/execution-context-di-scoped.ts
import { genericToken, LIFETIME } from "eridu-tech/di/contracts";
import { ExecutionContext } from "eridu-tech/execution-context";
import { AlsExecutionContextAdapter } from "eridu-tech/execution-context/als-execution-context-adapter";
import { contextToken } from "eridu-tech/execution-context/contracts";
import { ProxyExecutionContext } from "eridu-tech/execution-context/di";
import { container } from "./container.js";

import type { IExecutionContext } from "eridu-tech/execution-context/contracts";

type User = { id: string; name: string };

const userToken = contextToken<User>("user");
const executionContextToken =
genericToken<IExecutionContext>("executionContext");

container.registerFactory({
token: executionContextToken,
factory: () => new ExecutionContext(new AlsExecutionContextAdapter()),
deps: {},
lifetime: LIFETIME.SCOPED,
});

const executionContext = new ProxyExecutionContext({
container,
executionContextToken,
});

await container.init();

// The scoped execution context is resolved per operation, so the proxy must be
// used inside container.run()
await container.run({
scope: () => {
executionContext.run(() => {
executionContext.put(userToken, { id: "123", name: "Alice" });
});
},
});

Resolving the proxy outside of a scope throws an error.

Further information​

For further information refer to eridu-tech/execution-context API docs.