Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 27 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Popular options include:
### Type-Safe Validation of Nested Schema

```typescript
import { parseEnv, envvar, EnvaseError } from 'envase';
import { parseEnv, envvar } from 'envase';
import { z } from 'zod';

const config = parseEnv(process.env, {
Expand All @@ -49,29 +49,34 @@ const config = parseEnv(process.env, {
},
},
db: {
host: envvar('DB_HOST', z.string().min(1)),
host: envvar('DB_HOST', z.string().min(1).default('localhost')),
},
apiKey: envvar('API_KEY', z.string().min(32)),
apiKey: envvar('API_KEY', z.string().min(32).optional()),
});
// config.app.listen.port -> number
// config.db.host -> string
// config.apiKey -> string
// config.apiKey -> string | undefined
```

### Environment Detection

```typescript
const config = parseEnv(process.env, {});
// config.isProduction -> boolean
// config.isDevelopment -> boolean
// config.isTest -> boolean
import { detectNodeEnv } from 'envase';

const nodeEnv = detectNodeEnv(process.env);
// nodeEnv.isProduction -> boolean
// nodeEnv.isTest -> boolean
// nodeEnv.isDevelopment -> boolean
```

These flags are inferred from the `NODE_ENV` value (i.e. 'production', 'test', or 'development').

### Detailed error reporting

```typescript
import { parseEnv, envvar, EnvaseError } from 'envase';
import { z } from 'zod';

try {
parseEnv(process.env, {
apiKey: envvar('API_KEY', z.string().min(32)),
Expand Down Expand Up @@ -111,6 +116,9 @@ try {
### Type Inference

```typescript
import { envvar, type InferEnv } from 'envase';
import { z } from 'zod';

const envSchema = {
apiKey: envvar('API_KEY', z.string().min(32)),
db: {
Expand All @@ -135,8 +143,17 @@ This helps pair the raw env name with the shape you expect it to conform to.

`parseEnv(env: Record<string, string | undefined>, envSchema: T)`

Validates envvars against the schema and returns a typed configuration object
along with flags: `isProduction`, `isTest`, `isDevelopment`.
Validates envvars against the schema and returns a typed configuration object.

### `detectNodeEnv`

`detectNodeEnv(env: Record<string, string | undefined>)`

Standalone utility that reads NODE_ENV and returns an object with the following boolean flags:

- isProduction: true if NODE_ENV === 'production'
- isTest: true if NODE_ENV === 'test'
- isDevelopment: true if NODE_ENV === 'development'

### `EnvaseError`

Expand Down
44 changes: 35 additions & 9 deletions src/core.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,43 @@
import * as v from 'valibot';
import { describe, expect, it } from 'vitest';
import { z } from 'zod';
import { envvar, parseEnv } from './core.ts';
import { detectNodeEnv, envvar, parseEnv } from './core.ts';

describe('core', () => {
describe('detectNodeEnv', () => {
it('returns true for isProduction flag', () => {
const config = detectNodeEnv({ NODE_ENV: 'production' });

expect(config.isProduction).toBe(true);
expect(config.isTest).toBe(false);
expect(config.isDevelopment).toBe(false);
});

it('returns true for isTest flag', () => {
const config = detectNodeEnv({ NODE_ENV: 'test' });

expect(config.isProduction).toBe(false);
expect(config.isTest).toBe(true);
expect(config.isDevelopment).toBe(false);
});

it('returns true for isDevelopment flag', () => {
const config = detectNodeEnv({ NODE_ENV: 'development' });

expect(config.isProduction).toBe(false);
expect(config.isTest).toBe(false);
expect(config.isDevelopment).toBe(true);
});

it('returns all falsy flags if NODE_ENV is missing', () => {
const config = detectNodeEnv({});

expect(config.isProduction).toBe(false);
expect(config.isTest).toBe(false);
expect(config.isDevelopment).toBe(false);
});
});

describe('envvar', () => {
it('creates a tuple with environment variable name and Standard Schema validator', () => {
const envvarName = 'API_KEY';
Expand All @@ -24,14 +58,6 @@ describe('core', () => {
EMPTY: '',
};

it('sets environment flags from NODE_ENV', () => {
const config = parseEnv(mockEnv, {});

expect(config.isTest).toBe(true);
expect(config.isProduction).toBe(false);
expect(config.isDevelopment).toBe(false);
});

describe('using zod', () => {
it('parses flat config with Zod validators', () => {
const config = parseEnv(mockEnv, {
Expand Down
26 changes: 16 additions & 10 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,22 @@ import type {
EnvSchema,
EnvvarEntry,
EnvvarValidationIssue,
ParseEnvOutput,
InferEnv,
NodeEnvInfo,
} from './types.ts';

export const detectNodeEnv = (
env: Record<string, string | undefined>,
): NodeEnvInfo => {
const nodeEnv = env.NODE_ENV;

return {
isProduction: nodeEnv === 'production',
isTest: nodeEnv === 'test',
isDevelopment: nodeEnv === 'development',
};
};

export const envvar = <T extends StandardSchemaV1>(
name: string,
schema: T,
Expand All @@ -15,7 +28,7 @@ export const envvar = <T extends StandardSchemaV1>(
export const parseEnv = <T extends EnvSchema>(
env: Record<string, string | undefined>,
envSchema: T,
): ParseEnvOutput<T> => {
): InferEnv<T> => {
const envvarValidationIssues: EnvvarValidationIssue[] = [];

// biome-ignore lint/suspicious/noExplicitAny: Explicit 'any' is required due to nature of recursive processing
Expand Down Expand Up @@ -61,12 +74,5 @@ export const parseEnv = <T extends EnvSchema>(
throw new EnvaseError(envvarValidationIssues);
}

const nodeEnv = env.NODE_ENV;

return {
...config,
isProduction: nodeEnv === 'production',
isTest: nodeEnv === 'test',
isDevelopment: nodeEnv === 'development',
};
return config;
};
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { envvar, parseEnv } from './core.ts';
export { detectNodeEnv, envvar, parseEnv } from './core.ts';
export { EnvaseError } from './errors/envase-error.ts';
export type { InferEnv } from './types.ts';
16 changes: 6 additions & 10 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import type { SimplifyDeep } from 'type-fest';
import type { StandardSchemaV1 } from './standard-schema.ts';

export type NodeEnvInfo = {
isProduction: boolean;
isTest: boolean;
isDevelopment: boolean;
};

export type EnvvarEntry<T extends StandardSchemaV1> = [string, T];

export type EnvSchema = {
Expand All @@ -17,16 +23,6 @@ type RecursiveInferEnv<T extends EnvSchema> = {

export type InferEnv<T extends EnvSchema> = SimplifyDeep<RecursiveInferEnv<T>>;

export type NodeEnvInfo = {
isProduction: boolean;
isTest: boolean;
isDevelopment: boolean;
};

export type ParseEnvOutput<T extends EnvSchema> = SimplifyDeep<
RecursiveInferEnv<T> & NodeEnvInfo
>;

export type EnvvarValidationIssue = {
name: string;
value?: string;
Expand Down