@alterior/common
TypeScript icon, indicating that this package has built-in type declarations

3.8.0 • Public • Published

@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?

Versions

Current Tags

  • Version
    Downloads (Last 7 Days)
    • Tag
  • 3.8.0
    94
    • latest

Version History

Package Sidebar

Install

npm i @alterior/common

Weekly Downloads

254

Version

3.8.0

License

MIT

Unpacked Size

695 kB

Total Files

277

Last publish

Collaborators

  • rezonant