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
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,54 @@ const { config } = defineConfig(
);
```

### Environment variable fallback

You can specify multiple environment variable names for a single configuration field by providing an array of strings. Figue will use the first environment variable that is found in your environment sources.

This feature is particularly useful when:
- Supporting multiple deployment environments with different environment variable naming conventions
- Migrating from legacy environment variable names while maintaining backward compatibility
- Providing fallback options for missing environment variables

```typescript
const { config } = defineConfig(
{
port: {
doc: 'Application port to listen',
schema: z.coerce.number(),
default: 3000,
// Will use PORT if available, otherwise APP_PORT, otherwise SERVER_PORT
env: ['PORT', 'APP_PORT', 'SERVER_PORT'],
},
database: {
host: {
doc: 'Database host',
schema: z.string(),
default: 'localhost',
// Useful for supporting legacy environment variable names
env: ['DATABASE_HOST', 'DB_HOST', 'LEGACY_DB_URL'],
},
},
workerId: {
doc: 'Worker identifier',
schema: z.string().optional(),
// Or using some plateform specific environment variable
env: ['WORKER_ID', 'HEROKU_DYNO_ID', 'RENDER_INSTANCE_ID'],
},
},
{
envSource: process.env,
},
);
```



Some caveats:
- If none of the specified environment variables are found, Figue will fall back to the default as expected when no `env` key is present.
- If a variable is found but its value is nullish or falsy (like an empty string, or undefined), Figue will still consider it as set and use that value. If you want to ignore such values, you should handle that in your schema validation.
- Ensure that the order of environment variables in the array reflects their priority, as Figue will use the first one it finds.

### Get defaults

You can use the `getDefaults` key of the second argument of `defineConfig` to specify a function that will be called to get some defaults:
Expand Down
57 changes: 57 additions & 0 deletions src/figue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,63 @@ describe('figue tests', () => {
});
});

describe('the env option can be an array of env variables', () => {
test('the first env variable found is used', () => {
const { config } = defineConfig({
value: {
schema: z.any(),
env: ['NOT_EXISTING', 'BAR', 'ALSO_NOT_EXISTING', 'FOO'],
default: 'default',
},
}, {
envSource: {
FOO: 'foo',
BAR: 'bar',
},
});

expect(config).toEqual({
value: 'bar',
});
});

test('if an env variable exists but is empty, it is used', () => {
const { config } = defineConfig({
value: {
schema: z.any(),
env: ['NOT_EXISTING', 'BAR', 'ALSO_NOT_EXISTING', 'FOO'],
},
}, {
envSource: {
FOO: 'foo',
BAR: undefined,
},
});

expect(config).toEqual({
value: undefined,
});
});

test('if an env variable exists but is falsy, it is used', () => {
const { config } = defineConfig({
value: {
schema: z.any(),
env: ['NOT_EXISTING', 'BAR', 'ALSO_NOT_EXISTING', 'FOO'],
},
}, {
envSource: {
FOO: 'foo',
BAR: false,
},
});

expect(config).toEqual({
value: false,
});
});
});

describe('any validation libraries can be used', () => {
test('you can merge some schema from zod and some from valibot', () => {
const { config } = defineConfig({
Expand Down
12 changes: 10 additions & 2 deletions src/figue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,16 @@ function buildEnvConfig({ configDefinition, env }: { configDefinition: ConfigDef
return undefined;
}

const value = env[envKey as string];
return value;
const envKeys = castArray(envKey);
Comment thread
CorentinTh marked this conversation as resolved.

// Find the first environment variable that is set
const key = envKeys.find(key => key in env);

if (key === undefined) {
return undefined;
}

return env[key];
} else {
return buildEnvConfig({ configDefinition: config, env });
}
Expand Down
2 changes: 1 addition & 1 deletion src/figue.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Expand } from './types';

export type ConfigDefinitionElement<T extends StandardSchemaV1 = StandardSchemaV1> = {
schema: T;
env?: string;
env?: string | string[];
doc?: string;
default?: StandardSchemaV1.InferOutput<T>;
};
Expand Down