@deephaven-enterprise/query-utils 2026.1.27-- → 2026.1.29--

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,47 @@
1
+ import type { EnterpriseClient, WorkerKind } from '@deephaven-enterprise/jsapi-types';
2
+ import type { dh as DhType } from '@deephaven/jsapi-types';
3
+ /**
4
+ * Interface defining a connection manager for fetching APIs from and creating connections to CorePlus workers. Implementation may cache values.
5
+ */
6
+ export interface CorePlusManager {
7
+ /** The DHE JS API client */
8
+ get dheClient(): EnterpriseClient;
9
+ /** Whether this has been disposed. */
10
+ get isDisposed(): boolean;
11
+ /** Dispose resources. */
12
+ dispose(): Promise<void>;
13
+ /**
14
+ * Get the API for the specified worker kind.
15
+ * @param engine The engine to get the API for
16
+ * @param jsApiUrl The URL to load the API from
17
+ * @return The API for the specified worker kind
18
+ */
19
+ getApi(engine: string, jsApiUrl: string): Promise<typeof DhType>;
20
+ /**
21
+ * Get the CoreClient for the specified worker.
22
+ * @param dh The Core API to create the CoreClient with.
23
+ * @param grpcUrl The gRPC URL
24
+ * @param envoyPrefix The envoy prefix
25
+ * @return The CoreClient for the specified worker kind
26
+ */
27
+ getClient(dh: typeof DhType, grpcUrl: string, envoyPrefix?: string | null): Promise<DhType.CoreClient>;
28
+ /**
29
+ * Get the IDE connection for the specified worker.
30
+ * @param dh The Core API to create the CoreClient with.
31
+ * @param grpcUrl The gRPC URL
32
+ * @param envoyPrefix The envoy prefix
33
+ * @return The IDE connection for the specified worker kind
34
+ */
35
+ getConnection(dh: typeof DhType, grpcUrl: string, envoyPrefix?: string | null): Promise<DhType.IdeConnection>;
36
+ /**
37
+ * Get the worker kind or null for the specified engine.
38
+ * @param engine The engine to get the worker kind for
39
+ */
40
+ getEngineWorkerKind(engine: string): WorkerKind | null;
41
+ /**
42
+ * Check if an engine is a community worker kind.
43
+ * @param engine The engine name to check
44
+ */
45
+ isCommunityWorkerKind(engine: string | null): boolean;
46
+ }
47
+ export default CorePlusManager;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ export declare class QueryStatus {
2
+ static getDisplayString(status?: string | null): string;
3
+ static getClassName(status?: string | null): string;
4
+ static uninitialized: string;
5
+ static connecting: string;
6
+ static authenticating: string;
7
+ static acquiringWorker: string;
8
+ static initializing: string;
9
+ static running: string;
10
+ static failed: string;
11
+ static error: string;
12
+ static disconnected: string;
13
+ static stopping: string;
14
+ static stopped: string;
15
+ static completed: string;
16
+ static executing: string;
17
+ static none: string;
18
+ }
19
+ export default QueryStatus;
@@ -0,0 +1,31 @@
1
+ const NULL_STATUS_STRING = 'None';
2
+ export class QueryStatus {
3
+ // returns a humanized name from status
4
+ static getDisplayString(status) {
5
+ return status != null && status !== QueryStatus.none
6
+ ? status.replace(/([a-z])([A-Z])/g, '$1 $2')
7
+ : NULL_STATUS_STRING;
8
+ }
9
+ static getClassName(status) {
10
+ return (status != null && status !== QueryStatus.none
11
+ ? status.replace(/([a-z])([A-Z])/g, '$1-$2')
12
+ : NULL_STATUS_STRING).toLowerCase();
13
+ }
14
+ }
15
+ QueryStatus.uninitialized = 'Uninitialized';
16
+ QueryStatus.connecting = 'Connecting';
17
+ QueryStatus.authenticating = 'Authenticating';
18
+ QueryStatus.acquiringWorker = 'AcquiringWorker';
19
+ QueryStatus.initializing = 'Initializing';
20
+ QueryStatus.running = 'Running';
21
+ QueryStatus.failed = 'Failed';
22
+ QueryStatus.error = 'Error';
23
+ QueryStatus.disconnected = 'Disconnected';
24
+ QueryStatus.stopping = 'Stopping';
25
+ QueryStatus.stopped = 'Stopped';
26
+ QueryStatus.completed = 'Completed';
27
+ QueryStatus.executing = 'Executing';
28
+ // Some queries have a null status which is translated to an empty string for JS
29
+ // This is not an official status but is listed here for clarity
30
+ QueryStatus.none = '';
31
+ export default QueryStatus;
@@ -0,0 +1,277 @@
1
+ import type { dh } from '@deephaven/jsapi-types';
2
+ import type { UriVariableDescriptor } from '@deephaven/jsapi-bootstrap';
3
+ import type { EnterpriseClient, EnterpriseDhType, QueryInfo, QueryVariableDescriptor, ReplicaStatus, UserInfo } from '@deephaven-enterprise/jsapi-types';
4
+ import { type Brand } from '@deephaven/utils';
5
+ import DraftQuery, { type DraftQueryConstructorObject } from './DraftQuery';
6
+ import type { CorePlusManager } from './CorePlusManager';
7
+ export type ConsoleType = 'groovy' | 'python';
8
+ export type QuerySerial = Brand<'QuerySerial', string>;
9
+ export declare const DEFAULT_TEMPORARY_QUERY_AUTO_DELETE_TIMEOUT_MS: 600000;
10
+ export declare const DEFAULT_TEMPORARY_QUERY_TIMEOUT_MS: 60000;
11
+ export declare const INTERACTIVE_CONSOLE_TEMPORARY_QUEUE_NAME: "InteractiveConsoleTemporaryQueue";
12
+ export declare const QUERY_TIMEOUT: 10000;
13
+ export declare const MESSAGE_TIMEOUT: 10000;
14
+ /** Configuration type for the Core+ WebClientData query */
15
+ export declare const WEB_CLIENT_DATA_CORE_CONFIG_TYPE: "WebClientData";
16
+ export declare const WEB_CLIENT_DATA_CORE_QUERY: "WebClientData";
17
+ export declare const WEB_CLIENT_TABLE_FACTORY_TYPE: "WebClientTableFactoryService";
18
+ export declare const WEB_CLIENT_TABLE_FACTORY_NAME: "WebClientTableFactory";
19
+ export declare const WEB_CLIENT_REVERT_TABLE_TYPE: "RevertTableProviderService";
20
+ export declare const WEB_CLIENT_REVERT_TABLE_NAME: "RevertTableProvider";
21
+ export declare const WEB_CLIENT_WRITE_OBJECT_NAME: "WorkspaceDataWriter";
22
+ export declare const WEB_CLIENT_WRITE_OBJECT_TYPE: "WorkspaceDataWriterService";
23
+ export declare const QUERY_CONFIG_TABLE: "QueryInfo";
24
+ export declare const SCOPE_CONFIG_TABLE: "ScopeNamesAndType";
25
+ export declare const CATALOG_TABLE: "catalog";
26
+ export declare const FACTORY_SERVICE_TABLES: Set<"QueryInfo" | "ScopeNamesAndType" | "catalog">;
27
+ export type TemporaryDraftQueryConfig = Omit<DraftQueryConstructorObject, 'scheduling' | 'workerKind' | 'type'> & {
28
+ type: string;
29
+ engine: string;
30
+ isCommunityWorker?: boolean;
31
+ };
32
+ export type WebClientData2Response = {
33
+ id: string;
34
+ error: string | null;
35
+ };
36
+ export type WebClientData2WriteResponse = WebClientData2Response & {
37
+ uuids: string[];
38
+ };
39
+ /**
40
+ * Creates a temporary draft query with auto-delete enabled.
41
+ *
42
+ * Temporary queries are automatically scheduled on the InteractiveConsoleTemporaryQueue
43
+ * and will be deleted when they complete or timeout. This is useful for queries that
44
+ * should only run once and be cleaned up automatically.
45
+ *
46
+ * @param config Configuration object for the temporary draft query
47
+ * @param config.additionalMemory Additional memory in MB to allocate beyond the heap size (default: 0)
48
+ * @param config.engine The engine/worker kind to use for the query
49
+ * @param config.heapSize JVM heap size multiplier (default: 0.5)
50
+ * @param config.isClientSide Whether this is a client-side query (default: true)
51
+ * @param config.isCommunityWorker Whether this is a Community (Core+) worker (default: false)
52
+ * @param config.initializationThreads Number of threads for initialization (Core+ only)
53
+ * @param config.updateThreads Number of threads for updates (Core+ only)
54
+ * @param config.timeout Timeout in milliseconds before auto-deletion (default: 60000)
55
+ * @returns A configured DraftQuery instance ready to be created
56
+ */
57
+ export declare function createTemporaryDraftQuery({ additionalMemory, engine, heapSize, isClientSide, isCommunityWorker, initializationThreads, updateThreads, timeout, ...config }: TemporaryDraftQueryConfig): DraftQuery;
58
+ /**
59
+ * Gets the Core+ connection for a given query.
60
+ *
61
+ * @param params Function parameters
62
+ * @param params.query Query to get the connection for
63
+ * @param params.corePlusManager CorePlus manager for worker connections
64
+ * @returns the Core+ connection for the query
65
+ * @throws Error if the query is not running or not on a Core+ worker
66
+ */
67
+ export declare function getCoreConnection({ query, corePlusManager, }: {
68
+ query: QueryInfo;
69
+ corePlusManager: CorePlusManager;
70
+ }): Promise<dh.IdeConnection>;
71
+ /**
72
+ * Get QueryInfo for a string which may be the query name or its serial.
73
+ * @param params Function parameters
74
+ * @param params.client Enterprise client to use
75
+ * @param params.dh Deephaven Enterprise API
76
+ * @param params.queryNameOrSerial String representing either the query name or serial, but we don't know which it is.
77
+ * @returns QueryInfo for the specified query
78
+ * @throws Error if the query is not found
79
+ */
80
+ export declare function getQueryInfoForNameOrSerial({ client, dh, queryNameOrSerial, }: {
81
+ client: EnterpriseClient;
82
+ dh: EnterpriseDhType;
83
+ queryNameOrSerial: string;
84
+ }): Promise<QueryInfo>;
85
+ /**
86
+ * Get ObjectDefinition from a query by object name
87
+ * @param query Query
88
+ * @param name The name of the object to fetch
89
+ * @returns Resolves to the object definition
90
+ */
91
+ export declare function getQueryObjectDefinitionByName(query: QueryInfo, name: string): dh.ide.VariableDefinition;
92
+ /**
93
+ * Get replica or spare worker for a given slot. If slot is null, returns the designated worker.
94
+ * @param query Query to get the worker from
95
+ * @param slot The slot to get the worker for
96
+ * @returns Resolves to the ReplicaStatus
97
+ */
98
+ export declare function getWorkerForSlot(query: QueryInfo, slot?: number | null): ReplicaStatus | undefined;
99
+ /**
100
+ * Indicates that a table should be fetched from the WebClientTableFactoryService rather than the query scope.
101
+ *
102
+ * @param tableName the name of the table
103
+ * @returns true if the table is a factory service table, false otherwise
104
+ */
105
+ export declare function isFactoryServiceTable(tableName: string): boolean;
106
+ /**
107
+ * Turns a URI into a QueryVariableDescriptor
108
+ * @param params Function parameters
109
+ * @param params.client Enterprise client to use
110
+ * @param params.dh Deephaven Enterprise API
111
+ * @param params.uri The URI to convert to a descriptor
112
+ * @returns A promise that resolves to the query variable descriptor
113
+ * @throws Error if the URI is invalid, not found, or an unsupported protocol
114
+ */
115
+ export declare function makeDescriptorFromUri({ client, dh, uri, }: {
116
+ client: EnterpriseClient;
117
+ dh: EnterpriseDhType;
118
+ uri: UriVariableDescriptor;
119
+ }): Promise<QueryVariableDescriptor>;
120
+ /**
121
+ * Get a table from the WebClientData Core+ WebClientTableFactoryService
122
+ * @param params Function parameters
123
+ * @param params.client Enterprise client
124
+ * @param params.corePlusManager CorePlus manager for worker connections
125
+ * @param params.dh Deephaven Enterprise API
126
+ * @param params.userInfo User information
127
+ * @param params.tableName Table name to get
128
+ * @returns Promise for the table
129
+ */
130
+ export declare function makeFactoryServiceTablePromise({ client, corePlusManager, dh, userInfo, tableName, }: {
131
+ client: EnterpriseClient;
132
+ corePlusManager: CorePlusManager;
133
+ dh: EnterpriseDhType;
134
+ userInfo: UserInfo;
135
+ tableName?: string;
136
+ }): Promise<dh.Table>;
137
+ /**
138
+ * Creates PQ Query Promise
139
+ * @param params Function parameters
140
+ * @param params.client Enterprise client to use
141
+ * @param params.dh Deephaven Enterprise API
142
+ * @param params.queryName Query name
143
+ * @param params.querySerial Serial to check - if present, will use this instead of name to match
144
+ * @returns A promise for the query
145
+ */
146
+ export declare function makeQueryPromise({ client, dh, queryName, querySerial, }: {
147
+ client: EnterpriseClient;
148
+ dh: EnterpriseDhType;
149
+ queryName: string | undefined;
150
+ querySerial?: string | null;
151
+ }): Promise<QueryInfo>;
152
+ /**
153
+ * Creates PQ Query Object Promise
154
+ * @param params Function parameters
155
+ * @param params.client Enterprise client to use
156
+ * @param params.corePlusManager CorePlus manager for worker connections
157
+ * @param params.dh Deephaven Enterprise API
158
+ * @param params.queryName Query name to fetch
159
+ * @param params.querySerial Serial to check - if present, will use this instead of name to match
160
+ * @param params.objectDescriptor The descriptor of the object to fetch
161
+ * @param params.replicaSlot The replica slot to use, if null will use the designated worker
162
+ * @returns Resolves to the object fetched
163
+ */
164
+ export declare function makeQueryObjectPromise<T = unknown>({ client, corePlusManager, dh, queryName, querySerial, objectDescriptor, replicaSlot, }: {
165
+ client: EnterpriseClient;
166
+ corePlusManager: CorePlusManager;
167
+ dh: EnterpriseDhType;
168
+ queryName: string | null | undefined;
169
+ querySerial: string | null | undefined;
170
+ objectDescriptor: dh.ide.VariableDescriptor;
171
+ replicaSlot?: number;
172
+ }): Promise<T>;
173
+ /**
174
+ * Creates PQ Table Promise
175
+ * Should use makeQueryObjectPromise when possible instead.
176
+ * @param params Function parameters
177
+ * @param params.client Enterprise client
178
+ * @param params.corePlusManager CorePlus manager for worker connections
179
+ * @param params.dh Deephaven Enterprise API
180
+ * @param params.queryName Query name
181
+ * @param params.querySerial Query serial
182
+ * @param params.tableName Table name
183
+ * @returns A promise for the table or tree table
184
+ */
185
+ export declare function makeQueryTablePromise({ client, corePlusManager, dh, queryName, querySerial, tableName, }: {
186
+ client: EnterpriseClient;
187
+ corePlusManager: CorePlusManager;
188
+ dh: EnterpriseDhType;
189
+ queryName: string;
190
+ querySerial: string | null | undefined;
191
+ tableName: string;
192
+ }): Promise<dh.Table | dh.TreeTable>;
193
+ /**
194
+ * Retrieves a table with the revision history for a given query.
195
+ * @param params Function parameters
196
+ * @param params.client Enterprise client
197
+ * @param params.corePlusManager CorePlus manager for worker connections
198
+ * @param params.dh Deephaven Enterprise API
199
+ * @param params.serial Serial of the query to get the revision history for.
200
+ * @returns A promise for the revision history table.
201
+ */
202
+ export declare function makeRevertTablePromise({ client, corePlusManager, dh, serial, }: {
203
+ client: EnterpriseClient;
204
+ corePlusManager: CorePlusManager;
205
+ dh: EnterpriseDhType;
206
+ serial: string;
207
+ }): Promise<dh.Table>;
208
+ /**
209
+ * Sends a message to a WebClientData2 widget and returns a promise that resolves to the event detail.
210
+ *
211
+ * @param params Function parameters
212
+ * @param params.client Enterprise client
213
+ * @param params.corePlusManager CorePlus manager for worker connections
214
+ * @param params.dh Deephaven Enterprise API
215
+ * @param params.messageId the ID of the message to send
216
+ * @param params.message the message to send
217
+ * @param params.type the type of the widget to send the message to
218
+ * @param params.name the name of the widget to send the message to
219
+ * @returns the event detail from the response
220
+ */
221
+ export declare function sendWebClientData2Message<T extends WebClientData2Response>({ client, corePlusManager, dh, messageId, message, type, name, }: {
222
+ client: EnterpriseClient;
223
+ corePlusManager: CorePlusManager;
224
+ dh: EnterpriseDhType;
225
+ messageId: string;
226
+ message: string;
227
+ type: string;
228
+ name: string;
229
+ }): Promise<[T, dh.WidgetMessageDetails]>;
230
+ /**
231
+ * Sends a message to a widget on a Core+ query. Will call the provided callback with the event.
232
+ * It is the responsibility of the calling code to determine if the message is correct and to clean up the event listener.
233
+ * This method will time out after a set period if the message is not received.
234
+ *
235
+ * TODO: DH-20345: There are a number of issues with this function not properly
236
+ * handling Promise rejections and timeouts. Since it returns `void`, there is
237
+ * no way for an upstream caller to handle errors or even know about them.
238
+ *
239
+ * @param params Function parameters
240
+ * @param params.client Enterprise client
241
+ * @param params.corePlusManager CorePlus manager for worker connections
242
+ * @param params.dh Deephaven Enterprise API
243
+ * @param params.message the message to send
244
+ * @param params.onEventCallback the callback to handle the event when the message is received
245
+ * @param params.queryName the name of the query to send the message to, null if using serial
246
+ * @param params.querySerial the serial of the query to send the message to, null if using name
247
+ * @param params.objectDefinition the definition of the widget to send the message to
248
+ * @param params.replicaSlot the replica slot to send the message to, if null will use the designated worker
249
+ */
250
+ export declare function sendWidgetMessage({ client, corePlusManager, dh, message, onEventCallback, queryName, querySerial, objectDefinition, replicaSlot, }: {
251
+ client: EnterpriseClient;
252
+ corePlusManager: CorePlusManager;
253
+ dh: EnterpriseDhType;
254
+ message: string;
255
+ onEventCallback: (event: dh.Event<dh.WidgetMessageDetails>, cleanupFn: () => void) => void;
256
+ queryName: string | null | undefined;
257
+ querySerial: string | null | undefined;
258
+ objectDefinition: dh.ide.VariableDescriptor;
259
+ replicaSlot?: number;
260
+ }): void;
261
+ /**
262
+ * Starts a query and returns a promise that resolves when the query starts running.
263
+ *
264
+ * This function:
265
+ * - Creates the query on the server
266
+ * - Listens for status updates
267
+ * - Auto-starts temporary queries (which don't start automatically)
268
+ * - Resolves when the query reaches running status
269
+ * - Rejects if the query fails to start
270
+ *
271
+ * @param dh Deephaven Enterprise API
272
+ * @param client Enterprise client to create the query with
273
+ * @param draftQuery Draft query configuration to start
274
+ * @returns Promise that resolves to the QueryInfo when the query starts running
275
+ * @throws Error if the query fails to start or encounters an error
276
+ */
277
+ export declare function startQuery(dh: EnterpriseDhType, client: EnterpriseClient, draftQuery: DraftQuery): Promise<QueryInfo>;
@@ -0,0 +1,553 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { InvalidMetadataError } from '@deephaven/utils';
13
+ import Log from '@deephaven/log';
14
+ import { nanoid } from 'nanoid';
15
+ import QueryScheduler from './QueryScheduler';
16
+ import DraftQuery from './DraftQuery';
17
+ import { WorkerThreadUtils } from './WorkerThreadUtils';
18
+ import { QueryStatus } from './QueryStatus';
19
+ const log = Log.module('QueryUtils');
20
+ // 600 seconds is based on default `auto_delete_timeout` in
21
+ // `ControllerClient.make_temporary_config`
22
+ export const DEFAULT_TEMPORARY_QUERY_AUTO_DELETE_TIMEOUT_MS = 600000;
23
+ export const DEFAULT_TEMPORARY_QUERY_TIMEOUT_MS = 60000;
24
+ export const INTERACTIVE_CONSOLE_TEMPORARY_QUEUE_NAME = 'InteractiveConsoleTemporaryQueue';
25
+ // Query interaction constants
26
+ export const QUERY_TIMEOUT = 10000;
27
+ export const MESSAGE_TIMEOUT = 10000;
28
+ /** Configuration type for the Core+ WebClientData query */
29
+ export const WEB_CLIENT_DATA_CORE_CONFIG_TYPE = 'WebClientData';
30
+ // Name of the Core+ WebClientData query. Used for reading and writing workspace data.
31
+ export const WEB_CLIENT_DATA_CORE_QUERY = 'WebClientData';
32
+ export const WEB_CLIENT_TABLE_FACTORY_TYPE = 'WebClientTableFactoryService';
33
+ export const WEB_CLIENT_TABLE_FACTORY_NAME = 'WebClientTableFactory';
34
+ export const WEB_CLIENT_REVERT_TABLE_TYPE = 'RevertTableProviderService';
35
+ export const WEB_CLIENT_REVERT_TABLE_NAME = 'RevertTableProvider';
36
+ export const WEB_CLIENT_WRITE_OBJECT_NAME = 'WorkspaceDataWriter';
37
+ export const WEB_CLIENT_WRITE_OBJECT_TYPE = 'WorkspaceDataWriterService';
38
+ export const QUERY_CONFIG_TABLE = 'QueryInfo';
39
+ export const SCOPE_CONFIG_TABLE = 'ScopeNamesAndType';
40
+ export const CATALOG_TABLE = 'catalog';
41
+ export const FACTORY_SERVICE_TABLES = new Set([
42
+ QUERY_CONFIG_TABLE,
43
+ SCOPE_CONFIG_TABLE,
44
+ CATALOG_TABLE,
45
+ ]);
46
+ /**
47
+ * Creates a temporary draft query with auto-delete enabled.
48
+ *
49
+ * Temporary queries are automatically scheduled on the InteractiveConsoleTemporaryQueue
50
+ * and will be deleted when they complete or timeout. This is useful for queries that
51
+ * should only run once and be cleaned up automatically.
52
+ *
53
+ * @param config Configuration object for the temporary draft query
54
+ * @param config.additionalMemory Additional memory in MB to allocate beyond the heap size (default: 0)
55
+ * @param config.engine The engine/worker kind to use for the query
56
+ * @param config.heapSize JVM heap size multiplier (default: 0.5)
57
+ * @param config.isClientSide Whether this is a client-side query (default: true)
58
+ * @param config.isCommunityWorker Whether this is a Community (Core+) worker (default: false)
59
+ * @param config.initializationThreads Number of threads for initialization (Core+ only)
60
+ * @param config.updateThreads Number of threads for updates (Core+ only)
61
+ * @param config.timeout Timeout in milliseconds before auto-deletion (default: 60000)
62
+ * @returns A configured DraftQuery instance ready to be created
63
+ */
64
+ export function createTemporaryDraftQuery(_a) {
65
+ var { additionalMemory = 0, engine, heapSize = 0.5, isClientSide = true, isCommunityWorker = false, initializationThreads, updateThreads, timeout = DEFAULT_TEMPORARY_QUERY_TIMEOUT_MS } = _a, config = __rest(_a, ["additionalMemory", "engine", "heapSize", "isClientSide", "isCommunityWorker", "initializationThreads", "updateThreads", "timeout"]);
66
+ const scheduling = QueryScheduler.makeTemporaryScheduling({
67
+ autoDelete: true,
68
+ queueName: INTERACTIVE_CONSOLE_TEMPORARY_QUEUE_NAME,
69
+ });
70
+ const draftQuery = new DraftQuery(Object.assign(Object.assign({}, config), { additionalMemory,
71
+ heapSize,
72
+ isClientSide,
73
+ scheduling,
74
+ timeout, workerKind: engine,
75
+ // TODO: DH-22081: createTemporaryDraftQuery converts initializationThreads/
76
+ // updateThreads with WorkerThreadUtils.getValue(...) before storing them on
77
+ // the DraftQuery. DraftQuery later calls WorkerThreadUtils.addArgs, which
78
+ // expects display strings
79
+ initializationThreads: isCommunityWorker && initializationThreads != null
80
+ ? WorkerThreadUtils.getValue(initializationThreads)
81
+ : undefined, updateThreads: isCommunityWorker && updateThreads != null
82
+ ? WorkerThreadUtils.getValue(updateThreads)
83
+ : undefined }));
84
+ draftQuery.addThreadCountToJVMArgs();
85
+ draftQuery.updateSchedule();
86
+ return draftQuery;
87
+ }
88
+ /**
89
+ * Gets the Core+ connection for a given query.
90
+ *
91
+ * @param params Function parameters
92
+ * @param params.query Query to get the connection for
93
+ * @param params.corePlusManager CorePlus manager for worker connections
94
+ * @returns the Core+ connection for the query
95
+ * @throws Error if the query is not running or not on a Core+ worker
96
+ */
97
+ export function getCoreConnection({ query, corePlusManager, }) {
98
+ const designated = getWorkerForSlot(query);
99
+ if (designated == null || designated.status !== QueryStatus.running) {
100
+ throw new Error(`Query ${query.name} is not running.`);
101
+ }
102
+ // Sending a message to a widget requires a Core+ worker
103
+ if (!corePlusManager.isCommunityWorkerKind(query.workerKind)) {
104
+ throw new Error(`Query ${query.name} is not running on a Core+ worker.`);
105
+ }
106
+ const { grpcUrl, envoyPrefix, jsApiUrl } = designated;
107
+ return corePlusManager
108
+ .getApi(query.workerKind, jsApiUrl)
109
+ .then(api => corePlusManager.getConnection(api, grpcUrl, envoyPrefix));
110
+ }
111
+ /**
112
+ * Get QueryInfo for a string which may be the query name or its serial.
113
+ * @param params Function parameters
114
+ * @param params.client Enterprise client to use
115
+ * @param params.dh Deephaven Enterprise API
116
+ * @param params.queryNameOrSerial String representing either the query name or serial, but we don't know which it is.
117
+ * @returns QueryInfo for the specified query
118
+ * @throws Error if the query is not found
119
+ */
120
+ export async function getQueryInfoForNameOrSerial({ client, dh, queryNameOrSerial, }) {
121
+ try {
122
+ return await makeQueryPromise({
123
+ client,
124
+ dh,
125
+ queryName: undefined,
126
+ querySerial: queryNameOrSerial,
127
+ });
128
+ }
129
+ catch (error) {
130
+ log.debug(`${queryNameOrSerial} not found as a serial. Trying as a name.`);
131
+ }
132
+ return makeQueryPromise({
133
+ client,
134
+ dh,
135
+ queryName: queryNameOrSerial,
136
+ querySerial: undefined,
137
+ });
138
+ }
139
+ /**
140
+ * Get ObjectDefinition from a query by object name
141
+ * @param query Query
142
+ * @param name The name of the object to fetch
143
+ * @returns Resolves to the object definition
144
+ */
145
+ export function getQueryObjectDefinitionByName(query, name) {
146
+ var _a;
147
+ // Non-running queries don't list the objects
148
+ if (((_a = query.designated) === null || _a === void 0 ? void 0 : _a.status) !== QueryStatus.running) {
149
+ throw new Error(`Query ${query.name} isn't running.`);
150
+ }
151
+ const objectDefinition = query.designated.objects.find(o => o.title === name);
152
+ if (objectDefinition == null) {
153
+ throw new Error(`Object ${name} not found in query ${query.name}.`);
154
+ }
155
+ return objectDefinition;
156
+ }
157
+ /**
158
+ * Get replica or spare worker for a given slot. If slot is null, returns the designated worker.
159
+ * @param query Query to get the worker from
160
+ * @param slot The slot to get the worker for
161
+ * @returns Resolves to the ReplicaStatus
162
+ */
163
+ export function getWorkerForSlot(query, slot) {
164
+ if (slot == null) {
165
+ return query.designated;
166
+ }
167
+ if (slot < 0) {
168
+ return query.spares[-slot - 1];
169
+ }
170
+ return query.replicas[slot];
171
+ }
172
+ /**
173
+ * Indicates that a table should be fetched from the WebClientTableFactoryService rather than the query scope.
174
+ *
175
+ * @param tableName the name of the table
176
+ * @returns true if the table is a factory service table, false otherwise
177
+ */
178
+ export function isFactoryServiceTable(tableName) {
179
+ return FACTORY_SERVICE_TABLES.has(tableName);
180
+ }
181
+ /**
182
+ * Turns a URI into a QueryVariableDescriptor
183
+ * @param params Function parameters
184
+ * @param params.client Enterprise client to use
185
+ * @param params.dh Deephaven Enterprise API
186
+ * @param params.uri The URI to convert to a descriptor
187
+ * @returns A promise that resolves to the query variable descriptor
188
+ * @throws Error if the URI is invalid, not found, or an unsupported protocol
189
+ */
190
+ export async function makeDescriptorFromUri({ client, dh, uri, }) {
191
+ var _a;
192
+ let url;
193
+ try {
194
+ url = new URL(uri);
195
+ }
196
+ catch (e) {
197
+ throw new Error(`Invalid URI: ${uri}. Did you forget to URI encode it?`);
198
+ }
199
+ const protocol = url.protocol.slice(0, -1); // Remove the trailing ':'
200
+ const querySerialOrName = decodeURIComponent(url.hostname);
201
+ const pathName = decodeURIComponent(url.pathname);
202
+ const widgetName = pathName.replace(/^\/scope\//, '');
203
+ if (protocol !== 'pq') {
204
+ throw new Error(`Unsupported URI protocol: ${protocol}`);
205
+ }
206
+ if (!pathName.startsWith('/scope/')) {
207
+ throw new Error(`Expected PQ URI path to start with /scope/. Got ${pathName}`);
208
+ }
209
+ const queryInfo = await getQueryInfoForNameOrSerial({
210
+ client,
211
+ dh,
212
+ queryNameOrSerial: querySerialOrName,
213
+ });
214
+ const variableDefinition = (_a = queryInfo.designated) === null || _a === void 0 ? void 0 : _a.objects.find(obj => obj.name === widgetName);
215
+ if (!variableDefinition) {
216
+ throw new Error(`Query ${querySerialOrName} (Serial: ${queryInfo.serial}) does not have widget named ${widgetName}`);
217
+ }
218
+ return {
219
+ querySerial: queryInfo.serial,
220
+ query: queryInfo.name,
221
+ name: widgetName,
222
+ type: variableDefinition.type,
223
+ };
224
+ }
225
+ /**
226
+ * Get a table from the WebClientData Core+ WebClientTableFactoryService
227
+ * @param params Function parameters
228
+ * @param params.client Enterprise client
229
+ * @param params.corePlusManager CorePlus manager for worker connections
230
+ * @param params.dh Deephaven Enterprise API
231
+ * @param params.userInfo User information
232
+ * @param params.tableName Table name to get
233
+ * @returns Promise for the table
234
+ */
235
+ export async function makeFactoryServiceTablePromise({ client, corePlusManager, dh, userInfo, tableName = QUERY_CONFIG_TABLE, }) {
236
+ // This message id is used to correlate the message sent to the workspace widget
237
+ const messageId = nanoid();
238
+ const message = JSON.stringify({
239
+ id: messageId,
240
+ user: userInfo.operateAs,
241
+ tableNames: [tableName],
242
+ });
243
+ const [, eventDetail] = await sendWebClientData2Message({
244
+ client,
245
+ corePlusManager,
246
+ dh,
247
+ messageId,
248
+ message,
249
+ type: WEB_CLIENT_TABLE_FACTORY_TYPE,
250
+ name: WEB_CLIENT_TABLE_FACTORY_NAME,
251
+ });
252
+ if (eventDetail.exportedObjects.length < 1) {
253
+ throw new Error('No objects exported from the widget');
254
+ }
255
+ const table = await eventDetail.exportedObjects[0].fetch();
256
+ return table;
257
+ }
258
+ /**
259
+ * Creates PQ Query Promise
260
+ * @param params Function parameters
261
+ * @param params.client Enterprise client to use
262
+ * @param params.dh Deephaven Enterprise API
263
+ * @param params.queryName Query name
264
+ * @param params.querySerial Serial to check - if present, will use this instead of name to match
265
+ * @returns A promise for the query
266
+ */
267
+ export async function makeQueryPromise({ client, dh, queryName, querySerial, }) {
268
+ function getMatchingQuery(queries) {
269
+ return querySerial !== undefined
270
+ ? queries.find(query => query.serial === querySerial)
271
+ : queries.find(query => query.name === queryName);
272
+ }
273
+ function waitForAddedQuery() {
274
+ return new Promise((resolve, reject) => {
275
+ let removeListener;
276
+ const timeout = setTimeout(() => {
277
+ reject(new Error(`Query ${querySerial !== null && querySerial !== void 0 ? querySerial : queryName} not found or you do not have access.`));
278
+ removeListener === null || removeListener === void 0 ? void 0 : removeListener();
279
+ }, QUERY_TIMEOUT);
280
+ function handleConfigAdded(event) {
281
+ const addedQueries = [event === null || event === void 0 ? void 0 : event.detail];
282
+ const matchingQuery = getMatchingQuery(addedQueries);
283
+ if (matchingQuery) {
284
+ resolve(matchingQuery);
285
+ clearTimeout(timeout);
286
+ removeListener === null || removeListener === void 0 ? void 0 : removeListener();
287
+ }
288
+ }
289
+ removeListener = client.addEventListener(dh.Client.EVENT_CONFIG_ADDED, handleConfigAdded);
290
+ });
291
+ }
292
+ if (querySerial == null && queryName == null) {
293
+ throw new InvalidMetadataError('Invalid query');
294
+ }
295
+ // Get the query from the known configs,
296
+ const matchingQuery = getMatchingQuery(client.getKnownConfigs());
297
+ if (matchingQuery) {
298
+ return matchingQuery;
299
+ }
300
+ if (queryName === WEB_CLIENT_DATA_CORE_QUERY) {
301
+ // WebClientData is a special case - try and wait for it to become available
302
+ return waitForAddedQuery();
303
+ }
304
+ throw new Error(`Query ${querySerial !== null && querySerial !== void 0 ? querySerial : queryName} not found or you do not have access.`);
305
+ }
306
+ /**
307
+ * Creates PQ Query Object Promise
308
+ * @param params Function parameters
309
+ * @param params.client Enterprise client to use
310
+ * @param params.corePlusManager CorePlus manager for worker connections
311
+ * @param params.dh Deephaven Enterprise API
312
+ * @param params.queryName Query name to fetch
313
+ * @param params.querySerial Serial to check - if present, will use this instead of name to match
314
+ * @param params.objectDescriptor The descriptor of the object to fetch
315
+ * @param params.replicaSlot The replica slot to use, if null will use the designated worker
316
+ * @returns Resolves to the object fetched
317
+ */
318
+ export async function makeQueryObjectPromise({ client, corePlusManager, dh, queryName, querySerial, objectDescriptor, replicaSlot, }) {
319
+ const query = await makeQueryPromise({
320
+ client,
321
+ dh,
322
+ queryName: queryName !== null && queryName !== void 0 ? queryName : undefined,
323
+ querySerial: querySerial !== null && querySerial !== void 0 ? querySerial : undefined,
324
+ });
325
+ const designated = getWorkerForSlot(query, replicaSlot);
326
+ if (designated == null || designated.status !== QueryStatus.running) {
327
+ throw new Error(`Query ${query.name} is not running.`);
328
+ }
329
+ if (corePlusManager.isCommunityWorkerKind(query.workerKind)) {
330
+ const { grpcUrl, envoyPrefix, jsApiUrl } = designated;
331
+ const api = await corePlusManager.getApi(query.workerKind, jsApiUrl);
332
+ const coreConnection = await corePlusManager.getConnection(api, grpcUrl, envoyPrefix);
333
+ // DH-20545: We shouldn't pass the object definition we got from the Enterprise QueryInfo object directly to Core connection.getObject, as they may be incompatible.
334
+ return coreConnection.getObject({
335
+ name: objectDescriptor.name,
336
+ type: objectDescriptor.type,
337
+ });
338
+ }
339
+ return designated.getObject(objectDescriptor);
340
+ }
341
+ /**
342
+ * Creates PQ Table Promise
343
+ * Should use makeQueryObjectPromise when possible instead.
344
+ * @param params Function parameters
345
+ * @param params.client Enterprise client
346
+ * @param params.corePlusManager CorePlus manager for worker connections
347
+ * @param params.dh Deephaven Enterprise API
348
+ * @param params.queryName Query name
349
+ * @param params.querySerial Query serial
350
+ * @param params.tableName Table name
351
+ * @returns A promise for the table or tree table
352
+ */
353
+ export async function makeQueryTablePromise({ client, corePlusManager, dh, queryName, querySerial, tableName, }) {
354
+ var _a;
355
+ const query = await makeQueryPromise({
356
+ client,
357
+ dh,
358
+ queryName,
359
+ querySerial: querySerial !== null && querySerial !== void 0 ? querySerial : undefined,
360
+ });
361
+ if (query.designated == null ||
362
+ query.designated.status !== QueryStatus.running) {
363
+ throw new Error(`Query ${queryName} is not running.`);
364
+ }
365
+ const isTreeTable = ((_a = query.designated.objects.find(({ name }) => name === tableName)) === null || _a === void 0 ? void 0 : _a.type) ===
366
+ dh.VariableType.TREETABLE;
367
+ const { designated } = query;
368
+ if (corePlusManager.isCommunityWorkerKind(query.workerKind)) {
369
+ const { grpcUrl, envoyPrefix, jsApiUrl } = designated;
370
+ const api = await corePlusManager.getApi(query.workerKind, jsApiUrl);
371
+ const coreConnection = await corePlusManager.getConnection(api, grpcUrl, envoyPrefix);
372
+ return coreConnection.getObject({
373
+ type: isTreeTable ? dh.VariableType.TREETABLE : dh.VariableType.TABLE,
374
+ name: tableName,
375
+ });
376
+ }
377
+ return isTreeTable
378
+ ? designated.getTreeTable(tableName)
379
+ : designated.getTable(tableName);
380
+ }
381
+ /**
382
+ * Retrieves a table with the revision history for a given query.
383
+ * @param params Function parameters
384
+ * @param params.client Enterprise client
385
+ * @param params.corePlusManager CorePlus manager for worker connections
386
+ * @param params.dh Deephaven Enterprise API
387
+ * @param params.serial Serial of the query to get the revision history for.
388
+ * @returns A promise for the revision history table.
389
+ */
390
+ export async function makeRevertTablePromise({ client, corePlusManager, dh, serial, }) {
391
+ const messageId = nanoid();
392
+ const message = JSON.stringify({
393
+ id: messageId,
394
+ serial,
395
+ });
396
+ const [, eventDetail] = await sendWebClientData2Message({
397
+ client,
398
+ corePlusManager,
399
+ dh,
400
+ messageId,
401
+ message,
402
+ type: WEB_CLIENT_REVERT_TABLE_TYPE,
403
+ name: WEB_CLIENT_REVERT_TABLE_NAME,
404
+ });
405
+ if (eventDetail.exportedObjects.length < 1) {
406
+ throw new Error('No objects exported from the widget');
407
+ }
408
+ const table = await eventDetail.exportedObjects[0].fetch();
409
+ return table;
410
+ }
411
+ /**
412
+ * Sends a message to a WebClientData2 widget and returns a promise that resolves to the event detail.
413
+ *
414
+ * @param params Function parameters
415
+ * @param params.client Enterprise client
416
+ * @param params.corePlusManager CorePlus manager for worker connections
417
+ * @param params.dh Deephaven Enterprise API
418
+ * @param params.messageId the ID of the message to send
419
+ * @param params.message the message to send
420
+ * @param params.type the type of the widget to send the message to
421
+ * @param params.name the name of the widget to send the message to
422
+ * @returns the event detail from the response
423
+ */
424
+ export function sendWebClientData2Message({ client, corePlusManager, dh, messageId, message, type, name, }) {
425
+ return new Promise((resolve, reject) => {
426
+ // Handle message events from the workspace widget
427
+ const handleMessageEvent = (event, cleanupFn) => {
428
+ const eventDetail = JSON.parse(event.detail.getDataAsString());
429
+ // Only handle the message if the ID matches
430
+ if (eventDetail.id === messageId) {
431
+ // We're handling the message, so we can clean up
432
+ cleanupFn();
433
+ if (eventDetail.error != null) {
434
+ log.error(`Error returned from ${name}:`, eventDetail.error);
435
+ reject(new Error(eventDetail.error));
436
+ return;
437
+ }
438
+ resolve([eventDetail, event.detail]);
439
+ }
440
+ };
441
+ sendWidgetMessage({
442
+ client,
443
+ corePlusManager,
444
+ dh,
445
+ message,
446
+ onEventCallback: handleMessageEvent,
447
+ queryName: WEB_CLIENT_DATA_CORE_QUERY,
448
+ querySerial: null,
449
+ objectDefinition: {
450
+ type,
451
+ name,
452
+ },
453
+ });
454
+ });
455
+ }
456
+ /**
457
+ * Sends a message to a widget on a Core+ query. Will call the provided callback with the event.
458
+ * It is the responsibility of the calling code to determine if the message is correct and to clean up the event listener.
459
+ * This method will time out after a set period if the message is not received.
460
+ *
461
+ * TODO: DH-20345: There are a number of issues with this function not properly
462
+ * handling Promise rejections and timeouts. Since it returns `void`, there is
463
+ * no way for an upstream caller to handle errors or even know about them.
464
+ *
465
+ * @param params Function parameters
466
+ * @param params.client Enterprise client
467
+ * @param params.corePlusManager CorePlus manager for worker connections
468
+ * @param params.dh Deephaven Enterprise API
469
+ * @param params.message the message to send
470
+ * @param params.onEventCallback the callback to handle the event when the message is received
471
+ * @param params.queryName the name of the query to send the message to, null if using serial
472
+ * @param params.querySerial the serial of the query to send the message to, null if using name
473
+ * @param params.objectDefinition the definition of the widget to send the message to
474
+ * @param params.replicaSlot the replica slot to send the message to, if null will use the designated worker
475
+ */
476
+ export function sendWidgetMessage({ client, corePlusManager, dh, message, onEventCallback, queryName, querySerial, objectDefinition, replicaSlot, }) {
477
+ makeQueryPromise({
478
+ client,
479
+ dh,
480
+ queryName: queryName !== null && queryName !== void 0 ? queryName : undefined,
481
+ querySerial: querySerial !== null && querySerial !== void 0 ? querySerial : undefined,
482
+ }).then(async (query) => {
483
+ const designated = getWorkerForSlot(query, replicaSlot);
484
+ if (designated == null || designated.status !== QueryStatus.running) {
485
+ throw new Error(`Query ${query.name} is not running.`);
486
+ }
487
+ // Sending a message to a widget requires a Core+ worker
488
+ if (!corePlusManager.isCommunityWorkerKind(query.workerKind)) {
489
+ throw new Error(`Query ${query.name} is not running on a Core+ worker.`);
490
+ }
491
+ const { grpcUrl, envoyPrefix, jsApiUrl } = designated;
492
+ const api = await corePlusManager.getApi(query.workerKind, jsApiUrl);
493
+ const coreConnection = await corePlusManager.getConnection(api, grpcUrl, envoyPrefix);
494
+ const widget = (await coreConnection.getObject(objectDefinition));
495
+ // Set a timeout for the message to be received
496
+ // TODO: DH-20345: This timeout doesn't properly cleanup or surface an error
497
+ // to the caller.
498
+ const timeoutId = setTimeout(() => {
499
+ // This logs a warning rather than throwing an error because the message may still be received later and to avoid breaking the UI
500
+ log.warn(`No response from the server after ${MESSAGE_TIMEOUT}ms for message: ${message}`);
501
+ }, MESSAGE_TIMEOUT);
502
+ // Messages may come out of order, so the calling code is responsible for determining if the message is correct and clean up
503
+ const removeListener = widget.addEventListener(api.Widget.EVENT_MESSAGE, event => onEventCallback(event, () => {
504
+ clearTimeout(timeoutId);
505
+ removeListener();
506
+ widget.close();
507
+ }));
508
+ widget.sendMessage(message);
509
+ });
510
+ }
511
+ /**
512
+ * Starts a query and returns a promise that resolves when the query starts running.
513
+ *
514
+ * This function:
515
+ * - Creates the query on the server
516
+ * - Listens for status updates
517
+ * - Auto-starts temporary queries (which don't start automatically)
518
+ * - Resolves when the query reaches running status
519
+ * - Rejects if the query fails to start
520
+ *
521
+ * @param dh Deephaven Enterprise API
522
+ * @param client Enterprise client to create the query with
523
+ * @param draftQuery Draft query configuration to start
524
+ * @returns Promise that resolves to the QueryInfo when the query starts running
525
+ * @throws Error if the query fails to start or encounters an error
526
+ */
527
+ export async function startQuery(dh, client, draftQuery) {
528
+ const serial = await client.createQuery(draftQuery);
529
+ const queryInfoPromise = new Promise((resolve, reject) => {
530
+ const removeEventListener = client.addEventListener(dh.Client.EVENT_CONFIG_UPDATED, ({ detail }) => {
531
+ var _a, _b, _c, _d;
532
+ if (detail.serial !== serial) {
533
+ return;
534
+ }
535
+ if (((_a = detail.designated) === null || _a === void 0 ? void 0 : _a.status) === QueryStatus.running) {
536
+ removeEventListener();
537
+ resolve(detail);
538
+ }
539
+ if (((_b = detail.designated) === null || _b === void 0 ? void 0 : _b.status) === QueryStatus.error ||
540
+ ((_c = detail.designated) === null || _c === void 0 ? void 0 : _c.status) === QueryStatus.failed) {
541
+ removeEventListener();
542
+ reject(new Error('Query failed to start', {
543
+ cause: (_d = detail.designated) === null || _d === void 0 ? void 0 : _d.shortCauses,
544
+ }));
545
+ }
546
+ });
547
+ });
548
+ // Temporary queries don't auto start, so we need to start it manually.
549
+ if (draftQuery.scheduler.type === QueryScheduler.TYPES.TEMPORARY) {
550
+ client.restartQueries([serial]);
551
+ }
552
+ return queryInfoPromise;
553
+ }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,10 @@
1
+ export * from './CorePlusManager';
1
2
  export * from './DraftQuery';
2
3
  export * from './QueryColumns';
3
4
  export * from './QueryDisplayType';
4
5
  export * from './QueryScheduler';
5
6
  export * from './QuerySchedulerValidation';
7
+ export * from './QueryStatus';
6
8
  export * from './QueryType';
9
+ export * from './QueryUtils';
7
10
  export * from './WorkerThreadUtils';
package/dist/index.js CHANGED
@@ -1,7 +1,10 @@
1
+ export * from './CorePlusManager';
1
2
  export * from './DraftQuery';
2
3
  export * from './QueryColumns';
3
4
  export * from './QueryDisplayType';
4
5
  export * from './QueryScheduler';
5
6
  export * from './QuerySchedulerValidation';
7
+ export * from './QueryStatus';
6
8
  export * from './QueryType';
9
+ export * from './QueryUtils';
7
10
  export * from './WorkerThreadUtils';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deephaven-enterprise/query-utils",
3
- "version": "2026.1.27--",
3
+ "version": "2026.1.29--",
4
4
  "description": "Deephaven Enterprise Query Utils",
5
5
  "author": "Deephaven Data Labs LLC",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -18,6 +18,7 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@deephaven/jsapi-types": "^1.0.0-dev0.36.1",
21
+ "@deephaven-enterprise/jsapi-types": "file:../jsapi-types",
21
22
  "@deephaven/log": "^0.97.0",
22
23
  "@deephaven/utils": "^0.97.0",
23
24
  "@internationalized/date": "^3.5.5",