|
| 1 | +// Type definitions for @logdna/env-config |
| 2 | +// Minimal, inferred key/value typing for configuration definitions |
| 3 | + |
| 4 | +// Utility types to extract literal types from arrays |
| 5 | +export type ReadonlyStringArray = readonly string[]; |
| 6 | + |
| 7 | +// Base definition interface (runtime class is untyped JS; this is a structural representation) |
| 8 | +interface BaseDefinition<Name extends string = string, Kind extends string = string> { |
| 9 | + _name: Name; |
| 10 | + _type: Kind; |
| 11 | + // Chainable common methods |
| 12 | + required(): this; |
| 13 | + desc(str: string): this; |
| 14 | + description(str: string): this; |
| 15 | + default(val: any): this; // default does not change exposed type here (runtime may still allow null) |
| 16 | + allowEmpty(): this; |
| 17 | + name(str: string): this; |
| 18 | +} |
| 19 | + |
| 20 | +// Enum extension captures allowed values to narrow type |
| 21 | +interface EnumCapable<Name extends string = string> extends BaseDefinition<Name, 'enum'> { |
| 22 | + values<V extends ReadonlyStringArray>(vals: V): EnumCapableWithValues<Name, V>; |
| 23 | +} |
| 24 | +interface EnumCapableWithValues<Name extends string, V extends ReadonlyStringArray> extends BaseDefinition<Name, 'enum'> { |
| 25 | + readonly __enumValues: V[number]; |
| 26 | + values(vals: V): this; // further calls keep same type |
| 27 | +} |
| 28 | + |
| 29 | +// Regex definition |
| 30 | +interface RegexDefinition<Name extends string = string> extends BaseDefinition<Name, 'regex'> { |
| 31 | + match(re: string | RegExp): this; |
| 32 | +} |
| 33 | + |
| 34 | +// Number definition (min/max chaining) |
| 35 | +interface NumberDefinition<Name extends string = string> extends BaseDefinition<Name, 'number'> { |
| 36 | + min(n: number): this; |
| 37 | + max(n: number): this; |
| 38 | +} |
| 39 | + |
| 40 | +// Boolean definition (no special methods beyond base) |
| 41 | +interface BooleanDefinition<Name extends string = string> extends BaseDefinition<Name, 'boolean'> {} |
| 42 | + |
| 43 | +// String definition |
| 44 | +interface StringDefinition<Name extends string = string> extends BaseDefinition<Name, 'string'> {} |
| 45 | + |
| 46 | +// List definition (captures element type & separator) |
| 47 | +interface ListDefinition<Name extends string = string, ElemKind extends ListElementKind | undefined = undefined> extends BaseDefinition<Name, 'list'> { |
| 48 | + type<T extends ListElementKind>(t: T): ListDefinitionWithType<Name, T>; |
| 49 | + separator(val: string | RegExp): this; |
| 50 | +} |
| 51 | +interface ListDefinitionWithType<Name extends string, ElemKind extends ListElementKind> extends BaseDefinition<Name, 'list'> { |
| 52 | + readonly __listType: ElemKind; |
| 53 | + type(t: ElemKind): this; // idempotent if called again |
| 54 | + separator(val: string | RegExp): this; |
| 55 | +} |
| 56 | + |
| 57 | +type ListElementKind = 'string' | 'number' | 'boolean'; |
| 58 | + |
| 59 | +// Aggregate union of any definition forms |
| 60 | +export type DefinitionAny = |
| 61 | + | StringDefinition<string> |
| 62 | + | NumberDefinition<string> |
| 63 | + | BooleanDefinition<string> |
| 64 | + | RegexDefinition<string> |
| 65 | + | EnumCapable<string> |
| 66 | + | EnumCapableWithValues<string, ReadonlyStringArray> |
| 67 | + | ListDefinition<string> |
| 68 | + | ListDefinitionWithType<string, ListElementKind>; |
| 69 | + |
| 70 | +// Infer the names from a readonly tuple of definitions |
| 71 | +export type DefinitionNames<Defs extends readonly DefinitionAny[]> = Defs[number]['_name']; |
| 72 | + |
| 73 | +// Find definition by name in tuple |
| 74 | +export type FindDefinition<Defs extends readonly DefinitionAny[], N extends string> = Extract<Defs[number], { _name: N }>; |
| 75 | + |
| 76 | +// Map definition kind (and its refinements) to a resulting value type. |
| 77 | +// These reflect runtime behavior loosely (null for unset strings/lists, undefined for invalid booleans, etc.) |
| 78 | +export type ValueOfDefinition<D> = |
| 79 | + D extends EnumCapableWithValues<any, any> ? D['__enumValues'] | null : |
| 80 | + D extends { _type: 'enum' } ? string | null : |
| 81 | + D extends { _type: 'string' } ? string | null : |
| 82 | + D extends { _type: 'number' } ? number : |
| 83 | + D extends { _type: 'boolean' } ? boolean | undefined : |
| 84 | + D extends { _type: 'regex' } ? string | null : |
| 85 | + D extends ListDefinitionWithType<any, infer E> ? ( |
| 86 | + E extends 'string' ? string[] | null : |
| 87 | + E extends 'number' ? number[] | null : |
| 88 | + E extends 'boolean' ? (boolean | undefined)[] | null : any |
| 89 | + ) : |
| 90 | + D extends { _type: 'list' } ? any[] | null : |
| 91 | + unknown; |
| 92 | + |
| 93 | +// Produce a record type for all definitions in a tuple |
| 94 | +export type ConfigShape<Defs extends readonly DefinitionAny[]> = { |
| 95 | + [K in DefinitionNames<Defs>]: ValueOfDefinition<FindDefinition<Defs, K>> |
| 96 | +}; |
| 97 | + |
| 98 | +// Main Env (Config) class declaration |
| 99 | +declare class Env<Defs extends readonly DefinitionAny[] = DefinitionAny[]> extends Map<DefinitionNames<Defs>, any> { |
| 100 | + constructor(input: Defs); |
| 101 | + // Override get/has with key autocomplete and value typing |
| 102 | + get<K extends DefinitionNames<Defs>>(key: K): ConfigShape<Defs>[K]; |
| 103 | + has<K extends DefinitionNames<Defs>>(key: K): boolean; |
| 104 | + toJSON(): ConfigShape<Defs>; |
| 105 | + validateEnvVars(): void; |
| 106 | + // Static builders (preserve name literal type) |
| 107 | + static string<N extends string>(name: N): StringDefinition<N>; |
| 108 | + static number<N extends string>(name: N): NumberDefinition<N>; |
| 109 | + static boolean<N extends string>(name: N): BooleanDefinition<N>; |
| 110 | + static regex<N extends string>(name: N): RegexDefinition<N>; |
| 111 | + static enum<N extends string>(name: N): EnumCapable<N>; |
| 112 | + static list<N extends string>(name: N): ListDefinition<N>; |
| 113 | +} |
| 114 | + |
| 115 | +export = Env; |
0 commit comments