tydantic-settings 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +742 -0
- package/dist/core/computed.d.ts +16 -0
- package/dist/core/computed.d.ts.map +1 -0
- package/dist/core/computed.js +33 -0
- package/dist/core/computed.js.map +1 -0
- package/dist/core/defaults.d.ts +9 -0
- package/dist/core/defaults.d.ts.map +1 -0
- package/dist/core/defaults.js +39 -0
- package/dist/core/defaults.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +17 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/nested-bundles.d.ts +15 -0
- package/dist/core/nested-bundles.d.ts.map +1 -0
- package/dist/core/nested-bundles.js +68 -0
- package/dist/core/nested-bundles.js.map +1 -0
- package/dist/core/pipeline.d.ts +27 -0
- package/dist/core/pipeline.d.ts.map +1 -0
- package/dist/core/pipeline.js +137 -0
- package/dist/core/pipeline.js.map +1 -0
- package/dist/core/unflatten.d.ts +8 -0
- package/dist/core/unflatten.d.ts.map +1 -0
- package/dist/core/unflatten.js +25 -0
- package/dist/core/unflatten.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/resolvers/aws.d.ts +19 -0
- package/dist/resolvers/aws.d.ts.map +1 -0
- package/dist/resolvers/aws.js +107 -0
- package/dist/resolvers/aws.js.map +1 -0
- package/dist/resolvers/dotenv.d.ts +35 -0
- package/dist/resolvers/dotenv.d.ts.map +1 -0
- package/dist/resolvers/dotenv.js +43 -0
- package/dist/resolvers/dotenv.js.map +1 -0
- package/dist/resolvers/environment.d.ts +77 -0
- package/dist/resolvers/environment.d.ts.map +1 -0
- package/dist/resolvers/environment.js +109 -0
- package/dist/resolvers/environment.js.map +1 -0
- package/dist/resolvers/index.d.ts +5 -0
- package/dist/resolvers/index.d.ts.map +1 -0
- package/dist/resolvers/index.js +15 -0
- package/dist/resolvers/index.js.map +1 -0
- package/dist/settings.d.ts +211 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +162 -0
- package/dist/settings.js.map +1 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +15 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +44 -0
- package/dist/utils.js.map +1 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
# Tydantic Settings
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
A flexible, type-safe configuration management library for TypeScript applications, inspired by [Pydantic Settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/) and built on [TypeBox](https://github.com/sinclairzx81/typebox).
|
|
6
|
+
|
|
7
|
+
Define your configuration schema once and resolve values from multiple sources (environment variables, `.env` files, AWS Secrets Manager, and more) with a clear priority order.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- ✅ **Type-Safe**: Leverages TypeBox for schema definition and runtime validation
|
|
12
|
+
- ✅ **Multiple Sources**: Environment variables, `.env` files, AWS Secrets Manager, and custom resolvers
|
|
13
|
+
- ✅ **Priority-based Merging**: Resolvers are processed in order, allowing overrides
|
|
14
|
+
- ✅ **Nested Configuration**: Supports deeply nested objects via delimited keys (e.g., `DATABASE__HOST`)
|
|
15
|
+
- ✅ **Automatic Type Coercion**: Converts string values to proper types (`'5432'` → `5432`, `'true'` → `true`)
|
|
16
|
+
- ✅ **Case-Insensitive Matching**: Matches `DATABASE_HOST` to `databaseHost` automatically
|
|
17
|
+
- ✅ **Computed Properties**: Add derived fields like Pydantic's `@computed_field`
|
|
18
|
+
- ✅ **Extensible**: Create custom resolvers for any configuration source
|
|
19
|
+
|
|
20
|
+
## Table of Contents
|
|
21
|
+
|
|
22
|
+
- [Installation](#installation)
|
|
23
|
+
- [Quick Start](#quick-start)
|
|
24
|
+
- [Core Concepts](#core-concepts)
|
|
25
|
+
- [Configuration Schema](#configuration-schema)
|
|
26
|
+
- [Resolvers](#resolvers)
|
|
27
|
+
- [Nested Configuration](#nested-configuration)
|
|
28
|
+
- [Advanced Features](#advanced-features)
|
|
29
|
+
- [Computed Properties](#settings-with-computed-properties-recommended)
|
|
30
|
+
- [Automatic Nested Computed Properties](#automatic-nested-computed-properties)
|
|
31
|
+
- [Immutable Configuration](#immutable-configuration)
|
|
32
|
+
- [Type Coercion](#type-coercion)
|
|
33
|
+
- [Custom Resolvers](#custom-resolvers)
|
|
34
|
+
- [API Reference](#api-reference)
|
|
35
|
+
- [TypeScript Support](#typescript-support)
|
|
36
|
+
- [Error Handling](#error-handling)
|
|
37
|
+
- [Best Practices](#best-practices)
|
|
38
|
+
- [Examples](#examples)
|
|
39
|
+
- [Comparison with Pydantic Settings](#comparison-with-pydantic-settings)
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install tydantic-settings
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Optional: AWS Secrets Manager
|
|
48
|
+
|
|
49
|
+
To use the `fromAwsSecretsManager()` resolver, install the AWS SDK:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install @aws-sdk/client-secrets-manager
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This dependency is optional — if you only use `fromEnvironment()` or `fromDotenv()`, you don't need it.
|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
58
|
+
|
|
59
|
+
### 1. Define Your Schema with `Settings()`
|
|
60
|
+
|
|
61
|
+
The new `Settings()` function provides a clean, unified API for defining configuration schemas with optional computed properties.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { Settings } from 'tydantic-settings';
|
|
65
|
+
|
|
66
|
+
// Simple schema without computed properties
|
|
67
|
+
const SimpleConfig = Settings({
|
|
68
|
+
host: Settings.String({ default: 'localhost' }),
|
|
69
|
+
port: Settings.Number({ default: 3000 })
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Schema with computed properties
|
|
73
|
+
const DatabaseConfig = Settings(
|
|
74
|
+
{
|
|
75
|
+
host: Settings.String({ default: 'localhost' }),
|
|
76
|
+
port: Settings.Number({ default: 5432 }),
|
|
77
|
+
user: Settings.String({ default: 'postgres' }),
|
|
78
|
+
password: Settings.Optional(Settings.String()),
|
|
79
|
+
database: Settings.String({ default: 'myapp' })
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
// Computed properties - second argument
|
|
83
|
+
url: cfg => `postgresql://${cfg.user}:${cfg.password}@${cfg.host}:${cfg.port}/${cfg.database}`
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 2. Compose Configs with Automatic Nesting
|
|
89
|
+
|
|
90
|
+
When you nest a schema bundle, its computed properties come along automatically:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { Settings } from 'tydantic-settings';
|
|
94
|
+
import { DatabaseConfig } from './database-config'; // Your reusable config bundle
|
|
95
|
+
|
|
96
|
+
const AppConfig = Settings(
|
|
97
|
+
{
|
|
98
|
+
environment: Settings.String({ default: 'development' }),
|
|
99
|
+
database: DatabaseConfig // Pass the bundle - computed props auto-scoped!
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
isDev: cfg => cfg.environment === 'development',
|
|
103
|
+
isProduction: cfg => cfg.environment === 'production'
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 3. Create Configuration Singleton with `defineConfig` (Recommended)
|
|
109
|
+
|
|
110
|
+
The simplest way to create application configuration:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import {
|
|
114
|
+
Settings,
|
|
115
|
+
defineConfig,
|
|
116
|
+
fromEnvironment,
|
|
117
|
+
fromDotenv,
|
|
118
|
+
type InferConfigType
|
|
119
|
+
} from 'tydantic-settings';
|
|
120
|
+
|
|
121
|
+
export const AppConfig = Settings(
|
|
122
|
+
{
|
|
123
|
+
/* schema properties */
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
/* computed properties */
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
export type AppConfigType = InferConfigType<typeof AppConfig>;
|
|
131
|
+
|
|
132
|
+
// Singleton pattern - resolvers inherit nestingSeparator automatically
|
|
133
|
+
export const { getConfig, resetConfig } = defineConfig(AppConfig, {
|
|
134
|
+
nestingSeparator: '__',
|
|
135
|
+
resolvers: [fromEnvironment(), fromDotenv()] // Both inherit '__'
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Benefits:**
|
|
140
|
+
|
|
141
|
+
- Encapsulates singleton caching pattern
|
|
142
|
+
- Automatically extracts schema and computed from bundle
|
|
143
|
+
- **Separator inheritance**: resolvers without a `nestingSeparator` inherit from `defineConfig`
|
|
144
|
+
- Resolvers can override with their own separator if needed
|
|
145
|
+
|
|
146
|
+
### 3b. Alternative: Manual Settings Creation
|
|
147
|
+
|
|
148
|
+
For one-shot configuration (no singleton caching):
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { createSettings, fromEnvironment, fromDotenv } from 'tydantic-settings';
|
|
152
|
+
|
|
153
|
+
const settings = await createSettings(
|
|
154
|
+
AppConfig, // Pass bundle directly (schema + computed auto-extracted)
|
|
155
|
+
[
|
|
156
|
+
fromEnvironment({ nestingSeparator: '__' }), // Highest priority
|
|
157
|
+
fromDotenv({ nestingSeparator: '__' }) // Fallback
|
|
158
|
+
],
|
|
159
|
+
{ nestingSeparator: '__' }
|
|
160
|
+
);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 4. Use Your Typed Settings
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// Regular properties
|
|
167
|
+
console.log(settings.database.host); // Type: string
|
|
168
|
+
console.log(settings.database.port); // Type: number
|
|
169
|
+
console.log(settings.environment); // Type: string
|
|
170
|
+
|
|
171
|
+
// Computed properties (auto-scoped from DatabaseConfig!)
|
|
172
|
+
console.log(settings.database.url); // Type: string - computed
|
|
173
|
+
console.log(settings.isDev); // Type: boolean - computed
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Synchronous Usage (for Prisma CLI, constructors, etc.)
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { defineConfigSync, fromEnvironmentSync, fromDotenvSync } from 'tydantic-settings';
|
|
180
|
+
|
|
181
|
+
// Use defineConfigSync for CLI tools - resolvers inherit separator
|
|
182
|
+
export const { getConfig, resetConfig } = defineConfigSync(AppConfig, {
|
|
183
|
+
nestingSeparator: '__',
|
|
184
|
+
resolvers: [fromEnvironmentSync(), fromDotenvSync()] // Both inherit '__'
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const config = getConfig(); // Synchronous!
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
For manual control:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { createSyncSettings, fromEnvironmentSync } from 'tydantic-settings';
|
|
194
|
+
|
|
195
|
+
const config = createSyncSettings(
|
|
196
|
+
DatabaseConfig, // Pass bundle directly
|
|
197
|
+
[fromEnvironmentSync({ nestingSeparator: '__' })],
|
|
198
|
+
{ nestingSeparator: '__' }
|
|
199
|
+
);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Core Concepts
|
|
203
|
+
|
|
204
|
+
### Configuration Schema
|
|
205
|
+
|
|
206
|
+
Use `Settings()` to define your configuration structure. It provides all TypeBox types as static helpers:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
const Schema = Settings({
|
|
210
|
+
// Primitives
|
|
211
|
+
appName: Settings.String({ default: 'MyApp' }),
|
|
212
|
+
port: Settings.Number({ default: 3000 }),
|
|
213
|
+
debug: Settings.Boolean({ default: false }),
|
|
214
|
+
|
|
215
|
+
// Enums
|
|
216
|
+
logLevel: Settings.Enum(
|
|
217
|
+
{ Debug: 'debug', Info: 'info', Warn: 'warn' },
|
|
218
|
+
{ default: 'info' }
|
|
219
|
+
),
|
|
220
|
+
|
|
221
|
+
// Nested objects
|
|
222
|
+
database: Settings({
|
|
223
|
+
host: Settings.String(),
|
|
224
|
+
port: Settings.Number({ default: 5432 })
|
|
225
|
+
}),
|
|
226
|
+
|
|
227
|
+
// Optional fields
|
|
228
|
+
apiKey: Settings.Optional(Settings.String())
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Resolvers
|
|
233
|
+
|
|
234
|
+
Resolvers fetch configuration from different sources. They're processed in order, with the first resolver having the highest priority.
|
|
235
|
+
|
|
236
|
+
#### Built-in Resolvers
|
|
237
|
+
|
|
238
|
+
**`fromEnvironment(options?)`**
|
|
239
|
+
|
|
240
|
+
Reads from `process.env`:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
fromEnvironment({
|
|
244
|
+
caseSensitive: false, // Default: false
|
|
245
|
+
nestingSeparator: '__', // For nested keys (inherited from defineConfig if not specified)
|
|
246
|
+
prefix: 'DATABASE__' // Optional: filter and strip prefix from env vars
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Prefix example** - useful for scoping configuration to a namespace:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// With DATABASE__HOST=localhost, DATABASE__PORT=5432
|
|
254
|
+
const resolver = fromEnvironment({ prefix: 'DATABASE__' });
|
|
255
|
+
// Resolves to: { HOST: 'localhost', PORT: '5432' }
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**`fromDotenv(options?)`**
|
|
259
|
+
|
|
260
|
+
Loads a `.env` file into `process.env`:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
fromDotenv({
|
|
264
|
+
path: '.env.production', // Default: '.env'
|
|
265
|
+
caseSensitive: false,
|
|
266
|
+
nestingSeparator: '__', // Inherited from defineConfig if not specified
|
|
267
|
+
prefix: 'APP__' // Optional: filter and strip prefix
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**`fromAwsSecretsManager(secretId, region, options?)`**
|
|
272
|
+
|
|
273
|
+
> Requires `@aws-sdk/client-secrets-manager` — see [Installation](#optional-aws-secrets-manager).
|
|
274
|
+
|
|
275
|
+
Fetches secrets from AWS Secrets Manager:
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
fromAwsSecretsManager(
|
|
279
|
+
'myapp/database', // Secret ID or ARN
|
|
280
|
+
'us-east-1', // AWS region
|
|
281
|
+
{ caseSensitive: false }
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// Multiple secrets
|
|
285
|
+
fromAwsSecretsManager(['myapp/database', 'myapp/api-keys'], 'us-east-1');
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
#### Priority Example
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
const settings = await createSettings(
|
|
292
|
+
Schema,
|
|
293
|
+
[
|
|
294
|
+
fromEnvironment(), // 1st priority (highest)
|
|
295
|
+
fromAwsSecretsManager(...), // 2nd priority
|
|
296
|
+
fromDotenv({ path: '.env' }), // 3rd priority (lowest)
|
|
297
|
+
]
|
|
298
|
+
);
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
If `DATABASE__HOST` is set in both environment variables and `.env`, the environment variable wins.
|
|
302
|
+
|
|
303
|
+
### Nested Configuration
|
|
304
|
+
|
|
305
|
+
Use a separator (like `__`) to represent nested objects in flat environment variables:
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
# .env file
|
|
309
|
+
DATABASE__HOST=localhost
|
|
310
|
+
DATABASE__PORT=5432
|
|
311
|
+
DATABASE__POOL__MIN=2
|
|
312
|
+
DATABASE__POOL__MAX=10
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
const Schema = Settings({
|
|
317
|
+
database: Settings({
|
|
318
|
+
host: Settings.String(),
|
|
319
|
+
port: Settings.Number(),
|
|
320
|
+
pool: Settings({
|
|
321
|
+
min: Settings.Number(),
|
|
322
|
+
max: Settings.Number(),
|
|
323
|
+
}),
|
|
324
|
+
}),
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const { getConfig } = defineConfig(Schema, {
|
|
328
|
+
nestingSeparator: '__',
|
|
329
|
+
resolvers: [fromEnvironment(), fromDotenv()]
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const settings = await getConfig();
|
|
333
|
+
settings.database.pool.min; // 2
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Advanced Features
|
|
337
|
+
|
|
338
|
+
### Settings() with Computed Properties (Recommended)
|
|
339
|
+
|
|
340
|
+
The `Settings()` function is the recommended way to define schemas with computed properties:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
import { Settings } from 'tydantic-settings';
|
|
344
|
+
|
|
345
|
+
// Library defines its config with computed properties
|
|
346
|
+
export const DatabaseConfig = Settings(
|
|
347
|
+
{
|
|
348
|
+
host: Settings.String({ default: 'localhost' }),
|
|
349
|
+
port: Settings.Number({ default: 5432 }),
|
|
350
|
+
user: Settings.String({ default: 'postgres' }),
|
|
351
|
+
password: Settings.Optional(Settings.String()),
|
|
352
|
+
database: Settings.String({ default: 'myapp' }),
|
|
353
|
+
ssl: Settings.Boolean({ default: false })
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
url: cfg => {
|
|
357
|
+
const auth = cfg.password ? `${cfg.user}:${cfg.password}@` : `${cfg.user}@`;
|
|
358
|
+
const sslParam = cfg.ssl ? '?sslmode=require' : '';
|
|
359
|
+
return `postgresql://${auth}${cfg.host}:${cfg.port}/${cfg.database}${sslParam}`;
|
|
360
|
+
},
|
|
361
|
+
isSecure: cfg => cfg.ssl
|
|
362
|
+
}
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
// Export the type for consumers
|
|
366
|
+
export type DatabaseConfigType = typeof DatabaseConfig._type;
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Key Benefits:**
|
|
370
|
+
|
|
371
|
+
- **Single function** - No separate `createSchemaWithComputed()`
|
|
372
|
+
- **Clean API** - `Settings(props, computed)` is intuitive
|
|
373
|
+
- **Automatic nesting** - Nested bundles bring their computed properties automatically
|
|
374
|
+
- **Full type inference** - TypeScript knows about computed properties
|
|
375
|
+
|
|
376
|
+
### Automatic Nested Computed Properties
|
|
377
|
+
|
|
378
|
+
When you nest a `Settings()` bundle in another config, its computed properties are automatically scoped:
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
// App composes library configs
|
|
382
|
+
const AppConfig = Settings(
|
|
383
|
+
{
|
|
384
|
+
environment: Settings.String({ default: 'development' }),
|
|
385
|
+
database: DatabaseConfig, // Just pass the bundle!
|
|
386
|
+
redis: RedisConfig // Same here
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
isDev: cfg => cfg.environment === 'development'
|
|
390
|
+
}
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
// Result:
|
|
394
|
+
// config.database.url - from DatabaseConfig (auto-scoped!)
|
|
395
|
+
// config.redis.url - from RedisConfig (auto-scoped!)
|
|
396
|
+
// config.isDev - from app-level computed
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Computed properties are:
|
|
400
|
+
|
|
401
|
+
- **Reactive**: Recalculate on every access
|
|
402
|
+
- **Serializable**: Included in JSON.stringify()
|
|
403
|
+
- **Read-only**: Implemented as getters
|
|
404
|
+
|
|
405
|
+
### Immutable Configuration
|
|
406
|
+
|
|
407
|
+
All configuration objects are deeply frozen after creation. Attempting to mutate a config value throws a `TypeError`:
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
const config = await getConfig();
|
|
411
|
+
config.database.port = 9999; // TypeError: Cannot assign to read only property
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
This prevents accidental mutation of cached singletons and ensures configuration consistency throughout your application. The `DeepReadonly` type is applied to all return types for compile-time safety.
|
|
415
|
+
|
|
416
|
+
### Type Coercion
|
|
417
|
+
|
|
418
|
+
By default, string values from environment variables are automatically coerced to match your schema:
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
// Environment: DATABASE__PORT=5432 (string)
|
|
422
|
+
// Schema: port: Settings.Number()
|
|
423
|
+
// Result: settings.database.port === 5432 (number)
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Disable coercion for strict type checking:
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
const settings = await createSettings(Schema, [...], {
|
|
430
|
+
coerce: false, // Strict mode - no automatic conversion
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Custom Resolvers
|
|
435
|
+
|
|
436
|
+
Create custom resolvers for any configuration source:
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
import { SettingsResolver } from 'tydantic-settings';
|
|
440
|
+
import { TObject } from 'typebox';
|
|
441
|
+
|
|
442
|
+
function fromJsonFile(filePath: string): SettingsResolver {
|
|
443
|
+
return async (schema: TObject) => {
|
|
444
|
+
const fs = await import('fs/promises');
|
|
445
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
446
|
+
return JSON.parse(content);
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function fromConsulKV(url: string, prefix: string): SettingsResolver {
|
|
451
|
+
return async (schema: TObject) => {
|
|
452
|
+
// Fetch from Consul Key-Value store
|
|
453
|
+
const response = await fetch(`${url}/v1/kv/${prefix}?recurse`);
|
|
454
|
+
const data = await response.json();
|
|
455
|
+
// Transform and return configuration
|
|
456
|
+
return transformConsulData(data);
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Use custom resolvers
|
|
461
|
+
const settings = await createSettings(Schema, [
|
|
462
|
+
fromEnvironment(),
|
|
463
|
+
fromConsulKV('http://consul:8500', 'myapp'),
|
|
464
|
+
fromJsonFile('./config.json')
|
|
465
|
+
]);
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
See the [examples/](examples/) directory for a complete custom resolver implementation.
|
|
469
|
+
|
|
470
|
+
## API Reference
|
|
471
|
+
|
|
472
|
+
### `Settings(properties, computed?)` ⭐ Recommended
|
|
473
|
+
|
|
474
|
+
Creates a TypeBox schema with optional computed properties.
|
|
475
|
+
|
|
476
|
+
**Parameters:**
|
|
477
|
+
|
|
478
|
+
- `properties: Record<string, TSchema | SchemaWithComputed>` - Schema properties (can include nested bundles)
|
|
479
|
+
- `computed?: Record<string, (cfg) => any>` - Optional computed property functions
|
|
480
|
+
|
|
481
|
+
**Returns:**
|
|
482
|
+
|
|
483
|
+
- Without computed: `TObject` (plain TypeBox schema)
|
|
484
|
+
- With computed: `SchemaWithComputed<TObject, TComputed>` (bundle with schema + computed)
|
|
485
|
+
|
|
486
|
+
**Example:**
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
// Without computed - returns plain schema
|
|
490
|
+
const SimpleConfig = Settings({
|
|
491
|
+
host: Settings.String({ default: 'localhost' })
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// With computed - returns bundle
|
|
495
|
+
const DatabaseConfig = Settings(
|
|
496
|
+
{ host: Settings.String(), port: Settings.Number() },
|
|
497
|
+
{ url: cfg => `postgresql://${cfg.host}:${cfg.port}` }
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
// Nesting - computed properties auto-scoped
|
|
501
|
+
const AppConfig = Settings(
|
|
502
|
+
{ database: DatabaseConfig },
|
|
503
|
+
{ isDev: cfg => cfg.environment === 'development' }
|
|
504
|
+
);
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### `defineConfig(bundle, options)` ⭐ Recommended
|
|
508
|
+
|
|
509
|
+
Creates a singleton configuration factory with automatic schema/computed extraction.
|
|
510
|
+
|
|
511
|
+
**Parameters:**
|
|
512
|
+
|
|
513
|
+
- `bundle: SchemaWithComputed` - Configuration bundle created with `Settings()`
|
|
514
|
+
- `options: DefineConfigOptions` - Configuration options
|
|
515
|
+
- `nestingSeparator?: string` - Separator for nested keys (e.g., `'__'`). **Inherited by resolvers.**
|
|
516
|
+
- `resolvers: SettingsResolver[]` - Array of resolvers (required)
|
|
517
|
+
|
|
518
|
+
**Returns:** `{ getConfig: () => Promise<T>, resetConfig: () => void }`
|
|
519
|
+
|
|
520
|
+
**Example:**
|
|
521
|
+
|
|
522
|
+
```typescript
|
|
523
|
+
import {
|
|
524
|
+
Settings,
|
|
525
|
+
defineConfig,
|
|
526
|
+
fromEnvironment,
|
|
527
|
+
fromDotenv,
|
|
528
|
+
type InferConfigType
|
|
529
|
+
} from 'tydantic-settings';
|
|
530
|
+
|
|
531
|
+
const AppConfig = Settings(
|
|
532
|
+
{ host: Settings.String({ default: 'localhost' }) },
|
|
533
|
+
{ url: cfg => `http://${cfg.host}` }
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
export type AppConfigType = InferConfigType<typeof AppConfig>;
|
|
537
|
+
|
|
538
|
+
// Resolvers inherit nestingSeparator automatically
|
|
539
|
+
export const { getConfig, resetConfig } = defineConfig(AppConfig, {
|
|
540
|
+
nestingSeparator: '__',
|
|
541
|
+
resolvers: [fromEnvironment(), fromDotenv()] // Both inherit '__'
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// Usage
|
|
545
|
+
const config = await getConfig();
|
|
546
|
+
console.log(config.url); // http://localhost
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### `defineConfigSync(bundle, options)`
|
|
550
|
+
|
|
551
|
+
Synchronous version of `defineConfig`. Use for CLI tools (Prisma migrations), class constructors, or other synchronous contexts.
|
|
552
|
+
|
|
553
|
+
**Parameters:**
|
|
554
|
+
|
|
555
|
+
- `bundle: SchemaWithComputed` - Configuration bundle created with `Settings()`
|
|
556
|
+
- `options: DefineConfigOptions<SyncSettingsResolver>` - Configuration options
|
|
557
|
+
- `nestingSeparator?: string` - Separator for nested keys (e.g., `'__'`). **Inherited by resolvers.**
|
|
558
|
+
- `resolvers: SyncSettingsResolver[]` - Array of sync resolvers (required)
|
|
559
|
+
|
|
560
|
+
**Returns:** `{ getConfig: () => T, resetConfig: () => void }`
|
|
561
|
+
|
|
562
|
+
**Example:**
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
import { Settings, defineConfigSync, fromEnvironmentSync, fromDotenvSync } from 'tydantic-settings';
|
|
566
|
+
|
|
567
|
+
// Resolvers inherit nestingSeparator automatically
|
|
568
|
+
export const { getConfig, resetConfig } = defineConfigSync(AppConfig, {
|
|
569
|
+
nestingSeparator: '__',
|
|
570
|
+
resolvers: [fromEnvironmentSync(), fromDotenvSync()] // Both inherit '__'
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// Usage (synchronous!)
|
|
574
|
+
const config = getConfig();
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### `createSettings<T>(schema, resolvers, options?)`
|
|
578
|
+
|
|
579
|
+
Creates a validated, type-safe configuration object asynchronously.
|
|
580
|
+
|
|
581
|
+
**Parameters:**
|
|
582
|
+
|
|
583
|
+
- `schema: TObject` - TypeBox schema defining the configuration
|
|
584
|
+
- `resolvers: SettingsResolver[]` - Array of resolvers (highest priority first)
|
|
585
|
+
- `options?: object` - Configuration options
|
|
586
|
+
- `nestingSeparator?: string` - Separator for nested keys (e.g., `'__'`)
|
|
587
|
+
- `coerce?: boolean` - Enable type coercion (default: `true`)
|
|
588
|
+
- `computed?: ComputedProperties` - Map of computed property functions
|
|
589
|
+
|
|
590
|
+
**Returns:** `Promise<Static<T>>` - Validated configuration object
|
|
591
|
+
|
|
592
|
+
**Example:**
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
const settings = await createSettings(AppConfig.schema, [fromEnvironment(), fromDotenv()], {
|
|
596
|
+
nestingSeparator: '__',
|
|
597
|
+
computed: AppConfig.computed
|
|
598
|
+
});
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### `createSyncSettings<T>(schema, resolvers, options?)`
|
|
602
|
+
|
|
603
|
+
Synchronous version of `createSettings`. Use for Prisma CLI, class constructors, or other synchronous contexts.
|
|
604
|
+
|
|
605
|
+
**Parameters:** Same as `createSettings`
|
|
606
|
+
|
|
607
|
+
**Returns:** `Static<T>` - Validated configuration object (synchronous)
|
|
608
|
+
|
|
609
|
+
**Example:**
|
|
610
|
+
|
|
611
|
+
```typescript
|
|
612
|
+
const config = createSyncSettings(
|
|
613
|
+
DatabaseConfig.schema,
|
|
614
|
+
[fromEnvironmentSync({ nestingSeparator: '__' })],
|
|
615
|
+
{ computed: DatabaseConfig.computed }
|
|
616
|
+
);
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### Built-in Types
|
|
620
|
+
|
|
621
|
+
All TypeBox types are available via `Settings`:
|
|
622
|
+
|
|
623
|
+
```typescript
|
|
624
|
+
Settings.String(options?)
|
|
625
|
+
Settings.Number(options?)
|
|
626
|
+
Settings.Boolean(options?)
|
|
627
|
+
Settings.Enum(values, options?)
|
|
628
|
+
Settings.Optional(schema)
|
|
629
|
+
Settings.Array(schema, options?)
|
|
630
|
+
Settings.Union([schema1, schema2, ...])
|
|
631
|
+
Settings.Object(properties, options?)
|
|
632
|
+
Settings.Literal(value)
|
|
633
|
+
// ... and all other TypeBox types
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
## TypeScript Support
|
|
637
|
+
|
|
638
|
+
Full TypeScript support with static type inference, including computed properties:
|
|
639
|
+
|
|
640
|
+
```typescript
|
|
641
|
+
import { Settings, defineConfig, type InferConfigType } from 'tydantic-settings';
|
|
642
|
+
|
|
643
|
+
const AppConfig = Settings(
|
|
644
|
+
{
|
|
645
|
+
database: Settings(
|
|
646
|
+
{
|
|
647
|
+
host: Settings.String(),
|
|
648
|
+
port: Settings.Number({ default: 5432 }),
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
url: cfg => `postgresql://${cfg.host}:${cfg.port}`
|
|
652
|
+
}
|
|
653
|
+
),
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
isDev: cfg => cfg.database.host === 'localhost'
|
|
657
|
+
}
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
// InferConfigType extracts the full type including computed properties
|
|
661
|
+
export type AppConfigType = InferConfigType<typeof AppConfig>;
|
|
662
|
+
// {
|
|
663
|
+
// database: {
|
|
664
|
+
// host: string;
|
|
665
|
+
// port: number;
|
|
666
|
+
// url: string; // ← computed property included
|
|
667
|
+
// };
|
|
668
|
+
// isDev: boolean; // ← computed property included
|
|
669
|
+
// }
|
|
670
|
+
|
|
671
|
+
const { getConfig } = defineConfig(AppConfig, {
|
|
672
|
+
nestingSeparator: '__',
|
|
673
|
+
resolvers: [fromEnvironment(), fromDotenv()]
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
const settings = await getConfig();
|
|
677
|
+
// settings is fully typed as AppConfigType
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
## Error Handling
|
|
681
|
+
|
|
682
|
+
Tydantic Settings provides clear error messages for configuration issues:
|
|
683
|
+
|
|
684
|
+
```typescript
|
|
685
|
+
// Missing required field
|
|
686
|
+
// Error: ❌ Invalid application configuration:
|
|
687
|
+
// - Required property (at path: "/database/host")
|
|
688
|
+
|
|
689
|
+
// Type mismatch (with coerce: false)
|
|
690
|
+
// Error: ❌ Invalid application configuration:
|
|
691
|
+
// - Expected number (at path: "/database/port")
|
|
692
|
+
|
|
693
|
+
// Invalid computed property path
|
|
694
|
+
// Error: Cannot add computed property "NonExistent.field":
|
|
695
|
+
// parent object "NonExistent" does not exist
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
## Best Practices
|
|
699
|
+
|
|
700
|
+
1. **Define your schema first** - Use TypeBox schema as the single source of truth
|
|
701
|
+
2. **Use environment variables for secrets** - Never commit secrets to `.env` files
|
|
702
|
+
3. **Order resolvers by priority** - Put most specific sources first
|
|
703
|
+
4. **Use computed properties for derived data** - Connection URLs, environment flags, etc.
|
|
704
|
+
5. **Validate early** - Call `getConfig()` at application startup
|
|
705
|
+
6. **Export typed settings** - Use `InferConfigType<typeof AppConfig>` for type inference
|
|
706
|
+
|
|
707
|
+
## Examples
|
|
708
|
+
|
|
709
|
+
The [examples/](examples/) directory contains runnable examples covering basic configuration, multi-environment setups, AWS Secrets Manager, custom resolvers, computed properties, and more. See the [examples README](examples/README.md) for a full listing.
|
|
710
|
+
|
|
711
|
+
## Comparison with Pydantic Settings
|
|
712
|
+
|
|
713
|
+
If you're familiar with Python's Pydantic Settings:
|
|
714
|
+
|
|
715
|
+
| Pydantic Settings | Tydantic Settings |
|
|
716
|
+
| ---------------------------- | ----------------------------------------- |
|
|
717
|
+
| `BaseSettings` | `Settings()` |
|
|
718
|
+
| `Field(default=...)` | `Settings.String({ default: ... })` |
|
|
719
|
+
| `@computed_field` | `computed: { 'field': (cfg) => ... }` |
|
|
720
|
+
| `model_config['env_prefix']` | `nestingSeparator` option |
|
|
721
|
+
| `.env` file support | `fromDotenv()` resolver |
|
|
722
|
+
| Custom sources | Custom resolvers |
|
|
723
|
+
|
|
724
|
+
## Contributing
|
|
725
|
+
|
|
726
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
727
|
+
|
|
728
|
+
## License
|
|
729
|
+
|
|
730
|
+
MIT
|
|
731
|
+
|
|
732
|
+
## Related Projects
|
|
733
|
+
|
|
734
|
+
- [TypeBox](https://github.com/sinclairzx81/typebox) - JSON Schema Type Builder
|
|
735
|
+
- [Pydantic Settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/) - Python configuration management
|
|
736
|
+
- [dotenv](https://github.com/motdotla/dotenv) - `.env` file support
|
|
737
|
+
|
|
738
|
+
## Support
|
|
739
|
+
|
|
740
|
+
- 📖 [Documentation](examples/)
|
|
741
|
+
- 🐛 [Issue Tracker](https://github.com/yourusername/tydantic-settings/issues)
|
|
742
|
+
- 💬 [Discussions](https://github.com/yourusername/tydantic-settings/discussions)
|