@malloydata/malloy 0.0.372 → 0.0.373

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.
@@ -0,0 +1,34 @@
1
+ import type { LogMessage } from '../../lang/parse-log';
2
+ /**
3
+ * A compiled config node. The compiler produces a tree of these; the resolver
4
+ * walks the tree against a ConfigOverlays dict and produces a plain POJO.
5
+ */
6
+ export type ConfigNode = ConfigDict | ConfigLiteral | ConfigReference;
7
+ export interface ConfigDict {
8
+ kind: 'dict';
9
+ entries: Record<string, ConfigNode>;
10
+ }
11
+ /**
12
+ * A leaf node holding a literal — primitives for typed slots, arbitrary
13
+ * JSON for `json`-typed slots (no reference expansion, no type checking).
14
+ */
15
+ export interface ConfigLiteral {
16
+ kind: 'value';
17
+ value: unknown;
18
+ }
19
+ export interface ConfigReference {
20
+ kind: 'reference';
21
+ /** Overlay name: "env", "config", "session", etc. */
22
+ source: string;
23
+ /** Path into the overlay. */
24
+ path: string[];
25
+ }
26
+ export type SectionCompiler = (value: unknown, log: LogMessage[]) => ConfigNode | undefined;
27
+ /**
28
+ * Compile a POJO into a typed dictionary tree, collecting validation warnings.
29
+ * Does not throw — caller inspects `log` for issues.
30
+ */
31
+ export declare function compileConfig(pojo: unknown): {
32
+ compiled: ConfigDict;
33
+ log: LogMessage[];
34
+ };
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Contributors to the Malloy project
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.compileConfig = compileConfig;
8
+ const registry_1 = require("../../connection/registry");
9
+ const TOP_LEVEL_SECTIONS = {
10
+ connections: compileConnections,
11
+ manifestPath: compileManifestPath,
12
+ virtualMap: compileVirtualMap,
13
+ includeDefaultConnections: compileIncludeDefaultConnections,
14
+ };
15
+ const KNOWN_TOP_LEVEL_KEYS = new Set(Object.keys(TOP_LEVEL_SECTIONS));
16
+ // =============================================================================
17
+ // Entry point
18
+ // =============================================================================
19
+ /**
20
+ * Compile a POJO into a typed dictionary tree, collecting validation warnings.
21
+ * Does not throw — caller inspects `log` for issues.
22
+ */
23
+ function compileConfig(pojo) {
24
+ const log = [];
25
+ const compiled = { kind: 'dict', entries: {} };
26
+ if (!isRecord(pojo)) {
27
+ log.push({
28
+ message: 'config is not a JSON object',
29
+ severity: 'error',
30
+ code: 'config-validation',
31
+ });
32
+ return { compiled, log };
33
+ }
34
+ for (const [key, value] of Object.entries(pojo)) {
35
+ const compiler = TOP_LEVEL_SECTIONS[key];
36
+ if (!compiler) {
37
+ const suggestion = closestMatch(key, [...KNOWN_TOP_LEVEL_KEYS]);
38
+ const hint = suggestion ? `. Did you mean "${suggestion}"?` : '';
39
+ log.push(makeWarning(key, `unknown config key "${key}"${hint}`));
40
+ continue;
41
+ }
42
+ const node = compiler(value, log);
43
+ if (node !== undefined) {
44
+ compiled.entries[key] = node;
45
+ }
46
+ }
47
+ return { compiled, log };
48
+ }
49
+ // =============================================================================
50
+ // connections
51
+ // =============================================================================
52
+ function compileConnections(value, log) {
53
+ if (!isRecord(value)) {
54
+ log.push(makeWarning('connections', '"connections" should be an object'));
55
+ return undefined;
56
+ }
57
+ const registeredTypes = new Set((0, registry_1.getRegisteredConnectionTypes)());
58
+ const connections = { kind: 'dict', entries: {} };
59
+ for (const [name, rawEntry] of Object.entries(value)) {
60
+ const prefix = `connections.${name}`;
61
+ if (!isRecord(rawEntry)) {
62
+ log.push(makeWarning(prefix, 'should be an object'));
63
+ continue;
64
+ }
65
+ const is = rawEntry['is'];
66
+ if (is === undefined || is === null || is === '') {
67
+ log.push(makeWarning(prefix, 'missing required "is" field (connection type)'));
68
+ continue;
69
+ }
70
+ if (typeof is !== 'string') {
71
+ log.push(makeWarning(`${prefix}.is`, '"is" should be a string'));
72
+ continue;
73
+ }
74
+ if (!registeredTypes.has(is)) {
75
+ const suggestion = closestMatch(is, [...registeredTypes]);
76
+ const hint = suggestion ? ` Did you mean "${suggestion}"?` : '';
77
+ log.push(makeWarning(`${prefix}.is`, `unknown connection type "${is}".${hint} Available types: ${[...registeredTypes].join(', ')}`));
78
+ continue;
79
+ }
80
+ const entry = compileConnectionEntry(prefix, is, rawEntry, log);
81
+ connections.entries[name] = entry;
82
+ }
83
+ return connections;
84
+ }
85
+ function compileConnectionEntry(prefix, typeName, rawEntry, log) {
86
+ var _a;
87
+ const props = (_a = (0, registry_1.getConnectionProperties)(typeName)) !== null && _a !== void 0 ? _a : [];
88
+ const propMap = new Map(props.map(p => [p.name, p]));
89
+ const entry = { kind: 'dict', entries: {} };
90
+ // Record the connection type as a plain value.
91
+ entry.entries['is'] = { kind: 'value', value: typeName };
92
+ for (const [key, value] of Object.entries(rawEntry)) {
93
+ if (key === 'is')
94
+ continue;
95
+ const propDef = propMap.get(key);
96
+ if (!propDef) {
97
+ const suggestion = closestMatch(key, [...propMap.keys()]);
98
+ const hint = suggestion ? `. Did you mean "${suggestion}"?` : '';
99
+ log.push(makeWarning(`${prefix}.${key}`, `unknown property "${key}" for connection type "${typeName}"${hint}`));
100
+ continue;
101
+ }
102
+ const node = compileConnectionProperty(`${prefix}.${key}`, propDef, value, log);
103
+ if (node !== undefined) {
104
+ entry.entries[key] = node;
105
+ }
106
+ }
107
+ return entry;
108
+ }
109
+ /**
110
+ * Compile a single connection property value. At non-`json` property slots,
111
+ * a single-key object whose value is a string or string[] is recognized as
112
+ * an overlay reference. `json`-typed slots always pass through as literal
113
+ * data — this is the security invariant that prevents reference injection
114
+ * into structured config.
115
+ */
116
+ function compileConnectionProperty(path, propDef, value, log) {
117
+ if (value === undefined || value === null)
118
+ return undefined;
119
+ if (propDef.type === 'json') {
120
+ return { kind: 'value', value };
121
+ }
122
+ const ref = asReferenceShape(value);
123
+ if (ref !== undefined) {
124
+ return ref;
125
+ }
126
+ const typeError = checkValueType(value, propDef.type);
127
+ if (typeError) {
128
+ log.push(makeWarning(path, `${typeError} (expected ${propDef.type})`));
129
+ return undefined;
130
+ }
131
+ return { kind: 'value', value };
132
+ }
133
+ // =============================================================================
134
+ // Pass-through sections
135
+ // =============================================================================
136
+ function compileManifestPath(value, log) {
137
+ if (typeof value !== 'string') {
138
+ log.push(makeWarning('manifestPath', '"manifestPath" should be a string'));
139
+ return undefined;
140
+ }
141
+ return { kind: 'value', value };
142
+ }
143
+ function compileVirtualMap(value, _log) {
144
+ // virtualMap is a literal dict slot — no reference expansion, even if
145
+ // entries happen to look reference-shaped. The resolver will convert the
146
+ // plain structure into the runtime `VirtualMap` representation.
147
+ return { kind: 'value', value };
148
+ }
149
+ function compileIncludeDefaultConnections(value, _log) {
150
+ return { kind: 'value', value };
151
+ }
152
+ // =============================================================================
153
+ // Reference-shape detection
154
+ // =============================================================================
155
+ /**
156
+ * Inspect a raw POJO value for the "overlay reference" shape: a single-key
157
+ * object whose value is a string (scalar path) or string[] (nested path).
158
+ * Returns a ConfigReference node if it matches, otherwise undefined.
159
+ *
160
+ * The single-key constraint distinguishes references from literal objects.
161
+ * This is a runtime invariant — TypeScript can't express "exactly one key".
162
+ */
163
+ function asReferenceShape(value) {
164
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
165
+ return undefined;
166
+ }
167
+ const keys = Object.keys(value);
168
+ if (keys.length !== 1)
169
+ return undefined;
170
+ const source = keys[0];
171
+ const inner = value[source];
172
+ if (typeof inner === 'string') {
173
+ return { kind: 'reference', source, path: [inner] };
174
+ }
175
+ if (Array.isArray(inner) && inner.every(x => typeof x === 'string')) {
176
+ return { kind: 'reference', source, path: inner };
177
+ }
178
+ return undefined;
179
+ }
180
+ // =============================================================================
181
+ // Helpers
182
+ // =============================================================================
183
+ function isRecord(value) {
184
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
185
+ }
186
+ function makeWarning(path, message) {
187
+ return {
188
+ message: `${path}: ${message}`,
189
+ severity: 'warn',
190
+ code: 'config-validation',
191
+ };
192
+ }
193
+ function checkValueType(value, expectedType) {
194
+ switch (expectedType) {
195
+ case 'number':
196
+ if (typeof value !== 'number')
197
+ return `should be a number, got ${typeof value}`;
198
+ break;
199
+ case 'boolean':
200
+ if (typeof value !== 'boolean')
201
+ return `should be a boolean, got ${typeof value}`;
202
+ break;
203
+ case 'string':
204
+ case 'password':
205
+ case 'secret':
206
+ case 'file':
207
+ case 'text':
208
+ if (typeof value !== 'string')
209
+ return `should be a string, got ${typeof value}`;
210
+ break;
211
+ case 'json':
212
+ break;
213
+ }
214
+ return undefined;
215
+ }
216
+ function levenshtein(a, b) {
217
+ const m = a.length;
218
+ const n = b.length;
219
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
220
+ for (let i = 0; i <= m; i++)
221
+ dp[i][0] = i;
222
+ for (let j = 0; j <= n; j++)
223
+ dp[0][j] = j;
224
+ for (let i = 1; i <= m; i++) {
225
+ for (let j = 1; j <= n; j++) {
226
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
227
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
228
+ }
229
+ }
230
+ return dp[m][n];
231
+ }
232
+ function closestMatch(input, candidates) {
233
+ if (candidates.length === 0)
234
+ return undefined;
235
+ let best = candidates[0];
236
+ let bestDist = Infinity;
237
+ for (const c of candidates) {
238
+ const dist = levenshtein(input.toLowerCase(), c.toLowerCase());
239
+ if (dist < bestDist) {
240
+ bestDist = dist;
241
+ best = c;
242
+ }
243
+ }
244
+ const maxDist = Math.max(1, Math.floor(Math.max(input.length, best.length) / 3));
245
+ return bestDist <= maxDist ? best : undefined;
246
+ }
247
+ //# sourceMappingURL=config_compile.js.map
@@ -0,0 +1,37 @@
1
+ import type { URLReader } from '../../runtime_types';
2
+ import { MalloyConfig } from './config';
3
+ import type { ConfigOverlays } from './config_overlays';
4
+ /**
5
+ * Walk upward from `startURL` toward `ceilingURL`, looking for a
6
+ * `malloy-config.json` (or `malloy-config-local.json`) at each directory
7
+ * level. On a hit, build a `MalloyConfig` from the parsed POJO with a
8
+ * `config` overlay carrying `rootDirectory` (the ceiling) and `configURL`
9
+ * (the actual location of the file that matched). Returns `null` if no
10
+ * config file is found between `startURL` and `ceilingURL` (inclusive).
11
+ *
12
+ * `ceilingURL` is the host-supplied project root — discovery stops at that
13
+ * level, and it is what `config.rootDirectory` binds to in the typical
14
+ * overlay wiring. The matched config file's actual location rides on
15
+ * `config.configURL` so that `MalloyConfig.manifestURL` can resolve
16
+ * `MANIFESTS/malloy-manifest.json` relative to the file the config came
17
+ * from (not the project root).
18
+ *
19
+ * Callers can inspect what discovery found via `readOverlay` on the
20
+ * returned config:
21
+ *
22
+ * const config = await discoverConfig(startURL, ceilingURL, urlReader);
23
+ * config?.readOverlay('config', 'rootDirectory') // the ceiling URL
24
+ * config?.readOverlay('config', 'configURL') // the matched file URL
25
+ *
26
+ * Hosts that want to layer additional overlays on top of discovery's
27
+ * `config` overlay (e.g. a `session` overlay for per-request data) pass
28
+ * them via `extraOverlays`. The merge is plain object-spread: extras with
29
+ * the same key as discovery's entries replace them wholesale. In
30
+ * particular, passing `{config: myConfigOverlay}` will clobber the
31
+ * `rootDirectory` + `configURL` discovery built — callers who want to
32
+ * extend discovery's `config` should read the keys back off and re-include
33
+ * them, or skip `discoverConfig` and build `MalloyConfig` directly.
34
+ *
35
+ * URL-based, so it works in browser-safe environments through `URLReader`.
36
+ */
37
+ export declare function discoverConfig(startURL: URL, ceilingURL: URL, urlReader: URLReader, extraOverlays?: ConfigOverlays): Promise<MalloyConfig | null>;
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Contributors to the Malloy project
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.discoverConfig = discoverConfig;
8
+ const config_1 = require("./config");
9
+ const config_overlays_1 = require("./config_overlays");
10
+ const SHARED_FILENAME = 'malloy-config.json';
11
+ const LOCAL_FILENAME = 'malloy-config-local.json';
12
+ /**
13
+ * Walk upward from `startURL` toward `ceilingURL`, looking for a
14
+ * `malloy-config.json` (or `malloy-config-local.json`) at each directory
15
+ * level. On a hit, build a `MalloyConfig` from the parsed POJO with a
16
+ * `config` overlay carrying `rootDirectory` (the ceiling) and `configURL`
17
+ * (the actual location of the file that matched). Returns `null` if no
18
+ * config file is found between `startURL` and `ceilingURL` (inclusive).
19
+ *
20
+ * `ceilingURL` is the host-supplied project root — discovery stops at that
21
+ * level, and it is what `config.rootDirectory` binds to in the typical
22
+ * overlay wiring. The matched config file's actual location rides on
23
+ * `config.configURL` so that `MalloyConfig.manifestURL` can resolve
24
+ * `MANIFESTS/malloy-manifest.json` relative to the file the config came
25
+ * from (not the project root).
26
+ *
27
+ * Callers can inspect what discovery found via `readOverlay` on the
28
+ * returned config:
29
+ *
30
+ * const config = await discoverConfig(startURL, ceilingURL, urlReader);
31
+ * config?.readOverlay('config', 'rootDirectory') // the ceiling URL
32
+ * config?.readOverlay('config', 'configURL') // the matched file URL
33
+ *
34
+ * Hosts that want to layer additional overlays on top of discovery's
35
+ * `config` overlay (e.g. a `session` overlay for per-request data) pass
36
+ * them via `extraOverlays`. The merge is plain object-spread: extras with
37
+ * the same key as discovery's entries replace them wholesale. In
38
+ * particular, passing `{config: myConfigOverlay}` will clobber the
39
+ * `rootDirectory` + `configURL` discovery built — callers who want to
40
+ * extend discovery's `config` should read the keys back off and re-include
41
+ * them, or skip `discoverConfig` and build `MalloyConfig` directly.
42
+ *
43
+ * URL-based, so it works in browser-safe environments through `URLReader`.
44
+ */
45
+ async function discoverConfig(startURL, ceilingURL, urlReader, extraOverlays) {
46
+ // Normalize both to directory form.
47
+ let current = new URL('.', startURL);
48
+ const ceiling = new URL('.', ceilingURL);
49
+ // If we're not under (or at) the ceiling, there's nothing meaningful to
50
+ // walk — bail.
51
+ if (!isWithinOrEqual(current, ceiling))
52
+ return null;
53
+ for (;;) {
54
+ const found = await tryReadAtLevel(current, urlReader);
55
+ if (found)
56
+ return buildConfig(found, ceilingURL, extraOverlays);
57
+ if (current.toString() === ceiling.toString())
58
+ break;
59
+ const parent = new URL('..', current);
60
+ // URL scheme roots (`file:///`, `http://host/`) return themselves when
61
+ // you ask for their parent. Guard against the loop.
62
+ if (parent.toString() === current.toString())
63
+ break;
64
+ current = parent;
65
+ }
66
+ return null;
67
+ }
68
+ function buildConfig(hit, ceilingURL, extraOverlays) {
69
+ const discoveryOverlays = {
70
+ config: (0, config_overlays_1.contextOverlay)({
71
+ rootDirectory: ceilingURL.toString(),
72
+ configURL: hit.configURL.toString(),
73
+ }),
74
+ };
75
+ const merged = { ...discoveryOverlays, ...extraOverlays };
76
+ return new config_1.MalloyConfig(hit.pojo, merged);
77
+ }
78
+ async function tryReadAtLevel(dirURL, urlReader) {
79
+ const sharedURL = new URL(SHARED_FILENAME, dirURL);
80
+ const localURL = new URL(LOCAL_FILENAME, dirURL);
81
+ const [sharedPOJO, localPOJO] = await Promise.all([
82
+ tryReadJSON(sharedURL, urlReader),
83
+ tryReadJSON(localURL, urlReader),
84
+ ]);
85
+ if (!sharedPOJO && !localPOJO)
86
+ return null;
87
+ if (sharedPOJO && localPOJO) {
88
+ // Shallow-merge top-level keys, local wins. `connections` is the one
89
+ // section we deep-merge by entry name (local winning) — the documented
90
+ // shop workflow is shared credentials as env refs in git, local
91
+ // overrides with literal values outside git.
92
+ //
93
+ // All other sections (virtualMap, manifestPath, includeDefaultConnections)
94
+ // are replaced wholesale when present in the local file. If you want to
95
+ // augment a shared virtualMap from the local file, you have to copy
96
+ // the shared entries into the local file explicitly.
97
+ const sharedConns = isRecord(sharedPOJO['connections'])
98
+ ? sharedPOJO['connections']
99
+ : {};
100
+ const localConns = isRecord(localPOJO['connections'])
101
+ ? localPOJO['connections']
102
+ : {};
103
+ const merged = {
104
+ ...sharedPOJO,
105
+ ...localPOJO,
106
+ connections: { ...sharedConns, ...localConns },
107
+ };
108
+ return { configURL: localURL, pojo: merged };
109
+ }
110
+ if (localPOJO)
111
+ return { configURL: localURL, pojo: localPOJO };
112
+ // Narrowed: sharedPOJO is truthy here.
113
+ return { configURL: sharedURL, pojo: sharedPOJO };
114
+ }
115
+ /**
116
+ * Try to read and parse a config file at `url`.
117
+ *
118
+ * Two failure modes are distinguished:
119
+ * - URL read fails (file not present) → returns `null`, signalling the
120
+ * walker to keep going up.
121
+ * - Read succeeds but contents are unparseable or not a JSON object →
122
+ * throws. A matched-but-broken config file is a hard error; silently
123
+ * skipping it would hide user mistakes and quietly fall through to a
124
+ * grandparent config.
125
+ */
126
+ async function tryReadJSON(url, urlReader) {
127
+ let contents;
128
+ try {
129
+ const result = await urlReader.readURL(url);
130
+ contents = typeof result === 'string' ? result : result.contents;
131
+ }
132
+ catch {
133
+ return null;
134
+ }
135
+ let parsed;
136
+ try {
137
+ parsed = JSON.parse(contents);
138
+ }
139
+ catch (e) {
140
+ const msg = e instanceof Error ? e.message : String(e);
141
+ throw new Error(`Malformed JSON in ${url.toString()}: ${msg}`);
142
+ }
143
+ if (!isRecord(parsed)) {
144
+ throw new Error(`Config file ${url.toString()} must contain a JSON object at the top level`);
145
+ }
146
+ return parsed;
147
+ }
148
+ function isRecord(value) {
149
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
150
+ }
151
+ function isWithinOrEqual(child, ancestor) {
152
+ return child.toString().startsWith(ancestor.toString());
153
+ }
154
+ //# sourceMappingURL=config_discover.js.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * An overlay is a lambda that takes a compiled reference path and returns
3
+ * a value (or undefined if the path is not present in the overlay source).
4
+ *
5
+ * The path is an array because references can address nested data:
6
+ * {env: "HOME"} -> path = ["HOME"]
7
+ * {user: ["address", "zip"]} -> path = ["address", "zip"]
8
+ *
9
+ * Overlay values should resolve to ordinary JSON-compatible values.
10
+ */
11
+ export type Overlay = (path: string[]) => unknown;
12
+ /**
13
+ * A dict keyed by overlay name. A config reference like
14
+ * `{env: "PG_PASSWORD"}` is resolved by looking up `overlays["env"]` and
15
+ * calling it with `["PG_PASSWORD"]`.
16
+ *
17
+ * Hosts provide additional overlays by passing a partial dict to the
18
+ * `MalloyConfig` constructor. The constructor merges it onto the default
19
+ * overlays by plain object spread, giving three behaviors:
20
+ *
21
+ * - Add: {session: sessionFn} — env and config stay, session added
22
+ * - Replace: {config: myConfigFn} — host replaces the default config
23
+ * - Disable: {config: () => undefined} — refs to {config: ...} drop
24
+ */
25
+ export type ConfigOverlays = Record<string, Overlay>;
26
+ /**
27
+ * Built-in `env` overlay: reads `process.env[path[0]]`.
28
+ *
29
+ * Hardened against missing `process` so `defaultConfigOverlays()` is safe
30
+ * to call from any runtime. In the browser or a worker with no process.env,
31
+ * `{env: "X"}` references resolve to "not present" (property dropped),
32
+ * symmetric with the no-op default `config` overlay. Browser hosts can
33
+ * still swap in their own `env` overlay if they want a different source.
34
+ */
35
+ export declare function envOverlay(): Overlay;
36
+ /**
37
+ * Helper that turns a plain dict into an overlay lambda. Used by hosts
38
+ * (and discovery wiring) to build a populated `config` overlay.
39
+ *
40
+ * const overlays = {
41
+ * config: contextOverlay({
42
+ * rootDirectory: ceilingURL,
43
+ * configURL: configURL,
44
+ * }),
45
+ * };
46
+ */
47
+ export declare function contextOverlay(dict: Record<string, unknown>): Overlay;
48
+ /**
49
+ * The default config overlays: `env` wired to process.env, and a no-op
50
+ * `config` overlay. Hosts that want discovery-populated context replace
51
+ * the `config` entry; soloists leave it alone and `{config: ...}` refs
52
+ * resolve to "not present".
53
+ */
54
+ export declare function defaultConfigOverlays(): ConfigOverlays;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Contributors to the Malloy project
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.envOverlay = envOverlay;
8
+ exports.contextOverlay = contextOverlay;
9
+ exports.defaultConfigOverlays = defaultConfigOverlays;
10
+ /**
11
+ * Built-in `env` overlay: reads `process.env[path[0]]`.
12
+ *
13
+ * Hardened against missing `process` so `defaultConfigOverlays()` is safe
14
+ * to call from any runtime. In the browser or a worker with no process.env,
15
+ * `{env: "X"}` references resolve to "not present" (property dropped),
16
+ * symmetric with the no-op default `config` overlay. Browser hosts can
17
+ * still swap in their own `env` overlay if they want a different source.
18
+ */
19
+ function envOverlay() {
20
+ if (typeof process === 'undefined' || !process.env) {
21
+ return () => undefined;
22
+ }
23
+ return path => process.env[path[0]];
24
+ }
25
+ /**
26
+ * Helper that turns a plain dict into an overlay lambda. Used by hosts
27
+ * (and discovery wiring) to build a populated `config` overlay.
28
+ *
29
+ * const overlays = {
30
+ * config: contextOverlay({
31
+ * rootDirectory: ceilingURL,
32
+ * configURL: configURL,
33
+ * }),
34
+ * };
35
+ */
36
+ function contextOverlay(dict) {
37
+ return path => dict[path[0]];
38
+ }
39
+ /**
40
+ * The default config overlays: `env` wired to process.env, and a no-op
41
+ * `config` overlay. Hosts that want discovery-populated context replace
42
+ * the `config` entry; soloists leave it alone and `{config: ...}` refs
43
+ * resolve to "not present".
44
+ */
45
+ function defaultConfigOverlays() {
46
+ return {
47
+ env: envOverlay(),
48
+ config: () => undefined,
49
+ };
50
+ }
51
+ //# sourceMappingURL=config_overlays.js.map
@@ -0,0 +1,49 @@
1
+ import type { LogMessage } from '../../lang/parse-log';
2
+ import type { ConnectionConfigEntry } from '../../connection/registry';
3
+ import type { ConfigDict } from './config_compile';
4
+ import type { ConfigOverlays } from './config_overlays';
5
+ /**
6
+ * The shape the class body consumes: a plain POJO with the same top-level
7
+ * sections as the input, but with all references resolved and defaults
8
+ * applied. This is fed into the connection registry to build connections.
9
+ *
10
+ * `connections` uses the registry's `ConnectionConfigEntry` shape directly —
11
+ * a resolved entry is precisely what the registry consumes, so there's no
12
+ * reason to invent a local alias and cast across the boundary.
13
+ *
14
+ * Note: `includeDefaultConnections` is an input directive, not a resolved
15
+ * value — by the time `resolveConfig` returns, fabrication has already
16
+ * happened. It intentionally does not appear on this interface.
17
+ */
18
+ export interface ResolvedConfig {
19
+ connections: Record<string, ConnectionConfigEntry>;
20
+ manifestPath?: string;
21
+ virtualMap?: unknown;
22
+ }
23
+ /**
24
+ * Walk a compiled tree against the overlay dict and produce a plain
25
+ * resolved POJO.
26
+ *
27
+ * Two distinct "defaults" mechanisms, deliberately separated:
28
+ *
29
+ * 1. **Property defaults** (`applyPropertyDefaults`) — fill in missing
30
+ * properties on *every* connection entry, user-listed or fabricated.
31
+ * This is a uniform per-property rule; there is no asymmetry between
32
+ * explicit and auto-generated entries.
33
+ *
34
+ * 2. **`includeDefaultConnections`** (`fabricateMissingConnections`) —
35
+ * fabricate a bare `{is: typeName}` entry for each registered backend
36
+ * not already represented. Property defaults then fill in their
37
+ * properties via (1).
38
+ *
39
+ * Order matters: fabrication runs before property defaults so that
40
+ * fabricated entries pick up defaults in the same pass as user-listed
41
+ * ones.
42
+ *
43
+ * Three unresolved-reference cases, each with different handling:
44
+ * 1. Unknown overlay source → warning, drop property
45
+ * 2. Known overlay → undefined → silent drop
46
+ * 3. Property default → unresolved (either of the above inside a default)
47
+ * → silent drop (a default is a hint, not a requirement)
48
+ */
49
+ export declare function resolveConfig(compiled: ConfigDict, overlays: ConfigOverlays, log: LogMessage[]): ResolvedConfig;