npm.io
1.2.0 • Published 7 months ago

fount

Licence
MIT
Version
1.2.0
Deps
3
Size
65 kB
Vulns
0
Weekly
0

Fount

Fount provides synchronous and asynchronous dependency injection with automatic deferred resolution.

Getting Started

npm i fount
import { fount } from 'fount';

Key Features

  • Deferred Resolution: When resolving dependencies that haven't been registered yet, fount will wait until they become available rather than throwing an error.
  • Multiple Containers: Organize dependencies into namespaced containers.
  • Scoped Resolution: Cache factory results per scope for memoization.
  • Flexible Registration: Register values, factories, or npm modules.

Dependency Identification Styles

Fount supports two styles of identifying dependencies:

// By argument name - each argument is looked up by name in fount's store
fount().inject((one, two, three) => {
  // your code here
});

// By string array - dependencies are looked up and passed in order
fount().inject(['one', 'two', 'three'], (one, two, three) => {
  // your code here
});

The string array approach is recommended for production code as minifiers will rename function arguments and break name-based resolution.

Multiple Containers

Fount supports multiple dependency containers identified by namespace. If you don't specify one, it uses the default namespace.

You can use a non-default namespace in two ways:

  • Pass the namespace in parenthesis before making the call
  • Use period-delimited namespaces in your key names
// Using the default namespace
const value = await fount().resolve('myKey');

// Using a named namespace
const value = await fount('myContainer').resolve('myKey');

// Using period-delimited keys to access other namespaces
const value = await fount().resolve('myContainer.myKey');

Scopes

Scopes work with factory dependencies to provide memoization. The first time a scoped factory is resolved within a scope, the result is cached. Subsequent resolutions within the same scope return the cached value.

// Register a scoped factory
fount().registerFactory('timestamp', () => Date.now(), 'scoped');

// First call computes the value
const t1 = await fount().resolve('timestamp', 'myScope');

// Second call returns cached value
const t2 = await fount().resolve('timestamp', 'myScope');

console.log(t1 === t2); // true
Purging Scopes
// Purge a specific scope from the default namespace
fount().purgeScope('myScope');

// Purge all scopes from the default namespace
fount().purgeScopes();

// Purge a specific scope from a named namespace
fount('example').purgeScope('myScope');

// Purge all scopes from a named namespace
fount('example').purgeScopes();

Registering Dependencies

Values

Register static values that fount returns as-is:

fount().register('port', 8080);
fount().register('config', { debug: true, maxRetries: 3 });
fount('custom').register('apiKey', 'secret-key');
Factories

Register functions that fount invokes during resolution:

// Simple factory
fount().registerFactory('timestamp', () => Date.now());

// Factory with dependencies
fount().registerFactory('connection', ['host', 'port'], (host, port) => {
  return createConnection(host, port);
});

// Dependencies inferred from argument names
fount().registerFactory('connection', (host, port) => {
  return createConnection(host, port);
});
Factory Lifecycles

Factories support three lifecycles:

  • factory (default): Invoked on every resolution
  • static: Invoked once, result is cached permanently
  • scoped: Invoked once per scope, result is cached per scope
// Factory lifecycle - new instance every time
fount().registerFactory('id', () => generateId(), 'factory');

// Static lifecycle - computed once, cached forever
fount().registerFactory('startTime', () => Date.now(), 'static');

// Scoped lifecycle - computed once per scope
fount().registerFactory('requestId', () => generateId(), 'scoped');
NPM Modules

Register npm modules directly:

fount().registerModule('lodash');

// Later, resolve it
const _ = await fount().resolve('lodash');

If the module exports a function that fount cannot resolve dependencies for, it provides the function as-is.

Asynchronous Methods

These methods return promises and support deferred resolution - if a dependency isn't registered yet, fount waits until it becomes available.

resolve

Resolve a single dependency:

const port = await fount().resolve('port');

// With scope
const value = await fount().resolve('config', 'myScope');
resolveAll

Resolve multiple dependencies at once:

const { host, port } = await fount().resolveAll(['host', 'port']);

// Across namespaces
const results = await fount().resolveAll(['db.host', 'cache.host']);
inject

Resolve dependencies and pass them to a function:

// With explicit dependency list
const result = await fount().inject(['host', 'port'], (host, port) => {
  return `${host}:${port}`;
});

// With inferred dependencies
const result = await fount().inject((host, port) => {
  return `${host}:${port}`;
});

// With scope
const result = await fount().inject(['config'], (config) => {
  return config.value;
}, 'myScope');
Deferred Resolution

A key feature of fount is that async methods wait for dependencies to be registered:

// Start resolving before the dependency exists
const valuePromise = fount().resolve('laterValue');

// Register the dependency later
setTimeout(() => {
  fount().register('laterValue', 42);
}, 1000);

// The promise resolves once the dependency is registered
const value = await valuePromise; // 42

This also works with dependency chains:

// Register factories that depend on unregistered values
fount().registerFactory('doubled', ['base'], (base) => base * 2);
fount().registerFactory('result', ['doubled'], (doubled) => doubled + 10);

// Start resolution
const resultPromise = fount().resolve('result');

// Register the base value later
setTimeout(() => {
  fount().register('base', 5);
}, 100);

const result = await resultPromise; // 20 (5 * 2 + 10)

Synchronous Methods

These methods return values immediately and assume no promises in the dependency chain.

Warning: If a promise is encountered in the dependency chain, synchronous methods will not resolve it properly.

get

Get a dependency value synchronously:

const port = fount().get('port');

// With scope
const value = fount().get('config', 'myScope');

// Multiple values
const { host, port } = fount().getAll(['host', 'port']);
invoke

Invoke a function with resolved dependencies synchronously:

const result = fount().invoke(['host', 'port'], (host, port) => {
  return `${host}:${port}`;
});

// With inferred dependencies
const result = fount().invoke((host, port) => {
  return `${host}:${port}`;
});

Checking Resolution

Use canResolve to check if dependencies can be resolved without waiting:

if (fount().canResolve('myKey')) {
  const value = fount().get('myKey');
}

// Check multiple keys
if (fount().canResolve(['key1', 'key2'])) {
  const values = fount().getAll(['key1', 'key2']);
}

Configuration

Configure multiple containers and values at once:

fount({
  default: {
    host: 'localhost',
    port: 8080
  },
  database: {
    host: 'db.example.com',
    port: 5432,
    pool: {
      factory: () => createPool()
    }
  },
  cache: {
    ttl: 3600,
    maxSize: {
      scoped: () => calculateMaxSize()
    }
  }
});

Values can be:

  • Plain values (registered as static)
  • Objects with factory key (registered as factory lifecycle)
  • Objects with scoped key (registered as scoped lifecycle)
  • Objects with static key (registered as static lifecycle)

Utility Methods

Key Lists
// All keys across all namespaces
const allKeys = fount.allKeys();

// Keys in a specific namespace
const keys = fount('myNamespace').keys();
Namespaces
const namespaces = fount.namespaces();
Purging
// Remove everything
fount.purgeAll();

// Remove all keys from the default namespace
fount().purge();

// Remove all keys from a specific namespace
fount('myNamespace').purge();
Diagnostics
// Log the current state of all containers
fount.log();

TypeScript Support

Fount is written in TypeScript and exports its types:

import { fount, Fount, ContainerApi } from 'fount';

// The main fount export
const f: Fount = fount;

// Container API returned when calling fount()
const container: ContainerApi = fount();

Testing

# Run tests once
npm test

# Run tests in watch mode
npm run test:watch

Keywords