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

pojo-constructor

v9.2.0

Published

Configuration as code helper for TypeScript.

Downloads

105

Readme

POJO Constructor

Jest coverage semantic-release: angular Code Style by Prettier Package License MIT Npm Version

Configuration as code helper for TypeScript - write own constructor for each property, sync or async.

A setup like this allows you to focus on individual properties of configuration object, which is useful when single property can control complicated behaviour and selecting a proper value for it should be a maintainable piece of code.

Example use cases

  • Maintaining configuration for server-side application code
  • Building options for a complex JS class

Examples

1. Simple server-side config, sync mode

(Run with npm run ts-file ./examples/example-1-simple-server-side-config-sync-mode.ts)

/*
 * Use TypeScript to make configuration type safe.
 */
type AppCfg = {
  appName: string;
  listenOnPort: number;
  thirdPartyApiEndpoint: string;
};

type Env = 'dev' | 'staging' | 'production';

/**
 * Define configuration properties in methods.
 */
const appCfgCtor = new PojoConstructorSync<AppCfg, Env>({
  appName(env: Env) {
    return { value: `awesome-app-in-${env}` };
  },

  listenOnPort() {
    return { value: 3003 };
  },

  thirdPartyApiEndpoint(env: Env) {
    switch (env) {
      case 'dev':
      case 'staging':
        return { value: 'https://sandbox.thrird-party-api.example.com' };
      case 'production':
        return { value: 'https://api.example.com' };
      default:
        throw new Error('Unknown env');
    }
  },
});

/**
 * Produce configuration for dev env.
 */
const { value: configDev } = appCfgCtor.pojo('dev' as Env);

/**
 * Print result.
 */
console.log(JSON.stringify(configDev, null, 2));

prints

{
  appName: 'awesome-app-in-dev',
  listenOnPort: 3003,
  thirdPartyApiEndpoint: 'https://sandbox.thrird-party-api.example.com'
}

2. Server-side config with feature flags, async mode

(Run with npm run ts-file ./examples/example-2-simple-server-side-config-async-mode.ts)

type AppCfg = {
  appName: string;
  listenOnPort: number;
  featureFlags: {
    feature1: boolean;
    feature2: boolean;
  };
};

type Env = 'dev' | 'staging' | 'production';

const appCfgCtor = new PojoConstructorAsync<AppCfg, Env>({
  async appName(env: Env) {
    return { value: `awesome-app-in-${env}` };
  },

  async listenOnPort() {
    return { value: 3003 };
  },

  /**
   * Emulates fetching feature flags from database or a CMS.
   */
  async featureFlags(env: Env) {
    const GET_0_OR_1 = `https://www.random.org/integers/?num=1&min=0&max=1&col=1&base=2&format=plain&rnd=id.${env}`;
    const feature1Flag = Boolean(
      Number((await axios.get(GET_0_OR_1 + 'feature1')).data),
    );
    const feature2Flag = Boolean(
      Number((await axios.get(GET_0_OR_1 + 'feature2')).data),
    );
    return {
      value: {
        feature1: feature1Flag,
        feature2: feature2Flag,
      },
    };
  },
});

(async () => {
  const { value: configDev } = await appCfgCtor.pojo('dev' as Env);
  console.log(JSON.stringify(configDev, null, 2));
})();

prints

{
  appName: 'awesome-app-in-dev',
  featureFlags: { feature1: false, feature2: true },
  listenOnPort: 3003
}

3. Using combined sync + async mode declaration

(Run with npm run ts-file ./examples/example-3-simple-server-side-config-combined-mode.ts)

Using "sync mode" fails because featureFlags property constructor does not return a sync function, but this fail is handled by handler and so the rest of the object is still constructed.

Using "async mode" falls back on sync functions.

type AppCfg = {
  appName: string;
  listenOnPort: number;
  featureFlags: {
    feature1: boolean;
    feature2: boolean;
  };
};

type Env = 'dev' | 'staging' | 'production';

const appCfgCtor = new PojoConstructorSyncAndAsync<AppCfg, Env>({
  appName(env: Env) {
    const sync = () => {
      return { value: `awesome-app-in-${env}` };
    };
    return { sync };
  },

  listenOnPort() {
    const sync = () => {
      return { value: 3003 };
    };
    return { sync };
  },

  featureFlags(env: Env) {
    const async = async () => {
      const GET_0_OR_1 = `https://www.random.org/integers/?num=1&min=0&max=1&col=1&base=2&format=plain&rnd=id.${env}`;
      const feature1Flag = Boolean(
        Number((await axios.get(GET_0_OR_1 + 'feature1')).data),
      );
      const feature2Flag = Boolean(
        Number((await axios.get(GET_0_OR_1 + 'feature2')).data),
      );
      return {
        value: {
          feature1: feature1Flag,
          feature2: feature2Flag,
        },
      };
    };
    return { async };
  },
});

function handler(caught: unknown, { key }: PojoConstructorOptionsCatchFn) {
  console.log(`- - Caught trying to construct ${key}`);
  console.log(caught);
  console.log('---');
}

console.log('- dev (sync mode):');
const { value: configDev } = appCfgCtor.pojo('dev' as Env, { catch: handler }).sync();
console.log(configDev);

(async () => {
  console.log('- dev (async mode):');
  const { value: configDev } = await appCfgCtor.pojo('dev' as Env).async();
  console.log(configDev);
})();

4. Optional properties vs undefined value

(Run with npm run ts-file ./examples/example-4-optional-fields.ts)

Notice that providing { value: undefined } and empty object {} is different in the same way as having a property on an object with value undefined and not having a property on an object.

type AppCfg = {
  dev_option?: string;
  prod_option: string | undefined;
};

type Env = 'dev' | 'staging' | 'production';

type Input = { env: Env; prodOption?: string };

const appCfgCtor = new PojoConstructorSync<AppCfg, Input>({
  dev_option({ env }) {
    if (env === 'dev') {
      return { value: 'this-option-is-only-set-in-dev' };
    }
    return {};
  },

  prod_option({ prodOption }) {
    return {
      value: prodOption,
    };
  },
});

produces

- dev:
{
  dev_option: 'this-option-is-only-set-in-dev',
  prod_option: undefined
}

- staging:
{ prod_option: undefined }

- production:
{ prod_option: 'prodOption value' }

5. Using cache

(Run with npm run ts-file ./examples/example-5-cache.ts)

Using cache proxy you can make sure that property constructor method is only called once.

type AppCfg = {
  remote_fetched_option: string;
  derived_option_1: string;
  derived_option_2: string;
};

let remoteCalls = 0;

const appCfgCtor = new PojoConstructorAsync<AppCfg>({
  /**
   * Emulates fetching config from database or a CMS.
   */
  async remote_fetched_option(_, { key }) {
    const GET_0_OR_1 = `https://www.random.org/integers/?num=1&min=0&max=1&col=1&base=2&format=plain&rnd=id.${Date.now()}`;
    const value = (await axios.get(GET_0_OR_1)).data;
    remoteCalls++;
    return {
      value: key + ' : ' + value,
    };
  },

  async derived_option_1(_, { key, cache }) {
    return {
      value: key + ' / ' + (await cache.remote_fetched_option()).value,
    };
  },

  async derived_option_2(_, { key, cache }) {
    return {
      value: key + ' / ' + (await cache.derived_option_1()).value,
    };
  },
});

(async () => {
  const { value: cfg } = await appCfgCtor.pojo();
  console.log(cfg);
  console.log({ remoteCalls });
})();

prints

{
  derived_option_1: 'derived_option_1 / remote_fetched_option : 1',
  derived_option_2: 'derived_option_2 / derived_option_1 / remote_fetched_option : 1',
  remote_fetched_option: 'remote_fetched_option : 1'
}
{ remoteCalls: 1 }

API

PojoConstructorSync

Constructor methods for each of properties returns { value } object synchronously.

PojoConstructorAsync

Constructor methods for each of properties returns promise for { value } object.

PojoConstructorSyncAndAsync

Can operate in both sync mode and async mode. Constructor methods for each of properties returns an object with either one of sync, async methods or both.

All of these are valid:

  • { sync, async }.
  • { sync }.
  • { async }.

Where

  • async - returns promise for { value } object
  • sync - returns { value } object synchronously

If you only specify sync methods, you can use them for "async mode" ( calling PojoConstructorSyncAndAsync#new().async()), but you cannot use "sync mode" (calling PojoConstructorSyncAndAsync#new().sync()) if you only specify promise methods.

You can specify async methods for some fields and still construct an object in "sync mode" if you also specify a catch option. catch will be called each time constructing a property fails, but all properties that do not fail will be added to resulting object.

Links

GitHub

https://github.com/dany-fedorov/pojo-constructor.git

Npm

https://www.npmjs.com/package/pojo-constructor