npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@innobridge/memoizedsingleton

v0.0.5

Published

A TypeScript library for Dependency Injection

Downloads

7

Readme

Memoized Singleton Decorators

@innobridge/memoirizedsingleton is a lightweight dependency injection framework that caches decorated components and injects them by retrieving the cached instance. You define a component with a decorator, instantiate it once, and every subsequent resolution fetches the cached instance automatically.

Table of Contents

  1. Overview
  2. Decorators
  3. Qualifier Model
  4. Nested Components
  5. Lifecycle APIs
  6. Architecture
  7. Tests

Overview

The framework is purpose-built for modern TypeScript projects that want simple dependency wiring without metadata emit requirements. Decorators wrap a class so it automatically registers itself with the application context on first construction and reuses the stored instance on subsequent resolutions.

Decorators

@Singleton

Creates one application-wide instance per qualifier. The default qualifier (@Singleton) stores the instance in an in-memory map.

import { Singleton } from '@/scopes/scopes';

@Singleton // default qualifier
class DefaultCache {}

@Singleton('secondary')
class SecondaryCache {}

const cacheA = new DefaultCache();
const cacheB = new DefaultCache();
const secondary = new SecondaryCache();

console.log(cacheA === cacheB);    // true
console.log(cacheA === secondary); // false

@Prototype

Returns a brand-new instance every time you construct the decorated class while still letting the container inject collaborators and apply lifecycle hooks. Prototype instances are never cached in the application context, so each resolution is isolated.

Use @Prototype when you need container-managed wiring on short-lived objects:

  • The instance holds per-invocation state (for example, a job processor or calculation helper).
  • You want to keep using @Insert/qualifiers/AOP on that object without manually passing every dependency into its constructor.
  • You still want access to the shared Component helpers (getScope(), replace, etc.) for debugging or testing.

If the class has no dependencies and no container features, a plain new without @Prototype is simpler.

A common pattern is to instantiate a prototype inside a longer-lived component while still letting the container satisfy the prototype's dependencies:

import { Prototype, Singleton } from '@/scopes/scopes';
import { Insert } from '@/building-blocks/assembler';

type JobPayload = { id: string };

@Singleton
class Logger {
	log(message: string) {
		console.log(`[logger] ${message}`);
	}
}

@Prototype
class TransientJob {
	@Insert(Logger)
	private logger!: Logger;

	handle(payload: JobPayload) {
		this.logger.log(`Processing ${payload.id}`);
		// ... stateful work lives only for this invocation
	}
}

@Singleton
class JobRunner {
	run(payload: JobPayload) {
		const job = new TransientJob(); // fresh instance every run
		job.handle(payload);
	}
}

Here TransientJob receives its collaborators via @Insert just like a singleton would, yet every JobRunner.run call gets an isolated TransientJob instance with its own temporary state.

@Request

Creates one instance per qualifier per asynchronous request by storing the instance in AsyncLocalStorage.

import { Request } from '@/scopes/scopes';
import { initializeRequestContext } from '@/application-context/application_context';

@Request
class UserContext {
	constructor(public userId?: string) {}
}

@Request('admin')
class AdminContext {}

initializeRequestContext(() => {
	const defaultCtx = new UserContext('alice');
	const adminCtx = new AdminContext();

	console.log(defaultCtx === adminCtx); // false — different qualifiers
});

@Insert

Field decorator that resolves dependencies from the cache when an owning class is constructed.

class Service {
	@Insert(Logger)
	logger!: Logger; // throws if missing

	@Insert(Logger, true)
	optionalLogger?: Logger; // remains undefined if Logger is absent

	@Insert(DatabaseConnection, false, 'primary')
	primaryDb!: DatabaseConnection;
}

Signature:

@Insert(DependencyCtor, optional?: boolean, qualifier?: string)

Qualifier Model

Qualifiers map component constructors to a secondary key so multiple logical instances can coexist. The storage shape is:

Map<Constructor, Map<string, Component>>

Retrieve qualified instances explicitly with getApplicationContext:

import { getApplicationContext } from '@/application-context/application_context';

const defaultLogger = getApplicationContext(Logger);              // default qualifier
const secondaryLogger = getApplicationContext(Logger, 'secondary');

Prototype-scoped components are not cached in the container, so there is no dedicated cache for them, so getApplicationContext(TransientWorker) returns undefined.

Nested Components

Components can depend on other components through @Insert, regardless of scope. For example, a singleton can inject another singleton and a request scoped component can inject a qualified service.

@Singleton
class Logger {}

@Singleton('primary')
class DatabaseConnection {
	constructor(public url: string) {}
}

@Singleton
class Repository {
	@Insert(Logger)
	private logger!: Logger;

	@Insert(DatabaseConnection, false, 'primary')
	private db!: DatabaseConnection;
}

@Request
class RequestMetadata {
	@Insert(Repository)
	repo!: Repository;
}

The injected instances respect their own scope. A request-scoped component receives the same singleton instance as every other consumer, while maintaining isolation for other request-scoped collaborators.

Lifecycle APIs

Every decorated instance inherits lifecycle utilities from Component:

| Method | Description | | ------ | ----------- | | getScope() | Returns SINGLETON, PROTOTYPE, or REQUEST, useful for debugging and assertions. | | stop(qualifier?) | Removes the current instance from the cache. The optional qualifier lets you target a specific entry. | | replace(newInstance, qualifier?) | Swaps the cached instance with newInstance (optionally for a qualifier) and returns it. Handy for hot swapping in tests. |

Example:

const cache = new DefaultCache() as DefaultCache & Component;
cache.stop();                      // eviction

const altCache = cache.replace(new DefaultCache());
console.log(altCache === cache);   // false — new instance stored

Architecture

Storage Model

The framework maintains separate caches for singleton and request-scoped components using a nested map structure:

Prototype-scoped components bypass these caches entirely; they are constructed on demand and discarded when no longer referenced.

┌─────────────────────────────────────────┐
│   Application Context (Singleton)       │
├─────────────────────────────────────────┤
│ Map<Constructor, Map<Qualifier, Inst>>  │
│                                         │
│  Logger ──┬─→ "default" ──→ Logger#1   │
│           └─→ "audit"   ──→ Logger#2   │
│                                         │
│  Cache  ──┬─→ "default" ──→ Cache#1    │
│           └─→ "redis"   ──→ Cache#2    │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│   Request Context (AsyncLocalStorage)   │
├─────────────────────────────────────────┤
│ Map<Constructor, Map<Qualifier, Inst>>  │
│                                         │
│  UserCtx ──→ "default" ──→ UserCtx#A   │
│  AdminCtx ─→ "default" ──→ AdminCtx#A  │
└─────────────────────────────────────────┘

Resolution Flow

When you construct a decorated class, the decorator intercepts construction and performs cache-or-create logic:

flowchart TD
    Start([new MyClass]) --> Decorator{Decorator intercepts}
    Decorator --> CheckScope{Scope type?}
    
    CheckScope -->|Singleton| SingletonCache[(Singleton Cache<br/>Map→Map)]
    CheckScope -->|Request| RequestCache[(Request Cache<br/>AsyncLocalStorage)]
    
    SingletonCache --> LookupS{Instance exists<br/>for qualifier?}
    RequestCache --> LookupR{Instance exists<br/>for qualifier?}
    
    LookupS -->|Yes| ReturnS[Return cached instance]
    LookupS -->|No| ConstructS[Construct new instance]
    ConstructS --> StoreS[Store in singleton cache]
    StoreS --> ReturnS
    
    LookupR -->|Yes| ReturnR[Return cached instance]
    LookupR -->|No| ConstructR[Construct new instance]
    ConstructR --> StoreR[Store in request cache]
    StoreR --> ReturnR
    
    ReturnS --> End([Instance returned])
    ReturnR --> End
    
    style SingletonCache fill:#e1f5ff
    style RequestCache fill:#fff4e1
    style ConstructS fill:#d4edda
    style ConstructR fill:#d4edda

Resolution Flow

Prototype-scoped components are never cached, so there is no dedicated prototype cache. Each time a prototype dependency is injected, a new instance is created.

Request Scope Lifecycle

Request-scoped components live within an async boundary created by initializeRequestContext:

flowchart TD
    Start([Incoming Request]) --> InitCtx[initializeRequestContext called]
    InitCtx --> CreateALS[AsyncLocalStorage creates<br/>new async context]
    CreateALS --> EmptyCache[Initialize empty request cache<br/>Map&lt;Constructor, Map&lt;Qualifier, Instance&gt;&gt;]
    
    EmptyCache --> FirstCall[new UserContext#40;'alice'#41;]
    FirstCall --> CheckFirst{Cache has<br/>UserContext:default?}
    CheckFirst -->|No| Construct1[Construct new instance]
    Construct1 --> Store1[Store in cache:<br/>UserContext:default → instance]
    Store1 --> Return1[Return instance]
    
    Return1 --> SecondCall[new UserContext#40;#41;<br/>#40;2nd call, same request#41;]
    SecondCall --> CheckSecond{Cache has<br/>UserContext:default?}
    CheckSecond -->|Yes| Return2[Return cached instance]
    
    Return2 --> Handler[Execute request handler logic]
    Handler --> Complete[Request complete]
    Complete --> ExitALS[Exit async context]
    ExitALS --> ClearCache[Clear request cache]
    ClearCache --> End([Ready for next request])
    
    style EmptyCache fill:#fff4e1
    style Construct1 fill:#d4edda
    style Return2 fill:#d1ecf1
    style ClearCache fill:#f8d7da

Request Flow

Dependency Injection Flow

@Insert resolves dependencies eagerly during class construction:

flowchart LR
    A[Service constructed] --> B[@Insert decorator runs]
    B --> C{Check optional flag}
    
    C -->|Required| D[Get from cache]
    C -->|Optional| E[Try get from cache]
    
    D --> F{Found?}
    F -->|Yes| G[Inject instance]
    F -->|No| H[Throw error]
    
    E --> I{Found?}
    I -->|Yes| G
    I -->|No| J[Set undefined]
    
    G --> K[Property assigned]
    J --> K
    H --> L[Construction fails]
    
    style H fill:#f8d7da
    style G fill:#d4edda
    style J fill:#fff3cd

Lifecycle Flow

Tests

npm run test:integration --file=env.test.ts
npm run test:integration --file=expressrequestscope.test.ts
npm run test:integration --file=insert.test.ts
npm run test:integration --file=requestscope.test.ts
npm run test:integration --file=singletonscope.test.ts
npm run test:integration --file=prototypescope.test.ts