@arki/env 0.0.8 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/dist/contracts/common.d.ts +5 -5
- package/dist/contracts/common.d.ts.map +1 -1
- package/dist/contracts/common.js.map +1 -1
- package/dist/contracts/index.d.ts +11 -11
- package/dist/contracts/parsing.d.ts +1 -1
- package/dist/contracts/parsing.d.ts.map +1 -1
- package/dist/contracts/parsing.js +5 -5
- package/dist/contracts/parsing.js.map +1 -1
- package/dist/contracts/urls.d.ts +1 -1
- package/dist/contracts/urls.d.ts.map +1 -1
- package/dist/core/define-env.d.ts +2 -2
- package/dist/core/define-env.d.ts.map +1 -1
- package/dist/core/define-env.js +96 -3
- package/dist/core/define-env.js.map +1 -1
- package/dist/dot.d.ts +1 -1
- package/dist/dot.d.ts.map +1 -1
- package/dist/dot.js +1 -3
- package/dist/dot.js.map +1 -1
- package/package.json +2 -2
- package/src/contracts/parsing.ts +10 -16
- package/src/core/define-env.ts +108 -9
- package/src/dot.ts +4 -5
package/README.md
CHANGED
|
@@ -74,6 +74,56 @@ const env = defineEnv({
|
|
|
74
74
|
package for plugin authoring, lifecycle, diagnostics, and the
|
|
75
75
|
[adapter authoring guide](https://github.com/arkijs/arki/blob/main/packages/dot/docs/adapter-authoring.md).
|
|
76
76
|
|
|
77
|
+
<!-- bench:start -->
|
|
78
|
+
## Benchmarks
|
|
79
|
+
|
|
80
|
+
Defining and validating a 15-variable server environment with `defineEnv()`, against `@t3-oss/env-core` (which it wraps), envalid, and a plain zod object parse.
|
|
81
|
+
|
|
82
|
+
Node v22.13.1 on Apple M2 Max, 2026-10-13. Median time per operation, lower is better.
|
|
83
|
+
|
|
84
|
+
### Define + validate 15 variables + read one value
|
|
85
|
+
|
|
86
|
+
Each call builds a fresh environment from scratch, validates every variable, then reads `PORT`.
|
|
87
|
+
|
|
88
|
+
| Library | Time / op | vs fastest |
|
|
89
|
+
| --- | ---: | ---: |
|
|
90
|
+
| @arki/env defineEnv() | 25.03 µs | fastest |
|
|
91
|
+
| @t3-oss/env-core createEnv() (used directly) | 306.02 µs | 12.22× |
|
|
92
|
+
| envalid cleanEnv() | 59.23 µs | 2.37× |
|
|
93
|
+
| zod z.object().parse(process.env) | 177.92 µs | 7.11× |
|
|
94
|
+
|
|
95
|
+
### Define only (no read)
|
|
96
|
+
|
|
97
|
+
Declare the environment and never read it. `defineEnv()` defers all work to the first read; the others validate immediately.
|
|
98
|
+
|
|
99
|
+
| Library | Time / op | vs fastest |
|
|
100
|
+
| --- | ---: | ---: |
|
|
101
|
+
| @arki/env defineEnv() (lazy, no validation yet) | 126.8 ns | fastest |
|
|
102
|
+
| @t3-oss/env-core createEnv() (validates now) | 519.07 µs | 4094.95× |
|
|
103
|
+
| envalid cleanEnv() (validates now) | 115.66 µs | 912.46× |
|
|
104
|
+
| zod z.object().parse(process.env) (validates now) | 431.26 µs | 3402.26× |
|
|
105
|
+
|
|
106
|
+
### Read one value from an already-validated environment
|
|
107
|
+
|
|
108
|
+
The hot path: `env.PORT` after the environment exists.
|
|
109
|
+
|
|
110
|
+
| Library | Time / op | vs fastest |
|
|
111
|
+
| --- | ---: | ---: |
|
|
112
|
+
| @arki/env (lazy object over createEnv) | 229.1 ns | 28.25× |
|
|
113
|
+
| @t3-oss/env-core createEnv() | 50.3 ns | 6.20× |
|
|
114
|
+
| envalid cleanEnv() | 83.8 ns | 10.34× |
|
|
115
|
+
| zod parsed plain object | 8.1 ns | fastest |
|
|
116
|
+
|
|
117
|
+
Read the rows with the contracts in mind:
|
|
118
|
+
|
|
119
|
+
- `defineEnv()` is lazy: it builds and validates the environment on the first property read, so "define + first read" is the fair row for it. The "define only" group shows what declaring an env costs before anything reads it.
|
|
120
|
+
- Validation is forced on (`skipValidation: false`) for `defineEnv()` and `createEnv`, because `defineEnv()` skips validation outside development by default.
|
|
121
|
+
- `defineEnv()` and `createEnv` share the same zod schemas, so the gap between them is the wrapper alone: building `runtimeEnv` from `process.env`, a labelled error handler, and the lazy object. envalid uses its own validators, and the plain zod parse does no env-specific work (no empty-string handling, no frozen result).
|
|
122
|
+
- `createEnv`, envalid and the zod parse are given the whole `process.env`, as their docs show, so their cost grows with how many variables the process has. `defineEnv()` reads only the keys it declares. The gap therefore depends on the size of the environment it runs in.
|
|
123
|
+
|
|
124
|
+
Reproduce with `bun run bench` in the package directory; regenerate this section with `bun run bench --write-readme`.
|
|
125
|
+
<!-- bench:end -->
|
|
126
|
+
|
|
77
127
|
## License
|
|
78
128
|
|
|
79
129
|
MIT — see [LICENSE](./LICENSE).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export declare const commonContracts: {
|
|
3
|
-
string: (defaultValue?: string) => z.ZodString | z.
|
|
3
|
+
string: (defaultValue?: string) => z.ZodDefault<z.ZodString> | z.ZodString;
|
|
4
4
|
number: (defaultValue?: number) => z.ZodCoercedNumber<unknown> | z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
5
5
|
port: (defaultValue?: number) => z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
6
6
|
nodeEnv: () => z.ZodDefault<z.ZodEnum<{
|
|
@@ -11,14 +11,14 @@ export declare const commonContracts: {
|
|
|
11
11
|
boolean: (defaultValue?: boolean) => z.ZodPipe<z.ZodDefault<z.ZodEnum<{
|
|
12
12
|
0: "0";
|
|
13
13
|
1: "1";
|
|
14
|
-
true: "true";
|
|
15
14
|
false: "false";
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
true: "true";
|
|
16
|
+
}>>, z.ZodTransform<boolean, "0" | "1" | "false" | "true">>;
|
|
17
|
+
logLevel: (defaultValue?: 'debug' | 'info' | 'warn' | 'error') => z.ZodDefault<z.ZodEnum<{
|
|
18
18
|
debug: "debug";
|
|
19
|
+
error: "error";
|
|
19
20
|
info: "info";
|
|
20
21
|
warn: "warn";
|
|
21
|
-
error: "error";
|
|
22
22
|
}>>;
|
|
23
23
|
};
|
|
24
24
|
//# sourceMappingURL=common.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/contracts/common.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,eAAe;
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/contracts/common.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,eAAe;IAC1B,MAAM,kBAAkB,MAAM;IAK9B,MAAM,kBAAkB,MAAM;IAK9B,IAAI;IAOJ,OAAO;;;;;IAEP,OAAO;;;;;;IAMP,QAAQ,kBAAiB,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO;;;;;;CAE7D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/contracts/common.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,MAAM,EAAE,CAAC,YAAqB,EAAE,EAAE;QAChC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;QAC1B,OAAO,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9D,CAAC;IAED,MAAM,EAAE,CAAC,YAAqB,EAAE,EAAE;QAChC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACjC,OAAO,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9D,CAAC;IAED,IAAI,EAAE,CAAC,YAAY,GAAG,IAAI,EAAE,EAAE,CAC5B,CAAC;SACE,MAAM,CAAC,MAAM,EAAE;SACf,GAAG,CAAC,CAAC,EAAE,yBAAyB,CAAC;SACjC,GAAG,CAAC,MAAM,EAAE,4BAA4B,CAAC;SACzC,OAAO,CAAC,YAAY,CAAC;IAE1B,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC;IAEnF,OAAO,EAAE,CAAC,YAAY,GAAG,KAAK,EAAE,EAAE,CAChC,CAAC;SACE,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;SACjC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;SACxC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC;IAEpD,QAAQ,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/contracts/common.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,MAAM,EAAE,CAAC,YAAqB,EAAE,EAAE;QAChC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;QAC1B,OAAO,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9D,CAAC;IAED,MAAM,EAAE,CAAC,YAAqB,EAAE,EAAE;QAChC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACjC,OAAO,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9D,CAAC;IAED,IAAI,EAAE,CAAC,YAAY,GAAG,IAAI,EAAE,EAAE,CAC5B,CAAC;SACE,MAAM,CAAC,MAAM,EAAE;SACf,GAAG,CAAC,CAAC,EAAE,yBAAyB,CAAC;SACjC,GAAG,CAAC,MAAM,EAAE,4BAA4B,CAAC;SACzC,OAAO,CAAC,YAAY,CAAC;IAE1B,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC;IAEnF,OAAO,EAAE,CAAC,YAAY,GAAG,KAAK,EAAE,EAAE,CAChC,CAAC;SACE,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;SACjC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;SACxC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC;IAEpD,QAAQ,EAAE,CAAC,YAAY,GAAwC,MAAM,EAAE,EAAE,CACvE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC;CACnE,CAAC"}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
export declare const contracts: {
|
|
2
|
-
|
|
3
|
-
csvArray: <T extends import("zod").ZodType>(itemSchema: T) => import("zod").ZodPreprocess<import("zod").ZodArray<T>, unknown>;
|
|
4
|
-
csvSet: <T extends import("zod").ZodType>(itemSchema: T) => import("zod").ZodPreprocess<import("zod").ZodArray<T>, unknown>;
|
|
5
|
-
url: (defaultValue?: string) => import("zod").ZodURL | import("zod").ZodDefault<import("zod").ZodURL>;
|
|
6
|
-
databaseUrl: () => import("zod").ZodURL;
|
|
7
|
-
redisUrl: () => import("zod").ZodURL;
|
|
8
|
-
string: (defaultValue?: string) => import("zod").ZodString | import("zod").ZodDefault<import("zod").ZodString>;
|
|
2
|
+
string: (defaultValue?: string) => import("zod").ZodDefault<import("zod").ZodString> | import("zod").ZodString;
|
|
9
3
|
number: (defaultValue?: number) => import("zod").ZodCoercedNumber<unknown> | import("zod").ZodDefault<import("zod").ZodCoercedNumber<unknown>>;
|
|
10
4
|
port: (defaultValue?: number) => import("zod").ZodDefault<import("zod").ZodCoercedNumber<unknown>>;
|
|
11
5
|
nodeEnv: () => import("zod").ZodDefault<import("zod").ZodEnum<{
|
|
@@ -16,15 +10,21 @@ export declare const contracts: {
|
|
|
16
10
|
boolean: (defaultValue?: boolean) => import("zod").ZodPipe<import("zod").ZodDefault<import("zod").ZodEnum<{
|
|
17
11
|
0: "0";
|
|
18
12
|
1: "1";
|
|
19
|
-
true: "true";
|
|
20
13
|
false: "false";
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
true: "true";
|
|
15
|
+
}>>, import("zod").ZodTransform<boolean, "0" | "1" | "false" | "true">>;
|
|
16
|
+
logLevel: (defaultValue?: 'debug' | 'info' | 'warn' | 'error') => import("zod").ZodDefault<import("zod").ZodEnum<{
|
|
23
17
|
debug: "debug";
|
|
18
|
+
error: "error";
|
|
24
19
|
info: "info";
|
|
25
20
|
warn: "warn";
|
|
26
|
-
error: "error";
|
|
27
21
|
}>>;
|
|
22
|
+
json: <T extends import("zod").ZodType>(schema: T) => import("zod").ZodPipe<import("zod").ZodString, import("zod").ZodTransform<Awaited<import("zod").infer<T>>, string>>;
|
|
23
|
+
csvArray: <T extends import("zod").ZodType>(itemSchema: T) => import("zod").ZodPreprocess<import("zod").ZodArray<T>, unknown>;
|
|
24
|
+
csvSet: <T extends import("zod").ZodType>(itemSchema: T) => import("zod").ZodPreprocess<import("zod").ZodArray<T>, unknown>;
|
|
25
|
+
url: (defaultValue?: string) => import("zod").ZodDefault<import("zod").ZodURL> | import("zod").ZodURL;
|
|
26
|
+
databaseUrl: () => import("zod").ZodURL;
|
|
27
|
+
redisUrl: () => import("zod").ZodURL;
|
|
28
28
|
};
|
|
29
29
|
export { commonContracts } from './common.js';
|
|
30
30
|
export { urlContracts } from './urls.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export declare const parseContracts: {
|
|
3
|
-
json: <T extends z.ZodType>(schema: T) => z.ZodPipe<z.ZodString, z.ZodTransform<Awaited<z.
|
|
3
|
+
json: <T extends z.ZodType>(schema: T) => z.ZodPipe<z.ZodString, z.ZodTransform<Awaited<z.infer<T>>, string>>;
|
|
4
4
|
csvArray: <T extends z.ZodType>(itemSchema: T) => z.ZodPreprocess<z.ZodArray<T>, unknown>;
|
|
5
5
|
csvSet: <T extends z.ZodType>(itemSchema: T) => z.ZodPreprocess<z.ZodArray<T>, unknown>;
|
|
6
6
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parsing.d.ts","sourceRoot":"","sources":["../../src/contracts/parsing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,cAAc;
|
|
1
|
+
{"version":3,"file":"parsing.d.ts","sourceRoot":"","sources":["../../src/contracts/parsing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,cAAc;IACzB,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,OAAO,UAAU,CAAC;IAMrC,QAAQ,GAAG,CAAC,SAAS,CAAC,CAAC,OAAO,cAAc,CAAC;IAM7C,MAAM,GAAG,CAAC,SAAS,CAAC,CAAC,OAAO,cAAc,CAAC;CAK5C,CAAC"}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export const parseContracts = {
|
|
3
|
-
json: (schema) => z.string().transform(
|
|
3
|
+
json: (schema) => z.string().transform(str => {
|
|
4
4
|
const parsed = JSON.parse(str);
|
|
5
5
|
return schema.parse(parsed);
|
|
6
6
|
}),
|
|
7
|
-
csvArray: (itemSchema) => z.preprocess(
|
|
7
|
+
csvArray: (itemSchema) => z.preprocess(val => {
|
|
8
8
|
if (typeof val !== 'string')
|
|
9
9
|
return val;
|
|
10
|
-
return val.split(',').map(
|
|
10
|
+
return val.split(',').map(s => s.trim());
|
|
11
11
|
}, z.array(itemSchema)),
|
|
12
|
-
csvSet: (itemSchema) => z.preprocess(
|
|
12
|
+
csvSet: (itemSchema) => z.preprocess(val => {
|
|
13
13
|
if (typeof val !== 'string')
|
|
14
14
|
return val;
|
|
15
|
-
return [...new Set(val.split(',').map(
|
|
15
|
+
return [...new Set(val.split(',').map(s => s.trim()))];
|
|
16
16
|
}, z.array(itemSchema)),
|
|
17
17
|
};
|
|
18
18
|
//# sourceMappingURL=parsing.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parsing.js","sourceRoot":"","sources":["../../src/contracts/parsing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,CAAsB,MAAS,EAAE,EAAE,CACvC,CAAC,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"parsing.js","sourceRoot":"","sources":["../../src/contracts/parsing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,CAAsB,MAAS,EAAE,EAAE,CACvC,CAAC,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC,CAAC;IAEJ,QAAQ,EAAE,CAAsB,UAAa,EAAE,EAAE,CAC/C,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;QACjB,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,GAAG,CAAC;QACxC,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAEzB,MAAM,EAAE,CAAsB,UAAa,EAAE,EAAE,CAC7C,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;QACjB,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,GAAG,CAAC;QACxC,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACzD,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;CAC1B,CAAC"}
|
package/dist/contracts/urls.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"urls.d.ts","sourceRoot":"","sources":["../../src/contracts/urls.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,YAAY;
|
|
1
|
+
{"version":3,"file":"urls.d.ts","sourceRoot":"","sources":["../../src/contracts/urls.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,YAAY;IACvB,GAAG,kBAAkB,MAAM;IAK3B,WAAW;IAQX,QAAQ;CAOT,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createEnv } from '@t3-oss/env-core';
|
|
2
1
|
import type { CreateEnv, DefaultCombinedSchema } from '@t3-oss/env-core';
|
|
3
2
|
import type { ZodType } from 'zod';
|
|
3
|
+
import { createEnv } from '@t3-oss/env-core';
|
|
4
4
|
/** The issue shape t3-oss hands to `onValidationError`, derived so it stays in sync with the lib. */
|
|
5
5
|
type ValidationIssues = Parameters<NonNullable<Parameters<typeof createEnv>[0]['onValidationError']>>[0];
|
|
6
6
|
/**
|
|
@@ -13,7 +13,7 @@ type ValidationIssues = Parameters<NonNullable<Parameters<typeof createEnv>[0]['
|
|
|
13
13
|
* client keys (and only client keys) carry the prefix. Widening it to `string` would make every
|
|
14
14
|
* server key collapse to a type-level `ErrorMessage`, so the prefix must stay a literal.
|
|
15
15
|
*/
|
|
16
|
-
export declare function defineEnv<TServer extends Record<string, ZodType>, TClient extends Record<string, ZodType> = NonNullable<unknown>, TShared extends Record<string, ZodType> = NonNullable<unknown>, const TPrefix extends string = '', const TExtends extends
|
|
16
|
+
export declare function defineEnv<TServer extends Record<string, ZodType>, TClient extends Record<string, ZodType> = NonNullable<unknown>, TShared extends Record<string, ZodType> = NonNullable<unknown>, const TPrefix extends string = '', const TExtends extends Record<string, unknown>[] = []>(config: {
|
|
17
17
|
/**
|
|
18
18
|
* Label shown in validation-error logs, e.g. `'@acme/backend'`. Without it a misconfigured
|
|
19
19
|
* deploy logs a bare "Invalid environment variables" with no hint which env failed — exactly the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-env.d.ts","sourceRoot":"","sources":["../../src/core/define-env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"define-env.d.ts","sourceRoot":"","sources":["../../src/core/define-env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,qGAAqG;AACrG,KAAK,gBAAgB,GAAG,UAAU,CAAC,WAAW,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAkBzG;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CACvB,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,EAC9D,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,EAC9D,KAAK,CAAC,OAAO,SAAS,MAAM,GAAG,EAAE,EACjC,KAAK,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,EACrD,MAAM,EAAE;IACR;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wFAAwF;IACxF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,qFAAqF;IACrF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;IACnE,OAAO,CAAC,EAAE;QACR,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB;;;;WAIG;QACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;QACjC,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,KAAK,CAAC;KACzD,CAAC;CACH,GAAG,SAAS,CAAC,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAsExE"}
|
package/dist/core/define-env.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createEnv } from '@t3-oss/env-core';
|
|
2
|
-
import { lazyObject } from '@arki/ts';
|
|
3
2
|
/**
|
|
4
3
|
* Fold validation issues into a human-legible, log-pipeline-safe string. The t3-oss default handler
|
|
5
4
|
* logs `issues` as a raw object, which platforms like Coolify collapse to `[Object]` — so the var
|
|
@@ -80,8 +79,102 @@ export function defineEnv(config) {
|
|
|
80
79
|
emptyStringAsUndefined: config.options?.emptyStringAsUndefined ?? true,
|
|
81
80
|
onValidationError,
|
|
82
81
|
};
|
|
83
|
-
|
|
82
|
+
const env = createEnv(options);
|
|
83
|
+
// Skipped validation returns `runtimeEnv` itself: private only when `defineEnv` built it.
|
|
84
|
+
// Otherwise `createEnv` returns a fresh Proxy whose reads return its stored values on the server.
|
|
85
|
+
const isPrivate = skipValidation ? config.runtimeEnv === undefined : isServer();
|
|
86
|
+
return { env, isPrivate };
|
|
84
87
|
};
|
|
85
|
-
return
|
|
88
|
+
return lazyEnv(build);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Reads an env takes through the lazy view before it copies the values for fast reads. A copy costs
|
|
92
|
+
* about as much as this many reads save (ski-rental), so an env that is read only a few times, such
|
|
93
|
+
* as one built for a single check, never pays for it, and a hot env pays it once.
|
|
94
|
+
*/
|
|
95
|
+
const READS_BEFORE_COPY = 64;
|
|
96
|
+
/**
|
|
97
|
+
* A lazy view over the env `build()` returns: nothing runs until the first operation, then every
|
|
98
|
+
* operation forwards to the built env.
|
|
99
|
+
*
|
|
100
|
+
* Reads are the hot path. `createEnv` returns a Proxy, so a second Proxy over it pays two traps per
|
|
101
|
+
* read. Where it is safe, once the env has been read `READS_BEFORE_COPY` times, its values are copied
|
|
102
|
+
* into the Proxy's own target and the `get` trap is removed, so later reads are plain property reads
|
|
103
|
+
* on that copy. It is safe when the built object is private to this env and reads on it return its
|
|
104
|
+
* stored values:
|
|
105
|
+
* - on the server, `createEnv`'s Proxy returns stored values (its client guard is off), and its
|
|
106
|
+
* object is a fresh copy nobody else holds;
|
|
107
|
+
* - with validation skipped, `createEnv` returns the `runtimeEnv` it was given: only the one
|
|
108
|
+
* `defineEnv` built itself is private. A caller's own `runtimeEnv` stays a live view.
|
|
109
|
+
* Writes and deletes through this object update both the built env and the copy.
|
|
110
|
+
*/
|
|
111
|
+
function lazyEnv(build) {
|
|
112
|
+
let built;
|
|
113
|
+
let canCopy = false;
|
|
114
|
+
let copied = false;
|
|
115
|
+
let reads = 0;
|
|
116
|
+
const target = {};
|
|
117
|
+
const resolve = () => {
|
|
118
|
+
if (built !== undefined)
|
|
119
|
+
return built;
|
|
120
|
+
const result = build();
|
|
121
|
+
built = result.env;
|
|
122
|
+
canCopy = result.isPrivate;
|
|
123
|
+
return built;
|
|
124
|
+
};
|
|
125
|
+
const copy = (instance) => {
|
|
126
|
+
for (const key of Reflect.ownKeys(instance)) {
|
|
127
|
+
setValue(target, key, readValue(instance, key));
|
|
128
|
+
}
|
|
129
|
+
copied = true;
|
|
130
|
+
Reflect.deleteProperty(handler, 'get');
|
|
131
|
+
};
|
|
132
|
+
const handler = {
|
|
133
|
+
get: (_target, property) => {
|
|
134
|
+
const instance = resolve();
|
|
135
|
+
if (canCopy && ++reads >= READS_BEFORE_COPY) {
|
|
136
|
+
copy(instance);
|
|
137
|
+
return target[property];
|
|
138
|
+
}
|
|
139
|
+
return readValue(instance, property);
|
|
140
|
+
},
|
|
141
|
+
set: (_target, property, value) => {
|
|
142
|
+
const instance = resolve();
|
|
143
|
+
const ok = Reflect.set(instance, property, value);
|
|
144
|
+
if (ok && copied)
|
|
145
|
+
setValue(target, property, readValue(instance, property));
|
|
146
|
+
return ok;
|
|
147
|
+
},
|
|
148
|
+
deleteProperty: (_target, property) => {
|
|
149
|
+
const ok = Reflect.deleteProperty(resolve(), property);
|
|
150
|
+
if (ok && copied)
|
|
151
|
+
Reflect.deleteProperty(target, property);
|
|
152
|
+
return ok;
|
|
153
|
+
},
|
|
154
|
+
getPrototypeOf: () => Reflect.getPrototypeOf(resolve()),
|
|
155
|
+
has: (_target, property) => Reflect.has(resolve(), property),
|
|
156
|
+
ownKeys: () => Reflect.ownKeys(resolve()),
|
|
157
|
+
getOwnPropertyDescriptor: (_target, property) => Object.getOwnPropertyDescriptor(resolve(), property),
|
|
158
|
+
};
|
|
159
|
+
return new Proxy(target, handler);
|
|
160
|
+
}
|
|
161
|
+
/** Same test `createEnv` uses to decide whether its client-side access guard applies. */
|
|
162
|
+
function isServer() {
|
|
163
|
+
const { window } = globalThis;
|
|
164
|
+
return window === undefined || 'Deno' in window;
|
|
165
|
+
}
|
|
166
|
+
/** A read as the old lazy view returned it: functions come back bound to the built env. */
|
|
167
|
+
function readValue(instance, property) {
|
|
168
|
+
const value = Reflect.get(instance, property, instance);
|
|
169
|
+
return typeof value === 'function' ? value.bind(instance) : value;
|
|
170
|
+
}
|
|
171
|
+
/** An own data property. `__proto__` is defined, not assigned, so it never replaces the prototype. */
|
|
172
|
+
function setValue(target, key, value) {
|
|
173
|
+
if (key === '__proto__') {
|
|
174
|
+
Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
target[key] = value;
|
|
178
|
+
}
|
|
86
179
|
}
|
|
87
180
|
//# sourceMappingURL=define-env.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-env.js","sourceRoot":"","sources":["../../src/core/define-env.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"define-env.js","sourceRoot":"","sources":["../../src/core/define-env.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAK7C;;;;GAIG;AACH,SAAS,YAAY,CAAC,IAAwB,EAAE,MAAwB;IACtE,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;QAC/B,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;aAC5B,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;aACrG,IAAI,CAAC,GAAG,CAAC,CAAC;QACb,OAAO,KAAK,IAAI,IAAI,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;IACnD,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,KAAK,mCAAmC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AACzE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAMvB,MAoCD;IAGC,4EAA4E;IAC5E,2EAA2E;IAC3E,yEAAyE;IACzE,2EAA2E;IAC3E,0EAA0E;IAC1E,2EAA2E;IAC3E,+DAA+D;IAC/D,MAAM,KAAK,GAAG,GAAkB,EAAE;QAChC,MAAM,cAAc,GAClB,MAAM,CAAC,OAAO,EAAE,cAAc,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa,CAAC,CAAC;QAEjG,+FAA+F;QAC/F,8FAA8F;QAC9F,iGAAiG;QACjG,IAAI,UAAiE,CAAC;QACtE,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YACtB,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;QACjC,CAAC;aAAM,CAAC;YACN,UAAU,GAAG,EAAE,CAAC;YAChB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC7C,UAAU,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACrC,CAAC;YACD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;gBACnD,UAAU,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACrC,CAAC;YACD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;gBACnD,UAAU,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;QAED,MAAM,iBAAiB,GACrB,MAAM,CAAC,OAAO,EAAE,iBAAiB;YACjC,CAAC,CAAC,MAAwB,EAAS,EAAE;gBACnC,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;gBAClD,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBACvB,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;YAC3B,CAAC,CAAC,CAAC;QAEL,8FAA8F;QAC9F,iGAAiG;QACjG,iGAAiG;QACjG,kGAAkG;QAClG,qGAAqG;QACrG,oGAAoG;QACpG,MAAM,OAAO,GAAG;YACd,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE;YAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE;YAC3B,YAAY,EAAE,MAAM,CAAC,YAAY,IAAI,EAAE;YACvC,UAAU;YACV,cAAc;YACd,6FAA6F;YAC7F,gGAAgG;YAChG,wFAAwF;YACxF,sBAAsB,EAAE,MAAM,CAAC,OAAO,EAAE,sBAAsB,IAAI,IAAI;YACtE,iBAAiB;SACgB,CAAC;QAEpC,MAAM,GAAG,GAAG,SAAS,CAAC,OAA0C,CAAW,CAAC;QAC5E,0FAA0F;QAC1F,kGAAkG;QAClG,MAAM,SAAS,GAAG,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAChF,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;IAC5B,CAAC,CAAC;IAEF,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC;AACxB,CAAC;AAKD;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B;;;;;;;;;;;;;;GAcG;AACH,SAAS,OAAO,CAAmB,KAAqB;IACtD,IAAI,KAAoB,CAAC;IACzB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,MAAM,GAAiC,EAAE,CAAC;IAEhD,MAAM,OAAO,GAAG,GAAM,EAAE;QACtB,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QACtC,MAAM,MAAM,GAAG,KAAK,EAAE,CAAC;QACvB,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC;QACnB,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC;QAC3B,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,CAAC,QAAW,EAAQ,EAAE;QACjC,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5C,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,SAAS,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;QAClD,CAAC;QACD,MAAM,GAAG,IAAI,CAAC;QACd,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACzC,CAAC,CAAC;IAEF,MAAM,OAAO,GAA+C;QAC1D,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;YACzB,MAAM,QAAQ,GAAG,OAAO,EAAE,CAAC;YAC3B,IAAI,OAAO,IAAI,EAAE,KAAK,IAAI,iBAAiB,EAAE,CAAC;gBAC5C,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACf,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC1B,CAAC;YACD,OAAO,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACvC,CAAC;QACD,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;YAChC,MAAM,QAAQ,GAAG,OAAO,EAAE,CAAC;YAC3B,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;YAClD,IAAI,EAAE,IAAI,MAAM;gBAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC5E,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,cAAc,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;YACpC,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;YACvD,IAAI,EAAE,IAAI,MAAM;gBAAE,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;YAC3D,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,cAAc,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;QACvD,GAAG,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC;QAC5D,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACzC,wBAAwB,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,wBAAwB,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC;KACtG,CAAC;IAEF,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE,OAAO,CAAM,CAAC;AACzC,CAAC;AAED,yFAAyF;AACzF,SAAS,QAAQ;IACf,MAAM,EAAE,MAAM,EAAE,GAAG,UAAiC,CAAC;IACrD,OAAO,MAAM,KAAK,SAAS,IAAI,MAAM,IAAI,MAAM,CAAC;AAClD,CAAC;AAED,2FAA2F;AAC3F,SAAS,SAAS,CAAC,QAAgB,EAAE,QAAqB;IACxD,MAAM,KAAK,GAAY,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACjE,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAE,KAAyC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACzG,CAAC;AAED,sGAAsG;AACtG,SAAS,QAAQ,CAAC,MAAoC,EAAE,GAAgB,EAAE,KAAc;IACtF,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;QACxB,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IACtG,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACtB,CAAC;AACH,CAAC"}
|
package/dist/dot.d.ts
CHANGED
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
* this adapter without `@arki/dot` installed will fail at module load —
|
|
34
34
|
* that is intentional: the adapter only makes sense in a DOT app.
|
|
35
35
|
*/
|
|
36
|
-
import { type EmptyShape, type Plugin } from '@arki/dot/plugin';
|
|
37
36
|
import type { ZodType } from 'zod';
|
|
37
|
+
import type { EmptyShape, Plugin } from '@arki/dot/plugin';
|
|
38
38
|
/**
|
|
39
39
|
* Options for the env DOT adapter.
|
|
40
40
|
*
|
package/dist/dot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dot.d.ts","sourceRoot":"","sources":["../src/dot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"dot.d.ts","sourceRoot":"","sources":["../src/dot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAEnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAK3D;;;;;GAKG;AACH,MAAM,MAAM,aAAa,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACnE,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;CACnC,CAAC;AAEF,0DAA0D;AAC1D,MAAM,MAAM,WAAW,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjE,QAAQ,CAAC,GAAG,EAAE;QAAE,QAAQ,EAAE,CAAC,IAAI,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK;KAAE,CAAC;CAClG,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzD,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,GAC9B,MAAM,CAAC,UAAU,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC,CAoB1C"}
|
package/dist/dot.js
CHANGED
|
@@ -51,9 +51,7 @@ export function env(options) {
|
|
|
51
51
|
boot() {
|
|
52
52
|
const validated = defineEnv({
|
|
53
53
|
server: options.schema,
|
|
54
|
-
options: options.skipValidation
|
|
55
|
-
? { skipValidation: options.skipValidation }
|
|
56
|
-
: undefined,
|
|
54
|
+
options: options.skipValidation === undefined ? undefined : { skipValidation: options.skipValidation },
|
|
57
55
|
});
|
|
58
56
|
// `defineEnv` returns a Proxy from `@t3-oss/env-core`; we narrow it
|
|
59
57
|
// to the schema-derived shape so downstream typing flows through.
|
package/dist/dot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dot.js","sourceRoot":"","sources":["../src/dot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;
|
|
1
|
+
{"version":3,"file":"dot.js","sourceRoot":"","sources":["../src/dot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAKH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAwBjD;;;;;GAKG;AACH,MAAM,UAAU,GAAG,CACjB,OAA+B;IAE/B,OAAO,MAAM,CAAC;QACZ,IAAI,EAAE,KAAK;QACX,OAAO,EAAE,OAAO;QAChB,SAAS,CAAC,GAAG;YACX,GAAG,CAAC,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACpC,CAAC;QACD,IAAI;YACF,MAAM,SAAS,GAAG,SAAS,CAAC;gBAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,OAAO,EAAE,OAAO,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE;aACvG,CAAC,CAAC;YACH,oEAAoE;YACpE,kEAAkE;YAClE,8DAA8D;YAC9D,0DAA0D;YAC1D,MAAM,SAAS,GAAG,SAAmD,CAAC;YACtE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;QAC5B,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arki/env",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "Type-safe environment variable parsing for ARKI — defineEnv() built on @t3-oss/env-core with Zod contracts for common variable shapes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"zod": "4.6.5"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
|
-
"@arki/dot": "^0.
|
|
66
|
+
"@arki/dot": "^0.6.1"
|
|
67
67
|
},
|
|
68
68
|
"peerDependenciesMeta": {
|
|
69
69
|
"@arki/dot": {
|
package/src/contracts/parsing.ts
CHANGED
|
@@ -2,26 +2,20 @@ import { z } from 'zod';
|
|
|
2
2
|
|
|
3
3
|
export const parseContracts = {
|
|
4
4
|
json: <T extends z.ZodType>(schema: T) =>
|
|
5
|
-
z.string().transform(
|
|
5
|
+
z.string().transform(str => {
|
|
6
6
|
const parsed = JSON.parse(str);
|
|
7
|
-
return schema.parse(parsed)
|
|
7
|
+
return schema.parse(parsed);
|
|
8
8
|
}),
|
|
9
9
|
|
|
10
10
|
csvArray: <T extends z.ZodType>(itemSchema: T) =>
|
|
11
|
-
z.preprocess(
|
|
12
|
-
(val)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
},
|
|
16
|
-
z.array(itemSchema)
|
|
17
|
-
),
|
|
11
|
+
z.preprocess(val => {
|
|
12
|
+
if (typeof val !== 'string') return val;
|
|
13
|
+
return val.split(',').map(s => s.trim());
|
|
14
|
+
}, z.array(itemSchema)),
|
|
18
15
|
|
|
19
16
|
csvSet: <T extends z.ZodType>(itemSchema: T) =>
|
|
20
|
-
z.preprocess(
|
|
21
|
-
(val)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
},
|
|
25
|
-
z.array(itemSchema)
|
|
26
|
-
),
|
|
17
|
+
z.preprocess(val => {
|
|
18
|
+
if (typeof val !== 'string') return val;
|
|
19
|
+
return [...new Set(val.split(',').map(s => s.trim()))];
|
|
20
|
+
}, z.array(itemSchema)),
|
|
27
21
|
};
|
package/src/core/define-env.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { createEnv } from '@t3-oss/env-core';
|
|
2
1
|
import type { CreateEnv, DefaultCombinedSchema } from '@t3-oss/env-core';
|
|
3
|
-
import { lazyObject } from '@arki/ts';
|
|
4
2
|
import type { ZodType } from 'zod';
|
|
3
|
+
import { createEnv } from '@t3-oss/env-core';
|
|
5
4
|
|
|
6
5
|
/** The issue shape t3-oss hands to `onValidationError`, derived so it stays in sync with the lib. */
|
|
7
|
-
type ValidationIssues = Parameters<
|
|
8
|
-
NonNullable<Parameters<typeof createEnv>[0]['onValidationError']>
|
|
9
|
-
>[0];
|
|
6
|
+
type ValidationIssues = Parameters<NonNullable<Parameters<typeof createEnv>[0]['onValidationError']>>[0];
|
|
10
7
|
|
|
11
8
|
/**
|
|
12
9
|
* Fold validation issues into a human-legible, log-pipeline-safe string. The t3-oss default handler
|
|
@@ -39,7 +36,7 @@ export function defineEnv<
|
|
|
39
36
|
TClient extends Record<string, ZodType> = NonNullable<unknown>,
|
|
40
37
|
TShared extends Record<string, ZodType> = NonNullable<unknown>,
|
|
41
38
|
const TPrefix extends string = '',
|
|
42
|
-
const TExtends extends
|
|
39
|
+
const TExtends extends Record<string, unknown>[] = [],
|
|
43
40
|
>(config: {
|
|
44
41
|
/**
|
|
45
42
|
* Label shown in validation-error logs, e.g. `'@acme/backend'`. Without it a misconfigured
|
|
@@ -86,7 +83,7 @@ export function defineEnv<
|
|
|
86
83
|
// zero environment, and validation fires exactly when a value is actually
|
|
87
84
|
// consumed (boot, a request, a job). This is the same import/observe split
|
|
88
85
|
// drizzle tables have: define everywhere, observe only at use.
|
|
89
|
-
const build = (): Result => {
|
|
86
|
+
const build = (): Built<Result> => {
|
|
90
87
|
const skipValidation =
|
|
91
88
|
config.options?.skipValidation ?? (!!process.env.CI || process.env.NODE_ENV !== 'development');
|
|
92
89
|
|
|
@@ -138,8 +135,110 @@ export function defineEnv<
|
|
|
138
135
|
onValidationError,
|
|
139
136
|
} satisfies Record<string, unknown>;
|
|
140
137
|
|
|
141
|
-
|
|
138
|
+
const env = createEnv(options as Parameters<typeof createEnv>[0]) as Result;
|
|
139
|
+
// Skipped validation returns `runtimeEnv` itself: private only when `defineEnv` built it.
|
|
140
|
+
// Otherwise `createEnv` returns a fresh Proxy whose reads return its stored values on the server.
|
|
141
|
+
const isPrivate = skipValidation ? config.runtimeEnv === undefined : isServer();
|
|
142
|
+
return { env, isPrivate };
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return lazyEnv(build);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** The built env, and whether its reads may be copied (it is private and returns stored values). */
|
|
149
|
+
type Built<T> = { env: T; isPrivate: boolean };
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Reads an env takes through the lazy view before it copies the values for fast reads. A copy costs
|
|
153
|
+
* about as much as this many reads save (ski-rental), so an env that is read only a few times, such
|
|
154
|
+
* as one built for a single check, never pays for it, and a hot env pays it once.
|
|
155
|
+
*/
|
|
156
|
+
const READS_BEFORE_COPY = 64;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* A lazy view over the env `build()` returns: nothing runs until the first operation, then every
|
|
160
|
+
* operation forwards to the built env.
|
|
161
|
+
*
|
|
162
|
+
* Reads are the hot path. `createEnv` returns a Proxy, so a second Proxy over it pays two traps per
|
|
163
|
+
* read. Where it is safe, once the env has been read `READS_BEFORE_COPY` times, its values are copied
|
|
164
|
+
* into the Proxy's own target and the `get` trap is removed, so later reads are plain property reads
|
|
165
|
+
* on that copy. It is safe when the built object is private to this env and reads on it return its
|
|
166
|
+
* stored values:
|
|
167
|
+
* - on the server, `createEnv`'s Proxy returns stored values (its client guard is off), and its
|
|
168
|
+
* object is a fresh copy nobody else holds;
|
|
169
|
+
* - with validation skipped, `createEnv` returns the `runtimeEnv` it was given: only the one
|
|
170
|
+
* `defineEnv` built itself is private. A caller's own `runtimeEnv` stays a live view.
|
|
171
|
+
* Writes and deletes through this object update both the built env and the copy.
|
|
172
|
+
*/
|
|
173
|
+
function lazyEnv<T extends object>(build: () => Built<T>): T {
|
|
174
|
+
let built: T | undefined;
|
|
175
|
+
let canCopy = false;
|
|
176
|
+
let copied = false;
|
|
177
|
+
let reads = 0;
|
|
178
|
+
const target: Record<PropertyKey, unknown> = {};
|
|
179
|
+
|
|
180
|
+
const resolve = (): T => {
|
|
181
|
+
if (built !== undefined) return built;
|
|
182
|
+
const result = build();
|
|
183
|
+
built = result.env;
|
|
184
|
+
canCopy = result.isPrivate;
|
|
185
|
+
return built;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const copy = (instance: T): void => {
|
|
189
|
+
for (const key of Reflect.ownKeys(instance)) {
|
|
190
|
+
setValue(target, key, readValue(instance, key));
|
|
191
|
+
}
|
|
192
|
+
copied = true;
|
|
193
|
+
Reflect.deleteProperty(handler, 'get');
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const handler: ProxyHandler<Record<PropertyKey, unknown>> = {
|
|
197
|
+
get: (_target, property) => {
|
|
198
|
+
const instance = resolve();
|
|
199
|
+
if (canCopy && ++reads >= READS_BEFORE_COPY) {
|
|
200
|
+
copy(instance);
|
|
201
|
+
return target[property];
|
|
202
|
+
}
|
|
203
|
+
return readValue(instance, property);
|
|
204
|
+
},
|
|
205
|
+
set: (_target, property, value) => {
|
|
206
|
+
const instance = resolve();
|
|
207
|
+
const ok = Reflect.set(instance, property, value);
|
|
208
|
+
if (ok && copied) setValue(target, property, readValue(instance, property));
|
|
209
|
+
return ok;
|
|
210
|
+
},
|
|
211
|
+
deleteProperty: (_target, property) => {
|
|
212
|
+
const ok = Reflect.deleteProperty(resolve(), property);
|
|
213
|
+
if (ok && copied) Reflect.deleteProperty(target, property);
|
|
214
|
+
return ok;
|
|
215
|
+
},
|
|
216
|
+
getPrototypeOf: () => Reflect.getPrototypeOf(resolve()),
|
|
217
|
+
has: (_target, property) => Reflect.has(resolve(), property),
|
|
218
|
+
ownKeys: () => Reflect.ownKeys(resolve()),
|
|
219
|
+
getOwnPropertyDescriptor: (_target, property) => Object.getOwnPropertyDescriptor(resolve(), property),
|
|
142
220
|
};
|
|
143
221
|
|
|
144
|
-
return
|
|
222
|
+
return new Proxy(target, handler) as T;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Same test `createEnv` uses to decide whether its client-side access guard applies. */
|
|
226
|
+
function isServer(): boolean {
|
|
227
|
+
const { window } = globalThis as { window?: object };
|
|
228
|
+
return window === undefined || 'Deno' in window;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** A read as the old lazy view returned it: functions come back bound to the built env. */
|
|
232
|
+
function readValue(instance: object, property: PropertyKey): unknown {
|
|
233
|
+
const value: unknown = Reflect.get(instance, property, instance);
|
|
234
|
+
return typeof value === 'function' ? (value as (...args: unknown[]) => unknown).bind(instance) : value;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** An own data property. `__proto__` is defined, not assigned, so it never replaces the prototype. */
|
|
238
|
+
function setValue(target: Record<PropertyKey, unknown>, key: PropertyKey, value: unknown): void {
|
|
239
|
+
if (key === '__proto__') {
|
|
240
|
+
Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
|
|
241
|
+
} else {
|
|
242
|
+
target[key] = value;
|
|
243
|
+
}
|
|
145
244
|
}
|
package/src/dot.ts
CHANGED
|
@@ -34,9 +34,11 @@
|
|
|
34
34
|
* that is intentional: the adapter only makes sense in a DOT app.
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
|
-
import { plugin, type EmptyShape, type Plugin } from '@arki/dot/plugin';
|
|
38
37
|
import type { ZodType } from 'zod';
|
|
39
38
|
|
|
39
|
+
import type { EmptyShape, Plugin } from '@arki/dot/plugin';
|
|
40
|
+
import { plugin } from '@arki/dot/plugin';
|
|
41
|
+
|
|
40
42
|
import { defineEnv } from './core/define-env.js';
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -79,10 +81,7 @@ export function env<TSchema extends Record<string, ZodType>>(
|
|
|
79
81
|
boot() {
|
|
80
82
|
const validated = defineEnv({
|
|
81
83
|
server: options.schema,
|
|
82
|
-
options:
|
|
83
|
-
options.skipValidation !== undefined
|
|
84
|
-
? { skipValidation: options.skipValidation }
|
|
85
|
-
: undefined,
|
|
84
|
+
options: options.skipValidation === undefined ? undefined : { skipValidation: options.skipValidation },
|
|
86
85
|
});
|
|
87
86
|
// `defineEnv` returns a Proxy from `@t3-oss/env-core`; we narrow it
|
|
88
87
|
// to the schema-derived shape so downstream typing flows through.
|