@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.
- package/dist/api/foundation/config.d.ts +109 -108
- package/dist/api/foundation/config.js +205 -364
- package/dist/api/foundation/config_compile.d.ts +34 -0
- package/dist/api/foundation/config_compile.js +247 -0
- package/dist/api/foundation/config_discover.d.ts +37 -0
- package/dist/api/foundation/config_discover.js +133 -0
- package/dist/api/foundation/config_overlays.d.ts +54 -0
- package/dist/api/foundation/config_overlays.js +51 -0
- package/dist/api/foundation/config_resolve.d.ts +49 -0
- package/dist/api/foundation/config_resolve.js +230 -0
- package/dist/api/foundation/index.d.ts +3 -0
- package/dist/api/foundation/index.js +7 -1
- package/dist/api/foundation/runtime.d.ts +54 -9
- package/dist/api/foundation/runtime.js +98 -14
- package/dist/connection/registry.d.ts +14 -23
- package/dist/connection/registry.js +5 -30
- package/dist/index.d.ts +4 -3
- package/dist/index.js +5 -3
- package/dist/model/malloy_types.d.ts +8 -0
- package/dist/model/query_query.js +4 -1
- package/dist/model/sql_compiled.js +4 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -5
|
@@ -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
|
|
10
|
-
const
|
|
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
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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 `
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* a
|
|
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(
|
|
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 —
|
|
70
|
-
* mutate the same object, so passing this to a Runtime means
|
|
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
|
-
*
|
|
137
|
-
*
|
|
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
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
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
|
-
*
|
|
144
|
-
* const config = new MalloyConfig(configText);
|
|
132
|
+
* Typical usage:
|
|
145
133
|
*
|
|
146
|
-
* //
|
|
147
|
-
*
|
|
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
|
-
*
|
|
151
|
-
* config
|
|
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(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
186
|
-
*
|
|
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
|
|
204
|
-
return this.
|
|
202
|
+
get connections() {
|
|
203
|
+
return this._connections;
|
|
205
204
|
}
|
|
206
205
|
/**
|
|
207
|
-
*
|
|
208
|
-
*
|
|
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
|
-
|
|
211
|
-
|
|
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
|
-
*
|
|
220
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
226
|
-
*
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
279
|
-
|
|
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
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
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
|
-
|
|
289
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
299
|
-
|
|
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
|
-
|
|
295
|
+
configURL = new URL(configURLValue);
|
|
302
296
|
}
|
|
303
|
-
catch
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
328
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
*
|
|
371
|
-
*
|
|
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
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
|
419
|
-
for (const [
|
|
420
|
-
if (
|
|
421
|
-
|
|
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
|
|
334
|
+
return outer.size > 0 ? outer : undefined;
|
|
444
335
|
}
|
|
445
|
-
function
|
|
446
|
-
|
|
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
|
|
477
|
-
|
|
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
|