All files / src/internal/context sync-local-storage.mts

54.54% Statements 6/11
50% Branches 1/2
66.66% Functions 2/3
54.54% Lines 6/11

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54                                  1x             39x 39x 39x   39x               55x                                  
import type { IAsyncLocalStorage } from './async-local-storage.types.mjs'
 
/**
 * A synchronous-only polyfill for AsyncLocalStorage.
 *
 * This provides the same API as Node's AsyncLocalStorage but only works
 * for synchronous code paths. It uses a simple stack-based approach.
 *
 * Limitations:
 * - Context does NOT propagate across async boundaries (setTimeout, promises, etc.)
 * - Only suitable for environments where DI resolution is synchronous
 *
 * This is acceptable for browser environments where:
 * 1. Constructors are typically synchronous
 * 2. Circular dependency detection mainly needs sync tracking
 */
export class SyncLocalStorage<T> implements IAsyncLocalStorage<T> {
  private stack: T[] = []
 
  /**
   * Runs a function within the given store context.
   * The context is only available synchronously within the function.
   */
  run<R>(store: T, fn: () => R): R {
    this.stack.push(store)
    try {
      return fn()
    } finally {
      this.stack.pop()
    }
  }
 
  /**
   * Gets the current store value, or undefined if not in a context.
   */
  getStore(): T | undefined {
    return this.stack.length > 0 ? this.stack[this.stack.length - 1] : undefined
  }
 
  /**
   * Exits the current context and runs the function without any store.
   * This matches AsyncLocalStorage.exit() behavior.
   */
  exit<R>(fn: () => R): R {
    const savedStack = this.stack
    this.stack = []
    try {
      return fn()
    } finally {
      this.stack = savedStack
    }
  }
}