From 100cf4c5a97c3d69b11b810436f4bf2d722f2b9a Mon Sep 17 00:00:00 2001 From: Maxim Molochkov Date: Sun, 25 Sep 2022 17:12:32 +0400 Subject: [PATCH 1/2] Use more strict type for schema --- README.md | 24 ++++++++++-- types/index.d.ts | 27 +++++++------ types/index.test-d.ts | 89 ++++++++++++++++++++++++++----------------- 3 files changed, 92 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 71d697c..fa085ad 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ const config = envSchema({ schema: schema, data: data, // optional, default: process.env dotenv: true // load .env if it is there, default: false -}) +}) // config.data => ['127.0.0.1', '0.0.0.0'] ``` @@ -198,13 +198,13 @@ console.log(config) You can specify the type of your `config`: ```ts -import envSchema from 'env-schema'; +import { envSchema, JSONSchemaType } from 'env-schema'; interface Env { PORT: number; } -const schema = { +const schema: JSONSchemaType = { type: 'object', required: [ 'PORT' ], properties: { @@ -215,9 +215,27 @@ const schema = { } } +const config = envSchema({ + schema, +}) + +/* Or + +const schema = { + type: 'object', + required: [ 'PORT' ], + properties: { + PORT: { + type: 'number', + default: 3000 + } + } +} as const + const config = envSchema({ schema, }) +*/ ``` If no type is specified the `config` will have the `EnvSchemaData` type. diff --git a/types/index.d.ts b/types/index.d.ts index f98e005..b574d0b 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,27 +1,32 @@ -import Ajv, { KeywordDefinition } from "ajv"; -import { DotenvConfigOptions } from "dotenv"; +import Ajv, { KeywordDefinition, JSONSchemaType } from 'ajv'; +import { DotenvConfigOptions } from 'dotenv'; +import { ObjectSchema } from 'fluent-json-schema'; + +export type { JSONSchemaType }; export type EnvSchemaData = { [key: string]: unknown; }; -export type EnvSchemaOpt = { - schema?: object; +export type EnvSchemaOpt = { + schema?: JSONSchemaType | ObjectSchema; data?: [EnvSchemaData, ...EnvSchemaData[]] | EnvSchemaData; env?: boolean; dotenv?: boolean | DotenvConfigOptions; - expandEnv?: boolean - ajv?: Ajv | { - customOptions(ajvInstance: Ajv): Ajv; - }; + expandEnv?: boolean; + ajv?: + | Ajv + | { + customOptions(ajvInstance: Ajv): Ajv; + }; }; declare const loadAndValidateEnvironment: { - (_opts?: EnvSchemaOpt): T; + (_opts?: EnvSchemaOpt): T; keywords: { separator: KeywordDefinition; - } -} + }; +}; export default loadAndValidateEnvironment; export { loadAndValidateEnvironment as envSchema }; diff --git a/types/index.test-d.ts b/types/index.test-d.ts index 325c3a2..6aa02f4 100644 --- a/types/index.test-d.ts +++ b/types/index.test-d.ts @@ -1,19 +1,40 @@ -import { expectError, expectType } from "tsd"; -import envSchema, { EnvSchemaData, EnvSchemaOpt, envSchema as envSchemaNamed, default as envSchemaDefault } from ".."; -import Ajv, { KeywordDefinition } from 'ajv' +import { expectError, expectType } from 'tsd'; +import envSchema, { + EnvSchemaData, + EnvSchemaOpt, + envSchema as envSchemaNamed, + default as envSchemaDefault, +} from '..'; +import Ajv, { KeywordDefinition, JSONSchemaType } from 'ajv'; -const schema = { - type: "object", - required: ["PORT"], +interface EnvData { + PORT: number; +} + +const schemaAsConst = { + type: 'object', + required: ['PORT'], + properties: { + PORT: { + type: 'number', + default: 3000, + }, + }, +} as const; + +const schemaWithType: JSONSchemaType = { + type: 'object', + required: ['PORT'], properties: { PORT: { - type: "string", + type: 'number', default: 3000, }, }, }; + const data = { - foo: "bar", + foo: 'bar', }; expectType(envSchema()); @@ -23,10 +44,15 @@ expectType(envSchemaDefault()); const emptyOpt: EnvSchemaOpt = {}; expectType(emptyOpt); -const optWithSchema: EnvSchemaOpt = { - schema, +const optWithSchemaAsConst: EnvSchemaOpt = { + schema: schemaAsConst, }; -expectType(optWithSchema); +expectType>(optWithSchemaAsConst); + +const optWithSchemaWithType: EnvSchemaOpt = { + schema: schemaWithType, +}; +expectType>(optWithSchemaWithType); const optWithData: EnvSchemaOpt = { data, @@ -58,37 +84,32 @@ const optWithDotEnvOpt: EnvSchemaOpt = { expectType(optWithDotEnvOpt); const optWithEnvExpand: EnvSchemaOpt = { - expandEnv: true -} + expandEnv: true, +}; expectType(optWithEnvExpand); const optWithAjvInstance: EnvSchemaOpt = { - ajv: new Ajv() + ajv: new Ajv(), }; -expectType(optWithAjvInstance) -expectType(envSchema.keywords.separator) - +expectType(optWithAjvInstance); +expectType(envSchema.keywords.separator); const optWithAjvCustomOptions: EnvSchemaOpt = { - ajv: { - customOptions(ajvInstance: Ajv): Ajv { - return new Ajv(); - } - } + ajv: { + customOptions(ajvInstance: Ajv): Ajv { + return new Ajv(); + }, + }, }; -expectType(optWithAjvCustomOptions) +expectType(optWithAjvCustomOptions); expectError({ - ajv: { - customOptions(ajvInstance: Ajv) { - } - } + ajv: { + customOptions(ajvInstance: Ajv) {}, + }, }); -const envSchemaDefaultsToEnvSchemaData = envSchema({ schema: schema }); -expectType(envSchemaDefaultsToEnvSchemaData) +const envSchemaWithType = envSchema({ schema: schemaWithType }); +expectType(envSchemaWithType); -interface EnvData { - PORT: string -} -const envSchemaAllowsToSpecifyType = envSchema({ schema }); -expectType(envSchemaAllowsToSpecifyType) +const envSchemaAsConst = envSchema({ schema: schemaAsConst }); +expectType(envSchemaAsConst); From a602781deff13acbdb35206db90f12661e0d9618 Mon Sep 17 00:00:00 2001 From: Maxim Molochkov Date: Mon, 26 Sep 2022 21:44:49 +0400 Subject: [PATCH 2/2] Add type support for third-party json-schema libraries --- README.md | 31 +++++++++++++++---------------- package.json | 1 + types/index.d.ts | 3 ++- types/index.test-d.ts | 28 ++++++++++++---------------- 4 files changed, 30 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index fa085ad..620e709 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ console.log(config) You can specify the type of your `config`: ```ts -import { envSchema, JSONSchemaType } from 'env-schema'; +import { envSchema, JSONSchemaType } from 'env-schema' interface Env { PORT: number; @@ -216,26 +216,25 @@ const schema: JSONSchemaType = { } const config = envSchema({ - schema, + schema }) +``` -/* Or +You can also use a `JSON Schema` library like `typebox`: -const schema = { - type: 'object', - required: [ 'PORT' ], - properties: { - PORT: { - type: 'number', - default: 3000 - } - } -} as const +```ts +import { envSchema } from 'env-schema' +import { Static, Type } from '@sinclair/typebox' + +const schema = Type.Object({ + PORT: Type.Number({ default: 3000 }) +}) + +type Schema = Static -const config = envSchema({ - schema, +const config = envSchema({ + schema }) -*/ ``` If no type is specified the `config` will have the `EnvSchemaData` type. diff --git a/package.json b/package.json index 6b02c7e..8557e17 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ }, "devDependencies": { "@fastify/pre-commit": "^2.0.2", + "@sinclair/typebox": "^0.24.43", "ajv-formats": "^2.1.1", "fluent-json-schema": "^3.0.0", "snazzy": "^9.0.0", diff --git a/types/index.d.ts b/types/index.d.ts index b574d0b..b7406f7 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,4 +1,5 @@ import Ajv, { KeywordDefinition, JSONSchemaType } from 'ajv'; +import { AnySchema } from 'ajv/dist/core'; import { DotenvConfigOptions } from 'dotenv'; import { ObjectSchema } from 'fluent-json-schema'; @@ -9,7 +10,7 @@ export type EnvSchemaData = { }; export type EnvSchemaOpt = { - schema?: JSONSchemaType | ObjectSchema; + schema?: JSONSchemaType | AnySchema | ObjectSchema; data?: [EnvSchemaData, ...EnvSchemaData[]] | EnvSchemaData; env?: boolean; dotenv?: boolean | DotenvConfigOptions; diff --git a/types/index.test-d.ts b/types/index.test-d.ts index 6aa02f4..d5979bf 100644 --- a/types/index.test-d.ts +++ b/types/index.test-d.ts @@ -6,22 +6,12 @@ import envSchema, { default as envSchemaDefault, } from '..'; import Ajv, { KeywordDefinition, JSONSchemaType } from 'ajv'; +import { Static, Type } from '@sinclair/typebox'; interface EnvData { PORT: number; } -const schemaAsConst = { - type: 'object', - required: ['PORT'], - properties: { - PORT: { - type: 'number', - default: 3000, - }, - }, -} as const; - const schemaWithType: JSONSchemaType = { type: 'object', required: ['PORT'], @@ -33,6 +23,12 @@ const schemaWithType: JSONSchemaType = { }, }; +const schemaTypebox = Type.Object({ + PORT: Type.Number({ default: 3000 }), +}); + +type SchemaTypebox = Static; + const data = { foo: 'bar', }; @@ -44,10 +40,10 @@ expectType(envSchemaDefault()); const emptyOpt: EnvSchemaOpt = {}; expectType(emptyOpt); -const optWithSchemaAsConst: EnvSchemaOpt = { - schema: schemaAsConst, +const optWithSchemaTypebox: EnvSchemaOpt = { + schema: schemaTypebox, }; -expectType>(optWithSchemaAsConst); +expectType(optWithSchemaTypebox); const optWithSchemaWithType: EnvSchemaOpt = { schema: schemaWithType, @@ -111,5 +107,5 @@ expectError({ const envSchemaWithType = envSchema({ schema: schemaWithType }); expectType(envSchemaWithType); -const envSchemaAsConst = envSchema({ schema: schemaAsConst }); -expectType(envSchemaAsConst); +const envSchemaTypebox = envSchema({ schema: schemaTypebox }); +expectType(envSchemaTypebox);