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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@alterior/common

v3.9.1

Published

Useful utilities for Typescript apps

Downloads

248

Readme

@alterior/common

Version

Provides a number of base classes, utilities, and errors which are useful in constructing larger applications.

class Base64

Simple, lightweight, portable base64 encoding and decoding. Good isomorphic alternative to atob() or Node.js' Buffer class.

Base64.encode('hello world') // => "aGVsbG8gd29ybGQ="
Base64.decode('aGVsbG8gd29ybGQ=') // => "hello world"

class Cache<T>

Provides a simple in-memory cache with ergonomic APIs for fetching. Handles expiration and eviction.

let cache = new Cache<number>(/* timeToLive */ 1000, /* maxItems */ 100);
cache.fetch('some key', async () => await someExpensiveMethod());

clone()

Use clone() to create a serialized clone of the given object. This is implemented as passing to JSON and back.

coalesce()

Select the first defined value within the given parameters.

let a = undefined;
let b = 123;
let c = 321;

coalesce(a, b, c) // => 123

class ConsoleColors

Wrap strings in standard terminal ANSI colors.

console.log(ConsoleColors.green('All good!')) // => a green 'All good!' message on the screen

interceptConsole()

Augment, replace, or mute the functionality of the builtin Javascript console only for the code you are executing using the power of Zone.js. Code outside of your callback (even when it interrupts your callback due to an asynchronous call) are unaffected by the interception.

interceptConsole((method, original, console, args) => {
    original(`${args.join(' ')} world!`);
}, () => {
    console.log(`Hello`);
}) 

// => prints "Hello world!"

indentConsole()

A convenience function built from interceptConsole() that simply indents all console output by the given number of spaces.

indentConsole(4, () => {
    console.log("Hello\nWorld");
})

// => prints "    Hello"
// => prints "    World"

class Environment

Provides an injectable service which provides environment variables to your application. Will read from dotenv files as well.

Errors

This package provides many fundamental error types that are intended for you to use to simplify development. The rest of Alterior also relies upon these.

class SystemError

Base class for errors thrown by the system or framework

class ApplicationError

Base class for errors thrown by your application

class ArgumentError<ValueT>

An argument to a method or function was invalid. The argument name can be obtained with argumentName. THe invalid value can be obtained with value

class ArgumentNullError

The given argument was null and this is not allowed in this context.

class ArgumentOutOfRangeError

The given argument is out of the expected range.

class NotSupportedError

The current system, framework version, application, or configuration does not support the attempted operation.

class NotImplementedError

The attempted operation is not implemented in this context.

class OperationCanceledError

The operation was cancelled.

class TimeoutError

The operation was interrupted due to a timeout.

class IOError

An error occurred while performing an IO operation

class FormatError

An error occurred while formatting a provided value

class InvalidOperationError

The attempted operation is invalid in the current state

class AccessDeniedError

The attempted operation cannot be called given the current authorization and authentication state.

HttpError

An HTTP operation failed with a non-successful response.

class LazyPromise<T>

Create a promise which only starts doing work if an observer calls .then() or .catch().

let promise = new LazyPromise<T>(async () => {
    console.log("Called (once) only if an observer is interested in the result of the promise!");
    return 123;
});

class Lock

Provides an asynchronous lock which can be used to ensure non-overlapping execution. All calls to run() are enqueued and then executed in order as the previous executions complete.

let lock = new Lock();
lock.run(async () => {
    await timeout(1000);
    console.log("Hello");
});
lock.run(async () => {
    console.log("World!");
});

// => prints "Hello"
// => prints "World!"

If you need to synchronize code executions where sharing a Lock instance is inconvenient, it is also possible to define a lock by specifying a "token" value. All locks which share the same token value will execute without overlap.

let lock = Lock.forToken(123);
lock.run(() => {
    await timeout(1000);
    console.log('Hello')
});
let lock2 = Lock.forToken(123);
lock2.run(() => {
    console.log('World!')
});

// => prints "Hello"
// => prints "World!"

class ZoneLock

An enhanced version of Lock which will run target functions in AsyncZones and ensure that all async operations are fully complete before allowing the next execution to happen (even if that async operation is not tracked by a promise, or the promise is not curried via a return)

let lock = new ZoneLock();
lock.run(() => {
    setTimeout(() => console.log("Hello"), 1000);
});
lock.run(() => {
    console.log("World!");
});

// => prints "Hello"
// => prints "World!"

ZoneLock also supports token-based locks with ZoneLock.forToken() (see Lock.forToken() for more information).

prepareForSerialization(data : T): T

Prepares objects and data for serialization across the wire. Collapses undefined into null, calls toJSON() if defined, filters all functions out of the object, and recursively calls prepareForSerialization() on all child values.

prepareForSerialization({
    foo: 1,
    thisExists: undefined,
    increment() {
        this.foo += 1;
    },
    bar: {
        baz: "zzz",
        sayHi() {
            console.log('Hi!');
        }
    }
})

// => yields
// {
//   foo: 1,
//   thisExists: null,
//   bar: {
//     baz: 'zzz'
//   }
// }

timeout()

Use this to get a promise for a timeout interval.

await timeout(1000); // => waits 1000ms

class AsyncZone

A simpler way to use Zone.js to monitor the execution of an async operation, even without promises. The easiest way to use it is to call AsyncZone.run():

await AsyncZone.run(() => setTimeout(() => console.log('Hello'), 1000));
console.log('World!');

// => prints "Hello"
// => prints "World"

You can also instantiate AsyncZone directly and subscribe to the onStable and/or onError observables:

let zone = new AsyncZone();
zone.onStable.subscribe(() => console.log('Code has finished executing!'))
zone.onError.subscribe(err => console.log(`Error thrown while executing code: ${err}`));
zone.invoke(() => myCode());

class Presentation<T>

Allows for transforming data from one form to another using declarative rules. To use, you must declare a subclass which has one or more properties annotated with the @Expose() decorator. When the presentation is converted to JSON, the property values from the instance given during instantiation will be used within the JSON. If any property declaration within the Presentation<T> subclass has a property type annotation for a type that extends Presentation<T>, a new instance of that type will be constructed and passed the property value from the given instance. This allows you to control the presentation of subobjects.

For example:

export class ApiUser extends Presentation<User> {
     @Expose() username : string;
     @Expose() profile : ApiProfile;
     // other properties of `User`, such as `hashedPassword`, will be
     // omitted in the final JSON.
}

export class ApiProfile extends Presentation<Profile> {
     @Expose() displayName : string;
     @Expose() firstName : string;
     // ...
}

To define a "virtual" value or override the value of the underlying instance, use a standard getter (get()):

export class ExampleOfVirtualProperties extends Presentation<Profile> {
     @Expose() get myProperty() {
         return 'look ma, virtual!'
     }
}

You can "augment" a value as well by accessing the underlying property with this.instance:

export class ExampleOfTransformedProperties extends Presentation<Profile> {
     @Expose() get myProperty() {
         return `The underlying value was: ${this.instance.myProperty}`;
     }
}

You may wish to pass additional objects into the presentation so they can be used to compose the result. Simply override the base constructor to do so:

export class ApiMedia extends Presentation<MediaSnippet> {
     constructor(
         instance : MediaSnippet, 
         readonly details : MediaDetails
     ) {
         super(instance)
     }

     @Expose() get assetUrl() {
         return this.details.assetUrl;
     }
}

If you need to use such a presentation within another presentation, you would need to specify the value to use. A simple example might look like:

export class ApiArtist extends Presentation<Artist> {
     constructor(
         instance : Artist, 
         readonly featuredMediaSnippet : MediaSnippet,
         readonly featuredMediaDetails : MediaDetails
     ) {
     }

     @Expose() get featuredMedia() : ApiMedia {
         return new ApiMedia(this.featuredMediaSnippet, this.featuredMediaDetails);
     }
}

You can also expose a property using the value of a different property on the underlying instance:

export interface Movie {
     theTitle : string;
     // ...
}

export class ApiMovie extends Presentation<Movie> {
     @Expose({ useProperty: 'theTitle' }) title : string;
}

You can also specify a default value. It will be used when the value provided by the instance is null or undefined.

export class ApiMovie extends Presentation<Movie> {
     @Expose({ defaultValue: '(Not available)' }) audienceRating : string;
}

lazySubject({ start:..., stop:... })

Returns an observable which causes the provided start function to be called when the observable transitions from having no subscribers to having subscribers, and calls the provided stop function when the last subscriber unsubscribes.

This can be useful to implement lazy loading and other "connect on demand" strategies. If no one is around to subscribe to a tree observable in the forest, does anyone hear it fall?