1- import { func_remember , obj_assign_props , obj_props } from "@gaubee/util" ;
1+ import { iter_first_not_null } from "@gaubee/util" ;
22
33type CustomEnvConfig < T = unknown , Env = unknown > = {
44 default : ( env : Env ) => T ;
@@ -15,15 +15,7 @@ export type DefineEnvChain<P extends string, Env> = Env & {
1515 and < KV2 extends Record < string , EnvConfig < Env > > > ( kv : KV2 , env ?: Record < string , string > ) : DefineEnvChain < P , Env & DefineEnv < P , KV2 > > ;
1616 end ( ) : Env ;
1717} ;
18- export const viteEnvSource = ( ) =>
19- new Proxy ( ( import . meta as any ) . env , {
20- get ( target , key , receiver ) {
21- return Reflect . get ( target , `VITE_${ key as string } ` , receiver ) ;
22- } ,
23- set ( target , key , value , receiver ) {
24- return Reflect . set ( target , `VITE_${ key as string } ` , value , receiver ) ;
25- } ,
26- } ) ;
18+
2719declare const process : any ;
2820export const nodeEnvSource = ( ) => process . env ;
2921declare const Deno : any ;
@@ -42,123 +34,93 @@ export const denoEnvSource = () =>
4234 ) ;
4335declare const Bun : any ;
4436export const bunEnvSource = ( ) => Bun . env ;
45- export const storageEnvSource = ( storage : Storage = sessionStorage ) =>
37+ export const storageEnvSource = ( storages : Storage [ ] = [ sessionStorage , localStorage ] ) =>
4638 new Proxy (
4739 { } ,
4840 {
4941 get ( _target , key ) {
50- return storage . getItem ( key as string ) ;
42+ for ( const storage of storages ) {
43+ const res = storage . getItem ( key as string ) ;
44+ if ( res != null ) {
45+ return res ;
46+ }
47+ }
5148 } ,
5249 set ( _target , key , value ) {
53- storage . setItem ( key as string , value as string ) ;
54- return true ;
50+ for ( const storage of storages ) {
51+ storage . setItem ( key as string , value as string ) ;
52+ return true ;
53+ }
54+ return false ;
5555 } ,
5656 } ,
5757 ) ;
5858export const autoEnvSource = ( fallback : ( ) => Record < string , string > = ( ) => ( { } ) ) : Record < string , string | undefined > => {
59- return typeof Deno != "undefined"
60- ? denoEnvSource ( )
61- : typeof Bun != "undefined"
62- ? bunEnvSource ( )
63- : typeof ( import . meta as any ) . env != "undefined"
64- ? viteEnvSource ( )
65- : typeof process != "undefined"
66- ? nodeEnvSource ( )
67- : typeof sessionStorage != "undefined"
68- ? storageEnvSource ( sessionStorage )
69- : fallback ( ) ;
59+ return (
60+ iter_first_not_null (
61+ [
62+ [ typeof Deno != "undefined" , denoEnvSource ] ,
63+ [ typeof Bun != "undefined" , bunEnvSource ] ,
64+ [ typeof process != "undefined" , nodeEnvSource ] ,
65+ [ typeof sessionStorage != "undefined" , storageEnvSource ] ,
66+ ] as const ,
67+ ( [ match , fn ] ) => ( match ? fn ( ) : null ) ,
68+ ) ?? fallback ( )
69+ ) ;
7070} ;
7171
72- export const defineEnv = < P extends string , KV extends Record < string , EnvConfig > > (
73- prefix : P ,
74- kv : KV ,
75- source = autoEnvSource ( ) ,
76- ext : object = { } ,
77- ) : DefineEnvChain < P , DefineEnv < P , KV > > => {
78- const prefix_up = prefix . toUpperCase ( ) ;
79- const res = obj_assign_props ( ext , {
80- and ( kv , env ) {
81- return defineEnv ( prefix , kv as any , env , res ) ;
82- } ,
83- end ( ) {
84- const closed_res = obj_assign_props ( res , { and : undefined , end : undefined } ) as any ;
85- delete closed_res . and ;
86- delete closed_res . end ;
87- return closed_res ;
88- } ,
89- } as DefineEnvChain < P , DefineEnv < P , KV > > ) ;
90- for ( const key of obj_props ( kv , { excludeSymbols : true } ) ) {
91- const ENV_KEY = `${ prefix_up } _${ key . toUpperCase ( ) } ` as keyof DefineEnv < P , KV > ;
92- const env_value = kv [ key ] ;
93- let envConfig : CustomEnvConfig < any > ;
94- switch ( typeof env_value ) {
95- case "string" :
96- envConfig = {
97- default : ( ) => env_value ,
98- stringify : ( v ) => v ,
99- parse : ( v ) => v ,
100- } satisfies CustomEnvConfig < string > ;
101- break ;
102- case "number" :
103- envConfig = {
104- default : ( ) => env_value ,
105- stringify : ( v ) => `${ v } ` ,
106- parse : ( v ) => + v ,
107- } satisfies CustomEnvConfig < number > ;
108- break ;
109- case "bigint" :
110- envConfig = {
111- default : env_value ,
112- stringify : ( v ) => `${ v } ` ,
113- parse : ( v ) => BigInt ( v ) ,
114- } satisfies CustomEnvConfig < bigint > ;
115- break ;
116- case "boolean" :
117- envConfig = {
118- default : ( ) => env_value ,
119- stringify : ( v ) => `${ v } ` ,
120- parse : ( v ) => ! ( v === "false" || v === "" ) ,
121- } satisfies CustomEnvConfig < boolean > ;
122- break ;
123- case "object" :
124- envConfig = env_value ;
125- break ;
126- case "function" :
127- envConfig = {
128- default : env_value ,
129- stringify : ( v ) => v ,
130- parse : ( v ) => v ,
131- } satisfies CustomEnvConfig < string > ;
132- break ;
133- default :
134- throw new Error ( `unkonwn type for key:${ key } ` ) ;
135- }
72+ type Parser = { parse : ( v : string | undefined ) => unknown } | ( ( v : string | undefined ) => unknown ) ;
73+ type ParserReturn < T > = T extends { parse : ( v : string | undefined ) => infer R } ? R : T extends ( v : string | undefined ) => infer R ? R : unknown ;
13674
137- const getter = func_remember (
138- ( v ) => {
139- return envConfig . parse ( v , res ) ;
140- } ,
141- ( v ) => v ,
142- ) ;
143- const setter = func_remember (
144- ( v ) => {
145- source [ ENV_KEY ] = envConfig . stringify ( v , res ) ;
146- } ,
147- ( v ) => v ,
148- ) ;
75+ type SaveEnvReturn < T extends Record < string , Parser > > = {
76+ [ K in keyof T ] : T [ K ] extends Parser ? ParserReturn < T [ K ] > : never ;
77+ } ;
14978
150- obj_assign_props ( res , {
151- get [ ENV_KEY ] ( ) {
152- const val = source [ ENV_KEY ] ;
153- if ( typeof val === "string" ) {
154- return getter ( val ) ;
155- }
156- return envConfig . default ( res ) ;
157- } ,
158- set [ ENV_KEY ] ( v : string ) {
159- setter ( v ) ;
160- } ,
161- } ) ;
162- }
163- return res ;
79+ /**
80+ * @example
81+ * ```ts
82+ * const safeEnv = defineSafeEnv({
83+ * // use zod
84+ * STR: z.string().default("123"),
85+ * NUM: z.number().default(456),
86+ *
87+ * // use custom parser
88+ * BINT: (value) => BigInt(String(value ?? "0")),
89+ * });
90+ *
91+ * const r1 = safeEnv(); // will auto use runtime env: support bun/deno/node/vite/browser(sessionStorage+localStorage)
92+ * r1.STR; // safe type: string
93+ * r1.NUM; // safe type: number
94+ * r1.BINT; // safe type: bigint
95+ *
96+ * console.log(r1); // { STR: "114514", NUM: 123, BINT: 0n }
97+ *
98+ * const r2 = safeEnv({STR: "qqq"});
99+ * console.log(r2); // { STR: "114514", NUM: 123, BINT: 0n }
100+ * ```
101+ * @param envShape
102+ * @returns
103+ */
104+ export const defineSafeEnv = < ENV extends Record < string , Parser > > ( envShape : ENV ) => {
105+ return ( source : Record < string , string | undefined > = autoEnvSource ( ) ) : SaveEnvReturn < ENV > => {
106+ const env : any = { } ;
107+ for ( const prop in envShape ) {
108+ const parser = envShape [ prop as keyof ENV ] ;
109+ let get : undefined | ( ( ) => unknown ) ;
110+ if ( typeof parser === "function" ) {
111+ env [ prop ] = parser ( source [ prop as keyof ENV ] ) ;
112+ }
113+ if ( typeof parser === "object" && parser !== null && typeof parser . parse === "function" ) {
114+ env [ prop ] = parser . parse ( source [ prop as keyof ENV ] ) ;
115+ }
116+ if ( get ) {
117+ Object . defineProperty ( env , prop , {
118+ get,
119+ enumerable : true ,
120+ configurable : false ,
121+ } ) ;
122+ }
123+ }
124+ return env as SaveEnvReturn < ENV > ;
125+ } ;
164126} ;
0 commit comments