├── .gitattributes ├── .github ├── dependabot.yml ├── stale.yml └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── eslint.config.js ├── index.js ├── package.json ├── test ├── .env ├── basic.test.js ├── custom-ajv.test.js ├── expand.test.js ├── fluent-schema.test.js ├── make-test.js └── no-global.test.js └── types ├── index.d.ts └── index.test-d.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically convert line endings 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | open-pull-requests-limit: 10 8 | 9 | - package-ecosystem: "npm" 10 | directory: "/" 11 | schedule: 12 | interval: "monthly" 13 | open-pull-requests-limit: 10 14 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 15 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - "discussion" 8 | - "feature request" 9 | - "bug" 10 | - "help wanted" 11 | - "plugin suggestion" 12 | - "good first issue" 13 | # Label to use when marking an issue as stale 14 | staleLabel: stale 15 | # Comment to post when marking an issue as stale. Set to `false` to disable 16 | markComment: > 17 | This issue has been automatically marked as stale because it has not had 18 | recent activity. It will be closed if no further activity occurs. Thank you 19 | for your contributions. 20 | # Comment to post when closing a stale issue. Set to `false` to disable 21 | closeComment: false 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - next 8 | - 'v*' 9 | paths-ignore: 10 | - 'docs/**' 11 | - '*.md' 12 | pull_request: 13 | paths-ignore: 14 | - 'docs/**' 15 | - '*.md' 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | test: 22 | permissions: 23 | contents: write 24 | pull-requests: write 25 | uses: fastify/workflows/.github/workflows/plugins-ci.yml@v5 26 | with: 27 | license-check: true 28 | lint: true 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | # Vim swap files 133 | *.swp 134 | 135 | # macOS files 136 | .DS_Store 137 | 138 | # Clinic 139 | .clinic 140 | 141 | # lock files 142 | bun.lockb 143 | package-lock.json 144 | pnpm-lock.yaml 145 | yarn.lock 146 | 147 | # editor files 148 | .vscode 149 | .idea 150 | 151 | #tap files 152 | .tap/ 153 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Fastify 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # env-schema 2 | 3 | [![CI](https://github.com/fastify/env-schema/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fastify/env-schema/actions/workflows/ci.yml) 4 | [![NPM version](https://img.shields.io/npm/v/env-schema.svg?style=flat)](https://www.npmjs.com/package/env-schema) 5 | [![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard) 6 | 7 | Utility to check environment variables using [JSON schema](https://json-schema.org/), [Ajv](http://npm.im/ajv), and 8 | [dotenv](http://npm.im/dotenv). 9 | 10 | See [supporting resources](#supporting-resources) section for helpful guides on getting started. 11 | 12 | ## Install 13 | 14 | ``` 15 | npm i env-schema 16 | ``` 17 | 18 | ## Usage 19 | 20 | ```js 21 | const envSchema = require('env-schema') 22 | 23 | const schema = { 24 | type: 'object', 25 | required: [ 'PORT' ], 26 | properties: { 27 | PORT: { 28 | type: 'number', 29 | default: 3000 30 | } 31 | } 32 | } 33 | 34 | const config = envSchema({ 35 | schema: schema, 36 | data: data, // optional, default: process.env 37 | dotenv: true // load .env if it is there, default: false 38 | // or you can pass DotenvConfigOptions 39 | // dotenv: { 40 | // path: '/custom/path/to/.env' 41 | // } 42 | }) 43 | 44 | console.log(config) 45 | // output: { PORT: 3000 } 46 | ``` 47 | 48 | see [DotenvConfigOptions](https://github.com/motdotla/dotenv#options) 49 | 50 | ### Custom ajv instance 51 | 52 | Optionally, the user can supply their own ajv instance: 53 | 54 | ```js 55 | const envSchema = require('env-schema') 56 | const Ajv = require('ajv') 57 | 58 | const schema = { 59 | type: 'object', 60 | required: [ 'PORT' ], 61 | properties: { 62 | PORT: { 63 | type: 'number', 64 | default: 3000 65 | } 66 | } 67 | } 68 | 69 | const config = envSchema({ 70 | schema: schema, 71 | data: data, 72 | dotenv: true, 73 | ajv: new Ajv({ 74 | allErrors: true, 75 | removeAdditional: true, 76 | useDefaults: true, 77 | coerceTypes: true, 78 | allowUnionTypes: true 79 | }) 80 | }) 81 | 82 | console.log(config) 83 | // output: { PORT: 3000 } 84 | ``` 85 | 86 | It is possible to enhance the default ajv instance providing the `customOptions` function parameter. 87 | This example shows how to use the `format` keyword in your schemas. 88 | 89 | ```js 90 | const config = envSchema({ 91 | schema: schema, 92 | data: data, 93 | dotenv: true, 94 | ajv: { 95 | customOptions (ajvInstance) { 96 | require('ajv-formats')(ajvInstance) 97 | return ajvInstance 98 | } 99 | } 100 | }) 101 | ``` 102 | 103 | Note that it is mandatory to return the ajv instance. 104 | 105 | ### Order of configuration loading 106 | 107 | The order of precedence for configuration data is as follows, from least 108 | significant to most: 109 | 1. Data sourced from `.env` file (when `dotenv` configuration option is set) 110 | 2. Data sourced from environment variables in `process.env` 111 | 3. Data provided via the `data` configuration option 112 | 113 | ### Fluent-Schema API 114 | 115 | It is also possible to use [fluent-json-schema](http://npm.im/fluent-json-schema): 116 | 117 | ```js 118 | const envSchema = require('env-schema') 119 | const S = require('fluent-json-schema') 120 | 121 | const config = envSchema({ 122 | schema: S.object().prop('PORT', S.number().default(3000).required()), 123 | data: data, // optional, default: process.env 124 | dotenv: true, // load .env if it is there, default: false 125 | expandEnv: true, // use dotenv-expand, default: false 126 | }) 127 | 128 | console.log(config) 129 | // output: { PORT: 3000 } 130 | ``` 131 | 132 | **NB** Support for additional properties in the schema is disabled for this plugin, with the `additionalProperties` flag set to `false` internally. 133 | 134 | ### Custom keywords 135 | This library supports the following Ajv custom keywords: 136 | 137 | #### `separator` 138 | Type: `string` 139 | 140 | Applies to type: `string` 141 | 142 | When present, the provided schema value will be split on this value. 143 | 144 | Example: 145 | ```js 146 | const envSchema = require('env-schema') 147 | 148 | const schema = { 149 | type: 'object', 150 | required: [ 'ALLOWED_HOSTS' ], 151 | properties: { 152 | ALLOWED_HOSTS: { 153 | type: 'string', 154 | separator: ',' 155 | } 156 | } 157 | } 158 | 159 | const data = { 160 | ALLOWED_HOSTS: '127.0.0.1,0.0.0.0' 161 | } 162 | 163 | const config = envSchema({ 164 | schema: schema, 165 | data: data, // optional, default: process.env 166 | dotenv: true // load .env if it is there, default: false 167 | }) 168 | 169 | // config.ALLOWED_HOSTS => ['127.0.0.1', '0.0.0.0'] 170 | ``` 171 | 172 | The ajv keyword definition objects can be accessed through the property `keywords` on the `envSchema` function: 173 | 174 | ```js 175 | const envSchema = require('env-schema') 176 | const Ajv = require('ajv') 177 | 178 | const schema = { 179 | type: 'object', 180 | properties: { 181 | names: { 182 | type: 'string', 183 | separator: ',' 184 | } 185 | } 186 | } 187 | 188 | const config = envSchema({ 189 | schema: schema, 190 | data: data, 191 | dotenv: true, 192 | ajv: new Ajv({ 193 | allErrors: true, 194 | removeAdditional: true, 195 | useDefaults: true, 196 | coerceTypes: true, 197 | allowUnionTypes: true, 198 | keywords: [envSchema.keywords.separator] 199 | }) 200 | }) 201 | 202 | console.log(config) 203 | // output: { names: ['foo', 'bar'] } 204 | ``` 205 | 206 | ### TypeScript 207 | 208 | You can specify the type of your `config`: 209 | 210 | ```ts 211 | import { envSchema, JSONSchemaType } from 'env-schema' 212 | 213 | interface Env { 214 | PORT: number; 215 | } 216 | 217 | const schema: JSONSchemaType = { 218 | type: 'object', 219 | required: [ 'PORT' ], 220 | properties: { 221 | PORT: { 222 | type: 'number', 223 | default: 3000 224 | } 225 | } 226 | } 227 | 228 | const config = envSchema({ 229 | schema 230 | }) 231 | ``` 232 | 233 | You can also use a `JSON Schema` library like `typebox`: 234 | 235 | ```ts 236 | import { envSchema } from 'env-schema' 237 | import { Static, Type } from '@sinclair/typebox' 238 | 239 | const schema = Type.Object({ 240 | PORT: Type.Number({ default: 3000 }) 241 | }) 242 | 243 | type Schema = Static 244 | 245 | const config = envSchema({ 246 | schema 247 | }) 248 | ``` 249 | 250 | If no type is specified the `config` will have the `EnvSchemaData` type. 251 | 252 | ```ts 253 | export type EnvSchemaData = { 254 | [key: string]: unknown; 255 | } 256 | ``` 257 | 258 | ## Supporting resources 259 | 260 | The following section lists helpful reference applications, articles, guides, and other 261 | resources that demonstrate the use of env-schema in different use cases and scenarios: 262 | 263 | * A reference application using [Fastify with env-schema and dotenv](https://github.com/lirantal/fastify-dotenv-envschema-example) 264 | 265 | ## Acknowledgments 266 | 267 | Kindly sponsored by [Mia Platform](https://www.mia-platform.eu/) and 268 | [NearForm](https://nearform.com). 269 | 270 | ## License 271 | 272 | Licensed under [MIT](./LICENSE). 273 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = require('neostandard')({ 4 | ignores: require('neostandard').resolveIgnoresFromGitignore(), 5 | ts: true 6 | }) 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Ajv = require('ajv') 4 | 5 | const separator = { 6 | keyword: 'separator', 7 | type: 'string', 8 | metaSchema: { 9 | type: 'string', 10 | description: 'value separator' 11 | }, 12 | modifying: true, 13 | valid: true, 14 | errors: false, 15 | compile: (schema) => (data, { parentData: pData, parentDataProperty: pDataProperty }) => { 16 | pData[pDataProperty] = data === '' ? [] : data.split(schema) 17 | } 18 | } 19 | 20 | const optsSchema = { 21 | type: 'object', 22 | required: ['schema'], 23 | properties: { 24 | schema: { type: 'object', additionalProperties: true }, 25 | data: { 26 | oneOf: [ 27 | { type: 'array', items: { type: 'object' }, minItems: 1 }, 28 | { type: 'object' } 29 | ], 30 | default: {} 31 | }, 32 | env: { type: 'boolean', default: true }, 33 | dotenv: { type: ['boolean', 'object'], default: false }, 34 | expandEnv: { type: ['boolean'], default: false }, 35 | ajv: { type: 'object', additionalProperties: true } 36 | } 37 | } 38 | 39 | const sharedAjvInstance = getDefaultInstance() 40 | 41 | const optsSchemaValidator = sharedAjvInstance.compile(optsSchema) 42 | 43 | function envSchema (_opts) { 44 | const opts = Object.assign({}, _opts) 45 | 46 | if (opts.schema?.[Symbol.for('fluent-schema-object')]) { 47 | opts.schema = opts.schema.valueOf() 48 | } 49 | 50 | const isOptionValid = optsSchemaValidator(opts) 51 | if (!isOptionValid) { 52 | const error = new Error(sharedAjvInstance.errorsText(optsSchemaValidator.errors, { dataVar: 'opts' })) 53 | error.errors = optsSchemaValidator.errors 54 | throw error 55 | } 56 | 57 | const { schema } = opts 58 | schema.additionalProperties = false 59 | 60 | let { data, dotenv, env, expandEnv } = opts 61 | if (!Array.isArray(data)) { 62 | data = [data] 63 | } 64 | 65 | if (dotenv) { 66 | require('dotenv').config(Object.assign({}, dotenv)) 67 | } 68 | 69 | /* istanbul ignore else */ 70 | if (env) { 71 | data.unshift(process.env) 72 | } 73 | 74 | const merge = {} 75 | data.forEach(d => Object.assign(merge, d)) 76 | 77 | if (expandEnv) { 78 | require('dotenv-expand').expand({ ignoreProcessEnv: true, parsed: merge }) 79 | } 80 | 81 | const ajv = chooseAjvInstance(sharedAjvInstance, opts.ajv) 82 | 83 | const valid = ajv.validate(schema, merge) 84 | if (!valid) { 85 | const error = new Error(ajv.errorsText(ajv.errors, { dataVar: 'env' })) 86 | error.errors = ajv.errors 87 | throw error 88 | } 89 | 90 | return merge 91 | } 92 | 93 | function chooseAjvInstance (defaultInstance, ajvOpts) { 94 | if (!ajvOpts) { 95 | return defaultInstance 96 | } else if (typeof ajvOpts === 'object' && typeof ajvOpts.customOptions === 'function') { 97 | const ajv = ajvOpts.customOptions(getDefaultInstance()) 98 | if (!(ajv instanceof Ajv)) { 99 | throw new TypeError('customOptions function must return an instance of Ajv') 100 | } 101 | return ajv 102 | } 103 | return ajvOpts 104 | } 105 | 106 | function getDefaultInstance () { 107 | return new Ajv({ 108 | allErrors: true, 109 | removeAdditional: true, 110 | useDefaults: true, 111 | coerceTypes: true, 112 | allowUnionTypes: true, 113 | addUsedSchema: false, 114 | keywords: [separator] 115 | }) 116 | } 117 | 118 | envSchema.keywords = { separator } 119 | 120 | module.exports = envSchema 121 | module.exports.default = envSchema 122 | module.exports.envSchema = envSchema 123 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "env-schema", 3 | "version": "6.0.1", 4 | "description": "Validate your env variables using Ajv and dotenv", 5 | "main": "index.js", 6 | "type": "commonjs", 7 | "types": "types/index.d.ts", 8 | "scripts": { 9 | "lint": "eslint", 10 | "lint:fix": "eslint --fix", 11 | "test": "npm run test:unit && npm run test:typescript", 12 | "test:unit": "c8 --100 node --test", 13 | "test:typescript": "tsd" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/fastify/env-schema.git" 18 | }, 19 | "keywords": [ 20 | "ajv", 21 | "env", 22 | "schema", 23 | "json", 24 | "dotenv", 25 | "validate", 26 | "extract" 27 | ], 28 | "author": "Matteo Collina ", 29 | "contributors": [ 30 | { 31 | "name": "Manuel Spigolon", 32 | "email": "behemoth89@gmail.com" 33 | }, 34 | { 35 | "name": "Maksim Sinik", 36 | "url": "https://maksim.dev" 37 | }, 38 | { 39 | "name": "Aras Abbasi", 40 | "email": "aras.abbasi@gmail.com" 41 | }, 42 | { 43 | "name": "Frazer Smith", 44 | "email": "frazer.dev@icloud.com", 45 | "url": "https://github.com/fdawgs" 46 | } 47 | ], 48 | "license": "MIT", 49 | "bugs": { 50 | "url": "https://github.com/fastify/env-schema/issues" 51 | }, 52 | "homepage": "https://github.com/fastify/env-schema#readme", 53 | "funding": [ 54 | { 55 | "type": "github", 56 | "url": "https://github.com/sponsors/fastify" 57 | }, 58 | { 59 | "type": "opencollective", 60 | "url": "https://opencollective.com/fastify" 61 | } 62 | ], 63 | "dependencies": { 64 | "ajv": "^8.12.0", 65 | "dotenv": "^16.4.5", 66 | "dotenv-expand": "10.0.0" 67 | }, 68 | "devDependencies": { 69 | "@fastify/pre-commit": "^2.1.0", 70 | "@sinclair/typebox": "^0.34.3", 71 | "ajv-formats": "^3.0.1", 72 | "c8": "^10.1.2", 73 | "eslint": "^9.17.0", 74 | "fluent-json-schema": "^6.0.0", 75 | "neostandard": "^0.12.0", 76 | "tsd": "^0.32.0" 77 | }, 78 | "pre-commit": [ 79 | "lint", 80 | "test" 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /test/.env: -------------------------------------------------------------------------------- 1 | VALUE_FROM_DOTENV=look ma 2 | EXPANDED_VALUE_FROM_DOTENV=the password is $PASSWORD! -------------------------------------------------------------------------------- /test/basic.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('node:test') 4 | const makeTest = require('./make-test') 5 | const { join } = require('node:path') 6 | 7 | process.env.VALUE_FROM_ENV = 'pippo' 8 | 9 | const tests = [ 10 | { 11 | name: 'empty ok', 12 | schema: { type: 'object' }, 13 | data: { }, 14 | isOk: true, 15 | confExpected: {} 16 | }, 17 | { 18 | name: 'simple object - ok', 19 | schema: { 20 | type: 'object', 21 | properties: { 22 | PORT: { 23 | type: 'string' 24 | } 25 | } 26 | }, 27 | data: { 28 | PORT: '44' 29 | }, 30 | isOk: true, 31 | confExpected: { 32 | PORT: '44' 33 | } 34 | }, 35 | { 36 | name: 'simple object - ok - coerce value', 37 | schema: { 38 | type: 'object', 39 | properties: { 40 | PORT: { 41 | type: 'integer' 42 | } 43 | } 44 | }, 45 | data: { 46 | PORT: '44' 47 | }, 48 | isOk: true, 49 | confExpected: { 50 | PORT: 44 51 | } 52 | }, 53 | { 54 | name: 'simple object - ok - remove additional properties', 55 | schema: { 56 | type: 'object', 57 | properties: { 58 | PORT: { 59 | type: 'integer' 60 | } 61 | } 62 | }, 63 | data: { 64 | PORT: '44', 65 | ANOTHER_PORT: '55' 66 | }, 67 | isOk: true, 68 | confExpected: { 69 | PORT: 44 70 | } 71 | }, 72 | { 73 | name: 'simple object - ok - use default', 74 | schema: { 75 | type: 'object', 76 | properties: { 77 | PORT: { 78 | type: 'integer', 79 | default: 5555 80 | } 81 | } 82 | }, 83 | data: { }, 84 | isOk: true, 85 | confExpected: { 86 | PORT: 5555 87 | } 88 | }, 89 | { 90 | name: 'simple object - ok - required + default', 91 | schema: { 92 | type: 'object', 93 | required: ['PORT'], 94 | properties: { 95 | PORT: { 96 | type: 'integer', 97 | default: 6666 98 | } 99 | } 100 | }, 101 | data: { }, 102 | isOk: true, 103 | confExpected: { 104 | PORT: 6666 105 | } 106 | }, 107 | { 108 | name: 'simple object - ok - allow array', 109 | schema: { 110 | type: 'object', 111 | required: ['PORT'], 112 | properties: { 113 | PORT: { 114 | type: 'integer', 115 | default: 6666 116 | } 117 | } 118 | }, 119 | data: [{ }], 120 | isOk: true, 121 | confExpected: { 122 | PORT: 6666 123 | } 124 | }, 125 | { 126 | name: 'simple object - ok - merge multiple object + env', 127 | schema: { 128 | type: 'object', 129 | required: ['PORT', 'MONGODB_URL'], 130 | properties: { 131 | PORT: { 132 | type: 'integer', 133 | default: 6666 134 | }, 135 | MONGODB_URL: { 136 | type: 'string' 137 | }, 138 | VALUE_FROM_ENV: { 139 | type: 'string' 140 | } 141 | } 142 | }, 143 | data: [{ PORT: 3333 }, { MONGODB_URL: 'mongodb://localhost/pippo' }], 144 | isOk: true, 145 | confExpected: { 146 | PORT: 3333, 147 | MONGODB_URL: 'mongodb://localhost/pippo', 148 | VALUE_FROM_ENV: 'pippo' 149 | } 150 | }, 151 | { 152 | name: 'simple object - ok - load only from env', 153 | schema: { 154 | type: 'object', 155 | required: ['VALUE_FROM_ENV'], 156 | properties: { 157 | VALUE_FROM_ENV: { 158 | type: 'string' 159 | } 160 | } 161 | }, 162 | data: undefined, 163 | isOk: true, 164 | confExpected: { 165 | VALUE_FROM_ENV: 'pippo' 166 | } 167 | }, 168 | { 169 | name: 'simple object - ok - opts override environment', 170 | schema: { 171 | type: 'object', 172 | required: ['VALUE_FROM_ENV'], 173 | properties: { 174 | VALUE_FROM_ENV: { 175 | type: 'string' 176 | } 177 | } 178 | }, 179 | data: { VALUE_FROM_ENV: 'pluto' }, 180 | isOk: true, 181 | confExpected: { 182 | VALUE_FROM_ENV: 'pluto' 183 | } 184 | }, 185 | { 186 | name: 'simple object - ok - load only from .env', 187 | schema: { 188 | type: 'object', 189 | required: ['VALUE_FROM_DOTENV'], 190 | properties: { 191 | VALUE_FROM_DOTENV: { 192 | type: 'string' 193 | } 194 | } 195 | }, 196 | data: undefined, 197 | isOk: true, 198 | dotenv: { path: join(__dirname, '.env') }, 199 | confExpected: { 200 | VALUE_FROM_DOTENV: 'look ma' 201 | } 202 | }, 203 | { 204 | name: 'simple object - KO', 205 | schema: { 206 | type: 'object', 207 | required: ['PORT'], 208 | properties: { 209 | PORT: { 210 | type: 'integer' 211 | } 212 | } 213 | }, 214 | data: { }, 215 | isOk: false, 216 | errorMessage: 'env must have required property \'PORT\'' 217 | }, 218 | { 219 | name: 'simple object - invalid data', 220 | schema: { 221 | type: 'object', 222 | required: ['PORT'], 223 | properties: { 224 | PORT: { 225 | type: 'integer' 226 | } 227 | } 228 | }, 229 | data: [], 230 | isOk: false, 231 | errorMessage: 'opts/data must NOT have fewer than 1 items, opts/data must be object, opts/data must match exactly one schema in oneOf' 232 | }, 233 | { 234 | name: 'simple object - ok - with separator', 235 | schema: { 236 | type: 'object', 237 | required: ['ALLOWED_HOSTS'], 238 | properties: { 239 | ALLOWED_HOSTS: { 240 | type: 'string', 241 | separator: ',' 242 | } 243 | } 244 | }, 245 | data: { 246 | ALLOWED_HOSTS: '127.0.0.1,0.0.0.0' 247 | }, 248 | isOk: true, 249 | confExpected: { 250 | ALLOWED_HOSTS: ['127.0.0.1', '0.0.0.0'] 251 | } 252 | }, 253 | { 254 | name: 'simple object - ok - with separator - only one value', 255 | schema: { 256 | type: 'object', 257 | required: ['ALLOWED_HOSTS'], 258 | properties: { 259 | ALLOWED_HOSTS: { 260 | type: 'string', 261 | separator: ',' 262 | } 263 | } 264 | }, 265 | data: { 266 | ALLOWED_HOSTS: '127.0.0.1' 267 | }, 268 | isOk: true, 269 | confExpected: { 270 | ALLOWED_HOSTS: ['127.0.0.1'] 271 | } 272 | }, 273 | { 274 | name: 'simple object - ok - with separator - no values', 275 | schema: { 276 | type: 'object', 277 | required: ['ALLOWED_HOSTS'], 278 | properties: { 279 | ALLOWED_HOSTS: { 280 | type: 'string', 281 | separator: ',' 282 | } 283 | } 284 | }, 285 | data: { 286 | ALLOWED_HOSTS: '' 287 | }, 288 | isOk: true, 289 | confExpected: { 290 | ALLOWED_HOSTS: [] 291 | } 292 | }, 293 | { 294 | name: 'simple object - KO - with separator', 295 | schema: { 296 | type: 'object', 297 | required: ['ALLOWED_HOSTS'], 298 | properties: { 299 | ALLOWED_HOSTS: { 300 | type: 'string', 301 | separator: ',' 302 | } 303 | } 304 | }, 305 | data: {}, 306 | isOk: false, 307 | errorMessage: 'env must have required property \'ALLOWED_HOSTS\'' 308 | }, 309 | { 310 | name: 'simple object - KO - multiple required properties', 311 | schema: { 312 | type: 'object', 313 | required: ['A', 'B', 'C'], 314 | properties: { 315 | A: { type: 'string' }, 316 | B: { type: 'string' }, 317 | C: { type: 'string' }, 318 | D: { type: 'string' } 319 | } 320 | }, 321 | data: {}, 322 | isOk: false, 323 | errorMessage: 'env must have required property \'A\', env must have required property \'B\', env must have required property \'C\'' 324 | } 325 | ] 326 | 327 | tests.forEach(function (testConf) { 328 | test(testConf.name, t => { 329 | const options = { 330 | schema: testConf.schema, 331 | data: testConf.data, 332 | dotenv: testConf.dotenv, 333 | dotenvConfig: testConf.dotenvConfig 334 | } 335 | 336 | makeTest(t, options, testConf.isOk, testConf.confExpected, testConf.errorMessage) 337 | }) 338 | }) 339 | -------------------------------------------------------------------------------- /test/custom-ajv.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('node:test') 4 | const Ajv = require('ajv') 5 | const makeTest = require('./make-test') 6 | const { join } = require('node:path') 7 | 8 | process.env.VALUE_FROM_ENV = 'pippo' 9 | 10 | const tests = [ 11 | { 12 | name: 'empty ok', 13 | schema: { type: 'object' }, 14 | data: { }, 15 | isOk: true, 16 | confExpected: {} 17 | }, 18 | { 19 | name: 'simple object - ok', 20 | schema: { 21 | type: 'object', 22 | properties: { 23 | PORT: { 24 | type: 'string' 25 | } 26 | } 27 | }, 28 | data: { 29 | PORT: '44' 30 | }, 31 | isOk: true, 32 | confExpected: { 33 | PORT: '44' 34 | } 35 | }, 36 | { 37 | name: 'simple object - ok - coerce value', 38 | schema: { 39 | type: 'object', 40 | properties: { 41 | PORT: { 42 | type: 'integer' 43 | } 44 | } 45 | }, 46 | data: { 47 | PORT: '44' 48 | }, 49 | isOk: true, 50 | confExpected: { 51 | PORT: 44 52 | } 53 | }, 54 | { 55 | name: 'simple object - ok - remove additional properties', 56 | schema: { 57 | type: 'object', 58 | properties: { 59 | PORT: { 60 | type: 'integer' 61 | } 62 | } 63 | }, 64 | data: { 65 | PORT: '44', 66 | ANOTHER_PORT: '55' 67 | }, 68 | isOk: true, 69 | confExpected: { 70 | PORT: 44 71 | } 72 | }, 73 | { 74 | name: 'simple object - ok - use default', 75 | schema: { 76 | type: 'object', 77 | properties: { 78 | PORT: { 79 | type: 'integer', 80 | default: 5555 81 | } 82 | } 83 | }, 84 | data: { }, 85 | isOk: true, 86 | confExpected: { 87 | PORT: 5555 88 | } 89 | }, 90 | { 91 | name: 'simple object - ok - required + default', 92 | schema: { 93 | type: 'object', 94 | required: ['PORT'], 95 | properties: { 96 | PORT: { 97 | type: 'integer', 98 | default: 6666 99 | } 100 | } 101 | }, 102 | data: { }, 103 | isOk: true, 104 | confExpected: { 105 | PORT: 6666 106 | } 107 | }, 108 | { 109 | name: 'simple object - ok - allow array', 110 | schema: { 111 | type: 'object', 112 | required: ['PORT'], 113 | properties: { 114 | PORT: { 115 | type: 'integer', 116 | default: 6666 117 | } 118 | } 119 | }, 120 | data: [{ }], 121 | isOk: true, 122 | confExpected: { 123 | PORT: 6666 124 | } 125 | }, 126 | { 127 | name: 'simple object - ok - merge multiple object + env', 128 | schema: { 129 | type: 'object', 130 | required: ['PORT', 'MONGODB_URL'], 131 | properties: { 132 | PORT: { 133 | type: 'integer', 134 | default: 6666 135 | }, 136 | MONGODB_URL: { 137 | type: 'string' 138 | }, 139 | VALUE_FROM_ENV: { 140 | type: 'string' 141 | } 142 | } 143 | }, 144 | data: [{ PORT: 3333 }, { MONGODB_URL: 'mongodb://localhost/pippo' }], 145 | isOk: true, 146 | confExpected: { 147 | PORT: 3333, 148 | MONGODB_URL: 'mongodb://localhost/pippo', 149 | VALUE_FROM_ENV: 'pippo' 150 | } 151 | }, 152 | { 153 | name: 'simple object - ok - load only from env', 154 | schema: { 155 | type: 'object', 156 | required: ['VALUE_FROM_ENV'], 157 | properties: { 158 | VALUE_FROM_ENV: { 159 | type: 'string' 160 | } 161 | } 162 | }, 163 | data: undefined, 164 | isOk: true, 165 | confExpected: { 166 | VALUE_FROM_ENV: 'pippo' 167 | } 168 | }, 169 | { 170 | name: 'simple object - ok - opts override environment', 171 | schema: { 172 | type: 'object', 173 | required: ['VALUE_FROM_ENV'], 174 | properties: { 175 | VALUE_FROM_ENV: { 176 | type: 'string' 177 | } 178 | } 179 | }, 180 | data: { VALUE_FROM_ENV: 'pluto' }, 181 | isOk: true, 182 | confExpected: { 183 | VALUE_FROM_ENV: 'pluto' 184 | } 185 | }, 186 | { 187 | name: 'simple object - ok - load only from .env', 188 | schema: { 189 | type: 'object', 190 | required: ['VALUE_FROM_DOTENV'], 191 | properties: { 192 | VALUE_FROM_DOTENV: { 193 | type: 'string' 194 | } 195 | } 196 | }, 197 | data: undefined, 198 | isOk: true, 199 | dotenv: { path: join(__dirname, '.env') }, 200 | confExpected: { 201 | VALUE_FROM_DOTENV: 'look ma' 202 | } 203 | }, 204 | { 205 | name: 'simple object - KO', 206 | schema: { 207 | type: 'object', 208 | required: ['PORT'], 209 | properties: { 210 | PORT: { 211 | type: 'integer' 212 | } 213 | } 214 | }, 215 | data: { }, 216 | isOk: false, 217 | errorMessage: 'env must have required property \'PORT\'' 218 | }, 219 | { 220 | name: 'simple object - invalid data', 221 | schema: { 222 | type: 'object', 223 | required: ['PORT'], 224 | properties: { 225 | PORT: { 226 | type: 'integer' 227 | } 228 | } 229 | }, 230 | data: [], 231 | isOk: false, 232 | errorMessage: 'opts/data must NOT have fewer than 1 items, opts/data must be object, opts/data must match exactly one schema in oneOf' 233 | } 234 | ] 235 | 236 | const ajv = new Ajv({ 237 | allErrors: true, 238 | removeAdditional: true, 239 | useDefaults: true, 240 | coerceTypes: true, 241 | allowUnionTypes: true 242 | }) 243 | 244 | tests.forEach(function (testConf) { 245 | test(testConf.name, t => { 246 | const options = { 247 | schema: testConf.schema, 248 | data: testConf.data, 249 | dotenv: testConf.dotenv, 250 | dotenvConfig: testConf.dotenvConfig, 251 | ajv 252 | } 253 | 254 | makeTest(t, options, testConf.isOk, testConf.confExpected, testConf.errorMessage) 255 | }) 256 | }) 257 | 258 | const noCoercionTest = { 259 | name: 'simple object - not ok - should NOT coerce value', 260 | schema: { 261 | type: 'object', 262 | properties: { 263 | PORT: { 264 | type: 'integer' 265 | } 266 | } 267 | }, 268 | data: { 269 | PORT: '44' 270 | }, 271 | isOk: false, 272 | errorMessage: 'env/PORT must be integer', 273 | confExpected: { 274 | PORT: 44 275 | } 276 | } 277 | 278 | const strictValidator = new Ajv({ 279 | allErrors: true, 280 | removeAdditional: true, 281 | useDefaults: true, 282 | coerceTypes: false, 283 | allowUnionTypes: true 284 | }); 285 | 286 | [noCoercionTest].forEach(function (testConf) { 287 | test(testConf.name, t => { 288 | const options = { 289 | schema: testConf.schema, 290 | data: testConf.data, 291 | dotenv: testConf.dotenv, 292 | dotenvConfig: testConf.dotenvConfig, 293 | ajv: strictValidator 294 | } 295 | 296 | makeTest(t, options, testConf.isOk, testConf.confExpected, testConf.errorMessage) 297 | }) 298 | }) 299 | 300 | test('ajv enhancement', async t => { 301 | t.plan(2) 302 | const testCase = { 303 | schema: { 304 | type: 'object', 305 | required: ['MONGODB_URL'], 306 | properties: { 307 | MONGODB_URL: { 308 | type: 'string', 309 | format: 'uri' 310 | } 311 | } 312 | }, 313 | data: [{ PORT: 3333 }, { MONGODB_URL: 'mongodb://localhost/pippo' }], 314 | isOk: true, 315 | confExpected: { 316 | MONGODB_URL: 'mongodb://localhost/pippo' 317 | } 318 | } 319 | 320 | await t.test('return', async t => { 321 | const options = { 322 | schema: testCase.schema, 323 | data: testCase.data, 324 | ajv: { 325 | customOptions (ajvInstance) { 326 | require('ajv-formats')(ajvInstance) 327 | return ajvInstance 328 | } 329 | } 330 | } 331 | makeTest(t, options, testCase.isOk, testCase.confExpected) 332 | }) 333 | 334 | await t.test('no return', async t => { 335 | const options = { 336 | schema: testCase.schema, 337 | data: testCase.data, 338 | ajv: { 339 | customOptions (_ajvInstance) { 340 | // do nothing 341 | } 342 | } 343 | } 344 | makeTest(t, options, false, undefined, 'customOptions function must return an instance of Ajv') 345 | }) 346 | }) 347 | -------------------------------------------------------------------------------- /test/expand.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('node:test') 4 | const makeTest = require('./make-test') 5 | const { join } = require('node:path') 6 | 7 | process.env.K8S_NAMESPACE = 'pippo' 8 | process.env.K8S_CLUSTERID = 'pluto' 9 | process.env.URL = 'https://prefix.$K8S_NAMESPACE.$K8S_CLUSTERID.my.domain.com' 10 | process.env.PASSWORD = 'password' 11 | 12 | const tests = [ 13 | { 14 | name: 'simple object - ok - expandEnv', 15 | schema: { 16 | type: 'object', 17 | properties: { 18 | URL: { 19 | type: 'string' 20 | }, 21 | K8S_NAMESPACE: { 22 | type: 'string' 23 | } 24 | } 25 | }, 26 | expandEnv: true, 27 | isOk: true, 28 | confExpected: { 29 | URL: 'https://prefix.pippo.pluto.my.domain.com', 30 | K8S_NAMESPACE: 'pippo' 31 | } 32 | }, 33 | { 34 | name: 'simple object - ok - expandEnv use dotenv', 35 | schema: { 36 | type: 'object', 37 | properties: { 38 | EXPANDED_VALUE_FROM_DOTENV: { 39 | type: 'string' 40 | } 41 | } 42 | }, 43 | expandEnv: true, 44 | isOk: true, 45 | dotenv: { path: join(__dirname, '.env') }, 46 | confExpected: { 47 | EXPANDED_VALUE_FROM_DOTENV: 'the password is password!' 48 | } 49 | }, 50 | { 51 | name: 'simple object - ok - expandEnv works when passed an arbitrary new object based on process.env as data', 52 | schema: { 53 | type: 'object', 54 | properties: { 55 | URL: { 56 | type: 'string' 57 | }, 58 | K8S_NAMESPACE: { 59 | type: 'string' 60 | } 61 | } 62 | }, 63 | expandEnv: true, 64 | isOk: true, 65 | data: { 66 | ...process.env, 67 | K8S_NAMESPACE: 'hello' 68 | }, 69 | confExpected: { 70 | URL: 'https://prefix.hello.pluto.my.domain.com', 71 | K8S_NAMESPACE: 'hello' 72 | } 73 | } 74 | ] 75 | 76 | tests.forEach(function (testConf) { 77 | test(testConf.name, t => { 78 | const options = { 79 | schema: testConf.schema, 80 | data: testConf.data, 81 | dotenv: testConf.dotenv, 82 | dotenvConfig: testConf.dotenvConfig, 83 | expandEnv: testConf.expandEnv 84 | } 85 | 86 | makeTest(t, options, testConf.isOk, testConf.confExpected, testConf.errorMessage) 87 | }) 88 | }) 89 | -------------------------------------------------------------------------------- /test/fluent-schema.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('node:test') 4 | 5 | if (parseInt(process.versions.node.split('.', 1)[0]) <= 8) { 6 | test.skip('not supported') 7 | } else { 8 | run() 9 | } 10 | 11 | function run () { 12 | const S = require('fluent-json-schema') 13 | const makeTest = require('./make-test') 14 | 15 | test('simple object - fluent-json-schema', t => { 16 | const options = { 17 | schema: S.object().prop('PORT', S.string()), 18 | data: { 19 | PORT: '44' 20 | } 21 | } 22 | 23 | makeTest(t, options, true, { 24 | PORT: '44' 25 | }) 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /test/make-test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const envSchema = require('../index') 4 | 5 | function makeTest (t, options, isOk, confExpected, errorMessage) { 6 | t.plan(1) 7 | options = Object.assign({ confKey: 'config' }, options) 8 | 9 | try { 10 | const conf = envSchema(options) 11 | t.assert.deepStrictEqual(conf, confExpected) 12 | } catch (err) { 13 | if (isOk) { 14 | t.assert.fail(err) 15 | return 16 | } 17 | t.assert.strictEqual(err.message, errorMessage) 18 | } 19 | } 20 | 21 | module.exports = makeTest 22 | -------------------------------------------------------------------------------- /test/no-global.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('node:test') 4 | const envSchema = require('../index') 5 | 6 | test('no globals', t => { 7 | t.plan(2) 8 | 9 | const options = { 10 | confKey: 'secrets', 11 | data: { 12 | MONGO_URL: 'good' 13 | }, 14 | schema: { 15 | $id: 'schema:dotenv', 16 | type: 'object', 17 | required: ['MONGO_URL'], 18 | properties: { 19 | PORT: { 20 | type: 'integer', 21 | default: 3000 22 | }, 23 | MONGO_URL: { 24 | type: 'string' 25 | } 26 | } 27 | } 28 | } 29 | 30 | { 31 | const conf = envSchema(JSON.parse(JSON.stringify(options))) 32 | t.assert.deepStrictEqual(conf, { MONGO_URL: 'good', PORT: 3000 }) 33 | } 34 | { 35 | const conf = envSchema(JSON.parse(JSON.stringify(options))) 36 | t.assert.deepStrictEqual(conf, { MONGO_URL: 'good', PORT: 3000 }) 37 | } 38 | }) 39 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import Ajv, { KeywordDefinition, JSONSchemaType } from 'ajv' 2 | import { AnySchema } from 'ajv/dist/core' 3 | import { DotenvConfigOptions } from 'dotenv' 4 | 5 | type EnvSchema = typeof envSchema 6 | 7 | declare namespace envSchema { 8 | export type { JSONSchemaType } 9 | 10 | export type EnvSchemaData = { 11 | [key: string]: unknown; 12 | } 13 | 14 | export type EnvSchemaOpt = { 15 | schema?: JSONSchemaType | AnySchema; 16 | data?: [EnvSchemaData, ...EnvSchemaData[]] | EnvSchemaData; 17 | env?: boolean; 18 | dotenv?: boolean | DotenvConfigOptions; 19 | expandEnv?: boolean; 20 | ajv?: 21 | | Ajv 22 | | { 23 | customOptions(ajvInstance: Ajv): Ajv; 24 | }; 25 | } 26 | 27 | export const keywords: { 28 | separator: KeywordDefinition 29 | } 30 | 31 | export const envSchema: EnvSchema 32 | export { envSchema as default } 33 | } 34 | 35 | declare function envSchema (_opts?: envSchema.EnvSchemaOpt): T 36 | export = envSchema 37 | -------------------------------------------------------------------------------- /types/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectError, expectType } from 'tsd' 2 | import envSchema, { 3 | EnvSchemaData, 4 | EnvSchemaOpt, 5 | keywords, 6 | envSchema as envSchemaNamed, 7 | // eslint-disable-next-line import-x/no-named-default -- Testing default export 8 | default as envSchemaDefault, 9 | } from '..' 10 | import Ajv, { KeywordDefinition, JSONSchemaType } from 'ajv' 11 | import { Static, Type } from '@sinclair/typebox' 12 | 13 | interface EnvData { 14 | PORT: number; 15 | } 16 | 17 | const schemaWithType: JSONSchemaType = { 18 | type: 'object', 19 | required: ['PORT'], 20 | properties: { 21 | PORT: { 22 | type: 'number', 23 | default: 3000, 24 | }, 25 | }, 26 | } 27 | 28 | const schemaTypebox = Type.Object({ 29 | PORT: Type.Number({ default: 3000 }), 30 | }) 31 | 32 | type SchemaTypebox = Static 33 | 34 | const data = { 35 | foo: 'bar', 36 | } 37 | 38 | expectType(envSchema()) 39 | expectType(envSchemaNamed()) 40 | expectType(envSchemaDefault()) 41 | 42 | const emptyOpt: EnvSchemaOpt = {} 43 | expectType(emptyOpt) 44 | 45 | const optWithSchemaTypebox: EnvSchemaOpt = { 46 | schema: schemaTypebox, 47 | } 48 | expectType(optWithSchemaTypebox) 49 | 50 | const optWithSchemaWithType: EnvSchemaOpt = { 51 | schema: schemaWithType, 52 | } 53 | expectType>(optWithSchemaWithType) 54 | 55 | const optWithData: EnvSchemaOpt = { 56 | data, 57 | } 58 | expectType(optWithData) 59 | 60 | expectError({ 61 | data: [], // min 1 item 62 | }) 63 | 64 | const optWithArrayData: EnvSchemaOpt = { 65 | data: [{}], 66 | } 67 | expectType(optWithArrayData) 68 | 69 | const optWithMultipleItemArrayData: EnvSchemaOpt = { 70 | data: [{}, {}], 71 | } 72 | expectType(optWithMultipleItemArrayData) 73 | 74 | const optWithDotEnvBoolean: EnvSchemaOpt = { 75 | dotenv: true, 76 | } 77 | expectType(optWithDotEnvBoolean) 78 | 79 | const optWithDotEnvOpt: EnvSchemaOpt = { 80 | dotenv: {}, 81 | } 82 | expectType(optWithDotEnvOpt) 83 | 84 | const optWithEnvExpand: EnvSchemaOpt = { 85 | expandEnv: true, 86 | } 87 | expectType(optWithEnvExpand) 88 | 89 | const optWithAjvInstance: EnvSchemaOpt = { 90 | ajv: new Ajv(), 91 | } 92 | expectType(optWithAjvInstance) 93 | expectType(envSchema.keywords.separator) 94 | 95 | const optWithAjvCustomOptions: EnvSchemaOpt = { 96 | ajv: { 97 | customOptions (_ajvInstance: Ajv): Ajv { 98 | return new Ajv() 99 | }, 100 | }, 101 | } 102 | expectType(optWithAjvCustomOptions) 103 | expectError({ 104 | ajv: { 105 | customOptions (_ajvInstance: Ajv) {}, 106 | }, 107 | }) 108 | 109 | const envSchemaWithType = envSchema({ schema: schemaWithType }) 110 | expectType(envSchemaWithType) 111 | 112 | const envSchemaTypebox = envSchema({ schema: schemaTypebox }) 113 | expectType(envSchemaTypebox) 114 | 115 | expectType(keywords.separator) 116 | expectType(envSchema.keywords.separator) 117 | --------------------------------------------------------------------------------