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

@35up/tslib-utils

v4.1.5

Published

Suite of utils for javascript applications

Downloads

44

Readme

tslib-utils

Suite of utils for javascript applications

Operation and AsyncOperation

Operation and AsyncOperation are utility classes that helps to gracefully handle situations when we need to perform some operation and handle its status, including error. Another of their purposes is to make the handling of errors more explicit and harder to forget.

Operation has 3 variants:

  • Pending Operations: They have a pending status while we get a response.
  • Successful Operations: They have a success status when the request succeeds.
  • Failed Operations: They have a fail status when the request fails.

There is also the concept of a ResolvedOperation, which is an operation that can only be "Successful" or "Failed".

Operation is the base monad, containing the status, the data in case of success and the error in case of failure.

AsyncOperation is a Promise-like wrapper on top of Operation, made to simplify the use of operation in async code, as well as to catch some common edge-cases related. In instance of an AsyncOperation is still a Promise, that resolves into an ResolvedOperation, and therefore, to access the data, error or status, it needs to be treated as a Promise.

Here an example on its intended use:

// service.ts
import { AsyncOperation } from '@35up/tslib-utils';

function expensiveAsyncQuery(...args): AsyncOperation<QueryResult> {
  return AsyncOperation.try(async () => {
    // Do some stuff
  });
}

// component.tsx
import { Operation } from '@35up/tslib-utils';
import { expensiveAsyncQuery } from './service';
import { Error } from './utilities';


export function Component({ args }) {
  const [ queryResult, setQueryResult ] = useState(Operation.makePending());

  useEffect(async () => {
    setQueryResult(
      await expensiveAsyncQuery(...args)
        .mapFailure('Failed to performe expencive operation')
    );
  }, [args]);
  
  return (
    <div>
      ...
      {Operation.isSuccess(queryResult) && (
        <div>The result was: {queryResult.data}</div>
      )}
      ...
      {Operation.isFailure(queryResult) && (
        <Error>{queryResult.error.message}</Error>
      )}
    </div>
  );
}

Most important features

try [Operation and AsyncOperation]

Wraps a callback in a try catch so that it can be safely executed. When callback executes without errors, then it will return a "Successful Operation" with the data field containing the returned value of the provided callback (or resolved value in the case of AsyncOperation). When the callback generate an exception (or rejection in the case of AsyncOperation), it will return a "Failed Operation" with an error field containing the Error.

  const result = AsyncOperation.try(async () => fetch('/url'));
  
  if (Operation.isFailure(result)) {
    console.log(result.data); // fetched data
  } else {
    console.error(result.error);
  }
Utilities [only AsyncOperation]

Try offer convenience methods to reduce boilerplate. These are provided as the first argument of the callback.

  AsyncOperation.try(async (utilities) => {
    // Some complicated logic
  });
unwrapData

It allows you to access the data contained in an ResolvedOperation. When the ResolvedOperation is a "Failure", it throws; But that is not an issue, as try will catch it, and result in a "Failed Operation".

When provided with a promise of an ResolvedOperation or an AsyncOperation, it will return a promise that resolves in the data of the ResolvedOperation.

AsyncOperation.try(async ({ unwrap }) => {
  const a = await unwrapData(getA());
  const b = unwrapData(await getB());
  
  return a / b;
});

vs

  AsyncOperation.try(async () => {
    const a = await getA();
    const b = await getB();
  
    if (Operation.isFailure(a)) {
       throw a.error;
    }
    
    if (Operation.isFailure(b)) {
      throw b.error;
    }

    return a.data / b.data;
  });

wrap [AsyncOperation]

Wrap creates an AsyncOperation from an Operation or a promise that resolves in an Operation. Basically when operation is already finished we can create an instance from the final result.

import { Operation } from './operation';
import { AsyncOperation } from './async-operation';

const operation = await AsyncOperation.wrap(Operation.makeSuccess(7));
console.log(result.data); // 7
console.log(Operation.isSuccess(result)); // true

// or

const promise = Promise.resolve(Operation.makeSuccess(7));
const asyncOperation = AsyncOperation.wrap(promise);
console.log(result.data); // 7
console.log(Operation.isSuccess(result)); // true

aggregate [Operation and AsyncOperation]

Converts an array of Operations (and AsyncOperations in the case of AsyncOperation.wrap) into a single operation containing an array of all the results. In case of one of them failing, it returns "Failed Operation" containing the Error. In case that multiple fail, it will return a "Failed Operation" with an AggregateError containing all the underlying errors. This is intended to be used similarly to Promise.all

  const results = await AsyncOperation.aggregate([
    Promise.resolve(Operation.makeSuccess('a')),
    Promise.resolve(Operation.makeSuccess(1)),
  ]);
  
  console.log(results); // ['a', 1]

Checking the status

Use static methods of Operation class to check the status. AsyncOperations don't have status out of them selves, but as they are fancy promises, they resolve into an Operation.

Example of checking the status on an Operation
  import { Operation } from './operation';
  
  const operation = Operation.makePending();
  
  console.assert(Operation.isPending(operation) === true);
  console.assert(Operation.isSuccess(operation) === false);
  console.assert(Operation.isFailure(operation) === false);
Example of checking the status on an AsyncOperation
  const operation = Operation.makePending();
  operation = await AsyncOperation.try(async () => fetch('/url'));

  console.assert(Operation.isPending(operation) === false);
  console.assert(Operation.isSuccess(operation) === true);
  console.assert(Operation.isFailure(operation) === false);

Create Operation with specific status

There are static methods to create Operations and AsyncOperations in all its variants.

For Operations
  const emptySuccess = Operation.makeSuccess('test');
  const success = Operation.makeSuccess('test');
  const failure = Operation.makeFailure(new Error('fail'));
  const panding = Operation.makePending();
For AsyncOperations
  const emptyAsyncSuccess = AsyncOperation.makeSuccess();
  const asyncSuccess = AsyncOperation.makeSuccess('test');
  const asyncFailure = AsyncOperation.makeFailure(new Error('fail'));

Note: there is no makePending for AsyncOperation because it does not make sense to have a promise return a "Pending Operation" in the majority of valid use cases. We decided to forbid that in our types, to avoid common bugs.

Chaining executions

Often times we need to use the result of async operation in another operation. There is a way to compose operations in a monad-like way. For that you can create a chain of operations using mapSuccess to plug into successful result or mapFailure to plug to a failed result.

  AsyncOperation.try(async () => fetch('/settings'))
    .mapSuccess((settings) => fetch(`/products?lang=${settings.language}`))
    .mapSuccess((products) => orders.filter(p => p.isStock))
    .mapFailure('Failed to get data');

Note: mapFailure is not necessary to handle errors. It is just a way to specify a bit more concrete error message in case of failure. The original error in this case would be just added as a cause of the returned error object.

Handling Errors

To access the data of an Operation, we have to first check its status. This forces us to be more mindful of its error state. This is an example on how we would expect errors being handled:

  const result = await someExepensiveAsyncOperation()
    .mapFailure('Could not perform the expensive operation correctly');

  if (Operation.isFailure(result)) {
    // do something to recover or bail out
  } else {
    // do someting with the data
  }

  

wrapHttpAsOperation

Most of the services that deal with backend have the same structure:

  1. Make fetch to the server
  2. Validate response against schema
  3. Handle errors

On order to reduce the boilerplate there is a util function wrapHttpAsOperation that does all of the above. You just need to pass a url, validation schema and parameters.

Without wrapHttpAsOperation:

  try {
    const endpoint = '/price-overrides/';
    const result: TServerPriceOverrides = schema.parse(
      await post(endpoint, data),
      {path: [endpoint]},
    );

    return makeSuccess(result || null);
  } catch (e) {
    return makeFail(e as Error);
  }

With wrapHttpAsOperation:

  const wrapperPost = wrapHttpAsOperation(post);
  return wrappedPost('/price-overrides/', schema, data);