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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@robinw151/resolver

v0.3.1

Published

A dependency-aware task resolver using RxJS observables for asynchronous execution

Readme

Resolver

GitHub Actions Workflow Status GitHub License

A dependency-aware task resolver using RxJS observables for asynchronous execution. This library allows you to define tasks with dependencies and automatically resolves them in the correct order.

Features

  • Dependency Resolution: Automatically determines the execution order based on task dependencies
  • RxJS Integration: Built on RxJS observables for powerful async handling
  • Type Safety: Full TypeScript support with type inference
  • Error Handling: Built-in error handling with graceful failure modes

Installation

npm install @robinw151/resolver
# or
pnpm add @robinw151/resolver
# or
yarn add @robinw151/resolver

Usage

Basic Example

import { lastValueFrom } from 'rxjs';
import { Resolver, isSuccess } from '@robinw151/resolver';

// Create a resolver instance
const resolver = new Resolver()
  // Register task A with no dependencies
  .register({
    id: 'A',
    fn: () => 'Hello',
  })
  // Register task B with no dependencies
  .register({
    id: 'B',
    fn: () => 'World',
  })
  // Register task C that depends on A and B
  .register(
    {
      id: 'C',
      fn: ({ A, B }) => {
        if (isSuccess(A) && isSuccess(B)) {
          return `${A.data} ${B.data}!`;
        }
        throw new Error('Missing dependencies');
      },
    },
    ['A', 'B'],
  );

// Resolve all tasks
const result = await lastValueFrom(resolver.resolve());
console.log(result); // { tasks: { A: { data: 'Hello' }, B: { data: 'World' }, C: { data: 'Hello World!' } } }

Advanced Example with Error Handling

import { lastValueFrom } from 'rxjs';
import { Resolver, isSuccess, isError, hasNoErrors } from '@robinw151/resolver';

const resolver = new Resolver()
  .register({
    id: 'fetchUser',
    fn: () => ({ id: 1, name: 'John' }),
  })
  .register(
    {
      id: 'fetchPosts',
      fn: ({ fetchUser }) => {
        if (isSuccess(fetchUser)) {
          return [
            { id: 1, title: 'Post 1' },
            { id: 2, title: 'Post 2' },
          ];
        }
        throw new Error('User not found');
      },
    },
    ['fetchUser'],
  )
  .register(
    {
      id: 'generateReport',
      fn: ({ fetchUser, fetchPosts }) => {
        if (isSuccess(fetchUser) && isSuccess(fetchPosts)) {
          return {
            user: fetchUser.data,
            postCount: fetchPosts.data.length,
            timestamp: new Date().toISOString(),
          };
        }
        throw new Error('Missing data for report');
      },
    },
    ['fetchUser', 'fetchPosts'],
  );

try {
  const result = await lastValueFrom(resolver.resolve());

  if (isError(result.tasks.fetchUser)) {
    console.error('User fetch failed:', result.tasks.fetchUser.error);
  }

  if (isError(result.tasks.fetchPosts)) {
    console.error('Posts fetch failed:', result.tasks.fetchPosts.error);
  }

  if (isError(result.tasks.generateReport)) {
    console.error('Report generation failed:', result.tasks.generateReport.error);
  }

  if (hasNoErrors(result.tasks)) {
    console.log('Report generated:', result.tasks.generateReport.data);
  }
} catch (error) {
  console.error('Resolution failed:', error);
}

Type Safety

The resolver provides full TypeScript support with type inference:

import { isSuccess, isError } from '@robinw151/resolver';

const resolver = new Resolver<{
  user: { id: number; name: string };
  posts: Array<{ id: number; title: string }>;
}>()
  .register({
    id: 'user',
    fn: () => ({ id: 1, name: 'John' }),
  })
  .register(
    {
      id: 'posts',
      fn: ({ user }) => {
        // user is typed as { data: { id: number; name: string } } | { error: unknown }
        if (isSuccess(user)) {
          console.log('User loaded:', user.data.name);
          return [{ id: 1, title: 'Post 1' }];
        } else {
          console.error('User failed to load:', user.error);
          return [];
        }
      },
    },
    ['user'],
  );

Error Handling

Tasks can return either successful data or errors. The resolver handles both cases gracefully:

  • Successful tasks return { data: TResult }
  • Failed tasks return { error: unknown }

Global Arguments

The resolver supports global arguments that are passed to all task functions during execution. This is useful for sharing configuration, API keys, or other context across all tasks.

Constructor Global Arguments

You can provide global arguments when creating a resolver instance:

import { lastValueFrom } from 'rxjs';
import { Resolver } from '@robinw151/resolver';

// Create resolver with global arguments
const resolver = new Resolver({ apiKey: 'your-api-key', baseUrl: 'https://api.example.com' })
  .register({
    id: 'fetchUser',
    fn: (_args, globalArgs) => {
      // globalArgs is typed as { apiKey: string; baseUrl: string }
      return fetch(`${globalArgs.baseUrl}/user`, {
        headers: { Authorization: `Bearer ${globalArgs.apiKey}` },
      });
    },
  })
  .register({
    id: 'fetchPosts',
    fn: (_args, globalArgs) => {
      return fetch(`${globalArgs.baseUrl}/posts`, {
        headers: { Authorization: `Bearer ${globalArgs.apiKey}` },
      });
    },
  });

const result = await lastValueFrom(resolver.resolve());
console.log(result.globalArgs); // { apiKey: 'your-api-key', baseUrl: 'https://api.example.com' }

Dynamic Global Arguments

You can update global arguments after creating the resolver using setGlobalArgs():

const resolver = new Resolver({ version: 'v1' }).register({
  id: 'getVersion',
  fn: (_args, globalArgs) => globalArgs.version,
});

// First resolution
const result1 = await lastValueFrom(resolver.resolve());
console.log(result1.globalArgs.version); // 'v1'

// Update global arguments
resolver.setGlobalArgs({ version: 'v2' });

// Second resolution with updated arguments
const result2 = await lastValueFrom(resolver.resolve());
console.log(result2.globalArgs.version); // 'v2'

Resolve Options

The resolve() method accepts an optional options parameter to control its behavior:

interface ResolveOptions {
  globalArgs?: TGlobalArgs;
  withLoadingState?: boolean;
}

globalArgs (optional)

Provides a temporary override for global arguments passed to all tasks during this specific resolution. This does not mutate the instance's globalArgs and only affects this resolution call.

  • Purpose: Allows different global arguments for specific resolutions without changing the resolver instance
  • Behavior: Overrides the instance's globalArgs for this resolution only
  • Type: Same type as the resolver's global arguments (TGlobalArgs)
const resolver = new Resolver({ apiKey: 'default-key', baseUrl: 'https://api.example.com' }).register({
  id: 'fetchData',
  fn: (_args, globalArgs) => {
    return fetch(`${globalArgs.baseUrl}/data`, {
      headers: { Authorization: `Bearer ${globalArgs.apiKey}` },
    });
  },
});

// Use temporary global args for this resolution
const result = await lastValueFrom(
  resolver.resolve({
    globalArgs: { apiKey: 'temp-key', baseUrl: 'https://temp.api.com' },
  }),
);

console.log(result.globalArgs); // { apiKey: 'temp-key', baseUrl: 'https://temp.api.com' }

// Next resolution uses the original instance globalArgs
const result2 = await lastValueFrom(resolver.resolve());
console.log(result2.globalArgs); // { apiKey: 'default-key', baseUrl: 'https://api.example.com' }

withLoadingState (default: false)

Controls whether the resolver emits a loading state as the first value in the observable stream.

  • false (default): Only emits the final result without the loading state
  • true: Emits { loading: true } as the first value, followed by the final result
import { isLoading } from '@robinw151/resolver';

// Without loading state (default behavior)
resolver.resolve().subscribe((result) => {
  console.log('Final result:', result);
});

// With loading state
resolver.resolve({ withLoadingState: true }).subscribe((result) => {
  if (isLoading(result)) {
    console.log('Resolution in progress...');
  } else {
    console.log('Final result:', result);
  }
});

Utility Functions

The resolver provides several utility functions to help you work with task results and resolver states:

isSuccess(value)

Type guard that checks if a task result contains successful data.

import { isSuccess } from '@robinw151/resolver';

if (isSuccess(taskResult)) {
  // taskResult is typed as { data: TValue }
  console.log(taskResult.data);
}

isError(value)

Type guard that checks if a task result contains an error.

import { isError } from '@robinw151/resolver';

if (isError(taskResult)) {
  // taskResult is typed as { error: unknown }
  console.error(taskResult.error);
}

isLoading(value)

Type guard that checks if a resolver result is in a loading state.

import { isLoading } from '@robinw151/resolver';

if (isLoading(resolverResult)) {
  console.log('Resolution is still in progress...');
}

hasNoErrors(result)

Type guard that checks if all task results in a resolver result contain successful data (no errors). This function performs a runtime check to determine if every task in the result object has completed successfully.

import { hasNoErrors } from '@robinw151/resolver';

const result = await lastValueFrom(resolver.resolve());

if (hasNoErrors(result.tasks)) {
  // All tasks succeeded - safe to access data
  console.log('User:', result.tasks.user.data.name);
  console.log('Posts count:', result.tasks.posts.data.length);
} else {
  // Some tasks failed - handle errors appropriately
  console.log('Some tasks failed during resolution');
}

License

MIT