Skip to content

DOM: inject, context-request

Defined in: src/dom/events.ts:64

Standard W3C Community Context Protocol request event.

Dispatched synchronously from the injection target so ancestors can answer in the same task. Construct with (context, callback, { subscribe, multi }?); both flags default to false.

context

Token being requested.

callback

Response callback receiving the resolved value.

options

Optional { subscribe, multi } flags.

import { ContextRequestEvent } from '@sandlada/document-context'
const event = new ContextRequestEvent('logger', (value, dispose) => {
console.log('got logger', value)
})
element.dispatchEvent(event)
Type Parameter Default type
ContextType any
ValueType any

new ContextRequestEvent<ContextType, ValueType>(context, callback, options?): ContextRequestEvent<ContextType, ValueType>

Defined in: src/dom/events.ts:68

Parameter Type
context ContextType
callback ContextCallback<ValueType>
options? { multi?: boolean; subscribe?: boolean; }
options.multi? boolean
options.subscribe? boolean

ContextRequestEvent<ContextType, ValueType>

CustomEvent<ContextRequestDetail<ContextType, ValueType>>.constructor

Property Modifier Type Description Inherited from
AT_TARGET readonly 2 - CustomEvent.AT_TARGET
bubbles readonly boolean The bubbles read-only property of the Event interface indicates whether the event bubbles up through the DOM tree or not. MDN Reference CustomEvent.bubbles
BUBBLING_PHASE readonly 3 - CustomEvent.BUBBLING_PHASE
cancelable readonly boolean The cancelable read-only property of the Event interface indicates whether the event can be canceled, and therefore prevented as if the event never happened. MDN Reference CustomEvent.cancelable
cancelBubble public boolean The cancelBubble property of the Event interface is deprecated. Use Event.stopPropagation() instead. Setting its value to true before returning from an event handler prevents propagation of the event. In later implementations, setting this to false does nothing. See Browser compatibility for details. :::caution[Deprecated] MDN Reference ::: CustomEvent.cancelBubble
CAPTURING_PHASE readonly 1 - CustomEvent.CAPTURING_PHASE
composed readonly boolean The read-only composed property of the Event interface returns a boolean value which indicates whether or not the event will propagate across the shadow DOM boundary into the standard DOM. MDN Reference CustomEvent.composed
currentTarget readonly EventTarget | null The currentTarget read-only property of the Event interface identifies the element to which the event handler has been attached. MDN Reference CustomEvent.currentTarget
defaultPrevented readonly boolean The defaultPrevented read-only property of the Event interface returns a boolean value indicating whether or not the call to Event.preventDefault() canceled the event. MDN Reference CustomEvent.defaultPrevented
detail readonly ContextRequestDetail The read-only detail property of the CustomEvent interface returns any data passed when initializing the event. MDN Reference CustomEvent.detail
eventPhase readonly number The eventPhase read-only property of the Event interface indicates which phase of the event flow is currently being evaluated. MDN Reference CustomEvent.eventPhase
isTrusted readonly boolean The isTrusted read-only property of the Event interface is a boolean value that is true when the event was generated by the user agent (including via user actions and programmatic methods such as HTMLElement.focus()), and false when the event was dispatched via EventTarget.dispatchEvent(). The only exception is the click event, which initializes the isTrusted property to false in user agents. MDN Reference CustomEvent.isTrusted
NONE readonly 0 - CustomEvent.NONE
returnValue public boolean The Event property returnValue indicates whether the default action for this event has been prevented or not. :::caution[Deprecated] MDN Reference ::: CustomEvent.returnValue
srcElement readonly EventTarget | null The deprecated Event.srcElement is an alias for the Event.target property. Use Event.target instead. :::caution[Deprecated] MDN Reference ::: CustomEvent.srcElement
target readonly EventTarget | null The read-only target property of the Event interface is a reference to the object onto which the event was dispatched. It is different from Event.currentTarget when the event handler is called during the bubbling or capturing phase of the event. MDN Reference CustomEvent.target
timeStamp readonly number The timeStamp read-only property of the Event interface returns the time (in milliseconds) at which the event was created. MDN Reference CustomEvent.timeStamp
type readonly string The type read-only property of the Event interface returns a string containing the event’s type. It is set when the event is constructed and is the name commonly used to refer to the specific event, such as click, load, or error. MDN Reference CustomEvent.type

composedPath(): EventTarget[]

Defined in: site/node_modules/typescript/lib/lib.dom.d.ts:14245

The composedPath() method of the Event interface returns the event’s path which is an array of the objects on which listeners will be invoked. This does not include nodes in shadow trees if the shadow root was created with its ShadowRoot.mode closed.

MDN Reference

EventTarget[]

CustomEvent.composedPath

initCustomEvent(type, bubbles?, cancelable?, detail?): void

Defined in: site/node_modules/typescript/lib/lib.dom.d.ts:11288

The CustomEvent.initCustomEvent() method initializes a CustomEvent object. If the event has already been dispatched, this method does nothing.

Parameter Type
type string
bubbles? boolean
cancelable? boolean
detail? ContextRequestDetail<ContextType, ValueType>

void

CustomEvent.initCustomEvent

initEvent(type, bubbles?, cancelable?): void

Defined in: site/node_modules/typescript/lib/lib.dom.d.ts:14252

The Event.initEvent() method is used to initialize the value of an event created using Document.createEvent().

Parameter Type
type string
bubbles? boolean
cancelable? boolean

void

CustomEvent.initEvent

preventDefault(): void

Defined in: site/node_modules/typescript/lib/lib.dom.d.ts:14258

The preventDefault() method of the Event interface tells the user agent that the event is being explicitly handled, so its default action, such as page scrolling, link navigation, or pasting text, should not be taken.

MDN Reference

void

CustomEvent.preventDefault

stopImmediatePropagation(): void

Defined in: site/node_modules/typescript/lib/lib.dom.d.ts:14264

The stopImmediatePropagation() method of the Event interface prevents other listeners of the same event from being called.

MDN Reference

void

CustomEvent.stopImmediatePropagation

stopPropagation(): void

Defined in: site/node_modules/typescript/lib/lib.dom.d.ts:14270

The stopPropagation() method of the Event interface prevents further propagation of the current event in the capturing and bubbling phases. It does not, however, prevent any default behaviors from occurring; for instance, clicks on links are still processed. If you want to stop those behaviors, see the preventDefault() method. It also does not prevent propagation to other event-handlers of the current element. If you want to stop those, see stopImmediatePropagation().

MDN Reference

void

CustomEvent.stopPropagation

Defined in: src/dom/events.ts:35

Event detail payload for a context request.

Type Parameter
ContextType
ValueType
Property Modifier Type Description
callback readonly ContextCallback<ValueType> Invoked by the answering provider with the value.
context readonly ContextType Requested token (string key or branded token object).
multi? readonly boolean When true, every ancestor answers and the event keeps bubbling (injectAll); when false, the first answer stops propagation.
subscribe? readonly boolean When true, the provider keeps pushing updates (state-slice streaming) until the dispose callback runs.

Defined in: src/dom/inject-all.ts:28

Ordering plus cancellation options for injectAllAsync.

Property Modifier Type Description Inherited from
direction? readonly "bottomUp" | "topDown" See IInjectAllOptions. IInjectAllOptions.direction
signal? readonly AbortSignal Currently accepted for API symmetry; collection itself is synchronous event dispatch followed by Promise.all, so abort only affects awaiting callers that wire it manually. -

Defined in: src/dom/inject-all.ts:16

Ordering options for injectAll / injectAllAsync.

Property Modifier Type Description
direction? readonly "bottomUp" | "topDown" 'bottomUp' (default) collects nearest-first as the context-request event bubbles; 'topDown' reverses the array so the farthest ancestor comes first.

Defined in: src/dom/inject-async.ts:28

Options for injectAsync / injectAllAsync.

Property Modifier Type Description
signal? readonly AbortSignal Optional AbortSignal for per-caller cancellation. A caller abort rejects only that caller’s promise with an AbortError DOMException; the shared in-flight provider promise is unaffected.

ContextCallback<ValueType> = (value, dispose?) => void

Defined in: src/dom/events.ts:20

Response callback carried in ContextRequestDetail.

The provider invokes it with the resolved value; the optional second argument unsubscribes a streaming (subscribe: true) feed.

Type Parameter
ValueType
Parameter Type Description
value ValueType Resolved service instance or state slice.
dispose? () => void Optional streaming unsubscribe for subscribe: true requests.

void

void.

const CONTEXT_REQUEST_EVENT: "context-request" = 'context-request'

Defined in: src/dom/events.ts:7

Well-known event name for the W3C Community Context Protocol request channel. Dispatched by inject() / injectAll() and answered by setupProviderResponder() hosts. Always bubbles: true, composed: true, cancelable: true so requests cross shadow boundaries.

Migrates a session to a new host document (iframe, popup, or Picture-in-Picture window).

Stops the old document’s root observer when it differs, starts the new one, runs blueprint adopt hooks as (session, newDocument), and dispatches a bubbling, composed context-adopt CustomEvent with detail: { newDocument }. Hook and dispatch failures are sandboxed. The session target element itself is not moved; move or adopt the node first, then call this to rewire observation.

export function adoptSessionToDocument(
session: ISession<any, any>,
newDocument: Document
): void;

Defined in: src/dom/observer.ts

import { adoptSessionToDocument } from '@sandlada/document-context'
const popup = window.open('', '_blank')!
popup.document.body.appendChild(session.target)
adoptSessionToDocument(session, popup.document)

Clears every entry in the page-wide singleton registry.

Primarily a test-isolation helper (afterEach(clearGlobalSingletons)). Live sessions keep their scoped caches; only future 'singleton' resolutions re-run factories.

export function clearGlobalSingletons(): void;

Defined in: src/dom/inject.ts

import { clearGlobalSingletons } from '@sandlada/document-context'
afterEach(() => clearGlobalSingletons())

Reads a 'singleton' instance from the page-wide global registry without touching the DOM.

export function getGlobalSingleton<T = unknown>(token: string | ServiceToken<T>): T | undefined;

Defined in: src/dom/inject.ts

import { getGlobalSingleton } from '@sandlada/document-context'
const logger = getGlobalSingleton('logger')

Curried synchronous dependency injection verb.

Resolves token against an ISession directly (local providers first, then delegating to the session host element) or against an HTMLElement by dispatching a synchronous ContextRequestEvent that bubbles up the DOM tree: route-cache fast path → ancestor provider → global singleton → UnknownServiceError. Detached nodes throw UnconnectedNodeError unless the token is already a cached global singleton. Async providers throw AsyncServiceNotReadyError unless already cached; use injectAsync().

export function inject<K extends keyof ServiceRegistry>(
token: K
): (target: ISession<any, any> | HTMLElement) => ServiceRegistry[K]
export function inject<T>(
token: ServiceToken<T>
): (target: ISession<any, any> | HTMLElement) => T
export function inject<T = unknown>(
token: string
): (target: ISession<any, any> | HTMLElement) => T

Defined in: src/dom/inject.ts

token

Service identifier (registry key, branded token, or string).

When the target is detached and uncached.

When no provider answers.

When the provider is async and uncached.

When factories form a cycle.

import { inject } from '@sandlada/document-context'
const useLogger = inject('logger')
const logger = useLogger(session)
const nested = useLogger(document.getElementById('child')!)

Curried synchronous injection over the ServiceRegistry string-token map.

Typed overload: token must be a key of the globally augmented ServiceRegistry, and the resolved value is ServiceRegistry[K]. Use this overload for application services declared via declare module augmentation.

declare module '@sandlada/document-context' {
interface ServiceRegistry { 'auth-service': AuthService }
}
const auth = inject('auth-service')(document.getElementById('login')!)

Curried synchronous injection over a branded ServiceToken.

Typed overload: the resolved value is the token’s phantom type T, inferred from ServiceToken<T>. Prefer over strings for cross-bundle services where name collisions are a risk.

const logger = inject(LoggerToken)(session)

Curried synchronous injection over an untyped string token.

Fallback overload for ad-hoc or unregistered keys; the caller supplies T explicitly. For typed lookups, augment ServiceRegistry or use a branded ServiceToken instead.

const theme = inject<'light' | 'dark'>('theme-mode')(childEl)

Curried synchronous multi-provider accumulation verb.

Unlike inject() (first match wins via stopPropagation()), this dispatches with multi: true so every ancestor responder appends its instance: the local session registration first, then each bubbling host in turn. Each provider resolves with its own lifecycle semantics via resolveServiceInstance(). Returns a frozen-order plain array (empty when nothing provides the token; never throws UnknownServiceError).

export function injectAll<K extends keyof ServiceRegistry>(
token: K,
options?: IInjectAllOptions
): (target: ISession<any, any> | HTMLElement) => ReadonlyArray<ServiceRegistry[K]>
export function injectAll<T = unknown>(
token: string | ServiceToken<T>,
options?: IInjectAllOptions
): (target: ISession<any, any> | HTMLElement) => ReadonlyArray<T>

Defined in: src/dom/inject-all.ts

token

Service identifier.

options

Optional { direction }; 'topDown' reverses the nearest-first collection order.

import { injectAll } from '@sandlada/document-context'
const useMiddlewares = injectAll('middleware', { direction: 'topDown' })
const chain = useMiddlewares(document.getElementById('leaf')!)

Curried sync multi-provider accumulation over the ServiceRegistry map.

Typed overload: collects ReadonlyArray<ServiceRegistry[K]> from the local session plus every bubbling ancestor responder.

const loggers = injectAll('logger')(childEl)

Curried sync multi-provider accumulation over a string or branded token.

Generic overload for untyped keys and ServiceToken<T> values.

const middlewares = injectAll('middleware', { direction: 'topDown' })(session)

Curried asynchronous multi-provider accumulation verb.

Same bubbling multi: true collection as injectAll(), except each collected value may be a promise (async factories are invoked, not awaited at collection time) and the result is Promise.all()-resolved before returning. A single rejection rejects the whole array. Ordering follows options.direction ('bottomUp' default, 'topDown' reversed).

export function injectAllAsync<K extends keyof ServiceRegistry>(
token: K,
options?: IInjectAllAsyncOptions
): (target: ISession<any, any> | HTMLElement) => Promise<ReadonlyArray<Awaited<ServiceRegistry[K]>>>
export function injectAllAsync<T = unknown>(
token: string | ServiceToken<T>,
options?: IInjectAllAsyncOptions
): (target: ISession<any, any> | HTMLElement) => Promise<ReadonlyArray<T>>

Defined in: src/dom/inject-all.ts

token

Service identifier.

options

Optional { direction, signal }.

import { injectAllAsync } from '@sandlada/document-context'
const plugins = await injectAllAsync('plugin')(document.getElementById('leaf')!)

Curried async multi-provider accumulation over the ServiceRegistry map.

Typed overload: resolves to Promise<ReadonlyArray<Awaited<ServiceRegistry[K]>>>.

const configs = await injectAllAsync('remote-config')(childEl)

Curried async multi-provider accumulation over a string or branded token.

Generic overload for untyped keys and ServiceToken<T> values.

const svcs = await injectAllAsync('plugin', { direction: 'topDown' })(session)

Curried asynchronous injection verb with promise coalescing, self-healing eviction, and multi-caller abort isolation.

Accepts an ISession (local providers first) or a connected HTMLElement (nearest mounted ancestor via parentElement walk, then global singletons). Cached values ('singleton' globals, 'scoped' session entries) return immediately. Otherwise the factory runs once per token: concurrent callers share the same in-flight promise, rejections evict the entry instantly so the next call retries, and an already-aborted caller signal rejects with AbortError without cancelling the shared attempt. Unknown tokens throw UnknownServiceError; async cycles throw CircularDependencyError; detached nodes throw UnconnectedNodeError unless globally cached.

export function injectAsync<K extends keyof ServiceRegistry>(
token: K,
options?: IInjectAsyncOptions
): (target: ISession<any, any> | HTMLElement) => Promise<Awaited<ServiceRegistry[K]>>
export function injectAsync<T>(
token: ServiceToken<T>,
options?: IInjectAsyncOptions
): (target: ISession<any, any> | HTMLElement) => Promise<T>
export function injectAsync<T = unknown>(
token: string,
options?: IInjectAsyncOptions
): (target: ISession<any, any> | HTMLElement) => Promise<T>

Defined in: src/dom/inject-async.ts

token

Service identifier (registry key, branded token, or string).

options

Optional { signal } abort options.

When no provider or global entry exists.

When the element target is detached.

When async factories form a cycle.

import { injectAsync } from '@sandlada/document-context'
const useConfig = injectAsync('remote-config')
const config = await useConfig(session)
const [a, b] = await Promise.all([useConfig(el), useConfig(el)])

Curried async injection over the ServiceRegistry string-token map.

Typed overload: token must be a key of the augmented ServiceRegistry and the promise resolves to Awaited<ServiceRegistry[K]>.

const config = await injectAsync('remote-config')(document.getElementById('app')!)

Curried async injection over a branded ServiceToken.

Typed overload: resolves to the token’s phantom type T, unwrapping provider-level Promise nesting via await semantics.

const svc = await injectAsync(ConfigToken)(session)

Curried async injection over an untyped string token.

Fallback overload; the caller supplies T explicitly.

const ctl = new AbortController()
const svc = await injectAsync('remote-config', { signal: ctl.signal })(session)

Instantiates (or returns the cached) service for one registration record.

Internal resolution primitive used by inject, injectAll, and the context-request responder. Applies the lifecycle policy: 'singleton' consults and populates the global registry, 'scoped' consults and populates the session internals, 'transient' always invokes the factory. Guards re-entrant factories with the shared resolutionStack and throws CircularDependencyError on cycles. Async registrations never resolve here: a cached async value is returned when present, otherwise AsyncServiceNotReadyError is thrown and callers must use injectAsync().

export function resolveServiceInstance<T = unknown>(
registration: IServiceRegistration<T>,
session: ISession<any, any>
): T;

Defined in: src/dom/inject.ts

When the registration is async and uncached.

When the factory re-enters its own token.

import { resolveServiceInstance } from '@sandlada/document-context'
const reg = session.blueprint.providers.get('logger')!
const logger = resolveServiceInstance(reg, session)

Seeds or overwrites a 'singleton' instance in the page-wide global registry.

Singletons created via factories are cached here automatically; call this directly to pre-seed test doubles, framework singletons, or values that must resolve even on detached nodes.

export function setGlobalSingleton<T = unknown>(
token: string | ServiceToken<T>,
instance: T
): void;

Defined in: src/dom/inject.ts

import { setGlobalSingleton } from '@sandlada/document-context'
setGlobalSingleton('logger', new ConsoleLogger())

Attaches the W3C context-request responder to a mounted host element.

Internal mount-plugin helper (auto-registered via registerMountPlugin): listens for bubbling ContextRequestEvents from descendants, resolves the requested token against this session, and invokes event.detail.callback. Sync providers resolve via resolveServiceInstance(); async providers answer from cache or invoke the factory without awaiting (streaming state keys additionally subscribe to the session subject). Non-multi answers call stopPropagation() so the nearest provider wins; multi answers let the event keep bubbling for injectAll() accumulation.


export function setupProviderResponder(
element: HTMLElement,
session: ISession<any, any>
): () => void;

Defined in: src/dom/inject.ts

Starts (or reuses) the centralized document-level MutationObserver that drives automatic session GC.

One observer per Document, observing body ?? documentElement with { childList: true, subtree: true }. Removal batches are funneled into processDisconnections(), which runs inside queueMicrotask so a disconnect followed by a same-task reparent is treated as a move, not a destroy. Safe to call repeatedly; returns the cached observer.

export function startRootObserver(doc: Document = document): MutationObserver;

Defined in: src/dom/observer.ts

import { startRootObserver, stopRootObserver } from '@sandlada/document-context'
const observer = startRootObserver(document)
stopRootObserver(document)

Disconnects and drops the centralized observer for a document.

Tracked sessions are not disposed by this call; it only stops future removal detection (used when a session migrates documents via adoptSessionToDocument). Unknown documents are a safe no-op.

export function stopRootObserver(doc: Document = document): void;

Defined in: src/dom/observer.ts

import { stopRootObserver } from '@sandlada/document-context'
stopRootObserver(document)

Registers a host element and its session with the centralized GC tracker.

Auto-invoked by the DOM mount plugin on every mount(); also ensures the owner document’s root observer is running and registers a FinalizationRegistry fallback that disposes the session if the element is garbage-collected without a removal record. Entries hold both sides by WeakRef, so tracking itself never leaks.

GC policy on disconnect: hosts without data-context-key dispose deterministically on the next microtask; keyed hosts enter the suspended state (suspend hooks + context-suspend event with detail: { sessionKey, ttl: 50 }) and dispose only if still disconnected after the 50ms TTL.

export function trackElementForGC(
element: HTMLElement,
session: ISession<any, any>
): void;

Defined in: src/dom/observer.ts

import { trackElementForGC } from '@sandlada/document-context'
trackElementForGC(element, session)

Re-exports IContextBlueprint


Re-exports ILifecycleHookRegistration


Re-exports ILifecycleHooks


Re-exports ISession


Re-exports ServiceRegistry


Re-exports ServiceToken