@jayalfredprufrock/confetti 0.1.3 → 0.2.0

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 ADDED
@@ -0,0 +1,243 @@
1
+ <p align="center">
2
+ <img src="./confetti-logo.png" alt="confetti" width="240" />
3
+ </p>
4
+
5
+ <h1 align="center">confetti</h1>
6
+
7
+ <p align="center">
8
+ Type-safe, multi-environment configuration where <strong>your config file is the source of truth</strong>.
9
+ </p>
10
+
11
+ ---
12
+
13
+ ## Why
14
+
15
+ - **Ultra type-safe.** Paths and return values are fully inferred — `get('nested.prop')` gives you autocomplete and the correct type, no casting.
16
+ - **Environments, side-by-side.** Staging and prod values live next to each other in the config, not scattered across files.
17
+ - **Source of truth**. Making something configurable only requires touching one file. Start with a default across environments and easily override per environment later.
18
+ - **Consumers resolve special values.** `confetti` tracks _what_ a value is (an env var, a remote secret, a default) and hands that metadata to the consumer — it doesn't dial AWS for you.
19
+
20
+ ## Install
21
+
22
+ ```sh
23
+ npm add @jayalfredprufrock/confetti
24
+ ```
25
+
26
+ ## Basic usage
27
+
28
+ ```ts
29
+ import { makeConfig, DEFAULT, ENV, DATA, TYPE } from "@jayalfredprufrock/confetti";
30
+
31
+ const config = makeConfig({
32
+ appName: "my-app",
33
+ port: 3000,
34
+
35
+ feature: {
36
+ enabled: true,
37
+ limit: 50,
38
+ },
39
+
40
+ apiUrl: {
41
+ [DEFAULT]: "http://localhost:3000",
42
+ staging: "https://api.staging.example.com",
43
+ prod: "https://api.example.com",
44
+ },
45
+
46
+ dbPassword: {
47
+ [ENV]: "DB_PASSWORD",
48
+ [DATA]: "db/password",
49
+ [DEFAULT]: "",
50
+ },
51
+
52
+ maxConnections: {
53
+ [TYPE]: "number",
54
+ [ENV]: "MAX_CONNECTIONS",
55
+ [DEFAULT]: 10,
56
+ },
57
+ });
58
+
59
+ // Pick an environment, then read values.
60
+ const cfg = config("prod");
61
+
62
+ // paths and types fully inferred
63
+ cfg.get("appName"); // => 'my-app'
64
+ cfg.get("apiUrl"); // => 'https://api.example.com'
65
+ cfg.get("port"); // => 3000 (typed as number)
66
+ ```
67
+
68
+ ### Factory/Function pattern
69
+
70
+ Use the factory form when it's more convenient to produce multi-environment default values based on a naming convention:
71
+
72
+ ```ts
73
+ const config = makeConfig((env: string) => ({
74
+ serviceName: `my-app-${env}`,
75
+ dbPassword: {
76
+ [ENV]: "DB_PASSWORD",
77
+ [DATA]: `${env}/password`,
78
+ [DEFAULT]: "",
79
+ },
80
+ }));
81
+ ```
82
+
83
+ Individual values can also be functions, which also provides an escape hatch if you need to provide an object as a config leaf value.
84
+
85
+ ```ts
86
+ const config = makeConfig({
87
+ serviceName: (env) => `my-app-${env}`,
88
+ objValue: (env) => ({ enabled: true, value: 42 }),
89
+ });
90
+ ```
91
+
92
+ ### Sync reads with `get`
93
+
94
+ `get(path)` is synchronous. Per-env values are selected by precedence:
95
+
96
+ 1. explicit value (no multi-env object used)
97
+ 2. `process.env[ENV]` if set (coerced per `[TYPE]` — see below)
98
+ 3. the explicit per-env value (`cfg.get('apiUrl')` in `prod` returns the `prod` value)
99
+ 4. `[DEFAULT]`, if present
100
+ 5. otherwise, throw
101
+
102
+ ```ts
103
+ cfg.get("apiUrl"); // 'https://api.example.com'
104
+ cfg.get("feature.enabled"); // true (typed as boolean)
105
+
106
+ // Entire subtrees are fine too — everything resolves synchronously.
107
+ cfg.get("feature"); // { enabled: true, limit: 50 }
108
+ ```
109
+
110
+ _If a leaf cannot be resolved syncronously, `get` throws. See `resolve` below for handling async configuration._
111
+
112
+ ### Declaring leaf types with `[TYPE]`
113
+
114
+ External values (env vars, fetched secrets) are strings by nature but your config likely wants them typed. Use `[TYPE]` to declare the runtime shape and drive both TypeScript inference and automatic coercion.
115
+
116
+ ```ts
117
+ const config = makeConfig({
118
+ port: { [TYPE]: "number", [ENV]: "PORT", [DEFAULT]: 3000 },
119
+ featureFlag: { [TYPE]: "boolean", [DATA]: "flags/checkout-v2" },
120
+ allowedOrigins: { [TYPE]: "string[]", [ENV]: "ALLOWED_ORIGINS", [DEFAULT]: [] },
121
+ });
122
+
123
+ const cfg = config("prod");
124
+ cfg.get("port"); // typed as number — env var "8080" coerced to 8080
125
+ ```
126
+
127
+ Supported tags: `"string" | "number" | "boolean" | "string[]" | "number[]" | "boolean[]"`.
128
+
129
+ **Coercion rules (env vars and string fetcher returns):**
130
+
131
+ | Tag | Expected raw |
132
+ | --------- | --------------------------------------------------- |
133
+ | `string` | as-is |
134
+ | `number` | `Number(raw)`; empty or `NaN` throws |
135
+ | `boolean` | exactly `"true"` or `"false"`; anything else throws |
136
+ | `T[]` | `JSON.parse` + array check + element type check |
137
+
138
+ `[TYPE]` also constrains `[DEFAULT]` and per-env values at compile time — `{ [TYPE]: "number", [DEFAULT]: "nope" }` is a type error.
139
+
140
+ **When `[ENV]` or `[DATA]` are present without `[TYPE]`:** values are required to be strings (both `[DEFAULT]` and per-env overrides). The fetcher must also return a string. If you need a non-string here, add `[TYPE]`.
141
+
142
+ ### Async reads with `resolve`
143
+
144
+ `resolve(path, fetcher)` hands off to your code whenever a leaf can't be satisfied synchronously. You decide how to resolve it — read AWS Secrets Manager, call Vault, hit Parameter Store, whatever.
145
+
146
+ ```ts
147
+ const password = await cfg.resolve("dbPassword", async (ctx) => {
148
+ // ctx = { env: 'prod', envVar: 'DB_PASSWORD', data: 'prod/db/password', default: '' }
149
+ const secret = await secretsClient.getSecretValue({ SecretId: ctx.data });
150
+ return secret.SecretString;
151
+ });
152
+ ```
153
+
154
+ Rules:
155
+
156
+ - Explicit values and env overrides still win — the fetcher is only called when there's no sync value.
157
+ - Return `undefined` from the fetcher to fall back to `[DEFAULT]`.
158
+ - If `[TYPE]` is declared, a string return is coerced; a non-string must match `[TYPE]` exactly or it throws.
159
+ - Resolving a subtree calls the fetcher once per leaf that needs it; static leaves pass through untouched.
160
+
161
+ ### Walking the config with `entries`
162
+
163
+ `entries()` returns a lazy iterator that yields `[path, entry]` for every leaf. Use it to drive downstream tooling — synthesize IaC secret resources, check for unset values in CI, etc.
164
+
165
+ ```ts
166
+ for (const [path, entry] of cfg.entries()) {
167
+ if (entry.envVar) {
168
+ console.log(`${path} ← process.env.${entry.envVar}`);
169
+ }
170
+ if (entry.data) {
171
+ console.log(`${path} ← secret @ ${entry.data}`);
172
+ }
173
+ }
174
+ ```
175
+
176
+ Each entry has the shape:
177
+
178
+ ```ts
179
+ { path: string; value?: unknown; default?: unknown; envVar?: string; data?: unknown; type?: TypeTag }
180
+ ```
181
+
182
+ `value` is present if a syncronous value is available — useful for distinguishing "already known" from "needs fetching" without resolving anything.
183
+
184
+ Pass a subtree path to scope iteration to that part of the config. Paths are typed — only subtree paths compile, leaves are rejected.
185
+
186
+ ```ts
187
+ for (const [path, entry] of cfg.entries("feature")) {
188
+ // path is e.g. "feature.enabled", "feature.limit"
189
+ }
190
+ ```
191
+
192
+ ## Real-world use cases
193
+
194
+ ### Generate a required env var list for your deploy pipeline
195
+
196
+ ```ts
197
+ const required = Array.from(cfg.entries())
198
+ .filter(([, entry]) => entry.envVar && entry.value === undefined)
199
+ .map(([, entry]) => entry.envVar!);
200
+ ```
201
+
202
+ ### Synthesize Terraform / Pulumi secret resources
203
+
204
+ ```ts
205
+ for (const [path, entry] of cfg.entries()) {
206
+ if (!entry.data) continue;
207
+ new aws.secretsmanager.Secret(path, { name: entry.data as string });
208
+ }
209
+ ```
210
+
211
+ ### Fetch everything you need at boot
212
+
213
+ ```ts
214
+ const secrets = await cfg.resolve("secrets", async ({ data }) => {
215
+ return await secretsClient
216
+ .getSecretValue({ SecretId: data as string })
217
+ .then((r) => r.SecretString);
218
+ });
219
+ ```
220
+
221
+ ### Validate config is ready before starting
222
+
223
+ ```ts
224
+ for (const [path, entry] of cfg.entries()) {
225
+ if (entry.value === undefined && entry.default === undefined && !entry.data) {
226
+ throw new Error(`Config '${path}' has no resolvable value.`);
227
+ }
228
+ }
229
+ ```
230
+
231
+ ## API
232
+
233
+ | Method | Signature | Notes |
234
+ | ------------------ | --------------------------------------------------- | ------------------------------------------------------------------------------ |
235
+ | `makeConfig` | `(config \| (env) => config) => Confetti` | Accepts a config object or factory fn. |
236
+ | `confetti(env)` | `(env: string) => Accessor` | Binds an environment. |
237
+ | `accessor.get` | `(path) => value` | Sync. Throws if async resolution is required. |
238
+ | `accessor.resolve` | `(path, fetcher) => Promise<value>` | Async. Fetcher invoked per leaf that needs it. |
239
+ | `accessor.entries` | `(startPath?) => IterableIterator<[string, Entry]>` | Lazy iterator of every leaf with its metadata; optionally scoped to a subtree. |
240
+
241
+ ## License
242
+
243
+ MIT
package/dist/index.cjs CHANGED
@@ -2,115 +2,179 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#region src/symbols.ts
3
3
  const CONFETTI = Symbol.for("CONFETTI");
4
4
  const DEFAULT = Symbol.for("CONFETTI_DEFAULT");
5
- const ENV_VAR = Symbol.for("CONFETTI_ENV_VAR");
5
+ const ENV = Symbol.for("CONFETTI_ENV");
6
6
  const DATA = Symbol.for("CONFETTI_DATA");
7
+ const TYPE = Symbol.for("CONFETTI_TYPE");
7
8
  //#endregion
8
9
  //#region src/util.ts
9
10
  const isObj = (val) => {
10
11
  return val !== null && typeof val === "object" && !Array.isArray(val);
11
12
  };
12
13
  const isConfigValPerEnv = (val) => {
13
- return isObj(val) && (DEFAULT in val || DATA in val || ENV_VAR in val);
14
+ return isObj(val) && (DEFAULT in val || DATA in val || ENV in val || TYPE in val);
15
+ };
16
+ const isNestedConfig = (val) => {
17
+ return isObj(val) && !isConfigValPerEnv(val);
14
18
  };
15
19
  const isConfetti = (val) => {
16
20
  return typeof val === "function" && Object.hasOwn(val, CONFETTI);
17
21
  };
18
- const configFlatMap = (env, obj, map, _currentPath = "") => {
19
- return Object.entries(obj).flatMap(([key, value]) => {
20
- const path = `${_currentPath}${key}`;
21
- if (isObj(value)) {
22
- if (isConfigValPerEnv(value)) {
23
- let unresolvedValue;
24
- if (value[ENV_VAR] && process.env[value[ENV_VAR]] !== void 0) try {
25
- unresolvedValue = JSON.parse(process.env[value[ENV_VAR]]);
26
- } catch {
27
- unresolvedValue = process.env[value[ENV_VAR]];
28
- }
29
- else if (value[env] !== void 0) unresolvedValue = value[env];
30
- else if (value[DEFAULT] !== void 0) unresolvedValue = value[DEFAULT];
31
- if (unresolvedValue === void 0) throw new Error(`Unable to find '${env}' config value at '${path}`);
32
- return map({
33
- path,
34
- unresolvedValue,
35
- envVar: value[ENV_VAR],
36
- data: value[DATA]
37
- });
38
- }
39
- return configFlatMap(env, value, map, `${path}.`);
40
- }
41
- return map({
42
- path,
43
- unresolvedValue: value
44
- });
45
- });
22
+ const isThenable = (v) => {
23
+ return v !== null && typeof v === "object" && "then" in v && typeof v.then === "function";
46
24
  };
47
- const setAtPath = (obj, path, value) => {
25
+ const getAtPath = (config, path) => {
26
+ if (!path) return config;
48
27
  const segments = path.split(".");
49
- let objAtSeg = obj;
50
- for (let i = 0; i < segments.length; i++) {
51
- const segment = segments[i] ?? "";
52
- if (segments.length === i + 1) objAtSeg[segment] = value;
53
- else {
54
- if (!isObj(objAtSeg[segment])) objAtSeg[segment] = {};
55
- objAtSeg = objAtSeg[segment];
56
- }
28
+ let node = config;
29
+ for (const segment of segments) {
30
+ if (!isNestedConfig(node)) throw new Error(`Invalid config path '${path}'.`);
31
+ node = node[segment];
32
+ if (node === void 0) throw new Error(`Invalid config path '${path}'.`);
57
33
  }
34
+ return node;
35
+ };
36
+ const matchesType = (val, type) => {
37
+ if (type === "string") return typeof val === "string";
38
+ if (type === "number") return typeof val === "number" && !Number.isNaN(val);
39
+ if (type === "boolean") return typeof val === "boolean";
40
+ if (!Array.isArray(val)) return false;
41
+ const elem = type.slice(0, -2);
42
+ return val.every((x) => typeof x === elem);
43
+ };
44
+ const coerceFromString = (raw, type, path) => {
45
+ if (type === "string") return raw;
46
+ if (type === "number") {
47
+ if (raw === "") throw new Error(`Cannot coerce empty string to number at '${path}'.`);
48
+ const n = Number(raw);
49
+ if (Number.isNaN(n)) throw new Error(`Cannot coerce '${raw}' to number at '${path}'.`);
50
+ return n;
51
+ }
52
+ if (type === "boolean") {
53
+ if (raw === "true") return true;
54
+ if (raw === "false") return false;
55
+ throw new Error(`Cannot coerce '${raw}' to boolean at '${path}' (expected 'true' or 'false').`);
56
+ }
57
+ let parsed;
58
+ try {
59
+ parsed = JSON.parse(raw);
60
+ } catch {
61
+ throw new Error(`Cannot coerce '${raw}' to ${type} at '${path}' (invalid JSON).`);
62
+ }
63
+ if (!Array.isArray(parsed)) throw new Error(`Cannot coerce '${raw}' to ${type} at '${path}' (not an array).`);
64
+ const elem = type.slice(0, -2);
65
+ if (!parsed.every((x) => typeof x === elem)) throw new Error(`Cannot coerce '${raw}' to ${type} at '${path}' (element type mismatch).`);
66
+ return parsed;
67
+ };
68
+ const coerceFetched = (fetched, type, path) => {
69
+ if (type === void 0) {
70
+ if (typeof fetched !== "string") throw new Error(`Fetcher for '${path}' must return a string (add [TYPE] to use non-string values).`);
71
+ return fetched;
72
+ }
73
+ if (typeof fetched === "string") return coerceFromString(fetched, type, path);
74
+ if (matchesType(fetched, type)) return fetched;
75
+ throw new Error(`Fetcher for '${path}' returned value that doesn't match [TYPE] '${type}'.`);
76
+ };
77
+ const buildEntry = (node, path, env) => {
78
+ if (!isConfigValPerEnv(node)) return {
79
+ path,
80
+ value: node
81
+ };
82
+ const envVar = node[ENV];
83
+ const data = node[DATA];
84
+ const defaultVal = node[DEFAULT];
85
+ const typeTag = node[TYPE];
86
+ const entry = { path };
87
+ if (envVar && process.env[envVar] !== void 0) {
88
+ const raw = process.env[envVar];
89
+ entry.value = typeTag ? coerceFromString(raw, typeTag, path) : raw;
90
+ } else if (node[env] !== void 0) entry.value = node[env];
91
+ if (defaultVal !== void 0) entry.default = defaultVal;
92
+ if (envVar) entry.envVar = envVar;
93
+ if (data !== void 0) entry.data = data;
94
+ if (typeTag !== void 0) entry.type = typeTag;
95
+ return entry;
96
+ };
97
+ function* entriesIter(config, env, pathPrefix = "") {
98
+ for (const [key, value] of Object.entries(config)) {
99
+ const path = pathPrefix + key;
100
+ if (isNestedConfig(value)) yield* entriesIter(value, env, `${path}.`);
101
+ else yield [path, buildEntry(value, path, env)];
102
+ }
103
+ }
104
+ const invokeSync = (source, path, env) => {
105
+ if (typeof source !== "function") return source;
106
+ const result = source(env);
107
+ if (isThenable(result)) throw new Error(`Config at '${path}' requires async resolution (use resolve).`);
108
+ return result;
109
+ };
110
+ const invokeAsync = async (source, env) => {
111
+ return typeof source === "function" ? await source(env) : source;
112
+ };
113
+ const resolveEntrySync = (entry, env) => {
114
+ if ("value" in entry) return invokeSync(entry.value, entry.path, env);
115
+ if ("default" in entry) return invokeSync(entry.default, entry.path, env);
116
+ throw new Error(`Config at '${entry.path}' requires async resolution (use resolve).`);
117
+ };
118
+ const resolveEntryAsync = async (entry, env, fetcher) => {
119
+ if ("value" in entry) return invokeAsync(entry.value, env);
120
+ if (entry.envVar !== void 0 || entry.data !== void 0) {
121
+ const fetched = await fetcher({
122
+ env,
123
+ default: entry.default,
124
+ envVar: entry.envVar,
125
+ data: entry.data,
126
+ type: entry.type
127
+ });
128
+ if (fetched !== void 0) return coerceFetched(fetched, entry.type, entry.path);
129
+ }
130
+ if ("default" in entry) return invokeAsync(entry.default, env);
131
+ throw new Error(`Unable to resolve config value at '${entry.path}'.`);
132
+ };
133
+ const resolveSubtreeSync = (node, prefix, env) => {
134
+ const result = {};
135
+ for (const [key, child] of Object.entries(node)) {
136
+ const path = prefix ? `${prefix}.${key}` : key;
137
+ result[key] = isNestedConfig(child) ? resolveSubtreeSync(child, path, env) : resolveEntrySync(buildEntry(child, path, env), env);
138
+ }
139
+ return result;
140
+ };
141
+ const resolveSubtreeAsync = async (node, prefix, env, fetcher) => {
142
+ const keys = Object.keys(node);
143
+ const values = await Promise.all(keys.map((key) => {
144
+ const child = node[key];
145
+ const path = prefix ? `${prefix}.${key}` : key;
146
+ return isNestedConfig(child) ? resolveSubtreeAsync(child, path, env, fetcher) : resolveEntryAsync(buildEntry(child, path, env), env, fetcher);
147
+ }));
148
+ const result = {};
149
+ keys.forEach((key, i) => {
150
+ result[key] = values[i];
151
+ });
152
+ return result;
58
153
  };
59
154
  //#endregion
60
155
  //#region src/make-config.ts
61
- const makeConfig = (makeConfig) => {
156
+ const makeConfig = (input) => {
157
+ const factory = typeof input === "function" ? input : () => input;
62
158
  const confetti = (env) => {
63
- const config = makeConfig(env);
64
- const flatMap = (transform) => configFlatMap(env, config, transform);
65
- const contextByPath = Object.fromEntries(flatMap((context) => [[context.path, context]]));
66
- const resolveValue = async (path) => {
67
- const context = contextByPath[path];
68
- if (!context) throw new Error(`Invalid config path '${path}'.`);
69
- const { unresolvedValue } = context;
70
- const value = typeof unresolvedValue === "function" ? await unresolvedValue(context) : unresolvedValue;
71
- if (value === void 0) throw new Error(`Config value at '${path}' resolved to undefined.`);
72
- return value;
73
- };
74
- const resolveValueSync = (path) => {
75
- const context = contextByPath[path];
76
- if (!context) throw new Error(`Invalid config path '${path}'.`);
77
- const { unresolvedValue } = context;
78
- if (typeof unresolvedValue === "function") throw new Error(`Cannot resolve config value at "${path}" synchronously.`);
79
- return unresolvedValue;
80
- };
81
- const resolve = async () => {
82
- const resolvedConfig = {};
83
- const promises = Object.keys(contextByPath).map(async (path) => {
84
- return resolveValue(path).then((resolvedValue) => setAtPath(resolvedConfig, path, resolvedValue));
85
- });
86
- await Promise.all(promises);
87
- return resolvedConfig;
159
+ const config = factory(env);
160
+ const get = (path) => {
161
+ const node = getAtPath(config, path);
162
+ return isNestedConfig(node) ? resolveSubtreeSync(node, path, env) : resolveEntrySync(buildEntry(node, path, env), env);
88
163
  };
89
- const resolveSync = () => {
90
- const resolvedConfig = {};
91
- for (const path of Object.keys(contextByPath)) setAtPath(resolvedConfig, path, resolveValueSync(path));
92
- return resolvedConfig;
164
+ const resolve = async (path, fetcher) => {
165
+ const node = getAtPath(config, path);
166
+ return isNestedConfig(node) ? await resolveSubtreeAsync(node, path, env, fetcher) : await resolveEntryAsync(buildEntry(node, path, env), env, fetcher);
93
167
  };
94
- const resolveEnv = async () => {
95
- const envVars = {};
96
- const promises = Object.values(contextByPath).flatMap(async (context) => {
97
- const { envVar } = context;
98
- if (!envVar) return [];
99
- return resolveValue(context.path).then((resolvedValue) => {
100
- envVars[envVar] = JSON.stringify(resolvedValue);
101
- });
102
- });
103
- await Promise.all(promises);
104
- return envVars;
168
+ const entries = (startPath) => {
169
+ const root = startPath ? getAtPath(config, startPath) : config;
170
+ if (!isNestedConfig(root)) throw new Error(`'${startPath}' is not a config subtree.`);
171
+ return entriesIter(root, env, startPath ? `${startPath}.` : "");
105
172
  };
106
173
  return {
107
174
  config,
108
- flatMap,
109
- resolveValue,
110
- resolveValueSync,
175
+ get,
111
176
  resolve,
112
- resolveSync,
113
- resolveEnv
177
+ entries
114
178
  };
115
179
  };
116
180
  confetti[CONFETTI] = "CONFETTI";
@@ -120,12 +184,23 @@ const makeConfig = (makeConfig) => {
120
184
  exports.CONFETTI = CONFETTI;
121
185
  exports.DATA = DATA;
122
186
  exports.DEFAULT = DEFAULT;
123
- exports.ENV_VAR = ENV_VAR;
124
- exports.configFlatMap = configFlatMap;
187
+ exports.ENV = ENV;
188
+ exports.TYPE = TYPE;
189
+ exports.buildEntry = buildEntry;
190
+ exports.coerceFromString = coerceFromString;
191
+ exports.entriesIter = entriesIter;
192
+ exports.getAtPath = getAtPath;
193
+ exports.invokeAsync = invokeAsync;
194
+ exports.invokeSync = invokeSync;
125
195
  exports.isConfetti = isConfetti;
126
196
  exports.isConfigValPerEnv = isConfigValPerEnv;
197
+ exports.isNestedConfig = isNestedConfig;
127
198
  exports.isObj = isObj;
199
+ exports.isThenable = isThenable;
128
200
  exports.makeConfig = makeConfig;
129
- exports.setAtPath = setAtPath;
201
+ exports.resolveEntryAsync = resolveEntryAsync;
202
+ exports.resolveEntrySync = resolveEntrySync;
203
+ exports.resolveSubtreeAsync = resolveSubtreeAsync;
204
+ exports.resolveSubtreeSync = resolveSubtreeSync;
130
205
 
131
206
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/symbols.ts","../src/util.ts","../src/make-config.ts"],"sourcesContent":["export const CONFETTI: unique symbol = Symbol.for(\"CONFETTI\");\nexport const DEFAULT: unique symbol = Symbol.for(\"CONFETTI_DEFAULT\");\nexport const ENV_VAR: unique symbol = Symbol.for(\"CONFETTI_ENV_VAR\");\nexport const DATA: unique symbol = Symbol.for(\"CONFETTI_DATA\");\n","import { CONFETTI, DATA, DEFAULT, ENV_VAR } from \"./symbols\";\nimport type { Confetti, ConfigFlatMapContext, ConfigVal, ConfigValPerEnv, Obj } from \"./types\";\n\nexport const isObj = (val: unknown): val is Obj => {\n return val !== null && typeof val === \"object\" && !Array.isArray(val);\n};\n\nexport const isConfigValPerEnv = (val: unknown): val is ConfigValPerEnv => {\n return isObj(val) && (DEFAULT in val || DATA in val || ENV_VAR in val);\n};\n\nexport const isConfetti = (val: unknown): val is Confetti<any> => {\n return typeof val === \"function\" && Object.hasOwn(val, CONFETTI);\n};\n\nexport const configFlatMap = <T>(\n env: string,\n obj: Obj,\n map: (context: ConfigFlatMapContext) => T | T[],\n _currentPath = \"\",\n): T[] => {\n return Object.entries(obj).flatMap(([key, value]) => {\n const path = `${_currentPath}${key}`;\n\n if (isObj(value)) {\n if (isConfigValPerEnv(value)) {\n let unresolvedValue: ConfigVal | undefined;\n\n // first resolve environment variable if it exists\n if (value[ENV_VAR] && process.env[value[ENV_VAR]] !== undefined) {\n try {\n unresolvedValue = JSON.parse(process.env[value[ENV_VAR]]!);\n } catch {\n unresolvedValue = process.env[value[ENV_VAR]];\n }\n }\n // second resolve any explicit environment value\n else if (value[env] !== undefined) {\n unresolvedValue = value[env];\n }\n // finally look for a default value\n else if (value[DEFAULT] !== undefined) {\n unresolvedValue = value[DEFAULT];\n }\n\n if (unresolvedValue === undefined) {\n throw new Error(`Unable to find '${env}' config value at '${path}`);\n }\n\n return map({ path, unresolvedValue, envVar: value[ENV_VAR], data: value[DATA] });\n }\n\n return configFlatMap(env, value, map, `${path}.`);\n }\n\n return map({ path, unresolvedValue: value as ConfigVal });\n });\n};\n\nexport const setAtPath = (obj: Obj, path: string, value: unknown) => {\n const segments = path.split(\".\");\n\n let objAtSeg: any = obj;\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i] ?? \"\";\n if (segments.length === i + 1) {\n objAtSeg[segment] = value;\n } else {\n if (!isObj(objAtSeg[segment])) {\n objAtSeg[segment] = {};\n }\n objAtSeg = objAtSeg[segment];\n }\n }\n};\n","import { CONFETTI } from \"./symbols\";\nimport type { Confetti, Config, ConfigFlatMapContext, Obj, ResolvedConfig } from \"./types\";\nimport { configFlatMap, setAtPath } from \"./util\";\n\nexport const makeConfig = <C extends Config>(makeConfig: (env: string) => C): Confetti<C> => {\n const confetti = (env: string) => {\n const config = makeConfig(env);\n\n const flatMap = <T>(transform: (context: ConfigFlatMapContext) => T | T[]) =>\n configFlatMap<T>(env, config, transform);\n\n const contextByPath = Object.fromEntries(flatMap((context) => [[context.path, context]]));\n\n const resolveValue = async (path: string): Promise<any> => {\n const context = contextByPath[path];\n if (!context) throw new Error(`Invalid config path '${path}'.`);\n const { unresolvedValue } = context;\n const value =\n typeof unresolvedValue === \"function\" ? await unresolvedValue(context) : unresolvedValue;\n if (value === undefined) {\n throw new Error(`Config value at '${path}' resolved to undefined.`);\n }\n return value;\n };\n\n const resolveValueSync = (path: string): any => {\n const context = contextByPath[path];\n if (!context) throw new Error(`Invalid config path '${path}'.`);\n const { unresolvedValue } = context;\n if (typeof unresolvedValue === \"function\") {\n throw new Error(`Cannot resolve config value at \"${path}\" synchronously.`);\n }\n return unresolvedValue;\n };\n\n const resolve = async (): Promise<any> => {\n const resolvedConfig: Obj = {};\n const promises = Object.keys(contextByPath).map(async (path) => {\n return resolveValue(path).then((resolvedValue) =>\n setAtPath(resolvedConfig, path, resolvedValue),\n );\n });\n\n await Promise.all(promises);\n\n return resolvedConfig;\n };\n\n const resolveSync = (): any => {\n const resolvedConfig: Obj = {};\n for (const path of Object.keys(contextByPath)) {\n setAtPath(resolvedConfig, path, resolveValueSync(path));\n }\n return resolvedConfig as ResolvedConfig<C>;\n };\n\n const resolveEnv = async (): Promise<any> => {\n const envVars: Obj<string> = {};\n const promises = Object.values(contextByPath).flatMap(async (context) => {\n const { envVar } = context;\n if (!envVar) return [];\n return resolveValue(context.path).then((resolvedValue) => {\n envVars[envVar] = JSON.stringify(resolvedValue);\n });\n });\n\n await Promise.all(promises);\n\n return envVars;\n };\n\n return {\n config,\n flatMap,\n resolveValue,\n resolveValueSync,\n resolve,\n resolveSync,\n resolveEnv,\n };\n };\n\n confetti[CONFETTI] = \"CONFETTI\" as const;\n\n return confetti;\n};\n"],"mappings":";;AAAA,MAAa,WAA0B,OAAO,IAAI,WAAW;AAC7D,MAAa,UAAyB,OAAO,IAAI,mBAAmB;AACpE,MAAa,UAAyB,OAAO,IAAI,mBAAmB;AACpE,MAAa,OAAsB,OAAO,IAAI,gBAAgB;;;ACA9D,MAAa,SAAS,QAA6B;AACjD,QAAO,QAAQ,QAAQ,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,IAAI;;AAGvE,MAAa,qBAAqB,QAAyC;AACzE,QAAO,MAAM,IAAI,KAAK,WAAW,OAAO,QAAQ,OAAO,WAAW;;AAGpE,MAAa,cAAc,QAAuC;AAChE,QAAO,OAAO,QAAQ,cAAc,OAAO,OAAO,KAAK,SAAS;;AAGlE,MAAa,iBACX,KACA,KACA,KACA,eAAe,OACP;AACR,QAAO,OAAO,QAAQ,IAAI,CAAC,SAAS,CAAC,KAAK,WAAW;EACnD,MAAM,OAAO,GAAG,eAAe;AAE/B,MAAI,MAAM,MAAM,EAAE;AAChB,OAAI,kBAAkB,MAAM,EAAE;IAC5B,IAAI;AAGJ,QAAI,MAAM,YAAY,QAAQ,IAAI,MAAM,cAAc,KAAA,EACpD,KAAI;AACF,uBAAkB,KAAK,MAAM,QAAQ,IAAI,MAAM,UAAW;YACpD;AACN,uBAAkB,QAAQ,IAAI,MAAM;;aAI/B,MAAM,SAAS,KAAA,EACtB,mBAAkB,MAAM;aAGjB,MAAM,aAAa,KAAA,EAC1B,mBAAkB,MAAM;AAG1B,QAAI,oBAAoB,KAAA,EACtB,OAAM,IAAI,MAAM,mBAAmB,IAAI,qBAAqB,OAAO;AAGrE,WAAO,IAAI;KAAE;KAAM;KAAiB,QAAQ,MAAM;KAAU,MAAM,MAAM;KAAO,CAAC;;AAGlF,UAAO,cAAc,KAAK,OAAO,KAAK,GAAG,KAAK,GAAG;;AAGnD,SAAO,IAAI;GAAE;GAAM,iBAAiB;GAAoB,CAAC;GACzD;;AAGJ,MAAa,aAAa,KAAU,MAAc,UAAmB;CACnE,MAAM,WAAW,KAAK,MAAM,IAAI;CAEhC,IAAI,WAAgB;AACpB,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,UAAU,SAAS,MAAM;AAC/B,MAAI,SAAS,WAAW,IAAI,EAC1B,UAAS,WAAW;OACf;AACL,OAAI,CAAC,MAAM,SAAS,SAAS,CAC3B,UAAS,WAAW,EAAE;AAExB,cAAW,SAAS;;;;;;ACnE1B,MAAa,cAAgC,eAAgD;CAC3F,MAAM,YAAY,QAAgB;EAChC,MAAM,SAAS,WAAW,IAAI;EAE9B,MAAM,WAAc,cAClB,cAAiB,KAAK,QAAQ,UAAU;EAE1C,MAAM,gBAAgB,OAAO,YAAY,SAAS,YAAY,CAAC,CAAC,QAAQ,MAAM,QAAQ,CAAC,CAAC,CAAC;EAEzF,MAAM,eAAe,OAAO,SAA+B;GACzD,MAAM,UAAU,cAAc;AAC9B,OAAI,CAAC,QAAS,OAAM,IAAI,MAAM,wBAAwB,KAAK,IAAI;GAC/D,MAAM,EAAE,oBAAoB;GAC5B,MAAM,QACJ,OAAO,oBAAoB,aAAa,MAAM,gBAAgB,QAAQ,GAAG;AAC3E,OAAI,UAAU,KAAA,EACZ,OAAM,IAAI,MAAM,oBAAoB,KAAK,0BAA0B;AAErE,UAAO;;EAGT,MAAM,oBAAoB,SAAsB;GAC9C,MAAM,UAAU,cAAc;AAC9B,OAAI,CAAC,QAAS,OAAM,IAAI,MAAM,wBAAwB,KAAK,IAAI;GAC/D,MAAM,EAAE,oBAAoB;AAC5B,OAAI,OAAO,oBAAoB,WAC7B,OAAM,IAAI,MAAM,mCAAmC,KAAK,kBAAkB;AAE5E,UAAO;;EAGT,MAAM,UAAU,YAA0B;GACxC,MAAM,iBAAsB,EAAE;GAC9B,MAAM,WAAW,OAAO,KAAK,cAAc,CAAC,IAAI,OAAO,SAAS;AAC9D,WAAO,aAAa,KAAK,CAAC,MAAM,kBAC9B,UAAU,gBAAgB,MAAM,cAAc,CAC/C;KACD;AAEF,SAAM,QAAQ,IAAI,SAAS;AAE3B,UAAO;;EAGT,MAAM,oBAAyB;GAC7B,MAAM,iBAAsB,EAAE;AAC9B,QAAK,MAAM,QAAQ,OAAO,KAAK,cAAc,CAC3C,WAAU,gBAAgB,MAAM,iBAAiB,KAAK,CAAC;AAEzD,UAAO;;EAGT,MAAM,aAAa,YAA0B;GAC3C,MAAM,UAAuB,EAAE;GAC/B,MAAM,WAAW,OAAO,OAAO,cAAc,CAAC,QAAQ,OAAO,YAAY;IACvE,MAAM,EAAE,WAAW;AACnB,QAAI,CAAC,OAAQ,QAAO,EAAE;AACtB,WAAO,aAAa,QAAQ,KAAK,CAAC,MAAM,kBAAkB;AACxD,aAAQ,UAAU,KAAK,UAAU,cAAc;MAC/C;KACF;AAEF,SAAM,QAAQ,IAAI,SAAS;AAE3B,UAAO;;AAGT,SAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACD;;AAGH,UAAS,YAAY;AAErB,QAAO"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/symbols.ts","../src/util.ts","../src/make-config.ts"],"sourcesContent":["export const CONFETTI: unique symbol = Symbol.for(\"CONFETTI\");\nexport const DEFAULT: unique symbol = Symbol.for(\"CONFETTI_DEFAULT\");\nexport const ENV: unique symbol = Symbol.for(\"CONFETTI_ENV\");\nexport const DATA: unique symbol = Symbol.for(\"CONFETTI_DATA\");\nexport const TYPE: unique symbol = Symbol.for(\"CONFETTI_TYPE\");\n","import { CONFETTI, DATA, DEFAULT, ENV, TYPE } from \"./symbols\";\nimport type {\n ConfigEntry,\n Confetti,\n ConfigValPerEnv,\n Fetcher,\n FetcherContext,\n Obj,\n TypeTag,\n} from \"./types\";\n\nexport const isObj = (val: unknown): val is Obj => {\n return val !== null && typeof val === \"object\" && !Array.isArray(val);\n};\n\nexport const isConfigValPerEnv = (val: unknown): val is ConfigValPerEnv => {\n return isObj(val) && (DEFAULT in val || DATA in val || ENV in val || TYPE in val);\n};\n\nexport const isNestedConfig = (val: unknown): val is Obj => {\n return isObj(val) && !isConfigValPerEnv(val);\n};\n\nexport const isConfetti = (val: unknown): val is Confetti<any> => {\n return typeof val === \"function\" && Object.hasOwn(val, CONFETTI);\n};\n\nexport const isThenable = (v: unknown): v is PromiseLike<unknown> => {\n return (\n v !== null &&\n typeof v === \"object\" &&\n \"then\" in v &&\n typeof (v as { then: unknown }).then === \"function\"\n );\n};\n\nexport const getAtPath = (config: Obj, path: string): unknown => {\n if (!path) return config;\n const segments = path.split(\".\");\n let node: unknown = config;\n for (const segment of segments) {\n if (!isNestedConfig(node)) {\n throw new Error(`Invalid config path '${path}'.`);\n }\n node = node[segment];\n if (node === undefined) {\n throw new Error(`Invalid config path '${path}'.`);\n }\n }\n return node;\n};\n\nconst matchesType = (val: unknown, type: TypeTag): boolean => {\n if (type === \"string\") return typeof val === \"string\";\n if (type === \"number\") return typeof val === \"number\" && !Number.isNaN(val);\n if (type === \"boolean\") return typeof val === \"boolean\";\n if (!Array.isArray(val)) return false;\n const elem = type.slice(0, -2);\n return val.every((x) => typeof x === elem);\n};\n\nexport const coerceFromString = (raw: string, type: TypeTag, path: string): unknown => {\n if (type === \"string\") return raw;\n if (type === \"number\") {\n if (raw === \"\") throw new Error(`Cannot coerce empty string to number at '${path}'.`);\n const n = Number(raw);\n if (Number.isNaN(n)) throw new Error(`Cannot coerce '${raw}' to number at '${path}'.`);\n return n;\n }\n if (type === \"boolean\") {\n if (raw === \"true\") return true;\n if (raw === \"false\") return false;\n throw new Error(`Cannot coerce '${raw}' to boolean at '${path}' (expected 'true' or 'false').`);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`Cannot coerce '${raw}' to ${type} at '${path}' (invalid JSON).`);\n }\n if (!Array.isArray(parsed)) {\n throw new Error(`Cannot coerce '${raw}' to ${type} at '${path}' (not an array).`);\n }\n const elem = type.slice(0, -2);\n if (!parsed.every((x) => typeof x === elem)) {\n throw new Error(`Cannot coerce '${raw}' to ${type} at '${path}' (element type mismatch).`);\n }\n return parsed;\n};\n\nconst coerceFetched = (fetched: unknown, type: TypeTag | undefined, path: string): unknown => {\n if (type === undefined) {\n if (typeof fetched !== \"string\") {\n throw new Error(\n `Fetcher for '${path}' must return a string (add [TYPE] to use non-string values).`,\n );\n }\n return fetched;\n }\n if (typeof fetched === \"string\") return coerceFromString(fetched, type, path);\n if (matchesType(fetched, type)) return fetched;\n throw new Error(`Fetcher for '${path}' returned value that doesn't match [TYPE] '${type}'.`);\n};\n\nexport const buildEntry = (node: unknown, path: string, env: string): ConfigEntry => {\n if (!isConfigValPerEnv(node)) {\n return { path, value: node };\n }\n\n const envVar = node[ENV];\n const data = node[DATA];\n const defaultVal = node[DEFAULT];\n const typeTag = node[TYPE];\n const entry: ConfigEntry = { path };\n\n if (envVar && process.env[envVar] !== undefined) {\n const raw = process.env[envVar]!;\n entry.value = typeTag ? coerceFromString(raw, typeTag, path) : raw;\n } else if (node[env] !== undefined) {\n entry.value = node[env];\n }\n\n if (defaultVal !== undefined) entry.default = defaultVal;\n if (envVar) entry.envVar = envVar;\n if (data !== undefined) entry.data = data;\n if (typeTag !== undefined) entry.type = typeTag;\n\n return entry;\n};\n\nexport function* entriesIter(\n config: Obj,\n env: string,\n pathPrefix = \"\",\n): IterableIterator<[string, ConfigEntry]> {\n for (const [key, value] of Object.entries(config)) {\n const path = pathPrefix + key;\n if (isNestedConfig(value)) {\n yield* entriesIter(value, env, `${path}.`);\n } else {\n yield [path, buildEntry(value, path, env)];\n }\n }\n}\n\nexport const invokeSync = (source: unknown, path: string, env: string): unknown => {\n if (typeof source !== \"function\") return source;\n const result = (source as (env: string) => unknown)(env);\n if (isThenable(result)) {\n throw new Error(`Config at '${path}' requires async resolution (use resolve).`);\n }\n return result;\n};\n\nexport const invokeAsync = async (source: unknown, env: string): Promise<unknown> => {\n return typeof source === \"function\" ? await (source as (env: string) => unknown)(env) : source;\n};\n\nexport const resolveEntrySync = (entry: ConfigEntry, env: string): unknown => {\n if (\"value\" in entry) return invokeSync(entry.value, entry.path, env);\n if (\"default\" in entry) return invokeSync(entry.default, entry.path, env);\n throw new Error(`Config at '${entry.path}' requires async resolution (use resolve).`);\n};\n\nexport const resolveEntryAsync = async (\n entry: ConfigEntry,\n env: string,\n fetcher: Fetcher,\n): Promise<unknown> => {\n if (\"value\" in entry) return invokeAsync(entry.value, env);\n\n if (entry.envVar !== undefined || entry.data !== undefined) {\n const ctx: FetcherContext = {\n env,\n default: entry.default,\n envVar: entry.envVar,\n data: entry.data,\n type: entry.type,\n };\n const fetched = await fetcher(ctx);\n if (fetched !== undefined) return coerceFetched(fetched, entry.type, entry.path);\n }\n\n if (\"default\" in entry) return invokeAsync(entry.default, env);\n throw new Error(`Unable to resolve config value at '${entry.path}'.`);\n};\n\nexport const resolveSubtreeSync = (node: Obj, prefix: string, env: string): Obj => {\n const result: Obj = {};\n for (const [key, child] of Object.entries(node)) {\n const path = prefix ? `${prefix}.${key}` : key;\n result[key] = isNestedConfig(child)\n ? resolveSubtreeSync(child, path, env)\n : resolveEntrySync(buildEntry(child, path, env), env);\n }\n return result;\n};\n\nexport const resolveSubtreeAsync = async (\n node: Obj,\n prefix: string,\n env: string,\n fetcher: Fetcher,\n): Promise<Obj> => {\n const keys = Object.keys(node);\n const values = await Promise.all(\n keys.map((key) => {\n const child = node[key];\n const path = prefix ? `${prefix}.${key}` : key;\n return isNestedConfig(child)\n ? resolveSubtreeAsync(child, path, env, fetcher)\n : resolveEntryAsync(buildEntry(child, path, env), env, fetcher);\n }),\n );\n const result: Obj = {};\n keys.forEach((key, i) => {\n result[key] = values[i];\n });\n return result;\n};\n","import { CONFETTI } from \"./symbols\";\nimport type {\n Confetti,\n Config,\n ConfigEntry,\n Fetcher,\n Paths,\n SubtreePaths,\n ValidateConfig,\n ValueAtPath,\n} from \"./types\";\nimport {\n buildEntry,\n entriesIter,\n getAtPath,\n isNestedConfig,\n resolveEntryAsync,\n resolveEntrySync,\n resolveSubtreeAsync,\n resolveSubtreeSync,\n} from \"./util\";\n\nexport const makeConfig = <const C extends Config>(\n input: (C & ValidateConfig<C>) | ((env: string) => C & ValidateConfig<C>),\n): Confetti<C> => {\n const factory = typeof input === \"function\" ? input : () => input;\n\n const confetti = (env: string) => {\n const config = factory(env);\n\n const get = <P extends Paths<C> & string>(path: P): ValueAtPath<C, P> => {\n const node = getAtPath(config, path);\n const resolved = isNestedConfig(node)\n ? resolveSubtreeSync(node, path, env)\n : resolveEntrySync(buildEntry(node, path, env), env);\n return resolved as ValueAtPath<C, P>;\n };\n\n const resolve = async <P extends Paths<C> & string>(\n path: P,\n fetcher: Fetcher,\n ): Promise<ValueAtPath<C, P>> => {\n const node = getAtPath(config, path);\n const resolved = isNestedConfig(node)\n ? await resolveSubtreeAsync(node, path, env, fetcher)\n : await resolveEntryAsync(buildEntry(node, path, env), env, fetcher);\n return resolved as ValueAtPath<C, P>;\n };\n\n const entries = (\n startPath?: SubtreePaths<C> & string,\n ): IterableIterator<[string, ConfigEntry]> => {\n const root = startPath ? getAtPath(config, startPath) : config;\n if (!isNestedConfig(root)) {\n throw new Error(`'${startPath}' is not a config subtree.`);\n }\n return entriesIter(root, env, startPath ? `${startPath}.` : \"\");\n };\n\n return { config, get, resolve, entries };\n };\n\n confetti[CONFETTI] = \"CONFETTI\" as const;\n\n return confetti as Confetti<C>;\n};\n"],"mappings":"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"}