@malloydata/malloy 0.0.372 → 0.0.374

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.
@@ -6,54 +6,31 @@
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.MalloyConfig = exports.Manifest = void 0;
8
8
  const registry_1 = require("../../connection/registry");
9
- const DEFAULT_MANIFEST_PATH = 'MANIFESTS';
10
- const MANIFEST_FILENAME = 'malloy-manifest.json';
9
+ const config_compile_1 = require("./config_compile");
10
+ const config_resolve_1 = require("./config_resolve");
11
+ const config_overlays_1 = require("./config_overlays");
11
12
  /**
12
13
  * In-memory manifest store. Reads, updates, and serializes manifest data.
13
14
  *
14
- * Always valid — starts empty, call load() to populate from a file.
15
- * The BuildManifest object returned by `buildManifest` is stable: load()
16
- * and update() mutate the same entries object, so any reference obtained
17
- * via `buildManifest` stays current without re-assignment.
15
+ * Always valid — starts empty. Populate by calling `loadText(jsonText)` with
16
+ * the manifest file contents, which the caller reads themselves (typically
17
+ * through a `URLReader` they already have). The `Manifest` class deliberately
18
+ * does no IO it exists so that applications that want to build or mutate
19
+ * manifest state have a typed handle with a stable `buildManifest` reference.
18
20
  *
19
- * The `strict` flag controls what happens when a persist source's BuildID
20
- * is not found in the manifest. When strict, the compiler throws an error
21
- * instead of falling through to inline SQL. The flag is loaded from the
22
- * manifest file and can be overridden by the application before creating
23
- * a Runtime.
21
+ * The `BuildManifest` returned by `buildManifest` is stable: `loadText()` and
22
+ * `update()` mutate the same entries object, so any reference obtained via
23
+ * `buildManifest` stays current without re-assignment.
24
+ *
25
+ * The `strict` flag controls what happens when a persist source's BuildID is
26
+ * not found in the manifest. When strict, the compiler throws an error
27
+ * instead of falling through to inline SQL. Loaded from the manifest file by
28
+ * `loadText()`; applications may override it at any time.
24
29
  */
25
30
  class Manifest {
26
- constructor(urlReader) {
31
+ constructor() {
27
32
  this._manifest = { entries: {}, strict: false };
28
33
  this._touched = new Set();
29
- this._urlReader = urlReader;
30
- }
31
- /**
32
- * Load manifest data from a manifest root directory.
33
- * Reads `<manifestRoot>/malloy-manifest.json` via the URLReader.
34
- * Replaces any existing data. If the file doesn't exist or no URLReader
35
- * is available, clears to empty.
36
- */
37
- async load(manifestRoot) {
38
- this._clearEntries();
39
- this._touched.clear();
40
- this._manifest.strict = false;
41
- if (!this._urlReader)
42
- return;
43
- const dir = manifestRoot.toString().endsWith('/')
44
- ? manifestRoot.toString()
45
- : manifestRoot.toString() + '/';
46
- const manifestURL = new URL(MANIFEST_FILENAME, dir);
47
- let contents;
48
- try {
49
- const result = await this._urlReader.readURL(manifestURL);
50
- contents = typeof result === 'string' ? result : result.contents;
51
- }
52
- catch {
53
- // No manifest file — stay empty
54
- return;
55
- }
56
- this._loadParsed(JSON.parse(contents));
57
34
  }
58
35
  /**
59
36
  * Load manifest data from a JSON string.
@@ -66,9 +43,9 @@ class Manifest {
66
43
  this._loadParsed(JSON.parse(jsonText));
67
44
  }
68
45
  /**
69
- * The live BuildManifest. This is a stable reference — load() and update()
70
- * mutate the same object, so passing this to a Runtime means the Runtime
71
- * always sees current data including the strict flag.
46
+ * The live BuildManifest. This is a stable reference — `loadText()` and
47
+ * `update()` mutate the same object, so passing this to a Runtime means
48
+ * the Runtime always sees current data including the strict flag.
72
49
  */
73
50
  get buildManifest() {
74
51
  return this._manifest;
@@ -133,369 +110,233 @@ class Manifest {
133
110
  }
134
111
  exports.Manifest = Manifest;
135
112
  /**
136
- * Loads and holds a Malloy project configuration (connections + manifest +
137
- * virtualMap). Pass directly to Runtime everything flows automatically.
113
+ * A resolved Malloy runtime configuration. The constructor takes either a
114
+ * JSON string (legacy/compat) or a POJO plus an optional `ConfigOverlays`
115
+ * dict, compiles the input into a typed tree, resolves all overlay
116
+ * references, and builds the connection lookup via the connection registry.
138
117
  *
139
- * // From a URL — reads config + manifest via URLReader
140
- * const config = new MalloyConfig(urlReader, configURL);
141
- * await config.load();
118
+ * After construction:
119
+ * - `connections` the `LookupConnection` runtime consumes. Starts as the
120
+ * lookup created from the resolved connections; `wrapConnections()` replaces
121
+ * it with a wrapped version for host-specific behavior.
122
+ * - `virtualMap` — extracted from the POJO as-is.
123
+ * - `manifestPath` — extracted from the POJO as-is (the raw string).
124
+ * - `manifestURL` — resolved URL of `malloy-manifest.json`, computed from
125
+ * `manifestPath` + the `configURL` carried on the `config` overlay (if
126
+ * any). `undefined` when no `configURL` is available. Runtime uses this
127
+ * to lazily read the manifest on the first persistence query. Builders
128
+ * that want to construct or mutate manifest state use the standalone
129
+ * `Manifest` class directly — they don't go through MalloyConfig.
130
+ * - `log` — validation warnings and overlay-resolution warnings.
142
131
  *
143
- * // From text you already have
144
- * const config = new MalloyConfig(configText);
132
+ * Typical usage:
145
133
  *
146
- * // Either way, pass to Runtime connections, buildManifest, virtualMap
147
- * // all come from the config. No manual wiring needed.
134
+ * // From a pre-loaded POJO (e.g. from `discoverConfig` or Publisher):
135
+ * const config = new MalloyConfig(pojo);
148
136
  * const runtime = new Runtime({config, urlReader});
149
137
  *
150
- * To override specific fields, mutate the config before passing:
151
- * config.connectionMap = myCustomConnections;
138
+ * // From a JSON string:
139
+ * const config = new MalloyConfig(configText);
140
+ *
141
+ * // With host-supplied overlays (local hosts after discovery):
142
+ * const config = new MalloyConfig(pojo, {
143
+ * config: contextOverlay({rootDirectory: ceilingURL}),
144
+ * });
145
+ *
146
+ * // Wrap the connection lookup for host-specific behavior:
147
+ * config.wrapConnections(base => name => base(name) ?? fallback(name));
148
+ *
149
+ * The old async `new MalloyConfig(urlReader, configURL)` form has been
150
+ * removed. Callers that need to load from a URL should pre-load via
151
+ * `urlReader.readURL()` / `JSON.parse()` and pass the POJO, or use the
152
+ * sync string form after reading the file.
152
153
  */
153
154
  class MalloyConfig {
154
- constructor(urlReaderOrText, configURL) {
155
- this._log = [];
156
- if (typeof urlReaderOrText === 'string') {
157
- const { config, log } = parseConfigText(urlReaderOrText);
158
- this._data = config;
159
- this._log = log;
160
- this._connectionMap = this._data.connections
161
- ? { ...this._data.connections }
162
- : undefined;
163
- this._manifest = new Manifest();
155
+ constructor(sourceOrPojo, overlays) {
156
+ const log = [];
157
+ // Normalize input to a POJO.
158
+ let pojo;
159
+ if (typeof sourceOrPojo === 'string') {
160
+ try {
161
+ pojo = JSON.parse(sourceOrPojo);
162
+ }
163
+ catch (e) {
164
+ log.push({
165
+ message: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}`,
166
+ severity: 'error',
167
+ code: 'config-validation',
168
+ });
169
+ pojo = {};
170
+ }
164
171
  }
165
172
  else {
166
- this._urlReader = urlReaderOrText;
167
- this._configURL = configURL;
168
- this._manifest = new Manifest(urlReaderOrText);
169
- }
170
- }
171
- /**
172
- * Load everything: parse config file, then load the default manifest.
173
- * No-op if created from text.
174
- */
175
- async load() {
176
- var _a, _b;
177
- await this.loadConfig();
178
- if (this._configURL) {
179
- const manifestPath = (_b = (_a = this._data) === null || _a === void 0 ? void 0 : _a.manifestPath) !== null && _b !== void 0 ? _b : DEFAULT_MANIFEST_PATH;
180
- const manifestRoot = new URL(manifestPath, this._configURL);
181
- await this._manifest.load(manifestRoot);
173
+ pojo = sourceOrPojo;
182
174
  }
175
+ // Compile → typed tree + validation warnings.
176
+ const compiled = (0, config_compile_1.compileConfig)(pojo);
177
+ log.push(...compiled.log);
178
+ // Resolve against the merged config overlays. Entries from the passed
179
+ // overlays replace same-named entries in the defaults; anything new
180
+ // is added.
181
+ const mergedOverlays = {
182
+ ...(0, config_overlays_1.defaultConfigOverlays)(),
183
+ ...overlays,
184
+ };
185
+ const resolved = (0, config_resolve_1.resolveConfig)(compiled.compiled, mergedOverlays, log);
186
+ // Hand fully-resolved connection entries to the registry — factories
187
+ // never see raw references.
188
+ this._managedLookup = (0, registry_1.createConnectionsFromConfig)({
189
+ connections: resolved.connections,
190
+ });
191
+ this._connections = this._managedLookup;
192
+ this._overlays = mergedOverlays;
193
+ this.virtualMap = toVirtualMap(resolved.virtualMap);
194
+ this.manifestPath = resolved.manifestPath;
195
+ this.manifestURL = computeManifestURL(resolved.manifestPath, mergedOverlays);
196
+ this.log = log;
183
197
  }
184
198
  /**
185
- * Load only the config file. Does not load the manifest.
186
- * No-op if created from text.
187
- */
188
- async loadConfig() {
189
- if (!this._urlReader || !this._configURL)
190
- return;
191
- const result = await this._urlReader.readURL(new URL(this._configURL));
192
- const contents = typeof result === 'string' ? result : result.contents;
193
- const parsed = parseConfigText(contents);
194
- this._data = parsed.config;
195
- this._log = parsed.log;
196
- this._connectionMap = this._data.connections
197
- ? { ...this._data.connections }
198
- : undefined;
199
- }
200
- /**
201
- * The parsed config data. Undefined if created from URL and not yet loaded.
199
+ * The live `LookupConnection` consumed by Runtime. Stable until
200
+ * `wrapConnections()` is called; after that, returns the wrapped lookup.
202
201
  */
203
- get data() {
204
- return this._data;
202
+ get connections() {
203
+ return this._connections;
205
204
  }
206
205
  /**
207
- * The active connection entries. Set this to override which connections
208
- * are used before constructing a Runtime.
206
+ * Wrap the current connection lookup with a host-supplied wrapper.
207
+ * Mutates in place subsequent `connections` accesses return the new
208
+ * wrapped lookup. Runtime never needs to know whether wrapping happened.
209
+ *
210
+ * Typical uses:
211
+ * - VS Code layers settings + defaults below the config lookup
212
+ * - Publisher attaches session-specific behavior to resolved connections
209
213
  */
210
- get connectionMap() {
211
- return this._connectionMap;
212
- }
213
- set connectionMap(map) {
214
- this._connectionMap = map;
215
- // Invalidate cached managed lookup — new map means new connections
216
- this._managedLookup = undefined;
214
+ wrapConnections(wrapper) {
215
+ this._connections = wrapper(this._connections);
217
216
  }
218
217
  /**
219
- * A LookupConnection built from the current connectionMap via the
220
- * connection type registry. The result is cached repeated access
221
- * returns the same object (and the same underlying connections).
218
+ * Notify every connection this config has handed out that it is time to
219
+ * release its resources, then drop them from the internal cache.
222
220
  *
223
- * If `connectionLookup` has been set, returns the override instead.
221
+ * Most callers should use `Runtime.releaseConnections()` instead the
222
+ * expected contract is one MalloyConfig per Runtime, and the runtime is
223
+ * the natural handle for lifecycle. This method exists because Runtime
224
+ * forwards to it, and for the rare case of a MalloyConfig constructed
225
+ * without an accompanying Runtime.
224
226
  *
225
- * Changing `connectionMap` invalidates the cache; the next access
226
- * creates a fresh ManagedConnectionLookup.
227
- */
228
- get connections() {
229
- if (this._connectionLookupOverride) {
230
- return this._connectionLookupOverride;
231
- }
232
- if (!this._connectionMap) {
233
- throw new Error('Config not loaded. Call load() or loadConfig() first.');
234
- }
235
- if (!this._managedLookup) {
236
- this._managedLookup = (0, registry_1.createConnectionsFromConfig)({ connections: this._connectionMap }, this._onConnectionCreated);
237
- }
238
- return this._managedLookup;
239
- }
240
- /**
241
- * Override the connection lookup entirely. When set, the `connections`
242
- * getter returns this instead of building from `connectionMap`.
243
- * Use this when you need to merge config connections with other sources.
244
- */
245
- get connectionLookup() {
246
- return this._connectionLookupOverride;
247
- }
248
- set connectionLookup(lookup) {
249
- this._connectionLookupOverride = lookup;
250
- }
251
- /**
252
- * Callback invoked once per connection immediately after factory creation.
253
- * Use for post-creation setup (e.g., registering WASM file handlers).
254
- * Must be set before the first connection is looked up.
255
- */
256
- set onConnectionCreated(cb) {
257
- this._onConnectionCreated = cb;
258
- }
259
- /**
260
- * Close all connections created by this config's internal managed lookup.
261
- * Does nothing if connections were overridden via `connectionLookup`.
262
- */
263
- async close() {
264
- if (this._managedLookup) {
265
- await this._managedLookup.close();
266
- this._managedLookup = undefined;
267
- }
268
- }
269
- /**
270
- * The Manifest object. Always exists, may be empty if not yet loaded.
271
- */
272
- get manifest() {
273
- return this._manifest;
274
- }
275
- /**
276
- * The VirtualMap parsed from config, if present.
227
+ * MalloyConfig does not own any connection resources itself — pools,
228
+ * sockets, file handles, and in-process databases all live inside the
229
+ * individual Connection objects. What the managed lookup owns is a cache
230
+ * of `name → Connection` populated lazily as callers request connections.
231
+ * This method walks that cache and calls `Connection.close()` on each
232
+ * entry, which is the signal each connection uses to shut down whatever
233
+ * resources it actually holds.
234
+ *
235
+ * Connections that were never looked up were never constructed and have
236
+ * nothing to release; they are skipped. Wrapping lookups installed via
237
+ * `wrapConnections()` do not interfere — the managed lookup under the
238
+ * wrap is the one holding the cache.
277
239
  */
278
- get virtualMap() {
279
- var _a;
280
- return (_a = this._data) === null || _a === void 0 ? void 0 : _a.virtualMap;
240
+ async releaseConnections() {
241
+ await this._managedLookup.close();
281
242
  }
282
243
  /**
283
- * Errors and warnings from parsing and validating the config.
284
- * Includes JSON parse errors (severity 'error') and schema validation
285
- * warnings (severity 'warn') such as unknown keys, unknown connection
286
- * types, wrong value types, and missing environment variables.
244
+ * Query a value from the overlays used to resolve this config.
245
+ *
246
+ * Returns the value the named overlay produces for the given path,
247
+ * or `undefined` if the overlay doesn't exist or the path has no value.
248
+ *
249
+ * config.readOverlay('config', 'rootDirectory')
250
+ * config.readOverlay('config', 'configURL')
251
+ * config.readOverlay('env', 'PG_PASSWORD')
287
252
  */
288
- get log() {
289
- return this._log;
253
+ readOverlay(overlayName, ...path) {
254
+ var _a, _b;
255
+ return (_b = (_a = this._overlays)[overlayName]) === null || _b === void 0 ? void 0 : _b.call(_a, path);
290
256
  }
291
257
  }
292
258
  exports.MalloyConfig = MalloyConfig;
259
+ // =============================================================================
260
+ // Helpers
261
+ // =============================================================================
262
+ /**
263
+ * Default directory (relative to the config file) where the manifest lives
264
+ * when `manifestPath` is not explicitly set. ALL CAPS signals a generated
265
+ * artifact, not a hand-authored file. No dot prefix — visible and
266
+ * discoverable in `ls`.
267
+ */
268
+ const DEFAULT_MANIFEST_PATH = 'MANIFESTS';
293
269
  /**
294
- * Parse a config JSON string into a MalloyProjectConfig.
295
- * Invalid connection entries (missing `is`) are silently dropped.
296
- * Returns the processed config and validation log.
270
+ * The filename inside the manifest directory.
297
271
  */
298
- function parseConfigText(jsonText) {
299
- let parsed;
272
+ const MANIFEST_FILENAME = 'malloy-manifest.json';
273
+ /**
274
+ * Compute the resolved URL of the manifest file from the raw `manifestPath`
275
+ * string and the merged overlays (which may carry a `configURL`).
276
+ *
277
+ * Rules:
278
+ * - If there's no `configURL` in the `config` overlay → undefined.
279
+ * - `manifestPath` defaults to `'MANIFESTS'`.
280
+ * - `new URL(manifestPath, configURL)` handles three shapes uniformly:
281
+ * "MANIFESTS" → relative to configURL's directory
282
+ * "../shared/MANIFESTS" → parent-relative
283
+ * "/project/malloy/MANIFESTS" → absolute path within configURL's scheme
284
+ * "file:///elsewhere/stuff" → full URL, ignores configURL base
285
+ * - A trailing slash is forced onto the directory portion so that the
286
+ * final `new URL(MANIFEST_FILENAME, dir)` joins correctly.
287
+ */
288
+ function computeManifestURL(manifestPath, overlays) {
289
+ const configOverlay = overlays['config'];
290
+ const configURLValue = configOverlay === null || configOverlay === void 0 ? void 0 : configOverlay(['configURL']);
291
+ if (typeof configURLValue !== 'string')
292
+ return undefined;
293
+ let configURL;
300
294
  try {
301
- parsed = JSON.parse(jsonText);
295
+ configURL = new URL(configURLValue);
302
296
  }
303
- catch (e) {
304
- return {
305
- config: {},
306
- log: [
307
- {
308
- message: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}`,
309
- severity: 'error',
310
- code: 'config-validation',
311
- },
312
- ],
313
- };
297
+ catch {
298
+ return undefined;
314
299
  }
315
- if (!isRecord(parsed)) {
316
- return {
317
- config: {},
318
- log: [
319
- {
320
- message: 'config is not a JSON object',
321
- severity: 'error',
322
- code: 'config-validation',
323
- },
324
- ],
325
- };
300
+ const path = manifestPath !== null && manifestPath !== void 0 ? manifestPath : DEFAULT_MANIFEST_PATH;
301
+ let manifestRoot;
302
+ try {
303
+ manifestRoot = new URL(path, configURL);
326
304
  }
327
- const rawConnections = parsed['connections'];
328
- const connectionEntries = Object.entries(isRecord(rawConnections) ? rawConnections : {}).filter((entry) => (0, registry_1.isConnectionConfigEntry)(entry[1]));
329
- const connections = Object.fromEntries(connectionEntries);
330
- const result = { ...parsed, connections };
331
- const virtualMap = parsed['virtualMap'];
332
- if (virtualMap &&
333
- typeof virtualMap === 'object' &&
334
- !Array.isArray(virtualMap)) {
335
- const outer = new Map();
336
- for (const [connName, inner] of Object.entries(virtualMap)) {
337
- if (inner && typeof inner === 'object' && !Array.isArray(inner)) {
338
- const innerMap = new Map();
339
- for (const [virtualName, tablePath] of Object.entries(inner)) {
340
- if (typeof tablePath === 'string') {
341
- innerMap.set(virtualName, tablePath);
342
- }
343
- }
344
- outer.set(connName, innerMap);
345
- }
346
- }
347
- result.virtualMap = outer;
305
+ catch {
306
+ return undefined;
348
307
  }
349
- return { config: result, log: validateConfig(parsed) };
350
- }
351
- const KNOWN_TOP_LEVEL_KEYS = new Set([
352
- 'connections',
353
- 'manifestPath',
354
- 'virtualMap',
355
- ]);
356
- function isRecord(value) {
357
- return typeof value === 'object' && value !== null && !Array.isArray(value);
358
- }
359
- function isBuildManifestEntry(value) {
360
- return isRecord(value) && typeof value['tableName'] === 'string';
361
- }
362
- function makeWarning(path, message) {
363
- return {
364
- message: `${path}: ${message}`,
365
- severity: 'warn',
366
- code: 'config-validation',
367
- };
308
+ const asString = manifestRoot.toString();
309
+ const dirURL = asString.endsWith('/')
310
+ ? manifestRoot
311
+ : new URL(asString + '/');
312
+ return new URL(MANIFEST_FILENAME, dirURL);
368
313
  }
369
314
  /**
370
- * Validate a parsed config object against the connection type registry.
371
- * Returns LogMessage[] does not throw.
315
+ * Convert the raw virtualMap POJO shape (a dict of dicts of strings) into
316
+ * the runtime Map-of-Maps representation that Runtime consumes.
372
317
  */
373
- function validateConfig(data) {
374
- const log = [];
375
- for (const key of Object.keys(data)) {
376
- if (!KNOWN_TOP_LEVEL_KEYS.has(key)) {
377
- const suggestion = closestMatch(key, [...KNOWN_TOP_LEVEL_KEYS]);
378
- const hint = suggestion ? `. Did you mean "${suggestion}"?` : '';
379
- log.push(makeWarning(key, `unknown config key "${key}"${hint}`));
380
- }
381
- }
382
- if (data['manifestPath'] !== undefined &&
383
- typeof data['manifestPath'] !== 'string') {
384
- log.push(makeWarning('manifestPath', '"manifestPath" should be a string'));
385
- }
386
- const connections = data['connections'];
387
- if (connections === undefined)
388
- return log;
389
- if (!isRecord(connections)) {
390
- log.push(makeWarning('connections', '"connections" should be an object'));
391
- return log;
392
- }
393
- const registeredTypes = new Set((0, registry_1.getRegisteredConnectionTypes)());
394
- for (const [name, rawEntry] of Object.entries(connections)) {
395
- const prefix = `connections.${name}`;
396
- if (!isRecord(rawEntry)) {
397
- log.push(makeWarning(prefix, 'should be an object'));
398
- continue;
399
- }
400
- if (!rawEntry['is']) {
401
- log.push(makeWarning(prefix, 'missing required "is" field (connection type)'));
402
- continue;
403
- }
404
- if (typeof rawEntry['is'] !== 'string') {
405
- log.push(makeWarning(`${prefix}.is`, '"is" should be a string'));
406
- continue;
407
- }
408
- const typeName = rawEntry['is'];
409
- if (!registeredTypes.has(typeName)) {
410
- const suggestion = closestMatch(typeName, [...registeredTypes]);
411
- const hint = suggestion ? ` Did you mean "${suggestion}"?` : '';
412
- log.push(makeWarning(`${prefix}.is`, `unknown connection type "${typeName}".${hint} Available types: ${[...registeredTypes].join(', ')}`));
413
- continue;
414
- }
415
- const props = (0, registry_1.getConnectionProperties)(typeName);
416
- if (!props)
318
+ function toVirtualMap(raw) {
319
+ if (!isRecord(raw))
320
+ return undefined;
321
+ const outer = new Map();
322
+ for (const [connName, inner] of Object.entries(raw)) {
323
+ if (!isRecord(inner))
417
324
  continue;
418
- const knownProps = new Map(props.map(p => [p.name, p]));
419
- for (const [key, value] of Object.entries(rawEntry)) {
420
- if (key === 'is')
421
- continue;
422
- const propDef = knownProps.get(key);
423
- if (!propDef) {
424
- const suggestion = closestMatch(key, [...knownProps.keys()]);
425
- const hint = suggestion ? `. Did you mean "${suggestion}"?` : '';
426
- log.push(makeWarning(`${prefix}.${key}`, `unknown property "${key}" for connection type "${typeName}"${hint}`));
427
- continue;
428
- }
429
- // For env var references on non-json props, check the variable exists.
430
- if (propDef.type !== 'json' && (0, registry_1.isValueRef)(value)) {
431
- const env = value.env;
432
- if (process.env[env] === undefined) {
433
- log.push(makeWarning(`${prefix}.${key}`, `environment variable "${env}" is not set`));
434
- }
435
- continue;
436
- }
437
- const typeError = checkValueType(value, propDef.type);
438
- if (typeError) {
439
- log.push(makeWarning(`${prefix}.${key}`, `${typeError} (expected ${propDef.type})`));
325
+ const innerMap = new Map();
326
+ for (const [virtualName, tablePath] of Object.entries(inner)) {
327
+ if (typeof tablePath === 'string') {
328
+ innerMap.set(virtualName, tablePath);
440
329
  }
441
330
  }
331
+ if (innerMap.size > 0)
332
+ outer.set(connName, innerMap);
442
333
  }
443
- return log;
334
+ return outer.size > 0 ? outer : undefined;
444
335
  }
445
- function levenshtein(a, b) {
446
- const m = a.length;
447
- const n = b.length;
448
- const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
449
- for (let i = 0; i <= m; i++)
450
- dp[i][0] = i;
451
- for (let j = 0; j <= n; j++)
452
- dp[0][j] = j;
453
- for (let i = 1; i <= m; i++) {
454
- for (let j = 1; j <= n; j++) {
455
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
456
- dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
457
- }
458
- }
459
- return dp[m][n];
460
- }
461
- function closestMatch(input, candidates) {
462
- if (candidates.length === 0)
463
- return undefined;
464
- let best = candidates[0];
465
- let bestDist = Infinity;
466
- for (const c of candidates) {
467
- const dist = levenshtein(input.toLowerCase(), c.toLowerCase());
468
- if (dist < bestDist) {
469
- bestDist = dist;
470
- best = c;
471
- }
472
- }
473
- const maxDist = Math.max(1, Math.floor(Math.max(input.length, best.length) / 3));
474
- return bestDist <= maxDist ? best : undefined;
336
+ function isRecord(value) {
337
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
475
338
  }
476
- function checkValueType(value, expectedType) {
477
- if (value === undefined || value === null)
478
- return undefined;
479
- switch (expectedType) {
480
- case 'number':
481
- if (typeof value !== 'number')
482
- return `should be a number, got ${typeof value}`;
483
- break;
484
- case 'boolean':
485
- if (typeof value !== 'boolean')
486
- return `should be a boolean, got ${typeof value}`;
487
- break;
488
- case 'string':
489
- case 'password':
490
- case 'secret':
491
- case 'file':
492
- case 'text':
493
- if (typeof value !== 'string')
494
- return `should be a string, got ${typeof value}`;
495
- break;
496
- case 'json':
497
- break;
498
- }
499
- return undefined;
339
+ function isBuildManifestEntry(value) {
340
+ return isRecord(value) && typeof value['tableName'] === 'string';
500
341
  }
501
342
  //# sourceMappingURL=config.js.map