Skip to content

Context Package (@grest-ts/context)

Request-scoped context storage using AsyncLocalStorage. Use this for short-lived data that exists during a single request, job, or message processing - such as tracing IDs, auth data, or request metadata.

When do you need it?

  • Framework level: The framework uses GGContext to propagate request data like trace IDs, authentication, and request metadata through the call stack.
  • Your code: Use it when you need to access request-specific data without passing it through every function.

Note: GGContext is for request-scoped data (short-lived). For long-lived services, use GGLocator instead.

Basic usage example: Auth Context Through Services

typescript
// Define auth context key
const AuthUserKey = new GGContextKey("authUser", IsObject({id: IsString, role: IsString}))

// ServiceA - called by HTTP handler, auth is already set in context
class ServiceA {
    async updateDocument(docId: string, content: string) {
        // No need to pass user - ServiceB reads it from context
        await serviceB.saveDocument(docId, content)
    }
}

// ServiceB - deeper in the call chain
class ServiceB {
    async saveDocument(docId: string, content: string) {
        if (AuthUserKey.get()?.role !== "admin") throw new FORBIDDEN()
        // ... save document
    }
}

Basic Usage

Defining a Context Key

typescript
import {GGContextKey} from "@grest-ts/context"
import {t} from "@grest-ts/schema"

const RequestIdKey = new GGContextKey("requestId", t.string())

// With default value
const DebugModeKey = new GGContextKey("debugMode", t.boolean(), {
    defaultValue: false,
    description: "Enable debug logging for this request"
})

Setting and Accessing Context

typescript
import {GGContext} from "@grest-ts/context"

new GGContext("request").run(() => {

    // Set context value
    RequestIdKey.set("req-123")

    // Get context value (returns undefined if not set, or defaultValue if defined)
    const requestId = RequestIdKey.get()

    // Check if value exists
    if (RequestIdKey.has()) { ...
    }

    // Assert value exists (throws if not set)
    const id = RequestIdKey.assert()

    // Delete value
    RequestIdKey.delete()

})

Using GGContextStore Static Helpers

typescript
import {GGContextStore} from "@grest-ts/context"

// Check if context exists
if (GGContextStore.hasContext()) {
    const ctx = GGContextStore.getContext()
}

// Safe access (returns undefined if no context)
const ctx = GGContextStore.tryGetContext()

// Branch from current context (child inherits parent values)
const childCtx = GGContextStore.branch("child-request")

Context Branching

Child contexts inherit parent values but can override them:

typescript
const parentCtx = new GGContext("parent")
parentCtx.run(() => {
    RequestIdKey.set("req-123")

    const childCtx = GGContextStore.branch("child")
    childCtx.run(() => {
        RequestIdKey.get()  // "req-123" - inherited from parent
        RequestIdKey.set("req-456")  // Override in child
        RequestIdKey.get()  // "req-456"
    })

    RequestIdKey.get()  // "req-123" - parent unchanged
})

Async Context Helpers

Preserve context across async boundaries:

typescript
GGContextStore.setTimeout(() => {
    // Context is preserved here
    RequestIdKey.get()
}, 1000)

GGContextStore.setInterval(() => { ...
}, 5000)
GGContextStore.setImmediate(() => { ...
})