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

ctxstore

v1.0.2

Published

A context decorator library designed for nodejs that synchronizes contexts within a call stack.

Downloads

3

Readme

ctxstore

About

ContextStore (ctxstore) is a context decorator library designed for nodejs that synchronizes contexts within a call stack.

Disclaimer: This module has not been tested for multithreading nor designed with multithreading in mind.

Installation

Installing with npm:

npm i ctxstore

Usage

@Context decorator

Use the @Context decorator to associate a context object with a scope:

import { Context } from 'ctxstore';

class NotificationService {
    private userDb: UserDatabase = new UserDatabase();
    private emailClient: EmailClient = new EmailClient();

    @Context({
        // Associate a context to a scope
        spanName: 'NotificationService:notifyUser(...)',
        endpoint: '/notify/user/{username}',
    })
    async notifyUser(username: string): Promise<object> {
        await this.userDb.getUser(username);
        await this.emailClient.publish(username);
        return Context.get(); // retrieve the context for this scope
    }
}

In this example, a context is being associated with the async notifyUser(username: string): Promise<object> method. This means that any function invoked in scope of notifyUser(...) { } will have access to the context(s) defined in @Context(...). You can use the Context.get method to retrieve the context for a function (e.g. notifyUser(...) { }) as long as at least one ancestor function in the same call stack defines a context with @Context(...).

import { Context } from 'ctxstore';

class ExampleService {
    @Context({
        message: 'hello world',
    })
    method(): string {
        return this.innerOne();
    }

    innerOne() {
        return this.innerTwo();
    }

    innerTwo() {
        return Context.get(); // <--- returns 'hello world'
    }
}

In the example above, Context.get() will still have access to method()'s context because the inner methods are within method()'s scope.

Understanding

ContextStore handles the management of a Context object similar to how function lifecycles are managed in a CallStack/ StackFrame. When a function completes, its stack frame is naturally popped off the call stack; @Context functions the same way. When a method with a @Context is invoked, the context object is pushed onto a CallStack. When a method with a @Context completes (irrespective of whether it's synchronous or asynchronous), the context object is popped off the CallStack. Incoming context object keys take precedence over context objects existing on the CallStack. If the incoming context and the context located at the top of the stack both share the same key, the value of the incoming context object will "mask" the value of the context on the stack.

NOTE: A CallStack object is instantiated when the @Context decorator is encountered for the first time in a call stack trace.

Let's take a look at the following examples.

class UserDatabase:

class UserDatabase {
    @Context({
        subspanName: 'Database:User',
        connector: 'aurora:postgresql',
        table: 'user_table',
    })
    getUser(username: string): Promise<object> {
        return Promise.resolve(Context.get());
    }
}

class EmailClient:

class EmailClient {
    @Context({
        subspanName: 'Client:Email',
        connector: 'sns:topic',
        datatype: 'json',
    })
    publish(username: string): Promise<object> {
        return Promise.resolve(Context.get());
    }
}

class NotificationService:

class NotificationService {
    private userDb: UserDatabase = new UserDatabase();
    private emailClient: EmailClient = new EmailClient();

    @Context({
        spanName: 'NotificationService:notifyUser(...)',
        endpoint: '/notify/user/{username}',
    })
    async notifyUser(username: string): Promise<object> {
        await this.userDb.getUser(username);
        await this.emailClient.publish(username);
        return Context.get();
    }
}

If we invoke notifyUser:

await notifyUser('userId123');

The context object returned by Context.get() inside of notifyUser will be:

async notifyUser(username: string): Promise<object> {
    ...
    const ctx = Context.get();
    ctx will be:
    {
        spanName: 'NotificationService:notifyUser(...)',
        endpoint: '/notify/user/{username}',
    };
}

The context object returned by Context.get() inside of getUser will be:

getUser(username: string): Promise<object> {
    ...
    const ctx = Context.get();
    ctx will be:
    {
        spanName: 'NotificationService:notifyUser(...)',
        endpoint: '/notify/user/{username}',
        subspanName: 'Database:User',
        connector: 'aurora:postgresql',
        table: 'user_table',
    };
}

If getUser's context had a spanName property (e.g. UserDatabase:getUser(...)), then getUser's spanName property would override the context from notifyUser.

If we attempt to concurrently handle multiple notifyUser invocations:

await Promise.all([notifyUser('userId123'), notifyUser('userId456')]);

A context CallStack is instantiated per notifyUser invocation since this is the first usage of @Context within this context scope. Although asynchronous function resolutions (inner and top level) are unpredictable, ctxstore is capable of synchronizing contexts with their associated CallStacks. In other words, contexts won't get jumbled/ mixed up despite the chaotic nature of asynchrony.

Upcoming Features

function Context(target: Object, propertyKey: string | symbol, parameterIndex: number) : void

There is a compelling reason to support @Context as a parameter decorator.

class Lambda {
    private userDb: UserDatabase = new UserDatabase();
    private emailClient: EmailClient = new EmailClient();

    @Context({
        method: 'GET',
        path: '/organization/{organization}/user/{userId}',
    })
    async handler(event, @Context context): Promise<Response> {
        logger.info('Start Trace Logging...', { trace, context});
        return {
            body: {
                ...
            },
            statusCode: ...
        };
    }
}

Supporting parameter decorators reduces the overhead of boilerplate and complexity for the user. Unfortunately, parameter decorators do not function similarly to CallStacks, there is no way to determine when the decorated function has completed from the perspective of a parameter decorator. This makes management of a context trace difficult.

License

Copyright 2021. Licensed MIT.