UNPKG

@ghii/yaml-loader

Version:

A Funny yaml loader for ghii configuration manager

392 lines (299 loc) 9.62 kB
# @ghii/yaml-loader A YAML file loader for the [GHII configuration manager](https://github.com/iad-os/ghii) (v2). This package provides a flexible and robust way to load configuration data from YAML files with customizable error handling and logging. ## Features - 🎯 **Type-safe**: Built with TypeScript and provides full type definitions - 🔒 **Error handling**: Configurable error handling with `throwOnError` option - 📝 **Custom logging**: Support for custom logger functions - ✅ **File validation**: Validates file existence and ensures the path points to a file (not a directory) - 🚀 **Async/await**: Built on modern async/await patterns ## Installation ```bash npm install @ghii/yaml-loader ``` ### Peer Dependencies This package requires `@ghii/ghii-v2` as a peer dependency: ```bash npm install @ghii/ghii-v2 ``` ## Requirements - Node.js >= 22.0.0 ## Usage ### Basic Usage with GHII The `yamlLoader` function returns a loader that can be used directly with GHII's `.loader()` method: ```typescript import { ghii } from '@ghii/ghii-v2'; import { z } from 'zod'; import yamlLoader from '@ghii/yaml-loader'; // Define your configuration schema const schema = z.object({ server: z.object({ port: z.number(), host: z.string(), }), database: z.object({ url: z.string(), poolSize: z.number(), }), }); // Create GHII instance with validation engine const config = ghii({ validate: (data) => { const result = schema.safeParse(data); return result.success ? { success: true, value: result.data } : { success: false, errors: result.error.issues.map(issue => ({ path: issue.path.join('.'), input: issue.input, details: issue.code, message: issue.message, _raw: issue, })) }; }, toSchema: () => ({ type: 'object', properties: { server: { type: 'object', properties: { port: { type: 'number' }, host: { type: 'string' } } }, database: { type: 'object', properties: { url: { type: 'string' }, poolSize: { type: 'number' } } }, }, }), }) // Use yamlLoader as a loader .loader(yamlLoader( { throwOnError: true }, __dirname, 'config.yaml' )); // Take a snapshot (validates and loads configuration) const snapshot = await config.takeSnapshot(); console.log(snapshot.server.port); ``` ### With Multiple Loaders You can chain multiple loaders together, with later loaders overriding earlier ones: ```typescript import { ghii } from '@ghii/ghii-v2'; import yamlLoader from '@ghii/yaml-loader'; const config = ghii(validationEngine) // Default configuration from YAML .loader(yamlLoader( { throwOnError: false }, __dirname, 'defaults.yaml' )) // Environment-specific overrides .loader(yamlLoader( { throwOnError: false }, __dirname, `config.${process.env.NODE_ENV}.yaml` )) // Local development overrides (optional, won't fail if missing) .loader(yamlLoader( { throwOnError: false }, __dirname, 'config.local.yaml' )); const snapshot = await config.takeSnapshot(); ``` ### With Custom Logger ```typescript import { ghii } from '@ghii/ghii-v2'; import yamlLoader from '@ghii/yaml-loader'; const config = ghii(validationEngine) .loader(yamlLoader( { throwOnError: false, logger: (err, message) => { console.error(`[YAML Loader] ${message}`, err); }, }, __dirname, 'config.yaml' )); const snapshot = await config.takeSnapshot(); ``` ### Type-Safe Configuration ```typescript import { ghii } from '@ghii/ghii-v2'; import { z } from 'zod'; import yamlLoader from '@ghii/yaml-loader'; interface AppConfig { server: { port: number; host: string; }; database: { url: string; poolSize: number; }; } const schema = z.object({ server: z.object({ port: z.number(), host: z.string(), }), database: z.object({ url: z.string(), poolSize: z.number(), }), }); const config = ghii<AppConfig>({ validate: (data) => { const result = schema.safeParse(data); return result.success ? { success: true, value: result.data } : { success: false, errors: result.error.issues.map(/* ... */) }; }, toSchema: () => ({}), }) .loader(yamlLoader<AppConfig>( { throwOnError: true }, __dirname, 'config.yaml' )); const snapshot = await config.takeSnapshot(); // snapshot is typed as AppConfig console.log(snapshot.server.port); ``` ### Event Handling with GHII GHII supports event listeners for configuration changes: ```typescript import { ghii } from '@ghii/ghii-v2'; import yamlLoader from '@ghii/yaml-loader'; const config = ghii(validationEngine) .loader(yamlLoader( { throwOnError: true }, __dirname, 'config.yaml' )); // Listen for first configuration load config.once('ghii:first', () => { console.log('Configuration loaded for the first time'); }); // Listen for configuration changes config.on('ghii:refresh', (activeConfig) => { console.log('Configuration updated:', { version: activeConfig.version, config: activeConfig.config, }); }); await config.takeSnapshot(); ``` ## Integration with GHII This loader is designed to work seamlessly with [GHII configuration manager](https://github.com/iad-os/ghii/tree/next). The loader function returned by `yamlLoader` implements the `Loader` interface from `@ghii/ghii-v2` and can be used directly with GHII's `.loader()` method. ### How It Works 1. **Create a loader**: `yamlLoader()` returns a function that reads and parses a YAML file 2. **Chain with GHII**: Use the returned loader with GHII's `.loader()` method 3. **Validate**: GHII validates the loaded configuration using your validation engine 4. **Take snapshot**: Call `takeSnapshot()` to get the validated configuration ### Best Practices - Use `throwOnError: false` for optional configuration files (e.g., `config.local.yaml`) - Use `throwOnError: true` for required configuration files - Chain multiple loaders to support configuration layering (defaults → environment → local) - Always validate configuration using GHII's validation engine ## Error Handling The loader supports two error handling modes: ### Throw on Error (default: `false`) When `throwOnError: true`, the loader will throw errors for: - Missing files (404) - Paths pointing to directories instead of files - File read errors (e.g., file deleted after loader creation) ```typescript import { ghii } from '@ghii/ghii-v2'; import yamlLoader from '@ghii/yaml-loader'; const config = ghii(validationEngine) .loader(yamlLoader( { throwOnError: true, }, __dirname, 'config.yaml' )); try { const snapshot = await config.takeSnapshot(); } catch (error) { console.error('Failed to load config:', error); } ``` ### Graceful Error Handling (default) When `throwOnError: false`, the loader returns an empty object `{}` on errors and logs them via the logger function. This is useful for optional configuration files: ```typescript import { ghii } from '@ghii/ghii-v2'; import yamlLoader from '@ghii/yaml-loader'; const config = ghii(validationEngine) // Required config .loader(yamlLoader( { throwOnError: true }, __dirname, 'config.yaml' )) // Optional local overrides (won't fail if missing) .loader(yamlLoader( { throwOnError: false, logger: (err, message) => console.warn(message), }, __dirname, 'config.local.yaml' )); // Returns {} for optional loader if file doesn't exist, but continues with other loaders const snapshot = await config.takeSnapshot(); ``` ## API ### `yamlLoader<T>(options, ...filePathToken): Loader` Creates a YAML file loader function. #### Parameters **`options`** (object) - `throwOnError?: boolean` (optional, default: `false`) - When `true`, throws errors instead of returning empty objects - `logger?: (err: unknown, message: string) => void` (optional, default: `console.log`) - Custom logging function called when errors occur - Receives the error object and a message string **`...filePathToken`** (string[]) - Path segments that will be joined together to form the file path - Uses `path.join()` to combine the segments - Example: `yamlLoader(options, '/path/to', 'config', 'app.yaml')` → `/path/to/config/app.yaml` #### Returns A `Loader` function (from `@ghii/ghii-v2`) that: - Returns a `Promise<T>` where `T` extends `Record<string, unknown>` - Resolves with the parsed YAML content as a JavaScript object - Rejects with an error if `throwOnError: true` and an error occurs - Resolves with `{}` if `throwOnError: false` and an error occurs #### Type Parameters - `T extends Record<string, unknown>` - The expected shape of the configuration object ## Error Scenarios The loader handles several error scenarios: 1. **File doesn't exist**: Logs a 404 message 2. **Path is a directory**: Logs "Source {path} is not a file" 3. **File deleted after loader creation**: Logs "FILE DELETED OR A DIRECTORY" 4. **YAML parsing errors**: Handled by `js-yaml` library ## Example YAML File ```yaml # config.yaml database: host: localhost port: 5432 name: myapp server: port: 3000 timeout: 5000 ``` ## Development ### Build ```bash npm run build ``` ### Test ```bash npm test ``` ### Lint ```bash npm run lint ``` ## License MIT ## Related Projects - [GHII Configuration Manager](https://github.com/iad-os/ghii/tree/next) - The main configuration management library ## Repository [GitHub](https://github.com/iad-os/ghii-yaml-loader) ## Maintainer Daniele Fiungo <daniele.fiungo@iad2.it>