Code Monkey home page Code Monkey logo

env-schema's Introduction

env-schema

CI NPM version js-standard-style

Utility to check environment variables using JSON schema, Ajv, and dotenv.

See supporting resources section for helpful guides on getting started.

Install

npm i env-schema

Usage

const envSchema = require('env-schema')

const schema = {
  type: 'object',
  required: [ 'PORT' ],
  properties: {
    PORT: {
      type: 'number',
      default: 3000
    }
  }
}

const config = envSchema({
  schema: schema,
  data: data, // optional, default: process.env
  dotenv: true // load .env if it is there, default: false
  // or you can pass DotenvConfigOptions
  // dotenv: {
  //   path: '/custom/path/to/.env'
  // }
})

console.log(config)
// output: { PORT: 3000 }

see DotenvConfigOptions

Custom ajv instance

Optionally, the user can supply their own ajv instance:

const envSchema = require('env-schema')
const Ajv = require('ajv')

const schema = {
  type: 'object',
  required: [ 'PORT' ],
  properties: {
    PORT: {
      type: 'number',
      default: 3000
    }
  }
}

const config = envSchema({
  schema: schema,
  data: data,
  dotenv: true,
  ajv: new Ajv({
    allErrors: true,
    removeAdditional: true,
    useDefaults: true,
    coerceTypes: true,
    allowUnionTypes: true
  })
})

console.log(config)
// output: { PORT: 3000 }

It is possible to enhance the default ajv instance providing the customOptions function parameter. This example shows how to use the format keyword in your schemas.

const config = envSchema({
  schema: schema,
  data: data,
  dotenv: true,
  ajv: {
    customOptions (ajvInstance) {
      require('ajv-formats')(ajvInstance)
      return ajvInstance
    }
  }
})

Note that it is mandatory returning the ajv instance.

Order of configuration loading

The order of precedence for configuration data is as follows, from least significant to most:

  1. Data sourced from .env file (when dotenv configuration option is set)
  2. Data sourced from environment variables in process.env
  3. Data provided via the data configuration option

Fluent-Schema API

It is also possible to use fluent-json-schema:

const envSchema = require('env-schema')
const S = require('fluent-json-schema')

const config = envSchema({
  schema: S.object().prop('PORT', S.number().default(3000).required()),
  data: data, // optional, default: process.env
  dotenv: true, // load .env if it is there, default: false
  expandEnv: true, // use dotenv-expand, default: false
})

console.log(config)
// output: { PORT: 3000 }

NB Support for additional properties in the schema is disabled for this plugin, with the additionalProperties flag set to false internally.

Custom keywords

This library supports the following Ajv custom keywords:

separator

Type: string

Applies to type: string

When present, the provided schema value will be split on this value.

Example:

const envSchema = require('env-schema')

const schema = {
  type: 'object',
  required: [ 'ALLOWED_HOSTS' ],
  properties: {
    ALLOWED_HOSTS: {
      type: 'string',
      separator: ','
    }
  }
}

const data = {
  ALLOWED_HOSTS: '127.0.0.1,0.0.0.0'
}

const config = envSchema({
  schema: schema,
  data: data, // optional, default: process.env
  dotenv: true // load .env if it is there, default: false
})

// config.ALLOWED_HOSTS => ['127.0.0.1', '0.0.0.0']

The ajv keyword definition objects can be accessed through the property keywords on the envSchema function:

const envSchema = require('env-schema')
const Ajv = require('ajv')

const schema = {
  type: 'object',
  properties: {
    names: {
      type: 'string',
      separator: ','
    }
  }
}

const config = envSchema({
  schema: schema,
  data: data,
  dotenv: true,
  ajv: new Ajv({
    allErrors: true,
    removeAdditional: true,
    useDefaults: true,
    coerceTypes: true,
    allowUnionTypes: true,
    keywords: [envSchema.keywords.separator]
  })
})

console.log(config)
// output: { names: ['foo', 'bar'] }

TypeScript

You can specify the type of your config:

import { envSchema, JSONSchemaType } from 'env-schema'

interface Env {
  PORT: number;
}

const schema: JSONSchemaType<Env> = {
  type: 'object',
  required: [ 'PORT' ],
  properties: {
    PORT: {
      type: 'number',
      default: 3000
    }
  }
}

const config = envSchema({
  schema
})

You can also use a JSON Schema library like typebox:

import { envSchema } from 'env-schema'
import { Static, Type } from '@sinclair/typebox'

const schema = Type.Object({
  PORT: Type.Number({ default: 3000 })
})

type Schema = Static<typeof schema>

const config = envSchema<Schema>({
  schema
})

If no type is specified the config will have the EnvSchemaData type.

export type EnvSchemaData = {
  [key: string]: unknown;
}

Supporting resources

The following section lists helpful reference applications, articles, guides and other resources that demonstrate the use of env-schema in different use-cases and scenarios:

Acknowledgements

Kindly sponsored by Mia Platform and NearForm.

License

MIT

env-schema's People

Contributors

aderchox avatar adrianpacala avatar batovpasha avatar bwyx avatar climba03003 avatar delvedor avatar dependabot[bot] avatar eomm avatar fdawgs avatar fox1t avatar francisbrito avatar github-actions[bot] avatar greenkeeper[bot] avatar is2ei avatar jimmyhurrah avatar jsumners avatar klaseca avatar lirantal avatar marcolanaro avatar mcollina avatar mse99 avatar nigelhanlon avatar serdnam avatar sniperwolf avatar tommarien avatar uzlopak avatar zekth avatar zigastrgar avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

env-schema's Issues

TS: Open up envSchemaData

🚀 Feature Proposal

Allow a type to be specified when using envSchema().

Motivation

This can be used to give a an interface or a typebox from json schema inferred interface

Example

// change in type
declare const loadAndValidateEnvironment: {
  <T = EnvSchemaData>(_opts?: EnvSchemaOpt): T;
  keywords: {
    separator: KeywordDefinition;
  }
}
const schema = Schema.object()
  .prop('PORT', Schema.string());

interface Env {
  PORT?: string;
  LOG_LEVEL?: string;
  MONGO_URI: string;
  NODE_ENV?: string;
}

const env = envSchema<Env>({
  dotenv: !process.env.TAP,
  schema,
});

`customOptions` is not typed, so TypeScript is giving errors

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

N/A

Plugin version

"env-schema": "^3.5",

Node.js version

14

Operating system

Windows

Operating system version (i.e. 20.04, 11.3, 10)

N/A

Description

Following error when using customOptions:

TS2322: Type '{ customOptions(ajvInstance: any): any; }' is not assignable to type 'Ajv'.   Object literal may only specify known properties, and 'customOptions' does not exist in type 'Ajv'

Because it's not in the types.

Steps to Reproduce

Just use customOptions in a TS file

Expected Behavior

Typings know about this extra feature, so there are no TS errors

feature: allow custom ajv instance

Instead of adding the support one by one. I suggest to allow user pass a custom ajv instance.

Originally posted by @climba03003 in #52 (comment)

Goal:

  1. allow pass in custom ajv instance through option
  2. expose the current keyword for the user to use
  3. optional: add the current keyword to the custom ajv instance

config.data in the README file has to be config.ALLOWED_HOSTS?

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the issue has not already been raised

Issue

In the README file, below an example you have this comment:

// config.data => ['127.0.0.1', '0.0.0.0']

but it seems that it has to be this:

// config.ALLOWED_HOSTS => ['127.0.0.1', '0.0.0.0']

I tried on Runkit too, config.data logs undefined. Or I might be missing something...

Type.RegExp fails at Ajv compilation

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

4.26.0

Plugin version

5.2.1

Node.js version

20.10.0

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

Sonoma 14.3

Description

Type.RegExp() throw an error no matter what pattern I use.

The error:

Error: schema is invalid: data/properties/JWT_MAX_AGE/type must be equal to one of the allowed values, data/properties/JWT_MAX_AGE/type must be array, data/properties/JWT_MAX_AGE/type must match a schema in anyOf
    at Ajv.validateSchema

I used Type.String({ pattern: '....'}) as a work around.

Steps to Reproduce

import { Static, Type } from '@sinclair/typebox';

import { envSchema } from 'env-schema';

const schema = Type.Object({
    JWT_SECRET: Type.String({ minLength: 1 }),
    JWT_MAX_AGE: Type.RegExp( '^[1-9]\\d*d$' ),
});

export type Env = Static<typeof schema>;

export const config = envSchema<Env>({ schema });

Expected Behavior

To be able to use Type.RegExp() instead of using Type.String({ pattern}) work around.

Cast boolean from env variable

There is no type in env variable so for that how deal with casting boolean type ?

const envSchema = require('env-schema').default;

const schema = {
  type: 'object',
  properties: {
    BOOL_VAL: {
      type: 'boolean',
    },
  },
};

['0', 'f', 'F', 'false', 'FALSE', 'False'].forEach((v) => {
  const r = envSchema({
    schema,
    data: {
      BOOL_VAL: v,
    },
  });
  console.log(r.BOOL_VAL === false);
});

['1', 't', 'T', 'true', 'TRUE', 'True'].forEach((v) => {
  const r = envSchema({
    schema,
    data: {
      BOOL_VAL: v,
    },
  });
  console.log(r.BOOL_VAL === true);
});

/**
Error: should be boolean
    at loadAndValidateEnvironment (./node_modules/env-schema/index.js:97:19)
    at ./test.js:13:13
    at Array.forEach (<anonymous>)
    at Object.<anonymous> (./test.js:12:44)
    at Module._compile (node:internal/modules/cjs/loader:1109:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1138:10)
    at Module.load (node:internal/modules/cjs/loader:989:32)
    at Function.Module._load (node:internal/modules/cjs/loader:829:14)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)
    at node:internal/main/run_main_module:17:47 {
  errors: [
    {
      keyword: 'type',
      dataPath: '/BOOL_VAL',
      schemaPath: '#/properties/BOOL_VAL/type',
      params: { type: 'boolean' },
      message: 'should be boolean'
    }
  ]
}
 */

Sample golang source code:
https://golang.org/src/strconv/atob.go#L10

JSs Implementation:
https://github.com/validatorjs/validator.js/blob/master/src/lib/toBoolean.js

Use Ajv.errorsText instead of errors.map, otherwise you loose context

🚀 Feature Proposal

Suggestion to use the errorsText instead of the current custom formatter

Motivation

Atm you loose context of which variable is wrong

Error: must be equal to one of the allowed values

Example

If we would change the code to

 if (!valid) {
    const error = new Error(ajv.errorsText(valid.errors, { dataVar: 'process.env'}))
    error.errors = ajv.errors
    throw error
  }

we would get following output

Error: process.env/LOG_LEVEL must be equal to one of the allowed values

I would not mind creating a pull request for it

Expand environment variables

🚀 Feature Proposal

It would be great to add the ability to interpolate variables.

Motivation

Often boxes come with some env variables that you want to use to declare other environment variables.

Example

Given the machine comes with an env variable like the following:

K8S_NAMESPACE=my_k8s_namespace

When in .env file we define the following

SERVICE_URL=https://prefix.$K8S_NAMESPACE.my.domain.com

Then the variable should be valued like:

SERVICE_URL=https://prefix.my_k8s_namespace.my.domain.com

This could be done in the app code, but consider the scenario where for local development the service url needs to have the following value:

SERVICE_URL=https://some.staging.environment.my.domain.com

In this case the app code should check the type of environment and then declare different strategies. It would be useful to just declare composable variables in the .env file.

Help

I could open a PR if you agree with the implementation.
From my point of view env-schema should provide a boolean expand options defaulted to false. When expand=true, internally use dotenv-expand.

Use with fluent-json-schema now requires to call valueOf in ObjectSchema when using Typescript

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the regression has not already been reported

Last working version

5.1.0

Stopped working in version

5.1.1

Node.js version

18.x

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

12.3

💥 Regression Report

Typings in Typescript now throw an error when using a fluent-json-schema generated schema as input for envSchema

Steps to Reproduce

In a Typescript file the next block of code reproduces the issue

import envSchema from 'env-schema'
import S from 'fluent-json-schema'

envSchema({
  schema: S.object()
})
Type 'ObjectSchema' is not assignable to type 'AnySchema | UncheckedJSONSchemaType<EnvSchemaData, false> | undefined'.
  Type 'ObjectSchema' is not assignable to type '{ type: "object"; additionalProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; unevaluatedProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; ... 7 more ...; maxProperties?: number | undefined; } & { ...; } & { ...; } & { ...; }'.
    Property 'type' is missing in type 'ObjectSchema' but required in type '{ type: "object"; additionalProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; unevaluatedProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; ... 7 more ...; maxProperties?: number | undefined; }'.ts(2322)

Nevertheless the following block of code does not throw an error

import envSchema from 'env-schema'
import S from 'fluent-json-schema'

envSchema({
  schema: S.object().valueOf
})

Expected Behavior

As ObjectSchema from fluent-json-schema was removed from typings in #137 it might be solved otherwise or maybe be documented to avoid future confusion

Add method for process env validation

I need to know is there any method for preprocess env variable such as.

export TRUSTED_IPS=192.168.1.10,192.168.1.10
nodejs app.js
const schema = {
  type: 'object',
  required: [ 'TRUSTED_IPS' ],
  properties: {
    TRUSTED_IPS: {
      type: 'array',
      items: {
          type: 'string',
          format: 'ipv4',      
      },
      process(var) {
        return var.split(',');
      },
      default: [],
    }
  }
}

console.log(process.env.TRUSTED_IPS); // [ '192.168.1.10' , '192.168.1.10' ]

process could be array or json object like:

export SAMPLE='{"foo": { "bar": "baz" }}';

In schema could be:

process: 'Array'
// or
process: 'JSON'
// or user define like above example

Typing `separator` with an array instead of just string

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

4.19.2

Plugin version

No response

Node.js version

20.x

Operating system

Linux

Operating system version (i.e. 20.04, 11.3, 10)

6.4.1

Description

Hello,

I would like to type ALLOWED_HOSTS as string[] instead of string.

interface ConfigEnv {
  ALLOWED_HOSTS: string
}

export function loadConfig(): ConfigEnv {
  return envSchema({
    schema: {
      type: 'object',
      required: [
        'ALLOWED_HOSTS',
      ],
      properties: {
        ALLOWED_HOSTS: {
          type: 'string',
          separator: ',',
        },
      },
    },
  })
}

Now the actual type of loadConfig().ALLOWED_HOSTS is an array, empty or not. Here the type is incorrect to be string and not string[]. But if I change it I get the error on the line of type: "string":

Type '"string"' is not assignable to type '"array"'.ts(2322)

How can I do? Thanks.

Steps to Reproduce

// a.js
import envSchema = require("env-schema")

interface ConfigEnv {
  ALLOWED_HOSTS: string[]
}

export function loadConfig(): ConfigEnv {
  return envSchema({
    schema: {
      type: 'object',
      required: [
        'ALLOWED_HOSTS',
      ],
      properties: {
        ALLOWED_HOSTS: {
          type: 'string',
          separator: ',',
        },
      },
    },
  })
}

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": [
      "es2020",
      "dom"
    ],
    "declaration": true,
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": false,
    "inlineSourceMap": true,
    "inlineSources": true,
    "experimentalDecorators": true,
    "strictPropertyInitialization": false,
    "typeRoots": [
      "./node_modules/@types"
    ],
    "outDir": "dist",
    "resolveJsonModule": true
  },
  "exclude": [
    "node_modules",
    "cdk.out",
    "dist"
  ]
}

Run:

$ ts-node a.ts

Error:

TSError: ⨯ Unable to compile TypeScript:
a.ts(16,11): error TS2322: Type '"string"' is not assignable to type '"array"'.

Expected Behavior

Type ALLOWED_HOSTS as:

interface ConfigEnv {
  ALLOWED_HOSTS: string[]
}

Delete stale Greenkeeper branches

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the issue has not already been raised

Issue

Greenkeeper shutdown in 2020, so these can be removed.
Would make forking and working on this repo a bit quicker without the forks flooding the CLI when running git branch.

I've removed old Greenkeeper branches in repos where i have permissions, but do not have it for this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.