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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +742 -0
  3. package/dist/core/computed.d.ts +16 -0
  4. package/dist/core/computed.d.ts.map +1 -0
  5. package/dist/core/computed.js +33 -0
  6. package/dist/core/computed.js.map +1 -0
  7. package/dist/core/defaults.d.ts +9 -0
  8. package/dist/core/defaults.d.ts.map +1 -0
  9. package/dist/core/defaults.js +39 -0
  10. package/dist/core/defaults.js.map +1 -0
  11. package/dist/core/index.d.ts +6 -0
  12. package/dist/core/index.d.ts.map +1 -0
  13. package/dist/core/index.js +17 -0
  14. package/dist/core/index.js.map +1 -0
  15. package/dist/core/nested-bundles.d.ts +15 -0
  16. package/dist/core/nested-bundles.d.ts.map +1 -0
  17. package/dist/core/nested-bundles.js +68 -0
  18. package/dist/core/nested-bundles.js.map +1 -0
  19. package/dist/core/pipeline.d.ts +27 -0
  20. package/dist/core/pipeline.d.ts.map +1 -0
  21. package/dist/core/pipeline.js +137 -0
  22. package/dist/core/pipeline.js.map +1 -0
  23. package/dist/core/unflatten.d.ts +8 -0
  24. package/dist/core/unflatten.d.ts.map +1 -0
  25. package/dist/core/unflatten.js +25 -0
  26. package/dist/core/unflatten.js.map +1 -0
  27. package/dist/index.d.ts +6 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +25 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/resolvers/aws.d.ts +19 -0
  32. package/dist/resolvers/aws.d.ts.map +1 -0
  33. package/dist/resolvers/aws.js +107 -0
  34. package/dist/resolvers/aws.js.map +1 -0
  35. package/dist/resolvers/dotenv.d.ts +35 -0
  36. package/dist/resolvers/dotenv.d.ts.map +1 -0
  37. package/dist/resolvers/dotenv.js +43 -0
  38. package/dist/resolvers/dotenv.js.map +1 -0
  39. package/dist/resolvers/environment.d.ts +77 -0
  40. package/dist/resolvers/environment.d.ts.map +1 -0
  41. package/dist/resolvers/environment.js +109 -0
  42. package/dist/resolvers/environment.js.map +1 -0
  43. package/dist/resolvers/index.d.ts +5 -0
  44. package/dist/resolvers/index.d.ts.map +1 -0
  45. package/dist/resolvers/index.js +15 -0
  46. package/dist/resolvers/index.js.map +1 -0
  47. package/dist/settings.d.ts +211 -0
  48. package/dist/settings.d.ts.map +1 -0
  49. package/dist/settings.js +162 -0
  50. package/dist/settings.js.map +1 -0
  51. package/dist/types.d.ts +89 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +3 -0
  54. package/dist/types.js.map +1 -0
  55. package/dist/utils.d.ts +15 -0
  56. package/dist/utils.d.ts.map +1 -0
  57. package/dist/utils.js +44 -0
  58. package/dist/utils.js.map +1 -0
  59. package/package.json +64 -0
package/README.md ADDED
@@ -0,0 +1,742 @@
1
+ # Tydantic Settings
2
+
3
+ ![Coverage](https://img.shields.io/badge/coverage-97.85%25-brightgreen)
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)