@databricks/appkit 0.11.1 → 0.12.0

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.
Files changed (191) hide show
  1. package/CLAUDE.md +4 -0
  2. package/NOTICE.md +1 -0
  3. package/dist/_virtual/_rolldown/runtime.js +0 -2
  4. package/dist/appkit/package.js +1 -1
  5. package/dist/connectors/genie/client.js +208 -0
  6. package/dist/connectors/genie/client.js.map +1 -0
  7. package/dist/connectors/genie/defaults.js +10 -0
  8. package/dist/connectors/genie/defaults.js.map +1 -0
  9. package/dist/connectors/genie/index.js +5 -0
  10. package/dist/connectors/genie/poll-waiter.js +54 -0
  11. package/dist/connectors/genie/poll-waiter.js.map +1 -0
  12. package/dist/connectors/genie/types.d.ts +11 -0
  13. package/dist/connectors/genie/types.d.ts.map +1 -0
  14. package/dist/connectors/index.js +4 -0
  15. package/dist/context/execution-context.d.ts.map +1 -1
  16. package/dist/context/execution-context.js +6 -1
  17. package/dist/context/execution-context.js.map +1 -1
  18. package/dist/context/service-context.d.ts +2 -2
  19. package/dist/context/service-context.d.ts.map +1 -1
  20. package/dist/context/service-context.js +5 -4
  21. package/dist/context/service-context.js.map +1 -1
  22. package/dist/context/user-context.d.ts +2 -2
  23. package/dist/context/user-context.d.ts.map +1 -1
  24. package/dist/context/user-context.js.map +1 -1
  25. package/dist/core/appkit.d.ts.map +1 -1
  26. package/dist/core/appkit.js +3 -1
  27. package/dist/core/appkit.js.map +1 -1
  28. package/dist/index.d.ts +2 -1
  29. package/dist/index.js +2 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/logging/index.js +16 -0
  32. package/dist/plugins/genie/defaults.js +13 -0
  33. package/dist/plugins/genie/defaults.js.map +1 -0
  34. package/dist/plugins/genie/genie.d.ts +44 -0
  35. package/dist/plugins/genie/genie.d.ts.map +1 -0
  36. package/dist/plugins/genie/genie.js +141 -0
  37. package/dist/plugins/genie/genie.js.map +1 -0
  38. package/dist/plugins/genie/index.js +4 -0
  39. package/dist/plugins/genie/manifest.js +11 -0
  40. package/dist/plugins/genie/manifest.js.map +1 -0
  41. package/dist/plugins/genie/manifest.json +43 -0
  42. package/dist/plugins/genie/types.d.ts +14 -0
  43. package/dist/plugins/genie/types.d.ts.map +1 -0
  44. package/dist/plugins/index.js +3 -0
  45. package/dist/plugins/lakebase/manifest.json +1 -1
  46. package/dist/registry/types.generated.js.map +1 -1
  47. package/dist/shared/src/genie.d.ts +48 -0
  48. package/dist/shared/src/genie.d.ts.map +1 -0
  49. package/docs/docs/api/appkit/Class.AppKitError/index.html +2 -2
  50. package/docs/docs/api/appkit/Class.AuthenticationError/index.html +2 -2
  51. package/docs/docs/api/appkit/Class.ConfigurationError/index.html +2 -2
  52. package/docs/docs/api/appkit/Class.ConnectionError/index.html +2 -2
  53. package/docs/docs/api/appkit/Class.ExecutionError/index.html +2 -2
  54. package/docs/docs/api/appkit/Class.InitializationError/index.html +2 -2
  55. package/docs/docs/api/appkit/Class.Plugin/index.html +2 -2
  56. package/docs/docs/api/appkit/Class.ResourceRegistry/index.html +2 -2
  57. package/docs/docs/api/appkit/Class.ServerError/index.html +2 -2
  58. package/docs/docs/api/appkit/Class.TunnelError/index.html +2 -2
  59. package/docs/docs/api/appkit/Class.ValidationError/index.html +2 -2
  60. package/docs/docs/api/appkit/Enumeration.RequestedClaimsPermissionSet/index.html +2 -2
  61. package/docs/docs/api/appkit/Enumeration.ResourceType/index.html +2 -2
  62. package/docs/docs/api/appkit/Function.appKitTypesPlugin/index.html +2 -2
  63. package/docs/docs/api/appkit/Function.createApp/index.html +2 -2
  64. package/docs/docs/api/appkit/Function.createLakebasePool/index.html +2 -2
  65. package/docs/docs/api/appkit/Function.generateDatabaseCredential/index.html +2 -2
  66. package/docs/docs/api/appkit/Function.getExecutionContext/index.html +2 -2
  67. package/docs/docs/api/appkit/Function.getLakebaseOrmConfig/index.html +2 -2
  68. package/docs/docs/api/appkit/Function.getLakebasePgConfig/index.html +2 -2
  69. package/docs/docs/api/appkit/Function.getPluginManifest/index.html +2 -2
  70. package/docs/docs/api/appkit/Function.getResourceRequirements/index.html +2 -2
  71. package/docs/docs/api/appkit/Function.getUsernameWithApiLookup/index.html +2 -2
  72. package/docs/docs/api/appkit/Function.getWorkspaceClient/index.html +2 -2
  73. package/docs/docs/api/appkit/Function.isSQLTypeMarker/index.html +2 -2
  74. package/docs/docs/api/appkit/Interface.BasePluginConfig/index.html +2 -2
  75. package/docs/docs/api/appkit/Interface.CacheConfig/index.html +2 -2
  76. package/docs/docs/api/appkit/Interface.DatabaseCredential/index.html +2 -2
  77. package/docs/docs/api/appkit/Interface.GenerateDatabaseCredentialRequest/index.html +2 -2
  78. package/docs/docs/api/appkit/Interface.ITelemetry/index.html +2 -2
  79. package/docs/docs/api/appkit/Interface.LakebasePoolConfig/index.html +2 -2
  80. package/docs/docs/api/appkit/Interface.PluginManifest/index.html +2 -2
  81. package/docs/docs/api/appkit/Interface.RequestedClaims/index.html +2 -2
  82. package/docs/docs/api/appkit/Interface.RequestedResource/index.html +2 -2
  83. package/docs/docs/api/appkit/Interface.ResourceEntry/index.html +2 -2
  84. package/docs/docs/api/appkit/Interface.ResourceFieldEntry/index.html +2 -2
  85. package/docs/docs/api/appkit/Interface.ResourceRequirement/index.html +2 -2
  86. package/docs/docs/api/appkit/Interface.StreamExecutionSettings/index.html +2 -2
  87. package/docs/docs/api/appkit/Interface.TelemetryConfig/index.html +2 -2
  88. package/docs/docs/api/appkit/Interface.ValidationResult/index.html +2 -2
  89. package/docs/docs/api/appkit/TypeAlias.ConfigSchema/index.html +2 -2
  90. package/docs/docs/api/appkit/TypeAlias.IAppRouter/index.html +2 -2
  91. package/docs/docs/api/appkit/TypeAlias.ResourcePermission/index.html +2 -2
  92. package/docs/docs/api/appkit/TypeAlias.ToPlugin/index.html +2 -2
  93. package/docs/docs/api/appkit/Variable.sql/index.html +2 -2
  94. package/docs/docs/api/appkit/index.html +2 -2
  95. package/docs/docs/api/appkit-ui/data/AreaChart/index.html +3 -3
  96. package/docs/docs/api/appkit-ui/data/BarChart/index.html +3 -3
  97. package/docs/docs/api/appkit-ui/data/DataTable/index.html +3 -3
  98. package/docs/docs/api/appkit-ui/data/DonutChart/index.html +3 -3
  99. package/docs/docs/api/appkit-ui/data/HeatmapChart/index.html +3 -3
  100. package/docs/docs/api/appkit-ui/data/LineChart/index.html +3 -3
  101. package/docs/docs/api/appkit-ui/data/PieChart/index.html +3 -3
  102. package/docs/docs/api/appkit-ui/data/RadarChart/index.html +3 -3
  103. package/docs/docs/api/appkit-ui/data/ScatterChart/index.html +4 -4
  104. package/docs/docs/api/appkit-ui/genie/GenieChat/index.html +26 -0
  105. package/docs/docs/api/appkit-ui/genie/GenieChat.md +43 -0
  106. package/docs/docs/api/appkit-ui/genie/GenieChatInput/index.html +24 -0
  107. package/docs/docs/api/appkit-ui/genie/GenieChatInput.md +27 -0
  108. package/docs/docs/api/appkit-ui/genie/GenieChatMessage/index.html +24 -0
  109. package/docs/docs/api/appkit-ui/genie/GenieChatMessage.md +25 -0
  110. package/docs/docs/api/appkit-ui/genie/GenieChatMessageList/index.html +24 -0
  111. package/docs/docs/api/appkit-ui/genie/GenieChatMessageList.md +26 -0
  112. package/docs/docs/api/appkit-ui/index.html +3 -3
  113. package/docs/docs/api/appkit-ui/styling/index.html +4 -4
  114. package/docs/docs/api/appkit-ui/ui/Accordion/index.html +3 -3
  115. package/docs/docs/api/appkit-ui/ui/Alert/index.html +3 -3
  116. package/docs/docs/api/appkit-ui/ui/AlertDialog/index.html +3 -3
  117. package/docs/docs/api/appkit-ui/ui/AspectRatio/index.html +3 -3
  118. package/docs/docs/api/appkit-ui/ui/Avatar/index.html +3 -3
  119. package/docs/docs/api/appkit-ui/ui/Badge/index.html +3 -3
  120. package/docs/docs/api/appkit-ui/ui/Breadcrumb/index.html +3 -3
  121. package/docs/docs/api/appkit-ui/ui/Button/index.html +3 -3
  122. package/docs/docs/api/appkit-ui/ui/ButtonGroup/index.html +3 -3
  123. package/docs/docs/api/appkit-ui/ui/Calendar/index.html +3 -3
  124. package/docs/docs/api/appkit-ui/ui/Card/index.html +3 -3
  125. package/docs/docs/api/appkit-ui/ui/Carousel/index.html +3 -3
  126. package/docs/docs/api/appkit-ui/ui/ChartContainer/index.html +3 -3
  127. package/docs/docs/api/appkit-ui/ui/Checkbox/index.html +3 -3
  128. package/docs/docs/api/appkit-ui/ui/Collapsible/index.html +3 -3
  129. package/docs/docs/api/appkit-ui/ui/Command/index.html +3 -3
  130. package/docs/docs/api/appkit-ui/ui/ContextMenu/index.html +3 -3
  131. package/docs/docs/api/appkit-ui/ui/Dialog/index.html +3 -3
  132. package/docs/docs/api/appkit-ui/ui/Drawer/index.html +3 -3
  133. package/docs/docs/api/appkit-ui/ui/DropdownMenu/index.html +3 -3
  134. package/docs/docs/api/appkit-ui/ui/Empty/index.html +3 -3
  135. package/docs/docs/api/appkit-ui/ui/Field/index.html +3 -3
  136. package/docs/docs/api/appkit-ui/ui/FormControl/index.html +3 -3
  137. package/docs/docs/api/appkit-ui/ui/HoverCard/index.html +3 -3
  138. package/docs/docs/api/appkit-ui/ui/Input/index.html +3 -3
  139. package/docs/docs/api/appkit-ui/ui/InputGroup/index.html +3 -3
  140. package/docs/docs/api/appkit-ui/ui/InputOTP/index.html +3 -3
  141. package/docs/docs/api/appkit-ui/ui/Item/index.html +3 -3
  142. package/docs/docs/api/appkit-ui/ui/Kbd/index.html +3 -3
  143. package/docs/docs/api/appkit-ui/ui/Label/index.html +3 -3
  144. package/docs/docs/api/appkit-ui/ui/Menubar/index.html +3 -3
  145. package/docs/docs/api/appkit-ui/ui/NavigationMenu/index.html +3 -3
  146. package/docs/docs/api/appkit-ui/ui/Pagination/index.html +3 -3
  147. package/docs/docs/api/appkit-ui/ui/Popover/index.html +3 -3
  148. package/docs/docs/api/appkit-ui/ui/Progress/index.html +3 -3
  149. package/docs/docs/api/appkit-ui/ui/RadioGroup/index.html +3 -3
  150. package/docs/docs/api/appkit-ui/ui/ResizableHandle/index.html +3 -3
  151. package/docs/docs/api/appkit-ui/ui/ScrollArea/index.html +3 -3
  152. package/docs/docs/api/appkit-ui/ui/Select/index.html +3 -3
  153. package/docs/docs/api/appkit-ui/ui/Separator/index.html +3 -3
  154. package/docs/docs/api/appkit-ui/ui/Sheet/index.html +3 -3
  155. package/docs/docs/api/appkit-ui/ui/Sidebar/index.html +3 -3
  156. package/docs/docs/api/appkit-ui/ui/Skeleton/index.html +3 -3
  157. package/docs/docs/api/appkit-ui/ui/Slider/index.html +3 -3
  158. package/docs/docs/api/appkit-ui/ui/Spinner/index.html +3 -3
  159. package/docs/docs/api/appkit-ui/ui/Switch/index.html +3 -3
  160. package/docs/docs/api/appkit-ui/ui/Table/index.html +3 -3
  161. package/docs/docs/api/appkit-ui/ui/Tabs/index.html +3 -3
  162. package/docs/docs/api/appkit-ui/ui/Textarea/index.html +3 -3
  163. package/docs/docs/api/appkit-ui/ui/Toaster/index.html +3 -3
  164. package/docs/docs/api/appkit-ui/ui/Toggle/index.html +3 -3
  165. package/docs/docs/api/appkit-ui/ui/ToggleGroup/index.html +3 -3
  166. package/docs/docs/api/appkit-ui/ui/Tooltip/index.html +3 -3
  167. package/docs/docs/api/index.html +2 -2
  168. package/docs/docs/app-management/index.html +2 -2
  169. package/docs/docs/architecture/index.html +2 -2
  170. package/docs/docs/category/development/index.html +2 -2
  171. package/docs/docs/configuration/index.html +2 -2
  172. package/docs/docs/core-principles/index.html +2 -2
  173. package/docs/docs/development/ai-assisted-development/index.html +2 -2
  174. package/docs/docs/development/index.html +2 -2
  175. package/docs/docs/development/llm-guide/index.html +2 -2
  176. package/docs/docs/development/local-development/index.html +2 -2
  177. package/docs/docs/development/project-setup/index.html +2 -2
  178. package/docs/docs/development/remote-bridge/index.html +2 -2
  179. package/docs/docs/development/type-generation/index.html +2 -2
  180. package/docs/docs/index.html +2 -2
  181. package/docs/docs/plugins/analytics/index.html +2 -2
  182. package/docs/docs/plugins/caching/index.html +2 -2
  183. package/docs/docs/plugins/custom-plugins/index.html +2 -2
  184. package/docs/docs/plugins/execution-context/index.html +2 -2
  185. package/docs/docs/plugins/index.html +2 -2
  186. package/docs/docs/plugins/lakebase/index.html +4 -4
  187. package/docs/docs/plugins/lakebase.md +25 -24
  188. package/docs/docs/plugins/plugin-management/index.html +2 -2
  189. package/docs/docs/plugins/server/index.html +2 -2
  190. package/llms.txt +4 -0
  191. package/package.json +1 -1
package/CLAUDE.md CHANGED
@@ -81,6 +81,10 @@ The CLI will display the documentation content directly in the terminal.
81
81
  - [PieChart](./docs/docs/api/appkit-ui/data/PieChart.md): Pie Chart component for proportional data visualization.
82
82
  - [RadarChart](./docs/docs/api/appkit-ui/data/RadarChart.md): Radar Chart component for multi-dimensional data comparison.
83
83
  - [ScatterChart](./docs/docs/api/appkit-ui/data/ScatterChart.md): Scatter Chart component for correlation and distribution visualization.
84
+ - [GenieChat](./docs/docs/api/appkit-ui/genie/GenieChat.md): Full-featured chat interface for a single Databricks AI/BI Genie space. Handles message streaming, conversation history, and auto-reconnection via SSE.
85
+ - [GenieChatInput](./docs/docs/api/appkit-ui/genie/GenieChatInput.md): Auto-expanding textarea input with a send button for chat messages. Submits on Enter (Shift+Enter for newline).
86
+ - [GenieChatMessage](./docs/docs/api/appkit-ui/genie/GenieChatMessage.md): Renders a single Genie message bubble with optional expandable SQL query attachments.
87
+ - [GenieChatMessageList](./docs/docs/api/appkit-ui/genie/GenieChatMessageList.md): Scrollable message list that renders Genie chat messages with auto-scroll, skeleton loaders, and a streaming indicator.
84
88
  - [Styling](./docs/docs/api/appkit-ui/styling.md): This guide covers how to style AppKit UI components using CSS variables and theming.
85
89
  - [Accordion](./docs/docs/api/appkit-ui/ui/Accordion.md): Collapsible content sections organized in a vertical stack
86
90
  - [Alert](./docs/docs/api/appkit-ui/ui/Alert.md): Displays important information with optional icon and multiple variants
package/NOTICE.md CHANGED
@@ -66,6 +66,7 @@ This Software contains code from the following open source projects:
66
66
  | [express](https://www.npmjs.com/package/express) | 4.22.0 | MIT | http://expressjs.com/ |
67
67
  | [input-otp](https://www.npmjs.com/package/input-otp) | 1.4.2 | MIT | https://input-otp.rodz.dev/ |
68
68
  | [lucide-react](https://www.npmjs.com/package/lucide-react) | 0.554.0 | ISC | https://lucide.dev |
69
+ | [marked](https://www.npmjs.com/package/marked) | 16.4.2, 17.0.3 | MIT | https://marked.js.org |
69
70
  | [next-themes](https://www.npmjs.com/package/next-themes) | 0.4.6 | MIT | https://github.com/pacocoursey/next-themes#readme |
70
71
  | [obug](https://www.npmjs.com/package/obug) | 2.1.1 | MIT | https://github.com/sxzz/obug#readme |
71
72
  | [pg](https://www.npmjs.com/package/pg) | 8.18.0 | MIT | https://github.com/brianc/node-postgres |
@@ -1,5 +1,3 @@
1
- import "node:module";
2
-
3
1
  //#region \0rolldown/runtime.js
4
2
  var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
5
3
 
@@ -1,6 +1,6 @@
1
1
  //#region package.json
2
2
  var name = "@databricks/appkit";
3
- var version = "0.11.1";
3
+ var version = "0.12.0";
4
4
 
5
5
  //#endregion
6
6
  export { name, version };
@@ -0,0 +1,208 @@
1
+ import { createLogger } from "../../logging/logger.js";
2
+ import "../../logging/index.js";
3
+ import { genieConnectorDefaults } from "./defaults.js";
4
+ import { pollWaiter } from "./poll-waiter.js";
5
+ import { Time, TimeUnits } from "@databricks/sdk-experimental";
6
+
7
+ //#region src/connectors/genie/client.ts
8
+ const logger = createLogger("connectors:genie");
9
+ function mapAttachments(message) {
10
+ return message.attachments?.map((att) => ({
11
+ attachmentId: att.attachment_id,
12
+ query: att.query ? {
13
+ title: att.query.title,
14
+ description: att.query.description,
15
+ query: att.query.query,
16
+ statementId: att.query.statement_id
17
+ } : void 0,
18
+ text: att.text ? { content: att.text.content } : void 0,
19
+ suggestedQuestions: att.suggested_questions?.questions
20
+ })) ?? [];
21
+ }
22
+ function toMessageResponse(message) {
23
+ return {
24
+ messageId: message.message_id,
25
+ conversationId: message.conversation_id,
26
+ spaceId: message.space_id,
27
+ status: message.status ?? "COMPLETED",
28
+ content: message.content,
29
+ attachments: mapAttachments(message),
30
+ error: message.error?.error
31
+ };
32
+ }
33
+ var GenieConnector = class {
34
+ config;
35
+ constructor(config = {}) {
36
+ this.config = {
37
+ timeout: config.timeout ?? genieConnectorDefaults.timeout,
38
+ maxMessages: config.maxMessages ?? genieConnectorDefaults.maxMessages
39
+ };
40
+ }
41
+ async startMessage(workspaceClient, spaceId, content, conversationId) {
42
+ if (conversationId) {
43
+ const waiter = await workspaceClient.genie.createMessage({
44
+ space_id: spaceId,
45
+ conversation_id: conversationId,
46
+ content
47
+ });
48
+ return {
49
+ messageWaiter: waiter,
50
+ conversationId,
51
+ messageId: waiter.message_id ?? ""
52
+ };
53
+ }
54
+ const start = await workspaceClient.genie.startConversation({
55
+ space_id: spaceId,
56
+ content
57
+ });
58
+ return {
59
+ messageWaiter: start,
60
+ conversationId: start.conversation_id,
61
+ messageId: start.message_id
62
+ };
63
+ }
64
+ async waitForMessage(messageWaiter, options) {
65
+ const timeout = options?.timeout ?? this.config.timeout;
66
+ const waitOptions = timeout > 0 ? { timeout: new Time(timeout, TimeUnits.milliseconds) } : {};
67
+ return messageWaiter.wait(waitOptions);
68
+ }
69
+ async listConversationMessages(workspaceClient, spaceId, conversationId, options) {
70
+ const maxMessages = options?.maxMessages ?? this.config.maxMessages;
71
+ const allMessages = [];
72
+ let pageToken;
73
+ do {
74
+ const response = await workspaceClient.genie.listConversationMessages({
75
+ space_id: spaceId,
76
+ conversation_id: conversationId,
77
+ page_size: genieConnectorDefaults.pageSize,
78
+ ...pageToken ? { page_token: pageToken } : {}
79
+ });
80
+ if (response.messages) allMessages.push(...response.messages);
81
+ pageToken = response.next_page_token;
82
+ } while (pageToken && allMessages.length < maxMessages);
83
+ return allMessages.slice(0, maxMessages).reverse().map(toMessageResponse);
84
+ }
85
+ async getMessageAttachmentQueryResult(workspaceClient, spaceId, conversationId, messageId, attachmentId, _signal) {
86
+ return (await workspaceClient.genie.getMessageAttachmentQueryResult({
87
+ space_id: spaceId,
88
+ conversation_id: conversationId,
89
+ message_id: messageId,
90
+ attachment_id: attachmentId
91
+ })).statement_response;
92
+ }
93
+ async *streamSendMessage(workspaceClient, spaceId, content, conversationId, options) {
94
+ try {
95
+ const { messageWaiter, conversationId: resultConversationId, messageId: resultMessageId } = await this.startMessage(workspaceClient, spaceId, content, conversationId);
96
+ yield {
97
+ type: "message_start",
98
+ conversationId: resultConversationId,
99
+ messageId: resultMessageId,
100
+ spaceId
101
+ };
102
+ const timeout = options?.timeout != null ? options.timeout : this.config.timeout;
103
+ const waitOptions = timeout > 0 ? { timeout: new Time(timeout, TimeUnits.milliseconds) } : {};
104
+ let completedMessage;
105
+ for await (const event of pollWaiter(messageWaiter, waitOptions)) if (event.type === "progress" && event.value.status) yield {
106
+ type: "status",
107
+ status: event.value.status
108
+ };
109
+ else if (event.type === "completed") completedMessage = event.value;
110
+ const messageResponse = toMessageResponse(completedMessage);
111
+ yield {
112
+ type: "message_result",
113
+ message: messageResponse
114
+ };
115
+ yield* this.emitQueryResults(workspaceClient, spaceId, resultConversationId, messageResponse.messageId, messageResponse);
116
+ } catch (error) {
117
+ logger.error("Genie message error: %O", error);
118
+ yield {
119
+ type: "error",
120
+ error: error instanceof Error ? error.message : "Genie request failed"
121
+ };
122
+ }
123
+ }
124
+ async *emitQueryResults(workspaceClient, spaceId, conversationId, messageId, messageResponse) {
125
+ const attachments = messageResponse.attachments ?? [];
126
+ for (const att of attachments) {
127
+ if (!att.query?.statementId || !att.attachmentId) continue;
128
+ try {
129
+ const data = await this.getMessageAttachmentQueryResult(workspaceClient, spaceId, conversationId, messageId, att.attachmentId);
130
+ yield {
131
+ type: "query_result",
132
+ attachmentId: att.attachmentId,
133
+ statementId: att.query.statementId,
134
+ data
135
+ };
136
+ } catch (error) {
137
+ logger.error("Failed to fetch query result for attachment %s: %O", att.attachmentId, error);
138
+ yield {
139
+ type: "error",
140
+ error: `Failed to fetch query result for attachment ${att.attachmentId}`
141
+ };
142
+ }
143
+ }
144
+ }
145
+ async *streamConversation(workspaceClient, spaceId, conversationId, options) {
146
+ const includeQueryResults = options?.includeQueryResults !== false;
147
+ try {
148
+ const messageResponses = await this.listConversationMessages(workspaceClient, spaceId, conversationId);
149
+ for (const messageResponse of messageResponses) yield {
150
+ type: "message_result",
151
+ message: messageResponse
152
+ };
153
+ if (includeQueryResults) {
154
+ const queryAttachments = [];
155
+ for (const msg of messageResponses) for (const att of msg.attachments ?? []) if (att.query?.statementId && att.attachmentId) queryAttachments.push({
156
+ messageId: msg.messageId,
157
+ attachmentId: att.attachmentId,
158
+ statementId: att.query.statementId
159
+ });
160
+ const results = await Promise.allSettled(queryAttachments.map(async (att) => {
161
+ const data = await this.getMessageAttachmentQueryResult(workspaceClient, spaceId, conversationId, att.messageId, att.attachmentId);
162
+ return {
163
+ attachmentId: att.attachmentId,
164
+ statementId: att.statementId,
165
+ data
166
+ };
167
+ }));
168
+ for (const result of results) if (result.status === "fulfilled") yield {
169
+ type: "query_result",
170
+ attachmentId: result.value.attachmentId,
171
+ statementId: result.value.statementId,
172
+ data: result.value.data
173
+ };
174
+ else {
175
+ logger.error("Failed to fetch query result: %O", result.reason);
176
+ yield {
177
+ type: "error",
178
+ error: result.reason instanceof Error ? result.reason.message : "Failed to fetch query result"
179
+ };
180
+ }
181
+ }
182
+ } catch (error) {
183
+ logger.error("Genie getConversation error: %O", error);
184
+ yield {
185
+ type: "error",
186
+ error: error instanceof Error ? error.message : "Failed to fetch conversation"
187
+ };
188
+ }
189
+ }
190
+ async sendMessage(workspaceClient, spaceId, content, conversationId) {
191
+ const { messageWaiter, conversationId: resultConversationId } = await this.startMessage(workspaceClient, spaceId, content, conversationId);
192
+ return {
193
+ ...toMessageResponse(await this.waitForMessage(messageWaiter)),
194
+ conversationId: resultConversationId
195
+ };
196
+ }
197
+ async getConversation(workspaceClient, spaceId, conversationId) {
198
+ return {
199
+ conversationId,
200
+ spaceId,
201
+ messages: await this.listConversationMessages(workspaceClient, spaceId, conversationId)
202
+ };
203
+ }
204
+ };
205
+
206
+ //#endregion
207
+ export { GenieConnector };
208
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../../src/connectors/genie/client.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport { Time, TimeUnits } from \"@databricks/sdk-experimental\";\nimport type { GenieMessage } from \"@databricks/sdk-experimental/dist/apis/dashboards\";\nimport type { Waiter } from \"@databricks/sdk-experimental/dist/wait\";\nimport { createLogger } from \"../../logging\";\nimport { genieConnectorDefaults } from \"./defaults\";\nimport { pollWaiter } from \"./poll-waiter\";\nimport type {\n GenieAttachmentResponse,\n GenieConversationHistoryResponse,\n GenieMessageResponse,\n GenieStreamEvent,\n} from \"./types\";\n\nconst logger = createLogger(\"connectors:genie\");\n\ntype CreateMessageWaiter = Waiter<GenieMessage, GenieMessage>;\n\nexport interface GenieConnectorConfig {\n timeout?: number;\n maxMessages?: number;\n}\n\nfunction mapAttachments(message: GenieMessage): GenieAttachmentResponse[] {\n return (\n message.attachments?.map((att) => ({\n attachmentId: att.attachment_id,\n query: att.query\n ? {\n title: att.query.title,\n description: att.query.description,\n query: att.query.query,\n statementId: att.query.statement_id,\n }\n : undefined,\n text: att.text ? { content: att.text.content } : undefined,\n suggestedQuestions: att.suggested_questions?.questions,\n })) ?? []\n );\n}\n\nfunction toMessageResponse(message: GenieMessage): GenieMessageResponse {\n return {\n messageId: message.message_id,\n conversationId: message.conversation_id,\n spaceId: message.space_id,\n status: message.status ?? \"COMPLETED\",\n content: message.content,\n attachments: mapAttachments(message),\n error: message.error?.error,\n };\n}\n\nexport class GenieConnector {\n private readonly config: Required<GenieConnectorConfig>;\n\n constructor(config: GenieConnectorConfig = {}) {\n this.config = {\n timeout: config.timeout ?? genieConnectorDefaults.timeout,\n maxMessages: config.maxMessages ?? genieConnectorDefaults.maxMessages,\n };\n }\n\n async startMessage(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n content: string,\n conversationId: string | undefined,\n ): Promise<{\n messageWaiter: CreateMessageWaiter;\n conversationId: string;\n messageId: string;\n }> {\n if (conversationId) {\n const waiter = await workspaceClient.genie.createMessage({\n space_id: spaceId,\n conversation_id: conversationId,\n content,\n });\n return {\n messageWaiter: waiter,\n conversationId,\n messageId: waiter.message_id ?? \"\",\n };\n }\n const start = await workspaceClient.genie.startConversation({\n space_id: spaceId,\n content,\n });\n return {\n messageWaiter: start as unknown as CreateMessageWaiter,\n conversationId: start.conversation_id,\n messageId: start.message_id,\n };\n }\n\n async waitForMessage(\n messageWaiter: CreateMessageWaiter,\n options?: { timeout?: number },\n ): Promise<GenieMessage> {\n const timeout = options?.timeout ?? this.config.timeout;\n const waitOptions =\n timeout > 0 ? { timeout: new Time(timeout, TimeUnits.milliseconds) } : {};\n return messageWaiter.wait(waitOptions);\n }\n\n async listConversationMessages(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n conversationId: string,\n options?: { maxMessages?: number },\n ): Promise<GenieMessageResponse[]> {\n const maxMessages = options?.maxMessages ?? this.config.maxMessages;\n const allMessages: GenieMessage[] = [];\n let pageToken: string | undefined;\n\n do {\n const response = await workspaceClient.genie.listConversationMessages({\n space_id: spaceId,\n conversation_id: conversationId,\n page_size: genieConnectorDefaults.pageSize,\n ...(pageToken ? { page_token: pageToken } : {}),\n });\n\n if (response.messages) {\n allMessages.push(...response.messages);\n }\n\n pageToken = response.next_page_token;\n } while (pageToken && allMessages.length < maxMessages);\n\n return allMessages.slice(0, maxMessages).reverse().map(toMessageResponse);\n }\n\n async getMessageAttachmentQueryResult(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n conversationId: string,\n messageId: string,\n attachmentId: string,\n _signal?: AbortSignal,\n ): Promise<unknown> {\n const response =\n await workspaceClient.genie.getMessageAttachmentQueryResult({\n space_id: spaceId,\n conversation_id: conversationId,\n message_id: messageId,\n attachment_id: attachmentId,\n });\n return response.statement_response;\n }\n\n async *streamSendMessage(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n content: string,\n conversationId: string | undefined,\n options?: { timeout?: number },\n ): AsyncGenerator<GenieStreamEvent> {\n try {\n const {\n messageWaiter,\n conversationId: resultConversationId,\n messageId: resultMessageId,\n } = await this.startMessage(\n workspaceClient,\n spaceId,\n content,\n conversationId,\n );\n\n yield {\n type: \"message_start\",\n conversationId: resultConversationId,\n messageId: resultMessageId,\n spaceId,\n };\n\n const timeout =\n options?.timeout != null ? options.timeout : this.config.timeout;\n const waitOptions =\n timeout > 0\n ? { timeout: new Time(timeout, TimeUnits.milliseconds) }\n : {};\n\n let completedMessage!: GenieMessage;\n for await (const event of pollWaiter(messageWaiter, waitOptions)) {\n if (event.type === \"progress\" && event.value.status) {\n yield { type: \"status\", status: event.value.status };\n } else if (event.type === \"completed\") {\n completedMessage = event.value;\n }\n }\n\n const messageResponse = toMessageResponse(completedMessage);\n yield { type: \"message_result\", message: messageResponse };\n\n yield* this.emitQueryResults(\n workspaceClient,\n spaceId,\n resultConversationId,\n messageResponse.messageId,\n messageResponse,\n );\n } catch (error) {\n logger.error(\"Genie message error: %O\", error);\n yield {\n type: \"error\",\n error: error instanceof Error ? error.message : \"Genie request failed\",\n };\n }\n }\n\n private async *emitQueryResults(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n conversationId: string,\n messageId: string,\n messageResponse: GenieMessageResponse,\n ): AsyncGenerator<\n Extract<GenieStreamEvent, { type: \"query_result\" } | { type: \"error\" }>\n > {\n const attachments = messageResponse.attachments ?? [];\n for (const att of attachments) {\n if (!att.query?.statementId || !att.attachmentId) continue;\n try {\n const data = await this.getMessageAttachmentQueryResult(\n workspaceClient,\n spaceId,\n conversationId,\n messageId,\n att.attachmentId,\n );\n yield {\n type: \"query_result\",\n attachmentId: att.attachmentId,\n statementId: att.query.statementId,\n data,\n };\n } catch (error) {\n logger.error(\n \"Failed to fetch query result for attachment %s: %O\",\n att.attachmentId,\n error,\n );\n yield {\n type: \"error\",\n error: `Failed to fetch query result for attachment ${att.attachmentId}`,\n };\n }\n }\n }\n\n async *streamConversation(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n conversationId: string,\n options?: { includeQueryResults?: boolean },\n ): AsyncGenerator<GenieStreamEvent> {\n const includeQueryResults = options?.includeQueryResults !== false;\n\n try {\n const messageResponses = await this.listConversationMessages(\n workspaceClient,\n spaceId,\n conversationId,\n );\n\n for (const messageResponse of messageResponses) {\n yield { type: \"message_result\", message: messageResponse };\n }\n\n if (includeQueryResults) {\n const queryAttachments: Array<{\n messageId: string;\n attachmentId: string;\n statementId: string;\n }> = [];\n\n for (const msg of messageResponses) {\n for (const att of msg.attachments ?? []) {\n if (att.query?.statementId && att.attachmentId) {\n queryAttachments.push({\n messageId: msg.messageId,\n attachmentId: att.attachmentId,\n statementId: att.query.statementId,\n });\n }\n }\n }\n\n const results = await Promise.allSettled(\n queryAttachments.map(async (att) => {\n const data = await this.getMessageAttachmentQueryResult(\n workspaceClient,\n spaceId,\n conversationId,\n att.messageId,\n att.attachmentId,\n );\n return {\n attachmentId: att.attachmentId,\n statementId: att.statementId,\n data,\n };\n }),\n );\n\n for (const result of results) {\n if (result.status === \"fulfilled\") {\n yield {\n type: \"query_result\",\n attachmentId: result.value.attachmentId,\n statementId: result.value.statementId,\n data: result.value.data,\n };\n } else {\n logger.error(\"Failed to fetch query result: %O\", result.reason);\n yield {\n type: \"error\",\n error:\n result.reason instanceof Error\n ? result.reason.message\n : \"Failed to fetch query result\",\n };\n }\n }\n }\n } catch (error) {\n logger.error(\"Genie getConversation error: %O\", error);\n yield {\n type: \"error\",\n error:\n error instanceof Error\n ? error.message\n : \"Failed to fetch conversation\",\n };\n }\n }\n\n async sendMessage(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n content: string,\n conversationId: string | undefined,\n ): Promise<GenieMessageResponse> {\n const { messageWaiter, conversationId: resultConversationId } =\n await this.startMessage(\n workspaceClient,\n spaceId,\n content,\n conversationId,\n );\n const completedMessage = await this.waitForMessage(messageWaiter);\n const messageResponse = toMessageResponse(completedMessage);\n return {\n ...messageResponse,\n conversationId: resultConversationId,\n };\n }\n\n async getConversation(\n workspaceClient: WorkspaceClient,\n spaceId: string,\n conversationId: string,\n ): Promise<GenieConversationHistoryResponse> {\n const messages = await this.listConversationMessages(\n workspaceClient,\n spaceId,\n conversationId,\n );\n return {\n conversationId,\n spaceId,\n messages,\n };\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ //#region src/connectors/genie/defaults.ts
2
+ const genieConnectorDefaults = {
3
+ timeout: 12e4,
4
+ maxMessages: 200,
5
+ pageSize: 100
6
+ };
7
+
8
+ //#endregion
9
+ export { genieConnectorDefaults };
10
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.js","names":[],"sources":["../../../src/connectors/genie/defaults.ts"],"sourcesContent":["export const genieConnectorDefaults = {\n /** Genie waiter timeout in ms. 0 = indefinite. */\n timeout: 120_000,\n /** Max messages to fetch when listing a conversation. */\n maxMessages: 200,\n /** Default page size for listConversationMessages. */\n pageSize: 100,\n} as const;\n"],"mappings":";AAAA,MAAa,yBAAyB;CAEpC,SAAS;CAET,aAAa;CAEb,UAAU;CACX"}
@@ -0,0 +1,5 @@
1
+ import { genieConnectorDefaults } from "./defaults.js";
2
+ import { pollWaiter } from "./poll-waiter.js";
3
+ import { GenieConnector } from "./client.js";
4
+
5
+ export { };
@@ -0,0 +1,54 @@
1
+ //#region src/connectors/genie/poll-waiter.ts
2
+ /**
3
+ * Bridges a callback-based waiter into an async generator.
4
+ *
5
+ * The SDK's `waiter.wait({ onProgress })` API uses a callback to report
6
+ * progress and returns a promise that resolves with the final result.
7
+ * This function converts that push-based model into a pull-based async
8
+ * generator so callers can simply `for await (const event of pollWaiter(w))`.
9
+ *
10
+ * Yields `{ type: "progress", value }` for each `onProgress` callback,
11
+ * then `{ type: "completed", value }` for the final result.
12
+ * Throws if the waiter rejects.
13
+ */
14
+ async function* pollWaiter(waiter, options) {
15
+ const queue = [];
16
+ let notify = () => {};
17
+ let done = false;
18
+ let result;
19
+ let error = null;
20
+ waiter.wait({
21
+ onProgress: async (p) => {
22
+ queue.push(p);
23
+ notify();
24
+ },
25
+ ...options?.timeout != null ? { timeout: options.timeout } : {}
26
+ }).then((r) => {
27
+ result = r;
28
+ done = true;
29
+ notify();
30
+ }).catch((err) => {
31
+ error = err;
32
+ done = true;
33
+ notify();
34
+ });
35
+ while (!done || queue.length > 0) {
36
+ while (queue.length > 0) yield {
37
+ type: "progress",
38
+ value: queue.shift()
39
+ };
40
+ if (!done) await new Promise((resolve) => {
41
+ notify = resolve;
42
+ if (done || queue.length > 0) resolve();
43
+ });
44
+ }
45
+ if (error !== null) throw error;
46
+ yield {
47
+ type: "completed",
48
+ value: result
49
+ };
50
+ }
51
+
52
+ //#endregion
53
+ export { pollWaiter };
54
+ //# sourceMappingURL=poll-waiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poll-waiter.js","names":[],"sources":["../../../src/connectors/genie/poll-waiter.ts"],"sourcesContent":["/**\n * Structural interface matching the SDK's `Waiter.wait()` shape\n * without importing the SDK directly.\n */\nexport interface Pollable<P> {\n wait(options?: {\n onProgress?: (p: P) => Promise<void>;\n timeout?: unknown;\n }): Promise<P>;\n}\n\nexport type PollEvent<P> =\n | { type: \"progress\"; value: P }\n | { type: \"completed\"; value: P };\n\n/**\n * Bridges a callback-based waiter into an async generator.\n *\n * The SDK's `waiter.wait({ onProgress })` API uses a callback to report\n * progress and returns a promise that resolves with the final result.\n * This function converts that push-based model into a pull-based async\n * generator so callers can simply `for await (const event of pollWaiter(w))`.\n *\n * Yields `{ type: \"progress\", value }` for each `onProgress` callback,\n * then `{ type: \"completed\", value }` for the final result.\n * Throws if the waiter rejects.\n */\nexport async function* pollWaiter<P>(\n waiter: Pollable<P>,\n options?: { timeout?: unknown },\n): AsyncGenerator<PollEvent<P>> {\n const queue: P[] = [];\n let notify: () => void = () => {};\n let done = false;\n let result!: P;\n let error: unknown = null;\n\n waiter\n .wait({\n onProgress: async (p: P) => {\n queue.push(p);\n notify();\n },\n ...(options?.timeout != null ? { timeout: options.timeout } : {}),\n })\n .then((r) => {\n result = r;\n done = true;\n notify();\n })\n .catch((err) => {\n error = err;\n done = true;\n notify();\n });\n\n while (!done || queue.length > 0) {\n while (queue.length > 0) {\n const value = queue.shift() as P;\n yield { type: \"progress\", value };\n }\n\n if (!done) {\n await new Promise<void>((resolve) => {\n notify = resolve;\n if (done || queue.length > 0) resolve();\n });\n }\n }\n\n if (error !== null) {\n throw error;\n }\n\n yield { type: \"completed\", value: result };\n}\n"],"mappings":";;;;;;;;;;;;;AA2BA,gBAAuB,WACrB,QACA,SAC8B;CAC9B,MAAM,QAAa,EAAE;CACrB,IAAI,eAA2B;CAC/B,IAAI,OAAO;CACX,IAAI;CACJ,IAAI,QAAiB;AAErB,QACG,KAAK;EACJ,YAAY,OAAO,MAAS;AAC1B,SAAM,KAAK,EAAE;AACb,WAAQ;;EAEV,GAAI,SAAS,WAAW,OAAO,EAAE,SAAS,QAAQ,SAAS,GAAG,EAAE;EACjE,CAAC,CACD,MAAM,MAAM;AACX,WAAS;AACT,SAAO;AACP,UAAQ;GACR,CACD,OAAO,QAAQ;AACd,UAAQ;AACR,SAAO;AACP,UAAQ;GACR;AAEJ,QAAO,CAAC,QAAQ,MAAM,SAAS,GAAG;AAChC,SAAO,MAAM,SAAS,EAEpB,OAAM;GAAE,MAAM;GAAY,OADZ,MAAM,OAAO;GACM;AAGnC,MAAI,CAAC,KACH,OAAM,IAAI,SAAe,YAAY;AACnC,YAAS;AACT,OAAI,QAAQ,MAAM,SAAS,EAAG,UAAS;IACvC;;AAIN,KAAI,UAAU,KACZ,OAAM;AAGR,OAAM;EAAE,MAAM;EAAa,OAAO;EAAQ"}
@@ -0,0 +1,11 @@
1
+ import { GenieAttachmentResponse, GenieMessageResponse, GenieStreamEvent } from "../../shared/src/genie.js";
2
+
3
+ //#region src/connectors/genie/types.d.ts
4
+ interface GenieConversationHistoryResponse {
5
+ conversationId: string;
6
+ spaceId: string;
7
+ messages: GenieMessageResponse[];
8
+ }
9
+ //#endregion
10
+ export { GenieConversationHistoryResponse };
11
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/connectors/genie/types.ts"],"sourcesContent":[],"mappings":";;;AAUiB,UAAA,gCAAA,CAAgC;;;YAGrC"}
@@ -1,4 +1,8 @@
1
1
  import { RequestedClaimsPermissionSet, createLakebasePool, generateDatabaseCredential, getLakebaseOrmConfig, getLakebasePgConfig, getUsernameWithApiLookup, getWorkspaceClient } from "./lakebase/index.js";
2
+ import { genieConnectorDefaults } from "./genie/defaults.js";
3
+ import { pollWaiter } from "./genie/poll-waiter.js";
4
+ import { GenieConnector } from "./genie/client.js";
5
+ import "./genie/index.js";
2
6
  import "./lakebase-v1/index.js";
3
7
  import { SQLWarehouseConnector } from "./sql-warehouse/client.js";
4
8
  import "./sql-warehouse/index.js";
@@ -1 +1 @@
1
- {"version":3,"file":"execution-context.d.ts","names":[],"sources":["../../src/context/execution-context.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;iBAkCgB,mBAAA,CAAA,GAAuB"}
1
+ {"version":3,"file":"execution-context.d.ts","names":[],"sources":["../../src/context/execution-context.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;iBAmCgB,mBAAA,CAAA,GAAuB"}
@@ -1,4 +1,6 @@
1
1
  import { __esmMin } from "../_virtual/_rolldown/runtime.js";
2
+ import { ConfigurationError } from "../errors/configuration.js";
3
+ import { init_errors } from "../errors/index.js";
2
4
  import { ServiceContext, init_service_context } from "./service-context.js";
3
5
  import { init_user_context, isUserContext } from "./user-context.js";
4
6
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -48,7 +50,9 @@ function getWorkspaceClient() {
48
50
  * Get the warehouse ID promise.
49
51
  */
50
52
  function getWarehouseId() {
51
- return getExecutionContext().warehouseId;
53
+ const ctx = getExecutionContext();
54
+ if (!ctx.warehouseId) throw ConfigurationError.resourceNotFound("Warehouse ID", "No plugin requires a SQL Warehouse. Add a sql_warehouse resource to your plugin manifest, or set DATABRICKS_WAREHOUSE_ID");
55
+ return ctx.warehouseId;
52
56
  }
53
57
  /**
54
58
  * Get the workspace ID promise.
@@ -58,6 +62,7 @@ function getWorkspaceId() {
58
62
  }
59
63
  var executionContextStorage;
60
64
  var init_execution_context = __esmMin((() => {
65
+ init_errors();
61
66
  init_service_context();
62
67
  init_user_context();
63
68
  executionContextStorage = new AsyncLocalStorage();
@@ -1 +1 @@
1
- {"version":3,"file":"execution-context.js","names":[],"sources":["../../src/context/execution-context.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { ServiceContext } from \"./service-context\";\nimport {\n type ExecutionContext,\n isUserContext,\n type UserContext,\n} from \"./user-context\";\n\n/**\n * AsyncLocalStorage for execution context.\n * Used to pass user context through the call stack without explicit parameters.\n */\nconst executionContextStorage = new AsyncLocalStorage<UserContext>();\n\n/**\n * Run a function in the context of a user.\n * All calls within the function will have access to the user context.\n *\n * @param userContext - The user context to use\n * @param fn - The function to run\n * @returns The result of the function\n */\nexport function runInUserContext<T>(userContext: UserContext, fn: () => T): T {\n return executionContextStorage.run(userContext, fn);\n}\n\n/**\n * Get the current execution context.\n *\n * - If running inside a user context (via asUser), returns the user context\n * - Otherwise, returns the service context\n *\n * @throws Error if ServiceContext is not initialized\n */\nexport function getExecutionContext(): ExecutionContext {\n const userContext = executionContextStorage.getStore();\n if (userContext) {\n return userContext;\n }\n return ServiceContext.get();\n}\n\n/**\n * Get the current user ID for cache keying and telemetry.\n *\n * Returns the user ID if in user context, otherwise the service user ID.\n */\nexport function getCurrentUserId(): string {\n const ctx = getExecutionContext();\n if (isUserContext(ctx)) {\n return ctx.userId;\n }\n return ctx.serviceUserId;\n}\n\n/**\n * Get the WorkspaceClient for the current execution context.\n */\nexport function getWorkspaceClient() {\n return getExecutionContext().client;\n}\n\n/**\n * Get the warehouse ID promise.\n */\nexport function getWarehouseId(): Promise<string> {\n return getExecutionContext().warehouseId;\n}\n\n/**\n * Get the workspace ID promise.\n */\nexport function getWorkspaceId(): Promise<string> {\n return getExecutionContext().workspaceId;\n}\n\n/**\n * Check if currently running in a user context.\n */\nexport function isInUserContext(): boolean {\n const ctx = executionContextStorage.getStore();\n return ctx !== undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;AAsBA,SAAgB,iBAAoB,aAA0B,IAAgB;AAC5E,QAAO,wBAAwB,IAAI,aAAa,GAAG;;;;;;;;;;AAWrD,SAAgB,sBAAwC;CACtD,MAAM,cAAc,wBAAwB,UAAU;AACtD,KAAI,YACF,QAAO;AAET,QAAO,eAAe,KAAK;;;;;;;AAQ7B,SAAgB,mBAA2B;CACzC,MAAM,MAAM,qBAAqB;AACjC,KAAI,cAAc,IAAI,CACpB,QAAO,IAAI;AAEb,QAAO,IAAI;;;;;AAMb,SAAgB,qBAAqB;AACnC,QAAO,qBAAqB,CAAC;;;;;AAM/B,SAAgB,iBAAkC;AAChD,QAAO,qBAAqB,CAAC;;;;;AAM/B,SAAgB,iBAAkC;AAChD,QAAO,qBAAqB,CAAC;;;;uBAxEoB;oBAK3B;CAMlB,0BAA0B,IAAI,mBAAgC"}
1
+ {"version":3,"file":"execution-context.js","names":[],"sources":["../../src/context/execution-context.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { ConfigurationError } from \"../errors\";\nimport { ServiceContext } from \"./service-context\";\nimport {\n type ExecutionContext,\n isUserContext,\n type UserContext,\n} from \"./user-context\";\n\n/**\n * AsyncLocalStorage for execution context.\n * Used to pass user context through the call stack without explicit parameters.\n */\nconst executionContextStorage = new AsyncLocalStorage<UserContext>();\n\n/**\n * Run a function in the context of a user.\n * All calls within the function will have access to the user context.\n *\n * @param userContext - The user context to use\n * @param fn - The function to run\n * @returns The result of the function\n */\nexport function runInUserContext<T>(userContext: UserContext, fn: () => T): T {\n return executionContextStorage.run(userContext, fn);\n}\n\n/**\n * Get the current execution context.\n *\n * - If running inside a user context (via asUser), returns the user context\n * - Otherwise, returns the service context\n *\n * @throws Error if ServiceContext is not initialized\n */\nexport function getExecutionContext(): ExecutionContext {\n const userContext = executionContextStorage.getStore();\n if (userContext) {\n return userContext;\n }\n return ServiceContext.get();\n}\n\n/**\n * Get the current user ID for cache keying and telemetry.\n *\n * Returns the user ID if in user context, otherwise the service user ID.\n */\nexport function getCurrentUserId(): string {\n const ctx = getExecutionContext();\n if (isUserContext(ctx)) {\n return ctx.userId;\n }\n return ctx.serviceUserId;\n}\n\n/**\n * Get the WorkspaceClient for the current execution context.\n */\nexport function getWorkspaceClient() {\n return getExecutionContext().client;\n}\n\n/**\n * Get the warehouse ID promise.\n */\nexport function getWarehouseId(): Promise<string> {\n const ctx = getExecutionContext();\n if (!ctx.warehouseId) {\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"No plugin requires a SQL Warehouse. Add a sql_warehouse resource to your plugin manifest, or set DATABRICKS_WAREHOUSE_ID\",\n );\n }\n return ctx.warehouseId;\n}\n\n/**\n * Get the workspace ID promise.\n */\nexport function getWorkspaceId(): Promise<string> {\n return getExecutionContext().workspaceId;\n}\n\n/**\n * Check if currently running in a user context.\n */\nexport function isInUserContext(): boolean {\n const ctx = executionContextStorage.getStore();\n return ctx !== undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAuBA,SAAgB,iBAAoB,aAA0B,IAAgB;AAC5E,QAAO,wBAAwB,IAAI,aAAa,GAAG;;;;;;;;;;AAWrD,SAAgB,sBAAwC;CACtD,MAAM,cAAc,wBAAwB,UAAU;AACtD,KAAI,YACF,QAAO;AAET,QAAO,eAAe,KAAK;;;;;;;AAQ7B,SAAgB,mBAA2B;CACzC,MAAM,MAAM,qBAAqB;AACjC,KAAI,cAAc,IAAI,CACpB,QAAO,IAAI;AAEb,QAAO,IAAI;;;;;AAMb,SAAgB,qBAAqB;AACnC,QAAO,qBAAqB,CAAC;;;;;AAM/B,SAAgB,iBAAkC;CAChD,MAAM,MAAM,qBAAqB;AACjC,KAAI,CAAC,IAAI,YACP,OAAM,mBAAmB,iBACvB,gBACA,2HACD;AAEH,QAAO,IAAI;;;;;AAMb,SAAgB,iBAAkC;AAChD,QAAO,qBAAqB,CAAC;;;;cAhFgB;uBACI;oBAK3B;CAMlB,0BAA0B,IAAI,mBAAgC"}
@@ -11,8 +11,8 @@ interface ServiceContextState {
11
11
  client: WorkspaceClient;
12
12
  /** The service principal's user ID */
13
13
  serviceUserId: string;
14
- /** Promise that resolves to the warehouse ID */
15
- warehouseId: Promise<string>;
14
+ /** Promise that resolves to the warehouse ID (only present when a plugin requires `SQL_WAREHOUSE` resource) */
15
+ warehouseId?: Promise<string>;
16
16
  /** Promise that resolves to the workspace ID */
17
17
  workspaceId: Promise<string>;
18
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"service-context.d.ts","names":[],"sources":["../../src/context/service-context.ts"],"sourcesContent":[],"mappings":";;;;;AAqBA;;;AAMe,UANE,mBAAA,CAMF;;EAEO,MAAA,EANZ,eAMY;;;;eAFP;;eAEA"}
1
+ {"version":3,"file":"service-context.d.ts","names":[],"sources":["../../src/context/service-context.ts"],"sourcesContent":[],"mappings":";;;;;AAqBA;;;AAMgB,UANC,mBAAA,CAMD;;EAEM,MAAA,EANZ,eAMY;;;;gBAFN;;eAED"}
@@ -26,13 +26,14 @@ var init_service_context = __esmMin((() => {
26
26
  * Initialize the service context. Should be called once at app startup.
27
27
  * Safe to call multiple times - will return the same instance.
28
28
  *
29
+ * @param options - Which shared resources to resolve (derived from plugin manifests).
29
30
  * @param client - Optional pre-configured WorkspaceClient to use instead
30
31
  * of creating one from environment credentials.
31
32
  */
32
- static async initialize(client) {
33
+ static async initialize(options, client) {
33
34
  if (ServiceContext.instance) return ServiceContext.instance;
34
35
  if (ServiceContext.initPromise) return ServiceContext.initPromise;
35
- ServiceContext.initPromise = ServiceContext.createContext(client);
36
+ ServiceContext.initPromise = ServiceContext.createContext(options, client);
36
37
  ServiceContext.instance = await ServiceContext.initPromise;
37
38
  return ServiceContext.instance;
38
39
  }
@@ -83,9 +84,9 @@ var init_service_context = __esmMin((() => {
83
84
  static getClientOptions() {
84
85
  return getClientOptions();
85
86
  }
86
- static async createContext(client) {
87
+ static async createContext(options, client) {
87
88
  const wsClient = client ?? new WorkspaceClient({}, getClientOptions());
88
- const warehouseId = ServiceContext.getWarehouseId(wsClient);
89
+ const warehouseId = options?.warehouseId ? ServiceContext.getWarehouseId(wsClient) : void 0;
89
90
  const workspaceId = ServiceContext.getWorkspaceId(wsClient);
90
91
  const currentUser = await wsClient.currentUser.me();
91
92
  if (!currentUser.id) throw ConfigurationError.resourceNotFound("Service user ID");
@@ -1 +1 @@
1
- {"version":3,"file":"service-context.js","names":["productName","productVersion"],"sources":["../../src/context/service-context.ts"],"sourcesContent":["import {\n type ClientOptions,\n type sql,\n WorkspaceClient,\n} from \"@databricks/sdk-experimental\";\nimport { coerce } from \"semver\";\nimport {\n name as productName,\n version as productVersion,\n} from \"../../package.json\";\nimport {\n AuthenticationError,\n ConfigurationError,\n InitializationError,\n} from \"../errors\";\nimport type { UserContext } from \"./user-context\";\n\n/**\n * Service context holds the service principal client and shared resources.\n * This is initialized once at app startup and shared across all requests.\n */\nexport interface ServiceContextState {\n /** WorkspaceClient authenticated as the service principal */\n client: WorkspaceClient;\n /** The service principal's user ID */\n serviceUserId: string;\n /** Promise that resolves to the warehouse ID */\n warehouseId: Promise<string>;\n /** Promise that resolves to the workspace ID */\n workspaceId: Promise<string>;\n}\n\nfunction getClientOptions(): ClientOptions {\n const isDev = process.env.NODE_ENV === \"development\";\n const semver = coerce(productVersion);\n const normalizedVersion = (semver?.version ??\n productVersion) as ClientOptions[\"productVersion\"];\n\n return {\n product: productName,\n productVersion: normalizedVersion,\n ...(isDev && { userAgentExtra: { mode: \"dev\" } }),\n };\n}\n\n/**\n * ServiceContext is a singleton that manages the service principal's\n * WorkspaceClient and shared resources like warehouse/workspace IDs.\n *\n * It's initialized once at app startup and provides the foundation\n * for both service principal and user context execution.\n */\nexport class ServiceContext {\n private static instance: ServiceContextState | null = null;\n private static initPromise: Promise<ServiceContextState> | null = null;\n\n /**\n * Initialize the service context. Should be called once at app startup.\n * Safe to call multiple times - will return the same instance.\n *\n * @param client - Optional pre-configured WorkspaceClient to use instead\n * of creating one from environment credentials.\n */\n static async initialize(\n client?: WorkspaceClient,\n ): Promise<ServiceContextState> {\n if (ServiceContext.instance) {\n return ServiceContext.instance;\n }\n\n if (ServiceContext.initPromise) {\n return ServiceContext.initPromise;\n }\n\n ServiceContext.initPromise = ServiceContext.createContext(client);\n ServiceContext.instance = await ServiceContext.initPromise;\n return ServiceContext.instance;\n }\n\n /**\n * Get the initialized service context.\n * @throws Error if not initialized\n */\n static get(): ServiceContextState {\n if (!ServiceContext.instance) {\n throw InitializationError.notInitialized(\n \"ServiceContext\",\n \"Call ServiceContext.initialize() first\",\n );\n }\n return ServiceContext.instance;\n }\n\n /**\n * Check if the service context has been initialized.\n */\n static isInitialized(): boolean {\n return ServiceContext.instance !== null;\n }\n\n /**\n * Create a user context from request headers.\n *\n * @param token - The user's access token from x-forwarded-access-token header\n * @param userId - The user's ID from x-forwarded-user header\n * @param userName - Optional user name\n * @throws Error if token is not provided\n */\n static createUserContext(\n token: string,\n userId: string,\n userName?: string,\n ): UserContext {\n if (!token) {\n throw AuthenticationError.missingToken(\"user token\");\n }\n\n const host = process.env.DATABRICKS_HOST;\n if (!host) {\n throw ConfigurationError.missingEnvVar(\"DATABRICKS_HOST\");\n }\n\n const serviceCtx = ServiceContext.get();\n\n // Create user client with the OAuth token from Databricks Apps\n // Note: We use authType: \"pat\" because the token is passed as a Bearer token\n // just like a PAT, even though it's technically an OAuth token\n const userClient = new WorkspaceClient(\n {\n token,\n host,\n authType: \"pat\",\n },\n getClientOptions(),\n );\n\n return {\n client: userClient,\n userId,\n userName,\n warehouseId: serviceCtx.warehouseId,\n workspaceId: serviceCtx.workspaceId,\n isUserContext: true,\n };\n }\n\n /**\n * Get the client options for WorkspaceClient.\n * Exposed for testing purposes.\n */\n static getClientOptions(): ClientOptions {\n return getClientOptions();\n }\n\n private static async createContext(\n client?: WorkspaceClient,\n ): Promise<ServiceContextState> {\n const wsClient = client ?? new WorkspaceClient({}, getClientOptions());\n\n const warehouseId = ServiceContext.getWarehouseId(wsClient);\n const workspaceId = ServiceContext.getWorkspaceId(wsClient);\n const currentUser = await wsClient.currentUser.me();\n\n if (!currentUser.id) {\n throw ConfigurationError.resourceNotFound(\"Service user ID\");\n }\n\n return {\n client: wsClient,\n serviceUserId: currentUser.id,\n warehouseId,\n workspaceId,\n };\n }\n\n private static async getWorkspaceId(\n client: WorkspaceClient,\n ): Promise<string> {\n if (process.env.DATABRICKS_WORKSPACE_ID) {\n return process.env.DATABRICKS_WORKSPACE_ID;\n }\n\n const response = (await client.apiClient.request({\n path: \"/api/2.0/preview/scim/v2/Me\",\n method: \"GET\",\n headers: new Headers(),\n raw: false,\n query: {},\n responseHeaders: [\"x-databricks-org-id\"],\n })) as { \"x-databricks-org-id\": string };\n\n if (!response[\"x-databricks-org-id\"]) {\n throw ConfigurationError.resourceNotFound(\"Workspace ID\");\n }\n\n return response[\"x-databricks-org-id\"];\n }\n\n private static async getWarehouseId(\n client: WorkspaceClient,\n ): Promise<string> {\n if (process.env.DATABRICKS_WAREHOUSE_ID) {\n return process.env.DATABRICKS_WAREHOUSE_ID;\n }\n\n if (process.env.NODE_ENV === \"development\") {\n const response = (await client.apiClient.request({\n path: \"/api/2.0/sql/warehouses\",\n method: \"GET\",\n headers: new Headers(),\n raw: false,\n query: {\n skip_cannot_use: \"true\",\n },\n })) as { warehouses: sql.EndpointInfo[] };\n\n const priorities: Record<sql.State, number> = {\n RUNNING: 0,\n STOPPED: 1,\n STARTING: 2,\n STOPPING: 3,\n DELETED: 99,\n DELETING: 99,\n };\n\n const warehouses = (response.warehouses || []).sort((a, b) => {\n return (\n priorities[a.state as sql.State] - priorities[b.state as sql.State]\n );\n });\n\n if (response.warehouses.length === 0) {\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"Please configure the DATABRICKS_WAREHOUSE_ID environment variable\",\n );\n }\n\n const firstWarehouse = warehouses[0];\n if (\n firstWarehouse.state === \"DELETED\" ||\n firstWarehouse.state === \"DELETING\" ||\n !firstWarehouse.id\n ) {\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"Please configure the DATABRICKS_WAREHOUSE_ID environment variable\",\n );\n }\n\n return firstWarehouse.id;\n }\n\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"Please configure the DATABRICKS_WAREHOUSE_ID environment variable\",\n );\n }\n\n /**\n * Reset the service context. Only for testing purposes.\n */\n static reset(): void {\n ServiceContext.instance = null;\n ServiceContext.initPromise = null;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"service-context.js","names":["productName","productVersion"],"sources":["../../src/context/service-context.ts"],"sourcesContent":["import {\n type ClientOptions,\n type sql,\n WorkspaceClient,\n} from \"@databricks/sdk-experimental\";\nimport { coerce } from \"semver\";\nimport {\n name as productName,\n version as productVersion,\n} from \"../../package.json\";\nimport {\n AuthenticationError,\n ConfigurationError,\n InitializationError,\n} from \"../errors\";\nimport type { UserContext } from \"./user-context\";\n\n/**\n * Service context holds the service principal client and shared resources.\n * This is initialized once at app startup and shared across all requests.\n */\nexport interface ServiceContextState {\n /** WorkspaceClient authenticated as the service principal */\n client: WorkspaceClient;\n /** The service principal's user ID */\n serviceUserId: string;\n /** Promise that resolves to the warehouse ID (only present when a plugin requires `SQL_WAREHOUSE` resource) */\n warehouseId?: Promise<string>;\n /** Promise that resolves to the workspace ID */\n workspaceId: Promise<string>;\n}\n\nfunction getClientOptions(): ClientOptions {\n const isDev = process.env.NODE_ENV === \"development\";\n const semver = coerce(productVersion);\n const normalizedVersion = (semver?.version ??\n productVersion) as ClientOptions[\"productVersion\"];\n\n return {\n product: productName,\n productVersion: normalizedVersion,\n ...(isDev && { userAgentExtra: { mode: \"dev\" } }),\n };\n}\n\n/**\n * ServiceContext is a singleton that manages the service principal's\n * WorkspaceClient and shared resources like warehouse/workspace IDs.\n *\n * It's initialized once at app startup and provides the foundation\n * for both service principal and user context execution.\n */\nexport class ServiceContext {\n private static instance: ServiceContextState | null = null;\n private static initPromise: Promise<ServiceContextState> | null = null;\n\n /**\n * Initialize the service context. Should be called once at app startup.\n * Safe to call multiple times - will return the same instance.\n *\n * @param options - Which shared resources to resolve (derived from plugin manifests).\n * @param client - Optional pre-configured WorkspaceClient to use instead\n * of creating one from environment credentials.\n */\n static async initialize(\n options?: { warehouseId?: boolean },\n client?: WorkspaceClient,\n ): Promise<ServiceContextState> {\n if (ServiceContext.instance) {\n return ServiceContext.instance;\n }\n\n if (ServiceContext.initPromise) {\n return ServiceContext.initPromise;\n }\n\n ServiceContext.initPromise = ServiceContext.createContext(options, client);\n ServiceContext.instance = await ServiceContext.initPromise;\n return ServiceContext.instance;\n }\n\n /**\n * Get the initialized service context.\n * @throws Error if not initialized\n */\n static get(): ServiceContextState {\n if (!ServiceContext.instance) {\n throw InitializationError.notInitialized(\n \"ServiceContext\",\n \"Call ServiceContext.initialize() first\",\n );\n }\n return ServiceContext.instance;\n }\n\n /**\n * Check if the service context has been initialized.\n */\n static isInitialized(): boolean {\n return ServiceContext.instance !== null;\n }\n\n /**\n * Create a user context from request headers.\n *\n * @param token - The user's access token from x-forwarded-access-token header\n * @param userId - The user's ID from x-forwarded-user header\n * @param userName - Optional user name\n * @throws Error if token is not provided\n */\n static createUserContext(\n token: string,\n userId: string,\n userName?: string,\n ): UserContext {\n if (!token) {\n throw AuthenticationError.missingToken(\"user token\");\n }\n\n const host = process.env.DATABRICKS_HOST;\n if (!host) {\n throw ConfigurationError.missingEnvVar(\"DATABRICKS_HOST\");\n }\n\n const serviceCtx = ServiceContext.get();\n\n // Create user client with the OAuth token from Databricks Apps\n // Note: We use authType: \"pat\" because the token is passed as a Bearer token\n // just like a PAT, even though it's technically an OAuth token\n const userClient = new WorkspaceClient(\n {\n token,\n host,\n authType: \"pat\",\n },\n getClientOptions(),\n );\n\n return {\n client: userClient,\n userId,\n userName,\n warehouseId: serviceCtx.warehouseId,\n workspaceId: serviceCtx.workspaceId,\n isUserContext: true,\n };\n }\n\n /**\n * Get the client options for WorkspaceClient.\n * Exposed for testing purposes.\n */\n static getClientOptions(): ClientOptions {\n return getClientOptions();\n }\n\n private static async createContext(\n options?: { warehouseId?: boolean },\n client?: WorkspaceClient,\n ): Promise<ServiceContextState> {\n const wsClient = client ?? new WorkspaceClient({}, getClientOptions());\n\n const warehouseId = options?.warehouseId\n ? ServiceContext.getWarehouseId(wsClient)\n : undefined;\n\n const workspaceId = ServiceContext.getWorkspaceId(wsClient);\n const currentUser = await wsClient.currentUser.me();\n\n if (!currentUser.id) {\n throw ConfigurationError.resourceNotFound(\"Service user ID\");\n }\n\n return {\n client: wsClient,\n serviceUserId: currentUser.id,\n warehouseId,\n workspaceId,\n };\n }\n\n private static async getWorkspaceId(\n client: WorkspaceClient,\n ): Promise<string> {\n if (process.env.DATABRICKS_WORKSPACE_ID) {\n return process.env.DATABRICKS_WORKSPACE_ID;\n }\n\n const response = (await client.apiClient.request({\n path: \"/api/2.0/preview/scim/v2/Me\",\n method: \"GET\",\n headers: new Headers(),\n raw: false,\n query: {},\n responseHeaders: [\"x-databricks-org-id\"],\n })) as { \"x-databricks-org-id\": string };\n\n if (!response[\"x-databricks-org-id\"]) {\n throw ConfigurationError.resourceNotFound(\"Workspace ID\");\n }\n\n return response[\"x-databricks-org-id\"];\n }\n\n private static async getWarehouseId(\n client: WorkspaceClient,\n ): Promise<string> {\n if (process.env.DATABRICKS_WAREHOUSE_ID) {\n return process.env.DATABRICKS_WAREHOUSE_ID;\n }\n\n if (process.env.NODE_ENV === \"development\") {\n const response = (await client.apiClient.request({\n path: \"/api/2.0/sql/warehouses\",\n method: \"GET\",\n headers: new Headers(),\n raw: false,\n query: {\n skip_cannot_use: \"true\",\n },\n })) as { warehouses: sql.EndpointInfo[] };\n\n const priorities: Record<sql.State, number> = {\n RUNNING: 0,\n STOPPED: 1,\n STARTING: 2,\n STOPPING: 3,\n DELETED: 99,\n DELETING: 99,\n };\n\n const warehouses = (response.warehouses || []).sort((a, b) => {\n return (\n priorities[a.state as sql.State] - priorities[b.state as sql.State]\n );\n });\n\n if (response.warehouses.length === 0) {\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"Please configure the DATABRICKS_WAREHOUSE_ID environment variable\",\n );\n }\n\n const firstWarehouse = warehouses[0];\n if (\n firstWarehouse.state === \"DELETED\" ||\n firstWarehouse.state === \"DELETING\" ||\n !firstWarehouse.id\n ) {\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"Please configure the DATABRICKS_WAREHOUSE_ID environment variable\",\n );\n }\n\n return firstWarehouse.id;\n }\n\n throw ConfigurationError.resourceNotFound(\n \"Warehouse ID\",\n \"Please configure the DATABRICKS_WAREHOUSE_ID environment variable\",\n );\n }\n\n /**\n * Reset the service context. Only for testing purposes.\n */\n static reset(): void {\n ServiceContext.instance = null;\n ServiceContext.initPromise = null;\n }\n}\n"],"mappings":"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"}
@@ -13,8 +13,8 @@ interface UserContext {
13
13
  userId: string;
14
14
  /** The user's name (from request headers) */
15
15
  userName?: string;
16
- /** Promise that resolves to the warehouse ID (inherited from service context) */
17
- warehouseId: Promise<string>;
16
+ /** Promise that resolves to the warehouse ID (inherited from service context, only present when a plugin requires `SQL_WAREHOUSE` resource) */
17
+ warehouseId?: Promise<string>;
18
18
  /** Promise that resolves to the workspace ID (inherited from service context) */
19
19
  workspaceId: Promise<string>;
20
20
  /** Flag indicating this is a user context */
@@ -1 +1 @@
1
- {"version":3,"file":"user-context.d.ts","names":[],"sources":["../../src/context/user-context.ts"],"sourcesContent":[],"mappings":";;;;;;AAMA;;AAEU,UAFO,WAAA,CAEP;;QAQK,EARL,mBAQK,CAAA,QAAA,CAAA;EAAO;EAQV,MAAA,EAAA,MAAA;EAAgB;UAAG,CAAA,EAAA,MAAA;;EAAiC,WAAA,EAVjD,OAUiD,CAAA,MAAA,CAAA;;eARjD;;;;;;;KAQH,gBAAA,GAAmB,sBAAsB"}
1
+ {"version":3,"file":"user-context.d.ts","names":[],"sources":["../../src/context/user-context.ts"],"sourcesContent":[],"mappings":";;;;;;AAMA;;AAEU,UAFO,WAAA,CAEP;;QAQK,EARL,mBAQK,CAAA,QAAA,CAAA;EAAO;EAQV,MAAA,EAAA,MAAA;EAAgB;UAAG,CAAA,EAAA,MAAA;;EAAiC,WAAA,CAAA,EAVhD,OAUgD,CAAA,MAAA,CAAA;;eARjD;;;;;;;KAQH,gBAAA,GAAmB,sBAAsB"}
@@ -1 +1 @@
1
- {"version":3,"file":"user-context.js","names":[],"sources":["../../src/context/user-context.ts"],"sourcesContent":["import type { ServiceContextState } from \"./service-context\";\n\n/**\n * User execution context extends the service context with user-specific data.\n * Created on-demand when asUser(req) is called.\n */\nexport interface UserContext {\n /** WorkspaceClient authenticated as the user */\n client: ServiceContextState[\"client\"];\n /** The user's ID (from request headers) */\n userId: string;\n /** The user's name (from request headers) */\n userName?: string;\n /** Promise that resolves to the warehouse ID (inherited from service context) */\n warehouseId: Promise<string>;\n /** Promise that resolves to the workspace ID (inherited from service context) */\n workspaceId: Promise<string>;\n /** Flag indicating this is a user context */\n isUserContext: true;\n}\n\n/**\n * Execution context can be either service or user context.\n */\nexport type ExecutionContext = ServiceContextState | UserContext;\n\n/**\n * Check if an execution context is a user context.\n */\nexport function isUserContext(ctx: ExecutionContext): ctx is UserContext {\n return \"isUserContext\" in ctx && ctx.isUserContext === true;\n}\n"],"mappings":";;;;;;AA6BA,SAAgB,cAAc,KAA2C;AACvE,QAAO,mBAAmB,OAAO,IAAI,kBAAkB"}
1
+ {"version":3,"file":"user-context.js","names":[],"sources":["../../src/context/user-context.ts"],"sourcesContent":["import type { ServiceContextState } from \"./service-context\";\n\n/**\n * User execution context extends the service context with user-specific data.\n * Created on-demand when asUser(req) is called.\n */\nexport interface UserContext {\n /** WorkspaceClient authenticated as the user */\n client: ServiceContextState[\"client\"];\n /** The user's ID (from request headers) */\n userId: string;\n /** The user's name (from request headers) */\n userName?: string;\n /** Promise that resolves to the warehouse ID (inherited from service context, only present when a plugin requires `SQL_WAREHOUSE` resource) */\n warehouseId?: Promise<string>;\n /** Promise that resolves to the workspace ID (inherited from service context) */\n workspaceId: Promise<string>;\n /** Flag indicating this is a user context */\n isUserContext: true;\n}\n\n/**\n * Execution context can be either service or user context.\n */\nexport type ExecutionContext = ServiceContextState | UserContext;\n\n/**\n * Check if an execution context is a user context.\n */\nexport function isUserContext(ctx: ExecutionContext): ctx is UserContext {\n return \"isUserContext\" in ctx && ctx.isUserContext === true;\n}\n"],"mappings":";;;;;;AA6BA,SAAgB,cAAc,KAA2C;AACvE,QAAO,mBAAmB,OAAO,IAAI,kBAAkB"}
@@ -1 +1 @@
1
- {"version":3,"file":"appkit.d.ts","names":[],"sources":["../../src/core/appkit.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;iBA6LsB,oBACV,WAAW;YAGT;cACE;UACJ;WACC;IAEV,QAAQ,UAAU"}
1
+ {"version":3,"file":"appkit.d.ts","names":[],"sources":["../../src/core/appkit.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;iBAqMsB,oBACV,WAAW;YAGT;cACE;UACJ;WACC;IAEV,QAAQ,UAAU"}
@@ -3,6 +3,7 @@ import "../telemetry/index.js";
3
3
  import { CacheManager } from "../cache/index.js";
4
4
  import { ServiceContext } from "../context/service-context.js";
5
5
  import { init_context } from "../context/index.js";
6
+ import { ResourceType } from "../registry/types.generated.js";
6
7
  import { ResourceRegistry } from "../registry/resource-registry.js";
7
8
  import "../registry/index.js";
8
9
 
@@ -70,10 +71,11 @@ var AppKit = class AppKit {
70
71
  static async _createApp(config = {}) {
71
72
  TelemetryManager.initialize(config?.telemetry);
72
73
  await CacheManager.getInstance(config?.cache);
73
- await ServiceContext.initialize(config?.client);
74
74
  const rawPlugins = config.plugins;
75
75
  const registry = new ResourceRegistry();
76
76
  registry.collectResources(rawPlugins);
77
+ const needsWarehouse = registry.getRequired().some((r) => r.type === ResourceType.SQL_WAREHOUSE);
78
+ await ServiceContext.initialize({ warehouseId: needsWarehouse }, config?.client);
77
79
  registry.enforceValidation();
78
80
  const instance = new AppKit({ plugins: AppKit.preparePlugins(rawPlugins) });
79
81
  await Promise.all(instance.#setupPromises);