{"version":3,"file":"env.mjs","names":[],"sources":["../../../src/functions/create/env.ts"],"sourcesContent":["import type { Extension } from \"#/@types/extension\";\nimport type {\n    Conditions,\n    EnvOutput,\n    OnInvalidAccessContext,\n    OnInvalidAccessContextTarget,\n    Options,\n    RuntimeEnv,\n    Target,\n} from \"#/@types/options\";\nimport type {\n    StandardSchemaDictionary,\n    StandardSchemaV1,\n} from \"#/@types/standard\";\n\nimport { CONDITIONS_DEFAULT } from \"#/constants/conditions\";\nimport { TARGET_MAP } from \"#/constants/target\";\nimport { parseWithDictionary } from \"#/functions/standard\";\n\n/**\n * Apply `emptyStringAsUndefined` without mutating the caller's object.\n */\nconst normalizeRuntimeEnv = (\n    env: RuntimeEnv,\n    emptyStringAsUndefined: boolean,\n): RuntimeEnv => {\n    if (!emptyStringAsUndefined) return env;\n\n    const next: RuntimeEnv = {};\n\n    for (const [key, value] of Object.entries(env)) {\n        if (value === \"\") continue;\n        next[key] = value;\n    }\n\n    return next;\n};\n\n/**\n * Resolve a condition value, calling it if it is a function.\n */\nconst resolveCondition = (value: boolean | (() => boolean)): boolean => {\n    return typeof value === \"function\" ? value() : value;\n};\n\n/**\n * Detect the runtime target from the resolved conditions.\n */\nconst detectCurrentTarget = (conditions: Conditions): Target | \"unknown\" => {\n    if (resolveCondition(conditions.client)) return \"client\";\n\n    if (resolveCondition(conditions.server)) return \"server\";\n\n    return \"unknown\";\n};\n\nconst onInvalidAccessDefault = (ctx: OnInvalidAccessContext): never => {\n    throw new Error(\n        `Attempted to access \\`${ctx.variable}\\`, a ${ctx.target.expected} environment variable, on ${ctx.target.current}`,\n    );\n};\n\n/**\n * Default validation error handler: logs the issues and throws.\n */\nconst onValidationErrorDefault = (\n    issues: readonly StandardSchemaV1.Issue[],\n): never => {\n    const formatted: string = issues\n        .map((issue: StandardSchemaV1.Issue): string => {\n            const path: string =\n                issue.path?.map((p) => String(p)).join(\".\") ?? \"<root>\";\n\n            return `    - ${path}: ${issue.message}`;\n        })\n        .join(\"\\n\");\n\n    console.error(`\\nInvalid environment variables:\\n${formatted}\\n`);\n\n    throw new Error(`Invalid environment variables`);\n};\n\ntype ToReadOnlyProxyOptions<T extends Record<string, unknown>> = {\n    value: T;\n    onInvalidAccess: (ctx: OnInvalidAccessContext) => never;\n    accessPolicy: (prop: string) => boolean;\n    target: OnInvalidAccessContextTarget;\n};\n\n/**\n * Build a frozen, read-only proxy over the validated environment.\n */\nconst toReadOnlyProxy = <T extends Record<string, unknown>>({\n    value,\n    onInvalidAccess,\n    accessPolicy,\n    target,\n}: ToReadOnlyProxyOptions<T>): Readonly<T> => {\n    return new Proxy(value, {\n        get(targetObj, prop) {\n            if (typeof prop !== \"string\") return void 0;\n\n            if (prop === \"__esModule\" || prop === \"$$typeof\") return void 0;\n\n            if (!accessPolicy(prop)) {\n                return onInvalidAccess({\n                    target,\n                    variable: prop,\n                });\n            }\n\n            return Reflect.get(targetObj, prop);\n        },\n        set() {\n            throw new Error(\n                \"Cannot assign to read-only property of environment object\",\n            );\n        },\n        defineProperty() {\n            throw new Error(\n                \"Cannot define property on read-only environment object\",\n            );\n        },\n        deleteProperty() {\n            throw new Error(\n                \"Cannot delete property of read-only environment object\",\n            );\n        },\n    }) as Readonly<T>;\n};\n\nconst createEnv = <\n    Define extends StandardSchemaDictionary,\n    const Ext extends\n        readonly Extension<StandardSchemaDictionary>[] = readonly [],\n>(\n    options: Options<Define, Ext>,\n): EnvOutput<Define, Ext> => {\n    const extensions: readonly Extension<StandardSchemaDictionary>[] =\n        options.extends ?? [];\n\n    const runtimeEnv: RuntimeEnv = options.runtimeEnv;\n\n    const normalized: RuntimeEnv = normalizeRuntimeEnv(\n        runtimeEnv,\n        options.emptyStringAsUndefined ?? true,\n    );\n\n    const onInvalidAccess = options.onInvalidAccess ?? onInvalidAccessDefault;\n\n    const conditions: Conditions = {\n        ...CONDITIONS_DEFAULT,\n        ...(options.conditions ?? {}),\n    };\n\n    const targetCurrent: Target | \"unknown\" = detectCurrentTarget(conditions);\n\n    const accessTarget: OnInvalidAccessContextTarget = {\n        expected: options.target,\n        current: targetCurrent,\n    };\n\n    if (options.skipValidation ?? false) {\n        return toReadOnlyProxy({\n            value: normalized,\n            onInvalidAccess,\n            accessPolicy: (): boolean => true,\n            target: accessTarget,\n        }) as EnvOutput<Define, Ext>;\n    }\n\n    /**\n     * Merge extension `define` dictionaries in array order, then the user's\n     * `define` last so the user wins on key collision.\n     */\n    let define: StandardSchemaDictionary = options.define;\n\n    if (extensions.length > 0) {\n        const merged: Record<string, unknown> = {};\n\n        for (const extension of extensions) {\n            if (extension.define) {\n                Object.assign(merged, extension.define);\n            }\n        }\n\n        Object.assign(merged, options.define);\n\n        define = merged as StandardSchemaDictionary;\n    }\n\n    const parsed = parseWithDictionary(define, normalized);\n\n    const onValidationError =\n        options.onValidationError ?? onValidationErrorDefault;\n\n    if (parsed.issues) {\n        return onValidationError(parsed.issues);\n    }\n\n    const allowed: readonly Target[] = TARGET_MAP[options.target];\n\n    const isAllowedTarget: boolean = allowed.some((c: Target) =>\n        resolveCondition(conditions[c]),\n    );\n\n    const accessPolicy: (prop: string) => boolean = (): boolean =>\n        isAllowedTarget;\n\n    return toReadOnlyProxy({\n        value: parsed.value,\n        onInvalidAccess,\n        accessPolicy,\n        target: accessTarget,\n    }) as EnvOutput<Define, Ext>;\n};\n\nexport { createEnv };\n"],"mappings":";;;;;;;AAsBA,MAAM,uBACF,KACA,2BACa;CACb,IAAI,CAAC,wBAAwB,OAAO;CAEpC,MAAM,OAAmB,CAAC;CAE1B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,GAAG,GAAG;EAC5C,IAAI,UAAU,IAAI;EAClB,KAAK,OAAO;CAChB;CAEA,OAAO;AACX;;;;AAKA,MAAM,oBAAoB,UAA8C;CACpE,OAAO,OAAO,UAAU,aAAa,MAAM,IAAI;AACnD;;;;AAKA,MAAM,uBAAuB,eAA+C;CACxE,IAAI,iBAAiB,WAAW,MAAM,GAAG,OAAO;CAEhD,IAAI,iBAAiB,WAAW,MAAM,GAAG,OAAO;CAEhD,OAAO;AACX;AAEA,MAAM,0BAA0B,QAAuC;CACnE,MAAM,IAAI,MACN,yBAAyB,IAAI,SAAS,QAAQ,IAAI,OAAO,SAAS,4BAA4B,IAAI,OAAO,SAC7G;AACJ;;;;AAKA,MAAM,4BACF,WACQ;CACR,MAAM,YAAoB,OACrB,KAAK,UAA0C;EAI5C,OAAO,SAFH,MAAM,MAAM,KAAK,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,SAE9B,IAAI,MAAM;CACnC,CAAC,CAAC,CACD,KAAK,IAAI;CAEd,QAAQ,MAAM,qCAAqC,UAAU,GAAG;CAEhE,MAAM,IAAI,MAAM,+BAA+B;AACnD;;;;AAYA,MAAM,mBAAsD,EACxD,OACA,iBACA,cACA,aAC0C;CAC1C,OAAO,IAAI,MAAM,OAAO;EACpB,IAAI,WAAW,MAAM;GACjB,IAAI,OAAO,SAAS,UAAU,OAAO,KAAK;GAE1C,IAAI,SAAS,gBAAgB,SAAS,YAAY,OAAO,KAAK;GAE9D,IAAI,CAAC,aAAa,IAAI,GAClB,OAAO,gBAAgB;IACnB;IACA,UAAU;GACd,CAAC;GAGL,OAAO,QAAQ,IAAI,WAAW,IAAI;EACtC;EACA,MAAM;GACF,MAAM,IAAI,MACN,2DACJ;EACJ;EACA,iBAAiB;GACb,MAAM,IAAI,MACN,wDACJ;EACJ;EACA,iBAAiB;GACb,MAAM,IAAI,MACN,wDACJ;EACJ;CACJ,CAAC;AACL;AAEA,MAAM,aAKF,YACyB;CACzB,MAAM,aACF,QAAQ,WAAW,CAAC;CAExB,MAAM,aAAyB,QAAQ;CAEvC,MAAM,aAAyB,oBAC3B,YACA,QAAQ,0BAA0B,IACtC;CAEA,MAAM,kBAAkB,QAAQ,mBAAmB;CAEnD,MAAM,aAAyB;EAC3B,GAAG;EACH,GAAI,QAAQ,cAAc,CAAC;CAC/B;CAEA,MAAM,gBAAoC,oBAAoB,UAAU;CAExE,MAAM,eAA6C;EAC/C,UAAU,QAAQ;EAClB,SAAS;CACb;CAEA,IAAI,QAAQ,kBAAkB,OAC1B,OAAO,gBAAgB;EACnB,OAAO;EACP;EACA,oBAA6B;EAC7B,QAAQ;CACZ,CAAC;;;;;CAOL,IAAI,SAAmC,QAAQ;CAE/C,IAAI,WAAW,SAAS,GAAG;EACvB,MAAM,SAAkC,CAAC;EAEzC,KAAK,MAAM,aAAa,YACpB,IAAI,UAAU,QACV,OAAO,OAAO,QAAQ,UAAU,MAAM;EAI9C,OAAO,OAAO,QAAQ,QAAQ,MAAM;EAEpC,SAAS;CACb;CAEA,MAAM,SAAS,oBAAoB,QAAQ,UAAU;CAErD,MAAM,oBACF,QAAQ,qBAAqB;CAEjC,IAAI,OAAO,QACP,OAAO,kBAAkB,OAAO,MAAM;CAK1C,MAAM,kBAF6B,WAAW,QAAQ,OAEd,CAAC,MAAM,MAC3C,iBAAiB,WAAW,EAAE,CAClC;CAEA,MAAM,qBACF;CAEJ,OAAO,gBAAgB;EACnB,OAAO,OAAO;EACd;EACA;EACA,QAAQ;CACZ,CAAC;AACL"}