UNPKG

@layerfig/config

Version:

Layer and runtime-validate type-safe configs for JavaScript apps.

1 lines 28.4 kB
{"version":3,"file":"index.cjs","names":["slots: Slot[]","match: RegExpExecArray | null","#slotMatch","#slotContent","#separator","#fallbackValue","#references","#extractSlots","envVarValue: RuntimeEnvValue","#cleanUndefinedMarkers","result: Slot[]","newList: any[]","result: UnknownRecord","#options","#prefixWithSeparator","z","#object","zm","ServerConfigBuilderOptionsSchema: z.ZodMiniType<ValidatedServerConfigBuilderOptions>","z","path","#options","#sources","partialConfig: UnknownRecord","z","fs","z","#fileName","path","#getFileExtension"],"sources":["../src/parser/config-parser.ts","../src/utils/escape-break-line.ts","../src/utils/slot.ts","../src/sources/source.ts","../src/sources/env-var.ts","../src/sources/object.ts","../src/parser/parser-json.ts","../src/types.ts","../src/server/types.ts","../src/server/config-builder.ts","../src/utils/read-if-exist.ts","../src/server/file-source.ts"],"sourcesContent":["import type { Result } from \"../types\";\n\n/**\n * Abstract class for parsing configuration files.\n * It defines the interface for loading configuration data\n * and checks if a file extension is accepted.\n */\nexport abstract class ConfigParser {\n\tpublic acceptedFileExtensions: string[];\n\n\tabstract load(fileContent: string): Result<Record<string, unknown>, Error>;\n\n\tconstructor(options: { acceptedFileExtensions: string[] }) {\n\t\tthis.acceptedFileExtensions = options.acceptedFileExtensions;\n\t}\n\n\tacceptsExtension(fileExtension: string): boolean {\n\t\tconst ext = fileExtension.startsWith(\".\")\n\t\t\t? fileExtension\n\t\t\t: `.${fileExtension}`;\n\n\t\treturn this.acceptedFileExtensions.some(\n\t\t\t(accepted) => accepted === ext || accepted === ext.slice(1),\n\t\t);\n\t}\n}\n","export function escapeBreakLine<T = unknown>(value: T): T {\n\tif (typeof value !== \"string\") {\n\t\treturn value;\n\t}\n\n\treturn value\n\t\t.replace(/\\\\/g, \"\\\\\\\\\")\n\t\t.replace(/\"/g, '\\\\\"')\n\t\t.replace(/\\n/g, \"\\\\n\")\n\t\t.replace(/\\r/g, \"\\\\r\")\n\t\t.replace(/\\t/g, \"\\\\t\") as T;\n}\n","interface EnvVarSlot {\n\ttype: \"env_var\";\n\tenvVar: string;\n}\n\ninterface SelfReferenceSlot {\n\ttype: \"self_reference\";\n\tpropertyPath: string;\n}\n\nexport function extractSlotsFromExpression(\n\tcontent: string,\n\tslotPrefix: string,\n): Slot[] {\n\tconst slots: Slot[] = [];\n\tconst regex = getTemplateRegex(slotPrefix);\n\n\tlet match: RegExpExecArray | null;\n\n\t// biome-ignore lint/suspicious/noAssignInExpressions: easy brow, it's fine.\n\twhile ((match = regex.exec(content)) !== null) {\n\t\tconst [fullMatch, slotValue] = match;\n\n\t\tif (!slotValue) {\n\t\t\tthrow new Error(\"Slot value is missing\");\n\t\t}\n\n\t\tslots.push(new Slot(fullMatch, slotValue));\n\t}\n\n\treturn slots;\n}\n\nexport function hasSlot(content: string, slotPrefix: string): boolean {\n\tconst regex = getTemplateRegex(slotPrefix);\n\treturn regex.test(content);\n}\n\nexport class Slot {\n\t#references: (SelfReferenceSlot | EnvVarSlot)[] = [];\n\t#slotMatch: string;\n\t#slotContent: string;\n\t#fallbackValue: string | undefined;\n\t#separator = \"::\";\n\n\tconstructor(slotMatch: string, slotContent: string) {\n\t\tthis.#slotMatch = slotMatch;\n\t\tthis.#slotContent = slotContent;\n\n\t\tconst slotParts = this.#slotContent.split(this.#separator);\n\n\t\t//Check for fallback (last value starting with -)\n\t\tif (\n\t\t\tslotParts.length > 1 &&\n\t\t\tslotParts[slotParts.length - 1]?.startsWith(\"-\")\n\t\t) {\n\t\t\tthis.#fallbackValue = slotParts.pop()?.slice(1);\n\t\t}\n\n\t\tfor (const slotPart of slotParts) {\n\t\t\tif (slotPart.startsWith(\"self.\")) {\n\t\t\t\tconst propertyPath = slotPart.trim().replace(\"self.\", \"\").trim();\n\n\t\t\t\tif (!propertyPath) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Invalid self-referencing slot pattern: \"\\${self.}\". Object Path is missing.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tthis.#references.push({\n\t\t\t\t\ttype: \"self_reference\",\n\t\t\t\t\tpropertyPath,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.#references.push({\n\t\t\t\t\ttype: \"env_var\",\n\t\t\t\t\tenvVar: slotPart.trim(),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tget fallbackValue(): string | undefined {\n\t\treturn this.#fallbackValue;\n\t}\n\n\tget slotMatch(): string {\n\t\treturn this.#slotMatch;\n\t}\n\n\tget references(): (SelfReferenceSlot | EnvVarSlot)[] {\n\t\treturn [...this.#references];\n\t}\n}\n\nfunction getTemplateRegex(slotPrefix: string) {\n\t// Escape special regex characters in the prefix\n\tconst escapedPrefix = slotPrefix.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\t// Build the regex pattern\n\treturn new RegExp(`${escapedPrefix}\\\\{([^}]*)\\\\}`, \"g\");\n}\n","import { get } from \"es-toolkit/compat\";\nimport type {\n\tClientConfigBuilderOptions,\n\tPrettify,\n\tRuntimeEnvValue,\n\tServerConfigBuilderOptions,\n\tUnknownArray,\n\tUnknownRecord,\n} from \"../types\";\nimport { escapeBreakLine } from \"../utils/escape-break-line\";\nimport { extractSlotsFromExpression, hasSlot, type Slot } from \"../utils/slot\";\n\nconst UNDEFINED_MARKER = \"___UNDEFINED_MARKER___\" as const;\n\nexport abstract class Source<T = Record<string, unknown>> {\n\t/**\n\t * An abstract method that must be implemented by any subclass.\n\t * It defines the contract for loading a source.\n\t * @param loadSourceOptions - The options for loading the source.\n\t * @returns A record representing the loaded source data.\n\t */\n\tabstract loadSource(loadSourceOptions: LoadSourceOptions): Prettify<T>;\n\n\tmaybeReplaceSlots<T>(options: MaybeReplaceSlotsOptions<T>) {\n\t\tconst initialObject = options.transform(options.contentString);\n\t\t/**\n\t\t * If there's no slot, we don't need to do anything\n\t\t */\n\t\tif (!hasSlot(options.contentString, options.slotPrefix)) {\n\t\t\treturn initialObject;\n\t\t}\n\n\t\tconst slots = this.#extractSlots(\n\t\t\tinitialObject as UnknownRecord,\n\t\t\toptions.slotPrefix,\n\t\t);\n\n\t\t/**\n\t\t * At this moment it does not matter what parser the user had defined,\n\t\t * we're in the JS/JSON land.\n\t\t */\n\t\tlet updatedContentString = JSON.stringify(initialObject);\n\n\t\tfor (const slot of slots) {\n\t\t\tlet envVarValue: RuntimeEnvValue;\n\n\t\t\tfor (const reference of slot.references) {\n\t\t\t\tif (reference.type === \"env_var\") {\n\t\t\t\t\tenvVarValue = options.runtimeEnv[reference.envVar];\n\t\t\t\t}\n\n\t\t\t\tif (reference.type === \"self_reference\") {\n\t\t\t\t\tconst partialObj = JSON.parse(updatedContentString);\n\n\t\t\t\t\tenvVarValue = get(\n\t\t\t\t\t\tpartialObj,\n\t\t\t\t\t\treference.propertyPath,\n\t\t\t\t\t) as RuntimeEnvValue;\n\t\t\t\t}\n\n\t\t\t\tif (envVarValue !== null && envVarValue !== undefined) {\n\t\t\t\t\t// If we found a value for the env var, we can stop looking\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!envVarValue && slot.fallbackValue) {\n\t\t\t\tenvVarValue = slot.fallbackValue;\n\t\t\t}\n\n\t\t\tconst valueToInsert =\n\t\t\t\tenvVarValue !== null && envVarValue !== undefined\n\t\t\t\t\t? String(envVarValue)\n\t\t\t\t\t: UNDEFINED_MARKER;\n\n\t\t\tupdatedContentString = updatedContentString.replaceAll(\n\t\t\t\tslot.slotMatch,\n\t\t\t\tescapeBreakLine(valueToInsert),\n\t\t\t);\n\t\t}\n\n\t\tconst partialConfig = this.#cleanUndefinedMarkers(\n\t\t\tJSON.parse(updatedContentString),\n\t\t);\n\n\t\treturn partialConfig;\n\t}\n\n\t#extractSlots(\n\t\tvalue: UnknownRecord | UnknownArray,\n\t\tslotPrefix: string,\n\t): Slot[] {\n\t\tconst result: Slot[] = [];\n\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const item of value) {\n\t\t\t\tresult.push(...this.#extractSlots(item as UnknownRecord, slotPrefix));\n\t\t\t}\n\t\t} else if (typeof value === \"string\") {\n\t\t\tresult.push(...extractSlotsFromExpression(value, slotPrefix));\n\t\t} else if (value && typeof value === \"object\") {\n\t\t\tfor (const [_, v] of Object.entries(value)) {\n\t\t\t\tif (typeof v === \"string\") {\n\t\t\t\t\tresult.push(...extractSlotsFromExpression(v, slotPrefix));\n\t\t\t\t} else {\n\t\t\t\t\tresult.push(...this.#extractSlots(v as UnknownRecord, slotPrefix));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t#cleanUndefinedMarkers<T = unknown>(value: T): any {\n\t\tif (value === UNDEFINED_MARKER) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (typeof value === \"string\" && value.includes(UNDEFINED_MARKER)) {\n\t\t\t// If it's mixed content with undefined slots, return undefined\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (Array.isArray(value)) {\n\t\t\tconst newList: any[] = [];\n\n\t\t\tfor (const item of value) {\n\t\t\t\tconst cleanedItem = this.#cleanUndefinedMarkers(item);\n\t\t\t\tif (cleanedItem !== undefined) {\n\t\t\t\t\tnewList.push(cleanedItem);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn newList;\n\t\t}\n\n\t\tif (value && typeof value === \"object\") {\n\t\t\tconst result: UnknownRecord = {};\n\n\t\t\tfor (const [oKey, oValue] of Object.entries(value)) {\n\t\t\t\tresult[oKey] = this.#cleanUndefinedMarkers(oValue);\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\n\t\treturn value;\n\t}\n}\n\ntype LoadSourceOptions = Prettify<\n\tRequired<ClientConfigBuilderOptions | ServerConfigBuilderOptions>\n>;\n\ninterface MaybeReplaceSlotsOptions<T = unknown>\n\textends Pick<LoadSourceOptions, \"runtimeEnv\" | \"slotPrefix\"> {\n\tcontentString: string;\n\ttransform: (contentString: string) => T;\n}\n","import { set } from \"es-toolkit/compat\";\nimport { z } from \"zod/mini\";\nimport type { LoadSourceOptions } from \"../types\";\nimport { Source } from \"./source\";\n\nexport class EnvironmentVariableSource extends Source {\n\t#options: ValidatedEnvironmentVariableSourceOptions;\n\t#prefixWithSeparator: string;\n\n\tconstructor(options: EnvironmentVariableSourceOptions = {}) {\n\t\tsuper();\n\t\tthis.#options = EnvironmentVariableSourceOptions.parse(options);\n\n\t\tthis.#prefixWithSeparator = `${this.#options.prefix}${this.#options.prefixSeparator}`;\n\t}\n\n\tloadSource({\n\t\truntimeEnv,\n\t\tslotPrefix,\n\t}: Pick<LoadSourceOptions, \"runtimeEnv\" | \"slotPrefix\">): Record<\n\t\tstring,\n\t\tunknown\n\t> {\n\t\tconst envKeys = Object.keys(runtimeEnv).filter((key) =>\n\t\t\tkey.startsWith(this.#prefixWithSeparator),\n\t\t);\n\n\t\tconst tempObject = {} as Record<string, unknown>;\n\n\t\tfor (const envKey of envKeys) {\n\t\t\tconst envVarValue = runtimeEnv[envKey];\n\n\t\t\tif (envVarValue === undefined || envVarValue === null) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst keyWithoutPrefix = envKey.replace(this.#prefixWithSeparator, \"\");\n\t\t\tconst keyParts = keyWithoutPrefix\n\t\t\t\t.split(this.#options.separator)\n\t\t\t\t.join(\".\");\n\n\t\t\tconst value = this.maybeReplaceSlots({\n\t\t\t\tslotPrefix,\n\t\t\t\tcontentString: String(envVarValue),\n\t\t\t\truntimeEnv,\n\t\t\t\ttransform: (content) => content,\n\t\t\t});\n\n\t\t\tset(tempObject, keyParts, value);\n\t\t}\n\n\t\treturn tempObject;\n\t}\n}\n\ninterface EnvironmentVariableSourceOptions {\n\t/**\n\t * The environment variable prefix to use\n\t * @default 'APP\n\t */\n\tprefix?: string;\n\t/**\n\t * The separator to use between the prefix and the key\n\t * @default '_'\n\t */\n\tprefixSeparator?: string;\n\t/**\n\t * The separator to navigate the object\n\t * @default '__'\n\t */\n\tseparator?: string;\n}\n\nconst EnvironmentVariableSourceOptions = z.object({\n\tprefix: z._default(z.optional(z.string()), \"APP\"),\n\tprefixSeparator: z._default(z.optional(z.string()), \"_\"),\n\tseparator: z._default(z.optional(z.string()), \"__\"),\n}) satisfies z.ZodMiniType<EnvironmentVariableSourceOptions>;\n\ntype ValidatedEnvironmentVariableSourceOptions = z.output<\n\ttypeof EnvironmentVariableSourceOptions\n>;\n","import type { LoadSourceOptions, PartialDeepUnknown, Prettify } from \"../types\";\nimport { Source } from \"./source\";\n\nexport class ObjectSource<\n\tT extends object = Record<string, unknown>,\n> extends Source<T> {\n\t#object: Prettify<PartialDeepUnknown<T>>;\n\n\tconstructor(object: T); // For when you pass the exact object (type inference)\n\tconstructor(object: PartialDeepUnknown<T>); // For when you explicitly specify the type with flexible input\n\tconstructor(object: T | PartialDeepUnknown<T>) {\n\t\tsuper();\n\t\tthis.#object = object;\n\t}\n\n\toverride loadSource({\n\t\tslotPrefix,\n\t\truntimeEnv,\n\t}: Pick<LoadSourceOptions, \"runtimeEnv\" | \"slotPrefix\">): Prettify<T> {\n\t\treturn this.maybeReplaceSlots({\n\t\t\tcontentString: JSON.stringify(this.#object),\n\t\t\tslotPrefix,\n\t\t\truntimeEnv,\n\t\t\ttransform: (contentString) => JSON.parse(contentString) as Prettify<T>,\n\t\t});\n\t}\n}\n","import { ConfigParser } from \"./config-parser\";\n\nclass BasicJsonParser extends ConfigParser {\n\tconstructor() {\n\t\tsuper({ acceptedFileExtensions: [\"json\"] });\n\t}\n\n\tload(fileContent: string) {\n\t\ttry {\n\t\t\tconst content = JSON.parse(fileContent);\n\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tdata: content,\n\t\t\t} as const;\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror:\n\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t? error\n\t\t\t\t\t\t: new Error(\"Something went wrong while loading the file\"),\n\t\t\t} as const;\n\t\t}\n\t}\n}\n\nexport const basicJsonParser = new BasicJsonParser();\n","import type { z as zod } from \"zod\";\nimport { z as zm } from \"zod/mini\";\nimport type { ConfigParser } from \"./parser/config-parser\";\n\ninterface ResultSuccess<TSuccess = undefined> {\n\tok: true;\n\tdata: TSuccess;\n}\n\ninterface ResultError<TError = undefined> {\n\tok: false;\n\terror?: TError;\n}\n\nexport type Result<TSuccess = undefined, TError = undefined> =\n\t| ResultSuccess<TSuccess>\n\t| ResultError<TError>;\n\nexport type PartialDeepUnknown<T> = {\n\t[K in keyof T]?: T[K] extends Record<string, unknown>\n\t\t? T[K] extends unknown[]\n\t\t\t? unknown // Arrays become unknown\n\t\t\t: T[K] extends Date\n\t\t\t\t? unknown // Dates become unknown\n\t\t\t\t: // biome-ignore lint/complexity/noBannedTypes: It's a lib, trust me\n\t\t\t\t\tT[K] extends Function\n\t\t\t\t\t? unknown // Functions become unknown\n\t\t\t\t\t: PartialDeepUnknown<T[K]> // Recursively handle plain objects\n\t\t: unknown; // Primitives become any\n};\n\n/**\n * Utility type to resolves complex types (specially the ones with intersection types)\n * to a more readable format.\n */\nexport type Prettify<T> = {\n\t[K in keyof T]: T[K];\n} & {};\n\nexport const RuntimeEnvValue = zm.union([\n\tzm.boolean(),\n\tzm.null(),\n\tzm.number(),\n\tzm.string(),\n\tzm.undefined(),\n]);\nexport type RuntimeEnvValue = zm.output<typeof RuntimeEnvValue>;\n\nexport const RuntimeEnv = zm.pipe(\n\t/**\n\t * This transformation is needed because we're dealing with process.env in most of the cases.\n\t * It seems that process.env (for NodeJS environment) isn't a plain object for zod so\n\t * it'll never validate correctly.\n\t *\n\t * So this is a workaround to always \"spread\" the received object and transform it into a plain object.\n\t * @see https://github.com/colinhacks/zod/issues/5069#issuecomment-3166763647\n\t */\n\tzm.transform((env) => {\n\t\treturn Object.assign({}, env);\n\t}),\n\tzm.record(zm.string(), RuntimeEnvValue),\n);\n\nexport type RuntimeEnv = zm.output<typeof RuntimeEnv>;\n\nexport const DEFAULT_SLOT_PREFIX = \"$\" as const;\n\nexport type UnknownRecord = Record<string, unknown>;\nexport type UnknownArray = Array<unknown>;\n\ninterface BaseConfigBuilderOptions {\n\t/**\n\t * Prefix used to search for slotted values\n\t * @default \"$\"\n\t */\n\tslotPrefix?: string;\n}\n\nexport interface ClientConfigBuilderOptions<T extends object = UnknownRecord>\n\textends BaseConfigBuilderOptions {\n\t/**\n\t * The runtime environment variables to use (e.g., import.meta.env, object, etc.)\n\t * @default import.meta.env or {}\n\t */\n\truntimeEnv?: RuntimeEnv;\n\t/**\n\t * A function to validate the configuration object.\n\t * @param config - The configuration object to be validated\n\t * @param z - The zod 4 (mini) instance\n\t */\n\tvalidate: (config: UnknownRecord, z: typeof zm) => T;\n}\n\nexport type ValidatedClientConfigBuilderOptions<\n\tT extends object = UnknownRecord,\n> = Required<ClientConfigBuilderOptions<T>>;\n\nexport interface ServerConfigBuilderOptions<T extends object = UnknownRecord>\n\textends BaseConfigBuilderOptions {\n\t/**\n\t * The absolute path to the folder where the configuration files are located.\n\t * @default \"<process.cwd()>/config\"\n\t */\n\tabsoluteConfigFolderPath?: string;\n\t/**\n\t * The runtime environment variables to use (e.g., process.env, import.meta.env, etc.)\n\t * @default process.env\n\t */\n\truntimeEnv?: RuntimeEnv;\n\t/**\n\t * Load source from different source types\n\t *\n\t * @default basicJsonParser\n\t */\n\tparser?: ConfigParser;\n\t/**\n\t * A function to validate the configuration object.\n\t * @param config - The configuration object to be validated\n\t * @param z - The zod 4 instance\n\t */\n\tvalidate: (config: Record<string, unknown>, z: typeof zod) => T;\n}\n\nexport type ValidatedServerConfigBuilderOptions<\n\tT extends object = UnknownRecord,\n> = Required<ServerConfigBuilderOptions<T>>;\n\nexport type LoadSourceOptions<T extends object = UnknownRecord> =\n\t| ValidatedClientConfigBuilderOptions<T>\n\t| ValidatedServerConfigBuilderOptions<T>;\n","import path from \"node:path\";\nimport { z } from \"zod/mini\";\nimport type { ConfigParser } from \"../parser/config-parser\";\nimport { basicJsonParser } from \"../parser/parser-json\";\nimport {\n\tDEFAULT_SLOT_PREFIX,\n\tRuntimeEnv,\n\ttype ServerConfigBuilderOptions,\n\ttype UnknownRecord,\n\ttype ValidatedServerConfigBuilderOptions,\n} from \"../types\";\n\nexport const ServerConfigBuilderOptionsSchema: z.ZodMiniType<ValidatedServerConfigBuilderOptions> =\n\tz.object({\n\t\tabsoluteConfigFolderPath: z._default(\n\t\t\tz\n\t\t\t\t.string()\n\t\t\t\t.check(\n\t\t\t\t\tz.refine((value) => path.isAbsolute(value), \"Path must be absolute\"),\n\t\t\t\t),\n\t\t\tpath.resolve(process.cwd(), \"./config\"),\n\t\t),\n\t\truntimeEnv: z._default(RuntimeEnv, process.env),\n\t\tparser: z._default(z.custom<ConfigParser>(), basicJsonParser),\n\t\tvalidate: z.custom<ServerConfigBuilderOptions[\"validate\"]>(),\n\t\tslotPrefix: z._default(z.string(), DEFAULT_SLOT_PREFIX),\n\t});\n\nexport type ConfigBuilderOptions<T extends object = UnknownRecord> =\n\tServerConfigBuilderOptions<T>;\n","import { merge } from \"es-toolkit/compat\";\nimport { Source } from \"../sources/source\";\nimport type {\n\tUnknownRecord,\n\tValidatedServerConfigBuilderOptions,\n} from \"../types\";\nimport { z } from \"./index\";\nimport {\n\ttype ConfigBuilderOptions,\n\tServerConfigBuilderOptionsSchema,\n} from \"./types\";\n\nexport class ConfigBuilder<T extends object = UnknownRecord> {\n\t#options: ValidatedServerConfigBuilderOptions;\n\n\t#sources: Source[] = [];\n\n\tconstructor(options: ConfigBuilderOptions<T>) {\n\t\tthis.#options = ServerConfigBuilderOptionsSchema.parse(options);\n\t}\n\n\t/* Public */\n\tpublic build(): T {\n\t\tif (this.#sources.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t\"No source was added. Please provide one by using .addSource(<source>)\",\n\t\t\t);\n\t\t}\n\n\t\tlet partialConfig: UnknownRecord = {};\n\n\t\tfor (const source of this.#sources) {\n\t\t\tconst data = source.loadSource(this.#options);\n\n\t\t\tpartialConfig = merge({}, partialConfig, data);\n\t\t}\n\n\t\treturn this.#options.validate(partialConfig, z) as T;\n\t}\n\n\tpublic addSource(source: Source): this {\n\t\tif (source instanceof Source === false) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Invalid source. Please provide a valid one (EnvironmentVariableSource, FileSource, or ObjectSource)\",\n\t\t\t);\n\t\t}\n\n\t\tthis.#sources.push(source);\n\n\t\treturn this;\n\t}\n}\n","import fs from \"node:fs\";\nimport { z } from \"zod/mini\";\nimport type { Result } from \"../types\";\n\nexport function readIfExist(filePath: string): Result<string, string> {\n\tif (fs.existsSync(filePath)) {\n\t\tconst fileContent = fs.readFileSync(filePath, \"utf8\");\n\n\t\tconst fileContentResult = z.string().safeParse(fileContent);\n\n\t\tif (fileContentResult.success) {\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tdata: fileContentResult.data,\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: \"File content is not a string.\",\n\t\t};\n\t}\n\n\treturn {\n\t\tok: false,\n\t\terror: `File \"${filePath}\" does not exist`,\n\t};\n}\n","import path from \"node:path\";\nimport { Source } from \"../sources/source\";\nimport type { LoadSourceOptions, UnknownRecord } from \"../types\";\nimport { readIfExist } from \"../utils/read-if-exist\";\nimport { ServerConfigBuilderOptionsSchema } from \"./types\";\n\nexport class FileSource extends Source {\n\t#fileName: string;\n\n\tconstructor(fileName: string) {\n\t\tsuper();\n\t\tthis.#fileName = fileName;\n\t}\n\n\tloadSource(options: LoadSourceOptions): UnknownRecord {\n\t\t/**\n\t\t * This is validated before this method is called. I've done this\n\t\t * just to get a type-safe type for the options and discard\n\t\t * the ClientConfigBuilderOptionsSchema (since it's an union).\n\t\t */\n\t\tconst validatedOptions = ServerConfigBuilderOptionsSchema.parse(options);\n\n\t\tconst absoluteFilePath = path.resolve(\n\t\t\tvalidatedOptions.absoluteConfigFolderPath,\n\t\t\tthis.#fileName,\n\t\t);\n\t\tconst fileExtension = this.#getFileExtension(absoluteFilePath);\n\n\t\tif (validatedOptions.parser.acceptsExtension(fileExtension) === false) {\n\t\t\tthrow new Error(\n\t\t\t\t`\".${fileExtension}\" file is not supported by this parser. Accepted files are: \"${validatedOptions.parser.acceptedFileExtensions.join(\n\t\t\t\t\t\", \",\n\t\t\t\t)}\"`,\n\t\t\t);\n\t\t}\n\n\t\tconst fileContentResult = readIfExist(absoluteFilePath);\n\n\t\tif (fileContentResult.ok === false) {\n\t\t\tthrow new Error(fileContentResult.error);\n\t\t}\n\n\t\treturn this.maybeReplaceSlots({\n\t\t\tcontentString: fileContentResult.data,\n\t\t\tslotPrefix: validatedOptions.slotPrefix,\n\t\t\truntimeEnv: validatedOptions.runtimeEnv,\n\t\t\ttransform: (contentString: string) => {\n\t\t\t\tconst parserResult = validatedOptions.parser.load(contentString);\n\n\t\t\t\tif (!parserResult.ok) {\n\t\t\t\t\tthrow parserResult.error;\n\t\t\t\t}\n\n\t\t\t\treturn parserResult.data;\n\t\t\t},\n\t\t});\n\t}\n\n\t#getFileExtension(filePath: string): string {\n\t\treturn path.extname(filePath).slice(1);\n\t}\n}\n"],"mappings":"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"}