@malloydata/malloy 0.0.387 → 0.0.389

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.
@@ -133,27 +133,42 @@ export declare class MalloyConfig {
133
133
  */
134
134
  wrapConnections(wrapper: (base: LookupConnection<Connection>) => LookupConnection<Connection>): void;
135
135
  /**
136
- * Notify every connection this config has handed out that it is time to
137
- * release its resources, then drop them from the internal cache.
136
+ * Notify every connection this config has handed out that the host is
137
+ * done with it, applying the requested disposition to its backend
138
+ * resources. Two policies:
138
139
  *
139
- * Most callers should use `Runtime.releaseConnections()` instead — the
140
- * expected contract is one MalloyConfig per Runtime, and the runtime is
141
- * the natural handle for lifecycle. This method exists because Runtime
140
+ * - `'close'` (default) destructive. Calls `Connection.close()` on each
141
+ * cached entry and drops the cache. Subsequent operations on those
142
+ * connection objects may fail. Use this at real shutdown: process
143
+ * exit, extension deactivate, config-file change.
144
+ *
145
+ * - `'idle'` — reversible. Calls `Connection.idle()` on each cached entry
146
+ * without dropping the cache. The same Connection objects are reused
147
+ * on next lookup; schema cache and other in-process state survive.
148
+ * The next operation transparently reattaches any backend resources
149
+ * that were released. Use this between operations in long-lived hosts
150
+ * (VSCode, MCP servers, anything that builds Runtimes per request) to
151
+ * release file locks / pooled sockets while the host is otherwise
152
+ * idle.
153
+ *
154
+ * Most callers should use `Runtime.shutdown(...)` instead — the expected
155
+ * contract is one MalloyConfig per Runtime, and the runtime is the
156
+ * natural handle for lifecycle. This method exists because Runtime
142
157
  * forwards to it, and for the rare case of a MalloyConfig constructed
143
158
  * without an accompanying Runtime.
144
159
  *
145
160
  * MalloyConfig does not own any connection resources itself — pools,
146
161
  * sockets, file handles, and in-process databases all live inside the
147
162
  * individual Connection objects. What the managed lookup owns is a cache
148
- * of `name → Connection` populated lazily as callers request connections.
149
- * This method walks that cache and calls `Connection.close()` on each
150
- * entry, which is the signal each connection uses to shut down whatever
151
- * resources it actually holds.
152
- *
153
- * Connections that were never looked up were never constructed and have
154
- * nothing to release; they are skipped. Wrapping lookups installed via
155
- * `wrapConnections()` do not interfere — the managed lookup under the
156
- * wrap is the one holding the cache.
163
+ * of `name → Connection` populated lazily as callers request
164
+ * connections. Connections that were never looked up were never
165
+ * constructed and have nothing to release; they are skipped. Wrapping
166
+ * lookups installed via `wrapConnections()` do not interfere — the
167
+ * managed lookup under the wrap is the one holding the cache.
168
+ */
169
+ shutdown(connections?: 'close' | 'idle'): Promise<void>;
170
+ /**
171
+ * @deprecated Use `shutdown('close')` instead.
157
172
  */
158
173
  releaseConnections(): Promise<void>;
159
174
  /**
@@ -216,30 +216,52 @@ class MalloyConfig {
216
216
  this._connections = wrapper(this._connections);
217
217
  }
218
218
  /**
219
- * Notify every connection this config has handed out that it is time to
220
- * release its resources, then drop them from the internal cache.
219
+ * Notify every connection this config has handed out that the host is
220
+ * done with it, applying the requested disposition to its backend
221
+ * resources. Two policies:
221
222
  *
222
- * Most callers should use `Runtime.releaseConnections()` instead — the
223
- * expected contract is one MalloyConfig per Runtime, and the runtime is
224
- * the natural handle for lifecycle. This method exists because Runtime
223
+ * - `'close'` (default) destructive. Calls `Connection.close()` on each
224
+ * cached entry and drops the cache. Subsequent operations on those
225
+ * connection objects may fail. Use this at real shutdown: process
226
+ * exit, extension deactivate, config-file change.
227
+ *
228
+ * - `'idle'` — reversible. Calls `Connection.idle()` on each cached entry
229
+ * without dropping the cache. The same Connection objects are reused
230
+ * on next lookup; schema cache and other in-process state survive.
231
+ * The next operation transparently reattaches any backend resources
232
+ * that were released. Use this between operations in long-lived hosts
233
+ * (VSCode, MCP servers, anything that builds Runtimes per request) to
234
+ * release file locks / pooled sockets while the host is otherwise
235
+ * idle.
236
+ *
237
+ * Most callers should use `Runtime.shutdown(...)` instead — the expected
238
+ * contract is one MalloyConfig per Runtime, and the runtime is the
239
+ * natural handle for lifecycle. This method exists because Runtime
225
240
  * forwards to it, and for the rare case of a MalloyConfig constructed
226
241
  * without an accompanying Runtime.
227
242
  *
228
243
  * MalloyConfig does not own any connection resources itself — pools,
229
244
  * sockets, file handles, and in-process databases all live inside the
230
245
  * individual Connection objects. What the managed lookup owns is a cache
231
- * of `name → Connection` populated lazily as callers request connections.
232
- * This method walks that cache and calls `Connection.close()` on each
233
- * entry, which is the signal each connection uses to shut down whatever
234
- * resources it actually holds.
235
- *
236
- * Connections that were never looked up were never constructed and have
237
- * nothing to release; they are skipped. Wrapping lookups installed via
238
- * `wrapConnections()` do not interfere — the managed lookup under the
239
- * wrap is the one holding the cache.
246
+ * of `name → Connection` populated lazily as callers request
247
+ * connections. Connections that were never looked up were never
248
+ * constructed and have nothing to release; they are skipped. Wrapping
249
+ * lookups installed via `wrapConnections()` do not interfere — the
250
+ * managed lookup under the wrap is the one holding the cache.
251
+ */
252
+ async shutdown(connections = 'close') {
253
+ if (connections === 'idle') {
254
+ await this._managedLookup.idle();
255
+ }
256
+ else {
257
+ await this._managedLookup.close();
258
+ }
259
+ }
260
+ /**
261
+ * @deprecated Use `shutdown('close')` instead.
240
262
  */
241
263
  async releaseConnections() {
242
- await this._managedLookup.close();
264
+ await this.shutdown('close');
243
265
  }
244
266
  /**
245
267
  * Query a value from the overlays used to resolve this config.
@@ -83,6 +83,13 @@ function buildManagedLookup(compiledConnections, overlays, log) {
83
83
  await conn.close();
84
84
  }
85
85
  },
86
+ async idle() {
87
+ // Cache is preserved — same Connection objects are reused so that
88
+ // schema cache and other in-process state survive the idle.
89
+ for (const conn of cache.values()) {
90
+ await conn.idle();
91
+ }
92
+ },
86
93
  };
87
94
  }
88
95
  /**
@@ -91,7 +98,14 @@ function buildManagedLookup(compiledConnections, overlays, log) {
91
98
  * gets handed to the factory.
92
99
  */
93
100
  async function resolveCompiledEntry(entry, overlays, log) {
94
- const resolved = (await resolveNode(entry, overlays, log));
101
+ const resolved = await resolveNode(entry, overlays, log);
102
+ // resolveNode returns `unknown`. The compileConnections pipeline
103
+ // guarantees every connection entry is an object dict with `is` set to
104
+ // a literal string, so this should always pass — but a structured
105
+ // throw beats a downstream NPE if a compiler bug ever sneaks through.
106
+ if (!(0, registry_1.isConnectionConfigEntry)(resolved)) {
107
+ throw new Error('Connection entry did not resolve to a valid {is: string, ...} dict');
108
+ }
95
109
  await applyPropertyDefaults(resolved, overlays);
96
110
  return resolved;
97
111
  }
@@ -126,16 +126,27 @@ export declare class Runtime {
126
126
  get virtualMap(): VirtualMap | undefined;
127
127
  set virtualMap(map: VirtualMap | undefined);
128
128
  /**
129
- * Notify every connection this runtime's config has handed out that it
130
- * is time to release its resources (pools, sockets, file handles,
131
- * in-process databases). A no-op for runtimes constructed without a
132
- * MalloyConfig in that case the caller owns the connections they
133
- * passed in and is responsible for closing them.
134
- *
135
- * The expected contract is one MalloyConfig per Runtime. Long-running
136
- * hosts (Publisher, a VS Code extension tearing down a project) should
137
- * call this when a runtime goes out of scope; one-shot CLIs can skip it
138
- * and let process exit clean up.
129
+ * Tell this runtime's connections what to do with their backend
130
+ * resources now that the host is done with this Runtime. Two policies:
131
+ *
132
+ * - `'close'` (default) destructive shutdown. Connections release
133
+ * resources and become unusable. Use at real shutdown: process exit,
134
+ * extension deactivate, config-file change.
135
+ *
136
+ * - `'idle'` reversible release. Connections release expensive
137
+ * resources (DuckDB file locks, socket pools) but stay logically
138
+ * valid. The same Connection objects are reused on next lookup;
139
+ * schema cache and other in-process state survive. Use between
140
+ * operations in long-lived hosts (a VSCode extension, an MCP server,
141
+ * any host that builds Runtimes per request) so that other writers
142
+ * can claim resources during idle gaps.
143
+ *
144
+ * A no-op for runtimes constructed without a MalloyConfig — in that
145
+ * case the caller owns the connections they passed in.
146
+ */
147
+ shutdown(connections?: 'close' | 'idle'): Promise<void>;
148
+ /**
149
+ * @deprecated Use `shutdown('close')` instead.
139
150
  */
140
151
  releaseConnections(): Promise<void>;
141
152
  /**
@@ -160,7 +160,11 @@ class Runtime {
160
160
  return undefined;
161
161
  }
162
162
  try {
163
- return JSON.parse(text);
163
+ const parsed = JSON.parse(text);
164
+ if (!isBuildManifestShape(parsed)) {
165
+ throw new Error('manifest is not an object with an "entries" map');
166
+ }
167
+ return parsed;
164
168
  }
165
169
  catch (e) {
166
170
  // File was present but couldn't be parsed. Return an empty
@@ -193,20 +197,33 @@ class Runtime {
193
197
  this._virtualMap = map;
194
198
  }
195
199
  /**
196
- * Notify every connection this runtime's config has handed out that it
197
- * is time to release its resources (pools, sockets, file handles,
198
- * in-process databases). A no-op for runtimes constructed without a
199
- * MalloyConfig in that case the caller owns the connections they
200
- * passed in and is responsible for closing them.
200
+ * Tell this runtime's connections what to do with their backend
201
+ * resources now that the host is done with this Runtime. Two policies:
202
+ *
203
+ * - `'close'` (default) destructive shutdown. Connections release
204
+ * resources and become unusable. Use at real shutdown: process exit,
205
+ * extension deactivate, config-file change.
206
+ *
207
+ * - `'idle'` — reversible release. Connections release expensive
208
+ * resources (DuckDB file locks, socket pools) but stay logically
209
+ * valid. The same Connection objects are reused on next lookup;
210
+ * schema cache and other in-process state survive. Use between
211
+ * operations in long-lived hosts (a VSCode extension, an MCP server,
212
+ * any host that builds Runtimes per request) so that other writers
213
+ * can claim resources during idle gaps.
201
214
  *
202
- * The expected contract is one MalloyConfig per Runtime. Long-running
203
- * hosts (Publisher, a VS Code extension tearing down a project) should
204
- * call this when a runtime goes out of scope; one-shot CLIs can skip it
205
- * and let process exit clean up.
215
+ * A no-op for runtimes constructed without a MalloyConfig in that
216
+ * case the caller owns the connections they passed in.
206
217
  */
207
- async releaseConnections() {
218
+ async shutdown(connections = 'close') {
208
219
  var _a;
209
- await ((_a = this._config) === null || _a === void 0 ? void 0 : _a.releaseConnections());
220
+ await ((_a = this._config) === null || _a === void 0 ? void 0 : _a.shutdown(connections));
221
+ }
222
+ /**
223
+ * @deprecated Use `shutdown('close')` instead.
224
+ */
225
+ async releaseConnections() {
226
+ await this.shutdown('close');
210
227
  }
211
228
  /**
212
229
  * Load a Malloy model by URL or contents.
@@ -532,7 +549,7 @@ class ModelMaterializer extends FluentState {
532
549
  const result = await this.loadQuery(searchMapMalloy, options).run({
533
550
  rowLimit: 1000,
534
551
  });
535
- const rawResult = result._queryResult.result;
552
+ const rawResult = result.data.toObject();
536
553
  return rawResult.map(row => ({
537
554
  ...row,
538
555
  cardinality: (0, row_data_utils_1.rowDataToNumber)(row.cardinality),
@@ -870,4 +887,18 @@ class ExploreMaterializer extends FluentState {
870
887
  }
871
888
  }
872
889
  exports.ExploreMaterializer = ExploreMaterializer;
890
+ /**
891
+ * Structural check for the `BuildManifest` shape: a non-null object with an
892
+ * object `entries` field. Doesn't validate every entry — `BuildManifestEntry`
893
+ * is just `{tableName: string}`, so a stricter walk could come later if we
894
+ * find malformed entries causing trouble. The current goal is to fail
895
+ * cleanly on a manifest file that parsed to a string/array/null instead of
896
+ * leaving a downstream `entries[buildId]` lookup to crash on `undefined`.
897
+ */
898
+ function isBuildManifestShape(value) {
899
+ if (typeof value !== 'object' || value === null)
900
+ return false;
901
+ const entries = value.entries;
902
+ return typeof entries === 'object' && entries !== null;
903
+ }
873
904
  //# sourceMappingURL=runtime.js.map
@@ -40,6 +40,7 @@ export declare abstract class BaseConnection implements Connection {
40
40
  canPersist(): this is PersistSQLResults;
41
41
  canStream(): this is StreamingConnection;
42
42
  close(): Promise<void>;
43
+ idle(): Promise<void>;
43
44
  estimateQueryCost(_sqlCommand: string): Promise<QueryRunStats>;
44
45
  fetchMetadata(): Promise<{}>;
45
46
  fetchTableMetadata(_tablePath: string): Promise<{}>;
@@ -78,6 +78,7 @@ class BaseConnection {
78
78
  return false;
79
79
  }
80
80
  async close() { }
81
+ async idle() { }
81
82
  async estimateQueryCost(_sqlCommand) {
82
83
  return {};
83
84
  }
@@ -122,11 +122,14 @@ export declare function readConnectionsConfig(jsonText: string): ConnectionsConf
122
122
  export declare function writeConnectionsConfig(config: ConnectionsConfig): string;
123
123
  /**
124
124
  * A LookupConnection with lifecycle management: close() shuts down all
125
- * cached connections, and an optional onConnectionCreated callback fires
126
- * once per connection after factory creation (before caching).
125
+ * cached connections, idle() releases their backend resources but keeps
126
+ * the cache so the same connection objects are reused on next lookup, and
127
+ * an optional onConnectionCreated callback fires once per connection after
128
+ * factory creation (before caching).
127
129
  */
128
130
  export interface ManagedConnectionLookup extends LookupConnection<Connection> {
129
131
  close(): Promise<void>;
132
+ idle(): Promise<void>;
130
133
  }
131
134
  /**
132
135
  * Create a ManagedConnectionLookup from a ConnectionsConfig using registered
@@ -159,6 +159,13 @@ function createConnectionsFromConfig(config, onConnectionCreated) {
159
159
  await conn.close();
160
160
  }
161
161
  },
162
+ async idle() {
163
+ // Cache is preserved — the same Connection objects are reused so that
164
+ // schema cache and other in-process state survive the idle.
165
+ for (const conn of cache.values()) {
166
+ await conn.idle();
167
+ }
168
+ },
162
169
  };
163
170
  }
164
171
  //# sourceMappingURL=registry.js.map
@@ -77,6 +77,20 @@ export interface Connection extends InfoConnection {
77
77
  canPersist(): this is PersistSQLResults;
78
78
  canStream(): this is StreamingConnection;
79
79
  close(): Promise<void>;
80
+ /**
81
+ * Release expensive backend resources (file locks, sockets, sub-processes,
82
+ * pooled connections) but remain logically valid. The next operation
83
+ * transparently reattaches whatever was released; schema cache and other
84
+ * in-process state survive.
85
+ *
86
+ * The default is a no-op for backends that hold no release-able resources
87
+ * between operations. Backends that hold OS-level resources (DuckDB file
88
+ * locks, persistent socket pools) should override.
89
+ *
90
+ * Hosts that share a connection across concurrent operations should not
91
+ * call `idle()` while an operation is in flight.
92
+ */
93
+ idle(): Promise<void>;
80
94
  estimateQueryCost(sqlCommand: string): Promise<QueryRunStats>;
81
95
  fetchMetadata: () => Promise<ConnectionMetadata>;
82
96
  fetchTableMetadata: (tablePath: string) => Promise<TableMetadata>;
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const MALLOY_VERSION = "0.0.387";
1
+ export declare const MALLOY_VERSION = "0.0.389";
package/dist/version.js CHANGED
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MALLOY_VERSION = void 0;
4
4
  // generated with 'generate-version-file' script; do not edit manually
5
- exports.MALLOY_VERSION = '0.0.387';
5
+ exports.MALLOY_VERSION = '0.0.389';
6
6
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloy",
3
- "version": "0.0.387",
3
+ "version": "0.0.389",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",
@@ -51,9 +51,9 @@
51
51
  "generate-version-file": "VERSION=$(npm pkg get version --workspaces=false | tr -d \\\")\necho \"// generated with 'generate-version-file' script; do not edit manually\\nexport const MALLOY_VERSION = '$VERSION';\" > src/version.ts"
52
52
  },
53
53
  "dependencies": {
54
- "@malloydata/malloy-filter": "0.0.387",
55
- "@malloydata/malloy-interfaces": "0.0.387",
56
- "@malloydata/malloy-tag": "0.0.387",
54
+ "@malloydata/malloy-filter": "0.0.389",
55
+ "@malloydata/malloy-interfaces": "0.0.389",
56
+ "@malloydata/malloy-tag": "0.0.389",
57
57
  "@noble/hashes": "^1.8.0",
58
58
  "antlr4ts": "^0.5.0-alpha.4",
59
59
  "assert": "^2.0.0",