import { type X2jOptions, type XmlBuilderOptions } from 'fast-xml-parser'; import * as INI from 'ini'; import { z } from 'zod'; import * as T from '../../../base/lib/types'; import { PathBase } from './Volume'; /** * Bidirectional transformers for converting between the raw file format and * the application-level data type. Used with FileHelper factory methods. * * @typeParam Raw - The native type the file format parses to (e.g. `Record` for JSON) * @typeParam Transformed - The application-level type after transformation */ export type Transformers = { /** Transform raw parsed data into the application type */ onRead: (value: Raw) => Transformed; /** Transform application data back into the raw format for writing */ onWrite: (value: Validated) => Raw; }; type ToPath = string | { base: PathBase; subpath: string; }; type Validator<_T, U> = z.ZodType; type ReadType = { once: () => Promise; const: (effects: T.Effects) => Promise; watch: (effects: T.Effects, abort?: AbortSignal) => AsyncGenerator; onChange: (effects: T.Effects, callback: (value: A | null, error?: Error) => { cancel: boolean; } | Promise<{ cancel: boolean; }>) => void; waitFor: (effects: T.Effects, pred: (value: A | null) => boolean) => Promise; }; /** * @description Use this class to read/write an underlying configuration file belonging to the upstream service. * * These type definitions should reflect the underlying file as closely as possible. For example, if the service does not require a particular value, it should be marked as optional(), even if your package requires it. * * It is recommended to use onMismatch() whenever possible. This provides an escape hatch in case the user edits the file manually and accidentally sets a value to an unsupported type. * * Officially supported file types are json, yaml, and toml. Other files types can use "raw" * * Choose between officially supported file formats (), or a custom format (raw). * * @example * Below are a few examples * * ``` * import { matches, FileHelper } from '@start9labs/start-sdk' * const { arrayOf, boolean, literal, literals, object, natural, string } = matches * * export const jsonFile = FileHelper.json('./inputSpec.json', object({ * passwords: arrayOf(string).onMismatch([]) * type: literals('private', 'public').optional().onMismatch(undefined) * })) * * export const tomlFile = FileHelper.toml('./inputSpec.toml', object({ * url: literal('https://start9.com').onMismatch('https://start9.com') * public: boolean.onMismatch(true) * })) * * export const yamlFile = FileHelper.yaml('./inputSpec.yml', object({ * name: string.optional().onMismatch(undefined) * age: natural.optional().onMismatch(undefined) * })) * * export const bitcoinConfFile = FileHelper.raw( * './service.conf', * (obj: CustomType) => customConvertObjToFormattedString(obj), * (str) => customParseStringToTypedObj(str), * ) * ``` */ export declare class FileHelper { readonly path: string; readonly writeData: (dataIn: A) => string; readonly readData: (stringValue: string) => unknown; readonly validate: (value: unknown) => A; private consts; protected constructor(path: string, writeData: (dataIn: A) => string, readData: (stringValue: string) => unknown, validate: (value: unknown) => A); private writeFileRaw; /** * Accepts structured data and overwrites the existing file on disk. */ private writeFile; private readFileRaw; private readFile; /** * Reads the file from disk and converts it to structured data. */ private readOnce; private createFileWatchable; /** * Create a reactive reader for this file. * * Returns an object with multiple read strategies: * - `once()` - Read the file once and return the parsed value * - `const(effects)` - Read once but re-read when the file changes (for use with constRetry) * - `watch(effects)` - Async generator yielding new values on each file change * - `onChange(effects, callback)` - Fire a callback on each file change * - `waitFor(effects, predicate)` - Block until the file value satisfies a predicate * * @param map - Optional transform function applied after validation * @param eq - Optional equality function to deduplicate watch emissions */ read(): ReadType; read(map: (value: A) => B, eq?: (left: B | null, right: B | null) => boolean): ReadType; /** * Accepts full structured data and overwrites the existing file on disk if it exists. */ write(effects: T.Effects, data: T.AllowReadonly | A, options?: { allowWriteAfterConst?: boolean; }): Promise; /** * Accepts partial structured data and performs a merge with the existing file on disk. */ merge(effects: T.Effects, data: T.AllowReadonly>, options?: { allowWriteAfterConst?: boolean; }): Promise; /** * We wanted to be able to have a fileHelper, and just modify the path later in time. * Like one behavior of another dependency or something similar. */ withPath(path: ToPath): FileHelper; /** * Create a File Helper for an arbitrary file type. * * Provide custom functions for translating data to/from the file format. */ static raw(path: ToPath, toFile: (dataIn: A) => string, fromFile: (rawData: string) => unknown, validate: (data: unknown) => A): FileHelper; private static rawTransformed; /** * Create a File Helper for a text file */ static string(path: ToPath): FileHelper; static string(path: ToPath, shape: Validator): FileHelper; static string(path: ToPath, shape: Validator, transformers: Transformers): FileHelper; /** * Create a File Helper for a .json file. */ static json(path: ToPath, shape: Validator): FileHelper; static json(path: ToPath, shape: Validator, transformers: Transformers): FileHelper; /** * Create a File Helper for a .yaml file */ static yaml>(path: ToPath, shape: Validator, A>): FileHelper; static yaml>(path: ToPath, shape: Validator, transformers: Transformers, Transformed, A>): FileHelper; /** * Create a File Helper for a .toml file */ static toml>(path: ToPath, shape: Validator, A>): FileHelper; static toml>(path: ToPath, shape: Validator, transformers: Transformers, Transformed, A>): FileHelper; /** * Create a File Helper for a .ini file. * * Supports optional encode/decode options and custom transformers. */ static ini>(path: ToPath, shape: Validator, A>, options?: INI.EncodeOptions & INI.DecodeOptions): FileHelper; static ini>(path: ToPath, shape: Validator, options: INI.EncodeOptions & INI.DecodeOptions, transformers: Transformers, Transformed, A>): FileHelper; /** * Create a File Helper for a .env file (KEY=VALUE format, one per line). * * Lines starting with `#` are treated as comments and ignored on read. */ static env>(path: ToPath, shape: Validator, A>): FileHelper; static env>(path: ToPath, shape: Validator, transformers: Transformers, Transformed, A>): FileHelper; /** * Create a File Helper for an .xml file. * * Supports optional parser/builder options from `fast-xml-parser`. */ static xml>(path: ToPath, shape: Validator, A>, options?: { parser?: X2jOptions; builder?: XmlBuilderOptions; }): FileHelper; static xml>(path: ToPath, shape: Validator, options: { parser?: X2jOptions; builder?: XmlBuilderOptions; }, transformers: Transformers, Transformed, A>): FileHelper; } export default FileHelper;